From 05ca1919687662136182748988c281e307b4acb4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 Aug 2025 22:49:53 +0200 Subject: [PATCH] Complete Tachikoma Swift Package with logo header - Add complete Swift package implementation from vendored version - All providers: OpenAI, Anthropic, Grok, Ollama with type separation - Swift 6 strict concurrency support - Comprehensive test suite - Complete documentation (README, ARCHITECTURE, CHANGELOG) - Add centered logo header to README for GitHub presentation - Unified API for AI model integration - Tool calling, streaming, and multimodal support --- .DS_Store | Bin 0 -> 6148 bytes ARCHITECTURE.md | 476 ++++++++++++ CHANGELOG.md | 173 +++++ Package.resolved | 15 + Package.swift | 65 ++ README.md | 431 ++++++++++- Sources/Core/MessageTypes.swift | 366 ++++++++++ Sources/Core/ModelInterface.swift | 424 +++++++++++ Sources/Core/ModelParameters.swift | 240 +++++++ Sources/Core/ModelProvider.swift | 650 +++++++++++++++++ Sources/Core/StreamingTypes.swift | 381 ++++++++++ Sources/Core/TachikomaError.swift | 202 ++++++ Sources/Core/ToolDefinitions.swift | 597 +++++++++++++++ .../Providers/Anthropic/AnthropicModel.swift | 642 +++++++++++++++++ .../Providers/Anthropic/AnthropicTypes.swift | 678 ++++++++++++++++++ Sources/Providers/Grok/GrokModel.swift | 479 +++++++++++++ Sources/Providers/Grok/GrokTypes.swift | 346 +++++++++ Sources/Providers/Ollama/OllamaModel.swift | 336 +++++++++ Sources/Providers/Ollama/OllamaTypes.swift | 69 ++ Sources/Providers/OpenAI/OpenAIModel.swift | 516 +++++++++++++ Sources/Providers/OpenAI/OpenAITypes.swift | 481 +++++++++++++ Sources/Tachikoma.swift | 87 +++ .../TachikomaTests/AnthropicModelTests.swift | 373 ++++++++++ Tests/TachikomaTests/GrokModelTests.swift | 331 +++++++++ Tests/TachikomaTests/OllamaModelTests.swift | 611 ++++++++++++++++ Tests/TachikomaTests/OpenAIModelTests.swift | 475 ++++++++++++ .../StreamingAndToolTests.swift | 439 ++++++++++++ Tests/TachikomaTests/TachikomaCoreTests.swift | 507 +++++++++++++ assets/logo.png | Bin 0 -> 341825 bytes 29 files changed, 10388 insertions(+), 2 deletions(-) create mode 100644 .DS_Store create mode 100644 ARCHITECTURE.md create mode 100644 CHANGELOG.md create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 Sources/Core/MessageTypes.swift create mode 100644 Sources/Core/ModelInterface.swift create mode 100644 Sources/Core/ModelParameters.swift create mode 100644 Sources/Core/ModelProvider.swift create mode 100644 Sources/Core/StreamingTypes.swift create mode 100644 Sources/Core/TachikomaError.swift create mode 100644 Sources/Core/ToolDefinitions.swift create mode 100644 Sources/Providers/Anthropic/AnthropicModel.swift create mode 100644 Sources/Providers/Anthropic/AnthropicTypes.swift create mode 100644 Sources/Providers/Grok/GrokModel.swift create mode 100644 Sources/Providers/Grok/GrokTypes.swift create mode 100644 Sources/Providers/Ollama/OllamaModel.swift create mode 100644 Sources/Providers/Ollama/OllamaTypes.swift create mode 100644 Sources/Providers/OpenAI/OpenAIModel.swift create mode 100644 Sources/Providers/OpenAI/OpenAITypes.swift create mode 100644 Sources/Tachikoma.swift create mode 100644 Tests/TachikomaTests/AnthropicModelTests.swift create mode 100644 Tests/TachikomaTests/GrokModelTests.swift create mode 100644 Tests/TachikomaTests/OllamaModelTests.swift create mode 100644 Tests/TachikomaTests/OpenAIModelTests.swift create mode 100644 Tests/TachikomaTests/StreamingAndToolTests.swift create mode 100644 Tests/TachikomaTests/TachikomaCoreTests.swift create mode 100644 assets/logo.png diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ff30e0c302c8b3f6585895c75e1d9250bab482eb GIT binary patch literal 6148 zcmeHKOHKko5PgkAK{qVj5a$AtaDxGd1&O+l4NCA(>OckkmJ@x6HeltvW190u9Q4^>Es89t`)Q&+qkPJvPm=Y%s|O^i}HB<#Ye+y%ontzs6?|Yww1>IfuBzoWFZ4 zFptM(&p;GkAOG{b{u0LD$oam9T{+Qx_+fB_OLTF84zAHdr#s1qVW#r8kXbgCUVM5L zH_U7y!xgcCDMlL=ZyOc9%#1N$3>X7@!~oZ9k;;jpS!2K$Fa}l($oCb z;?v<$MYG0$F%UCwpqDjS|2w ModelResponse + + /// Get streaming response + func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream +} +``` + +**Key Design Decisions:** +- `Sendable` conformance ensures thread safety +- Async/await for all network operations +- Streaming uses `AsyncThrowingStream` for memory efficiency +- Availability annotations ensure compatibility + +### Message Type System + +Hierarchical message types that handle all AI interaction patterns: + +```swift +public enum Message: Codable, Sendable { + case system(id: String? = nil, content: String) + case user(id: String? = nil, content: MessageContent) + case assistant(id: String? = nil, content: [AssistantContent], status: MessageStatus = .completed) + case tool(id: String? = nil, toolCallId: String, content: String) + case reasoning(id: String? = nil, content: String) +} +``` + +**Content Type Hierarchy:** +- `MessageContent`: Text, images, multimodal, files, audio +- `AssistantContent`: Text output, refusals, tool calls +- `ImageContent`: URLs, base64 data, detail levels +- `AudioContent`: Transcripts, durations, metadata + +**Benefits:** +- Type safety prevents invalid message construction +- Codable conformance for persistence +- Sendable for concurrency safety +- Extensible for new content types + +### Streaming System + +Real-time event processing with comprehensive event types: + +```swift +public enum StreamEvent { + case responseStarted(StreamResponseStarted) + case textDelta(StreamTextDelta) + case toolCallDelta(StreamToolCallDelta) + case toolCallCompleted(StreamToolCallCompleted) + case responseCompleted(StreamResponseCompleted) + case error(StreamError) +} +``` + +**Stream Processing Flow:** +1. `responseStarted` - Metadata and initialization +2. `textDelta` - Incremental text content +3. `toolCallDelta` - Incremental tool call construction +4. `toolCallCompleted` - Complete tool call available +5. `responseCompleted` - Stream finished with final metadata +6. `error` - Error events for handling failures + +**Implementation Details:** +- Each provider converts its streaming format to unified events +- Back-pressure handling through `AsyncThrowingStream` +- Automatic event ordering and consistency validation + +### Tool Calling System + +Generic, type-safe tool execution with context support: + +```swift +public struct Tool { + public let execute: (ToolInput, Context) async throws -> ToolOutput + + public func toToolDefinition() -> ToolDefinition { + // Convert to provider-agnostic definition + } +} +``` + +**Type Safety Features:** +- Generic context ensures compile-time type checking +- Parameter validation through JSON Schema +- Async execution for I/O operations +- Error handling with structured failures + +**Tool Definition System:** +```swift +public struct ToolDefinition { + public let function: FunctionDefinition + public let type: ToolType = .function +} + +public struct FunctionDefinition { + public let name: String + public let description: String? + public let parameters: ToolParameters +} +``` + +### Error Handling + +Comprehensive error system with recovery guidance: + +```swift +public enum TachikomaError: Error, LocalizedError { + case modelNotFound(String) + case authenticationFailed + case invalidConfiguration(String) + case networkError(underlying: any Error) + case rateLimited + case insufficientQuota + case contextLengthExceeded + // ... more cases + + public var isRetryable: Bool { /* logic */ } + public var recoverySuggestion: String? { /* guidance */ } +} +``` + +**Error Categories:** +- **Client Errors**: Invalid requests, configuration issues +- **Authentication Errors**: API key problems, quota issues +- **Network Errors**: Connectivity, timeouts, server errors +- **Provider Errors**: Model-specific limitations + +## Provider Implementations + +### OpenAI Provider + +**Dual API Support:** +- Chat Completions API (`/v1/chat/completions`) for standard models +- Responses API (`/v1/responses`) for o3/o4 reasoning models + +**Key Features:** +- Automatic API selection based on model capabilities +- Parameter filtering (o3/o4 models don't support temperature) +- Reasoning summary handling for thinking models +- Complete streaming support for both APIs + +**Implementation Highlights:** +```swift +private func convertToOpenAIRequest(_ request: ModelRequest, stream: Bool) throws -> OpenAIRequest { + // Convert unified request to OpenAI format + // Handle parameter filtering + // Support both API formats +} +``` + +### Anthropic Provider + +**Native Claude Integration:** +- Direct Claude API with proper message formatting +- Content blocks for multimodal inputs +- System prompt separation +- Tool result handling as user messages + +**Streaming Implementation:** +- Server-Sent Events (SSE) processing +- Delta accumulation for tool calls +- Proper handling of Claude's content block structure + +**Claude 4 Features:** +- Extended thinking modes +- Long-running task support +- Advanced reasoning capabilities + +### Grok Provider + +**OpenAI Compatibility:** +- Uses OpenAI-compatible Chat Completions API +- Parameter filtering for Grok 3/4 models +- Standard streaming implementation + +**Optimizations:** +- Efficient parameter encoding +- Proper error response handling +- Rate limiting awareness + +### Ollama Provider + +**Local Inference:** +- HTTP API for local models +- Custom timeout handling (5 minutes for model loading) +- Tool calling detection for compatible models + +**Model Support:** +- Language models: llama3.3, mistral, etc. +- Vision models: llava, bakllava (no tool calling) +- Custom model endpoints + +## Provider Registry & Management + +### ModelProvider (Actor) + +Central registry for all model factories and instances: + +```swift +@MainActor +public final class ModelProvider { + public static let shared = ModelProvider() + + private var modelFactories: [String: @Sendable () throws -> any ModelInterface] = [:] + private var modelCache: [String: any ModelInterface] = [:] + + public func getModel(_ modelName: String) async throws -> any ModelInterface + public func register(modelName: String, factory: @escaping @Sendable () throws -> any ModelInterface) +} +``` + +**Registration System:** +- Default model registration at startup +- Custom factory registration +- Lenient name matching (e.g., "gpt" → "gpt-4.1") +- Provider/model path resolution ("openai/gpt-4") + +**Caching Strategy:** +- Model instances cached after first creation +- Cache invalidation on registration changes +- Memory-efficient with weak references where appropriate + +## Concurrency & Threading + +### Actor Usage + +**ModelProvider as MainActor:** +- Centralizes model management +- Ensures thread-safe registration +- Coordinates provider initialization + +**Sendable Conformance:** +- All message types are Sendable +- Model instances are Sendable +- Error types are Sendable +- Tool definitions are Sendable + +**Async/Await Integration:** +- All network operations are async +- Streaming uses AsyncThrowingStream +- No blocking operations on main thread + +### Memory Management + +**Streaming Efficiency:** +- Events processed incrementally +- No accumulation of entire responses +- Automatic memory cleanup + +**Cache Management:** +- Model instances cached intelligently +- Configurable cache policies +- Weak references for large objects + +## Security Considerations + +### API Key Handling + +**Environment Variables:** +- Support for multiple key formats +- Secure key storage recommendations +- Masked keys in debug output + +**Key Security:** +- Never log full API keys +- Secure transmission only +- No persistence of keys in plain text + +### Input Validation + +**Parameter Validation:** +- Type-safe parameter construction +- Range validation for numeric parameters +- Required field enforcement + +**Content Filtering:** +- Provider-specific content policies +- Error handling for filtered content +- Transparent policy communication + +## Performance Characteristics + +### Network Efficiency + +**Connection Management:** +- URLSession with appropriate timeouts +- HTTP/2 support where available +- Connection pooling + +**Request Optimization:** +- Minimal payload size +- Efficient JSON encoding +- Compression support + +### Memory Usage + +**Streaming Responses:** +- Constant memory usage regardless of response size +- Incremental processing +- Automatic garbage collection + +**Object Creation:** +- Minimal allocations in hot paths +- Reuse of formatter objects +- Efficient string handling + +## Testing Strategy + +### Unit Tests + +**Provider Tests:** +- Mock network responses +- Error condition testing +- Parameter validation tests + +**Integration Tests:** +- End-to-end flow testing +- Streaming behavior validation +- Tool calling integration + +**Performance Tests:** +- Memory usage profiling +- Response time benchmarks +- Concurrent request handling + +## Extension Points + +### Custom Providers + +Implement `ModelInterface` to add new providers: + +```swift +class CustomProvider: ModelInterface { + var maskedApiKey: String { "custom-***" } + + func getResponse(request: ModelRequest) async throws -> ModelResponse { + // Custom implementation + } + + func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + // Custom streaming + } +} +``` + +### Message Type Extensions + +Add new content types by extending `MessageContent`: + +```swift +extension MessageContent { + case customType(CustomData) +} +``` + +### Tool System Extensions + +Create specialized tool contexts: + +```swift +struct DatabaseContext { + let connection: DatabaseConnection + let schema: Schema +} + +let dbTool = Tool { input, context in + // Database operations with type-safe context +} +``` + +## Future Considerations + +### Planned Features + +**Enhanced Caching:** +- Persistent cache with TTL +- Smart cache invalidation +- Distributed caching support + +**Advanced Streaming:** +- Bidirectional streaming +- Stream multiplexing +- Custom event types + +**Provider Enhancements:** +- More granular configuration +- Provider-specific optimizations +- Enhanced error recovery + +### Scalability + +**High-Volume Usage:** +- Connection pooling improvements +- Request batching +- Rate limiting integration + +**Enterprise Features:** +- Audit logging +- Metrics collection +- Custom authentication + +--- + +This architecture provides a solid foundation for AI integration while maintaining flexibility for future enhancements and provider additions. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d05c911 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,173 @@ +# Changelog + +All notable changes to the Tachikoma project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2025-01-XX + +### Added + +#### Core Framework +- Initial release of Tachikoma AI integration library +- Unified `ModelInterface` protocol for all AI providers +- Comprehensive message type system with multimodal support +- Real-time streaming response handling with `AsyncThrowingStream` +- Type-safe tool calling system with generic context support +- Actor-based provider registry with intelligent caching +- Swift 6 strict concurrency compliance throughout + +#### Provider Support +- **OpenAI Provider**: Complete integration with dual API support + - Chat Completions API for standard models (GPT-4o, GPT-4.1) + - Responses API for reasoning models (o3, o4 series) + - Automatic API selection based on model capabilities + - Parameter filtering for reasoning models + - Full streaming support for both APIs + - Reasoning summary handling for thinking models + +- **Anthropic Provider**: Native Claude API integration + - Support for Claude 4 (Opus, Sonnet) with thinking modes + - Claude 3.5/3.7 series compatibility + - Content block handling for multimodal inputs + - System prompt separation + - Server-Sent Events streaming + - Extended reasoning capabilities + +- **Grok Provider**: xAI integration with OpenAI compatibility + - Grok 4, Grok 3, Grok 2 series support + - Vision model capabilities + - Parameter filtering for Grok 3/4 models + - Standard streaming implementation + - OpenAI-compatible Chat Completions API + +- **Ollama Provider**: Local model inference support + - Support for Llama 3.3 (recommended), Mistral, CodeLlama + - Vision models (llava, bakllava) without tool calling + - Configurable endpoints for self-hosted deployments + - Extended timeouts for local model loading + - Tool calling detection for compatible models + +#### Message System +- **Unified Message Types**: Support for system, user, assistant, tool, and reasoning messages +- **Content Types**: Text, images (URL/base64), multimodal, files, audio with transcripts +- **Assistant Content**: Text output, refusals, tool calls with proper typing +- **Image Support**: High/low detail levels, multiple formats, base64 encoding +- **Audio Support**: Transcript extraction, duration metadata + +#### Streaming System +- **Event-Based Architecture**: Comprehensive streaming event types +- **Real-Time Processing**: Incremental text deltas, tool call construction +- **Memory Efficiency**: Constant memory usage regardless of response size +- **Error Handling**: Structured error events with recovery information +- **Provider Abstraction**: Unified events across different provider formats + +#### Tool Calling +- **Generic Tool System**: Type-safe tool execution with context support +- **Parameter Validation**: JSON Schema-based parameter validation +- **Async Execution**: Non-blocking tool execution with proper error handling +- **Tool Definitions**: Provider-agnostic tool definition format +- **Context Management**: Type-safe context passing to tool functions + +#### Error Handling +- **Comprehensive Error Types**: Structured error hierarchy with recovery guidance +- **Provider-Specific Errors**: Tailored error handling for each provider +- **Retry Logic**: Built-in retry detection with exponential backoff support +- **Error Categories**: Client, authentication, network, and provider errors +- **Localized Descriptions**: User-friendly error messages with recovery suggestions + +#### Configuration System +- **Environment Variables**: Support for standard API key environment variables +- **Provider Configuration**: Flexible configuration for custom endpoints +- **Model Registration**: Runtime model factory registration +- **Lenient Matching**: Intelligent model name resolution +- **Cache Management**: Configurable caching policies + +### Technical Features + +#### Swift 6 Compliance +- **Strict Concurrency**: Full Swift 6 strict concurrency mode compliance +- **Sendable Conformance**: All public types conform to Sendable protocol +- **Actor Safety**: Thread-safe operations with proper isolation +- **Memory Safety**: No data races or concurrency issues +- **Performance**: Optimized for concurrent execution + +#### Performance Optimizations +- **Intelligent Caching**: Model instance caching with smart invalidation +- **Connection Pooling**: Efficient network connection management +- **Memory Management**: Minimal allocations and efficient garbage collection +- **Streaming Efficiency**: Incremental processing without accumulation +- **JSON Optimization**: Fast encoding/decoding without reflection + +#### Type Safety +- **Compile-Time Verification**: Strong typing throughout the API +- **Generic Constraints**: Type-safe tool contexts and parameters +- **Enum-Based Design**: Exhaustive pattern matching for robustness +- **Protocol-Oriented**: Clean abstractions with concrete implementations + +### Documentation +- Comprehensive README with quick start guide +- Detailed architecture documentation +- API reference documentation +- Code examples for common usage patterns +- Migration guide from PeekabooCore +- Performance optimization guidelines + +### Testing +- Unit tests for all core components +- Integration tests for provider functionality +- Mock providers for testing scenarios +- Performance benchmarks +- Concurrency safety tests + +### Platform Support +- macOS 14.0+ +- iOS 17.0+ +- watchOS 10.0+ +- tvOS 17.0+ +- Swift 6.0+ +- Xcode 16.0+ + +## [Unreleased] + +### Planned Features +- Enhanced caching with persistence and TTL +- Bidirectional streaming support +- Request batching for high-volume usage +- Advanced error recovery mechanisms +- Metrics collection and monitoring +- Distributed caching support + +--- + +## Version History + +- **v1.0.0**: Initial release extracted from PeekabooCore with Swift 6 compliance +- **v0.x.x**: Development versions (internal) + +## Migration Notes + +### From PeekabooCore +When migrating from PeekabooCore's AI system: + +1. **Error Types**: Replace `PeekabooError` with `TachikomaError` +2. **Import Statements**: Update to `import Tachikoma` +3. **Model Creation**: Use `Tachikoma.shared.getModel()` instead of direct instantiation +4. **Streaming Events**: Update event handling for new event type hierarchy +5. **Message Types**: Adopt new unified message type system +6. **Tool Calling**: Update to generic tool system with context support + +### Breaking Changes +This is the initial release, so no breaking changes from previous versions. + +## Contributors + +- **Extraction Lead**: AI Assistant +- **Original Code**: Peekaboo project contributors +- **Architecture Design**: Based on proven patterns from PeekabooCore +- **Swift 6 Migration**: Complete rewrite for strict concurrency compliance + +## License + +This project is licensed under the MIT License. See LICENSE file for details. \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..f2401e3 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "3929c9b9fd81518c26df5464b3e6a459042d74460f8bfd7fb79e21dc5c507bdf", + "pins" : [ + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", + "version" : "1.6.4" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..e820da2 --- /dev/null +++ b/Package.swift @@ -0,0 +1,65 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Tachikoma", + platforms: [ + .macOS(.v14), + .iOS(.v17), + .watchOS(.v10), + .tvOS(.v17) + ], + products: [ + .library( + name: "Tachikoma", + targets: ["Tachikoma"] + ), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), + ], + targets: [ + .target( + name: "Tachikoma", + dependencies: [ + .product(name: "Logging", package: "swift-log"), + ], + path: "Sources", + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("BareSlashRegexLiterals"), + .enableUpcomingFeature("ConciseMagicFile"), + .enableUpcomingFeature("ForwardTrailingClosures"), + .enableUpcomingFeature("ImportObjcForwardDeclarations"), + .enableUpcomingFeature("DisableOutwardActorInference"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("DeprecateApplicationMain"), + .enableUpcomingFeature("GlobalConcurrency"), + .enableUpcomingFeature("IsolatedDefaultValues"), + .enableUpcomingFeature("InternalImportsByDefault"), + ] + ), + .testTarget( + name: "TachikomaTests", + dependencies: [ + "Tachikoma", + .product(name: "Logging", package: "swift-log"), + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("BareSlashRegexLiterals"), + .enableUpcomingFeature("ConciseMagicFile"), + .enableUpcomingFeature("ForwardTrailingClosures"), + .enableUpcomingFeature("ImportObjcForwardDeclarations"), + .enableUpcomingFeature("DisableOutwardActorInference"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("DeprecateApplicationMain"), + .enableUpcomingFeature("GlobalConcurrency"), + .enableUpcomingFeature("IsolatedDefaultValues"), + .enableUpcomingFeature("InternalImportsByDefault"), + ] + ), + ] +) \ No newline at end of file diff --git a/README.md b/README.md index 14aa939..76b6d7a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,429 @@ -# Tachikoma -One interface, every AI model. A Swift SDK to interface with AI providers. +
+ Tachikoma Logo +

Tachikoma

+
+ +A comprehensive Swift package for AI model integration, providing a unified interface for multiple AI providers including OpenAI, Anthropic, Grok (xAI), and Ollama. + +Named after the spider-tank AI from Ghost in the Shell, Tachikoma provides an intelligent, adaptable interface for AI services. + +## Features + +- **Unified API**: Single interface for multiple AI providers +- **Swift 6 Compliant**: Built with Swift 6 strict concurrency mode for maximum safety +- **Streaming Support**: Real-time streaming responses for all supported providers +- **Tool Calling**: Complete function calling support for AI agent workflows +- **Multimodal**: Support for text, images, audio, and file inputs +- **Type Safety**: Strongly-typed message handling and error management +- **Performance**: Optimized for efficiency with intelligent caching and resource management + +## Supported Providers + +### OpenAI +- **Models**: GPT-4o, GPT-4.1, o3, o4 series with full parameter support +- **Features**: Chat Completions API, Responses API, streaming, tool calling, multimodal +- **API Types**: Automatic selection between Chat Completions and Responses APIs + +### Anthropic (Claude) +- **Models**: Claude 4 (Opus, Sonnet), Claude 3.5/3.7 series with thinking modes +- **Features**: Native streaming, tool calling, multimodal, extended reasoning +- **Capabilities**: Long-running tasks, system prompts, safety filtering + +### Grok (xAI) +- **Models**: Grok 4, Grok 3, Grok 2 series with vision capabilities +- **Features**: OpenAI-compatible API, streaming, tool calling, parameter filtering +- **Performance**: High-speed inference with competitive pricing + +### Ollama +- **Models**: Llama 3.3 (recommended), Mistral, CodeLlama, and vision models +- **Features**: Local inference, tool calling (select models), streaming +- **Deployment**: Self-hosted with configurable endpoints + +## Installation + +### Swift Package Manager + +Add Tachikoma as a dependency in your `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/steipete/Tachikoma", from: "1.0.0") +] +``` + +### Requirements + +- macOS 14.0+, iOS 17.0+, watchOS 10.0+, tvOS 17.0+ +- Swift 6.0+ +- Xcode 16.0+ + +## Quick Start + +### Basic Usage + +```swift +import Tachikoma + +// Get a model instance +let tachikoma = Tachikoma.shared +let model = try await tachikoma.getModel("claude-opus-4") + +// Create a simple request +let request = ModelRequest( + messages: [ + .user(content: .text("What is the capital of France?")) + ] +) + +// Get response +let response = try await model.getResponse(request: request) +print(response.content.first?.text ?? "No response") +``` + +### Streaming Responses + +```swift +let request = ModelRequest( + messages: [.user(content: .text("Write a story about AI"))], + settings: ModelSettings(temperature: 0.7) +) + +for try await event in try await model.getStreamedResponse(request: request) { + switch event { + case .textDelta(let delta): + print(delta.delta, terminator: "") + case .responseCompleted: + print("\n[Stream completed]") + default: + break + } +} +``` + +### Tool Calling + +```swift +// Define a tool +let weatherTool = ToolDefinition( + function: FunctionDefinition( + name: "get_weather", + description: "Get current weather for a location", + parameters: ToolParameters( + type: "object", + properties: [ + "location": ParameterSchema( + type: .string, + description: "City name" + ) + ], + required: ["location"] + ) + ) +) + +let request = ModelRequest( + messages: [.user(content: .text("What's the weather in Tokyo?"))], + tools: [weatherTool], + settings: ModelSettings(toolChoice: .auto) +) + +let response = try await model.getResponse(request: request) +// Handle tool calls in response.content +``` + +### Multimodal Inputs + +```swift +let imageData = Data(contentsOf: imageURL) +let base64Image = imageData.base64EncodedString() + +let request = ModelRequest( + messages: [ + .user(content: .multimodal([ + .text("What do you see in this image?"), + .imageUrl(ImageUrl( + base64: base64Image, + detail: .high + )) + ])) + ] +) + +let response = try await model.getResponse(request: request) +``` + +## Configuration + +### Environment Variables + +```bash +# OpenAI +export OPENAI_API_KEY="sk-..." + +# Anthropic +export ANTHROPIC_API_KEY="sk-ant-..." + +# Grok (xAI) +export X_AI_API_KEY="xai-..." +# or +export XAI_API_KEY="xai-..." + +# Ollama (optional, defaults to localhost:11434) +export PEEKABOO_OLLAMA_BASE_URL="http://localhost:11434" +``` + +### Provider Configuration + +```swift +// Configure custom provider settings +let openAIConfig = ProviderConfiguration.openAI( + apiKey: "your-api-key", + baseURL: URL(string: "https://api.openai.com/v1"), + organizationId: "org-id" +) + +try await Tachikoma.shared.configureProvider(openAIConfig) + +// Register custom model +await Tachikoma.shared.registerModel(name: "custom-gpt") { + OpenAIModel( + apiKey: "your-key", + modelName: "gpt-4-custom" + ) +} +``` + +## Architecture + +### Core Components + +#### ModelInterface +The unified protocol that all AI providers implement: + +```swift +protocol ModelInterface: Sendable { + var maskedApiKey: String { get } + func getResponse(request: ModelRequest) async throws -> ModelResponse + func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream +} +``` + +#### Message System +Type-safe message handling with support for all content types: + +```swift +public enum Message: Codable, Sendable { + case system(id: String? = nil, content: String) + case user(id: String? = nil, content: MessageContent) + case assistant(id: String? = nil, content: [AssistantContent], status: MessageStatus = .completed) + case tool(id: String? = nil, toolCallId: String, content: String) + case reasoning(id: String? = nil, content: String) +} +``` + +#### Streaming System +Real-time event handling for streaming responses: + +```swift +public enum StreamEvent { + case responseStarted(StreamResponseStarted) + case textDelta(StreamTextDelta) + case toolCallDelta(StreamToolCallDelta) + case toolCallCompleted(StreamToolCallCompleted) + case responseCompleted(StreamResponseCompleted) + case error(StreamError) +} +``` + +#### Tool System +Comprehensive function calling with generic context support: + +```swift +public struct Tool { + public let execute: (ToolInput, Context) async throws -> ToolOutput + public func toToolDefinition() -> ToolDefinition +} +``` + +### Provider Architecture + +Each provider implements the `ModelInterface` with provider-specific optimizations: + +- **OpenAI**: Dual API support (Chat Completions + Responses API) +- **Anthropic**: Native SSE streaming with content blocks +- **Grok**: OpenAI-compatible with parameter filtering +- **Ollama**: Local inference with tool calling detection + +### Error Handling + +Comprehensive error types with recovery suggestions: + +```swift +public enum TachikomaError: Error, LocalizedError { + case modelNotFound(String) + case authenticationFailed + case rateLimited + case insufficientQuota + case contextLengthExceeded + // ... more cases with detailed descriptions + + public var isRetryable: Bool { /* ... */ } + public var recoverySuggestion: String? { /* ... */ } +} +``` + +## Advanced Usage + +### Custom Providers + +```swift +class CustomAIModel: ModelInterface { + var maskedApiKey: String { "custom-***" } + + func getResponse(request: ModelRequest) async throws -> ModelResponse { + // Custom implementation + } + + func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + // Custom streaming implementation + } +} + +// Register the custom provider +await Tachikoma.shared.registerModel(name: "custom-ai") { + CustomAIModel() +} +``` + +### Batch Processing + +```swift +let requests = [ + ModelRequest(messages: [.user(content: .text("Question 1"))]), + ModelRequest(messages: [.user(content: .text("Question 2"))]), + ModelRequest(messages: [.user(content: .text("Question 3"))]) +] + +let responses = try await withThrowingTaskGroup(of: ModelResponse.self) { group in + for request in requests { + group.addTask { + try await model.getResponse(request: request) + } + } + + var results: [ModelResponse] = [] + for try await response in group { + results.append(response) + } + return results +} +``` + +### Tool Context Management + +```swift +struct WeatherContext { + let apiKey: String + let units: String +} + +let weatherTool = Tool { input, context in + let location = input.parameters["location"] as? String ?? "" + // Use context.apiKey and context.units for API call + return ToolOutput(content: "Weather data for \(location)") +} + +// Use with context +let context = WeatherContext(apiKey: "weather-key", units: "metric") +let toolDefinition = weatherTool.toToolDefinition() +``` + +## Testing + +Tachikoma includes comprehensive test coverage: + +```bash +# Run tests +swift test + +# Run with verbose output +swift test --verbose + +# Run specific test suites +swift test --filter "ModelProviderTests" +``` + +### Mock Providers + +```swift +class MockModel: ModelInterface { + var maskedApiKey: String = "mock-***" + var responses: [ModelResponse] = [] + + func getResponse(request: ModelRequest) async throws -> ModelResponse { + return responses.removeFirst() + } +} + +// Use in tests +let mockModel = MockModel() +mockModel.responses = [/* test responses */] +await Tachikoma.shared.registerModel(name: "mock") { mockModel } +``` + +## Performance Considerations + +### Caching +- Model instances are cached by default +- Clear cache with `ModelProvider.shared.clearCache()` +- Disable caching for specific models if needed + +### Memory Management +- Streaming responses use `AsyncThrowingStream` for memory efficiency +- Large responses are processed incrementally +- Tool contexts should be lightweight for optimal performance + +### Concurrency +- All APIs are actor-safe and Swift 6 compliant +- Use `TaskGroup` for parallel processing +- Respect rate limits with proper error handling + +## Migration Guide + +### From PeekabooCore + +If migrating from PeekabooCore's AI system: + +1. Replace `PeekabooError` with `TachikomaError` +2. Update import statements +3. Use `Tachikoma.shared.getModel()` instead of direct model creation +4. Update streaming event handling for new event types + +### Version History + +- **v1.0.0**: Initial release with Swift 6 support +- Core providers: OpenAI, Anthropic, Grok, Ollama +- Complete tool calling and streaming support + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Ensure Swift 6 strict mode compliance +4. Add comprehensive tests +5. Update documentation +6. Submit a pull request + +## License + +Tachikoma is available under the MIT License. See LICENSE for details. + +## Support + +- **Issues**: [GitHub Issues](https://github.com/steipete/Tachikoma/issues) +- **Discussions**: [GitHub Discussions](https://github.com/steipete/Tachikoma/discussions) +- **Documentation**: [API Reference](https://steipete.github.io/Tachikoma/) + +--- + +Built with ❤️ for the Swift AI community. diff --git a/Sources/Core/MessageTypes.swift b/Sources/Core/MessageTypes.swift new file mode 100644 index 0000000..f364eb6 --- /dev/null +++ b/Sources/Core/MessageTypes.swift @@ -0,0 +1,366 @@ +import Foundation + +// MARK: - Unified Message Type + +/// Unified message enum that provides type-safe message handling +public enum Message: Codable, Sendable { + case system(id: String? = nil, content: String) + case user(id: String? = nil, content: MessageContent) + case assistant(id: String? = nil, content: [AssistantContent], status: MessageStatus = .completed) + case tool(id: String? = nil, toolCallId: String, content: String) + case reasoning(id: String? = nil, content: String) + + // MARK: - Properties + + /// Get the message type + public var type: MessageType { + switch self { + case .system: .system + case .user: .user + case .assistant: .assistant + case .tool: .tool + case .reasoning: .reasoning + } + } + + /// Get the message ID + public var id: String? { + switch self { + case let .system(id, _), let .user(id, _), let .assistant(id, _, _), + let .tool(id, _, _), let .reasoning(id, _): + id + } + } + + // MARK: - Codable Implementation + + private enum CodingKeys: String, CodingKey { + case type, id, content, status, toolCallId + } + + public enum MessageType: String, Codable { + case system, user, assistant, tool, reasoning + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(MessageType.self, forKey: .type) + let id = try container.decodeIfPresent(String.self, forKey: .id) + + switch type { + case .system: + let content = try container.decode(String.self, forKey: .content) + self = .system(id: id, content: content) + + case .user: + let content = try container.decode(MessageContent.self, forKey: .content) + self = .user(id: id, content: content) + + case .assistant: + let content = try container.decode([AssistantContent].self, forKey: .content) + let status = try container.decodeIfPresent(MessageStatus.self, forKey: .status) ?? .completed + self = .assistant(id: id, content: content, status: status) + + case .tool: + let toolCallId = try container.decode(String.self, forKey: .toolCallId) + let content = try container.decode(String.self, forKey: .content) + self = .tool(id: id, toolCallId: toolCallId, content: content) + + case .reasoning: + let content = try container.decode(String.self, forKey: .content) + self = .reasoning(id: id, content: content) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.type, forKey: .type) + + switch self { + case let .system(id, content): + try container.encodeIfPresent(id, forKey: .id) + try container.encode(content, forKey: .content) + + case let .user(id, content): + try container.encodeIfPresent(id, forKey: .id) + try container.encode(content, forKey: .content) + + case let .assistant(id, content, status): + try container.encodeIfPresent(id, forKey: .id) + try container.encode(content, forKey: .content) + try container.encode(status, forKey: .status) + + case let .tool(id, toolCallId, content): + try container.encodeIfPresent(id, forKey: .id) + try container.encode(toolCallId, forKey: .toolCallId) + try container.encode(content, forKey: .content) + + case let .reasoning(id, content): + try container.encodeIfPresent(id, forKey: .id) + try container.encode(content, forKey: .content) + } + } +} + +// MARK: - Content Types + +/// User message content variants +public enum MessageContent: Codable, Sendable { + case text(String) + case image(ImageContent) + case file(FileContent) + case audio(AudioContent) + case multimodal([MessageContentPart]) + + // Custom coding for enum + enum CodingKeys: String, CodingKey { + case type, value + } + + enum ContentType: String, Codable { + case text, image, file, audio, multimodal + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(ContentType.self, forKey: .type) + + switch type { + case .text: + let value = try container.decode(String.self, forKey: .value) + self = .text(value) + case .image: + let value = try container.decode(ImageContent.self, forKey: .value) + self = .image(value) + case .file: + let value = try container.decode(FileContent.self, forKey: .value) + self = .file(value) + case .audio: + let value = try container.decode(AudioContent.self, forKey: .value) + self = .audio(value) + case .multimodal: + let value = try container.decode([MessageContentPart].self, forKey: .value) + self = .multimodal(value) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .text(value): + try container.encode(ContentType.text, forKey: .type) + try container.encode(value, forKey: .value) + case let .image(value): + try container.encode(ContentType.image, forKey: .type) + try container.encode(value, forKey: .value) + case let .file(value): + try container.encode(ContentType.file, forKey: .type) + try container.encode(value, forKey: .value) + case let .audio(value): + try container.encode(ContentType.audio, forKey: .type) + try container.encode(value, forKey: .value) + case let .multimodal(value): + try container.encode(ContentType.multimodal, forKey: .type) + try container.encode(value, forKey: .value) + } + } +} + +/// Image content for messages +public struct ImageContent: Codable, Sendable { + public let url: String? + public let base64: String? + public let detail: ImageDetail? + + public enum ImageDetail: String, Codable, Sendable { + case auto, low, high + } + + public init(url: String? = nil, base64: String? = nil, detail: ImageDetail? = nil) { + self.url = url + self.base64 = base64 + self.detail = detail + } +} + +/// File content for messages +public struct FileContent: Codable, Sendable { + public let id: String? + public let url: String? + public let name: String? + public let filename: String? + public let content: String? + public let mimeType: String? + + public init( + id: String? = nil, + url: String? = nil, + name: String? = nil, + filename: String? = nil, + content: String? = nil, + mimeType: String? = nil) + { + self.id = id + self.url = url + self.name = name + self.filename = filename + self.content = content + self.mimeType = mimeType + } + + // Convenience constructor for test compatibility + public init(filename: String, content: String, mimeType: String) { + self.init(name: filename, filename: filename, content: content, mimeType: mimeType) + } +} + +/// Audio content for messages +public struct AudioContent: Codable, Sendable { + public let url: String? + public let base64: String? + public let transcript: String? + public let duration: TimeInterval? + public let mimeType: String? + + public init( + url: String? = nil, + base64: String? = nil, + transcript: String? = nil, + duration: TimeInterval? = nil, + mimeType: String? = nil) + { + self.url = url + self.base64 = base64 + self.transcript = transcript + self.duration = duration + self.mimeType = mimeType + } +} + +/// Multimodal content part +public struct MessageContentPart: Codable, Sendable { + public let type: String + public let text: String? + public let imageUrl: ImageContent? + + public init(type: String, text: String? = nil, imageUrl: ImageContent? = nil) { + self.type = type + self.text = text + self.imageUrl = imageUrl + } +} + +/// Assistant response content variants +public enum AssistantContent: Codable, Sendable { + case outputText(String) + case refusal(String) + case toolCall(ToolCallItem) + + // Custom coding + enum CodingKeys: String, CodingKey { + case type, value + } + + enum ContentType: String, Codable { + case text, refusal, toolCall + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(ContentType.self, forKey: .type) + + switch type { + case .text: + let value = try container.decode(String.self, forKey: .value) + self = .outputText(value) + case .refusal: + let value = try container.decode(String.self, forKey: .value) + self = .refusal(value) + case .toolCall: + let value = try container.decode(ToolCallItem.self, forKey: .value) + self = .toolCall(value) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .outputText(value): + try container.encode(ContentType.text, forKey: .type) + try container.encode(value, forKey: .value) + case let .refusal(value): + try container.encode(ContentType.refusal, forKey: .type) + try container.encode(value, forKey: .value) + case let .toolCall(value): + try container.encode(ContentType.toolCall, forKey: .type) + try container.encode(value, forKey: .value) + } + } +} + +// MARK: - Tool Call Types + +/// Tool call item representing a function invocation +public struct ToolCallItem: Codable, Sendable { + public let id: String + public let type: ToolCallType + public let function: FunctionCall + public let status: ToolCallStatus? + + public init(id: String, type: ToolCallType = .function, function: FunctionCall, status: ToolCallStatus? = nil) { + self.id = id + self.type = type + self.function = function + self.status = status + } +} + +/// Types of tool calls +public enum ToolCallType: String, Codable, Sendable { + case function + case hosted = "hosted_tool" + case computer +} + +/// Function call details +public struct FunctionCall: Codable, Sendable { + public let name: String + public let arguments: String + + public init(name: String, arguments: String) { + self.name = name + self.arguments = arguments + } +} + +/// Tool call execution status +public enum ToolCallStatus: String, Codable, Sendable { + case inProgress = "in_progress" + case completed + case failed +} + +/// Message processing status +public enum MessageStatus: String, Codable, Sendable { + case inProgress = "in_progress" + case completed + case incomplete +} + +// MARK: - Helper Extensions + +extension AssistantContent { + /// Extract text content if available + public var textContent: String? { + switch self { + case let .outputText(text): + text + case let .refusal(text): + text + case .toolCall: + nil + } + } +} \ No newline at end of file diff --git a/Sources/Core/ModelInterface.swift b/Sources/Core/ModelInterface.swift new file mode 100644 index 0000000..c1eb5ca --- /dev/null +++ b/Sources/Core/ModelInterface.swift @@ -0,0 +1,424 @@ +import Foundation + +// MARK: - Model Interface Protocol + +/// Protocol defining the interface for AI model providers +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public protocol ModelInterface: Sendable { + /// Get a non-streaming response from the model + /// - Parameter request: The model request containing messages, tools, and settings + /// - Returns: The model response + func getResponse(request: ModelRequest) async throws -> ModelResponse + + /// Get a streaming response from the model + /// - Parameter request: The model request containing messages, tools, and settings + /// - Returns: An async stream of events + func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream + + /// Get a masked version of the API key for debugging + /// Returns the first 6 and last 2 characters of the API key + /// - Returns: Masked API key string (e.g., "sk-ant...AA") + var maskedApiKey: String { get } +} + +// MARK: - Model Request & Response Types + +/// Request to send to a model +public struct ModelRequest: Codable, Sendable { + /// The messages to send to the model + public let messages: [Message] + + /// Available tools for the model to use + public let tools: [ToolDefinition]? + + /// Model-specific settings + public let settings: ModelSettings + + /// System instructions (some models support this separately from messages) + public let systemInstructions: String? + + public init( + messages: [Message], + tools: [ToolDefinition]? = nil, + settings: ModelSettings, + systemInstructions: String? = nil) + { + self.messages = messages + self.tools = tools + self.settings = settings + self.systemInstructions = systemInstructions + } +} + +/// Response from a model +public struct ModelResponse: Codable, Sendable { + /// Unique identifier for the response + public let id: String + + /// The model that generated the response + public let model: String? + + /// Content returned by the model + public let content: [AssistantContent] + + /// Token usage statistics + public let usage: Usage? + + /// Whether the response was flagged for safety + public let flagged: Bool + + /// Reason for flagging if applicable + public let flaggedCategories: [String]? + + /// Finish reason + public let finishReason: FinishReason? + + public init( + id: String, + model: String? = nil, + content: [AssistantContent], + usage: Usage? = nil, + flagged: Bool = false, + flaggedCategories: [String]? = nil, + finishReason: FinishReason? = nil) + { + self.id = id + self.model = model + self.content = content + self.usage = usage + self.flagged = flagged + self.flaggedCategories = flaggedCategories + self.finishReason = finishReason + } +} + +// MARK: - Model Settings + +/// Settings for model behavior +public struct ModelSettings: Codable, Sendable { + /// The model name/identifier + public let modelName: String + + /// Temperature for randomness (0.0 to 2.0) + public let temperature: Double? + + /// Top-p sampling parameter + public let topP: Double? + + /// Maximum tokens to generate + public let maxTokens: Int? + + /// Frequency penalty (-2.0 to 2.0) + public let frequencyPenalty: Double? + + /// Presence penalty (-2.0 to 2.0) + public let presencePenalty: Double? + + /// Stop sequences + public let stopSequences: [String]? + + /// Tool choice setting + public let toolChoice: ToolChoice? + + /// Whether to use parallel tool calls + public let parallelToolCalls: Bool? + + /// Response format + public let responseFormat: ResponseFormat? + + /// Seed for deterministic generation + public let seed: Int? + + /// User identifier for tracking + public let user: String? + + /// Additional provider-specific parameters + public let additionalParameters: ModelParameters? + + public init( + modelName: String, + temperature: Double? = nil, + topP: Double? = nil, + maxTokens: Int? = nil, + frequencyPenalty: Double? = nil, + presencePenalty: Double? = nil, + stopSequences: [String]? = nil, + toolChoice: ToolChoice? = nil, + parallelToolCalls: Bool? = nil, + responseFormat: ResponseFormat? = nil, + seed: Int? = nil, + user: String? = nil, + additionalParameters: ModelParameters? = nil) + { + self.modelName = modelName + self.temperature = temperature + self.topP = topP + self.maxTokens = maxTokens + self.frequencyPenalty = frequencyPenalty + self.presencePenalty = presencePenalty + self.stopSequences = stopSequences + self.toolChoice = toolChoice + self.parallelToolCalls = parallelToolCalls + self.responseFormat = responseFormat + self.seed = seed + self.user = user + self.additionalParameters = additionalParameters + } + + /// Default settings for Claude Opus 4 + public static var `default`: ModelSettings { + ModelSettings(modelName: "claude-opus-4-20250514") + } + + // MARK: - Convenience Constructors + + /// Create settings with just specified parameters, using Claude Opus 4 as default model + public init( + temperature: Double? = nil, + topP: Double? = nil, + maxTokens: Int? = nil, + frequencyPenalty: Double? = nil, + presencePenalty: Double? = nil, + stopSequences: [String]? = nil, + toolChoice: ToolChoice? = nil, + parallelToolCalls: Bool? = nil, + responseFormat: ResponseFormat? = nil, + seed: Int? = nil, + user: String? = nil, + additionalParameters: ModelParameters? = nil) + { + self.init( + modelName: "claude-opus-4-20250514", + temperature: temperature, + topP: topP, + maxTokens: maxTokens, + frequencyPenalty: frequencyPenalty, + presencePenalty: presencePenalty, + stopSequences: stopSequences, + toolChoice: toolChoice, + parallelToolCalls: parallelToolCalls, + responseFormat: responseFormat, + seed: seed, + user: user, + additionalParameters: additionalParameters + ) + } + + /// Convenience constructors for specific API types and reasoning parameters + public init( + apiType: String, + modelName: String = "claude-opus-4-20250514") + { + let params = ModelParameters([ + "apiType": ModelParameters.Value.string(apiType) + ]) + self.init(modelName: modelName, additionalParameters: params) + } + + public init( + reasoningEffort: String, + reasoning: [String: String]? = nil, + temperature: Double? = nil, + modelName: String = "o3") + { + var params: [String: ModelParameters.Value] = [ + "reasoningEffort": ModelParameters.Value.string(reasoningEffort) + ] + if let reasoning = reasoning { + let reasoningValue = reasoning.mapValues { ModelParameters.Value.string($0) } + params["reasoning"] = ModelParameters.Value.dictionary(reasoningValue) + } + let modelParams = ModelParameters(params) + self.init( + modelName: modelName, + temperature: temperature, + additionalParameters: modelParams + ) + } + + // Custom coding for additionalParameters + enum CodingKeys: String, CodingKey { + case modelName, temperature, topP, maxTokens + case frequencyPenalty, presencePenalty, stopSequences + case toolChoice, parallelToolCalls, responseFormat + case seed, user, additionalParameters + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.modelName = try container.decode(String.self, forKey: .modelName) + self.temperature = try container.decodeIfPresent(Double.self, forKey: .temperature) + self.topP = try container.decodeIfPresent(Double.self, forKey: .topP) + self.maxTokens = try container.decodeIfPresent(Int.self, forKey: .maxTokens) + self.frequencyPenalty = try container.decodeIfPresent(Double.self, forKey: .frequencyPenalty) + self.presencePenalty = try container.decodeIfPresent(Double.self, forKey: .presencePenalty) + self.stopSequences = try container.decodeIfPresent([String].self, forKey: .stopSequences) + self.toolChoice = try container.decodeIfPresent(ToolChoice.self, forKey: .toolChoice) + self.parallelToolCalls = try container.decodeIfPresent(Bool.self, forKey: .parallelToolCalls) + self.responseFormat = try container.decodeIfPresent(ResponseFormat.self, forKey: .responseFormat) + self.seed = try container.decodeIfPresent(Int.self, forKey: .seed) + self.user = try container.decodeIfPresent(String.self, forKey: .user) + + // Decode additional parameters + self.additionalParameters = try container.decodeIfPresent(ModelParameters.self, forKey: .additionalParameters) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.modelName, forKey: .modelName) + try container.encodeIfPresent(self.temperature, forKey: .temperature) + try container.encodeIfPresent(self.topP, forKey: .topP) + try container.encodeIfPresent(self.maxTokens, forKey: .maxTokens) + try container.encodeIfPresent(self.frequencyPenalty, forKey: .frequencyPenalty) + try container.encodeIfPresent(self.presencePenalty, forKey: .presencePenalty) + try container.encodeIfPresent(self.stopSequences, forKey: .stopSequences) + try container.encodeIfPresent(self.toolChoice, forKey: .toolChoice) + try container.encodeIfPresent(self.parallelToolCalls, forKey: .parallelToolCalls) + try container.encodeIfPresent(self.responseFormat, forKey: .responseFormat) + try container.encodeIfPresent(self.seed, forKey: .seed) + try container.encodeIfPresent(self.user, forKey: .user) + + // Encode additional parameters + try container.encodeIfPresent(self.additionalParameters, forKey: .additionalParameters) + } +} + +// MARK: - Tool Choice + +/// Tool choice setting for models +public enum ToolChoice: Codable, Sendable, Equatable { + case auto + case none + case required + case specific(toolName: String) + + // Custom coding + enum CodingKeys: String, CodingKey { + case type, toolName + } + + enum ChoiceType: String, Codable { + case auto, none, required, specific + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(ChoiceType.self, forKey: .type) + + switch type { + case .auto: + self = .auto + case .none: + self = .none + case .required: + self = .required + case .specific: + let toolName = try container.decode(String.self, forKey: .toolName) + self = .specific(toolName: toolName) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .auto: + try container.encode(ChoiceType.auto, forKey: .type) + case .none: + try container.encode(ChoiceType.none, forKey: .type) + case .required: + try container.encode(ChoiceType.required, forKey: .type) + case let .specific(toolName): + try container.encode(ChoiceType.specific, forKey: .type) + try container.encode(toolName, forKey: .toolName) + } + } +} + +// MARK: - Response Format + +/// Response format specification +public struct ResponseFormat: Codable, Sendable { + public let type: ResponseFormatType + public let jsonSchema: JSONSchema? + + public init(type: ResponseFormatType, jsonSchema: JSONSchema? = nil) { + self.type = type + self.jsonSchema = jsonSchema + } + + /// Plain text response + public static var text: ResponseFormat { + ResponseFormat(type: .text) + } + + /// JSON object response + public static var jsonObject: ResponseFormat { + ResponseFormat(type: .jsonObject) + } +} + +/// Response format types +public enum ResponseFormatType: String, Codable, Sendable { + case text + case jsonObject = "json_object" + case jsonSchema = "json_schema" +} + +/// JSON schema specification +public struct JSONSchema: Codable, Sendable { + public let name: String + public let strict: Bool + private let schemaData: Data // Store as raw JSON data + + // Custom coding for schema + enum CodingKeys: String, CodingKey { + case name, strict, schema + } + + public init(name: String, strict: Bool = true, schema: [String: Any]) throws { + self.name = name + self.strict = strict + self.schemaData = try JSONSerialization.data(withJSONObject: schema) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.strict = try container.decode(Bool.self, forKey: .strict) + self.schemaData = try container.decode(Data.self, forKey: .schema) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.name, forKey: .name) + try container.encode(self.strict, forKey: .strict) + try container.encode(self.schemaData, forKey: .schema) + } + + /// Get the schema as a dictionary + public func getSchema() throws -> [String: Any] { + guard let dict = try JSONSerialization.jsonObject(with: schemaData) as? [String: Any] else { + throw TachikomaError.invalidConfiguration("Invalid JSON schema data") + } + return dict + } +} + +// MARK: - Model Provider Protocol + +/// Protocol for model provider factories +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public protocol ModelProviderProtocol { + /// Get a model by name + /// - Parameter modelName: The name of the model to retrieve + /// - Returns: A model instance conforming to ModelInterface + func getModel(modelName: String) throws -> any ModelInterface +} + +// MARK: - Model Errors + +// MARK: - Note +// ModelError is defined in TachikomaError.swift to avoid duplication \ No newline at end of file diff --git a/Sources/Core/ModelParameters.swift b/Sources/Core/ModelParameters.swift new file mode 100644 index 0000000..d0f9175 --- /dev/null +++ b/Sources/Core/ModelParameters.swift @@ -0,0 +1,240 @@ +import Foundation + +// MARK: - Model Parameters + +/// Type-safe representation of additional model parameters +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct ModelParameters: Codable, Sendable { + private let storage: [String: Value] + + /// Supported parameter value types + public enum Value: Codable, Sendable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case dictionary([String: Value]) + case array([Value]) + + // MARK: - Codable + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let intValue = try? container.decode(Int.self) { + self = .int(intValue) + } else if let doubleValue = try? container.decode(Double.self) { + self = .double(doubleValue) + } else if let boolValue = try? container.decode(Bool.self) { + self = .bool(boolValue) + } else if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let dictValue = try? container.decode([String: Value].self) { + self = .dictionary(dictValue) + } else if let arrayValue = try? container.decode([Value].self) { + self = .array(arrayValue) + } else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Unable to decode ModelParameters.Value") + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case let .string(value): + try container.encode(value) + case let .int(value): + try container.encode(value) + case let .double(value): + try container.encode(value) + case let .bool(value): + try container.encode(value) + case let .dictionary(value): + try container.encode(value) + case let .array(value): + try container.encode(value) + } + } + + /// Convert to raw value for JSON serialization + public var rawValue: Any { + switch self { + case let .string(value): + value + case let .int(value): + value + case let .double(value): + value + case let .bool(value): + value + case let .dictionary(dict): + dict.mapValues { $0.rawValue } + case let .array(array): + array.map(\.rawValue) + } + } + } + + // MARK: - Initialization + + public init(_ storage: [String: Value] = [:]) { + self.storage = storage + } + + /// Initialize from a dictionary of raw values + public init(from rawValues: [String: Any]) { + var convertedStorage: [String: Value] = [:] + for (key, value) in rawValues { + if let converted = Self.convertToValue(value) { + convertedStorage[key] = converted + } + } + self.storage = convertedStorage + } + + /// Convert any value to our Value enum + private static func convertToValue(_ value: Any) -> Value? { + switch value { + case let string as String: + return .string(string) + case let int as Int: + return .int(int) + case let double as Double: + return .double(double) + case let bool as Bool: + return .bool(bool) + case let dict as [String: Any]: + var converted: [String: Value] = [:] + for (k, v) in dict { + if let convertedValue = convertToValue(v) { + converted[k] = convertedValue + } + } + return .dictionary(converted) + case let array as [Any]: + let converted = array.compactMap { self.convertToValue($0) } + return .array(converted) + default: + return nil + } + } + + // MARK: - Access Methods + + public subscript(key: String) -> Value? { self.storage[key] } + + public func string(_ key: String) -> String? { + guard case let .string(value) = storage[key] else { return nil } + return value + } + + public func int(_ key: String) -> Int? { + guard case let .int(value) = storage[key] else { return nil } + return value + } + + public func double(_ key: String) -> Double? { + guard case let .double(value) = storage[key] else { return nil } + return value + } + + public func bool(_ key: String) -> Bool? { + guard case let .bool(value) = storage[key] else { return nil } + return value + } + + /// Get the raw dictionary for JSON serialization + public var rawDictionary: [String: Any] { + self.storage.mapValues { $0.rawValue } + } + + /// Check if empty + public var isEmpty: Bool { + self.storage.isEmpty + } + + // MARK: - Mutating Methods + + /// Set a parameter value + public mutating func set(_ key: String, value: Any) { + if let converted = Self.convertToValue(value) { + var newStorage = self.storage + newStorage[key] = converted + self = ModelParameters(newStorage) + } + } + + /// Get a parameter value + public func get(_ key: String) -> Any? { + return self.storage[key]?.rawValue + } + + // MARK: - Builder Methods + + public func with(_ key: String, value: String) -> ModelParameters { + var newStorage = self.storage + newStorage[key] = .string(value) + return ModelParameters(newStorage) + } + + public func with(_ key: String, value: Int) -> ModelParameters { + var newStorage = self.storage + newStorage[key] = .int(value) + return ModelParameters(newStorage) + } + + public func with(_ key: String, value: Double) -> ModelParameters { + var newStorage = self.storage + newStorage[key] = .double(value) + return ModelParameters(newStorage) + } + + public func with(_ key: String, value: Bool) -> ModelParameters { + var newStorage = self.storage + newStorage[key] = .bool(value) + return ModelParameters(newStorage) + } + + public func with(_ key: String, value: [String: Any]) -> ModelParameters { + guard let converted = Self.convertToValue(value) else { return self } + var newStorage = self.storage + newStorage[key] = converted + return ModelParameters(newStorage) + } + + // MARK: - Codable + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + self.storage = try container.decode([String: Value].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.storage) + } +} + +// MARK: - Convenience Builders + +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +extension ModelParameters { + /// Create parameters for OpenAI o3/o4 models + public static func o3Parameters( + reasoningEffort: String = "medium", + maxCompletionTokens: Int = 32768) -> ModelParameters + { + ModelParameters() + .with("reasoning_effort", value: reasoningEffort) + .with("max_completion_tokens", value: maxCompletionTokens) + .with("reasoning", value: ["summary": "detailed"]) + } + + /// Create parameters with API type + public static func withAPIType(_ apiType: String) -> ModelParameters { + ModelParameters().with("apiType", value: apiType) + } +} \ No newline at end of file diff --git a/Sources/Core/ModelProvider.swift b/Sources/Core/ModelProvider.swift new file mode 100644 index 0000000..487ee4e --- /dev/null +++ b/Sources/Core/ModelProvider.swift @@ -0,0 +1,650 @@ +import Foundation + +// MARK: - Model Provider + +/// Singleton provider for managing model instances in Tachikoma +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public actor ModelProvider { + /// Shared instance + public static let shared = ModelProvider() + + /// Registered model factories + private var modelFactories: [String: @Sendable () throws -> any ModelInterface] = [:] + + /// Model instance cache + private var modelCache: [String: any ModelInterface] = [:] + + private init() { + // Register default models when initialized + Task { + await self.registerDefaultModels() + } + } + + // MARK: - Public Methods + + /// Register a model factory + /// - Parameters: + /// - name: The model name + /// - factory: Factory closure that creates the model + public func register( + modelName: String, + factory: @escaping @Sendable () throws -> any ModelInterface) + { + self.modelFactories[modelName] = factory + // Clear cache for this model + self.modelCache.removeValue(forKey: modelName) + } + + /// Get a model by name + /// - Parameter modelName: The name of the model + /// - Returns: A model instance + /// - Throws: TachikomaError if model not found + public func getModel(modelName: String) throws -> any ModelInterface { + // Check cache first + if let cached = modelCache[modelName] { + return cached + } + + // Check for custom provider first (format: provider-id/model-path) + if let slashIndex = modelName.firstIndex(of: "/") { + let providerId = String(modelName[.. [String] { + Array(self.modelFactories.keys).sorted() + } + + /// Clear model cache + public func clearCache() { + self.modelCache.removeAll() + } + + /// Clear all model registrations and cache (useful for testing) + public func clearAll() async { + self.modelCache.removeAll() + self.modelFactories.removeAll() + // Re-register default models + await self.registerDefaultModels() + } + + /// Unregister a model + public func unregister(modelName: String) { + self.modelFactories.removeValue(forKey: modelName) + self.modelCache.removeValue(forKey: modelName) + } + + // MARK: - Private Methods + + private func registerDefaultModels() async { + // Register OpenAI models + self.registerOpenAIModels() + + // Register Anthropic models + self.registerAnthropicModels() + + // Register Grok models + self.registerGrokModels() + + // Register Ollama models + self.registerOllamaModels() + } + + /// Resolve lenient model names to their full versions + private func resolveLenientModelName(_ modelName: String) -> String? { + let lowercased = modelName.lowercased() + + // Claude model shortcuts + if lowercased == "claude-4-opus" || lowercased == "claude-opus-4" || lowercased == "claude-opus" { + return "claude-opus-4-20250514" + } + if lowercased == "claude-4-sonnet" || lowercased == "claude-sonnet-4" || lowercased == "claude-sonnet" { + return "claude-sonnet-4-20250514" + } + if lowercased == "claude-3.7-sonnet" || lowercased == "claude-3-7-sonnet" || lowercased == "claude-sonnet-3.7" { + return "claude-3-7-sonnet" + } + if lowercased == "claude-3.5-sonnet" || lowercased == "claude-3-5-sonnet" || lowercased == "claude-sonnet-3.5" { + return "claude-3-5-sonnet" + } + if lowercased == "claude-3.5-haiku" || lowercased == "claude-3-5-haiku" || lowercased == "claude-haiku-3.5" { + return "claude-3-5-haiku" + } + if lowercased == "claude-3.5-opus" || lowercased == "claude-3-5-opus" || lowercased == "claude-opus-3.5" { + return "claude-3-5-opus" + } + if lowercased == "claude" { + return "claude-opus-4-20250514" // Default to Claude Opus 4 + } + + // OpenAI model shortcuts + if lowercased == "gpt4" || lowercased == "gpt-4" { + return "gpt-4.1" + } + if lowercased == "gpt4-mini" || lowercased == "gpt-4-mini" { + return "gpt-4.1-mini" + } + if lowercased == "gpt" { + return "gpt-4.1" // Default to latest GPT + } + + // Grok model shortcuts + if lowercased == "grok" || lowercased == "grok4" || lowercased == "grok-4" { + return "grok-4-0709" + } + if lowercased == "grok3" || lowercased == "grok-3" { + return "grok-3" + } + if lowercased == "grok2" || lowercased == "grok-2" { + return "grok-2-vision-1212" + } + + // Ollama model shortcuts + if lowercased == "ollama" || lowercased == "llama" { + return "llama3.3" // Default to llama3.3 - best for agent tasks with tool support + } + if lowercased == "llama3" || lowercased == "llama-3" { + return "llama3.3" // Default to latest llama 3.x + } + + // Check if it's a partial match for any registered model + let registeredModels = Array(modelFactories.keys) + for model in registeredModels { + if model.lowercased().contains(lowercased) || lowercased.contains(model.lowercased()) { + return model + } + } + + return nil + } + + private func registerOpenAIModels() { + let models = [ + // GPT-4o series + "gpt-4o", + "gpt-4o-mini", + + // GPT-4.1 series + "gpt-4.1", + "gpt-4.1-mini", + + // o3 series (Responses API only) + "o3", + "o3-mini", + "o3-pro", + + // o4 series (Responses API only) + "o4-mini", + ] + + for modelName in models { + self.register(modelName: modelName) { + guard let apiKey = self.getOpenAIAPIKey() else { + throw TachikomaError.authenticationFailed + } + + return OpenAIModel(apiKey: apiKey, modelName: modelName) + } + } + } + + nonisolated private func getOpenAIAPIKey() -> String? { + // Check environment variable + if let apiKey = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] { + return apiKey + } + + // Check standard configuration directory + let configPath = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".tachikoma") + .appendingPathComponent("credentials") + + if let credentials = try? String(contentsOf: configPath) { + for line in credentials.components(separatedBy: .newlines) { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("OPENAI_API_KEY=") { + return String(trimmed.dropFirst("OPENAI_API_KEY=".count)) + } + } + } + + return nil + } + + private func registerAnthropicModels() { + // Map of model names to their actual IDs + let modelMappings: [String: String] = [ + // Claude 4 series (Latest - May 2025) + "claude-opus-4-20250514": "claude-opus-4-20250514", + "claude-opus-4-20250514-thinking": "claude-opus-4-20250514-thinking", + "claude-sonnet-4-20250514": "claude-sonnet-4-20250514", + "claude-sonnet-4-20250514-thinking": "claude-sonnet-4-20250514-thinking", + + // Claude 3.7 series (February 2025) + "claude-3-7-sonnet": "claude-3-7-sonnet", + + // Claude 3.5 series (Still available) + "claude-3-5-haiku": "claude-3-5-haiku", + "claude-3-5-sonnet": "claude-3-5-sonnet", + "claude-3-5-opus": "claude-3-5-opus", + ] + + for (alias, actualModelId) in modelMappings { + self.register(modelName: alias) { + guard let apiKey = self.getAnthropicAPIKey() else { + throw TachikomaError.authenticationFailed + } + + return AnthropicModel(apiKey: apiKey, modelName: actualModelId) + } + } + } + + nonisolated private func getAnthropicAPIKey() -> String? { + // Check environment variable + if let apiKey = ProcessInfo.processInfo.environment["ANTHROPIC_API_KEY"] { + return apiKey + } + + // Check standard configuration directory + let configPath = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".tachikoma") + .appendingPathComponent("credentials") + + if let credentials = try? String(contentsOf: configPath) { + for line in credentials.components(separatedBy: .newlines) { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("ANTHROPIC_API_KEY=") { + return String(trimmed.dropFirst("ANTHROPIC_API_KEY=".count)) + } + } + } + + return nil + } + + private func registerGrokModels() { + let models = [ + // Grok 4 series + "grok-4", + "grok-4-0709", + "grok-4-latest", + + // Grok 3 series + "grok-3", + "grok-3-mini", + "grok-3-fast", + "grok-3-mini-fast", + + // Grok 2 series + "grok-2-1212", + "grok-2-vision-1212", + "grok-2-image-1212", + + // Beta models + "grok-beta", + "grok-vision-beta", + ] + + for modelName in models { + self.register(modelName: modelName) { + guard let apiKey = self.getGrokAPIKey() else { + throw TachikomaError.authenticationFailed + } + + return GrokModel(apiKey: apiKey, modelName: modelName) + } + } + } + + nonisolated private func getGrokAPIKey() -> String? { + // Check environment variables (both variants) + if let apiKey = ProcessInfo.processInfo.environment["X_AI_API_KEY"] { + return apiKey + } + if let apiKey = ProcessInfo.processInfo.environment["XAI_API_KEY"] { + return apiKey + } + + // Check standard configuration directory + let configPath = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".tachikoma") + .appendingPathComponent("credentials") + + if let credentials = try? String(contentsOf: configPath) { + for line in credentials.components(separatedBy: .newlines) { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasPrefix("X_AI_API_KEY=") { + return String(trimmed.dropFirst("X_AI_API_KEY=".count)) + } + if trimmed.hasPrefix("XAI_API_KEY=") { + return String(trimmed.dropFirst("XAI_API_KEY=".count)) + } + } + } + + return nil + } + + private func registerOllamaModels() { + // Common Ollama models + let models = [ + // Language models with tool support (recommended for agent tasks) + "llama3.3", + "llama3.3:latest", + "llama3.2", + "llama3.2:latest", + + // Vision models (NOTE: These do NOT support tool calling) + "llava:latest", + "llava", + "bakllava:latest", + "bakllava", + "llama3.2-vision:11b", + "llama3.2-vision:90b", + "qwen2.5vl:7b", + "qwen2.5vl:32b", + + // Other language models (tool support varies) + "llama2", + "llama2:latest", + "llama4", + "llama4:latest", + "codellama", + "codellama:latest", + "mistral", + "mistral:latest", + "mixtral", + "mixtral:latest", + "neural-chat", + "neural-chat:latest", + "gemma", + "gemma:latest", + "devstral", + "devstral:latest", + "deepseek-r1:8b", + "deepseek-r1:671b", + ] + + // Get base URL from environment or default + let baseURLString = ProcessInfo.processInfo.environment["TACHIKOMA_OLLAMA_BASE_URL"] ?? "http://localhost:11434" + guard let baseURL = URL(string: baseURLString) else { return } + + for modelName in models { + self.register(modelName: modelName) { + OllamaModel(modelName: modelName, baseURL: baseURL) + } + } + } + + // MARK: - Custom Provider Support + + /// Create a model instance for a custom provider + /// - Parameters: + /// - providerId: The custom provider ID + /// - modelPath: The model path within the provider + /// - Returns: A model instance + /// - Throws: TachikomaError if provider not found or configuration invalid + private func createCustomProviderModel(providerId: String, modelPath: String) throws -> any ModelInterface { + // For now, return a basic implementation + // This can be extended with a configuration system later + throw TachikomaError.modelNotFound("Custom providers not yet implemented") + } +} + +// MARK: - Model Provider Configuration + +/// Configuration for model providers +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum ProviderConfiguration { + /// OpenAI configuration + public struct OpenAI: Sendable { + public let apiKey: String + public let organizationId: String? + public let baseURL: URL? + + public init( + apiKey: String, + organizationId: String? = nil, + baseURL: URL? = nil) + { + self.apiKey = apiKey + self.organizationId = organizationId + self.baseURL = baseURL + } + } + + /// Anthropic configuration + public struct Anthropic: Sendable { + public let apiKey: String + public let baseURL: URL? + + public init( + apiKey: String, + baseURL: URL? = nil) + { + self.apiKey = apiKey + self.baseURL = baseURL + } + } + + /// Ollama configuration + public struct Ollama: Sendable { + public let baseURL: URL + + public init(baseURL: URL = URL(string: "http://localhost:11434")!) { + self.baseURL = baseURL + } + } + + /// Grok/xAI configuration + public struct Grok: Sendable { + public let apiKey: String + public let baseURL: URL? + + public init( + apiKey: String, + baseURL: URL? = nil) + { + self.apiKey = apiKey + self.baseURL = baseURL + } + } +} + +// MARK: - Model Provider Extensions + +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +extension ModelProvider { + /// Configure OpenAI models with specific settings + public func configureOpenAI(_ config: ProviderConfiguration.OpenAI) { + let models = [ + // GPT-4o series + "gpt-4o", + "gpt-4o-mini", + + // GPT-4.1 series + "gpt-4.1", + "gpt-4.1-mini", + + // o3 series (Responses API only) + "o3", + "o3-mini", + "o3-pro", + + // o4 series (Responses API only) + "o4-mini", + ] + + for modelName in models { + self.register(modelName: modelName) { + OpenAIModel( + apiKey: config.apiKey, + baseURL: config.baseURL ?? URL(string: "https://api.openai.com/v1")!, + organizationId: config.organizationId, + modelName: modelName) + } + } + } + + /// Configure Anthropic models with specific settings + public func configureAnthropic(_ config: ProviderConfiguration.Anthropic) { + // Map of model names to their actual IDs + let modelMappings: [String: String] = [ + // Claude 4 series (Latest - May 2025) + "claude-opus-4-20250514": "claude-opus-4-20250514", + "claude-opus-4-20250514-thinking": "claude-opus-4-20250514-thinking", + "claude-sonnet-4-20250514": "claude-sonnet-4-20250514", + "claude-sonnet-4-20250514-thinking": "claude-sonnet-4-20250514-thinking", + + // Claude 3.7 series (February 2025) + "claude-3-7-sonnet": "claude-3-7-sonnet", + + // Claude 3.5 series (Still available) + "claude-3-5-haiku": "claude-3-5-haiku", + "claude-3-5-sonnet": "claude-3-5-sonnet", + "claude-3-5-opus": "claude-3-5-opus", + ] + + for (alias, actualModelId) in modelMappings { + self.register(modelName: alias) { + AnthropicModel( + apiKey: config.apiKey, + baseURL: config.baseURL ?? URL(string: "https://api.anthropic.com/v1")!, + modelName: actualModelId) + } + } + } + + /// Configure Ollama models with specific settings + public func configureOllama(_ config: ProviderConfiguration.Ollama) { + let models = [ + // Vision models + "llava:latest", + "llava", + "bakllava:latest", + "bakllava", + "llama3.2-vision:11b", + "llama3.2-vision:90b", + "qwen2.5vl:7b", + "qwen2.5vl:32b", + + // Language models + "llama2", + "llama2:latest", + "llama3.2", + "llama3.2:latest", + "llama3.3", + "llama3.3:latest", + "llama4", + "llama4:latest", + "codellama", + "codellama:latest", + "mistral", + "mistral:latest", + "mixtral", + "mixtral:latest", + "neural-chat", + "neural-chat:latest", + "gemma", + "gemma:latest", + "devstral", + "devstral:latest", + "deepseek-r1:8b", + "deepseek-r1:671b", + ] + + for modelName in models { + self.register(modelName: modelName) { + OllamaModel(modelName: modelName, baseURL: config.baseURL) + } + } + } + + /// Configure Grok models with specific settings + public func configureGrok(_ config: ProviderConfiguration.Grok) { + let models = [ + // Grok 4 series + "grok-4", + "grok-4-0709", + "grok-4-latest", + + // Grok 3 series + "grok-3", + "grok-3-mini", + "grok-3-fast", + "grok-3-mini-fast", + + // Grok 2 series + "grok-2-1212", + "grok-2-vision-1212", + "grok-2-image-1212", + + // Beta models + "grok-beta", + "grok-vision-beta", + ] + + for modelName in models { + self.register(modelName: modelName) { + GrokModel( + apiKey: config.apiKey, + modelName: modelName, + baseURL: config.baseURL ?? URL(string: "https://api.x.ai/v1")!) + } + } + } + + /// Quick setup with API key from environment + public func setupFromEnvironment() async throws { + if let apiKey = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] { + self.configureOpenAI(ProviderConfiguration.OpenAI(apiKey: apiKey)) + } + + if let apiKey = ProcessInfo.processInfo.environment["ANTHROPIC_API_KEY"] { + self.configureAnthropic(ProviderConfiguration.Anthropic(apiKey: apiKey)) + } + + // Configure Ollama (no API key needed) + let ollamaBaseURL = ProcessInfo.processInfo.environment["TACHIKOMA_OLLAMA_BASE_URL"] ?? "http://localhost:11434" + if let baseURL = URL(string: ollamaBaseURL) { + self.configureOllama(ProviderConfiguration.Ollama(baseURL: baseURL)) + } + + // Configure Grok with various API key options + if let apiKey = ProcessInfo.processInfo.environment["X_AI_API_KEY"] ?? + ProcessInfo.processInfo.environment["XAI_API_KEY"] + { + self.configureGrok(ProviderConfiguration.Grok(apiKey: apiKey)) + } + } +} \ No newline at end of file diff --git a/Sources/Core/StreamingTypes.swift b/Sources/Core/StreamingTypes.swift new file mode 100644 index 0000000..66202f4 --- /dev/null +++ b/Sources/Core/StreamingTypes.swift @@ -0,0 +1,381 @@ +import Foundation + +// MARK: - Streaming Event Types + +/// Base protocol for all streaming events +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public protocol StreamingEvent: Codable, Sendable { + var type: StreamEventType { get } +} + +/// Types of streaming events +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum StreamEventType: String, Codable, Sendable { + case textDelta = "text_delta" + case responseStarted = "response_started" + case responseCompleted = "response_completed" + case toolCallDelta = "tool_call_delta" + case toolCallCompleted = "tool_call_completed" + case functionCallArgumentsDelta = "function_call_arguments_delta" + case error + case unknown + case reasoningSummaryDelta = "reasoning_summary_delta" + case reasoningSummaryCompleted = "reasoning_summary_completed" +} + +/// Main streaming event enum that encompasses all event types +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum StreamEvent: Codable, Sendable { + case textDelta(StreamTextDelta) + case responseStarted(StreamResponseStarted) + case responseCompleted(StreamResponseCompleted) + case toolCallDelta(StreamToolCallDelta) + case toolCallCompleted(StreamToolCallCompleted) + case functionCallArgumentsDelta(StreamFunctionCallArgumentsDelta) + case error(StreamError) + case unknown(StreamUnknown) + case reasoningSummaryDelta(StreamReasoningSummaryDelta) + case reasoningSummaryCompleted(StreamReasoningSummaryCompleted) + + // Custom coding for the enum + enum CodingKeys: String, CodingKey { + case type, data + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(StreamEventType.self, forKey: .type) + + switch type { + case .textDelta: + let data = try container.decode(StreamTextDelta.self, forKey: .data) + self = .textDelta(data) + case .responseStarted: + let data = try container.decode(StreamResponseStarted.self, forKey: .data) + self = .responseStarted(data) + case .responseCompleted: + let data = try container.decode(StreamResponseCompleted.self, forKey: .data) + self = .responseCompleted(data) + case .toolCallDelta: + let data = try container.decode(StreamToolCallDelta.self, forKey: .data) + self = .toolCallDelta(data) + case .toolCallCompleted: + let data = try container.decode(StreamToolCallCompleted.self, forKey: .data) + self = .toolCallCompleted(data) + case .functionCallArgumentsDelta: + let data = try container.decode(StreamFunctionCallArgumentsDelta.self, forKey: .data) + self = .functionCallArgumentsDelta(data) + case .error: + let data = try container.decode(StreamError.self, forKey: .data) + self = .error(data) + case .unknown: + let data = try container.decode(StreamUnknown.self, forKey: .data) + self = .unknown(data) + case .reasoningSummaryDelta: + let data = try container.decode(StreamReasoningSummaryDelta.self, forKey: .data) + self = .reasoningSummaryDelta(data) + case .reasoningSummaryCompleted: + let data = try container.decode(StreamReasoningSummaryCompleted.self, forKey: .data) + self = .reasoningSummaryCompleted(data) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .textDelta(data): + try container.encode(StreamEventType.textDelta, forKey: .type) + try container.encode(data, forKey: .data) + case let .responseStarted(data): + try container.encode(StreamEventType.responseStarted, forKey: .type) + try container.encode(data, forKey: .data) + case let .responseCompleted(data): + try container.encode(StreamEventType.responseCompleted, forKey: .type) + try container.encode(data, forKey: .data) + case let .toolCallDelta(data): + try container.encode(StreamEventType.toolCallDelta, forKey: .type) + try container.encode(data, forKey: .data) + case let .toolCallCompleted(data): + try container.encode(StreamEventType.toolCallCompleted, forKey: .type) + try container.encode(data, forKey: .data) + case let .functionCallArgumentsDelta(data): + try container.encode(StreamEventType.functionCallArgumentsDelta, forKey: .type) + try container.encode(data, forKey: .data) + case let .error(data): + try container.encode(StreamEventType.error, forKey: .type) + try container.encode(data, forKey: .data) + case let .unknown(data): + try container.encode(StreamEventType.unknown, forKey: .type) + try container.encode(data, forKey: .data) + case let .reasoningSummaryDelta(data): + try container.encode(StreamEventType.reasoningSummaryDelta, forKey: .type) + try container.encode(data, forKey: .data) + case let .reasoningSummaryCompleted(data): + try container.encode(StreamEventType.reasoningSummaryCompleted, forKey: .type) + try container.encode(data, forKey: .data) + } + } +} + +// MARK: - Concrete Streaming Event Types + +/// Text delta event containing incremental text output +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamTextDelta: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.textDelta + public let delta: String + public let index: Int? + + public init(delta: String, index: Int? = nil) { + self.delta = delta + self.index = index + } +} + +/// Response started event +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamResponseStarted: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.responseStarted + public let id: String + public let model: String? + public let systemFingerprint: String? + + public init(id: String, model: String? = nil, systemFingerprint: String? = nil) { + self.id = id + self.model = model + self.systemFingerprint = systemFingerprint + } +} + +/// Response completed event with final metadata +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamResponseCompleted: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.responseCompleted + public let id: String + public let usage: Usage? + public let finishReason: FinishReason? + + public init(id: String, usage: Usage? = nil, finishReason: FinishReason? = nil) { + self.id = id + self.usage = usage + self.finishReason = finishReason + } +} + +/// Tool call delta event for incremental tool call information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamToolCallDelta: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.toolCallDelta + public let id: String + public let index: Int + public let function: FunctionCallDelta + + public init(id: String, index: Int, function: FunctionCallDelta) { + self.id = id + self.index = index + self.function = function + } +} + +/// Tool call completed event +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamToolCallCompleted: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.toolCallCompleted + public let id: String + public let function: FunctionCall + + public init(id: String, function: FunctionCall) { + self.id = id + self.function = function + } +} + +/// Function call arguments delta event for incremental function call argument information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamFunctionCallArgumentsDelta: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.functionCallArgumentsDelta + public let id: String + public let arguments: String + + public init(id: String, arguments: String) { + self.id = id + self.arguments = arguments + } +} + +/// Error event for stream errors +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamError: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.error + public let error: ErrorDetail + + public init(error: ErrorDetail) { + self.error = error + } +} + +/// Unknown event for forward compatibility +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamUnknown: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.unknown + public let eventType: String + public let rawJSON: [UInt8] + + public init(eventType: String, rawJSON: [UInt8]) { + self.eventType = eventType + self.rawJSON = rawJSON + } + + /// Get the raw data as a dictionary if possible + public func getRawData() throws -> [String: Any]? { + let data = Data(self.rawJSON) + return try JSONSerialization.jsonObject(with: data) as? [String: Any] + } + + /// Get the raw data as a pretty-printed JSON string + public func getRawJSONString() -> String? { + let data = Data(self.rawJSON) + if let json = try? JSONSerialization.jsonObject(with: data), + let prettyData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) + { + return String(data: prettyData, encoding: .utf8) + } + return String(data: data, encoding: .utf8) + } +} + +/// Reasoning summary delta event for o3 models +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamReasoningSummaryDelta: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.reasoningSummaryDelta + public let delta: String + public let index: Int? + + public init(delta: String, index: Int? = nil) { + self.delta = delta + self.index = index + } +} + +/// Reasoning summary completed event for o3 models +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct StreamReasoningSummaryCompleted: StreamingEvent, Codable, Sendable { + public var type = StreamEventType.reasoningSummaryCompleted + public let summary: String + public let reasoningTokens: Int? + + public init(summary: String, reasoningTokens: Int? = nil) { + self.summary = summary + self.reasoningTokens = reasoningTokens + } +} + +// MARK: - Supporting Types + +/// Function call delta for incremental function information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct FunctionCallDelta: Codable, Sendable { + public let name: String? + public let arguments: String? + + public init(name: String? = nil, arguments: String? = nil) { + self.name = name + self.arguments = arguments + } +} + +/// Token usage information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct Usage: Codable, Sendable { + public let promptTokens: Int + public let completionTokens: Int + public let totalTokens: Int + public let promptTokensDetails: TokenDetails? + public let completionTokensDetails: TokenDetails? + + public init( + promptTokens: Int, + completionTokens: Int, + totalTokens: Int, + promptTokensDetails: TokenDetails? = nil, + completionTokensDetails: TokenDetails? = nil) + { + self.promptTokens = promptTokens + self.completionTokens = completionTokens + self.totalTokens = totalTokens + self.promptTokensDetails = promptTokensDetails + self.completionTokensDetails = completionTokensDetails + } +} + +/// Detailed token usage breakdown +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct TokenDetails: Codable, Sendable { + public let cachedTokens: Int? + public let audioTokens: Int? + public let reasoningTokens: Int? + + public init(cachedTokens: Int? = nil, audioTokens: Int? = nil, reasoningTokens: Int? = nil) { + self.cachedTokens = cachedTokens + self.audioTokens = audioTokens + self.reasoningTokens = reasoningTokens + } +} + +/// Reason why the response finished +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum FinishReason: String, Codable, Sendable { + case stop + case length + case toolCalls = "tool_calls" + case contentFilter = "content_filter" + case functionCall = "function_call" +} + +/// Error detail information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct ErrorDetail: Codable, Sendable { + public let message: String + public let type: String? + public let code: String? + public let param: String? + + public init(message: String, type: String? = nil, code: String? = nil, param: String? = nil) { + self.message = message + self.type = type + self.code = code + self.param = param + } +} + +// MARK: - Stream Event Extensions + +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +extension StreamEvent { + /// Check if this is a final event + public var isFinal: Bool { + switch self { + case .responseCompleted, .error, .reasoningSummaryCompleted: + true + default: + false + } + } + + /// Extract any text content from the event + public var textContent: String? { + switch self { + case let .textDelta(delta): + delta.delta + case let .reasoningSummaryDelta(delta): + delta.delta + case let .reasoningSummaryCompleted(completed): + completed.summary + case let .error(error): + error.error.message + default: + nil + } + } +} \ No newline at end of file diff --git a/Sources/Core/TachikomaError.swift b/Sources/Core/TachikomaError.swift new file mode 100644 index 0000000..871d9d2 --- /dev/null +++ b/Sources/Core/TachikomaError.swift @@ -0,0 +1,202 @@ +@_exported import Foundation + +// MARK: - Tachikoma Error Types + +/// Main error type for the Tachikoma library +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum TachikomaError: Error, LocalizedError, Sendable { + case modelNotFound(String) + case authenticationFailed + case invalidConfiguration(String) + case networkError(underlying: any Error) + case decodingError(underlying: any Error) + case invalidRequest(String) + case apiError(message: String, code: String? = nil) + case timeout + case rateLimited + case insufficientQuota + case modelOverloaded + case contextLengthExceeded + case contentFiltered + case invalidToolCall(String) + case streamingError(String) + case configurationError(String) + + public var errorDescription: String? { + switch self { + case let .modelNotFound(model): + "Model not found: \(model)" + case .authenticationFailed: + "Authentication failed - check your API key" + case let .invalidConfiguration(message): + "Invalid configuration: \(message)" + case let .networkError(underlying): + "Network error: \(underlying.localizedDescription)" + case let .decodingError(underlying): + "Failed to decode response: \(underlying.localizedDescription)" + case let .invalidRequest(message): + "Invalid request: \(message)" + case let .apiError(message, code): + if let code { + "API error (\(code)): \(message)" + } else { + "API error: \(message)" + } + case .timeout: + "Request timed out" + case .rateLimited: + "Rate limited - please slow down requests" + case .insufficientQuota: + "Insufficient quota - check your billing" + case .modelOverloaded: + "Model is currently overloaded - try again later" + case .contextLengthExceeded: + "Context length exceeded - reduce input size" + case .contentFiltered: + "Content was filtered by safety systems" + case let .invalidToolCall(message): + "Invalid tool call: \(message)" + case let .streamingError(message): + "Streaming error: \(message)" + case let .configurationError(message): + "Configuration error: \(message)" + } + } + + public var recoverySuggestion: String? { + switch self { + case .modelNotFound: + "Check available models with listModels() or verify the model name is correct" + case .authenticationFailed: + "Verify your API key is set correctly in environment variables or credentials file" + case .rateLimited: + "Wait a moment before making another request" + case .insufficientQuota: + "Check your billing settings and account quota" + case .modelOverloaded: + "Try using a different model or retry after a delay" + case .contextLengthExceeded: + "Reduce the length of your input messages or use a model with larger context" + case .contentFiltered: + "Modify your input to comply with content policies" + case .timeout: + "Check your network connection and try again" + default: + nil + } + } + + /// Check if this error indicates a temporary condition that might resolve with retry + public var isRetryable: Bool { + switch self { + case .rateLimited, .modelOverloaded, .timeout, .networkError: + true + default: + false + } + } + + /// Check if this error indicates an authentication issue + public var isAuthenticationError: Bool { + switch self { + case .authenticationFailed, .insufficientQuota: + true + default: + false + } + } + + /// Check if this error indicates a client-side issue + public var isClientError: Bool { + switch self { + case .invalidRequest, .invalidConfiguration, .contextLengthExceeded, .contentFiltered, .invalidToolCall: + true + default: + false + } + } +} + +// MARK: - Model Request/Response Errors + +/// Specific errors for model requests and responses +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum ModelError: Error, LocalizedError, Sendable { + case invalidInput(String) + case missingRequiredParameter(String) + case unsupportedParameter(String) + case invalidParameterValue(String, value: String) + case responseTooLarge + case emptyResponse + case malformedResponse(String) + + public var errorDescription: String? { + switch self { + case let .invalidInput(message): + "Invalid input: \(message)" + case let .missingRequiredParameter(param): + "Missing required parameter: \(param)" + case let .unsupportedParameter(param): + "Unsupported parameter: \(param)" + case let .invalidParameterValue(param, value): + "Invalid value for parameter '\(param)': \(value)" + case .responseTooLarge: + "Response exceeds maximum size limit" + case .emptyResponse: + "Received empty response from API" + case let .malformedResponse(message): + "Malformed response: \(message)" + } + } +} + +// MARK: - Streaming Errors + +/// Errors specific to streaming operations +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum StreamingError: Error, LocalizedError, Sendable { + case streamClosed + case invalidEventFormat(String) + case bufferOverflow + case connectionLost + + public var errorDescription: String? { + switch self { + case .streamClosed: + "Stream was closed unexpectedly" + case let .invalidEventFormat(format): + "Invalid event format: \(format)" + case .bufferOverflow: + "Stream buffer overflow" + case .connectionLost: + "Connection to stream was lost" + } + } +} + +// MARK: - Tool Execution Errors + +/// Errors for tool execution +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum ToolExecutionError: Error, LocalizedError, Sendable { + case toolNotFound(String) + case invalidArguments(String) + case executionFailed(String) + case timeout + case missingContext + + public var errorDescription: String? { + switch self { + case let .toolNotFound(name): + "Tool not found: \(name)" + case let .invalidArguments(message): + "Invalid tool arguments: \(message)" + case let .executionFailed(message): + "Tool execution failed: \(message)" + case .timeout: + "Tool execution timed out" + case .missingContext: + "Required context is missing for tool execution" + } + } +} \ No newline at end of file diff --git a/Sources/Core/ToolDefinitions.swift b/Sources/Core/ToolDefinitions.swift new file mode 100644 index 0000000..3fd2969 --- /dev/null +++ b/Sources/Core/ToolDefinitions.swift @@ -0,0 +1,597 @@ +import Foundation + +// MARK: - Tool Definition + +/// A tool that can be used by an agent to perform actions +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct Tool { + /// Unique name of the tool + public let name: String + + /// Description of what the tool does + public let description: String + + /// Parameters the tool accepts + public let parameters: ToolParameters + + /// Whether to use strict parameter validation + public let strict: Bool + + /// The function to execute when the tool is called + public let execute: (ToolInput, Context) async throws -> ToolOutput + + public init( + name: String, + description: String, + parameters: ToolParameters, + strict: Bool = true, + execute: @escaping (ToolInput, Context) async throws -> ToolOutput) + { + self.name = name + self.description = description + self.parameters = parameters + self.strict = strict + self.execute = execute + } + + /// Convert to a tool definition for the model + public func toToolDefinition() -> ToolDefinition { + ToolDefinition( + type: .function, + function: FunctionDefinition( + name: self.name, + description: self.description, + parameters: self.parameters, + strict: self.strict)) + } +} + +// MARK: - Tool Definition Types + +/// Definition of a tool that can be sent to a model +public struct ToolDefinition: Codable, Sendable { + public let type: ToolType + public let function: FunctionDefinition + + public init(type: ToolType = .function, function: FunctionDefinition) { + self.type = type + self.function = function + } +} + +/// Type of tool +public enum ToolType: String, Codable, Sendable { + case function +} + +/// Function definition for a tool +public struct FunctionDefinition: Codable, Sendable { + public let name: String + public let description: String + public let parameters: ToolParameters + public let strict: Bool? + + public init( + name: String, + description: String, + parameters: ToolParameters, + strict: Bool? = nil) + { + self.name = name + self.description = description + self.parameters = parameters + self.strict = strict + } +} + +// MARK: - Tool Parameters + +/// Parameters schema for a tool +public struct ToolParameters: Codable, Sendable { + public let type: String + public let properties: [String: ParameterSchema] + public let required: [String] + public let additionalProperties: Bool + + public init( + type: String = "object", + properties: [String: ParameterSchema] = [:], + required: [String] = [], + additionalProperties: Bool = false) + { + self.type = type + self.properties = properties + self.required = required + self.additionalProperties = additionalProperties + } + + /// Create parameters from a dictionary of property definitions + public static func object( + properties: [String: ParameterSchema], + required: [String] = []) -> ToolParameters + { + ToolParameters( + type: "object", + properties: properties, + required: required, + additionalProperties: false) + } +} + +/// Schema for a single parameter +public struct ParameterSchema: Codable, Sendable { + public let type: ParameterType + public let description: String? + public let enumValues: [String]? + public let items: Box? + public let properties: [String: ParameterSchema]? + public let minimum: Double? + public let maximum: Double? + public let pattern: String? + + public init( + type: ParameterType, + description: String? = nil, + enumValues: [String]? = nil, + items: ParameterSchema? = nil, + properties: [String: ParameterSchema]? = nil, + minimum: Double? = nil, + maximum: Double? = nil, + pattern: String? = nil) + { + self.type = type + self.description = description + self.enumValues = enumValues + self.items = items.map(Box.init) + self.properties = properties + self.minimum = minimum + self.maximum = maximum + self.pattern = pattern + } + + // Convenience initializers + public static func string(description: String? = nil, pattern: String? = nil) -> ParameterSchema { + ParameterSchema(type: .string, description: description, pattern: pattern) + } + + public static func number( + description: String? = nil, + minimum: Double? = nil, + maximum: Double? = nil) -> ParameterSchema + { + ParameterSchema(type: .number, description: description, minimum: minimum, maximum: maximum) + } + + public static func integer( + description: String? = nil, + minimum: Double? = nil, + maximum: Double? = nil) -> ParameterSchema + { + ParameterSchema(type: .integer, description: description, minimum: minimum, maximum: maximum) + } + + public static func boolean(description: String? = nil) -> ParameterSchema { + ParameterSchema(type: .boolean, description: description) + } + + public static func array(of items: ParameterSchema, description: String? = nil) -> ParameterSchema { + ParameterSchema(type: .array, description: description, items: items) + } + + public static func object(properties: [String: ParameterSchema], description: String? = nil) -> ParameterSchema { + ParameterSchema(type: .object, description: description, properties: properties) + } + + public static func enumeration(_ values: [String], description: String? = nil) -> ParameterSchema { + ParameterSchema(type: .string, description: description, enumValues: values) + } + + // Custom coding keys + enum CodingKeys: String, CodingKey { + case type, description + case enumValues = "enum" + case items, properties + case minimum, maximum, pattern + } +} + +/// Parameter types +public enum ParameterType: String, Codable, Sendable { + case string + case number + case integer + case boolean + case array + case object + case null +} + +// MARK: - Tool Input/Output + +/// Input provided to a tool +public enum ToolInput { + case string(String) + case dictionary([String: Any]) + case array([Any]) + case null + + /// Parse from a JSON string + public init(jsonString: String) throws { + // Handle empty string as empty dictionary + if jsonString.isEmpty { + self = .dictionary([:]) + return + } + + guard let data = jsonString.data(using: .utf8) else { + throw ToolError.invalidInput("Invalid JSON string") + } + + let parsed = try JSONSerialization.jsonObject(with: data) + + if let dict = parsed as? [String: Any] { + self = .dictionary(dict) + } else if let array = parsed as? [Any] { + self = .array(array) + } else if let string = parsed as? String { + self = .string(string) + } else { + self = .null + } + } + + /// Get value for a specific key (for dictionary inputs) + public func value(for key: String) -> T? { + guard case let .dictionary(dict) = self else { return nil } + return dict[key] as? T + } + + /// Get the raw string value + public var stringValue: String? { + switch self { + case let .string(str): + return str + case .dictionary, .array: + if let data = try? JSONSerialization.data(withJSONObject: rawValue), + let str = String(data: data, encoding: .utf8) + { + return str + } + return nil + case .null: + return nil + } + } + + /// Get the raw value + public var rawValue: Any { + switch self { + case let .string(str): + str + case let .dictionary(dict): + dict + case let .array(array): + array + case .null: + NSNull() + } + } +} + +/// Strongly-typed output from a tool +public enum ToolOutput: Codable, Sendable { + case string(String) + case number(Double) + case boolean(Bool) + case object([String: ToolOutput]) + case array([ToolOutput]) + case null + case error(message: String, code: String? = nil) + + // MARK: - Codable Implementation + + private enum CodingKeys: String, CodingKey { + case type, value, message, code + } + + private enum OutputType: String, Codable { + case string, number, boolean, object, array, null, error + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(OutputType.self, forKey: .type) + + switch type { + case .string: + let value = try container.decode(String.self, forKey: .value) + self = .string(value) + case .number: + let value = try container.decode(Double.self, forKey: .value) + self = .number(value) + case .boolean: + let value = try container.decode(Bool.self, forKey: .value) + self = .boolean(value) + case .object: + let value = try container.decode([String: ToolOutput].self, forKey: .value) + self = .object(value) + case .array: + let value = try container.decode([ToolOutput].self, forKey: .value) + self = .array(value) + case .null: + self = .null + case .error: + let message = try container.decode(String.self, forKey: .message) + let code = try container.decodeIfPresent(String.self, forKey: .code) + self = .error(message: message, code: code) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .string(value): + try container.encode(OutputType.string, forKey: .type) + try container.encode(value, forKey: .value) + case let .number(value): + try container.encode(OutputType.number, forKey: .type) + try container.encode(value, forKey: .value) + case let .boolean(value): + try container.encode(OutputType.boolean, forKey: .type) + try container.encode(value, forKey: .value) + case let .object(value): + try container.encode(OutputType.object, forKey: .type) + try container.encode(value, forKey: .value) + case let .array(value): + try container.encode(OutputType.array, forKey: .type) + try container.encode(value, forKey: .value) + case .null: + try container.encode(OutputType.null, forKey: .type) + case let .error(message, code): + try container.encode(OutputType.error, forKey: .type) + try container.encode(message, forKey: .message) + try container.encodeIfPresent(code, forKey: .code) + } + } + + // MARK: - Conversion Methods + + /// Convert to JSON string for the model + public func toJSONString() throws -> String { + switch self { + case let .string(str): + return str // Return string directly for text output + case let .error(message, code): + // Special handling for errors to match expected format + var errorDict: [String: ToolOutput] = ["error": .string(message)] + if let code { + errorDict["error_code"] = .string(code) + } + let data = try JSONEncoder().encode(ToolOutput.object(errorDict)) + guard let string = String(data: data, encoding: .utf8) else { + throw ToolError.serializationFailed + } + return string + default: + // For all other types, encode normally + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(self) + guard let string = String(data: data, encoding: .utf8) else { + throw ToolError.serializationFailed + } + return string + } + } + + /// Convert to a dictionary representation (for compatibility) + public func toDictionary() -> [String: Any]? { + switch self { + case let .object(dict): + var result: [String: Any] = [:] + for (key, value) in dict { + if let converted = value.toAny() { + result[key] = converted + } + } + return result + default: + return nil + } + } + + /// Convert to Any (for legacy compatibility) + private func toAny() -> Any? { + switch self { + case let .string(value): + return value + case let .number(value): + return value + case let .boolean(value): + return value + case let .object(dict): + var result: [String: Any] = [:] + for (key, value) in dict { + if let converted = value.toAny() { + result[key] = converted + } + } + return result + case let .array(array): + return array.compactMap { $0.toAny() } + case .null: + return NSNull() + case let .error(message, _): + return ["error": message] + } + } +} + +// MARK: - Builder Methods + +extension ToolOutput { + /// Create a dictionary/object output using a builder pattern + public static func dictionary(_ builder: () -> [String: ToolOutput]) -> ToolOutput { + .object(builder()) + } + + /// Create a dictionary/object output from key-value pairs + public static func dictionary(_ pairs: (String, ToolOutput)...) -> ToolOutput { + var dict: [String: ToolOutput] = [:] + for (key, value) in pairs { + dict[key] = value + } + return .object(dict) + } + + /// Create from a Swift dictionary with automatic type conversion + public static func from(_ dict: [String: Any]) -> ToolOutput { + var result: [String: ToolOutput] = [:] + for (key, value) in dict { + result[key] = self.from(value) + } + return .object(result) + } + + /// Create from any Swift value with automatic type conversion + public static func from(_ value: Any) -> ToolOutput { + switch value { + case let str as String: + .string(str) + case let num as Int: + .number(Double(num)) + case let num as Double: + .number(num) + case let bool as Bool: + .boolean(bool) + case let dict as [String: Any]: + self.from(dict) + case let array as [Any]: + .array(array.map { self.from($0) }) + case is NSNull: + .null + default: + // Fallback to string representation + .string(String(describing: value)) + } + } + + /// Convenience method for success results + public static func success(_ message: String, metadata: (String, ToolOutput)...) -> ToolOutput { + var dict: [String: ToolOutput] = ["result": .string(message)] + for (key, value) in metadata { + dict[key] = value + } + return .object(dict) + } +} + +// MARK: - Tool Errors + +/// Errors that can occur during tool execution +public enum ToolError: Error, LocalizedError, Sendable { + case invalidInput(String) + case executionFailed(String) + case serializationFailed + case contextMissing + case toolNotFound(String) + + public var errorDescription: String? { + switch self { + case let .invalidInput(message): + "Invalid tool input: \(message)" + case let .executionFailed(message): + "Tool execution failed: \(message)" + case .serializationFailed: + "Failed to serialize tool output" + case .contextMissing: + "Required context is missing" + case let .toolNotFound(name): + "Tool not found: \(name)" + } + } +} + +// MARK: - Helper Types + +/// Box type for recursive data structures +public final class Box: Codable, Sendable { + public let value: T + + public init(_ value: T) { + self.value = value + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + self.value = try container.decode(T.self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.value) + } +} + +// MARK: - Tool Builder + +/// Builder pattern for creating tools +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct ToolBuilder { + private var name: String = "" + private var description: String = "" + private var parameters: ToolParameters = .init() + private var strict: Bool = true + private var execute: ((ToolInput, Context) async throws -> ToolOutput)? + + public init() {} + + public func withName(_ name: String) -> ToolBuilder { + var builder = self + builder.name = name + return builder + } + + public func withDescription(_ description: String) -> ToolBuilder { + var builder = self + builder.description = description + return builder + } + + public func withParameters(_ parameters: ToolParameters) -> ToolBuilder { + var builder = self + builder.parameters = parameters + return builder + } + + public func withStrict(_ strict: Bool) -> ToolBuilder { + var builder = self + builder.strict = strict + return builder + } + + public func withExecution(_ execute: @escaping (ToolInput, Context) async throws -> ToolOutput) + -> ToolBuilder { + var builder = self + builder.execute = execute + return builder + } + + public func build() throws -> Tool { + guard !self.name.isEmpty else { + throw ToolError.invalidInput("Tool name is required") + } + + guard let execute else { + throw ToolError.invalidInput("Tool execution function is required") + } + + return Tool( + name: self.name, + description: self.description, + parameters: self.parameters, + strict: self.strict, + execute: execute) + } +} \ No newline at end of file diff --git a/Sources/Providers/Anthropic/AnthropicModel.swift b/Sources/Providers/Anthropic/AnthropicModel.swift new file mode 100644 index 0000000..151106a --- /dev/null +++ b/Sources/Providers/Anthropic/AnthropicModel.swift @@ -0,0 +1,642 @@ +import Foundation + +/// Anthropic model implementation conforming to ModelInterface +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public final class AnthropicModel: ModelInterface, Sendable { + private let apiKey: String + private let baseURL: URL + private let session: URLSession + private let anthropicVersion: String + private let modelName: String? + private let customHeaders: [String: String]? + + public init( + apiKey: String, + baseURL: URL = URL(string: "https://api.anthropic.com/v1")!, + anthropicVersion: String = "2023-06-01", + modelName: String? = nil, + session: URLSession? = nil) + { + self.apiKey = apiKey + self.baseURL = baseURL + self.anthropicVersion = anthropicVersion + self.modelName = modelName + self.customHeaders = nil + + if let session { + self.session = session + } else { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 120 // 2 minutes + config.timeoutIntervalForResource = 120 + self.session = URLSession(configuration: config) + } + } + + /// Initialize with custom provider configuration + public init( + apiKey: String, + baseURL: String, + modelName: String? = nil, + headers: [String: String]? = nil, + anthropicVersion: String = "2023-06-01", + session: URLSession? = nil) + { + self.apiKey = apiKey + self.baseURL = URL(string: baseURL) ?? URL(string: "https://api.anthropic.com/v1")! + self.anthropicVersion = anthropicVersion + self.modelName = modelName + self.customHeaders = headers + + if let session { + self.session = session + } else { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 120 // 2 minutes + config.timeoutIntervalForResource = 120 + self.session = URLSession(configuration: config) + } + } + + // MARK: - ModelInterface Implementation + + public var maskedApiKey: String { + guard self.apiKey.count > 8 else { return "***" } + let start = self.apiKey.prefix(6) + let end = self.apiKey.suffix(2) + return "\(start)...\(end)" + } + + public func getResponse(request: ModelRequest) async throws -> ModelResponse { + let anthropicRequest = try convertToAnthropicRequest(request, stream: false) + let urlRequest = try createURLRequest(endpoint: "messages", body: anthropicRequest) + + let (data, response) = try await session.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw TachikomaError.networkError(underlying: URLError(.badServerResponse)) + } + + if httpResponse.statusCode != 200 { + try handleErrorResponse(data: data, response: httpResponse) + } + + do { + let anthropicResponse = try JSONDecoder().decode(AnthropicResponse.self, from: data) + return try self.convertFromAnthropicResponse(anthropicResponse) + } catch { + throw TachikomaError.decodingError(underlying: error) + } + } + + public func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + let anthropicRequest = try convertToAnthropicRequest(request, stream: true) + let urlRequest = try createURLRequest(endpoint: "messages", body: anthropicRequest) + + return AsyncThrowingStream { continuation in + Task { + do { + let (bytes, response) = try await session.bytes(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + continuation.finish(throwing: TachikomaError.networkError(underlying: URLError(.badServerResponse))) + return + } + + if httpResponse.statusCode != 200 { + var errorData = Data() + for try await byte in bytes.prefix(1024) { + errorData.append(byte) + } + try self.handleErrorResponse(data: errorData, response: httpResponse) + } + + // Process SSE stream + var currentToolCalls: [String: PartialToolCall] = [:] + var responseId: String? + var accumulatedText = "" + var currentContentIndex = 0 + var pendingUsage: Usage? + + for try await line in bytes.lines { + // Skip empty lines + if line.isEmpty { + continue + } + + // Handle SSE format + if line.hasPrefix("data: ") { + let data = String(line.dropFirst(6)) + + // Parse the event + if let eventData = data.data(using: .utf8) { + do { + let event = try JSONDecoder().decode( + AnthropicStreamEvent.self, + from: eventData) + + switch event.type { + case "message_start": + if let message = event.message { + responseId = message.id + continuation.yield(.responseStarted(StreamResponseStarted( + id: message.id, + model: message.model, + systemFingerprint: nil))) + } + + case "content_block_start": + currentContentIndex = event.index ?? 0 + if let block = event.contentBlock { + if block.type == "tool_use", let id = block.id, let name = block.name { + // Start tracking this tool call + let partialCall = PartialToolCall( + id: id, + name: name, + index: currentContentIndex) + currentToolCalls[id] = partialCall + } + } + + case "content_block_delta": + if let delta = event.delta { + if let text = delta.text { + // Text delta + continuation.yield(.textDelta(StreamTextDelta( + delta: text, + index: currentContentIndex))) + accumulatedText += text + } else if let partialJson = delta.partialJson { + // Tool use arguments delta + // Find the tool call being updated + if let toolCall = currentToolCalls.values + .first(where: { $0.index == currentContentIndex }) + { + toolCall.appendArguments(partialJson) + continuation.yield(.toolCallDelta(StreamToolCallDelta( + id: toolCall.id, + index: toolCall.index, + function: FunctionCallDelta( + name: toolCall.name, + arguments: partialJson)))) + } + } + } + + case "content_block_stop": + // Complete any tool calls at this index + for (id, toolCall) in currentToolCalls { + if toolCall.index == currentContentIndex { + if let completed = toolCall.toCompleted() { + continuation.yield(.toolCallCompleted( + StreamToolCallCompleted(id: id, function: completed))) + } + } + } + + case "message_delta": + // Skip regular parsing - will handle in catch block for usage data + break + + case "message_stop": + // Final completion - emit responseCompleted with usage if available + if let id = responseId { + continuation.yield(.responseCompleted(StreamResponseCompleted( + id: id, + usage: pendingUsage, + finishReason: .stop))) + } + continuation.finish() + return + + case "error": + if let error = event.error { + continuation.finish(throwing: TachikomaError.apiError( + message: error.message)) + return + } + + default: + // Unknown event type, ignore + break + } + } catch { + // Special handling for message_delta events with usage + if let jsonData = data.data(using: .utf8) { + do { + if let json = try JSONSerialization + .jsonObject(with: jsonData) as? [String: Any] + { + if json["type"] as? String == "message_delta" { + if let usage = json["usage"] as? [String: Any] { + if let outputTokens = usage["output_tokens"] as? Int { + // Extract input tokens if available + let inputTokens = usage["input_tokens"] as? Int ?? 0 + + // Create Usage object + let tokenUsage = Usage( + promptTokens: inputTokens, + completionTokens: outputTokens, + totalTokens: inputTokens + outputTokens, + promptTokensDetails: nil, + completionTokensDetails: nil) + + // Store the usage for later + pendingUsage = tokenUsage + } + } + } + } + } catch { + // Failed to parse JSON, ignore + } + } + } + } + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: TachikomaError.streamingError(error.localizedDescription)) + } + } + } + } + + // MARK: - Private Methods + + private func createURLRequest(endpoint: String, body: any Encodable) throws -> URLRequest { + let url = self.baseURL.appendingPathComponent(endpoint) + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue(self.apiKey, forHTTPHeaderField: "x-api-key") + request.setValue(self.anthropicVersion, forHTTPHeaderField: "anthropic-version") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + // Add custom headers from provider configuration + customHeaders?.forEach { key, value in + request.setValue(value, forHTTPHeaderField: key) + } + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + do { + request.httpBody = try encoder.encode(body) + } catch { + throw TachikomaError.configurationError("Failed to encode Anthropic request: \(error.localizedDescription)") + } + + request.timeoutInterval = 120 + + return request + } + + private func convertToAnthropicRequest(_ request: ModelRequest, stream: Bool) throws -> AnthropicRequest { + var anthropicMessages: [AnthropicMessage] = [] + var systemPrompt: String? = nil + + // Convert messages + for message in request.messages { + switch message { + case let .system(_, content): + // Anthropic uses a separate system parameter + if systemPrompt == nil { + systemPrompt = content + } else { + systemPrompt = (systemPrompt ?? "") + "\n\n" + content + } + + case let .user(_, content): + let anthropicMessage = try convertUserMessage(content) + anthropicMessages.append(anthropicMessage) + + case let .assistant(_, content, status): + let anthropicMessage = try convertAssistantMessage(content, status: status) + anthropicMessages.append(anthropicMessage) + + case let .tool(_, toolCallId, content): + // Convert tool result to user message with tool_result content block + let toolResultBlock = AnthropicContentBlock.toolResult( + toolUseId: toolCallId, + content: content) + anthropicMessages.append(AnthropicMessage( + role: .user, + content: .array([toolResultBlock]))) + + case let .reasoning(_, content): + // Treat reasoning as a system message for now + if systemPrompt == nil { + systemPrompt = "[Reasoning] " + content + } else { + systemPrompt = (systemPrompt ?? "") + "\n\n[Reasoning] " + content + } + } + } + + // Convert tools + let tools = request.tools?.map { toolDef -> AnthropicTool in + AnthropicTool( + name: toolDef.function.name, + description: toolDef.function.description, + inputSchema: self.convertToolParameters(toolDef.function.parameters)) + } + + // Convert tool choice + let toolChoice = self.convertToolChoice(request.settings.toolChoice) + + // Create system content with cache control + let systemContent: AnthropicSystemContent? = if let systemPrompt { + // Use array format with cache control for system prompt + .array([ + AnthropicSystemBlock( + type: "text", + text: systemPrompt, + cacheControl: AnthropicCacheControl(type: "ephemeral")), + ]) + } else { + nil + } + + return AnthropicRequest( + model: self.modelName ?? request.settings.modelName, + messages: anthropicMessages, + system: systemContent, + maxTokens: request.settings.maxTokens ?? 4096, + temperature: request.settings.temperature, + topP: request.settings.topP, + topK: request.settings.additionalParameters?.int("top_k"), + stream: stream, + stopSequences: request.settings.stopSequences, + tools: tools, + toolChoice: toolChoice, + metadata: request.settings.user.map { AnthropicMetadata(userId: $0) }) + } + + private func convertUserMessage(_ content: MessageContent) throws -> AnthropicMessage { + switch content { + case let .text(text): + return AnthropicMessage(role: .user, content: .string(text)) + + case let .image(imageContent): + var blocks: [AnthropicContentBlock] = [] + + if let base64 = imageContent.base64 { + blocks.append(.image(base64: base64, mediaType: "image/jpeg")) + } else if imageContent.url != nil { + // For URLs, we'd need to download and convert to base64 + // For now, throw an error + throw TachikomaError.invalidRequest("Image URLs not supported - please provide base64 data") + } + + return AnthropicMessage(role: .user, content: .array(blocks)) + + case let .multimodal(parts): + let blocks = try parts.compactMap { part -> AnthropicContentBlock? in + if let text = part.text { + return .text(text) + } else if let image = part.imageUrl { + if let base64 = image.base64 { + return .image(base64: base64, mediaType: "image/jpeg") + } else if image.url != nil { + throw TachikomaError.invalidRequest("Image URLs not supported - please provide base64 data") + } + } + return nil + } + return AnthropicMessage(role: .user, content: .array(blocks)) + + case .file: + throw TachikomaError.invalidRequest("File content not supported in Anthropic API") + + case let .audio(audioContent): + // Claude doesn't support native audio, so we need to use the transcript + if let transcript = audioContent.transcript { + // Include metadata about the audio source + var text = transcript + if let duration = audioContent.duration { + text = "[Audio transcript, duration: \(Int(duration))s] \(transcript)" + } else { + text = "[Audio transcript] \(transcript)" + } + return AnthropicMessage(role: .user, content: .string(text)) + } else { + throw TachikomaError.invalidRequest("Audio content must be transcribed before sending to Claude. Please ensure transcript is provided.") + } + } + } + + private func convertAssistantMessage( + _ content: [AssistantContent], + status: MessageStatus) throws -> AnthropicMessage + { + var blocks: [AnthropicContentBlock] = [] + + for content in content { + switch content { + case let .outputText(text): + blocks.append(.text(text)) + + case let .refusal(refusal): + blocks.append(.text(refusal)) + + case let .toolCall(toolCall): + // Parse arguments as JSON + let arguments: [String: Any] = if let data = toolCall.function.arguments.data(using: .utf8), + let json = try? JSONSerialization + .jsonObject(with: data) as? [String: Any] + { + json + } else { + [:] + } + + blocks.append(.toolUse( + id: toolCall.id, + name: toolCall.function.name, + input: arguments)) + } + } + + return AnthropicMessage(role: .assistant, content: .array(blocks)) + } + + private func convertToolParameters(_ params: ToolParameters) -> AnthropicJSONSchema { + var properties: [String: AnthropicPropertySchema] = [:] + + for (key, schema) in params.properties { + properties[key] = self.convertParameterSchema(schema) + } + + return AnthropicJSONSchema( + type: params.type, + properties: properties, + required: params.required) + } + + private func convertParameterSchema(_ schema: ParameterSchema) -> AnthropicPropertySchema { + // Handle nested items for arrays + let items: AnthropicPropertySchema? = if schema.type == .array, let schemaItems = schema.items { + self.convertParameterSchema(schemaItems.value) + } else { + nil + } + + // Handle nested properties for objects + let properties: [String: AnthropicPropertySchema]? + if schema.type == .object, let schemaProps = schema.properties { + var convertedProps: [String: AnthropicPropertySchema] = [:] + for (key, nestedSchema) in schemaProps { + convertedProps[key] = self.convertParameterSchema(nestedSchema) + } + properties = convertedProps + } else { + properties = nil + } + + return AnthropicPropertySchema( + type: schema.type.rawValue, + description: schema.description, + enum: schema.enumValues, + items: items, + properties: properties, + required: nil // ParameterSchema doesn't have required field at this level + ) + } + + private func convertToolChoice(_ toolChoice: ToolChoice?) -> AnthropicToolChoice? { + guard let toolChoice else { return nil } + + switch toolChoice { + case .auto: + return .auto + case .none: + return nil // Anthropic doesn't have a "none" option + case .required: + return .any + case let .specific(toolName): + return .tool(name: toolName) + } + } + + private func convertFromAnthropicResponse(_ response: AnthropicResponse) throws -> ModelResponse { + var content: [AssistantContent] = [] + + // Convert content blocks + for block in response.content { + switch block.type { + case "text": + if let text = block.text { + content.append(.outputText(text)) + } + + case "tool_use": + if let id = block.id, + let name = block.name, + let input = block.input + { + // Convert input dictionary to JSON string + let arguments: String = if let data = try? JSONSerialization.data(withJSONObject: input.mapValues { $0.toAny() }), + let json = String(data: data, encoding: .utf8) + { + json + } else { + "{}" + } + + content.append(.toolCall(ToolCallItem( + id: id, + type: .function, + function: FunctionCall( + name: name, + arguments: arguments)))) + } + + default: + // Unknown content block type, ignore + break + } + } + + let usage = Usage( + promptTokens: response.usage.inputTokens, + completionTokens: response.usage.outputTokens, + totalTokens: response.usage.inputTokens + response.usage.outputTokens, + promptTokensDetails: nil, + completionTokensDetails: nil) + + return ModelResponse( + id: response.id, + model: response.model, + content: content, + usage: usage, + flagged: false, + finishReason: self.convertStopReason(response.stopReason)) + } + + private func convertStopReason(_ reason: String?) -> FinishReason? { + guard let reason else { return nil } + + switch reason { + case "end_turn": + return .stop + case "max_tokens": + return .length + case "stop_sequence": + return .stop + case "tool_use": + return .toolCalls + default: + return .stop + } + } + + private func handleErrorResponse(data: Data, response: HTTPURLResponse) throws { + if let errorResponse = try? JSONDecoder().decode(AnthropicErrorResponse.self, from: data) { + let message = errorResponse.error.message + + switch response.statusCode { + case 401: + throw TachikomaError.authenticationFailed + case 429: + throw TachikomaError.rateLimited + case 400: + if message.contains("credit") || message.contains("usage") { + throw TachikomaError.insufficientQuota + } else { + throw TachikomaError.invalidRequest(message) + } + case 500...599: + throw TachikomaError.modelOverloaded + default: + throw TachikomaError.apiError(message: message) + } + } else { + throw TachikomaError.apiError(message: "HTTP \(response.statusCode)") + } + } +} + +// MARK: - Helper Types + +private class PartialToolCall { + let id: String + let name: String + let index: Int + var arguments: String = "" + + init(id: String, name: String, index: Int) { + self.id = id + self.name = name + self.index = index + } + + func appendArguments(_ args: String) { + self.arguments += args + } + + func toCompleted() -> FunctionCall? { + FunctionCall(name: self.name, arguments: self.arguments) + } +} \ No newline at end of file diff --git a/Sources/Providers/Anthropic/AnthropicTypes.swift b/Sources/Providers/Anthropic/AnthropicTypes.swift new file mode 100644 index 0000000..1e91af9 --- /dev/null +++ b/Sources/Providers/Anthropic/AnthropicTypes.swift @@ -0,0 +1,678 @@ +import Foundation + +// MARK: - Anthropic API Request Types + +/// Cache control configuration +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicCacheControl: Codable, Sendable { + public let type: String // "ephemeral" + + public init(type: String = "ephemeral") { + self.type = type + } +} + +/// System content that can be cached +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum AnthropicSystemContent: Codable, Sendable { + case string(String) + case array([AnthropicSystemBlock]) + + // Custom encoding/decoding + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let arrayValue = try? container.decode([AnthropicSystemBlock].self) { + self = .array(arrayValue) + } else { + throw DecodingError.typeMismatch( + AnthropicSystemContent.self, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Expected String or Array of system blocks")) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(value): + try container.encode(value) + case let .array(blocks): + try container.encode(blocks) + } + } +} + +/// System block that can contain cache control +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicSystemBlock: Codable, Sendable { + public let type: String // "text" + public let text: String + public let cacheControl: AnthropicCacheControl? + + enum CodingKeys: String, CodingKey { + case type, text + case cacheControl = "cache_control" + } + + public init(type: String = "text", text: String, cacheControl: AnthropicCacheControl? = nil) { + self.type = type + self.text = text + self.cacheControl = cacheControl + } +} + +/// Main request structure for Anthropic's Messages API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicRequest: Codable, Sendable { + /// ID of the model to use (e.g., "claude-3-opus-20240229") + public let model: String + + /// Input messages + public let messages: [AnthropicMessage] + + /// System prompt (separate from messages) + public let system: AnthropicSystemContent? + + /// Maximum number of tokens to generate + public let maxTokens: Int + + /// Temperature for randomness (0.0 to 1.0) + public let temperature: Double? + + /// Top-p sampling parameter + public let topP: Double? + + /// Top-k sampling parameter + public let topK: Int? + + /// Whether to stream the response + public let stream: Bool? + + /// Stop sequences + public let stopSequences: [String]? + + /// Available tools + public let tools: [AnthropicTool]? + + /// Tool choice configuration + public let toolChoice: AnthropicToolChoice? + + /// Metadata about the request + public let metadata: AnthropicMetadata? + + enum CodingKeys: String, CodingKey { + case model, messages, system + case maxTokens = "max_tokens" + case temperature + case topP = "top_p" + case topK = "top_k" + case stream + case stopSequences = "stop_sequences" + case tools + case toolChoice = "tool_choice" + case metadata + } +} + +/// Anthropic message structure +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicMessage: Codable, Sendable { + /// Role of the message sender + public let role: AnthropicRole + + /// Content of the message + public let content: AnthropicContent + + public init(role: AnthropicRole, content: AnthropicContent) { + self.role = role + self.content = content + } +} + +/// Message roles in Anthropic API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum AnthropicRole: String, Codable, Sendable { + case user + case assistant +} + +/// Content types for Anthropic messages +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum AnthropicContent: Codable, Sendable { + case string(String) + case array([AnthropicContentBlock]) + + // Custom encoding/decoding + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let arrayValue = try? container.decode([AnthropicContentBlock].self) { + self = .array(arrayValue) + } else { + throw DecodingError.typeMismatch( + AnthropicContent.self, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Expected String or Array of content blocks")) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(value): + try container.encode(value) + case let .array(blocks): + try container.encode(blocks) + } + } +} + +/// Content block for multimodal messages +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicContentBlock: Codable, Sendable { + public let type: String + + // Text content + public let text: String? + + // Image content + public let source: AnthropicImageSource? + + // Tool use content + public let id: String? + public let name: String? + public let input: [String: AnthropicInputValue]? + + // Tool result content + public let toolUseId: String? + public let content: AnthropicContent? + public let isError: Bool? + + // Cache control + public let cacheControl: AnthropicCacheControl? + + enum CodingKeys: String, CodingKey { + case type, text, source, id, name, input + case toolUseId = "tool_use_id" + case content + case isError = "is_error" + case cacheControl = "cache_control" + } +} + +/// Image source for content blocks +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicImageSource: Codable, Sendable { + public let type: String // "base64" + public let mediaType: String // "image/jpeg", "image/png", etc. + public let data: String // base64 encoded image data + + enum CodingKeys: String, CodingKey { + case type + case mediaType = "media_type" + case data + } +} + +// MARK: - Tool Definitions + +/// Tool definition for Anthropic +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicTool: Codable, Sendable { + public let name: String + public let description: String + public let inputSchema: AnthropicJSONSchema + public let cacheControl: AnthropicCacheControl? + + enum CodingKeys: String, CodingKey { + case name, description + case inputSchema = "input_schema" + case cacheControl = "cache_control" + } + + public init( + name: String, + description: String, + inputSchema: AnthropicJSONSchema, + cacheControl: AnthropicCacheControl? = nil) + { + self.name = name + self.description = description + self.inputSchema = inputSchema + self.cacheControl = cacheControl + } +} + +/// JSON Schema for tool parameters +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicJSONSchema: Codable, Sendable { + public let type: String + public let properties: [String: AnthropicPropertySchema]? + public let required: [String]? + public let description: String? + + public init( + type: String = "object", + properties: [String: AnthropicPropertySchema]? = nil, + required: [String]? = nil, + description: String? = nil) + { + self.type = type + self.properties = properties + self.required = required + self.description = description + } +} + +/// Tool choice configuration +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum AnthropicToolChoice: Codable, Sendable { + case auto + case any + case tool(name: String) + + // Custom encoding/decoding + enum CodingKeys: String, CodingKey { + case type, name + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(String.self, forKey: .type) + + switch type { + case "auto": + self = .auto + case "any": + self = .any + case "tool": + let name = try container.decode(String.self, forKey: .name) + self = .tool(name: name) + default: + throw DecodingError.dataCorruptedError( + forKey: .type, + in: container, + debugDescription: "Unknown tool choice type: \(type)") + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .auto: + try container.encode("auto", forKey: .type) + case .any: + try container.encode("any", forKey: .type) + case let .tool(name): + try container.encode("tool", forKey: .type) + try container.encode(name, forKey: .name) + } + } +} + +/// Metadata for requests +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicMetadata: Codable, Sendable { + public let userId: String? + + enum CodingKeys: String, CodingKey { + case userId = "user_id" + } +} + +// MARK: - Response Types + +/// Response from Anthropic's Messages API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicResponse: Codable, Sendable { + public let id: String + public let type: String + public let role: AnthropicRole + public let content: [AnthropicContentBlock] + public let model: String + public let stopReason: String? + public let stopSequence: String? + public let usage: AnthropicUsage + + enum CodingKeys: String, CodingKey { + case id, type, role, content, model + case stopReason = "stop_reason" + case stopSequence = "stop_sequence" + case usage + } +} + +/// Usage statistics +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicUsage: Codable, Sendable { + public let inputTokens: Int + public let outputTokens: Int + + enum CodingKeys: String, CodingKey { + case inputTokens = "input_tokens" + case outputTokens = "output_tokens" + } +} + +// MARK: - Streaming Types + +/// Server-sent event for streaming +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicStreamEvent: Codable, Sendable { + public let type: String + + // Common fields + public let index: Int? + public let delta: AnthropicDelta? + + // Message start + public let message: AnthropicStreamMessage? + + // Content block start + public let contentBlock: AnthropicContentBlock? + + // Message complete + public let usage: AnthropicUsage? + public let stopReason: String? + public let stopSequence: String? + + // Error + public let error: AnthropicError? + + enum CodingKeys: String, CodingKey { + case type, index, delta, message + case contentBlock = "content_block" + case usage + case stopReason = "stop_reason" + case stopSequence = "stop_sequence" + case error + } +} + +/// Stream message metadata +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicStreamMessage: Codable, Sendable { + public let id: String + public let type: String + public let role: AnthropicRole + public let content: [AnthropicContentBlock] + public let model: String + public let usage: AnthropicUsage + + enum CodingKeys: String, CodingKey { + case id, type, role, content, model, usage + } +} + +/// Delta updates for streaming +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicDelta: Codable, Sendable { + // Text delta + public let text: String? + + // Message delta fields + public let stopReason: String? + public let stopSequence: String? + + // Tool use delta + public let type: String? + public let partialJson: String? + + enum CodingKeys: String, CodingKey { + case text + case stopReason = "stop_reason" + case stopSequence = "stop_sequence" + case type + case partialJson = "partial_json" + } +} + +// MARK: - Error Types + +/// Anthropic error response +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicErrorResponse: Codable, Sendable { + public let error: AnthropicError + + public var message: String { + self.error.message + } + + public var code: String? { + nil // Anthropic doesn't provide error codes + } + + public var type: String? { + self.error.type + } +} + +/// Error details +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicError: Codable, Sendable { + public let type: String + public let message: String +} + +// MARK: - Helper Extensions + +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +extension AnthropicContentBlock { + /// Create a text content block + public static func text(_ text: String, cacheControl: AnthropicCacheControl? = nil) -> AnthropicContentBlock { + AnthropicContentBlock( + type: "text", + text: text, + source: nil, + id: nil, + name: nil, + input: nil, + toolUseId: nil, + content: nil, + isError: nil, + cacheControl: cacheControl) + } + + /// Create an image content block + public static func image( + base64: String, + mediaType: String, + cacheControl: AnthropicCacheControl? = nil) -> AnthropicContentBlock + { + AnthropicContentBlock( + type: "image", + text: nil, + source: AnthropicImageSource( + type: "base64", + mediaType: mediaType, + data: base64), + id: nil, + name: nil, + input: nil, + toolUseId: nil, + content: nil, + isError: nil, + cacheControl: cacheControl) + } + + /// Create a tool use content block + public static func toolUse(id: String, name: String, input: [String: Any]) -> AnthropicContentBlock { + AnthropicContentBlock( + type: "tool_use", + text: nil, + source: nil, + id: id, + name: name, + input: input.compactMapValues { AnthropicInputValue(from: $0) }, + toolUseId: nil, + content: nil, + isError: nil, + cacheControl: nil) + } + + /// Create a tool result content block + public static func toolResult(toolUseId: String, content: String, isError: Bool = false) -> AnthropicContentBlock { + AnthropicContentBlock( + type: "tool_result", + text: nil, + source: nil, + id: nil, + name: nil, + input: nil, + toolUseId: toolUseId, + content: .string(content), + isError: isError, + cacheControl: nil) + } +} + +// MARK: - Input Value Types + +/// Type-safe input value for Anthropic tool use +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public enum AnthropicInputValue: Codable, Sendable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case array([AnthropicInputValue]) + case object([String: AnthropicInputValue]) + case null + + // MARK: - Codable + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self = .null + } else if let string = try? container.decode(String.self) { + self = .string(string) + } else if let int = try? container.decode(Int.self) { + self = .int(int) + } else if let double = try? container.decode(Double.self) { + self = .double(double) + } else if let bool = try? container.decode(Bool.self) { + self = .bool(bool) + } else if let array = try? container.decode([AnthropicInputValue].self) { + self = .array(array) + } else if let dict = try? container.decode([String: AnthropicInputValue].self) { + self = .object(dict) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode AnthropicInputValue") + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case let .string(value): + try container.encode(value) + case let .int(value): + try container.encode(value) + case let .double(value): + try container.encode(value) + case let .bool(value): + try container.encode(value) + case let .array(values): + try container.encode(values) + case let .object(dict): + try container.encode(dict) + case .null: + try container.encodeNil() + } + } + + // MARK: - Conversion + + /// Convert from Any value (for migration) + public init?(from value: Any) { + switch value { + case let string as String: + self = .string(string) + case let int as Int: + self = .int(int) + case let double as Double: + self = .double(double) + case let bool as Bool: + self = .bool(bool) + case let array as [Any]: + let values = array.compactMap { AnthropicInputValue(from: $0) } + if values.count == array.count { + self = .array(values) + } else { + return nil + } + case let dict as [String: Any]: + var values: [String: AnthropicInputValue] = [:] + for (key, val) in dict { + if let inputValue = AnthropicInputValue(from: val) { + values[key] = inputValue + } else { + return nil + } + } + self = .object(values) + case is NSNull: + self = .null + default: + return nil + } + } + + /// Convert to Any (for JSON serialization) + public func toAny() -> Any { + switch self { + case let .string(value): + value + case let .int(value): + value + case let .double(value): + value + case let .bool(value): + value + case let .array(values): + values.map { $0.toAny() } + case let .object(dict): + dict.mapValues { $0.toAny() } + case .null: + NSNull() + } + } +} + +/// Type-safe property schema for Anthropic JSON Schema +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnthropicPropertySchema: Codable, Sendable { + public let type: String + public let description: String? + public let `enum`: [String]? + public let items: Box? + public let properties: [String: AnthropicPropertySchema]? + public let required: [String]? + + public init( + type: String, + description: String? = nil, + enum enumValues: [String]? = nil, + items: AnthropicPropertySchema? = nil, + properties: [String: AnthropicPropertySchema]? = nil, + required: [String]? = nil) + { + self.type = type + self.description = description + self.enum = enumValues + self.items = items.map(Box.init) + self.properties = properties + self.required = required + } +} \ No newline at end of file diff --git a/Sources/Providers/Grok/GrokModel.swift b/Sources/Providers/Grok/GrokModel.swift new file mode 100644 index 0000000..d397ece --- /dev/null +++ b/Sources/Providers/Grok/GrokModel.swift @@ -0,0 +1,479 @@ +import Foundation + +/// Grok model implementation using OpenAI-compatible Chat Completions API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public final class GrokModel: ModelInterface, Sendable { + private let apiKey: String + private let modelName: String + private let baseURL: URL + private let session: URLSession + + public init( + apiKey: String, + modelName: String = "grok-4-0709", + baseURL: URL = URL(string: "https://api.x.ai/v1")!, + session: URLSession? = nil) + { + self.apiKey = apiKey + self.modelName = modelName + self.baseURL = baseURL + + // Create custom session with appropriate timeout + if let session { + self.session = session + } else { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 300 // 5 minutes + config.timeoutIntervalForResource = 300 + self.session = URLSession(configuration: config) + } + } + + // MARK: - ModelInterface Implementation + + public var maskedApiKey: String { + guard self.apiKey.count > 8 else { return "***" } + let start = self.apiKey.prefix(6) + let end = self.apiKey.suffix(2) + return "\(start)...\(end)" + } + + public func getResponse(request: ModelRequest) async throws -> ModelResponse { + let grokRequest = try convertToGrokRequest(request, stream: false) + let urlRequest = try createURLRequest(endpoint: "chat/completions", body: grokRequest) + + let (data, response) = try await session.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw TachikomaError.networkError(underlying: URLError(.badServerResponse)) + } + + if httpResponse.statusCode != 200 { + try handleErrorResponse(data: data, response: httpResponse) + } + + let chatResponse = try JSONDecoder().decode(GrokChatCompletionResponse.self, from: data) + return try self.convertFromGrokResponse(chatResponse) + } + + public func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + let grokRequest = try convertToGrokRequest(request, stream: true) + let urlRequest = try createURLRequest(endpoint: "chat/completions", body: grokRequest) + + return AsyncThrowingStream { continuation in + Task { + do { + let (bytes, response) = try await session.bytes(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + continuation.finish(throwing: TachikomaError.networkError(underlying: URLError(.badServerResponse))) + return + } + + if httpResponse.statusCode != 200 { + var errorData = Data() + for try await byte in bytes.prefix(1024) { + errorData.append(byte) + } + try self.handleErrorResponse(data: errorData, response: httpResponse) + } + + // Process SSE stream + var currentToolCalls: [String: GrokPartialToolCall] = [:] + + for try await line in bytes.lines { + // Handle SSE format + if line.hasPrefix("data: ") { + let data = String(line.dropFirst(6)) + + if data == "[DONE]" { + // Send any pending tool calls + for (id, toolCall) in currentToolCalls { + if let completed = toolCall.toCompleted() { + continuation.yield(.toolCallCompleted( + StreamToolCallCompleted(id: id, function: completed))) + } + } + continuation.finish() + return + } + + // Parse chunk + if let chunkData = data.data(using: .utf8), + let chunk = try? JSONDecoder().decode( + GrokChatCompletionChunk.self, + from: chunkData) + { + if let events = self.processGrokChunk(chunk, toolCalls: ¤tToolCalls) { + for event in events { + continuation.yield(event) + } + } + } + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: TachikomaError.streamingError(error.localizedDescription)) + } + } + } + } + + // MARK: - Private Helper Methods + + private func createURLRequest(endpoint: String, body: any Encodable) throws -> URLRequest { + let url = self.baseURL.appendingPathComponent(endpoint) + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + do { + request.httpBody = try encoder.encode(body) + } catch { + throw TachikomaError.configurationError("Failed to encode Grok request: \(error.localizedDescription)") + } + + request.timeoutInterval = 300 // 5 minutes for Grok + + return request + } + + private func convertToGrokRequest(_ request: ModelRequest, stream: Bool) throws -> GrokChatCompletionRequest { + // Convert messages to OpenAI-compatible format + let messages = try request.messages.map { message -> GrokMessage in + switch message { + case let .system(_, content): + return GrokMessage(role: "system", content: .string(content), toolCalls: nil, toolCallId: nil) + + case let .user(_, content): + return try self.convertUserMessageContent(content) + + case let .assistant(_, content, _): + return try self.convertAssistantMessageContent(content) + + case let .tool(_, toolCallId, content): + return GrokMessage( + role: "tool", + content: .string(content), + toolCalls: nil, + toolCallId: toolCallId) + + case .reasoning: + throw TachikomaError.invalidRequest("Reasoning messages not supported in Grok") + } + } + + // Convert tools to OpenAI-compatible format if present + let tools = request.tools?.map { toolDef -> GrokTool in + GrokTool( + type: "function", + function: GrokTool.Function( + name: toolDef.function.name, + description: toolDef.function.description, + parameters: self.convertToolParameters(toolDef.function.parameters))) + } + + // Filter parameters for Grok 4 + let temperature = request.settings.temperature + var frequencyPenalty = request.settings.frequencyPenalty + var presencePenalty = request.settings.presencePenalty + var stop = request.settings.stopSequences + + if self.modelName.contains("grok-4") || self.modelName.contains("grok-3") { + // Grok 3 and 4 models don't support these parameters + frequencyPenalty = nil + presencePenalty = nil + stop = nil + } + + return GrokChatCompletionRequest( + model: self.modelName, + messages: messages, + tools: tools, + toolChoice: self.convertToolChoice(request.settings.toolChoice), + temperature: temperature, + maxTokens: request.settings.maxTokens, + stream: stream, + frequencyPenalty: frequencyPenalty, + presencePenalty: presencePenalty, + stop: stop) + } + + private func convertUserMessageContent(_ content: MessageContent) throws -> GrokMessage { + switch content { + case let .text(text): + return GrokMessage(role: "user", content: .string(text), toolCalls: nil, toolCallId: nil) + + case let .image(imageContent): + var content: [GrokMessageContentPart] = [] + + if let url = imageContent.url { + content.append(GrokMessageContentPart( + type: "image_url", + text: nil, + imageUrl: GrokImageUrl( + url: url, + detail: imageContent.detail?.rawValue))) + } else if let base64 = imageContent.base64 { + content.append(GrokMessageContentPart( + type: "image_url", + text: nil, + imageUrl: GrokImageUrl( + url: "data:image/jpeg;base64,\(base64)", + detail: imageContent.detail?.rawValue))) + } + + return GrokMessage(role: "user", content: .array(content), toolCalls: nil, toolCallId: nil) + + case let .multimodal(parts): + let content = parts.compactMap { part -> GrokMessageContentPart? in + if let text = part.text { + return GrokMessageContentPart( + type: "text", + text: text, + imageUrl: nil) + } else if let image = part.imageUrl { + if let url = image.url { + return GrokMessageContentPart( + type: "image_url", + text: nil, + imageUrl: GrokImageUrl(url: url, detail: image.detail?.rawValue)) + } else if let base64 = image.base64 { + return GrokMessageContentPart( + type: "image_url", + text: nil, + imageUrl: GrokImageUrl( + url: "data:image/jpeg;base64,\(base64)", + detail: image.detail?.rawValue)) + } + } + return nil + } + return GrokMessage(role: "user", content: .array(content), toolCalls: nil, toolCallId: nil) + + case .file: + throw TachikomaError.invalidRequest("File content not supported in Grok chat completions") + + case let .audio(audioContent): + // Grok doesn't support native audio, so we need to use the transcript + if let transcript = audioContent.transcript { + // Include metadata about the audio source + var text = transcript + if let duration = audioContent.duration { + text = "[Audio transcript, duration: \(Int(duration))s] \(transcript)" + } else { + text = "[Audio transcript] \(transcript)" + } + return GrokMessage(role: "user", content: .string(text), toolCalls: nil, toolCallId: nil) + } else { + throw TachikomaError.invalidRequest("Audio content must be transcribed before sending to Grok. Please ensure transcript is provided.") + } + } + } + + private func convertAssistantMessageContent(_ contentArray: [AssistantContent]) throws -> GrokMessage { + var textContent = "" + var toolCalls: [GrokToolCall] = [] + + for content in contentArray { + switch content { + case let .outputText(text): + textContent += text + + case let .refusal(refusal): + return GrokMessage(role: "assistant", content: .string(refusal), toolCalls: nil, toolCallId: nil) + + case let .toolCall(toolCall): + toolCalls.append(GrokToolCall( + id: toolCall.id, + type: toolCall.type.rawValue, + function: GrokFunctionCall( + name: toolCall.function.name, + arguments: toolCall.function.arguments))) + } + } + + // Include tool calls if present + if !toolCalls.isEmpty { + return GrokMessage( + role: "assistant", + content: textContent.isEmpty ? nil : .string(textContent), + toolCalls: toolCalls, + toolCallId: nil) + } + + return GrokMessage(role: "assistant", content: .string(textContent), toolCalls: nil, toolCallId: nil) + } + + private func convertToolParameters(_ params: ToolParameters) -> GrokTool.Parameters { + let (type, properties, required) = params.toGrokParameters() + return GrokTool.Parameters( + type: type, + properties: properties, + required: required) + } + + private func convertToolChoice(_ toolChoice: ToolChoice?) -> GrokToolChoice? { + guard let toolChoice else { return nil } + + switch toolChoice { + case .auto: + return .string("auto") + case .none: + return .string("none") + case .required: + return .string("required") + case let .specific(toolName): + return .object(GrokToolChoiceObject( + type: "function", + function: GrokToolChoiceFunction(name: toolName))) + } + } + + private func convertFromGrokResponse(_ response: GrokChatCompletionResponse) throws -> ModelResponse { + guard let choice = response.choices.first else { + throw TachikomaError.apiError(message: "No choices in response") + } + + var content: [AssistantContent] = [] + + // Add text content if present + if let textContent = choice.message.content { + content.append(.outputText(textContent)) + } + + // Add tool calls if present + if let toolCalls = choice.message.toolCalls { + for toolCall in toolCalls { + content.append(.toolCall(ToolCallItem( + id: toolCall.id, + type: .function, + function: FunctionCall( + name: toolCall.function.name, + arguments: toolCall.function.arguments)))) + } + } + + let usage = response.usage.map { usage in + Usage( + promptTokens: usage.promptTokens, + completionTokens: usage.completionTokens, + totalTokens: usage.totalTokens, + promptTokensDetails: nil, + completionTokensDetails: nil) + } + + return ModelResponse( + id: response.id, + model: response.model, + content: content, + usage: usage, + flagged: false, + finishReason: self.convertFinishReason(choice.finishReason)) + } + + private func convertFinishReason(_ reason: String?) -> FinishReason? { + guard let reason else { return nil } + return FinishReason(rawValue: reason) + } + + private func processGrokChunk( + _ chunk: GrokChatCompletionChunk, + toolCalls: inout [String: GrokPartialToolCall]) -> [StreamEvent]? + { + var events: [StreamEvent] = [] + + // First chunk often contains metadata + if !chunk.id.isEmpty, chunk.model.isEmpty == false { + events.append(.responseStarted(StreamResponseStarted( + id: chunk.id, + model: chunk.model, + systemFingerprint: chunk.systemFingerprint))) + } + + for choice in chunk.choices { + let delta = choice.delta + + // Handle text content + if let content = delta.content, !content.isEmpty { + events.append(.textDelta(StreamTextDelta(delta: content, index: choice.index))) + } + + // Handle tool calls + if let deltaToolCalls = delta.toolCalls { + for toolCallDelta in deltaToolCalls { + let toolCallId = toolCallDelta.id ?? "" + + if toolCalls[toolCallId] == nil { + let partialCall = GrokPartialToolCall(from: toolCallDelta) + toolCalls[toolCallId] = partialCall + } else { + toolCalls[toolCallId]?.update(with: toolCallDelta) + } + + // Emit delta event + if let functionDelta = toolCallDelta.function { + events.append(.toolCallDelta(StreamToolCallDelta( + id: toolCallId, + index: toolCallDelta.index, + function: FunctionCallDelta( + name: functionDelta.name, + arguments: functionDelta.arguments)))) + } + } + } + + // Handle finish reason + if let finishReason = choice.finishReason { + // If this is a tool call finish, emit completed events + if finishReason == "tool_calls" { + for (id, toolCall) in toolCalls { + if let completed = toolCall.toCompleted() { + events.append(.toolCallCompleted( + StreamToolCallCompleted(id: id, function: completed))) + } + } + } + + events.append(.responseCompleted(StreamResponseCompleted( + id: chunk.id, + usage: nil, + finishReason: FinishReason(rawValue: finishReason)))) + } + } + + return events.isEmpty ? nil : events + } + + private func handleErrorResponse(data: Data, response: HTTPURLResponse) throws { + if let errorResponse = try? JSONDecoder().decode(GrokErrorResponse.self, from: data) { + let message = errorResponse.error.message + + switch response.statusCode { + case 401: + throw TachikomaError.authenticationFailed + case 429: + throw TachikomaError.rateLimited + case 400: + if message.contains("credit") || message.contains("usage") { + throw TachikomaError.insufficientQuota + } else { + throw TachikomaError.invalidRequest(message) + } + case 500...599: + throw TachikomaError.modelOverloaded + default: + throw TachikomaError.apiError(message: message, code: errorResponse.error.code) + } + } else { + throw TachikomaError.apiError(message: "HTTP \(response.statusCode)") + } + } +} + diff --git a/Sources/Providers/Grok/GrokTypes.swift b/Sources/Providers/Grok/GrokTypes.swift new file mode 100644 index 0000000..cbee943 --- /dev/null +++ b/Sources/Providers/Grok/GrokTypes.swift @@ -0,0 +1,346 @@ +import Foundation + +// MARK: - Helper Types + +internal class GrokPartialToolCall { + var id: String = "" + var type: String = "function" + var index: Int = 0 + var name: String? + var arguments: String = "" + + init() { + // Default initializer + } + + init(from delta: GrokToolCallDelta) { + self.id = delta.id ?? "" + self.index = delta.index + self.name = delta.function?.name + self.arguments = delta.function?.arguments ?? "" + } + + func update(with delta: GrokToolCallDelta) { + if let funcName = delta.function?.name { + self.name = funcName + } + if let args = delta.function?.arguments { + self.arguments += args + } + } + + func toCompleted() -> FunctionCall? { + guard let name else { return nil } + return FunctionCall(name: name, arguments: self.arguments) + } +} + +// MARK: - Grok Request Types + +internal struct GrokChatCompletionRequest: Encodable { + let model: String + let messages: [GrokMessage] + let tools: [GrokTool]? + let toolChoice: GrokToolChoice? + let temperature: Double? + let maxTokens: Int? + let stream: Bool + let frequencyPenalty: Double? + let presencePenalty: Double? + let stop: [String]? + + enum CodingKeys: String, CodingKey { + case model, messages, tools, temperature, stream + case toolChoice = "tool_choice" + case maxTokens = "max_tokens" + case frequencyPenalty = "frequency_penalty" + case presencePenalty = "presence_penalty" + case stop + } +} + +internal struct GrokMessage: Encodable { + let role: String + let content: GrokMessageContent? + let toolCalls: [GrokToolCall]? + let toolCallId: String? + + enum CodingKeys: String, CodingKey { + case role, content + case toolCalls = "tool_calls" + case toolCallId = "tool_call_id" + } +} + +internal enum GrokMessageContent: Encodable { + case string(String) + case array([GrokMessageContentPart]) + + func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(text): + try container.encode(text) + case let .array(parts): + try container.encode(parts) + } + } +} + +internal struct GrokMessageContentPart: Encodable { + let type: String + let text: String? + let imageUrl: GrokImageUrl? + + enum CodingKeys: String, CodingKey { + case type, text + case imageUrl = "image_url" + } +} + +internal struct GrokImageUrl: Encodable { + let url: String + let detail: String? +} + +internal struct GrokToolCall: Encodable { + let id: String + let type: String + let function: GrokFunctionCall +} + +internal struct GrokFunctionCall: Encodable { + let name: String + let arguments: String +} + +internal struct GrokTool: Encodable { + let type: String + let function: Function + + struct Function: Encodable { + let name: String + let description: String? + let parameters: Parameters + } + + struct Parameters: Encodable { + let type: String + let properties: [String: GrokPropertySchema] + let required: [String] + } +} + +internal enum GrokToolChoice: Encodable { + case string(String) + case object(GrokToolChoiceObject) + + func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(value): + try container.encode(value) + case let .object(obj): + try container.encode(obj) + } + } +} + +internal struct GrokToolChoiceObject: Encodable { + let type: String + let function: GrokToolChoiceFunction +} + +internal struct GrokToolChoiceFunction: Encodable { + let name: String +} + +// MARK: - Response Types + +internal struct GrokChatCompletionResponse: Decodable { + let id: String + let model: String + let choices: [Choice] + let usage: Usage? + + struct Choice: Decodable { + let message: Message + let finishReason: String? + + enum CodingKeys: String, CodingKey { + case message + case finishReason = "finish_reason" + } + } + + struct Message: Decodable { + let role: String + let content: String? + let toolCalls: [ToolCall]? + + enum CodingKeys: String, CodingKey { + case role, content + case toolCalls = "tool_calls" + } + + struct ToolCall: Decodable { + let id: String + let type: String + let function: Function + + struct Function: Decodable { + let name: String + let arguments: String + } + } + } + + struct Usage: Decodable { + let promptTokens: Int + let completionTokens: Int + let totalTokens: Int + + enum CodingKeys: String, CodingKey { + case promptTokens = "prompt_tokens" + case completionTokens = "completion_tokens" + case totalTokens = "total_tokens" + } + } +} + +// MARK: - Streaming Types + +internal struct GrokChatCompletionChunk: Decodable { + let id: String + let model: String + let choices: [StreamChoice] + let systemFingerprint: String? + + enum CodingKeys: String, CodingKey { + case id, model, choices + case systemFingerprint = "system_fingerprint" + } + + struct StreamChoice: Decodable { + let index: Int + let delta: Delta + let finishReason: String? + + enum CodingKeys: String, CodingKey { + case index, delta + case finishReason = "finish_reason" + } + + struct Delta: Decodable { + let role: String? + let content: String? + let toolCalls: [GrokToolCallDelta]? + + enum CodingKeys: String, CodingKey { + case role, content + case toolCalls = "tool_calls" + } + } + } +} + +internal struct GrokToolCallDelta: Decodable { + let index: Int + let id: String? + let type: String? + let function: StreamFunction? + + struct StreamFunction: Decodable { + let name: String? + let arguments: String? + } +} + +// MARK: - Error Types + +internal struct GrokErrorResponse: Decodable { + let error: GrokError + + var message: String { + self.error.message + } + + var code: String? { + self.error.code + } + + var type: String? { + self.error.type + } +} + +internal struct GrokError: Decodable { + let message: String + let type: String + let code: String? +} + +// MARK: - Property Schema + +/// Type-safe property schema for Grok tool parameters +internal struct GrokPropertySchema: Codable, Sendable { + let type: String + let description: String? + let `enum`: [String]? + let items: Box? + let properties: [String: GrokPropertySchema]? + let minimum: Double? + let maximum: Double? + let pattern: String? + let required: [String]? + + init( + type: String, + description: String? = nil, + enum enumValues: [String]? = nil, + items: GrokPropertySchema? = nil, + properties: [String: GrokPropertySchema]? = nil, + minimum: Double? = nil, + maximum: Double? = nil, + pattern: String? = nil, + required: [String]? = nil) + { + self.type = type + self.description = description + self.enum = enumValues + self.items = items.map(Box.init) + self.properties = properties + self.minimum = minimum + self.maximum = maximum + self.pattern = pattern + self.required = required + } + + /// Create from a ParameterSchema + init(from schema: ParameterSchema) { + self.type = schema.type.rawValue + self.description = schema.description + self.enum = schema.enumValues + self.items = schema.items.map { Box(GrokPropertySchema(from: $0.value)) } + self.properties = schema.properties?.mapValues { GrokPropertySchema(from: $0) } + self.minimum = schema.minimum + self.maximum = schema.maximum + self.pattern = schema.pattern + self.required = nil + } +} + +// MARK: - Extensions + +/// Helper to convert ToolParameters to Grok-compatible structure +internal extension ToolParameters { + func toGrokParameters() -> (type: String, properties: [String: GrokPropertySchema], required: [String]) { + var grokProperties: [String: GrokPropertySchema] = [:] + + for (key, schema) in properties { + grokProperties[key] = GrokPropertySchema(from: schema) + } + + return (type: type, properties: grokProperties, required: required) + } +} \ No newline at end of file diff --git a/Sources/Providers/Ollama/OllamaModel.swift b/Sources/Providers/Ollama/OllamaModel.swift new file mode 100644 index 0000000..e58ffd2 --- /dev/null +++ b/Sources/Providers/Ollama/OllamaModel.swift @@ -0,0 +1,336 @@ +import Foundation + +/// Ollama model implementation conforming to ModelInterface +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public final class OllamaModel: ModelInterface, Sendable { + private let modelName: String + private let baseURL: URL + private let session: URLSession + + public init( + modelName: String, + baseURL: URL = URL(string: "http://localhost:11434")!, + session: URLSession? = nil) + { + self.modelName = modelName + self.baseURL = baseURL + + if let session { + self.session = session + } else { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 300 // 5 minutes for local models + config.timeoutIntervalForResource = 300 + self.session = URLSession(configuration: config) + } + } + + // MARK: - ModelInterface Implementation + + public var maskedApiKey: String { + "local-ollama" + } + + public func getResponse(request: ModelRequest) async throws -> ModelResponse { + let ollamaRequest = try convertToOllamaRequest(request, stream: false) + let urlRequest = try createURLRequest(endpoint: "api/chat", body: ollamaRequest) + + let (data, response) = try await session.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw TachikomaError.networkError(underlying: URLError(.badServerResponse)) + } + + if httpResponse.statusCode != 200 { + try handleErrorResponse(data: data, response: httpResponse) + } + + let ollamaResponse = try JSONDecoder().decode(OllamaChatResponse.self, from: data) + return try self.convertFromOllamaResponse(ollamaResponse) + } + + public func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + let ollamaRequest = try convertToOllamaRequest(request, stream: true) + let urlRequest = try createURLRequest(endpoint: "api/chat", body: ollamaRequest) + + return AsyncThrowingStream { continuation in + Task { + do { + let (bytes, response) = try await session.bytes(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + continuation.finish(throwing: TachikomaError.networkError(underlying: URLError(.badServerResponse))) + return + } + + if httpResponse.statusCode != 200 { + var errorData = Data() + for try await byte in bytes.prefix(1024) { + errorData.append(byte) + } + try self.handleErrorResponse(data: errorData, response: httpResponse) + } + + // Process JSON stream (one JSON object per line) + for try await line in bytes.lines { + if line.isEmpty { continue } + + if let data = line.data(using: .utf8), + let chunk = try? JSONDecoder().decode(OllamaChatChunk.self, from: data) { + + if let events = self.processOllamaChunk(chunk) { + for event in events { + continuation.yield(event) + } + } + + // Check if done + if chunk.done { + continuation.finish() + return + } + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: TachikomaError.streamingError(error.localizedDescription)) + } + } + } + } + + // MARK: - Private Methods + + private func createURLRequest(endpoint: String, body: any Encodable) throws -> URLRequest { + let url = self.baseURL.appendingPathComponent(endpoint) + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + do { + request.httpBody = try encoder.encode(body) + } catch { + throw TachikomaError.configurationError("Failed to encode Ollama request: \(error.localizedDescription)") + } + + request.timeoutInterval = 300 // 5 minutes for local models + + return request + } + + private func convertToOllamaRequest(_ request: ModelRequest, stream: Bool) throws -> OllamaChatRequest { + // Convert messages + let messages = try request.messages.compactMap { message -> OllamaMessage? in + switch message { + case let .system(_, content): + return OllamaMessage(role: "system", content: content, images: nil) + + case let .user(_, content): + return try convertUserMessage(content) + + case let .assistant(_, content, _): + return convertAssistantMessage(content) + + case let .tool(_, _, content): + // Ollama handles tool results differently - might need adaptation + return OllamaMessage(role: "user", content: "Tool result: \(content)", images: nil) + + case .reasoning: + // Skip reasoning messages for Ollama + return nil + } + } + + // Convert tools (if supported by the model) + let tools = request.tools?.map { toolDef -> OllamaTool in + OllamaTool( + type: "function", + function: OllamaFunction( + name: toolDef.function.name, + description: toolDef.function.description, + parameters: convertToolParameters(toolDef.function.parameters))) + } + + return OllamaChatRequest( + model: self.modelName, + messages: messages, + tools: tools, + stream: stream, + options: OllamaOptions( + temperature: request.settings.temperature, + topP: request.settings.topP, + stop: request.settings.stopSequences)) + } + + private func convertUserMessage(_ content: MessageContent) throws -> OllamaMessage { + switch content { + case let .text(text): + return OllamaMessage(role: "user", content: text, images: nil) + + case let .image(imageContent): + // Ollama supports base64 images + if let base64 = imageContent.base64 { + return OllamaMessage(role: "user", content: "", images: [base64]) + } else if imageContent.url != nil { + throw TachikomaError.invalidRequest("Image URLs not supported in Ollama - please provide base64 data") + } else { + throw TachikomaError.invalidRequest("No image data provided") + } + + case let .multimodal(parts): + var text = "" + var images: [String] = [] + + for part in parts { + if let partText = part.text { + text += partText + } else if let image = part.imageUrl { + if let base64 = image.base64 { + images.append(base64) + } else if image.url != nil { + throw TachikomaError.invalidRequest("Image URLs not supported in Ollama - please provide base64 data") + } + } + } + + return OllamaMessage(role: "user", content: text, images: images.isEmpty ? nil : images) + + case .file: + throw TachikomaError.invalidRequest("File content not supported in Ollama API") + + case let .audio(audioContent): + // Ollama doesn't support native audio, use transcript if available + if let transcript = audioContent.transcript { + var text = transcript + if let duration = audioContent.duration { + text = "[Audio transcript, duration: \(Int(duration))s] \(transcript)" + } else { + text = "[Audio transcript] \(transcript)" + } + return OllamaMessage(role: "user", content: text, images: nil) + } else { + throw TachikomaError.invalidRequest("Audio content must be transcribed before sending to Ollama") + } + } + } + + private func convertAssistantMessage(_ content: [AssistantContent]) -> OllamaMessage { + var text = "" + + for item in content { + switch item { + case let .outputText(outputText): + text += outputText + case let .refusal(refusal): + text += refusal + case let .toolCall(toolCall): + // Convert tool call to text representation for now + text += "\n[Tool Call: \(toolCall.function.name)(\(toolCall.function.arguments))]" + } + } + + return OllamaMessage(role: "assistant", content: text, images: nil) + } + + private func convertToolParameters(_ params: ToolParameters) -> [String: Any] { + var properties: [String: Any] = [:] + + for (key, schema) in params.properties { + properties[key] = convertParameterSchema(schema) + } + + return [ + "type": params.type, + "properties": properties, + "required": params.required + ] + } + + private func convertParameterSchema(_ schema: ParameterSchema) -> [String: Any] { + var result: [String: Any] = [ + "type": schema.type.rawValue + ] + + if let description = schema.description { + result["description"] = description + } + + if let enumValues = schema.enumValues { + result["enum"] = enumValues + } + + return result + } + + private func convertFromOllamaResponse(_ response: OllamaChatResponse) throws -> ModelResponse { + let content: [AssistantContent] = [.outputText(response.message.content)] + + // Ollama doesn't provide detailed usage info in the same format + let usage = Usage( + promptTokens: 0, // Not provided by Ollama + completionTokens: 0, // Not provided by Ollama + totalTokens: 0, // Not provided by Ollama + promptTokensDetails: nil, + completionTokensDetails: nil) + + return ModelResponse( + id: UUID().uuidString, // Ollama doesn't provide response IDs + model: response.model, + content: content, + usage: usage, + flagged: false, + finishReason: response.done ? .stop : nil) + } + + private func processOllamaChunk(_ chunk: OllamaChatChunk) -> [StreamEvent]? { + var events: [StreamEvent] = [] + + // First chunk with model info + if !chunk.model.isEmpty && events.isEmpty { + events.append(.responseStarted(StreamResponseStarted( + id: UUID().uuidString, + model: chunk.model, + systemFingerprint: nil))) + } + + // Text content + if let content = chunk.message?.content, !content.isEmpty { + events.append(.textDelta(StreamTextDelta(delta: content, index: 0))) + } + + // Done + if chunk.done { + events.append(.responseCompleted(StreamResponseCompleted( + id: UUID().uuidString, + usage: nil, + finishReason: .stop))) + } + + return events.isEmpty ? nil : events + } + + private func handleErrorResponse(data: Data, response: HTTPURLResponse) throws { + // Try to decode Ollama error format + if let errorText = String(data: data, encoding: .utf8) { + let message = errorText.isEmpty ? "HTTP \(response.statusCode)" : errorText + + switch response.statusCode { + case 400: + throw TachikomaError.invalidRequest(message) + case 404: + throw TachikomaError.modelNotFound(self.modelName) + case 500...599: + throw TachikomaError.modelOverloaded + default: + throw TachikomaError.apiError(message: message) + } + } else { + throw TachikomaError.apiError(message: "HTTP \(response.statusCode)") + } + } +} + diff --git a/Sources/Providers/Ollama/OllamaTypes.swift b/Sources/Providers/Ollama/OllamaTypes.swift new file mode 100644 index 0000000..c64799f --- /dev/null +++ b/Sources/Providers/Ollama/OllamaTypes.swift @@ -0,0 +1,69 @@ +import Foundation + +// MARK: - Ollama Request Types + +internal struct OllamaChatRequest: Encodable { + let model: String + let messages: [OllamaMessage] + let tools: [OllamaTool]? + let stream: Bool + let options: OllamaOptions? +} + +internal struct OllamaMessage: Codable { + let role: String + let content: String + let images: [String]? +} + +internal struct OllamaTool: Encodable { + let type: String + let function: OllamaFunction +} + +internal struct OllamaFunction: Encodable { + let name: String + let description: String + let parameters: [String: Any] + + enum CodingKeys: String, CodingKey { + case name, description, parameters + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + + // Encode parameters as JSON + let data = try JSONSerialization.data(withJSONObject: parameters) + let jsonString = String(data: data, encoding: .utf8) ?? "{}" + try container.encode(jsonString, forKey: .parameters) + } +} + +internal struct OllamaOptions: Encodable { + let temperature: Double? + let topP: Double? + let stop: [String]? + + enum CodingKeys: String, CodingKey { + case temperature + case topP = "top_p" + case stop + } +} + +// MARK: - Ollama Response Types + +internal struct OllamaChatResponse: Decodable { + let model: String + let message: OllamaMessage + let done: Bool +} + +internal struct OllamaChatChunk: Decodable { + let model: String + let message: OllamaMessage? + let done: Bool +} \ No newline at end of file diff --git a/Sources/Providers/OpenAI/OpenAIModel.swift b/Sources/Providers/OpenAI/OpenAIModel.swift new file mode 100644 index 0000000..8abd840 --- /dev/null +++ b/Sources/Providers/OpenAI/OpenAIModel.swift @@ -0,0 +1,516 @@ +import Foundation + +/// OpenAI model implementation conforming to ModelInterface +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public final class OpenAIModel: ModelInterface, Sendable { + private let apiKey: String + private let baseURL: URL + private let session: URLSession + private let organizationId: String? + private let customHeaders: [String: String]? + private let customModelName: String? + + public init( + apiKey: String, + baseURL: URL = URL(string: "https://api.openai.com/v1")!, + organizationId: String? = nil, + modelName: String? = nil, + headers: [String: String]? = nil, + session: URLSession? = nil) + { + self.apiKey = apiKey + self.baseURL = baseURL + self.organizationId = organizationId + self.customHeaders = headers + self.customModelName = modelName + + if let session { + self.session = session + } else { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 600 // 10 minutes for o3 models + config.timeoutIntervalForResource = 600 + self.session = URLSession(configuration: config) + } + } + + // MARK: - ModelInterface Implementation + + public var maskedApiKey: String { + guard self.apiKey.count > 8 else { return "***" } + let start = self.apiKey.prefix(6) + let end = self.apiKey.suffix(2) + return "\(start)...\(end)" + } + + public func getResponse(request: ModelRequest) async throws -> ModelResponse { + let openAIRequest = try convertToOpenAIRequest(request, stream: false) + let endpoint = getEndpointForModel(request.settings.modelName) + let urlRequest = try createURLRequest(endpoint: endpoint, body: openAIRequest) + + let (data, response) = try await session.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw TachikomaError.networkError(underlying: URLError(.badServerResponse)) + } + + if httpResponse.statusCode != 200 { + try handleErrorResponse(data: data, response: httpResponse) + } + + do { + let openAIResponse = try JSONDecoder().decode(OpenAIResponse.self, from: data) + return try convertFromOpenAIResponse(openAIResponse) + } catch { + throw TachikomaError.decodingError(underlying: error) + } + } + + public func getStreamedResponse(request: ModelRequest) async throws -> AsyncThrowingStream { + let openAIRequest = try convertToOpenAIRequest(request, stream: true) + let endpoint = getEndpointForModel(request.settings.modelName) + let urlRequest = try createURLRequest(endpoint: endpoint, body: openAIRequest) + + return AsyncThrowingStream { continuation in + Task { + do { + let (bytes, response) = try await session.bytes(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + continuation.finish(throwing: TachikomaError.networkError(underlying: URLError(.badServerResponse))) + return + } + + if httpResponse.statusCode != 200 { + var errorData = Data() + for try await byte in bytes.prefix(1024) { + errorData.append(byte) + } + try self.handleErrorResponse(data: errorData, response: httpResponse) + } + + // Process SSE stream + for try await line in bytes.lines { + if line.hasPrefix("data: ") { + let data = String(line.dropFirst(6)) + + if data == "[DONE]" { + continuation.finish() + return + } + + if let chunkData = data.data(using: .utf8), + let chunk = try? JSONDecoder().decode(OpenAIStreamChunk.self, from: chunkData) { + if let events = processStreamChunk(chunk) { + for event in events { + continuation.yield(event) + } + } + } + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: TachikomaError.streamingError(error.localizedDescription)) + } + } + } + } + + // MARK: - Private Methods + + private func getEndpointForModel(_ modelName: String) -> String { + // Use Responses API for o3/o4 models, Chat Completions for others + if modelName.hasPrefix("o3") || modelName.hasPrefix("o4") { + return "responses" + } else { + return "chat/completions" + } + } + + private func createURLRequest(endpoint: String, body: any Encodable) throws -> URLRequest { + let url = self.baseURL.appendingPathComponent(endpoint) + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + if let orgId = organizationId { + request.setValue(orgId, forHTTPHeaderField: "OpenAI-Organization") + } + + customHeaders?.forEach { key, value in + request.setValue(value, forHTTPHeaderField: key) + } + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + do { + request.httpBody = try encoder.encode(body) + } catch { + throw TachikomaError.configurationError("Failed to encode OpenAI request: \(error.localizedDescription)") + } + + // Set timeout for different API types + if endpoint == "responses" { + request.timeoutInterval = 600 // 10 minutes for o3 models + } else { + request.timeoutInterval = 120 // 2 minutes for other models + } + + return request + } + + private func convertToOpenAIRequest(_ request: ModelRequest, stream: Bool) throws -> any Encodable { + let modelName = customModelName ?? request.settings.modelName + + if modelName.hasPrefix("o3") || modelName.hasPrefix("o4") { + return try convertToResponsesRequest(request, stream: stream) + } else { + return try convertToChatRequest(request, stream: stream) + } + } + + private func convertToChatRequest(_ request: ModelRequest, stream: Bool) throws -> OpenAIChatRequest { + let messages = try request.messages.map { message -> OpenAIMessage in + switch message { + case let .system(_, content): + return OpenAIMessage(role: "system", content: .string(content)) + case let .user(_, content): + return try convertUserMessage(content) + case let .assistant(_, content, _): + return try convertAssistantMessage(content) + case let .tool(_, toolCallId, content): + return OpenAIMessage(role: "tool", content: .string(content), toolCallId: toolCallId) + case .reasoning: + throw TachikomaError.invalidRequest("Reasoning messages not supported in Chat Completions API") + } + } + + let tools = request.tools?.map { tool in + OpenAITool( + type: "function", + function: OpenAIFunction( + name: tool.function.name, + description: tool.function.description, + parameters: convertToolParameters(tool.function.parameters))) + } + + return OpenAIChatRequest( + model: customModelName ?? request.settings.modelName, + messages: messages, + tools: tools, + toolChoice: convertToolChoice(request.settings.toolChoice), + temperature: request.settings.temperature, + topP: request.settings.topP, + stream: stream, + maxTokens: request.settings.maxTokens) + } + + private func convertToResponsesRequest(_ request: ModelRequest, stream: Bool) throws -> OpenAIResponsesRequest { + let messages = try request.messages.compactMap { message -> OpenAIMessage? in + switch message { + case let .system(_, content): + return OpenAIMessage(role: "system", content: .string(content)) + case let .user(_, content): + return try convertUserMessage(content) + case let .assistant(_, content, _): + return try convertAssistantMessage(content) + case let .tool(_, _, content): + return OpenAIMessage(role: "user", content: .string(content)) + case .reasoning: + return nil // Skip reasoning messages + } + } + + let tools = request.tools?.map { tool in + OpenAIResponsesTool( + type: "function", + name: tool.function.name, + description: tool.function.description, + parameters: convertToolParameters(tool.function.parameters)) + } + + let modelName = customModelName ?? request.settings.modelName + + return OpenAIResponsesRequest( + model: modelName, + input: messages, + tools: tools, + toolChoice: convertToolChoice(request.settings.toolChoice), + temperature: nil, // o3/o4 models don't support temperature + topP: request.settings.topP, + stream: stream, + maxOutputTokens: request.settings.maxTokens ?? 65536, + reasoning: (modelName.hasPrefix("o3") || modelName.hasPrefix("o4")) ? + OpenAIReasoning( + effort: request.settings.additionalParameters?.string("reasoning_effort") ?? "medium", + summary: "detailed") : nil) + } + + private func convertUserMessage(_ content: MessageContent) throws -> OpenAIMessage { + switch content { + case let .text(text): + return OpenAIMessage(role: "user", content: .string(text)) + case let .image(imageContent): + var parts: [OpenAIMessageContentPart] = [] + + if let url = imageContent.url { + parts.append(OpenAIMessageContentPart( + type: "image_url", + text: nil, + imageUrl: OpenAIImageUrl(url: url, detail: imageContent.detail?.rawValue))) + } else if let base64 = imageContent.base64 { + parts.append(OpenAIMessageContentPart( + type: "image_url", + text: nil, + imageUrl: OpenAIImageUrl(url: "data:image/jpeg;base64,\(base64)", detail: imageContent.detail?.rawValue))) + } + + return OpenAIMessage(role: "user", content: .array(parts)) + case let .multimodal(parts): + let contentParts = parts.compactMap { part -> OpenAIMessageContentPart? in + if let text = part.text { + return OpenAIMessageContentPart(type: "text", text: text, imageUrl: nil) + } else if let image = part.imageUrl { + if let url = image.url { + return OpenAIMessageContentPart( + type: "image_url", + text: nil, + imageUrl: OpenAIImageUrl(url: url, detail: image.detail?.rawValue)) + } else if let base64 = image.base64 { + return OpenAIMessageContentPart( + type: "image_url", + text: nil, + imageUrl: OpenAIImageUrl(url: "data:image/jpeg;base64,\(base64)", detail: image.detail?.rawValue)) + } + } + return nil + } + return OpenAIMessage(role: "user", content: .array(contentParts)) + case .file: + throw TachikomaError.invalidRequest("File content not supported in OpenAI API") + case let .audio(audioContent): + if let transcript = audioContent.transcript { + var text = transcript + if let duration = audioContent.duration { + text = "[Audio transcript, duration: \(Int(duration))s] \(transcript)" + } else { + text = "[Audio transcript] \(transcript)" + } + return OpenAIMessage(role: "user", content: .string(text)) + } else { + throw TachikomaError.invalidRequest("Audio content must be transcribed before sending to OpenAI") + } + } + } + + private func convertAssistantMessage(_ content: [AssistantContent]) throws -> OpenAIMessage { + var textContent = "" + var toolCalls: [OpenAIToolCall] = [] + + for item in content { + switch item { + case let .outputText(text): + textContent += text + case let .refusal(refusal): + return OpenAIMessage(role: "assistant", content: .string(refusal)) + case let .toolCall(toolCall): + toolCalls.append(OpenAIToolCall( + id: toolCall.id, + type: "function", + function: OpenAIFunction( + name: toolCall.function.name, + description: nil, + parameters: nil, + arguments: toolCall.function.arguments))) + } + } + + if !toolCalls.isEmpty { + return OpenAIMessage(role: "assistant", content: .string(textContent), toolCalls: toolCalls) + } else { + return OpenAIMessage(role: "assistant", content: .string(textContent)) + } + } + + private func convertToolParameters(_ params: ToolParameters) -> [String: Any] { + var properties: [String: Any] = [:] + + for (key, schema) in params.properties { + properties[key] = convertParameterSchema(schema) + } + + return [ + "type": params.type, + "properties": properties, + "required": params.required, + "additionalProperties": params.additionalProperties + ] + } + + private func convertParameterSchema(_ schema: ParameterSchema) -> [String: Any] { + var result: [String: Any] = [ + "type": schema.type.rawValue + ] + + if let description = schema.description { + result["description"] = description + } + + if let enumValues = schema.enumValues { + result["enum"] = enumValues + } + + if let minimum = schema.minimum { + result["minimum"] = minimum + } + + if let maximum = schema.maximum { + result["maximum"] = maximum + } + + if let pattern = schema.pattern { + result["pattern"] = pattern + } + + if let items = schema.items { + result["items"] = convertParameterSchema(items.value) + } + + if let properties = schema.properties { + result["properties"] = properties.mapValues { convertParameterSchema($0) } + } + + return result + } + + private func convertToolChoice(_ toolChoice: ToolChoice?) -> String? { + guard let toolChoice else { return nil } + + switch toolChoice { + case .auto: + return "auto" + case .none: + return "none" + case .required: + return "required" + case let .specific(toolName): + return toolName + } + } + + private func convertFromOpenAIResponse(_ response: OpenAIResponse) throws -> ModelResponse { + guard let choice = response.choices.first else { + throw TachikomaError.apiError(message: "No choices in OpenAI response") + } + + var content: [AssistantContent] = [] + + if let messageContent = choice.message.content { + switch messageContent { + case let .string(text): + content.append(.outputText(text)) + case let .array(parts): + // Extract text from content parts + let text = parts.compactMap { $0.text }.joined() + if !text.isEmpty { + content.append(.outputText(text)) + } + } + } + + if let toolCalls = choice.message.toolCalls { + for toolCall in toolCalls { + content.append(.toolCall(ToolCallItem( + id: toolCall.id, + type: .function, + function: FunctionCall( + name: toolCall.function.name, + arguments: toolCall.function.arguments ?? "")))) + } + } + + let usage = response.usage.map { usage in + Usage( + promptTokens: usage.promptTokens, + completionTokens: usage.completionTokens, + totalTokens: usage.totalTokens, + promptTokensDetails: nil, + completionTokensDetails: nil) + } + + return ModelResponse( + id: response.id, + model: response.model, + content: content, + usage: usage, + flagged: false, + finishReason: convertFinishReason(choice.finishReason)) + } + + private func convertFinishReason(_ reason: String?) -> FinishReason? { + guard let reason else { return nil } + return FinishReason(rawValue: reason) + } + + private func processStreamChunk(_ chunk: OpenAIStreamChunk) -> [StreamEvent]? { + var events: [StreamEvent] = [] + + if let delta = chunk.choices?.first?.delta { + if let content = delta.content { + events.append(.textDelta(StreamTextDelta(delta: content, index: 0))) + } + + if let toolCalls = delta.toolCalls { + for toolCall in toolCalls { + if let id = toolCall.id, let function = toolCall.function { + events.append(.toolCallDelta(StreamToolCallDelta( + id: id, + index: toolCall.index ?? 0, + function: FunctionCallDelta( + name: function.name, + arguments: function.arguments)))) + } + } + } + } + + if let finishReason = chunk.choices?.first?.finishReason { + events.append(.responseCompleted(StreamResponseCompleted( + id: chunk.id ?? "", + usage: nil, + finishReason: convertFinishReason(finishReason)))) + } + + return events.isEmpty ? nil : events + } + + private func handleErrorResponse(data: Data, response: HTTPURLResponse) throws { + if let errorResponse = try? JSONDecoder().decode(OpenAIErrorResponse.self, from: data) { + let message = errorResponse.error.message + let code = errorResponse.error.code + + switch response.statusCode { + case 401: + throw TachikomaError.authenticationFailed + case 429: + throw TachikomaError.rateLimited + case 400: + if message.contains("context_length_exceeded") { + throw TachikomaError.contextLengthExceeded + } else { + throw TachikomaError.invalidRequest(message) + } + case 500...599: + throw TachikomaError.modelOverloaded + default: + throw TachikomaError.apiError(message: message, code: code) + } + } else { + throw TachikomaError.apiError(message: "HTTP \(response.statusCode)", code: "\(response.statusCode)") + } + } +} \ No newline at end of file diff --git a/Sources/Providers/OpenAI/OpenAITypes.swift b/Sources/Providers/OpenAI/OpenAITypes.swift new file mode 100644 index 0000000..d44ecf6 --- /dev/null +++ b/Sources/Providers/OpenAI/OpenAITypes.swift @@ -0,0 +1,481 @@ +import Foundation + +// MARK: - Helper Types + +/// Sendable wrapper for Any values +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnySendable: Codable, @unchecked Sendable { + public let value: Any + + public init(_ value: Any) { + self.value = value + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let intValue = try? container.decode(Int.self) { + self.value = intValue + } else if let doubleValue = try? container.decode(Double.self) { + self.value = doubleValue + } else if let boolValue = try? container.decode(Bool.self) { + self.value = boolValue + } else if let stringValue = try? container.decode(String.self) { + self.value = stringValue + } else if let arrayValue = try? container.decode([AnySendable].self) { + self.value = arrayValue.map(\.value) + } else if let dictValue = try? container.decode([String: AnySendable].self) { + self.value = dictValue.mapValues(\.value) + } else { + self.value = NSNull() + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + + switch value { + case let intValue as Int: + try container.encode(intValue) + case let doubleValue as Double: + try container.encode(doubleValue) + case let boolValue as Bool: + try container.encode(boolValue) + case let stringValue as String: + try container.encode(stringValue) + case let arrayValue as [Any]: + try container.encode(arrayValue.map { AnySendable($0) }) + case let dictValue as [String: Any]: + try container.encode(dictValue.mapValues { AnySendable($0) }) + default: + try container.encodeNil() + } + } +} + +// MARK: - OpenAI API Request Types + +/// Chat Completions API request format +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIChatRequest: Codable, Sendable { + public let model: String + public let messages: [OpenAIMessage] + public let tools: [OpenAITool]? + public let toolChoice: String? + public let temperature: Double? + public let topP: Double? + public let stream: Bool? + public let maxTokens: Int? + + enum CodingKeys: String, CodingKey { + case model, messages, tools, temperature, stream + case toolChoice = "tool_choice" + case topP = "top_p" + case maxTokens = "max_tokens" + } +} + +/// Responses API request format (for o3/o4 models) +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIResponsesRequest: Codable, Sendable { + public let model: String + public let input: [OpenAIMessage] + public let tools: [OpenAIResponsesTool]? + public let toolChoice: String? + public let temperature: Double? + public let topP: Double? + public let stream: Bool? + public let maxOutputTokens: Int? + public let reasoning: OpenAIReasoning? + + enum CodingKeys: String, CodingKey { + case model, input, tools, temperature, stream, reasoning + case toolChoice = "tool_choice" + case topP = "top_p" + case maxOutputTokens = "max_output_tokens" + } +} + +/// OpenAI message format +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIMessage: Codable, Sendable { + public let role: String + public let content: MessageContent? + public let toolCalls: [OpenAIToolCall]? + public let toolCallId: String? + + public enum MessageContent: Codable, Sendable { + case string(String) + case array([OpenAIMessageContentPart]) + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let arrayValue = try? container.decode([OpenAIMessageContentPart].self) { + self = .array(arrayValue) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unable to decode message content") + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(value): + try container.encode(value) + case let .array(value): + try container.encode(value) + } + } + } + + enum CodingKeys: String, CodingKey { + case role, content + case toolCalls = "tool_calls" + case toolCallId = "tool_call_id" + } + + public init(role: String, content: MessageContent? = nil, toolCalls: [OpenAIToolCall]? = nil, toolCallId: String? = nil) { + self.role = role + self.content = content + self.toolCalls = toolCalls + self.toolCallId = toolCallId + } +} + +/// OpenAI message content part for multimodal messages +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIMessageContentPart: Codable, Sendable { + public let type: String + public let text: String? + public let imageUrl: OpenAIImageUrl? + + enum CodingKeys: String, CodingKey { + case type, text + case imageUrl = "image_url" + } + + public init(type: String, text: String? = nil, imageUrl: OpenAIImageUrl? = nil) { + self.type = type + self.text = text + self.imageUrl = imageUrl + } +} + +/// OpenAI image URL format +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIImageUrl: Codable, Sendable { + public let url: String + public let detail: String? + + public init(url: String, detail: String? = nil) { + self.url = url + self.detail = detail + } +} + +/// OpenAI tool definition for Chat Completions API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAITool: Codable, Sendable { + public let type: String + public let function: OpenAIFunction + + public init(type: String, function: OpenAIFunction) { + self.type = type + self.function = function + } +} + +/// OpenAI tool definition for Responses API +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIResponsesTool: Codable, Sendable { + public let type: String + public let name: String + public let description: String + public let parameters: [String: AnySendable] + + enum CodingKeys: String, CodingKey { + case type, name, description, parameters + } + + public init(type: String, name: String, description: String, parameters: [String: Any]) { + self.type = type + self.name = name + self.description = description + self.parameters = parameters.mapValues { AnySendable($0) } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.type = try container.decode(String.self, forKey: .type) + self.name = try container.decode(String.self, forKey: .name) + self.description = try container.decode(String.self, forKey: .description) + + // Decode parameters as generic JSON + let parametersContainer = try container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: .parameters) + var parameters: [String: AnySendable] = [:] + for key in parametersContainer.allKeys { + parameters[key.stringValue] = try parametersContainer.decode(AnySendable.self, forKey: key) + } + self.parameters = parameters + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + + // Skip encoding parameters for now - this needs to be handled at runtime + try container.encodeIfPresent(nil as String?, forKey: .parameters) + } +} + +/// OpenAI function definition +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIFunction: Codable, Sendable { + public let name: String + public let description: String? + public let parameters: [String: AnySendable]? + public let arguments: String? + + enum CodingKeys: String, CodingKey { + case name, description, parameters, arguments + } + + public init(name: String, description: String? = nil, parameters: [String: Any]? = nil, arguments: String? = nil) { + self.name = name + self.description = description + self.parameters = parameters?.mapValues { AnySendable($0) } + self.arguments = arguments + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.description = try container.decodeIfPresent(String.self, forKey: .description) + self.arguments = try container.decodeIfPresent(String.self, forKey: .arguments) + + if container.contains(.parameters) { + let parametersContainer = try container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: .parameters) + var parameters: [String: AnySendable] = [:] + for key in parametersContainer.allKeys { + parameters[key.stringValue] = try parametersContainer.decode(AnySendable.self, forKey: key) + } + self.parameters = parameters + } else { + self.parameters = nil + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encodeIfPresent(description, forKey: .description) + try container.encodeIfPresent(arguments, forKey: .arguments) + + if let parameters = parameters { + // Convert AnySendable parameters to AnyCodable for encoding + let codableParams = parameters.mapValues { AnyCodable($0.value) } + try container.encode(codableParams, forKey: .parameters) + } + } +} + +/// OpenAI tool call +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIToolCall: Codable, Sendable { + public let id: String + public let type: String + public let function: OpenAIFunction + + public init(id: String, type: String, function: OpenAIFunction) { + self.id = id + self.type = type + self.function = function + } +} + +/// OpenAI reasoning configuration for o3/o4 models +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIReasoning: Codable, Sendable { + public let effort: String + public let summary: String + + public init(effort: String, summary: String) { + self.effort = effort + self.summary = summary + } +} + +// MARK: - OpenAI Response Types + +/// OpenAI Chat Completions response +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIResponse: Codable, Sendable { + public let id: String + public let model: String + public let choices: [OpenAIChoice] + public let usage: OpenAIUsage? +} + +/// OpenAI response choice +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIChoice: Codable, Sendable { + public let message: OpenAIMessage + public let finishReason: String? + + enum CodingKeys: String, CodingKey { + case message + case finishReason = "finish_reason" + } +} + +/// OpenAI usage information +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIUsage: Codable, Sendable { + public let promptTokens: Int + public let completionTokens: Int + public let totalTokens: Int + + enum CodingKeys: String, CodingKey { + case promptTokens = "prompt_tokens" + case completionTokens = "completion_tokens" + case totalTokens = "total_tokens" + } +} + +// MARK: - OpenAI Streaming Types + +/// OpenAI streaming chunk +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIStreamChunk: Codable, Sendable { + public let id: String? + public let model: String? + public let choices: [OpenAIStreamChoice]? +} + +/// OpenAI streaming choice +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIStreamChoice: Codable, Sendable { + public let delta: OpenAIStreamDelta? + public let finishReason: String? + + enum CodingKeys: String, CodingKey { + case delta + case finishReason = "finish_reason" + } +} + +/// OpenAI streaming delta +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIStreamDelta: Codable, Sendable { + public let content: String? + public let toolCalls: [OpenAIStreamToolCall]? + + enum CodingKeys: String, CodingKey { + case content + case toolCalls = "tool_calls" + } +} + +/// OpenAI streaming tool call +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIStreamToolCall: Codable, Sendable { + public let id: String? + public let type: String? + public let index: Int? + public let function: OpenAIStreamFunction? +} + +/// OpenAI streaming function +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIStreamFunction: Codable, Sendable { + public let name: String? + public let arguments: String? +} + +// MARK: - Error Types + +/// OpenAI error response +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIErrorResponse: Codable, Sendable { + public let error: OpenAIError +} + +/// OpenAI error details +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct OpenAIError: Codable, Sendable { + public let message: String + public let type: String? + public let code: String? +} + +// MARK: - Helper Types + +/// Dynamic coding key for JSON encoding/decoding +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +struct AnyCodingKey: CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } +} + +/// Wrapper for any codable value +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public struct AnyCodable: Codable, @unchecked Sendable { + public let value: Any + + public init(_ value: Any) { + self.value = value + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + + if let intValue = value as? Int { + try container.encode(intValue) + } else if let doubleValue = value as? Double { + try container.encode(doubleValue) + } else if let stringValue = value as? String { + try container.encode(stringValue) + } else if let boolValue = value as? Bool { + try container.encode(boolValue) + } else { + let data = try JSONSerialization.data(withJSONObject: value) + let str = String(data: data, encoding: .utf8) ?? "{}" + try container.encode(str) + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let intValue = try? container.decode(Int.self) { + value = intValue + } else if let doubleValue = try? container.decode(Double.self) { + value = doubleValue + } else if let stringValue = try? container.decode(String.self) { + value = stringValue + } else if let boolValue = try? container.decode(Bool.self) { + value = boolValue + } else { + value = NSNull() + } + } +} + +// MARK: - Container Extensions + +// Note: Complex Any encoding/decoding extensions removed +// We use AnySendable for type-safe handling of dynamic JSON data \ No newline at end of file diff --git a/Sources/Tachikoma.swift b/Sources/Tachikoma.swift new file mode 100644 index 0000000..5d00166 --- /dev/null +++ b/Sources/Tachikoma.swift @@ -0,0 +1,87 @@ +import Foundation +@_exported import Logging + +/// Tachikoma - A comprehensive Swift package for AI model integration +/// +/// Tachikoma provides a unified interface for connecting to various AI providers +/// including OpenAI, Anthropic, Grok (xAI), Ollama, and custom endpoints. +/// It supports both streaming and non-streaming responses, tool calling, +/// multimodal inputs, and configuration management. +/// +/// Named after the AI entity from Ghost in the Shell, Tachikoma embodies +/// the cyberpunk aesthetic of autonomous AI systems. +@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) +public final class Tachikoma: @unchecked Sendable { + public static let shared = Tachikoma() + + private let logger: Logger + + private init() { + self.logger = Logger(label: "build.tachikoma") + } + + /// Get the shared logger instance + public var log: Logger { + logger + } + + /// Get a model instance for the specified model name + /// - Parameter modelName: The model identifier (e.g., "gpt-4.1", "claude-opus-4", "provider-id/model-name") + /// - Returns: A model instance conforming to ModelInterface + /// - Throws: TachikomaError if the model is not available or configuration is invalid + public func getModel(_ modelName: String) async throws -> any ModelInterface { + return try await ModelProvider.shared.getModel(modelName: modelName) + } + + /// Configure OpenAI provider with specific settings + /// - Parameter configuration: OpenAI configuration + public func configureOpenAI(_ configuration: ProviderConfiguration.OpenAI) async { + await ModelProvider.shared.configureOpenAI(configuration) + } + + /// Configure Anthropic provider with specific settings + /// - Parameter configuration: Anthropic configuration + public func configureAnthropic(_ configuration: ProviderConfiguration.Anthropic) async { + await ModelProvider.shared.configureAnthropic(configuration) + } + + /// Configure Ollama provider with specific settings + /// - Parameter configuration: Ollama configuration + public func configureOllama(_ configuration: ProviderConfiguration.Ollama) async { + await ModelProvider.shared.configureOllama(configuration) + } + + /// Configure Grok provider with specific settings + /// - Parameter configuration: Grok configuration + public func configureGrok(_ configuration: ProviderConfiguration.Grok) async { + await ModelProvider.shared.configureGrok(configuration) + } + + /// Set up all providers from environment variables + /// - Throws: TachikomaError if setup fails + public func setupFromEnvironment() async throws { + try await ModelProvider.shared.setupFromEnvironment() + } + + /// List all available models from configured providers + /// - Returns: Array of available model identifiers + public func availableModels() async -> [String] { + return await ModelProvider.shared.listModels() + } + + /// Clear all cached model instances + public func clearModelCache() async { + await ModelProvider.shared.clearCache() + } + + /// Register a custom model factory + /// - Parameters: + /// - modelName: The model name to register + /// - factory: Factory closure that creates the model instance + public func registerModel( + name modelName: String, + factory: @escaping @Sendable () throws -> any ModelInterface) async + { + await ModelProvider.shared.register(modelName: modelName, factory: factory) + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/AnthropicModelTests.swift b/Tests/TachikomaTests/AnthropicModelTests.swift new file mode 100644 index 0000000..8c34cd7 --- /dev/null +++ b/Tests/TachikomaTests/AnthropicModelTests.swift @@ -0,0 +1,373 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("Anthropic Model Tests") +struct AnthropicModelTests { + @Test("Model initialization") + func modelInitialization() async throws { + let model = AnthropicModel( + apiKey: "sk-ant-test-key-123456789", + modelName: "claude-opus-4-20250514") + + #expect(model.maskedApiKey == "sk-ant...789") + } + + @Test("API key masking") + func apiKeyMasking() async throws { + // Test short key + let shortModel = AnthropicModel(apiKey: "short") + #expect(shortModel.maskedApiKey == "***") + + // Test normal key + let normalModel = AnthropicModel(apiKey: "sk-ant-api-key-1234567890abcdefghijklmnopqrstuvwxyz") + #expect(normalModel.maskedApiKey == "sk-ant...xyz") + } + + @Test("System message extraction") + func systemMessageExtraction() async throws { + let model = AnthropicModel(apiKey: "test-key") + + let request = ModelRequest( + messages: [ + Message.system(content: "You are a helpful assistant."), + Message.user(content: .text("Hello!")), + ], + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // System messages should be properly handled + #expect(request.messages.first?.type == .system) + } + + @Test("Tool conversion") + func toolConversion() async throws { + let model = AnthropicModel(apiKey: "test-key") + + let toolDef = ToolDefinition( + function: FunctionDefinition( + name: "get_weather", + description: "Get the current weather", + parameters: ToolParameters( + properties: ["location": ParameterSchema(type: .string, description: "The location")], + required: ["location"]))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("What's the weather?")), + ], + tools: [toolDef], + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + #expect(request.tools?.count == 1) + #expect(request.tools?.first?.function.name == "get_weather") + + // Test that the model can process the request (will fail at network level) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Image content handling") + func imageContentHandling() async throws { + let model = AnthropicModel(apiKey: "test-key", modelName: "claude-opus-4-20250514") + + let imageData = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" + + let request = ModelRequest( + messages: [ + Message.user(content: .multimodal([ + MessageContentPart(type: "text", text: "What's in this image?"), + MessageContentPart(type: "image", imageUrl: ImageContent(base64: imageData)), + ])), + ], + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + if case let .user(_, content) = request.messages.first, + case let .multimodal(parts) = content + { + #expect(parts.count == 2) + } else { + Issue.record("Expected multimodal content") + } + + // Test that URL images are supported + let urlRequest = ModelRequest( + messages: [ + Message.user(content: .image(ImageContent(url: "https://example.com/image.jpg"))), + ], + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + if case let .user(_, content) = urlRequest.messages.first, + case .image = content + { + // Expected image content + } else { + Issue.record("Expected image content") + } + + // Test processing (will fail at network level) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Message type conversion") + func messageTypeConversion() async throws { + let model = AnthropicModel(apiKey: "test-key") + + // Test various message types + let messages: [Message] = [ + Message.system(content: "You are Claude."), + Message.user(content: .text("Hello Claude!")), + Message.assistant(content: [.outputText("Hello! How can I help you?")]), + Message.user(content: .text("What's 2+2?")), + Message.assistant(content: [.outputText("2 + 2 = 4")]), + ] + + let request = ModelRequest( + messages: messages, + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // Verify message structure + #expect(request.messages.count == 5) + #expect(request.messages[0].type == .system) + #expect(request.messages[1].type == .user) + #expect(request.messages[2].type == .assistant) + + // Test processing (will fail at network level) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Streaming response handling") + func streamingResponseHandling() async throws { + let model = AnthropicModel(apiKey: "test-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Write a short poem")), + ], + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // Test streaming (will fail at network level) + do { + let stream = try await model.getStreamedResponse(request: request) + var eventCount = 0 + + for try await event in stream { + eventCount += 1 + _ = event + } + + Issue.record("Expected network error but got \(eventCount) events") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Tool call handling") + func toolCallHandling() async throws { + let model = AnthropicModel(apiKey: "test-key") + + // Create a tool call message + let toolCall = ToolCallItem( + id: "call_123", + type: .function, + function: FunctionCall( + name: "get_weather", + arguments: "{\"location\": \"Paris\"}" + ) + ) + + let messages: [Message] = [ + Message.user(content: .text("What's the weather in Paris?")), + Message.assistant(content: [.toolCall(toolCall)]), + Message.tool(toolCallId: "call_123", content: "It's sunny, 22°C"), + ] + + let request = ModelRequest( + messages: messages, + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // Test tool call processing (will fail at network level) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Model variants") + func modelVariants() async throws { + let modelNames = [ + "claude-opus-4-20250514", + "claude-sonnet-4-20250514", + "claude-opus-4-20250514-thinking", + "claude-sonnet-4-20250514-thinking", + "claude-3-7-sonnet", + "claude-3-5-sonnet", + "claude-3-5-haiku", + ] + + for modelName in modelNames { + let model = AnthropicModel(apiKey: "test-key", modelName: modelName) + #expect(model.maskedApiKey == "***") + + // Test that each model variant can be created and handles requests + let request = ModelRequest( + messages: [Message.user(content: .text("Test"))], + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(modelName)") + } catch { + #expect(error is TachikomaError) + } + } + } + + @Test("Error handling") + func errorHandling() async throws { + let model = AnthropicModel(apiKey: "invalid-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error but got success") + } catch let error as TachikomaError { + // Verify we get appropriate error types + switch error { + case .apiError, .authenticationFailed, .networkError: + // Expected error types for invalid API key + break + default: + Issue.record("Unexpected error type: \(error)") + } + } catch { + Issue.record("Unexpected error type: \(type(of: error))") + } + } + + @Test("Audio content handling") + func audioContentHandling() async throws { + let model = AnthropicModel(apiKey: "test-key") + + let audioContent = AudioContent( + transcript: "Hello, this is a test transcript.", + duration: 5.0 + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .audio(audioContent)), + ], + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // Test audio content processing + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("File content rejection") + func fileContentRejection() async throws { + let model = AnthropicModel(apiKey: "test-key") + + let fileContent = FileContent( + id: nil, + url: nil, + name: "test.txt" + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .file(fileContent)), + ], + settings: ModelSettings(modelName: "claude-opus-4-20250514")) + + // File content should be rejected + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for file content") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - file content not supported + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } +} + +// MARK: - Provider Configuration Tests + +@Suite("Anthropic Provider Configuration Tests") +struct AnthropicProviderConfigurationTests { + @Test("Provider configuration") + func providerConfiguration() async throws { + let tachikoma = Tachikoma.shared + + // Test custom Anthropic configuration + let config = ProviderConfiguration.Anthropic( + apiKey: "test-key", + baseURL: URL(string: "https://api.anthropic.com/v1")! + ) + + await tachikoma.configureAnthropic(config) + } + + @Test("Model registration") + func modelRegistration() async throws { + let tachikoma = Tachikoma.shared + + // Register Claude models + let modelNames = [ + "claude-opus-4", + "claude-sonnet-4", + "claude-3-5-sonnet", + "claude-3-5-haiku", + ] + + for modelName in modelNames { + await tachikoma.registerModel(name: modelName, factory: { + AnthropicModel(apiKey: "test-key", modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel(modelName) + #expect(model is AnthropicModel) + } catch { + Issue.record("Failed to get model \(modelName): \(error)") + } + } + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/GrokModelTests.swift b/Tests/TachikomaTests/GrokModelTests.swift new file mode 100644 index 0000000..47f2f96 --- /dev/null +++ b/Tests/TachikomaTests/GrokModelTests.swift @@ -0,0 +1,331 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("Grok Model Tests") +struct GrokModelTests { + @Test("Model initialization") + func modelInitialization() async throws { + let model = GrokModel( + apiKey: "test-key-123456", + baseURL: URL(string: "https://api.x.ai/v1")!) + + #expect(model.maskedApiKey == "test-k...56") + } + + @Test("API key masking") + func apiKeyMasking() async throws { + // Test short key + let shortModel = GrokModel(apiKey: "short") + #expect(shortModel.maskedApiKey == "***") + + // Test normal key + let normalModel = GrokModel(apiKey: "test-api-key-1234567890abcdefghijklmnopqrstuvwxyz") + #expect(normalModel.maskedApiKey == "test-a...yz") + } + + @Test("Default base URL") + func defaultBaseURL() async throws { + let model = GrokModel(apiKey: "test-key-123456") + + // Verify it uses the correct xAI API endpoint + // We can't directly access baseURL, but we can test the behavior + #expect(model.maskedApiKey == "test-k...56") + } + + @Test("Parameter filtering for Grok 4") + func grok4ParameterFiltering() async throws { + let model = GrokModel(apiKey: "test-key") + + // Create a request with parameters that should be filtered for Grok 4 + let settings = ModelSettings( + modelName: "grok-4", + temperature: 0.7, + frequencyPenalty: 0.5, // Should be removed for grok-4 + presencePenalty: 0.5, // Should be removed for grok-4 + stopSequences: ["stop"] // Should be removed for grok-4 + ) + + let request = ModelRequest( + messages: [ + Message.system(content: "Test system message"), + Message.user(content: .text("Test user message")), + ], + tools: nil, + settings: settings) + + // We can't directly test the filtering without mocking the network request + // But we can verify the model handles the request without crashing + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + // Expected to fail due to no valid API key/network + #expect(error is TachikomaError) + } + } + + @Test("Tool parameter conversion") + func toolParameterConversion() async throws { + let model = GrokModel(apiKey: "test-key") + + // Create a tool definition + let tool = ToolDefinition( + function: FunctionDefinition( + name: "test_tool", + description: "A test tool", + parameters: ToolParameters( + type: "object", + properties: [ + "message": ParameterSchema( + type: .string, + description: "A test message"), + "count": ParameterSchema( + type: .integer, + description: "A count", + minimum: 0, + maximum: 100), + ], + required: ["message"]))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Use the test tool")), + ], + tools: [tool], + settings: ModelSettings(modelName: "grok-4")) + + // Verify the model can process tool definitions + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + // Expected to fail due to no valid API key/network + #expect(error is TachikomaError) + } + } + + @Test("Message type conversion") + func messageTypeConversion() async throws { + let model = GrokModel(apiKey: "test-key") + + // Test various message types + let messages: [Message] = [ + Message.system(content: "System prompt"), + Message.user(content: .text("User text")), + Message.assistant(content: [.outputText("Assistant response")]), + Message.tool( + toolCallId: "tool-123", + content: "Tool result"), + ] + + let request = ModelRequest( + messages: messages, + tools: nil, + settings: ModelSettings(modelName: "grok-4")) + + // Verify message conversion doesn't crash + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + // Expected to fail due to no valid API key/network + #expect(error is TachikomaError) + } + } + + @Test("Multimodal message support") + func multimodalMessageSupport() async throws { + let model = GrokModel(apiKey: "test-key", modelName: "grok-2-vision-1212") + + // Create a multimodal message with text and image + let imageData = Data([0xFF, 0xD8, 0xFF]) // Minimal JPEG header + + let request = ModelRequest( + messages: [ + Message.user(content: .multimodal([ + MessageContentPart(type: "text", text: "What is in this image?"), + MessageContentPart(type: "image", imageUrl: ImageContent(base64: imageData.base64EncodedString())), + ])), + ], + tools: nil, + settings: ModelSettings(modelName: "grok-2-vision-1212")) + + // Verify multimodal content handling + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + // Expected to fail due to no valid API key/network + #expect(error is TachikomaError) + } + } + + @Test("Streaming response handling") + func streamingResponse() async throws { + let model = GrokModel(apiKey: "test-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Stream this response")), + ], + tools: nil, + settings: ModelSettings(modelName: "grok-4")) + + // Test streaming + do { + let stream = try await model.getStreamedResponse(request: request) + var eventCount = 0 + + for try await event in stream { + eventCount += 1 + // Would normally process events here + _ = event + } + + Issue.record("Expected network error but got success with \(eventCount) events") + } catch { + // Expected to fail due to no valid API key/network + #expect(error is TachikomaError) + } + } + + @Test("Error handling") + func errorHandling() async throws { + let model = GrokModel(apiKey: "invalid-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "grok-4")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error but got success") + } catch let error as TachikomaError { + // Verify we get appropriate error types + switch error { + case .apiError, .authenticationFailed: + // Expected error types for invalid API key + break + default: + Issue.record("Unexpected error type: \(error)") + } + } catch { + Issue.record("Unexpected error type: \(type(of: error))") + } + } + + @Test("Reasoning message rejection") + func reasoningMessageRejection() async throws { + let model = GrokModel(apiKey: "test-key") + + let request = ModelRequest( + messages: [ + Message.reasoning(content: "Some reasoning"), + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "grok-4")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for reasoning messages") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - reasoning messages not supported + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } +} + +// MARK: - Model Provider Tests + +@Suite("Grok Model Provider Tests") +struct GrokModelProviderTests { + @Test("Grok model registration") + func grokModelRegistration() async throws { + let tachikoma = Tachikoma.shared + + // Register a test Grok model + await tachikoma.registerModel(name: "test-grok", factory: { + GrokModel(apiKey: "test-key", modelName: "grok-4") + }) + + // Test that we can get the model + do { + let model = try await tachikoma.getModel("test-grok") + #expect(model is GrokModel) + } catch { + Issue.record("Failed to get registered model: \(error)") + } + } + + @Test("Model name variants") + func modelNameVariants() async throws { + let tachikoma = Tachikoma.shared + + // Register various Grok model names + let modelNames = [ + "grok-4", + "grok-4-0709", + "grok-4-latest", + "grok-2-1212", + "grok-2-vision-1212", + "grok-beta", + "grok-vision-beta", + ] + + for modelName in modelNames { + await tachikoma.registerModel(name: modelName, factory: { + GrokModel(apiKey: "test-key", modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel(modelName) + #expect(model is GrokModel) + } catch { + Issue.record("Failed to get model \(modelName): \(error)") + } + } + } + + @Test("Parameter filtering detection") + func parameterFilteringDetection() async throws { + // Test that Grok 3 and 4 models filter parameters correctly + let grok3Model = GrokModel(apiKey: "test-key", modelName: "grok-3") + let grok4Model = GrokModel(apiKey: "test-key", modelName: "grok-4") + let grokBetaModel = GrokModel(apiKey: "test-key", modelName: "grok-beta") + + // All should handle the same request without errors (though network will fail) + let settings = ModelSettings( + modelName: "grok-4", + temperature: 0.7, + frequencyPenalty: 0.5, + presencePenalty: 0.5, + stopSequences: ["stop"] + ) + + let request = ModelRequest( + messages: [Message.user(content: .text("Test"))], + settings: settings + ) + + // Test each model handles parameter filtering + for (name, model) in [("grok-3", grok3Model), ("grok-4", grok4Model), ("grok-beta", grokBetaModel)] { + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(name)") + } catch { + // Expected to fail due to network/auth, but not due to parameter issues + #expect(error is TachikomaError) + } + } + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/OllamaModelTests.swift b/Tests/TachikomaTests/OllamaModelTests.swift new file mode 100644 index 0000000..f4cdbd0 --- /dev/null +++ b/Tests/TachikomaTests/OllamaModelTests.swift @@ -0,0 +1,611 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("Ollama Model Tests") +struct OllamaModelTests { + @Test("Model initialization") + func modelInitialization() async throws { + let model = OllamaModel( + modelName: "llama3.3", + baseURL: URL(string: "http://localhost:11434")!) + + // Ollama doesn't use API keys, so maskedApiKey should return a placeholder + #expect(model.maskedApiKey == "local-ollama") + } + + @Test("Default base URL") + func defaultBaseURL() async throws { + let model = OllamaModel(modelName: "llama3.3") + + // Should use default localhost URL + #expect(model.maskedApiKey == "local-ollama") + } + + @Test("Custom base URL") + func customBaseURL() async throws { + let customURL = URL(string: "http://remote-server:11434")! + let model = OllamaModel(modelName: "llama3.3", baseURL: customURL) + + #expect(model.maskedApiKey == "local-ollama") + } + + @Test("Tool calling support detection") + func toolCallingSupportDetection() async throws { + // Models with tool calling support + let supportedModels = [ + "llama3.3", + "llama3.2", + "llama3.1", + "mistral-nemo", + "firefunction-v2", + "command-r-plus", + "command-r", + ] + + for modelName in supportedModels { + let model = OllamaModel(modelName: modelName) + + let toolDef = ToolDefinition( + function: FunctionDefinition( + name: "get_time", + description: "Get current time", + parameters: ToolParameters( + properties: [:], + required: []))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("What time is it?")), + ], + tools: [toolDef], + settings: ModelSettings(modelName: "llama3.3")) + + // Should handle tool calls (will fail at network level) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(modelName)") + } catch { + #expect(error is TachikomaError) + } + } + } + + @Test("Vision model tool rejection") + func visionModelToolRejection() async throws { + // Vision models that don't support tool calling + let visionModels = [ + "llava", + "bakllava", + "llama3.2-vision:11b", + "qwen2.5vl:7b", + ] + + for modelName in visionModels { + let model = OllamaModel(modelName: modelName) + + let toolDef = ToolDefinition( + function: FunctionDefinition( + name: "get_time", + description: "Get current time", + parameters: ToolParameters( + properties: [:], + required: []))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("What time is it?")), + ], + tools: [toolDef], + settings: ModelSettings(modelName: "llama3.3")) + + // Should reject tool calls for vision models + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for vision model \(modelName) with tools") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - vision models don't support tool calling + break + default: + Issue.record("Expected invalidRequest error for \(modelName), got: \(error)") + } + } + } + } + + @Test("Message type conversion") + func messageTypeConversion() async throws { + let model = OllamaModel(modelName: "llama3.3") + + // Test various message types + let messages: [Message] = [ + Message.system(content: "You are a helpful assistant."), + Message.user(content: .text("Hello!")), + Message.assistant(content: [.outputText("Hi there!")]), + Message.tool( + toolCallId: "call_123", + content: "Current time: 2:30 PM"), + ] + + let request = ModelRequest( + messages: messages, + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + // Verify message conversion doesn't crash + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Multimodal message support for vision models") + func multimodalMessageSupport() async throws { + let model = OllamaModel(modelName: "llava") + + // Create a multimodal message with text and image + let imageData = Data([0xFF, 0xD8, 0xFF]) // Minimal JPEG header + + let request = ModelRequest( + messages: [ + Message.user(content: .multimodal([ + MessageContentPart(type: "text", text: "What is in this image?"), + MessageContentPart(type: "image", imageUrl: ImageContent(base64: imageData.base64EncodedString())), + ])), + ], + tools: nil, + settings: ModelSettings(modelName: "llava")) + + // Verify multimodal content handling for vision models + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Text-only models image rejection") + func textOnlyModelsImageRejection() async throws { + let model = OllamaModel(modelName: "llama3.3") // Text-only model + + let imageData = Data([0xFF, 0xD8, 0xFF]) + + let request = ModelRequest( + messages: [ + Message.user(content: .image(ImageContent(base64: imageData.base64EncodedString()))), + ], + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + // Should reject image content for text-only models + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for text-only model with image") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - text-only models don't support images + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } + + @Test("Streaming response handling") + func streamingResponse() async throws { + let model = OllamaModel(modelName: "llama3.3") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Tell me a joke")), + ], + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + // Test streaming + do { + let stream = try await model.getStreamedResponse(request: request) + var eventCount = 0 + + for try await event in stream { + eventCount += 1 + _ = event + } + + Issue.record("Expected network error but got success with \(eventCount) events") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Extended timeout handling") + func extendedTimeoutHandling() async throws { + let model = OllamaModel(modelName: "llama3.3") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Generate a long response")), + ], + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + // Ollama requests should have extended timeouts (5 minutes) + // We can't test the actual timeout without a real server, + // but we can verify the request structure + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Tool call JSON parsing") + func toolCallJSONParsing() async throws { + let model = OllamaModel(modelName: "llama3.3") + + // Test models that output tool calls as JSON in content + let toolDef = ToolDefinition( + function: FunctionDefinition( + name: "calculate", + description: "Perform calculation", + parameters: ToolParameters( + properties: [ + "expression": ParameterSchema(type: .string, description: "Math expression") + ], + required: ["expression"]))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("What is 5 + 3?")), + ], + tools: [toolDef], + settings: ModelSettings(modelName: "llama3.3")) + + // Should handle tool call parsing + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Error handling") + func errorHandling() async throws { + let model = OllamaModel(modelName: "nonexistent-model") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error but got success") + } catch let error as TachikomaError { + // Verify we get appropriate error types + switch error { + case .apiError, .networkError: + // Expected error types for nonexistent model + break + default: + Issue.record("Unexpected error type: \(error)") + } + } catch { + Issue.record("Unexpected error type: \(type(of: error))") + } + } + + @Test("Reasoning message rejection") + func reasoningMessageRejection() async throws { + let model = OllamaModel(modelName: "llama3.3") + + let request = ModelRequest( + messages: [ + Message.reasoning(content: "Let me think..."), + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for reasoning messages") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - reasoning messages not supported + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } + + @Test("File content rejection") + func fileContentRejection() async throws { + let model = OllamaModel(modelName: "llama3.3") + + let fileContent = FileContent( + id: nil, + url: nil, + name: "test.txt" + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .file(fileContent)), + ], + settings: ModelSettings(modelName: "llama3.3")) + + // File content should be rejected + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for file content") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - file content not supported + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } + + @Test("Audio content handling") + func audioContentHandling() async throws { + let model = OllamaModel(modelName: "llama3.3") + + let audioContent = AudioContent( + transcript: "Hello, this is a test transcript.", + duration: 5.0 + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .audio(audioContent)), + ], + settings: ModelSettings(modelName: "llama3.3")) + + // Test audio content processing (converts to text) + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } +} + +// MARK: - Model Provider Tests + +@Suite("Ollama Model Provider Tests") +struct OllamaModelProviderTests { + @Test("Ollama model registration") + func ollamaModelRegistration() async throws { + let tachikoma = Tachikoma.shared + + // Register a test Ollama model + await tachikoma.registerModel(name: "test-llama", factory: { + OllamaModel(modelName: "llama3.3") + }) + + // Test that we can get the model + do { + let model = try await tachikoma.getModel("test-llama") + #expect(model is OllamaModel) + } catch { + Issue.record("Failed to get registered model: \(error)") + } + } + + @Test("Model name variants") + func modelNameVariants() async throws { + let tachikoma = Tachikoma.shared + + // Register various Ollama model names + let modelNames = [ + "llama3.3", + "llama3.2", + "llama3.1", + "llava", + "mistral-nemo", + "firefunction-v2", + "command-r-plus", + "deepseek-r1:8b", + ] + + for modelName in modelNames { + await tachikoma.registerModel(name: modelName, factory: { + OllamaModel(modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel(modelName) + #expect(model is OllamaModel) + } catch { + Issue.record("Failed to get model \(modelName): \(error)") + } + } + } + + @Test("Base URL configuration") + func baseURLConfiguration() async throws { + let tachikoma = Tachikoma.shared + + // Test custom base URL + let customURL = URL(string: "http://remote-ollama:11434")! + + await tachikoma.registerModel(name: "custom-ollama", factory: { + OllamaModel(modelName: "llama3.3", baseURL: customURL) + }) + + do { + let model = try await tachikoma.getModel("custom-ollama") + #expect(model is OllamaModel) + } catch { + Issue.record("Failed to get custom URL model: \(error)") + } + } + + @Test("Tool support matrix") + func toolSupportMatrix() async throws { + let tachikoma = Tachikoma.shared + + // Models with tool support + let toolSupportedModels = [ + "llama3.3", + "llama3.2", + "mistral-nemo", + "firefunction-v2", + ] + + // Models without tool support + let nonToolModels = [ + "llava", + "bakllava", + "devstral", + ] + + let toolDef = ToolDefinition( + function: FunctionDefinition( + name: "test_tool", + description: "Test tool", + parameters: ToolParameters(properties: [:], required: []))) + + // Test tool-supported models + for modelName in toolSupportedModels { + await tachikoma.registerModel(name: "tool-\(modelName)", factory: { + OllamaModel(modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel("tool-\(modelName)") + #expect(model is OllamaModel) + + // Should accept tool definitions + let request = ModelRequest( + messages: [Message.user(content: .text("Use tool"))], + tools: [toolDef], + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(modelName)") + } catch { + #expect(error is TachikomaError) + } + } catch { + Issue.record("Failed to get tool model \(modelName): \(error)") + } + } + + // Test non-tool models + for modelName in nonToolModels { + await tachikoma.registerModel(name: "no-tool-\(modelName)", factory: { + OllamaModel(modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel("no-tool-\(modelName)") + #expect(model is OllamaModel) + + // Should reject tool definitions + let request = ModelRequest( + messages: [Message.user(content: .text("Use tool"))], + tools: [toolDef], + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for non-tool model \(modelName)") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected for non-tool models + break + default: + Issue.record("Expected invalidRequest for \(modelName), got: \(error)") + } + } + } catch { + Issue.record("Failed to get non-tool model \(modelName): \(error)") + } + } + } + + @Test("Vision capability detection") + func visionCapabilityDetection() async throws { + let tachikoma = Tachikoma.shared + + // Vision models + let visionModels = ["llava", "bakllava", "llama3.2-vision:11b"] + + // Text-only models + let textModels = ["llama3.3", "mistral-nemo", "command-r"] + + let imageData = Data([0xFF, 0xD8, 0xFF]) + + // Test vision models accept images + for modelName in visionModels { + await tachikoma.registerModel(name: "vision-\(modelName)", factory: { + OllamaModel(modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel("vision-\(modelName)") + let request = ModelRequest( + messages: [Message.user(content: .image(ImageContent(base64: imageData.base64EncodedString())))], + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for vision model \(modelName)") + } catch { + #expect(error is TachikomaError) + } + } catch { + Issue.record("Failed vision model test for \(modelName): \(error)") + } + } + + // Test text models reject images + for modelName in textModels { + await tachikoma.registerModel(name: "text-\(modelName)", factory: { + OllamaModel(modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel("text-\(modelName)") + let request = ModelRequest( + messages: [Message.user(content: .image(ImageContent(base64: imageData.base64EncodedString())))], + settings: ModelSettings(modelName: "llama3.3")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for text model \(modelName) with image") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected for text-only models + break + default: + Issue.record("Expected invalidRequest for \(modelName), got: \(error)") + } + } + } catch { + Issue.record("Failed text model test for \(modelName): \(error)") + } + } + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/OpenAIModelTests.swift b/Tests/TachikomaTests/OpenAIModelTests.swift new file mode 100644 index 0000000..a1f1644 --- /dev/null +++ b/Tests/TachikomaTests/OpenAIModelTests.swift @@ -0,0 +1,475 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("OpenAI Model Tests") +struct OpenAIModelTests { + @Test("Model initialization") + func modelInitialization() async throws { + let model = OpenAIModel( + apiKey: "sk-test-key-123456789", + modelName: "gpt-4.1") + + #expect(model.maskedApiKey == "sk-...789") + } + + @Test("API key masking") + func apiKeyMasking() async throws { + // Test short key + let shortModel = OpenAIModel(apiKey: "short") + #expect(shortModel.maskedApiKey == "***") + + // Test normal key + let normalModel = OpenAIModel(apiKey: "sk-test-api-key-1234567890abcdefghijklmnopqrstuvwxyz") + #expect(normalModel.maskedApiKey == "sk-...xyz") + } + + @Test("Default base URL") + func defaultBaseURL() async throws { + let model = OpenAIModel(apiKey: "test-key") + + // Verify it uses the correct OpenAI API endpoint + // We can't directly access baseURL, but we can test the behavior + #expect(model.maskedApiKey == "***") + } + + @Test("Dual API support") + func dualAPISupport() async throws { + let model = OpenAIModel(apiKey: "test-key") + + // Test Chat Completions API request + let chatRequest = ModelRequest( + messages: [ + Message.user(content: .text("Hello")), + ], + tools: nil, + settings: ModelSettings(apiType: "chat")) + + // Test Responses API request (for reasoning models) + let responsesRequest = ModelRequest( + messages: [ + Message.user(content: .text("Hello")), + ], + tools: nil, + settings: ModelSettings(apiType: "responses")) + + // Both should be processable (will fail at network level) + do { + _ = try await model.getResponse(request: chatRequest) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + + do { + _ = try await model.getResponse(request: responsesRequest) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Reasoning models parameter handling") + func reasoningModelsParameterHandling() async throws { + let model = OpenAIModel(apiKey: "test-key", modelName: "o3") + + // Create a request with reasoning parameters + let settings = ModelSettings( + reasoningEffort: "medium", + reasoning: ["summary": "detailed"], + temperature: nil // o3 models don't support temperature + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Solve this complex problem")), + ], + tools: nil, + settings: settings) + + // Should handle reasoning parameters correctly + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Tool parameter conversion") + func toolParameterConversion() async throws { + let model = OpenAIModel(apiKey: "test-key") + + // Create a tool definition + let tool = ToolDefinition( + function: FunctionDefinition( + name: "get_weather", + description: "Get current weather", + parameters: ToolParameters( + type: "object", + properties: [ + "location": ParameterSchema( + type: .string, + description: "The location"), + "units": ParameterSchema( + type: .string, + description: "Temperature units", + enumValues: ["celsius", "fahrenheit"]), + ], + required: ["location"]))) + + let request = ModelRequest( + messages: [ + Message.user(content: .text("What's the weather in Paris?")), + ], + tools: [tool], + settings: ModelSettings(modelName: "gpt-4.1")) + + // Verify the model can process tool definitions + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Message type conversion") + func messageTypeConversion() async throws { + let model = OpenAIModel(apiKey: "test-key") + + // Test various message types + let messages: [Message] = [ + Message.system(content: "You are a helpful assistant."), + Message.user(content: .text("Hello!")), + Message.assistant(content: [.outputText("Hi there!")]), + Message.tool( + toolCallId: "call_123", + content: "Weather data"), + ] + + let request = ModelRequest( + messages: messages, + tools: nil, + settings: ModelSettings(modelName: "gpt-4.1")) + + // Verify message conversion doesn't crash + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Multimodal message support") + func multimodalMessageSupport() async throws { + let model = OpenAIModel(apiKey: "test-key", modelName: "gpt-4o") + + // Create a multimodal message with text and image + let imageData = Data([0xFF, 0xD8, 0xFF]) // Minimal JPEG header + + let request = ModelRequest( + messages: [ + Message.user(content: .multimodal([ + MessageContentPart(type: "text", text: "What is in this image?"), + MessageContentPart(type: "image", imageUrl: ImageContent(base64: imageData.base64EncodedString())), + ])), + ], + tools: nil, + settings: ModelSettings(modelName: "gpt-4.1")) + + // Verify multimodal content handling + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Streaming response handling") + func streamingResponse() async throws { + let model = OpenAIModel(apiKey: "test-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Write a short story")), + ], + tools: nil, + settings: ModelSettings(modelName: "gpt-4.1")) + + // Test streaming + do { + let stream = try await model.getStreamedResponse(request: request) + var eventCount = 0 + + for try await event in stream { + eventCount += 1 + _ = event + } + + Issue.record("Expected network error but got success with \(eventCount) events") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Error handling") + func errorHandling() async throws { + let model = OpenAIModel(apiKey: "invalid-key") + + let request = ModelRequest( + messages: [ + Message.user(content: .text("Test")), + ], + tools: nil, + settings: ModelSettings(modelName: "gpt-4.1")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error but got success") + } catch let error as TachikomaError { + // Verify we get appropriate error types + switch error { + case .apiError, .authenticationFailed: + // Expected error types for invalid API key + break + default: + Issue.record("Unexpected error type: \(error)") + } + } catch { + Issue.record("Unexpected error type: \(type(of: error))") + } + } + + @Test("Reasoning message handling") + func reasoningMessageHandling() async throws { + let model = OpenAIModel(apiKey: "test-key", modelName: "o3") + + let request = ModelRequest( + messages: [ + Message.reasoning(content: "Let me think about this step by step..."), + Message.user(content: .text("What is 2+2?")), + ], + tools: nil, + settings: ModelSettings(modelName: "gpt-4.1")) + + // o3 models should handle reasoning messages + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error but got success") + } catch { + #expect(error is TachikomaError) + } + } + + @Test("Model variants") + func modelVariants() async throws { + let modelNames = [ + "o3", + "o3-mini", + "o3-pro", + "o4-mini", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4o", + "gpt-4o-mini", + ] + + for modelName in modelNames { + let model = OpenAIModel(apiKey: "test-key", modelName: modelName) + #expect(model.maskedApiKey == "***") + + // Test that each model variant can be created and handles requests + let request = ModelRequest( + messages: [Message.user(content: .text("Test"))], + settings: ModelSettings(modelName: "gpt-4.1")) + + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(modelName)") + } catch { + #expect(error is TachikomaError) + } + } + } + + @Test("Temperature parameter filtering") + func temperatureParameterFiltering() async throws { + // o3 models should not support temperature + let o3Model = OpenAIModel(apiKey: "test-key", modelName: "o3") + let gptModel = OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + + let settings = ModelSettings(temperature: 0.7) + + let request = ModelRequest( + messages: [Message.user(content: .text("Test"))], + settings: settings) + + // Both should handle the request (temperature filtered for o3) + for (name, model) in [("o3", o3Model), ("gpt-4.1", gptModel)] { + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error for \(name)") + } catch { + #expect(error is TachikomaError) + } + } + } + + @Test("File content rejection") + func fileContentRejection() async throws { + let model = OpenAIModel(apiKey: "test-key") + + let fileContent = FileContent( + filename: "test.txt", + content: "Test file content", + mimeType: "text/plain" + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .file(fileContent)), + ], + settings: ModelSettings(modelName: "gpt-4.1")) + + // File content should be rejected + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected error for file content") + } catch let error as TachikomaError { + switch error { + case .invalidRequest: + // Expected - file content not supported + break + default: + Issue.record("Expected invalidRequest error, got: \(error)") + } + } + } + + @Test("Audio content handling") + func audioContentHandling() async throws { + let model = OpenAIModel(apiKey: "test-key") + + let audioContent = AudioContent( + transcript: "Hello, this is a test transcript.", + duration: 5.0 + ) + + let request = ModelRequest( + messages: [ + Message.user(content: .audio(audioContent)), + ], + settings: ModelSettings(modelName: "gpt-4.1")) + + // Test audio content processing + do { + _ = try await model.getResponse(request: request) + Issue.record("Expected network error") + } catch { + #expect(error is TachikomaError) + } + } +} + +// MARK: - Provider Configuration Tests + +@Suite("OpenAI Provider Configuration Tests") +struct OpenAIProviderConfigurationTests { + @Test("Provider configuration") + func providerConfiguration() async throws { + let tachikoma = Tachikoma.shared + + // Test custom OpenAI configuration + let config = ProviderConfiguration.OpenAI( + apiKey: "test-key", + baseURL: URL(string: "https://api.openai.com/v1")! + ) + + await tachikoma.configureOpenAI(config) + } + + @Test("Model registration") + func modelRegistration() async throws { + let tachikoma = Tachikoma.shared + + // Register OpenAI models + let modelNames = [ + "o3", + "o3-mini", + "gpt-4.1", + "gpt-4o", + ] + + for modelName in modelNames { + await tachikoma.registerModel(name: modelName, factory: { + OpenAIModel(apiKey: "test-key", modelName: modelName) + }) + + do { + let model = try await tachikoma.getModel(modelName) + #expect(model is OpenAIModel) + } catch { + Issue.record("Failed to get model \(modelName): \(error)") + } + } + } + + @Test("API type selection") + func apiTypeSelection() async throws { + let tachikoma = Tachikoma.shared + + // Register models with different API preferences + await tachikoma.registerModel(name: "gpt-4.1-chat", factory: { + OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + }) + + await tachikoma.registerModel(name: "o3-responses", factory: { + OpenAIModel(apiKey: "test-key", modelName: "o3") + }) + + // Test that both can be retrieved + do { + let chatModel = try await tachikoma.getModel("gpt-4.1-chat") + #expect(chatModel is OpenAIModel) + + let responsesModel = try await tachikoma.getModel("o3-responses") + #expect(responsesModel is OpenAIModel) + } catch { + Issue.record("Failed to get models: \(error)") + } + } + + @Test("Lenient model name resolution") + func lenientModelNameResolution() async throws { + let tachikoma = Tachikoma.shared + + // Register base models + await tachikoma.registerModel(name: "gpt-4.1", factory: { + OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + }) + + await tachikoma.registerModel(name: "o3", factory: { + OpenAIModel(apiKey: "test-key", modelName: "o3") + }) + + // Test lenient name matching + let nameMapping = [ + "gpt": "gpt-4.1", + "gpt-4": "gpt-4.1", + "gpt4": "gpt-4.1", + "o3": "o3", + ] + + for (input, _) in nameMapping { + do { + let model = try await tachikoma.getModel(input) + #expect(model is OpenAIModel) + } catch { + Issue.record("Failed to resolve \(input): \(error)") + } + } + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/StreamingAndToolTests.swift b/Tests/TachikomaTests/StreamingAndToolTests.swift new file mode 100644 index 0000000..b40458d --- /dev/null +++ b/Tests/TachikomaTests/StreamingAndToolTests.swift @@ -0,0 +1,439 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("Streaming Types Tests") +struct StreamingTypesTests { + @Test("StreamEvent creation and types") + func streamEventCreationAndTypes() { + let events: [StreamEvent] = [ + .contentDelta("Hello"), + .contentComplete("Hello, world!"), + .toolCallDelta("tool_123", "get_", "{}"), + .toolCallComplete("tool_123", "get_weather", "{\"location\": \"SF\"}"), + .reasoningDelta("I need to think..."), + .reasoningComplete("I need to think about this carefully."), + .done, + .error("Network error"), + .metadata(["usage": ["tokens": 42]]), + ] + + #expect(events.count == 9) + + // Test specific event properties + if case let .contentDelta(text) = events[0] { + #expect(text == "Hello") + } else { + Issue.record("Expected contentDelta event") + } + + if case let .toolCallDelta(id, name, args) = events[2] { + #expect(id == "tool_123") + #expect(name == "get_") + #expect(args == "{}") + } else { + Issue.record("Expected toolCallDelta event") + } + + if case .done = events[6] { + // Expected + } else { + Issue.record("Expected done event") + } + } + + @Test("StreamEvent Codable") + func streamEventCodable() throws { + let originalEvents: [StreamEvent] = [ + .contentDelta("Hello"), + .toolCallComplete("tool_123", "get_weather", "{\"location\": \"SF\"}"), + .done, + .error("Network error"), + .metadata(["usage": ["tokens": 42]]), + ] + + let encoder = JSONEncoder() + let decoder = JSONDecoder() + + for originalEvent in originalEvents { + let data = try encoder.encode(originalEvent) + let decodedEvent = try decoder.decode(StreamEvent.self, from: data) + + // Compare events (simplified comparison) + switch (originalEvent, decodedEvent) { + case let (.contentDelta(orig), .contentDelta(decoded)): + #expect(orig == decoded) + case let (.toolCallComplete(origId, origName, origArgs), .toolCallComplete(decodedId, decodedName, decodedArgs)): + #expect(origId == decodedId) + #expect(origName == decodedName) + #expect(origArgs == decodedArgs) + case (.done, .done): + // Expected + break + case let (.error(orig), .error(decoded)): + #expect(orig == decoded) + case (.metadata, .metadata): + // Metadata comparison is complex, just verify it decodes + break + default: + Issue.record("Event types don't match") + } + } + } + + @Test("StreamingResponseIterator behavior") + func streamingResponseIteratorBehavior() async throws { + // Create a mock stream of events + let events: [StreamEvent] = [ + .contentDelta("Hello"), + .contentDelta(" world"), + .contentComplete("Hello world!"), + .done + ] + + // Create an AsyncStream to simulate streaming + let stream = AsyncThrowingStream { continuation in + Task { + for event in events { + continuation.yield(event) + try await Task.sleep(nanoseconds: 1_000_000) // 1ms delay + } + continuation.finish() + } + } + + // Collect events from the stream + var collectedEvents: [StreamEvent] = [] + + do { + for try await event in stream { + collectedEvents.append(event) + } + } catch { + Issue.record("Stream iteration failed: \(error)") + } + + #expect(collectedEvents.count == 4) + + // Verify event sequence + if case let .contentDelta(text1) = collectedEvents[0] { + #expect(text1 == "Hello") + } else { + Issue.record("Expected first contentDelta") + } + + if case let .contentDelta(text2) = collectedEvents[1] { + #expect(text2 == " world") + } else { + Issue.record("Expected second contentDelta") + } + + if case .done = collectedEvents[3] { + // Expected + } else { + Issue.record("Expected done event at end") + } + } + + @Test("StreamEvent error handling") + func streamEventErrorHandling() async throws { + let stream = AsyncThrowingStream { continuation in + continuation.yield(.contentDelta("Hello")) + continuation.finish(throwing: TachikomaError.networkError("Connection lost")) + } + + var collectedEvents: [StreamEvent] = [] + var caughtError: Error? + + do { + for try await event in stream { + collectedEvents.append(event) + } + } catch { + caughtError = error + } + + #expect(collectedEvents.count == 1) + #expect(caughtError is TachikomaError) + + if let tachikomaError = caughtError as? TachikomaError, + case let .networkError(message) = tachikomaError { + #expect(message == "Connection lost") + } else { + Issue.record("Expected TachikomaError.networkError") + } + } +} + +// MARK: - Tool Definition Tests + +@Suite("Tool Definition Tests") +struct ToolDefinitionTests { + @Test("Simple tool definition") + func simpleToolDefinition() { + let tool = ToolDefinition( + function: FunctionDefinition( + name: "get_time", + description: "Get the current time", + parameters: ToolParameters( + properties: [:], + required: [] + ) + ) + ) + + #expect(tool.function.name == "get_time") + #expect(tool.function.description == "Get the current time") + #expect(tool.function.parameters.properties.isEmpty) + #expect(tool.function.parameters.required.isEmpty) + } + + @Test("Tool definition with parameters") + func toolDefinitionWithParameters() { + let tool = ToolDefinition( + function: FunctionDefinition( + name: "get_weather", + description: "Get weather information", + parameters: ToolParameters( + type: "object", + properties: [ + "location": ParameterSchema( + type: .string, + description: "The location to get weather for" + ), + "units": ParameterSchema( + type: .string, + description: "Temperature units", + enumValues: ["celsius", "fahrenheit"] + ), + "include_forecast": ParameterSchema( + type: .boolean, + description: "Include forecast data" + ) + ], + required: ["location"] + ) + ) + ) + + #expect(tool.function.name == "get_weather") + #expect(tool.function.parameters.properties.count == 3) + #expect(tool.function.parameters.required == ["location"]) + + // Check parameter schemas + let locationParam = tool.function.parameters.properties["location"] + #expect(locationParam?.type == .string) + #expect(locationParam?.description == "The location to get weather for") + + let unitsParam = tool.function.parameters.properties["units"] + #expect(unitsParam?.enumValues == ["celsius", "fahrenheit"]) + } + + @Test("ParameterSchema types") + func parameterSchemaTypes() { + let schemas: [ParameterSchema] = [ + ParameterSchema(type: .string, description: "A string value"), + ParameterSchema(type: .integer, description: "An integer value", minimum: 0, maximum: 100), + ParameterSchema(type: .number, description: "A number value"), + ParameterSchema(type: .boolean, description: "A boolean value"), + ParameterSchema(type: .array, description: "An array value"), + ParameterSchema(type: .object, description: "An object value"), + ] + + #expect(schemas.count == 6) + + // Test specific properties + let intSchema = schemas[1] + #expect(intSchema.type == .integer) + #expect(intSchema.minimum == 0) + #expect(intSchema.maximum == 100) + } + + @Test("Tool parameters codable") + func toolParametersCodable() throws { + let original = ToolParameters( + type: "object", + properties: [ + "name": ParameterSchema(type: .string, description: "Name field"), + "age": ParameterSchema(type: .integer, description: "Age field", minimum: 0), + ], + required: ["name"] + ) + + let encoder = JSONEncoder() + let data = try encoder.encode(original) + + let decoder = JSONDecoder() + let decoded = try decoder.decode(ToolParameters.self, from: data) + + #expect(decoded.type == original.type) + #expect(decoded.properties.count == original.properties.count) + #expect(decoded.required == original.required) + + let nameParam = decoded.properties["name"] + #expect(nameParam?.type == .string) + #expect(nameParam?.description == "Name field") + } + + @Test("Generic Tool creation") + func genericToolCreation() async throws { + // Context type for the tool + struct WeatherContext { + let apiKey: String + } + + // Create a generic tool + let weatherTool = Tool( + name: "get_weather", + description: "Get weather for a location", + parameters: ToolParameters( + properties: [ + "location": ParameterSchema(type: .string, description: "Location name") + ], + required: ["location"] + ) + ) { input, context in + // Simulate tool execution + var location = "Unknown" + if case let .dictionary(dict) = input { + location = dict["location"] as? String ?? "Unknown" + } + return .string("Weather in \(location): 72°F and sunny (API key: \(context.apiKey.prefix(3))...)") + } + + // Convert to tool definition + let toolDef = weatherTool.toToolDefinition() + + #expect(toolDef.function.name == "get_weather") + #expect(toolDef.function.description == "Get weather for a location") + + // Test tool execution + let context = WeatherContext(apiKey: "test-api-key-123") + let input = ToolInput.dictionary(["location": "San Francisco"]) + + let output = try await weatherTool.execute(input, context) + + if case let .string(result) = output { + #expect(result.contains("San Francisco")) + #expect(result.contains("test...")) + } else { + Issue.record("Expected string output") + } + } + + @Test("ToolCallItem creation") + func toolCallItemCreation() { + let toolCall = ToolCallItem( + id: "call_abc123", + type: .function, + function: FunctionCall( + name: "calculate", + arguments: "{\"expression\": \"2 + 2\"}" + ) + ) + + #expect(toolCall.id == "call_abc123") + #expect(toolCall.type == .function) + #expect(toolCall.function.name == "calculate") + #expect(toolCall.function.arguments == "{\"expression\": \"2 + 2\"}") + } + + @Test("ToolInput and ToolOutput") + func toolInputAndOutput() { + let input = ToolInput.dictionary([ + "location": "Paris", + "units": "celsius" + ]) + + if case let .dictionary(args) = input { + #expect(args["location"] as? String == "Paris") + #expect(args["units"] as? String == "celsius") + } else { + Issue.record("Expected dictionary input") + } + + let output = ToolOutput.string("Weather in Paris: 18°C and cloudy") + + if case let .string(content) = output { + #expect(content == "Weather in Paris: 18°C and cloudy") + } else { + Issue.record("Expected string output") + } + } + + @Test("FunctionCall argument parsing") + func functionCallArgumentParsing() throws { + let functionCall = FunctionCall( + name: "get_weather", + arguments: "{\"location\": \"Tokyo\", \"units\": \"celsius\"}" + ) + + #expect(functionCall.name == "get_weather") + #expect(functionCall.arguments == "{\"location\": \"Tokyo\", \"units\": \"celsius\"}") + + // Test parsing arguments as JSON + let data = functionCall.arguments.data(using: .utf8)! + let parsed = try JSONSerialization.jsonObject(with: data) as? [String: Any] + + #expect(parsed?["location"] as? String == "Tokyo") + #expect(parsed?["units"] as? String == "celsius") + } + + @Test("ParameterSchema with complex properties") + func parameterSchemaWithComplexProperties() { + let schema = ParameterSchema( + type: .object, + description: "A complex object", + properties: [ + "nested_string": ParameterSchema(type: .string, description: "Nested string"), + "nested_array": ParameterSchema(type: .array, description: "Nested array"), + ] + ) + + #expect(schema.type == .object) + #expect(schema.properties?.count == 2) + + let nestedString = schema.properties?["nested_string"] as? ParameterSchema + #expect(nestedString?.type == .string) + #expect(nestedString?.description == "Nested string") + } + + @Test("Tool definition JSON serialization") + func toolDefinitionJSONSerialization() throws { + let tool = ToolDefinition( + function: FunctionDefinition( + name: "calculate", + description: "Perform a calculation", + parameters: ToolParameters( + type: "object", + properties: [ + "expression": ParameterSchema( + type: .string, + description: "Mathematical expression to evaluate" + ) + ], + required: ["expression"] + ) + ) + ) + + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + let jsonData = try encoder.encode(tool) + let jsonString = String(data: jsonData, encoding: .utf8)! + + #expect(jsonString.contains("calculate")) + #expect(jsonString.contains("Perform a calculation")) + #expect(jsonString.contains("expression")) + #expect(jsonString.contains("Mathematical expression")) + + // Test round-trip encoding/decoding + let decoder = JSONDecoder() + let decoded = try decoder.decode(ToolDefinition.self, from: jsonData) + + #expect(decoded.function.name == tool.function.name) + #expect(decoded.function.description == tool.function.description) + #expect(decoded.function.parameters.required == tool.function.parameters.required) + } +} \ No newline at end of file diff --git a/Tests/TachikomaTests/TachikomaCoreTests.swift b/Tests/TachikomaTests/TachikomaCoreTests.swift new file mode 100644 index 0000000..06cd39f --- /dev/null +++ b/Tests/TachikomaTests/TachikomaCoreTests.swift @@ -0,0 +1,507 @@ +import Foundation +import Testing +@testable import Tachikoma + +@Suite("Tachikoma Core Tests") +struct TachikomaCoreTests { + @Test("Tachikoma singleton initialization") + func singletonInitialization() async throws { + let tachikoma1 = Tachikoma.shared + let tachikoma2 = Tachikoma.shared + + // Should be the same instance + #expect(tachikoma1 === tachikoma2) + } + + @Test("Model registration and retrieval") + func modelRegistrationAndRetrieval() async throws { + let tachikoma = Tachikoma.shared + + // Register a test model + await tachikoma.registerModel(name: "test-model", factory: { + OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + }) + + // Should be able to retrieve it + do { + let model = try await tachikoma.getModel("test-model") + #expect(model is OpenAIModel) + } catch { + Issue.record("Failed to retrieve registered model: \(error)") + } + } + + @Test("Model not found error") + func modelNotFoundError() async throws { + let tachikoma = Tachikoma.shared + + // Should throw error for non-existent model + do { + _ = try await tachikoma.getModel("nonexistent-model") + Issue.record("Expected error for nonexistent model") + } catch let error as TachikomaError { + switch error { + case .modelNotFound: + // Expected + break + default: + Issue.record("Expected modelNotFound error, got: \(error)") + } + } + } + + @Test("Provider configuration") + func providerConfiguration() async throws { + let tachikoma = Tachikoma.shared + + // Test OpenAI configuration + let openaiConfig = ProviderConfiguration.OpenAI( + apiKey: "test-key", + baseURL: URL(string: "https://api.openai.com/v1")! + ) + await tachikoma.configureOpenAI(openaiConfig) + + // Test Anthropic configuration + let anthropicConfig = ProviderConfiguration.Anthropic( + apiKey: "test-key", + baseURL: URL(string: "https://api.anthropic.com/v1")! + ) + await tachikoma.configureAnthropic(anthropicConfig) + + // Test Grok configuration + let grokConfig = ProviderConfiguration.Grok( + apiKey: "test-key", + baseURL: URL(string: "https://api.x.ai/v1")! + ) + await tachikoma.configureGrok(grokConfig) + + // Test Ollama configuration + let ollamaConfig = ProviderConfiguration.Ollama( + baseURL: URL(string: "http://localhost:11434")! + ) + await tachikoma.configureOllama(ollamaConfig) + } + + @Test("Model factory functions") + func modelFactoryFunctions() async throws { + let tachikoma = Tachikoma.shared + + // Test that factory functions work correctly + await tachikoma.registerModel(name: "factory-openai", factory: { + OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + }) + + await tachikoma.registerModel(name: "factory-anthropic", factory: { + AnthropicModel(apiKey: "test-key", modelName: "claude-opus-4-20250514") + }) + + await tachikoma.registerModel(name: "factory-grok", factory: { + GrokModel(apiKey: "test-key", modelName: "grok-4") + }) + + await tachikoma.registerModel(name: "factory-ollama", factory: { + OllamaModel(modelName: "llama3.3") + }) + + // Test retrieval + let modelNames = ["factory-openai", "factory-anthropic", "factory-grok", "factory-ollama"] + + for modelName in modelNames { + do { + let model = try await tachikoma.getModel(modelName) + #expect(model is any ModelInterface) + } catch { + Issue.record("Failed to get \(modelName): \(error)") + } + } + } + + @Test("Concurrent model access") + func concurrentModelAccess() async throws { + let tachikoma = Tachikoma.shared + + // Register a model + await tachikoma.registerModel(name: "concurrent-test", factory: { + OpenAIModel(apiKey: "test-key", modelName: "gpt-4.1") + }) + + // Access it concurrently + await withTaskGroup(of: Void.self) { group in + for i in 0..<10 { + group.addTask { + do { + let model = try await tachikoma.getModel("concurrent-test") + #expect(model is OpenAIModel) + } catch { + Issue.record("Concurrent access failed for iteration \(i): \(error)") + } + } + } + } + } +} + +// MARK: - Message Type Tests + +@Suite("Message Type Tests") +struct MessageTypeTests { + @Test("System message creation") + func systemMessageCreation() { + let message = Message.system(content: "You are a helpful assistant.") + + if case let .system(id, content) = message { + #expect(id == nil) + #expect(content == "You are a helpful assistant.") + } else { + Issue.record("Expected system message") + } + } + + @Test("User message with text content") + func userMessageWithTextContent() { + let message = Message.user(content: .text("Hello, AI!")) + + if case let .user(id, content) = message { + #expect(id == nil) + if case let .text(text) = content { + #expect(text == "Hello, AI!") + } else { + Issue.record("Expected text content") + } + } else { + Issue.record("Expected user message") + } + } + + @Test("User message with multimodal content") + func userMessageWithMultimodalContent() { + let imageData = Data([0xFF, 0xD8, 0xFF]) + let message = Message.user(content: .multimodal([ + .text("What's in this image?"), + .imageUrl(ImageUrl(base64: imageData.base64EncodedString())) + ])) + + if case let .user(_, content) = message { + if case let .multimodal(parts) = content { + #expect(parts.count == 2) + + if case let .text(text) = parts[0] { + #expect(text == "What's in this image?") + } else { + Issue.record("Expected text part at index 0") + } + + if case .imageUrl = parts[1] { + // Expected image part + } else { + Issue.record("Expected image part at index 1") + } + } else { + Issue.record("Expected multimodal content") + } + } else { + Issue.record("Expected user message") + } + } + + @Test("Assistant message creation") + func assistantMessageCreation() { + let message = Message.assistant(content: [ + .outputText("Hello! How can I help you?") + ]) + + if case let .assistant(id, content, status) = message { + #expect(id == nil) + #expect(status == .completed) + #expect(content.count == 1) + + if case let .outputText(text) = content[0] { + #expect(text == "Hello! How can I help you?") + } else { + Issue.record("Expected output text content") + } + } else { + Issue.record("Expected assistant message") + } + } + + @Test("Tool call message") + func toolCallMessage() { + let toolCall = ToolCallItem( + id: "call_123", + type: .function, + function: FunctionCall( + name: "get_weather", + arguments: "{\"location\": \"San Francisco\"}" + ) + ) + + let message = Message.assistant(content: [.toolCall(toolCall)]) + + if case let .assistant(_, content, _) = message { + if case let .toolCall(call) = content[0] { + #expect(call.id == "call_123") + #expect(call.function.name == "get_weather") + #expect(call.function.arguments == "{\"location\": \"San Francisco\"}") + } else { + Issue.record("Expected tool call content") + } + } else { + Issue.record("Expected assistant message") + } + } + + @Test("Tool result message") + func toolResultMessage() { + let message = Message.tool( + toolCallId: "call_123", + content: "The weather in San Francisco is 72°F and sunny." + ) + + if case let .tool(id, toolCallId, content) = message { + #expect(id == nil) + #expect(toolCallId == "call_123") + #expect(content == "The weather in San Francisco is 72°F and sunny.") + } else { + Issue.record("Expected tool message") + } + } + + @Test("Reasoning message") + func reasoningMessage() { + let message = Message.reasoning(content: "Let me think about this step by step...") + + if case let .reasoning(id, content) = message { + #expect(id == nil) + #expect(content == "Let me think about this step by step...") + } else { + Issue.record("Expected reasoning message") + } + } + + @Test("Message with custom ID") + func messageWithCustomID() { + let message = Message.user(id: "custom-123", content: .text("Hello")) + + if case let .user(id, _) = message { + #expect(id == "custom-123") + } else { + Issue.record("Expected user message with custom ID") + } + } + + @Test("Message type property") + func messageTypeProperty() { + let messages: [Message] = [ + .system(content: "System"), + .user(content: .text("User")), + .assistant(content: [.outputText("Assistant")]), + .tool(toolCallId: "call", content: "Tool"), + .reasoning(content: "Reasoning") + ] + + let expectedTypes: [MessageType] = [.system, .user, .assistant, .tool, .reasoning] + + for (message, expectedType) in zip(messages, expectedTypes) { + #expect(message.type == expectedType) + } + } +} + +// MARK: - MessageContent Tests + +@Suite("MessageContent Tests") +struct MessageContentTests { + @Test("Text content") + func textContent() { + let content = MessageContent.text("Hello, world!") + + if case let .text(text) = content { + #expect(text == "Hello, world!") + } else { + Issue.record("Expected text content") + } + } + + @Test("Image content with base64") + func imageContentWithBase64() { + let imageData = Data([0xFF, 0xD8, 0xFF]) + let imageUrl = ImageUrl(base64: imageData.base64EncodedString()) + let content = MessageContent.image(imageUrl) + + if case let .image(url) = content { + #expect(url.base64 == imageData.base64EncodedString()) + #expect(url.url == nil) + } else { + Issue.record("Expected image content") + } + } + + @Test("Image content with URL") + func imageContentWithURL() { + let imageUrl = ImageUrl(url: "https://example.com/image.jpg") + let content = MessageContent.image(imageUrl) + + if case let .image(url) = content { + #expect(url.url == "https://example.com/image.jpg") + #expect(url.base64 == nil) + } else { + Issue.record("Expected image content") + } + } + + @Test("Audio content") + func audioContent() { + let audioData = AudioContent( + transcript: "Hello, this is a test.", + duration: 5.0 + ) + let content = MessageContent.audio(audioData) + + if case let .audio(audio) = content { + #expect(audio.transcript == "Hello, this is a test.") + #expect(audio.duration == 5.0) + } else { + Issue.record("Expected audio content") + } + } + + @Test("File content") + func fileContent() { + let fileData = FileContent( + filename: "test.txt", + content: "File content here", + mimeType: "text/plain" + ) + let content = MessageContent.file(fileData) + + if case let .file(file) = content { + #expect(file.filename == "test.txt") + #expect(file.content == "File content here") + #expect(file.mimeType == "text/plain") + } else { + Issue.record("Expected file content") + } + } + + @Test("Multimodal content") + func multimodalContent() { + let parts: [MessageContentPart] = [ + .text("Describe this image:"), + .imageUrl(ImageUrl(url: "https://example.com/image.jpg")), + .text("What do you see?") + ] + let content = MessageContent.multimodal(parts) + + if case let .multimodal(contentParts) = content { + #expect(contentParts.count == 3) + + #expect(contentParts[0].type == "text") + #expect(contentParts[0].text == "Describe this image:") + + #expect(contentParts[1].type == "image") + #expect(contentParts[1].imageUrl != nil) + + #expect(contentParts[2].type == "text") + #expect(contentParts[2].text == "What do you see?") + } else { + Issue.record("Expected multimodal content") + } + } +} + +// MARK: - Error Handling Tests + +@Suite("Error Handling Tests") +struct ErrorHandlingTests { + @Test("TachikomaError cases") + func tachikomaErrorCases() { + let errors: [TachikomaError] = [ + .modelNotFound("test-model"), + .invalidRequest("Invalid parameters"), + .authenticationFailed, + .apiError(message: "Rate limit exceeded", code: "429"), + .networkError(underlying: URLError(.notConnectedToInternet)), + .configurationError("Missing configuration"), + .streamingError("Stream interrupted"), + ] + + // Verify all error cases can be created + #expect(errors.count == 9) + + // Test error descriptions + for error in errors { + let description = error.localizedDescription + #expect(!description.isEmpty) + } + } + + @Test("Error equality") + func errorEquality() { + let error1 = TachikomaError.modelNotFound("test") + let error2 = TachikomaError.modelNotFound("test") + let error3 = TachikomaError.modelNotFound("different") + + #expect(error1.localizedDescription == error2.localizedDescription) + #expect(error1.localizedDescription != error3.localizedDescription) + } +} + +// MARK: - Model Settings Tests + +@Suite("Model Settings Tests") +struct ModelSettingsTests { + @Test("Default model settings") + func defaultModelSettings() { + let settings = ModelSettings(modelName: "test-model") + + #expect(settings.modelName == "test-model") + #expect(settings.temperature == nil) + #expect(settings.maxTokens == nil) + #expect(settings.topP == nil) + #expect(settings.frequencyPenalty == nil) + #expect(settings.presencePenalty == nil) + #expect(settings.stopSequences == nil) + #expect(settings.seed == nil) + #expect(settings.toolChoice == nil) + #expect(settings.parallelToolCalls == nil) + #expect(settings.additionalParameters == nil) + } + + @Test("Custom model settings") + func customModelSettings() { + var additionalParams = ModelParameters() + additionalParams.set("apiType", value: "chat") + additionalParams.set("reasoningEffort", value: "medium") + additionalParams.set("reasoning", value: ["summary": "detailed"]) + additionalParams.set("logprobs", value: true) + additionalParams.set("topLogprobs", value: 5) + + let settings = ModelSettings( + modelName: "test-model", + temperature: 0.7, + topP: 0.9, + maxTokens: 1000, + frequencyPenalty: 0.1, + presencePenalty: 0.2, + stopSequences: ["STOP"], + toolChoice: .auto, + parallelToolCalls: true, + seed: 42, + additionalParameters: additionalParams + ) + + #expect(settings.modelName == "test-model") + #expect(settings.temperature == 0.7) + #expect(settings.maxTokens == 1000) + #expect(settings.topP == 0.9) + #expect(settings.frequencyPenalty == 0.1) + #expect(settings.presencePenalty == 0.2) + #expect(settings.stopSequences == ["STOP"]) + #expect(settings.seed == 42) + #expect(settings.toolChoice == .auto) + #expect(settings.parallelToolCalls == true) + #expect(settings.additionalParameters?.get("apiType") as? String == "chat") + #expect(settings.additionalParameters?.get("reasoningEffort") as? String == "medium") + } +} \ No newline at end of file diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6681c8edb46668adbb1a9ec803569f8a61c72fe8 GIT binary patch literal 341825 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 gIOqvxwAXb0qZgO@P41+A%V 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 z zN?rR1z6q7c8alA)mFKbh#V7U53opLw&VM-P+`y+~9{|q$>H}Yyeditjuw(rj=zPQpXc#cSsG^KDWI<@|gUh5hq$~Lps?j?$Q6!@$ z01%PjP-a2Ra0?EzG({FGoYSP3HAA#)VkSFZS_#|oybK#RskZ;%V^6>J)59O#Ctt#Q zzV^J=lfL-lH)h^*d!MeO&m*!9lmMlWnhq}J(@FPR(l*g35<%X-r4+E?nR}Tb%QWAz zzIE5iN3sd$U9j+>uU%XR5tYeEFGj`kq?TvF$e*l$B;TAvm#E1ubA3CD`q^TD@)T4W*yO+<< zT`xYJSspD^>J7*x(wwdAqn@G}<;!Q61|YiQ3}~v5QmHId zla?k4C7`}fU#skYkTiBX&|X`+z5Lv|P3Ipta(m#j~}Zkdz)Eg$!^tlVsq|4Z3OZ!_qO|!81R2Svt>2y8we;?H?2rj(?rwf|fyU-0x6P6OZ6gxF zp(`PynHVL-j0EOpPIW+XRfL~cnVUq*W7olB`NDHAW8F(HtvdZ1zpngGpG>Asy(Rb< z@%Pu|-(Naz(Vah<^U@Y!w_)P|s(5l}A!LK|uUyCE9;KAD(}Te*QB4Sm5=|olN`cJD zkOA8^Rd+6(Q8{|z#7zr-@R{Ck`(E$EI_;kx`qhGm@BLuj&|5kTI9#Go)N-Vy$M2cH z2ndMC9qAPD95=?c76SwHksF2xTxDM2rH@`*B{e`<6;{&u2wI+lUoP^>E~bzyQ9C z!6`Y3a0S;Qf)w{eB`oGODOKuVdCg}}LZ!J#$4P|#L6WW%0e;w`xrV@3N=d$}&XpEK z|8ZGHYiX9PtLCHsehnCN+Brj~f9AxM`|j?yS9|^C)Hz?9-RYT?bKiJri2SWa^EWt2+?6Cl0F6av<5w#@{A<^E$4hX`N#Z_lX@{9o<_~G3TdX4);Gn+fcjV9m+Z*RUzFeH;_2Z8YUpMFbpXxW|iog0&-$VA-Ez2G$ZGYt%YNZmP zCFXg}LrD-^eGn7|(jtqrC#onS{E=2O2y?f@001bcA#phpL_pu6Qa9~eq)#I>3z;y_ zfkFsP+IWL}4^qt0SU8S~aAA}A5b;#eF3#0V2l5caS>fz*6; z0pMn27A2W6rQCJ{2w|y1C)@b)rqMfh)#_uVBf1PYEOyv$f6Gb=T25FLL{IvL0c!4Q z4|<5U5HJgLoW|ETnlE1IT<(KV$HHRjtCPOaz(|RqT$6Igy!zU++_~)DQnzlgboyl< zyLrhE|GHy)Xl3P=|#TH`pGq8o|=5-+1Hd?tFq(rE*v9cy^U_>r|5AC!F$`+dn_yjeUK+@2C2GQ3m3Utawrr)Xe7mhY+W~yj18l!iF}lsMyV8)piHAWhBjeiGKP!_fDv`| z740%)G*&HHRrkiLPtC(0eB0Zrz1r(fuivHMeBfX1y>H&_Hx2AMctWhyRWxtj9aKvw zvH}wa42qO&k@Ut){wTpMD*ZnM5e*bIq)bz%l8K1q)hf2`-l0;i*M{yrq2+S0JkOZt zX@#Odph>WACgQX-r4qsEfnrLgRAUGTOX@`fAce6ss3I_T%~M1|1IqOc(el8}%N~?$ z+e=}@xY64teem+5Zv9`A-?!Ne@0F5XhaPwA(L=anDvvec{vfw8`H@Op4ibR-X0>}Ww;HSeMSzVcfO zdi=hmPW?kj)D>U<^nm1mQD;Pnq@;Ck=NXHpUfrU&c<DT=B1 zDVyql#!Q&EYNb=#;;`X&%^)KDSqUy(2a4E59M)yzE=EzcT0co6iV+ z2D8-v;81IBi5ip)>R$d-bxBh)Ate!gFeM6DOjHgiQ}dHxmn0QTex?inW3^Q(4IL56 zb!GyHF*;AIB<^ihxHLGG+BJYCk_<6ZJQTF($K1ImC87vb7z%&7B&(2|6w4Z*3T%CP zx$a(ad)>hUdxnYUUHp|<*MF|(Pd_nw^*;F?{>*CnwZk_ZG;ZSMU3>Q7uBYY@l?L5| z&9IPZ3ScI|HJ^Z53a1fi^O165kU<3|)M^b4z56lmG{ByG;*l#p`n@M7?At4Sd)08z zVaFabZbHkh)w49O<)RYcF`X$CK|DDHjEdx210moZ4gxCuFi24ejEU$GYgLpEI6|LW z^J00!(~IZ+zMuVjk>{(fn{nXGCm#Ec=2|_v9y&DUt*t5B-u=%>6&3T)SkXvxXjMc+ zOyGjS0!j_0Q2;T`V+<^n(X?}O%-?uHM~nc$Kt8`2zVeR0IsJ-v`v$+8b>V-^xMk&{ zCB5<<2ccun{S=m~9}xZ5Lvry`8U?EALp%7hEFx4!DoB;K6%=mu6L;;z*4cO3@KaA| z-rccoRhVh2K8Zp1u(1UtRx%{eI`t4V-t&8t1?SA!nN@##Zz~4k~Xrt@% zgrZV7qR>)eZW4V6a>s%pZF>_$G`XlsBE5^zTl37ZAW`QsE4?EZ1)-u4N`wRe=2?kt z+C-*R(wEXi*ibVQh0pR-1g0T^CSCgtH1<417CiXiac6zy&U^RPU2m`U`m?S#AyQXd zKWpj(KmGncGZlK5cK``ak#j)fXn;JaY1B^KI+Pq4~Av z%t{Rs5CJn~idjoMK=_!_#3OcG634>Q6ap2KC~gbITFwp!4v`(ZS}^O02OpgJ?HL{S z<#oQDy65VVO@qcvII&;<{@Au`KBZK$geLj6iN-g{`qVi0iF*#a_^bDP=eK|M?{(XF`S}&IUw`opbQv+WmSrV@ekg zG8aHO$|@*AL;*mcF$tc;C_yDgt39_g96I!<8hdv?@!hB|$ zKH!LGjGo*~K%gcfG=(?IgeTq*1sv%oBBiL0$^aA^S|uW_`K9F;e9U2?`{Bb+dOt^N zzBlR*)06Jsy0CV~7f-q9xYN!)dfaJKUaD(-UA8ZuftJQbhH|}6QBoL+qrjT@Nf4+A zBq`J(fN2uPJIHJ)NYWB|-4=mFi3-5fy>rZraDg!35>j(tcLaeT`0LL?^$Lk`iCK6vT54}bmY(Lej-F>Ckf&Hpp52mbNQV~m1i zaRN7TaMuP`bBZR8ph@BYLPfS#6e{$5qVJDsX zpY_}#yI)=dRO$)RL<^3KfyGoe9V}g-=QhE?UoIv!rV1S%3yA2o|G`>qI0%bp-1~`3 zzcZ`bn}6=_G|!iQ{o&6qe{x0ttoNX5U6z&dYSn*y8s@;G>xB?A_zzQ463T!;6Z0H# zQU42;L%5&IQwtU+P0VO?>k-}`R0@Tmd(2FdoRDb-ntAK zO%)A@o?NHUNDyX1_0&2-#Vnzf(H(xw+|t+EC5v=N2Lh#fwyb{vohuQ;PdNGJJFgwL za9>~VpKZM<-h28rW1gDS<*Gx*o&JHpI_!`>HGO3s8ee->%9Tpgkhww@G>Ey;l(|lx z6t_54D~_{?@+|(Ik|uPkX!tx-sLtF-$*U%srPo^_A{t|oAsS#bR}jEpNSS1?0MKJt z8QmWrNh6YIm4MUXAf&`xYppf(IC2DbTPZG_{_}IMx_;67+7`0C+UvbgAGu-Klv(#q zpTB;46S^OL5@OyIHAeG1C703*{q3fBW}x}MIptAZQLmz4NIpL5Mwubmn0l_31KAV< z856@bikeWP&w6u*X_O#n3gni;0(=ODgw2T1y?MaG`jV>RA`ekvG>RxyGBT8zZ+IP> zmp&>Tvql|%?gjVtI(T^9t)D*rF24hBn(F-?VO4v*y*l8?p(FMmI?T4Op3jhFga+@2 zqaT4n66H9v2XMB!Icn#_1SgUj02(POQf-xTk6!3@_^|lkU3VXQ)wgGSc3)oO+pW9* zcFGq=oqWnGwO5|BxN~>Za)p+o$X!U8@<0(rMyJ3)%)~;Gi78M|%Jf(V0w zsxQC#hOB>TX|h}N=lwSx^r_QsDX&_x@ZWY<>(Ke2!!=fG{?o)q({#Nr%*Q_oGZXLM zVPZm2D15$5HK9f363wJk-yv>){z-Q2(IXo+X59b1>vJb8*vt3#9jz5h=d5{t#mneC z=onV&>s4Zna7R8s7!;~bEh8L9NJA8)5gQo-3mXQAhzg=nN+rgoEunS$Ivp^0#MYmG z;na5V#k){#E*!rl1emsZy4?NcQ@(r1_{oFDpEBji+Pdekap5D1mR8BKGE_4aAHaH& zAp9(!^1VI8JIxVPlMl0`;3Gr_tBnZo(L+=b^>i@;K9@~Cp1$EGU;zL?b7UwK|H<)a z5`sWqJza?8zs@8JqKJy*iah4lV*8IdSvKxywO{}8hQIyrb&uSzH|}?Pwb!3xUG>A| zXV1U;)(2jC<@H*RV^6H*)m%c183aO$DMr^JE%!xGm$|sjQi7peMHm6N)1-uyasevD z*<+-ey^92hN(jD?Aef~YK1rmmS%P>|z#!s6WZ&x3M{!L>5@r|yqYBN^;7fs!2?P?G zccbx{MVhZ$9gZ1&O!dSMUU=fe|M-Ek9-lTb-*Xq>MJ5{|88#?m9qmQwjD`&VkDGN+7@d@j40b!9!r=l?L(TXgzLW&?lY9v^# zRRhXptsgLyOBc*P_7m67JpvG7+BE%*<~f!+J+^xBlC1NfqpXsZGAU&QA|#ro)L>D7 zi2kB7s)S@A;h349k~MhdI88O?5<@BD))$^L-SKiZ{=`vF-1)h&|9juu-nUY}%auLr z|J?uRilr+$bQ(Pw<<4D0Y-v(cUn~tKAdw@&qDQYoM4bpOjLzs!5sQ9)6lj!Cu*FOH z&Q%NI;Rg@ofkzG;xc9I3FQfjTw|V;ScYSK~Yxn*0#Us!E(1%YPe&m3B>%6FJfV zT5F8V4Y689V2m^${}{{YS`#+_S&mP(?=sD5CUprWP{C1qGZ09dkphc`T= zF{A~+O?Q$*oG12qu&Ict3WESfpU@j%7SvtlpfuM_K%mYNRJhH6lcb>rEu@V}(QH7> zjHYI}7+DfASOloZLfLk{`VuzIzeg&&*WvUFKltN=C--i+<(knm_vuali>cqF8UFIG zj=%EwlPA9*>sExF8`kHcR6?+bU|{jRx>G|MG_Y`n3Sue>|DPCLtp-q7Z4Eif=yv!} zzW&OqVdJZ7AKFJ3`?hMtC$296z)e?=+jMl8<+538bErSyh`2jfduHL{2LS-nrdeC_JoW2y>z6N{e>}>a z(9o+d^E^T3^Owq(O5cGi(#fL|PfM)L2B@)FV)R<_Y9XjUNy%n-)Woh(;Q(n09 z|2yZTeRGT7nY!?sb1t9%!~-X@TmRUh`+hJ{g9H!6Ocb<{Iidaofsyp+~n)VZ?+?{?(TT-SWj#KRW%wKOCmkUhjVO?0?Yk zgGY`+kouL=kJ=H9Pk1ofjY!4=O3zTPIJL zYx^lG1WlCc$4z6*4Go<&>pBRlRxLi@vhU0v1H6g0A#A>U-Sf|&<3U4>Od>!3pT|YG zHH3x4ogyPr?vO~N^RZ0;22i3w-G8f;OW5)JDjspnh#i#!#tzxb_x0`9w6C+bLywkfwbi}M)ZIli(ba@1PHk~frF)r$NO%&QD@HUWqAJA<($E!+ zFF%K@X`P;Y)|uDe@bP0G-j|o%UVG~MSC6dS`8QKeo%Hd~95V9wahr6_Y}vTz*J_P< zEo5a_mN{*i?hVb9;>}=wuyqeR8dEBi0098Rjaj_^bpc`#DCpooblG45HYJPb0>DU%vHve3>28Ntb-T{F3yU*ojgzdUBE|)8WhWI_}7D@&f1{>xAwNBcqn1Q@9__l+;wb`nJN9|x#X6-py=GgLZS zR_RjPzGiu~ao#WWphJ4uqz_&8)tNv1Lc@~r>K_ny}l@wzuK9N+!M3YZbN^y0KV=U?51u_ePR%?=O2OlQQF<|-pIiJ4tx>@g| zz`Ea>mWr_3K|@X$bjV?{;qhB4vD)lBDT;|j5h#^WR0tKA5=x4y2bIxfXi>!|OmA}l z6tSAsA8@ek*xbbCEpL1icvJJdZNu8XH7XkV?GKB&B+{k(2Z+HW2P}XLkdzbwniD&N zXj>u@3@MLWs*Ss3$1AI(-@yHA2aY~r@cq{w+WNj54Nsfamh98K@s&9%=R98OF=8^J zc=VDc*KN}vBpKmuB<}4Kfl$oK7(~=GabMtu-7FEoLI!JYWYhEW^n_E-Sbx*!CSJ1- zueiN_Urn2)KfUVc7v}!pGkwnb?B~8SaKB!)WpAVM>_yY3dIxxQ3aJu3BB;Y4o-&lC?$YORbNFl>9GsH zo+$oXLvXvh7`1il)>6N5r?F8R@WgGu`p}vGeBb+Wbk|<(wKwYG>lc0g@teN!^*3JG zA>Afi;;Ex_p>T*k4K`;=kxCa5OKO@KP-L1xfJoN?`uUdv&~530&nY9RiLwbARg_c# zj9KC0%#=i$ymbs>^y$3OfUVD0`8?PDl@aV~>%_~j*aJIL zG}(0*KB~@`7J-=1B6=TnyuI}F3wZg3HSOHJ0i-26X9VuROnIFp!$(sb8Pl z{nh85?$1hhl(Gsfk4f7LxW}AN0{m5vfKoylPB9`9=6oB9q=X1sjF#`(!d{g;9C7qf z=iGkv$T$Ab#lH7T_>pfv@#Pu6`qkkbkDQ3CQx9mh%HXl&Z9a>jY6f4k)D-TC;>_3N ztKo=45mQGhq9If&+`Q;+9693HTIWLszW>nqzl3^|5az~f#(rw(>F4!1`{D~}V+ zxohb@xWnz$UhmnOHjR4D*M9xo$G&&n*VpZ?pu^}Xq{fUfragd#-GO*6pNbfiBuP-6 zHsxugP_Yzr0&&-87KRByMGMk2lJPRZi)lkK>~!9#-WmE zUj0ob~W5&|qTId6DxA#FTp*9I1HG)?X=NC-7)IgpXnW|TU2l={O)V8!g2 zy*r2M&-cx>zB6^>C&tY^>Vy;TtZjS4;_fD41eifQsl-xp9nm)D0g!Ak=!UVD2F9Ll zCzew}hWeg;_}t26<%_<*;4lS%x{95&?xh#9N{>E_Vu30OOJ`wGbH$RMT%#I&N@YGN z3KE#6PLv1>9#Pb?dz*CVP=|w$Jbc-G|NE>*_Thbe+x4OA7Z1Asw%fkSF8$H%h#_W; z+oOauL5KU#NrX@h3N7$pFO=O#tmjwKy~ zT_$M$rWueJ(&^Rpv_cqyDJVv58_7Z~Et*k^L$1pZ+_b};J1FW& z(YkB53>bHkqU&Kc>yA5)nf&*+K0oE(AMCI%?{Ryz*L$UA=eIxc=nX%-rm^e6>@j9a zVj<@c(GU_5Bl_{y*LN)%Rwr0EoiS;|X;c$)-?@+=LlBxaaRCL!WH^TcbfH>d*#z&6 z#f|R7l5<%Ef^Yji>N1!Lv!uNdngq!~3CJ2co9*1f4UgSi?#gDJaPGx3k3Md4pPzqr z;`jF9ZEmkWqNaagT=U=&<0l+^$N|`}bgq;_Fi{~y5!kfli1S6=? zsVjt01)}QvqbCQaC=TZah&ov9*$N~WrP8A}TbuHX&5a*a0LWXKPH$-i#|N_utWNC*U|^I`T1v}DXdDepfie5 z$w+UT#XOx*+_{-P!Pt{a=?U-d>g-JDB+YeN2iv4ZKyYzVq(P%&&M9R|DMXYcUUc0A znSAU_{XdbUh5!L#%dYL{dc;vu9x$YJ)~z=lynDl@mp}G{`7Zsyvf}0JN29i=N03I^yZepegJ`px; zd+UwKXfCPCp>DMysS~FZ!{Fp;nX(5dbX&@_5vd>~$_L=tEJ7nyEe~~+EZFA7Pw0+S zkIONWPHGx;&eUTc{p!UhPXEH2ft&Vy=)GJwUUS^?5#vw(a-CLX({rm3vI>&m%6UN| zf+%e%>`vF9(0wIT;n|guL~;Q02PGPM4jpZ;ZQhD!7cIQ;%5N{8So}<+`E#?bk0L0s*+ELvli6G>8ff zFftMl(ZIY0t?MAAa)nzrZ5}58aLSi|y5_NaA3nI>iRZ-Drj~LD8AXGm*D;Eb;vUA{ z@HvsPc#So2IB6tQ%~f+wlow>E7J zVv!n>DZ849Nr04U0%0xIiAW8?l9@RurxbyRCGB!P-+p8c}SkUZk?z|Esw$x#`J4xNH6B%k&u;8-L;+DmOjYI=bpd)p?|rg$KHPM+UuRI z%f9#I;EgXo|HP9|Jk(=HYdLm2d>AVYor8oDz}#tO0EAH}MV77!WAJS;&{GOvNO~X` z41x?ImefZqV!r_cVgNL{(VdA-sma<;h2M@R$?wamSAxcl|ZTE#7x`w!PYG@7E`;U)<%vM<059-O8sr_8LAR4?X&Z zmfbZ}vR33!#+p$xsCeRuMWkFmF-gHBQ79qOL9U`7^mIz+{&t9)kkFgM<}O zgHYYGSJE1SB7PY(GgT@~X_|!l?%WX#2!ddmdFEX6wk^{5++6I}rx%W&GI>Glzw`KZ z7mfE|DPgZ+>c7w*Rq5J38%ZYV0V+IuR??VvGn`9&4>)v|L3p ziw18r%@hQ*Wa=@urYMB5A_@!s)~HEVQp=+S0b+E%QgIHXI+qbZtB7jM^GqZ_#Y{DT zq6lRz!}45ewa}W!5JEXf78HmL)z(qp!A!hfRUogSY5Qi{jt#JCtB9MVAP8upq5|iCmCE#lQ6(+H8Sq5#@fPxA z=_P4-5zvrjN=7%lmeAIo0s=D=3Blw2s8AI^qAx>8R}x%#ss`5b+#|*%E3U{Sh*B~H z0qT18V!1eO4pn|-4%|Ib-h&Y$=APhWZJY2+b8zhh5mkgA5n!QgLv#cM3D)Z4$J9LbP+!iVS~l<>AEE+gAhgM?BiZZ z`l7VG3N*z}6>+@o6|Ln4TGLK!Sv3z`>#B14MVDXq^Us|8h2Ok#7Q1wNwb!3jUGnX@ zr!AYe=#HmXtSWUs^4L({vp;Lq=3?K4`oWg2^UWbl{uu#4EKEp)OIuuj1h)vY^snMu zQnI!K3KOlHXtfaV7` z5zz#BXHZF5q+d?$BSbM^6~PQl?Ft zrVp&|-newmvU1N8&au4OlsWTC6Vby20u!T?v{L$6!LCe47Bb}q`{xjUlk*xp=aZ0V zh>3UQf6Q;LsTMA_#pg_aAE`o@Re;4b3T=n#G{}n1<#dO6s!$Ngwp${>A?h}=4z4(Gp{pfQiedpbO&Dv{k))oK0yu-#VPyh7Q74yz|dezgF&V!G) z$^k=SmQ_mD(gan+yk+>(9XiJ*+D1s!&i%K?ezbh%%#W;F`*N-8u!&izV;5^}X$+F0j@&iYlAzbplpF;!QqbA0YQE7xP1HOz zGm-R7zyN06&jk@gniGkgU@U?XQdb3`KrBKuz&uY|DS>ol03s;r26FTuUBaXUm6*0h z4?IW?kqDqL({zcVQcwd5fXTdRb*MR++W37E0}{hgq9}b>3{xpr^38K@597}|XN`cA zG1q*fW$B{jS+8;DTCKS$leP>Rg~i-8`>tGY6br?q$z#)uc(V<)B)eNtBQ=zY&x&+a z0FqIFm`ja)SC4Kx>Ek7ci78DO6e1CfDQGRNPs!uf2VS7V>|`?j@ganN{~#qTJ;o$6 z1=YQFU77`Fj;VmrO+>`WFGj?`SZJ=&UoQ%8SQ)?>ca=z&T>eNAd^-XK9y;@(m`RT(}E?@T0ocrz^*cv*b>*3>3?m2*x?`o~J zR)dC^!I^3Xp_O6w{`1r`%2j>1SX%Mh}%o1rZWp0K_~pF!7)zeF_s1F{djs{fZi5P6=6-iPUOM zJ9Nu)YqD+~Q5!mb%+uX_?l*b*wG%e&;~U;y?`Zk+d&Pf0e)k>U`RB94K@-{G;G;xp zO~@?=By%=^c(X>h#aiO;CI3_cfNAQZp^_qNg$hp|pnBLPsgR^=ck?HLPyr$ciOD`$ zsA`fX?nwKLwlP~1^-u&s17gzTq@Q0fNC0QRCNp4~GAI*J_q*RPqgv{Un=o(PgU@B2<-VwVF6@ z+?f1j3x{{VG?*jtP-z3DAtJ7PEAr6bzm*6O&w!wjmLswzX1T;zQ;WIQVlL6lxBLhp zsOEfWiE8f5<<^%V@<3XlZwk)(HlgLnX=03GgwRLOk}M1*(u5`k<}gMjMWg>f5tu~t zbU3pV`I7t*U6?@uOcf5*xiNy!J+YhUqA&7a^wy#-e z2M--x4L$ph+IuhATdntiaHJ3gybpi&*DpSD&BOn4$$^*t&EH=<xO7$^!aM=%;j=NGSG4fh&eQPE?`koa)=E1elnT@F>yr+!elAx+kQ;;};}~h64;w69N1l~0pYdS#6%R~*@uF|d``9~jf7`3Q z_G$Speew4ejI1tQzG?pLw;bBq>oDswe4KWytIy1;NGS#iAgU(c8V@3qKzi2^6VcSs z9~Qf&kF$w3o`uh^{TM4$A>lr}9v~&7FMO?j}~zT=O7MH-{Hb5^zgr2qepb z#F{m|@k+UQxVZq7456VbMz8dOr_ue|EL#|3pOkAPS)bbkZO2L26gwI~TVa`L3_Ut|3LV?x1 z9WNeVixIHkh_Sj^fWa9ON&Zh*5EV0n`v{5}U`WBikwh|_bn_h~nsn;BBgr6Id}~Qn zEU8EE^vQxq;o&);fkD#p6a@$@D4l+=XB(>|Fp43#Z@l*fxfz41I^74xf;VrFAij(h zq78=>EODB}|7Nw+*c+)TZCrCdmHG1rI4n*v2`Y$H#8BbZCl^TH-es9E<*ajm`Pt*| zdbeM#_ImHsl|Nn3W6LWq|8)LCGbZh*btrW>;7F_MQXiPe%SxfS*eLxIpzabYF^z_O z@hj##0s-=9$f)jg#Yl*%iFhKsra2g+8N`#vg8Gvj#nf5WK1WONw|DNAnEgcbiC0uD zRnTe@cP@G$bg3tXo_5|NgZArl#`mutS^Ixpe0#Omd$6WXTU~CdfAzMlfMPD(H%koi=y5EB$oO5#{}r(4hwVRZP!3J5Kk$vG~QcpC|Vl7yrs zk+ACr9XaxUH^L(x#E8VnGzlIKD1?y)5fu?(Y-!e7Z~n~q zb-#VxZx)@k*ZaFpnl>YQeA>kP((kP3w)D~4*1z=3Goi}~muRq>MCPPX#8Fk}$tR7V zW7aIXxxxej{tpF<*@azDjUa-z;Ua}@oyFiLX<*$(St9~MYR?S@b%i=pOt`-{dwxE| zR3!kgn0`Hxj-A09L?l8aIPV|{%aUt@8JRq%P(bMrwB?LNkb&@QK}#{f0w-mtgW-Xa z0Qv%arU3%DBe4oegi?vjWB$ftH^k?S?08IJvjmu`-efQArW6q(5pfoW@v!G=3 z4c)!15f4~(ZHaNf-?X> zRAE5!s!!v*wgr@lK0=5{P)ckyggWke<%NJP&&e4VUHr{kzIe*Dd+qId+x7c}VP{{z zxS>-^-alsA)yOO;x(iKnF|!&OLWUY!TLQC8l(bwmh=mvcGGvz5w2r#Phzz9=a!OQG zH47PXWDuBUY>mb&GeHhYRA>bO$RkaRb)^cMP%A|$GJ$ENsS5I%iKwUrn3+W}sFKJ+ z0FXPgJ|)NMF!@13$jdimn4L!l9vF~ z09X_OW6a5Vt?k-S7dlqgZd`jro>gA#*t=_~u6ftjH?7?|e)Y67N zXixMu6ujvON=B5*ao5U4s=Hs;LytJP>F{w=Cg1ewm8}{zcpvz>&q7} zdS=n$UacMavFqT`B4vYCt5DIL8iG4HC5=1_`e7kGPuvPYp_p=RVs!D9iU|lafBO=_ z5MmktM2bOFg;t<7KC~7{FyA~PtWBQo$^^%>g3%qLBv?!$L*!FB*=}bFjlK#7DO9SF26ec6C-w}mgfCcj@-8ec%A>+JcJ8QdTk=5Zvd?|tdI5lQ|Ic05J#zQ$ zpRGIecq?`878r9=O+#KpCo#By(vlN5;KXPJW7^E&u60)MMa0CQ$O>7L|Jh=;Qaq(? zvLmG72ILic-c>;wnMgf~ z`i*_AoAc29pIN(h1Ih=EWJBKp5^9aUYrArnF}lh{)P;bw;?b5p7GF+Sb}|O65V(7z z?F>hxKk1A}H#RT^s6-~?ET)l?xKx3fHY7nNMuYRGk*2X}GL(L@0YFW_psGaPD%Fjz zu>=dXY76pObtj-!ZOtplTZ0N?7J_D$t5Jp4n3Vh?g4{qA8vTE$Q9}S@OrDns!vX=C zB~fe4l>K@C)kOl(t=f^kX#dv@#9Y^D0xt6#6?{f{{G zf%*=8uKUGj#;x4z59M2~Hv?P#&nN!<-OHEU^|PUJ_(U{x>rGfq;MzT);0+bfuR6Bt z3R4G(6JJBUv+}`-6k%_!Wp$V_o#p5>RmWQsn3Q)%dk20r$sCHdRFf>0~SVJ{SRK3YZf)SU|vl?~>AXI5xtd z$kcIp=DCzQbdEb-e%^HBQ>8OM{P9x-06zGy_n$iN(T5(`k@c>19e8-i^A=T6N5(CR z6+{?7R8*`D1NMq$ex(TqE4U?+77)-0%!yM0N&d8{z1E^1MT9V<14x=0NMc7gHdl1< zg%Otm5)m0p#MF2FkPtQ&Pl_OS&qGOHDqKqGk?#IICKTP%p$dSmjUnQ9Rojq z6)QV$*{~IobwJcIJNF+53F>4Yqlv0I+@dK7ww7FQ61P{yeN5f1iCHNlV0k)00SpSs zja3ODqnKI%;EG@P^CU5M{t#d$5(Gv;W>mnXyfhjzff!VU8fp;HtT>vt!01(3U4B}-Pb92tnntQKpgp%g=Xr%Z$) zlqyz>(41DSlq(2bdz6p}dx>$RNnim{jcBtR^8X`krw?EM3)^$1hF6tf1fU8Zd_{o; zszgNiga$Q%2Ebf(q-ljpQ!alO(z@tFWOL1ge?h7$BBlaV>Md(+MB{7E#qBSxkYOW6 zwhTFT^kp~x^~rbd?I*Oo+Ut+0Yi?S3(!z%yyXWco^E;Y$%Day~L8ZR4SZkx>kq%y| zLX9+NQxp<3P15+bWYNDgQ<#!xaf8i33+ zWaW%tbWw3E*oGPjUrK}lL87LnSbSJ(X0DjmAc2DDhq{DiyuXZK^L1+YmisZ;t zRMh;=!s8%m$h5`sUAwJ$_bwI7L)Weyb;!_T)^#7a|FyS%{*>GI{uBCk%A4ntuA9-d z@!4meUp9Y!_s&O+mr`A4Qldu%xEMwYc`AZa2j2XpVGjmbke~Pb0+>{P+Qu4A=SqL( zm6AoIsi-$yF>Tv>%!2)IcOpjy(2Pz@D%eR#xR9ffQPOWtUarN}vSN0wvh|y(>y2sV3=m^ta?EGD485mT=A|p$s4z^*%f^gV?&N6uP@SXxV?`!FT zA2hxE(z-bCtSiFq&D%}N<;?P2MBP^hMj#|myGQ{M6w!nJ(zr!OI^y}gPbj&j*t4|9 z`>Vn>NYHNx)fXd?f;3@J1T#8}CVTYA0Mez3l4b=LwD1A+voYbKXNv;O+E6qx_$e&) zzNt|s;iu#191lN0BmEhsZz4H4tG4;kCDmhLeXk}oaWg_i&4{LDbI`GqVd4dsTs-|V zh`{Hw}S9fcS0S)^NLWiEcfGkLCtwBPdsY*yg zM}Sd?khlW=eA91uL{0=h-g*>>Kch(JoVJsV5pGHYK^C1-fpKULWqzBr8C(!M?&3i}j$KA-HGqp&n1TgsFlIc-OYGx^Z zO5&5bizH!^c~-Ab9m{b^FOg8~;OAt&8&a?!392XXs8FH+VhU9!C^_Drc4V5V_-{@( zP$a_V`r*erL4!+t1iL^dPs_Uv#Bs=?^?>pSXT;&!?Yx=JCZd?mwKB&Y}Ck zM?gCCMr00ZNZm2a7$pHgNGn9*DyBXc5S0R8^OwZ!Ny%g!mGm<#{)&Wide>2KM5S2Y ziXTJ3E$s^Op{2>I&vvzqZfNuuIjvf;4M%lYiiD^ic+DV!2u6ed{Dfiod+wN^s?Svg z=K4pP|E6ShnFivHJZWNS`&(;-sH#C3(h}iHNvf#3+rkIfDXt?4?FH~jR>Hf{cDFTB zsEF8Ou#2MQfN5gxdeh)HBhA|+B`Ct8F;j5|A~A%l#3HW|^vSsMTYZf#srMIPWWIYh zTDEMK+Lo8lrBely&pGRLD|h(tEuWwK$h-ddzQc9Jx91)Cz&&?9wPR=1dJh|?u~svv zs)MWMi+D_ZniP>eY}3*J{@(+IIVlLqwt__mCjK?G5sQlc%Mu6HyFCrbAdY)jPsMx_ zkhbqKq00V2w9N&Lz7^$akfaG}^8ikS5$`l0;!lHjXX!6WJP|2=mlhoB!~zD8JgdD& zaNNz$asCFXz-TpO<+|9syG6G@elyOy>WcZde&v!AiY6Wx{qvpkZu!nP#`KzWVYRMf z&r)+s6M+CtQn^HKaP^)-lU;M-Ciql1?S%ej8oh@h#G6*XmlR34p{dPaI*w=|7K|dE zT$*00Xp|sg-kj1W0B!(Rup|T)88wJHNKJy8`IqEga^?)qwxtR}QdHC;*!Ia2+uc`) z;4D;t<~JRZ&~rDYQ@ZD-BxC_(KwvaKvqxwsS8T_cr$sidlqr{A_0Sz(KK<0a^5(ol zb=h}jk6HA0G4 zcD^en4pQhtMZ_ScggRN!4=*A}>%AgUpc4IrCKBCWhNQY13er}R#vkh5Iix8PZQn_~ z6hO@BKtu#2sAC#rKq56HCq$nVI%Nxff-f8(L$oX}621J9f73#(fu2B;w4{mEOt;KH zPyx(xh$O=c1@q$Yn2w|L-qqm8Kjuty{1jDb7M(ps5J3cHR28u(lw#$R;v`<&X`ncT zru7Gf{5pw`3K^l%s39QIJNLFf?DAw&8jK;+kkuPN+xpVej4v$=gN6;$krT$>zH4{o zs=NQ!_+5Md>20sS*m_d~{fX-rm)_X2^+yXIe(;iwufLLYIdW9qvHu`Mn#Kqvb1Q;K zm{_5~#ON7kz6!WIfuF34h_(Wr(*hs!A(wZ$h46@Ad{gj(Ps-H8c!Mu@#Y{KpsS2>! zkMdHWO=)p6ngnmq+W-dwY3T`o8DlXJMrxQ-JLAYl zLXeS+8Yq@d=K%^+lVF-)LL{;Z6|Q?mav~`ju9UjV2p|YEqvUAwCV+F)Qe&Zzw$w$? zeF%I*_p!YRC75`?Mqq@BN^}B=2qA$YQ7u`;ih=Y70itS29VTuP;q5+xOQ>S4X05Nk zB28{_y|G7qF*u` zIzsf$*)iN4S$!wnv}{&hzx&lNIC`_*4hoZt4`Uh>VkM?8M>^-piw74rQjUy!x# z+yX-=j=qGXv_y!AgMjXI;V#x7mpLl`&EotwBm#b6m`Fq-=~vwWJ3Ry@fLcOeqz%Pzk(wm31cLyi3E?kO zNGqgcBMB9Xk(9RY4*vdll7N9JzB(=ONzy~#*wRy23_2w75xwO!->{O_!5v{F$!?Q^ zmkEUq8ZD;GN(YzBbAgJ}fI9Z=h)a5pi|L0S6F}F@QjCH?qlu;nz{K=&NA@HSNDLxL zm=cmWh!WDx710EF6_0fKz{x=)&82)M%OnJlJ+3$KrABEm*I6W9h)~p#CPT7)PE%$7 z2*qh0(&lvz$&gj**s^N+x3TU@y0G|$vI_)x{S-$F6ecB)L9T6)z&f(8<#Do|Ajtg_qn%tR#>qpG4?ueL1YP5Nb7{9xmeOE65@+5l|pl{FCp80cO-qdJ;Ds9dlg`wZ>&*(Ube)lKWpuC@13F< z^@aojN%SH>qBjA?HnwpA%f>dgaa@RF8@D9R%P%h;f0LJ&@bc|ce6;4;QIx60>QwD(l+4Vz)mFwIhC?6RB{<&1zDwa``KCvcD-s~yiY;G z4Mz=c(_ku4n8vg0r*IsVaU`o+)i?V&?gCw}J1K-D(Grki?dAV&$DMK3hO0h(-c-N- zSl{`;vG*N!`Wx?0{ZC-igZGwJ?}FyiKQk>MentviTqiCzxu`?Ha>K3H*7iGK zZ|rsO!AHMv3+Jlz+p*66%B{Oyy7+51u3x_f-E)sYt)nY(xF2GyIK<)0Z;nN@yK}pO02E+A<$eF$Z!(s74bT?*|ZvIS8>363WOV%-dEW zA&9i0ivQ%ViF)A-h)9B%k2qs`Q-KjxfH+6033UisL{vP-NGXCA%i(^c&MwY4?p4y7 zv@h?wdsX)jzxeqtz2u``dGf9Qe)B;)>7m}W{s*sjfAzLwU-hS7UiH0y{p5G!B7 z_1c$aAd$StqqzvbfFV4hQg-ZHB~z*>+c;TVV<-M9S9*JigUZQdNU~#-bzMme$Q6_t zAemW2T-QK~s0o}oX1?e7ZX-_4E+KTJ zi6uhkyr_!6G?6I0(uN3Y3MmH%A+=gD>B!S%pe_0OpDcdQS)aV}uSe^dex_^sp8Fqm z$YF=HHr;!Zv`Z^ZYXGxLzV?UD7jU5)9}ChG3{AMp0-vyGQA_^qG0dh#7h zE?@cZ^4n@tPC5sfCc-RKm52g#8VVvT(ajXx(~t3AtBGVU5WMl$z|8?4=D0}+oy8(b zT88&RUdI`-Bo<ONX}(~Ca6UONA-YaM7ysZ3F{nORH#R= zCrm)s&SoF!glD@DVq#$n5xk?Q_Cnwp(*YB$oLsys4kCw$FlTx$b*8s9PzGJdCzx7- zC`MGO1%S$%Fe-Wx|AvtRz-2{8693wbo*^HaSKTquL}ujsLA{bhc2Bv;F_M6LTwD=- zbq=3a!pEc(F)^yCRK%(L6|W+~P)0F)lN@z&KF&7@v#)433Rk z78eHr6|=f9A`8tOQUZ&p208EpEaoomdKB#wAymnh39U)3-hna?+R$SwQ@Lh2CXeaH zfyW(pfA1c1&i&qdkGioMKJQw))^oAm{>5A7tX;AEircTgbk^F9L+F@0Kljd>&03>7 zS!-CT(TkWfWL^K@5NKF50s^Wzt3NA{GRQ)NIU{sCPRZ~RBTy6!tOJ-72 zY>5G2i$i37*{qOUuK@{B*a%n9%RPZgMGu3}cSM7_l-fsEnM4wGG|uO0KFkd&g9CKP zpm`JT)l=-eA#*gh{fYS!Mo?+M^+4fG3xkMy-ht;kC_GU-WvA3p;KD}9!pLEWJ!8c1 zvx>2+HgxCDKql;Ky^2Ss_qUm;|L&vwv{D%OfMwsRJdo*wh4S zLaK?+1RZH`7$%c6g%vQ_vg~J=y~j)(_>wbT^PlfO;U_!op?(h5qEB6W+O7Y7{R~fB5$up_NtX9Kk6;D6KYC z12n;`6pNPCKA=VmQ`!r(%SH(ZNBkRJ>i}2y8o(=zk0vt_tkiA*DLz|^XkjL zcfkq03tm;G&fY@BT)s!G`c1k}gNdpz3l4<)IC+&t!XZf@$KcLq5a!O?LDq5D0M!tt zqF8{SQQccd13(S;BgC((oVG7w%a{zCwDnn*z9F$wa0 zK1oT_B2JD{DrcUN(i$*Pruaw#yS5#6GlU_DvQno6dph~ETiGiE5HaT1jOd~W|3E4k zW5v@0;k$_sY{i_M7VipazDXn)v*QHSdB=q89+EyJ_4ZO!ge9V%1uQYpWfcoSp>x!I znq0lm@KhoPzcW=tN`UDpP9Q!cHWSwBDxwK_Xo#&RSD@VZxb%+e#h&wycyRI_`yEqC%Y9&h~;PYJy z)wz5z3Gi(&_-n!>6!R~J6d?_o5<T_^QZ^;2N(q`UtB>X# zpCX)m;Rq;#MDA2saaNMieVw3Q00d`t$7vl*C#jk_#=-0GzXVJKmlXsrNYn%tDSUCr zBV>Y=2+;qCyG|R$M?R6?l4()^!V$!$B5hlIul{O405yZ{@VpSmqP%5O1~t3UDb4L$ z%l@mrRUGk>mkrlu9zK3)-^s((XZjn(GeCL$-~Ra0%l_l@r!{BJFQ@K*c+qOLG8`Xv zD(8+(Lq}LDer!hiAROzjOsw5eF6J;=RiyqS#3#%!r>9q=AQcERQPHl=;)wTJm{mb# zaR=&MV(21*uHyYX-~}I(ilb?gGZ}@HBnoqItxz@h5JsaOcZzB}mBsZ5K$1)#CGWjR zL(!4g9?lzX_zCx(zaZcL&5v}xIM6jOUf=YuHywD{cfNS{VCU|6^1g@3aC5s9T1bXK zT}B%;A1C)JggI;;#QkWzrE>OeFe{uz>suk7lH$Xg!&YL%$= zB7>+TG0R|4WYv_|9Lj@_KPa~D0qyP`kGcELd0_T}|O{D8@oIFfFqP*S%0&4Iv9xH`2 z2u%VbG7N<* z(3<$%5y`Eg9UyR|+=GZiacu#Q7x0;Q4iTWZJ`b8fss*1+yq<9!)GLdpB(BEZ`4rD9 zFTBM-7A8*gK=7-jnyAHD?p*>AILMBaYO%nay`^Gx=tEPHt=$GvLTmjh+LL$5$&1dp z><54L(pT)fC;i#4MHgO}R$sei^G#P?)iZAXtFm^~z+5^2N@!(?;hbb4awH>2%xPiq zoFjak{28Ekgd;Na7$P`S2C5>ea9HHCgdYPDqENt3GyxJSHm?bw;oUsK90}yepK^2C z)VQPwBoYZbX)~Yp`ti>c9WF((3c@A=6}=JFd>0aZXqJvHX|8@mHr;i#9DKx)Ht)n^ zXI^mLiL0ObnJRu`2`5b6?UhHr?(7F^n;wyJ-Gi)mbgF1g1DtcjmGNHC_YKmqy$Evf zlDVoIqe};?GdM$}ia_)B)WlgQ!Q%279sVLScmh;GWwhxm7T;$87x4N3KEO6fA|t9T z2?~J;9aKoHti8NgF^cD?1T)#+TNQ)G9Nj160%xVCDBg_OOC_edByH!7H~&H=?>V

KrHYVP|Z9Ar@#B*VjM0y$PS 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%0ANSBko?EEI-7lK==2Rf;9{Zre`n-yIge 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%0D156S8M_-`| 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