style: run swiftformat

This commit is contained in:
Peter Steinberger 2026-03-13 20:19:11 +00:00
parent 8465b4e598
commit 46a5405d40
No known key found for this signature in database
104 changed files with 1772 additions and 1566 deletions

View File

@ -301,8 +301,7 @@ struct AgentCLI: AsyncParsableCommand {
private func loadMessages(from path: String) async throws -> [ModelMessage] {
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url)
let messages = try JSONDecoder().decode([ModelMessage].self, from: data)
return messages
return try JSONDecoder().decode([ModelMessage].self, from: data)
}
private func saveMessages(_ messages: [ModelMessage], to path: String) async throws {
@ -546,7 +545,7 @@ enum CLIError: LocalizedError {
}
}
// Extension to add provider helpers
/// Extension to add provider helpers
extension Provider {
static var allStandard: [Provider] {
[.openai, .anthropic, .google, .mistral, .groq, .grok, .ollama]

View File

@ -4,14 +4,14 @@ import Foundation
// swift-sh Tachikoma ~> 1.0.0
/// This demo script shows how to use Tachikoma in a real application
/// Run with: swift-sh DemoScript.swift
/// Or: swift run DemoScript (if added to Package.swift)
// This demo script shows how to use Tachikoma in a real application
// Run with: swift-sh DemoScript.swift
// Or: swift run DemoScript (if added to Package.swift)
print("🕷️ Tachikoma Demo Script")
print("=" * 40)
// Check environment variables directly
/// Check environment variables directly
let env = ProcessInfo.processInfo.environment
print("Environment check:")
for key in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "X_AI_API_KEY"] {

View File

@ -106,7 +106,7 @@ func testRealtimeConfiguration() async throws {
print(" swift run RealtimeVoiceAssistant --basic")
}
// Extension for string multiplication
/// Extension for string multiplication
extension String {
static func * (string: String, count: Int) -> String {
String(repeating: string, count: count)

View File

@ -485,7 +485,6 @@ extension ProviderFactory {
-> any ModelProvider
{
// Create a provider with configuration
let provider = try createProvider(for: model, configuration: configuration)
return provider
try createProvider(for: model, configuration: configuration)
}
}

View File

@ -17,7 +17,7 @@ public final class CustomProviderRegistry: @unchecked Sendable {
private init() {}
// Load from ~/.<profile>/config.json customProviders
/// Load from ~/.<profile>/config.json customProviders
public func loadFromProfile() {
guard let json = Self.loadRawConfigJSON() else { return }
guard let cp = json["customProviders"] as? [String: Any] else { return }
@ -60,7 +60,9 @@ public final class CustomProviderRegistry: @unchecked Sendable {
return "\(home)/\(TachikomaConfiguration.profileDirectoryName)"
}
private static func profileConfigPath() -> String { "\(self.profileDirectoryPath())/config.json" }
private static func profileConfigPath() -> String {
"\(self.profileDirectoryPath())/config.json"
}
private static func stripJSONComments(from json: String) -> String {
var result = ""

View File

@ -220,7 +220,7 @@ protocol EmbeddingProvider: Sendable {
/// Request for embedding generation
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct EmbeddingRequest: Sendable {
struct EmbeddingRequest {
let input: EmbeddingInput
let settings: EmbeddingSettings
}

View File

@ -172,7 +172,7 @@ extension Provider {
let key = Self.processEnvironmentValue(for: self.environmentVariable),
!key.isEmpty
{
return key
return key
}
// Check alternative environment variables

View File

@ -74,7 +74,7 @@ public struct ProviderConfiguration: Sendable {
self.customHeaders = customHeaders
}
// Common configurations for providers
/// Common configurations for providers
public static let openAI = ProviderConfiguration(
maxTokens: 4096,
maxContextLength: 128_000,
@ -111,10 +111,21 @@ public final class ProviderAdapter: EnhancedModelProvider {
private let baseProvider: ModelProvider
public let configuration: ProviderConfiguration
public var modelId: String { self.baseProvider.modelId }
public var baseURL: String? { self.baseProvider.baseURL }
public var apiKey: String? { self.baseProvider.apiKey }
public var capabilities: ModelCapabilities { self.baseProvider.capabilities }
public var modelId: String {
self.baseProvider.modelId
}
public var baseURL: String? {
self.baseProvider.baseURL
}
public var apiKey: String? {
self.baseProvider.apiKey
}
public var capabilities: ModelCapabilities {
self.baseProvider.capabilities
}
public init(provider: ModelProvider, configuration: ProviderConfiguration) {
self.baseProvider = provider

View File

@ -27,9 +27,13 @@ public enum AgentValueType: String, Sendable, Codable, CaseIterable {
// MARK: - Built-in Type Conformances
extension String: AgentToolValue {
public static var agentValueType: AgentValueType { .string }
public static var agentValueType: AgentValueType {
.string
}
public func toJSON() throws -> Any { self }
public func toJSON() throws -> Any {
self
}
public static func fromJSON(_ json: Any) throws -> Self {
guard let string = json as? String else {
@ -40,9 +44,13 @@ extension String: AgentToolValue {
}
extension Int: AgentToolValue {
public static var agentValueType: AgentValueType { .integer }
public static var agentValueType: AgentValueType {
.integer
}
public func toJSON() throws -> Any { self }
public func toJSON() throws -> Any {
self
}
public static func fromJSON(_ json: Any) throws -> Self {
if let int = json as? Int {
@ -56,9 +64,13 @@ extension Int: AgentToolValue {
}
extension Double: AgentToolValue {
public static var agentValueType: AgentValueType { .number }
public static var agentValueType: AgentValueType {
.number
}
public func toJSON() throws -> Any { self }
public func toJSON() throws -> Any {
self
}
public static func fromJSON(_ json: Any) throws -> Self {
if let double = json as? Double {
@ -72,9 +84,13 @@ extension Double: AgentToolValue {
}
extension Bool: AgentToolValue {
public static var agentValueType: AgentValueType { .boolean }
public static var agentValueType: AgentValueType {
.boolean
}
public func toJSON() throws -> Any { self }
public func toJSON() throws -> Any {
self
}
public static func fromJSON(_ json: Any) throws -> Self {
guard let bool = json as? Bool else {
@ -87,11 +103,15 @@ extension Bool: AgentToolValue {
/// Null value type
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public struct AgentNullValue: AgentToolValue, Equatable {
public static var agentValueType: AgentValueType { .null }
public static var agentValueType: AgentValueType {
.null
}
public init() {}
public func toJSON() throws -> Any { NSNull() }
public func toJSON() throws -> Any {
NSNull()
}
public static func fromJSON(_ json: Any) throws -> Self {
if json is NSNull {
@ -103,7 +123,9 @@ public struct AgentNullValue: AgentToolValue, Equatable {
/// Array conformance
extension Array: AgentToolValue where Element: AgentToolValue {
public static var agentValueType: AgentValueType { .array }
public static var agentValueType: AgentValueType {
.array
}
public func toJSON() throws -> Any {
try map { try $0.toJSON() }
@ -119,7 +141,9 @@ extension Array: AgentToolValue where Element: AgentToolValue {
/// Dictionary conformance
extension Dictionary: AgentToolValue where Key == String, Value: AgentToolValue {
public static var agentValueType: AgentValueType { .object }
public static var agentValueType: AgentValueType {
.object
}
public func toJSON() throws -> Any {
var result: [String: Any] = [:]
@ -158,7 +182,9 @@ public struct AnyAgentToolValue: AgentToolValue, Equatable, Codable {
case object([String: AnyAgentToolValue])
}
public static var agentValueType: AgentValueType { .object } // Generic type
public static var agentValueType: AgentValueType {
.object
} // Generic type
public init(_ value: some AgentToolValue) throws {
let json = try value.toJSON()

View File

@ -246,7 +246,7 @@ public struct ModelMessage: Sendable, Codable, Equatable {
self.metadata = metadata
}
// Convenience initializers
/// Convenience initializers
public static func system(_ text: String) -> ModelMessage {
ModelMessage(role: .system, content: [.text(text)])
}
@ -366,7 +366,7 @@ public struct GenerationSettings: Sendable {
public static let `default` = GenerationSettings()
}
// Manual Codable conformance excluding non-codable stopConditions
/// Manual Codable conformance excluding non-codable stopConditions
extension GenerationSettings: Codable {
enum CodingKeys: String, CodingKey {
case maxTokens
@ -508,7 +508,7 @@ public struct TextStreamDelta: Sendable {
self.finishReason = finishReason
}
// Convenience constructors
/// Convenience constructors
public static func text(_ content: String, channel: ResponseChannel? = nil) -> TextStreamDelta {
TextStreamDelta(type: .textDelta, content: content, channel: channel)
}

View File

@ -151,7 +151,7 @@ public struct RecoverySuggestion: Sendable {
self.helpURL = helpURL
}
// Common recovery suggestions
/// Common recovery suggestions
public static let checkAPIKey = RecoverySuggestion(
suggestion: "Check that your API key is valid and has the necessary permissions",
actions: [.validateAPIKey, .regenerateAPIKey],

View File

@ -31,13 +31,13 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
// MARK: - Provider Sub-Enums
public enum OpenAI: Sendable, Hashable, CaseIterable {
// Latest models (2025)
/// Latest models (2025)
case o4Mini
// GPT-5.2 Series
/// GPT-5.2 Series
case gpt52 // Flagship GPT-5.2
// GPT-5.1 Series (November 2025)
/// GPT-5.1 Series (November 2025)
case gpt51 // Flagship GPT-5.1
// GPT-5 Series (August 2025)
@ -63,7 +63,7 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case gpt4Turbo
case gpt35Turbo
// Fine-tuned models
/// Fine-tuned models
case custom(String)
public static var allCases: [OpenAI] {
@ -188,7 +188,7 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case sonnet45
case haiku45
// Fine-tuned models
/// Fine-tuned models
case custom(String)
public static var allCases: [Anthropic] {
@ -223,7 +223,9 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsTools: Bool { true } // All Claude models support tools
public var supportsTools: Bool {
true
} // All Claude models support tools
public var supportsAudioInput: Bool {
// Anthropic has voice features in mobile apps but limited API support as of 2025
@ -251,7 +253,9 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case gemini25Flash = "gemini-2.5-flash"
case gemini25FlashLite = "gemini-2.5-flash-lite"
public var apiModelId: String { self.rawValue }
public var apiModelId: String {
self.rawValue
}
public var userFacingModelId: String {
switch self {
@ -262,8 +266,13 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsVision: Bool { true }
public var supportsTools: Bool { true }
public var supportsVision: Bool {
true
}
public var supportsTools: Bool {
true
}
public var supportsAudioInput: Bool {
switch self {
@ -274,7 +283,9 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsAudioOutput: Bool { false }
public var supportsAudioOutput: Bool {
false
}
public var contextLength: Int {
switch self {
@ -301,10 +312,16 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsTools: Bool { true }
public var supportsTools: Bool {
true
}
public var supportsAudioInput: Bool { false } // Mistral doesn't support audio yet
public var supportsAudioOutput: Bool { false }
public var supportsAudioInput: Bool {
false
} // Mistral doesn't support audio yet
public var supportsAudioOutput: Bool {
false
}
public var contextLength: Int {
switch self {
@ -326,11 +343,19 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case mixtral8x7b = "mixtral-8x7b"
case gemma29b = "gemma2-9b"
public var supportsVision: Bool { false } // Groq models don't support vision yet
public var supportsTools: Bool { true }
public var supportsVision: Bool {
false
} // Groq models don't support vision yet
public var supportsTools: Bool {
true
}
public var supportsAudioInput: Bool { false } // Groq focuses on text inference speed
public var supportsAudioOutput: Bool { false }
public var supportsAudioInput: Bool {
false
} // Groq focuses on text inference speed
public var supportsAudioOutput: Bool {
false
}
public var contextLength: Int {
switch self {
@ -356,7 +381,7 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case grokVisionBeta
case grokBeta
// Custom models
/// Custom models
case custom(String)
public static var allCases: [Grok] {
@ -401,7 +426,9 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsTools: Bool { true }
public var supportsTools: Bool {
true
}
public var supportsAudioInput: Bool {
// Grok has voice support but limited API access as of 2025
@ -472,7 +499,7 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case firefunction
case commandR
// Custom/other models
/// Custom/other models
case custom(String)
public static var allCases: [Ollama] {
@ -587,9 +614,12 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
}
}
public var supportsAudioInput: Bool { false
public var supportsAudioInput: Bool {
false
} // Ollama models run locally and don't support native audio processing
public var supportsAudioOutput: Bool { false }
public var supportsAudioOutput: Bool {
false
}
public var contextLength: Int {
switch self {
@ -630,10 +660,10 @@ public enum LanguageModel: Sendable, CustomStringConvertible, Hashable {
case mistral7B
case phi3Mini
// Currently loaded model
/// Currently loaded model
case current
// Custom model path
/// Custom model path
case custom(String)
public static var allCases: [LMStudio] {
@ -1227,7 +1257,7 @@ extension LanguageModel {
return .anthropic(.haiku45)
}
let genericClaudeIdentifiers: Set<String> = [
let genericClaudeIdentifiers: Set = [
"claude",
"claudelatest",
"claude-latest",
@ -1265,7 +1295,7 @@ extension LanguageModel {
return .google(.gemini25Flash)
}
let genericGeminiIdentifiers: Set<String> = [
let genericGeminiIdentifiers: Set = [
"gemini",
"geminiflash",
"gemini-flash",

View File

@ -194,7 +194,7 @@ enum AnthropicContent: Codable {
try Self.encodeAnyDict(self.input, to: inputEncoder)
}
// Helper methods for encoding/decoding [String: Any]
/// Helper methods for encoding/decoding [String: Any]
private static func decodeAnyDict(from decoder: Decoder) throws -> [String: Any] {
let container = try decoder.singleValueContainer()
return try container.decode([String: JSONValue].self).mapValues { $0.value }

View File

@ -9,8 +9,13 @@ public final class AzureOpenAIProvider: ModelProvider {
public let modelId: String
public let apiVersion: String
public let capabilities: ModelCapabilities
public var baseURL: String? { self.resolvedBaseURL }
public var apiKey: String? { self.resolvedAPIKey }
public var baseURL: String? {
self.resolvedBaseURL
}
public var apiKey: String? {
self.resolvedAPIKey
}
private let authHeaderName: String
private let authHeaderValuePrefix: String

View File

@ -12,7 +12,9 @@ public final class GoogleProvider: ModelProvider {
public let capabilities: ModelCapabilities
private let model: LanguageModel.Google
private var apiModelName: String { self.model.apiModelId }
private var apiModelName: String {
self.model.apiModelId
}
public init(model: LanguageModel.Google, configuration: TachikomaConfiguration) throws {
self.model = model

View File

@ -16,11 +16,22 @@ public actor LMStudioProvider: ModelProvider {
private let configuredModelId: String
private let configuredCapabilities: ModelCapabilities
// Expose as optional for protocol conformance, but it's never actually nil
public nonisolated var baseURL: String? { self.actualBaseURL }
public nonisolated var apiKey: String? { self.configuredApiKey }
public nonisolated var modelId: String { self.configuredModelId }
public nonisolated var capabilities: ModelCapabilities { self.configuredCapabilities }
/// Expose as optional for protocol conformance, but it's never actually nil
public nonisolated var baseURL: String? {
self.actualBaseURL
}
public nonisolated var apiKey: String? {
self.configuredApiKey
}
public nonisolated var modelId: String {
self.configuredModelId
}
public nonisolated var capabilities: ModelCapabilities {
self.configuredCapabilities
}
private let session: URLSession
private let encoder = JSONEncoder()

View File

@ -12,10 +12,15 @@ struct OpenAIEmbeddingProvider: EmbeddingProvider, ModelProvider {
let apiKey: String?
let baseURL: String?
var modelId: String { self.model.rawValue }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
self.model.rawValue
}
// ModelProvider conformance (not used for embeddings)
var capabilities: ModelCapabilities {
ModelCapabilities()
}
/// ModelProvider conformance (not used for embeddings)
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
throw TachikomaError.unsupportedOperation("Text generation not supported for embedding models")
}
@ -92,17 +97,25 @@ struct OpenAIEmbeddingProvider: EmbeddingProvider, ModelProvider {
}
}
// Placeholder providers for other services
/// Placeholder providers for other services
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct CohereEmbeddingProvider: EmbeddingProvider, ModelProvider {
let model: EmbeddingModel.CohereEmbedding
let apiKey: String?
var modelId: String { self.model.rawValue }
var baseURL: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
self.model.rawValue
}
// ModelProvider conformance (not used for embeddings)
var baseURL: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
/// ModelProvider conformance (not used for embeddings)
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
throw TachikomaError.unsupportedOperation("Text generation not supported for embedding models")
}
@ -121,11 +134,19 @@ struct VoyageEmbeddingProvider: EmbeddingProvider, ModelProvider {
let model: EmbeddingModel.VoyageEmbedding
let apiKey: String?
var modelId: String { self.model.rawValue }
var baseURL: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
self.model.rawValue
}
// ModelProvider conformance (not used for embeddings)
var baseURL: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
/// ModelProvider conformance (not used for embeddings)
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
throw TachikomaError.unsupportedOperation("Text generation not supported for embedding models")
}

View File

@ -11,7 +11,7 @@ struct OpenAIResponsesRequest: Encodable {
let topP: Double?
let maxOutputTokens: Int?
// Response format and text configuration
/// Response format and text configuration
let text: TextConfig? // GPT-5 text configuration with verbosity
// Tool configuration
@ -28,13 +28,13 @@ struct OpenAIResponsesRequest: Encodable {
let serviceTier: String?
let include: [String]?
// Reasoning configuration (for o3/o4/GPT-5)
/// Reasoning configuration (for o3/o4/GPT-5)
let reasoning: ReasoningConfig?
// Truncation for long inputs
/// Truncation for long inputs
let truncation: String?
// Streaming support
/// Streaming support
let stream: Bool?
enum CodingKeys: String, CodingKey {
@ -62,7 +62,7 @@ struct OpenAIResponsesRequest: Encodable {
/// Text verbosity levels for GPT-5
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
enum TextVerbosity: String, Codable, Sendable {
enum TextVerbosity: String, Codable {
case low
case medium
case high
@ -70,7 +70,7 @@ enum TextVerbosity: String, Codable, Sendable {
/// Internal reasoning effort levels for OpenAI responses provider
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
enum OpenAIReasoningEffort: String, Codable, Sendable {
enum OpenAIReasoningEffort: String, Codable {
case minimal
case low
case medium
@ -79,7 +79,7 @@ enum OpenAIReasoningEffort: String, Codable, Sendable {
/// Reasoning summary modes for reasoning models
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
enum ReasoningSummary: String, Codable, Sendable {
enum ReasoningSummary: String, Codable {
case concise
case detailed
case auto
@ -87,13 +87,13 @@ enum ReasoningSummary: String, Codable, Sendable {
/// Text configuration for GPT-5 models
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct TextConfig: Codable, Sendable {
struct TextConfig: Codable {
let verbosity: TextVerbosity?
}
/// Reasoning configuration for reasoning models
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct ReasoningConfig: Codable, Sendable {
struct ReasoningConfig: Codable {
let effort: OpenAIReasoningEffort?
let summary: ReasoningSummary?
}
@ -186,11 +186,11 @@ struct JSONSchemaFormat: Codable {
/// Message format for Responses API
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct ResponsesMessage: Codable, Sendable {
struct ResponsesMessage: Codable {
let role: String
let content: ResponsesContent
enum ResponsesContent: Codable, Sendable {
enum ResponsesContent: Codable {
case text(String)
case parts([ResponsesContentPart])
@ -218,7 +218,7 @@ struct ResponsesMessage: Codable, Sendable {
/// Content part for multimodal messages
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct ResponsesContentPart: Codable, Sendable {
struct ResponsesContentPart: Codable {
let type: String
let text: String?
/// OpenAI Responses API (GPT5.x) accepts `image_url` only as a string (URL or data URL).
@ -226,7 +226,7 @@ struct ResponsesContentPart: Codable, Sendable {
/// avoid 400s ("expected an image URL, but got an object instead").
let imageUrl: ImageURL?
struct ImageURL: Codable, Sendable {
struct ImageURL: Codable {
let url: String
let detail: String?
}
@ -271,12 +271,12 @@ struct ResponsesContentPart: Codable, Sendable {
/// Heterogeneous input entries supported by the Responses API
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
enum ResponsesInputItem: Encodable, Sendable {
enum ResponsesInputItem: Encodable {
case message(ResponsesMessage)
case functionCall(FunctionCall)
case functionCallOutput(FunctionCallOutput)
struct FunctionCall: Encodable, Sendable {
struct FunctionCall: Encodable {
let type: String = "function_call"
let callId: String
let name: String
@ -290,7 +290,7 @@ enum ResponsesInputItem: Encodable, Sendable {
}
}
struct FunctionCallOutput: Encodable, Sendable {
struct FunctionCallOutput: Encodable {
let type: String = "function_call_output"
let callId: String
let output: String
@ -473,7 +473,7 @@ struct ResponsesTool: Codable {
/// Response from OpenAI Responses API (GPT-5 format)
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct OpenAIResponsesResponse: Codable, Sendable {
struct OpenAIResponsesResponse: Codable {
let id: String
let object: String?
let createdAt: Int? // GPT-5 uses created_at
@ -491,8 +491,8 @@ struct OpenAIResponsesResponse: Codable, Sendable {
case created
}
// GPT-5 output format
struct ResponsesOutput: Codable, Sendable {
/// GPT-5 output format
struct ResponsesOutput: Codable {
let id: String
let type: String
let status: String?
@ -510,7 +510,7 @@ struct OpenAIResponsesResponse: Codable, Sendable {
case toolCall = "tool_call"
}
struct OutputContent: Codable, Sendable {
struct OutputContent: Codable {
let type: String
let text: String?
let toolCall: ResponsesToolCall?
@ -523,8 +523,8 @@ struct OpenAIResponsesResponse: Codable, Sendable {
}
}
// O3 choices format (kept for compatibility)
struct ResponsesChoice: Codable, Sendable {
/// O3 choices format (kept for compatibility)
struct ResponsesChoice: Codable {
let index: Int
let message: ResponsesOutputMessage
let finishReason: String?
@ -538,7 +538,7 @@ struct OpenAIResponsesResponse: Codable, Sendable {
}
}
struct ResponsesOutputMessage: Codable, Sendable {
struct ResponsesOutputMessage: Codable {
let role: String
let content: String?
let toolCalls: [ResponsesToolCall]?
@ -552,18 +552,18 @@ struct OpenAIResponsesResponse: Codable, Sendable {
}
}
struct ResponsesToolCall: Codable, Sendable {
struct ResponsesToolCall: Codable {
let id: String
let type: String
let function: ResponsesToolFunction
struct ResponsesToolFunction: Codable, Sendable {
struct ResponsesToolFunction: Codable {
let name: String
let arguments: String
}
}
struct ResponsesUsage: Codable, Sendable {
struct ResponsesUsage: Codable {
let inputTokens: Int?
let outputTokens: Int?
let totalTokens: Int?
@ -584,7 +584,7 @@ struct OpenAIResponsesResponse: Codable, Sendable {
case outputTokensDetails = "output_tokens_details"
}
struct TokenDetails: Codable, Sendable {
struct TokenDetails: Codable {
let cachedTokens: Int?
enum CodingKeys: String, CodingKey {
@ -592,7 +592,7 @@ struct OpenAIResponsesResponse: Codable, Sendable {
}
}
struct OutputTokenDetails: Codable, Sendable {
struct OutputTokenDetails: Codable {
let reasoningTokens: Int?
enum CodingKeys: String, CodingKey {
@ -601,7 +601,7 @@ struct OpenAIResponsesResponse: Codable, Sendable {
}
}
struct ResponsesMetadata: Codable, Sendable {
struct ResponsesMetadata: Codable {
let responseId: String?
let reasoningItemIds: [String]?
@ -616,14 +616,14 @@ struct OpenAIResponsesResponse: Codable, Sendable {
/// Server-sent event for streaming responses (O3 and older models)
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
struct OpenAIResponsesStreamChunk: Codable, Sendable {
struct OpenAIResponsesStreamChunk: Codable {
let id: String
let object: String
let created: Int
let model: String
let choices: [StreamChoice]
struct StreamChoice: Codable, Sendable {
struct StreamChoice: Codable {
let index: Int
let delta: StreamDelta
let finishReason: String?
@ -635,7 +635,7 @@ struct OpenAIResponsesStreamChunk: Codable, Sendable {
}
}
struct StreamDelta: Codable, Sendable {
struct StreamDelta: Codable {
let role: String?
let content: String?
let toolCalls: [StreamToolCall]?
@ -647,13 +647,13 @@ struct OpenAIResponsesStreamChunk: Codable, Sendable {
}
}
struct StreamToolCall: Codable, Sendable {
struct StreamToolCall: Codable {
let index: Int
let id: String?
let type: String?
let function: StreamToolFunction?
struct StreamToolFunction: Codable, Sendable {
struct StreamToolFunction: Codable {
let name: String?
let arguments: String?
}

View File

@ -140,7 +140,7 @@ enum OpenAIChatMessageContent: Codable {
let url: String
}
// Provide custom Codable to match OpenAI schema (flattened objects with type field)
/// Provide custom Codable to match OpenAI schema (flattened objects with type field)
enum CodingKeys: String, CodingKey {
case type
case text
@ -326,8 +326,8 @@ struct OpenAIErrorResponse: Codable {
}
}
// Helper type for Either content
enum Either<Left, Right>: Codable where Left: Codable, Right: Codable {
/// Helper type for Either content
enum Either<Left: Codable, Right: Codable>: Codable {
case left(Left)
case right(Right)

View File

@ -97,9 +97,17 @@ public struct AnyAgentTool: Sendable {
private let _schema: AgentToolSchema
private let _execute: @Sendable (AnyAgentToolValue, ToolExecutionContext) async throws -> AnyAgentToolValue
public var name: String { self._name }
public var description: String { self._description }
public var schema: AgentToolSchema { self._schema }
public var name: String {
self._name
}
public var description: String {
self._description
}
public var schema: AgentToolSchema {
self._schema
}
public init<T: AgentToolProtocol>(_ tool: T) {
self._name = tool.name

View File

@ -547,10 +547,21 @@ public struct CacheAwareProvider<Base: ModelProvider>: ModelProvider {
let provider: Base
let cache: ResponseCache
public var modelId: String { self.provider.modelId }
public var baseURL: String? { self.provider.baseURL }
public var apiKey: String? { self.provider.apiKey }
public var capabilities: ModelCapabilities { self.provider.capabilities }
public var modelId: String {
self.provider.modelId
}
public var baseURL: String? {
self.provider.baseURL
}
public var apiKey: String? {
self.provider.apiKey
}
public var capabilities: ModelCapabilities {
self.provider.capabilities
}
public func generateText(request: ProviderRequest) async throws -> ProviderResponse {
// Check cache with smart TTL based on request type

View File

@ -267,7 +267,7 @@ public struct UsageOperation: Sendable, Codable {
self.type = type
}
// For backward compatibility, provide a computed property to reconstruct model info
/// For backward compatibility, provide a computed property to reconstruct model info
public var modelDescription: String {
"\(self.providerName)/\(self.modelId)"
}
@ -393,10 +393,21 @@ public struct UsageReport: Sendable {
public let endDate: Date
public let sessions: [UsageSession]
public var totalSessions: Int { self.sessions.count }
public var totalOperations: Int { self.sessions.reduce(0) { $0 + $1.operations.count } }
public var totalTokens: Int { self.sessions.reduce(0) { $0 + $1.totalTokens } }
public var totalCost: Double { self.sessions.reduce(0) { $0 + $1.totalCost } }
public var totalSessions: Int {
self.sessions.count
}
public var totalOperations: Int {
self.sessions.reduce(0) { $0 + $1.operations.count }
}
public var totalTokens: Int {
self.sessions.reduce(0) { $0 + $1.totalTokens }
}
public var totalCost: Double {
self.sessions.reduce(0) { $0 + $1.totalCost }
}
public let providerBreakdown: [String: ProviderUsage]
public let modelBreakdown: [String: ModelUsage]

View File

@ -124,7 +124,7 @@ public final class Agent<Context>: @unchecked Sendable {
)
// Track final message in conversation (this is approximate for streaming)
let trackedStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
return AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
do {
var assistantText = ""
@ -151,8 +151,6 @@ public final class Agent<Context>: @unchecked Sendable {
}
}
}
return trackedStream
}
/// Reset the agent's conversation history

View File

@ -126,7 +126,7 @@ public final class Conversation: @unchecked Sendable {
)
// Create a new stream to process the response and update the conversation
let processedStream = AsyncThrowingStream<String, Error> { continuation in
return AsyncThrowingStream<String, Error> { continuation in
Task {
var fullResponse = ""
do {
@ -151,8 +151,6 @@ public final class Conversation: @unchecked Sendable {
}
}
}
return processedStream
}
}

View File

@ -150,7 +150,7 @@ public func createTool(
)
}
// Helper struct for type-safe tool creation (moved outside to avoid nesting in generic function)
/// Helper struct for type-safe tool creation (moved outside to avoid nesting in generic function)
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
private struct ConcreteAgentTool<I: AgentToolValue, O: AgentToolValue>: AgentToolProtocol {
typealias Input = I

View File

@ -50,7 +50,7 @@ public final class RealtimeAudioManager: NSObject {
private let audioSession = AVAudioSession.sharedInstance()
#endif
// Audio format for API (24kHz PCM16)
/// Audio format for API (24kHz PCM16)
private let apiFormat = AVAudioFormat(
commonFormat: .pcmFormatInt16,
sampleRate: 24000,
@ -297,7 +297,7 @@ public final class RealtimeAudioManager: NSObject {
#endif
}
/// Set audio input device (macOS only)
// Set audio input device (macOS only)
#if os(macOS)
public func setInputDevice(_: AudioDeviceID) throws {
// Note: Setting audio device on macOS requires more complex Core Audio API

View File

@ -304,9 +304,7 @@ public final class RealtimeAudioProcessor: @unchecked Sendable {
}
let mantissa = UInt8((s >> (exponent + 3)) & 0x0F)
let encoded = ~(sign | (exponent << 4) | mantissa)
return encoded
return ~(sign | (exponent << 4) | mantissa)
}
private func decodeUlaw(_ ulaw: UInt8) -> Int16 {

View File

@ -275,7 +275,7 @@ public protocol AudioStreamPipelineDelegate: AnyObject {
func audioStreamPipeline(didEncounterError error: Error) async
}
// Default implementation for optional methods
/// Default implementation for optional methods
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension AudioStreamPipelineDelegate {
// Pipeline started

View File

@ -135,7 +135,7 @@ public final class RealtimeConversation: ObservableObject {
private var audioBuffer = Data()
private let audioChunkSize = 1024 * 4 // 4KB chunks
// Background tasks
/// Background tasks
private var eventProcessingTask: Task<Void, Never>?
// MARK: - Initialization

View File

@ -39,7 +39,9 @@ public enum RealtimeClientEvent: RealtimeEventProtocol {
}
}
public var eventId: String? { UUID().uuidString }
public var eventId: String? {
UUID().uuidString
}
}
// MARK: - Server Events (Events sent from server to client)
@ -111,7 +113,9 @@ public enum RealtimeServerEvent: RealtimeEventProtocol {
}
}
public var eventId: String? { nil }
public var eventId: String? {
nil
}
}
// MARK: - Session Events

View File

@ -111,7 +111,7 @@ public struct AgentToolWrapper: RealtimeExecutableTool {
}
}
// Helper function to convert Any to AnyAgentToolValue
/// Helper function to convert Any to AnyAgentToolValue
private func convertAnyToToolArgument(_ value: Any) -> AnyAgentToolValue {
do {
return try AnyAgentToolValue.fromJSON(value)
@ -121,7 +121,7 @@ public struct AgentToolWrapper: RealtimeExecutableTool {
}
}
// Helper function to convert AnyAgentToolValue to Any
/// Helper function to convert AnyAgentToolValue to Any
private func convertToolArgumentToAny(_ arg: AnyAgentToolValue) -> Any {
do {
return try arg.toJSON()

View File

@ -88,7 +88,7 @@ public final class AudioRecorder: ObservableObject, AudioRecorderProtocol {
private let channels: AVAudioChannelCount = 1
private let bitDepth: Int = 16
// Maximum recording duration (5 minutes by default)
/// Maximum recording duration (5 minutes by default)
public var maxRecordingDuration: TimeInterval = 300
// MARK: - Initialization
@ -211,8 +211,7 @@ public final class AudioRecorder: ObservableObject, AudioRecorderProtocol {
recordingURL = nil
}
let audioData = try AudioData(contentsOf: url)
return audioData
return try AudioData(contentsOf: url)
}
/// Cancel recording without returning data
@ -418,9 +417,17 @@ private let logger = Logger(label: "tachikoma.audio.recorder")
public final class AudioRecorder: AudioRecorderProtocol {
public init() {}
public var isRecording: Bool { false }
public var isAvailable: Bool { false }
public var recordingDuration: TimeInterval { 0 }
public var isRecording: Bool {
false
}
public var isAvailable: Bool {
false
}
public var recordingDuration: TimeInterval {
0
}
public func startRecording() async throws {
throw AudioRecordingError.audioEngineError("Audio recording is unavailable on this platform")

View File

@ -39,8 +39,13 @@ public enum TranscriptionModel: Sendable, CustomStringConvertible {
case whisperLargeV3Turbo = "whisper-large-v3-turbo"
case distilWhisperLargeV3En = "distil-whisper-large-v3-en"
public var supportsTimestamps: Bool { true }
public var supportsLanguageDetection: Bool { true }
public var supportsTimestamps: Bool {
true
}
public var supportsLanguageDetection: Bool {
true
}
}
public enum Deepgram: String, CaseIterable, Sendable {
@ -49,17 +54,30 @@ public enum TranscriptionModel: Sendable, CustomStringConvertible {
case enhanced
case base
public var supportsTimestamps: Bool { true }
public var supportsLanguageDetection: Bool { true }
public var supportsSummarization: Bool { true }
public var supportsTimestamps: Bool {
true
}
public var supportsLanguageDetection: Bool {
true
}
public var supportsSummarization: Bool {
true
}
}
public enum ElevenLabs: String, CaseIterable, Sendable {
case scribeV1 = "scribe_v1"
case scribeV1Experimental = "scribe_v1_experimental"
public var supportsTimestamps: Bool { false }
public var supportsLanguageDetection: Bool { true }
public var supportsTimestamps: Bool {
false
}
public var supportsLanguageDetection: Bool {
true
}
}
// MARK: - Model Properties
@ -160,8 +178,13 @@ public enum SpeechModel: Sendable, CustomStringConvertible {
case multilingualV2 = "eleven_multilingual_v2"
case englishV1 = "eleven_english_v1"
public var supportsVoiceCloning: Bool { true }
public var supportedFormats: [AudioFormat] { [.mp3, .wav, .pcm] }
public var supportsVoiceCloning: Bool {
true
}
public var supportedFormats: [AudioFormat] {
[.mp3, .wav, .pcm]
}
}
// MARK: - Model Properties

View File

@ -98,7 +98,7 @@ public enum VoiceOption: Sendable, Hashable {
case nova
case shimmer
// Custom voice (provider-specific)
/// Custom voice (provider-specific)
case custom(String)
public var stringValue: String {

View File

@ -30,21 +30,21 @@ enum MCPContentBridge {
static func convert(_ content: MCP.Tool.Content) -> AnyAgentToolValue {
switch content {
case let .text(text):
return AnyAgentToolValue(string: text)
AnyAgentToolValue(string: text)
case let .image(data, mimeType, _):
return AnyAgentToolValue(object: [
AnyAgentToolValue(object: [
"type": AnyAgentToolValue(string: "image"),
"mimeType": AnyAgentToolValue(string: mimeType),
"data": AnyAgentToolValue(string: data),
])
case let .resource(resource, annotations, meta):
return AnyAgentToolValue(object: self.resourceObject(
AnyAgentToolValue(object: self.resourceObject(
resource: resource,
annotations: annotations,
meta: meta,
))
case let .resourceLink(uri, name, title, description, mimeType, annotations):
return AnyAgentToolValue(object: self.resourceLinkObject(
AnyAgentToolValue(object: self.resourceLinkObject(
uri: uri,
name: name,
title: title,
@ -53,7 +53,7 @@ enum MCPContentBridge {
annotations: annotations,
))
case let .audio(data, mimeType):
return AnyAgentToolValue(object: [
AnyAgentToolValue(object: [
"type": AnyAgentToolValue(string: "audio"),
"mimeType": AnyAgentToolValue(string: mimeType),
"data": AnyAgentToolValue(string: data),
@ -65,7 +65,9 @@ enum MCPContentBridge {
resource: Resource.Content,
annotations: Resource.Annotations?,
meta: Metadata?,
) -> [String: AnyAgentToolValue] {
)
-> [String: AnyAgentToolValue]
{
var resourceDict: [String: AnyAgentToolValue] = [
"type": AnyAgentToolValue(string: "resource"),
"uri": AnyAgentToolValue(string: resource.uri),
@ -105,7 +107,9 @@ enum MCPContentBridge {
description: String?,
mimeType: String?,
annotations: Resource.Annotations?,
) -> [String: AnyAgentToolValue] {
)
-> [String: AnyAgentToolValue]
{
var resourceDict: [String: AnyAgentToolValue] = [
"type": AnyAgentToolValue(string: "resourceLink"),
"uri": AnyAgentToolValue(string: uri),

View File

@ -4,7 +4,7 @@ import FoundationNetworking
#endif
import Logging
// Actor to manage mutable state for Sendable conformance
/// Actor to manage mutable state for Sendable conformance
private actor HTTPTransportState {
var urlSession: URLSession?
var baseURL: URL?
@ -18,10 +18,21 @@ private actor HTTPTransportState {
self.headers = headers
}
func getSession() -> URLSession? { self.urlSession }
func getBaseURL() -> URL? { self.baseURL }
func getTimeout() -> TimeInterval { self.requestTimeout }
func getHeaders() -> [String: String] { self.headers }
func getSession() -> URLSession? {
self.urlSession
}
func getBaseURL() -> URL? {
self.baseURL
}
func getTimeout() -> TimeInterval {
self.requestTimeout
}
func getHeaders() -> [String: String] {
self.headers
}
}
/// HTTP transport for MCP communication

View File

@ -2,7 +2,7 @@ import Foundation
import Logging
import MCP
// Shared JSON-RPC types for HTTP transport
/// Shared JSON-RPC types for HTTP transport
struct HTTPJSONRPCRequest<P: Encodable>: Encodable {
let jsonrpc = "2.0"
let method: String
@ -56,7 +56,7 @@ public struct MCPServerConfig: Sendable, Codable {
}
}
// Actor to manage mutable state for Sendable conformance
/// Actor to manage mutable state for Sendable conformance
private actor MCPClientState {
var transport: (any MCPTransport)?
var tools: [Tool] = []
@ -258,7 +258,7 @@ struct InitializeParams: Codable {
}
}
// Some servers use snake_case for protocol_version in initialize
/// Some servers use snake_case for protocol_version in initialize
struct InitializeParamsSnake: Codable {
let protocolVersion: String
let clientInfo: ClientInfo

View File

@ -189,7 +189,7 @@ public final class TachikomaMCPClientManager {
return all
}
// Execute a tool on a specific server
/// Execute a tool on a specific server
public func executeTool(
serverName: String,
toolName: String,
@ -222,7 +222,9 @@ public final class TachikomaMCPClientManager {
// MARK: Health/Info (lightweight)
public func getServerNames() -> [String] { Array(self.effectiveConfigs.keys).sorted() }
public func getServerNames() -> [String] {
Array(self.effectiveConfigs.keys).sorted()
}
public func getServerInfo(name: String) async -> (config: MCPServerConfig, connected: Bool)? {
guard let cfg = effectiveConfigs[name] else { return nil }

View File

@ -5,7 +5,7 @@ import FoundationNetworking
import Logging
import MCP
// Internal state for SSE transport
/// Internal state for SSE transport
private actor SSEState {
var transport: HTTPClientTransport?
var baseURL: URL?
@ -16,29 +16,60 @@ private actor SSEState {
var timeoutTasks: [Int: Task<Void, Never>] = [:]
var requestTimeoutNs: UInt64 = 30_000_000_000 // default 30s
func setTransport(_ t: HTTPClientTransport?) { self.transport = t }
func getTransport() -> HTTPClientTransport? { self.transport }
func setBaseURL(_ url: URL?) { self.baseURL = url }
func setHeaders(_ h: [String: String]) { self.headers = h }
func setEndpoint(_ url: URL?) { self.endpointURL = url }
func getEndpoint() -> URL? { self.endpointURL }
func getBaseURL() -> URL? { self.baseURL }
func setTransport(_ t: HTTPClientTransport?) {
self.transport = t
}
func getNextId() -> Int { defer { nextId += 1 }
func getTransport() -> HTTPClientTransport? {
self.transport
}
func setBaseURL(_ url: URL?) {
self.baseURL = url
}
func setHeaders(_ h: [String: String]) {
self.headers = h
}
func setEndpoint(_ url: URL?) {
self.endpointURL = url
}
func getEndpoint() -> URL? {
self.endpointURL
}
func getBaseURL() -> URL? {
self.baseURL
}
func getNextId() -> Int {
defer { nextId += 1 }
return self.nextId
}
func addPending(_ id: Int, _ cont: CheckedContinuation<Data, Swift.Error>) { self.pendingRequests[id] = cont }
func removePending(_ id: Int) -> CheckedContinuation<Data, Swift.Error>? { self.pendingRequests
.removeValue(forKey: id)
func addPending(_ id: Int, _ cont: CheckedContinuation<Data, Swift.Error>) {
self.pendingRequests[id] = cont
}
func removePending(_ id: Int) -> CheckedContinuation<Data, Swift.Error>? {
self.pendingRequests
.removeValue(forKey: id)
}
func setTimeout(_ seconds: TimeInterval) {
self.requestTimeoutNs = UInt64((seconds > 0 ? seconds : 30) * 1_000_000_000)
}
func addTimeoutTask(_ id: Int, _ task: Task<Void, Never>) { self.timeoutTasks[id] = task }
func cancelTimeout(_ id: Int) { self.timeoutTasks.removeValue(forKey: id)?.cancel() }
func addTimeoutTask(_ id: Int, _ task: Task<Void, Never>) {
self.timeoutTasks[id] = task
}
func cancelTimeout(_ id: Int) {
self.timeoutTasks.removeValue(forKey: id)?.cancel()
}
func cancelAll(_ error: Swift.Error) {
for (_, c) in self.pendingRequests {
c.resume(throwing: error)
@ -99,7 +130,7 @@ public final class SSETransport: MCPTransport {
await self.state.setTransport(nil)
}
// Expose underlying swift-sdk HTTP transport for advanced usage
/// Expose underlying swift-sdk HTTP transport for advanced usage
public func underlyingSDKTransport() async -> HTTPClientTransport? {
await self.state.getTransport()
}

View File

@ -8,7 +8,7 @@ import Logging
import MCP
import Tachikoma
// Actor to manage mutable state for Sendable conformance
/// Actor to manage mutable state for Sendable conformance
private actor StdioTransportState {
var process: Process?
var inputPipe: Pipe?

View File

@ -19,7 +19,7 @@ public protocol MCPTool: Sendable {
/// Wrapper for tool arguments received from MCP
public struct ToolArguments: Sendable {
// Execute the tool with the given arguments
/// Execute the tool with the given arguments
private let raw: Value
public init(raw: [String: Any]) {
@ -174,7 +174,7 @@ private func ValueToAny(_ value: Value) -> Any {
}
}
// Helper function to convert Any to Value
/// Helper function to convert Any to Value
private func convertToValue(_ value: Any) -> Value {
switch value {
case let string as String:
@ -250,5 +250,5 @@ public struct ToolResponse: Sendable {
}
}
// Type alias for convenience
/// Type alias for convenience
public typealias MCPContent = MCP.Tool.Content

View File

@ -4,10 +4,9 @@ import Testing
@testable import Tachikoma
@testable import TachikomaMCP
@Suite("MCP Client Tests")
struct MCPClientTests {
@Test("MCPServerConfig initialization with all parameters")
func serverConfigFullInit() {
@Test
func `MCPServerConfig initialization with all parameters`() {
let config = MCPServerConfig(
transport: "stdio",
command: "npx",
@ -29,8 +28,8 @@ struct MCPClientTests {
#expect(config.description == "Test server")
}
@Test("MCPServerConfig initialization with minimal parameters")
func serverConfigMinimalInit() {
@Test
func `MCPServerConfig initialization with minimal parameters`() {
let config = MCPServerConfig(command: "test")
#expect(config.transport == "stdio")
@ -43,8 +42,8 @@ struct MCPClientTests {
#expect(config.description == nil)
}
@Test("MCPClient initialization")
func clientInit() {
@Test
func `MCPClient initialization`() {
let config = MCPServerConfig(
command: "test-command",
args: ["arg1"],
@ -54,8 +53,8 @@ struct MCPClientTests {
_ = MCPClient(name: "test-client", config: config)
}
@Test("MCPError descriptions")
func errorDescriptions() {
@Test
func `MCPError descriptions`() {
#expect(MCPError.serverDisabled.errorDescription == "MCP server is disabled")
#expect(MCPError.notConnected.errorDescription == "MCP client is not connected")
#expect(MCPError.invalidResponse.errorDescription == "Invalid response from MCP server")
@ -64,8 +63,8 @@ struct MCPClientTests {
#expect(MCPError.executionFailed("error").errorDescription == "Execution failed: error")
}
@Test("ToolArguments convenience methods")
func toolArgumentsConvenienceMethods() {
@Test
func `ToolArguments convenience methods`() {
let args = ToolArguments(raw: [
"text": "hello",
"number": 42,
@ -106,8 +105,8 @@ struct MCPClientTests {
#expect(emptyArgs.isEmpty == true)
}
@Test("ToolArguments from Value")
func toolArgumentsFromValue() {
@Test
func `ToolArguments from Value`() {
let value = Value.object([
"name": .string("test"),
"count": .int(42),
@ -121,8 +120,8 @@ struct MCPClientTests {
#expect(args.getBool("active") == true)
}
@Test("ToolArguments raw dictionary preserves nested structures")
func toolArgumentsRawDictionary() {
@Test
func `ToolArguments raw dictionary preserves nested structures`() {
let value = Value.object([
"text": .string("hello"),
"number": .int(5),
@ -167,8 +166,8 @@ struct MCPClientTests {
#expect(dictionary["none"] is NSNull)
}
@Test("ToolResponse creation methods")
func toolResponseCreation() {
@Test
func `ToolResponse creation methods`() {
// Text response
let textResponse = ToolResponse.text("Success")
#expect(textResponse.content.count == 1)
@ -207,8 +206,8 @@ struct MCPClientTests {
#expect(multiResponse.content.count == 2)
}
@Test("ToolResponse with metadata")
func toolResponseWithMetadata() {
@Test
func `ToolResponse with metadata`() {
let meta = Value.object([
"executionTime": .double(1.5),
"status": .string("ok"),
@ -225,15 +224,15 @@ struct MCPClientTests {
}
}
@Test("MCPToolProvider initialization")
func toolProviderInit() {
@Test
func `MCPToolProvider initialization`() {
let config = MCPServerConfig(command: "test")
let client = MCPClient(name: "test", config: config)
_ = MCPToolProvider(client: client)
}
@Test("MCPToolProvider as DynamicToolProvider")
func toolProviderAsDynamicToolProvider() {
@Test
func `MCPToolProvider as DynamicToolProvider`() {
let config = MCPServerConfig(command: "test")
let client = MCPClient(name: "test-server", config: config)
let provider = MCPToolProvider(client: client)
@ -242,8 +241,8 @@ struct MCPClientTests {
_ = provider as DynamicToolProvider
}
@Test("Tool metadata structure")
func toolMetadata() {
@Test
func `Tool metadata structure`() {
let tool = MCP.Tool(
name: "test-tool",
description: "A test tool",
@ -269,8 +268,8 @@ struct MCPClientTests {
}
}
@Test("Value encoding and decoding")
func valueEncodingDecoding() throws {
@Test
func `Value encoding and decoding`() throws {
let originalValue = Value.object([
"string": .string("test"),
"number": .int(42),
@ -292,8 +291,8 @@ struct MCPClientTests {
#expect(originalValue == decodedValue)
}
@Test("ToolArguments decoding")
func toolArgumentsDecoding() throws {
@Test
func `ToolArguments decoding`() throws {
struct TestArgs: Decodable {
let name: String
let count: Int
@ -341,10 +340,9 @@ private struct MockMCPTool: MCPTool {
}
}
@Suite("Mock Tool Tests")
struct MockToolTests {
@Test("Mock tool execution with valid arguments")
func mockToolValidExecution() async throws {
@Test
func `Mock tool execution with valid arguments`() async throws {
let tool = MockMCPTool()
let args = ToolArguments(raw: ["message": "Hello World"])
@ -358,8 +356,8 @@ struct MockToolTests {
}
}
@Test("Mock tool execution with missing arguments")
func mockToolMissingArguments() async throws {
@Test
func `Mock tool execution with missing arguments`() async throws {
let tool = MockMCPTool()
let args = ToolArguments(raw: [:])
@ -373,8 +371,8 @@ struct MockToolTests {
}
}
@Test("Mock tool schema validation")
func mockToolSchema() {
@Test
func `Mock tool schema validation`() {
let tool = MockMCPTool()
#expect(tool.name == "mock_tool")

View File

@ -3,10 +3,9 @@ import Tachikoma
import Testing
@testable import TachikomaMCP
@Suite("MCP Tool Adapter Tests")
struct MCPToolAdapterTests {
@Test("ToolArguments getString")
func toolArgumentsGetString() {
@Test
func `ToolArguments getString`() {
let args = ToolArguments(raw: [
"name": "Alice",
"count": 42,
@ -21,8 +20,8 @@ struct MCPToolAdapterTests {
#expect(args.getString("missing") == nil)
}
@Test("ToolArguments getNumber")
func toolArgumentsGetNumber() {
@Test
func `ToolArguments getNumber`() {
let args = ToolArguments(raw: [
"int": 42,
"double": 3.14,
@ -37,8 +36,8 @@ struct MCPToolAdapterTests {
#expect(args.getNumber("missing") == nil)
}
@Test("ToolArguments getInt")
func toolArgumentsGetInt() {
@Test
func `ToolArguments getInt`() {
let args = ToolArguments(raw: [
"int": 42,
"double": 3.14,
@ -53,8 +52,8 @@ struct MCPToolAdapterTests {
#expect(args.getInt("missing") == nil)
}
@Test("ToolArguments getBool")
func toolArgumentsGetBool() {
@Test
func `ToolArguments getBool`() {
let args = ToolArguments(raw: [
"bool": true,
"stringTrue": "true",
@ -75,8 +74,8 @@ struct MCPToolAdapterTests {
#expect(args.getBool("missing") == nil)
}
@Test("ToolArguments getStringArray")
func toolArgumentsGetStringArray() {
@Test
func `ToolArguments getStringArray`() {
let args = ToolArguments(raw: [
"array": ["a", "b", "c"],
"mixed": ["string", 123, true],
@ -89,8 +88,8 @@ struct MCPToolAdapterTests {
#expect(args.getStringArray("missing") == nil)
}
@Test("ToolArguments isEmpty")
func toolArgumentsIsEmpty() {
@Test
func `ToolArguments isEmpty`() {
let emptyArgs = ToolArguments(raw: [:])
#expect(emptyArgs.isEmpty == true)
@ -98,8 +97,8 @@ struct MCPToolAdapterTests {
#expect(nonEmptyArgs.isEmpty == false)
}
@Test("ToolResponse text creation")
func toolResponseText() {
@Test
func `ToolResponse text creation`() {
let response = ToolResponse.text("Hello, world!")
#expect(response.isError == false)
@ -111,8 +110,8 @@ struct MCPToolAdapterTests {
}
}
@Test("ToolResponse error creation")
func toolResponseError() {
@Test
func `ToolResponse error creation`() {
let response = ToolResponse.error("Something went wrong")
#expect(response.isError == true)
@ -124,8 +123,8 @@ struct MCPToolAdapterTests {
}
}
@Test("ToolResponse image creation")
func toolResponseImage() {
@Test
func `ToolResponse image creation`() {
let imageData = Data([0xFF, 0xD8, 0xFF]) // JPEG header
let response = ToolResponse.image(data: imageData, mimeType: "image/jpeg")

View File

@ -2,10 +2,9 @@ import MCP
import Testing
@testable import TachikomaMCP
@Suite("Schema Builder Tests")
struct SchemaBuilderTests {
@Test("Build string schema")
func stringSchema() {
@Test
func `Build string schema`() {
let schema = SchemaBuilder.string(
description: "User name",
enum: ["Alice", "Bob"],
@ -32,8 +31,8 @@ struct SchemaBuilderTests {
}
}
@Test("Build boolean schema")
func booleanSchema() {
@Test
func `Build boolean schema`() {
let schema = SchemaBuilder.boolean(
description: "Is active",
default: true,
@ -49,8 +48,8 @@ struct SchemaBuilderTests {
#expect(dict["default"] == .bool(true))
}
@Test("Build number schema")
func numberSchema() {
@Test
func `Build number schema`() {
let schema = SchemaBuilder.number(
description: "Temperature",
minimum: 0.0,
@ -70,8 +69,8 @@ struct SchemaBuilderTests {
#expect(dict["default"] == .double(25.0))
}
@Test("Build integer schema")
func integerSchema() {
@Test
func `Build integer schema`() {
let schema = SchemaBuilder.integer(
description: "Count",
minimum: 1,
@ -91,8 +90,8 @@ struct SchemaBuilderTests {
#expect(dict["default"] == .int(10))
}
@Test("Build array schema")
func arraySchema() {
@Test
func `Build array schema`() {
let itemSchema = SchemaBuilder.string()
let schema = SchemaBuilder.array(
items: itemSchema,
@ -115,8 +114,8 @@ struct SchemaBuilderTests {
#expect(dict["uniqueItems"] == .bool(true))
}
@Test("Build object schema")
func objectSchema() {
@Test
func `Build object schema`() {
let schema = SchemaBuilder.object(
properties: [
"name": SchemaBuilder.string(description: "User name"),
@ -151,8 +150,8 @@ struct SchemaBuilderTests {
}
}
@Test("Build nullable schema")
func testNullableSchema() {
@Test
func `Build nullable schema`() {
let baseSchema = SchemaBuilder.string(description: "Optional value")
let nullableSchema = SchemaBuilder.nullable(baseSchema)
@ -168,8 +167,8 @@ struct SchemaBuilderTests {
}
}
@Test("Build oneOf schema")
func oneOfSchema() {
@Test
func `Build oneOf schema`() {
let schemas = [
SchemaBuilder.string(),
SchemaBuilder.integer(),
@ -191,8 +190,8 @@ struct SchemaBuilderTests {
}
}
@Test("Build anyOf schema")
func anyOfSchema() {
@Test
func `Build anyOf schema`() {
let schemas = [
SchemaBuilder.string(),
SchemaBuilder.integer(),

View File

@ -4,10 +4,9 @@ import Testing
@testable import Tachikoma
@testable import TachikomaMCP
@Suite("Type Conversion Tests")
struct TypeConversionTests {
@Test("AnyAgentToolValue to JSON conversion")
func anyAgentToolValueToJSON() throws {
@Test
func `AnyAgentToolValue to JSON conversion`() throws {
// String
let stringVal = AnyAgentToolValue(string: "hello")
let stringJSON = try stringVal.toJSON()
@ -50,8 +49,8 @@ struct TypeConversionTests {
#expect(objectJSON?["count"] as? Int == 5)
}
@Test("Any to AnyAgentToolValue conversion")
func anyToAnyAgentToolValue() throws {
@Test
func `Any to AnyAgentToolValue conversion`() {
// String
let stringVal = AnyAgentToolValue.from("hello")
#expect(stringVal.stringValue == "hello")
@ -99,8 +98,8 @@ struct TypeConversionTests {
#expect(dateVal.stringValue != nil)
}
@Test("AnyAgentToolValue to Value conversion")
func anyAgentToolValueToValue() {
@Test
func `AnyAgentToolValue to Value conversion`() {
// String
let stringVal = AnyAgentToolValue(string: "hello")
let stringValue = stringVal.toValue()
@ -151,8 +150,8 @@ struct TypeConversionTests {
}
}
@Test("Value to AnyAgentToolValue conversion")
func valueToAnyAgentToolValue() {
@Test
func `Value to AnyAgentToolValue conversion`() {
// String
let stringValue = Value.string("hello")
let stringVal = stringValue.toAnyAgentToolValue()
@ -203,8 +202,8 @@ struct TypeConversionTests {
}
}
@Test("ToolArguments initialization from AgentToolArguments")
func toolArgumentsFromAgentToolArguments() throws {
@Test
func `ToolArguments initialization from AgentToolArguments`() {
let agentArgs = AgentToolArguments([
"text": AnyAgentToolValue(string: "hello"),
"number": AnyAgentToolValue(int: 42),
@ -229,8 +228,8 @@ struct TypeConversionTests {
}
}
@Test("ToolResponse to AnyAgentToolValue conversion via toAgentToolResult")
func toolResponseToAgentToolResult() {
@Test
func `ToolResponse to AnyAgentToolValue conversion via toAgentToolResult`() {
// Text response
let textResponse = ToolResponse.text("Success message")
let textResult = textResponse.toAgentToolResult()
@ -257,8 +256,8 @@ struct TypeConversionTests {
#expect(emptyResult.stringValue == "Success")
}
@Test("ToolResponse to AnyAgentToolValue conversion")
func toolResponseToAnyAgentToolValue() {
@Test
func `ToolResponse to AnyAgentToolValue conversion`() {
// Single text content
let textResponse = ToolResponse.text("Hello")
let textVal = textResponse.toAnyAgentToolValue()
@ -298,7 +297,7 @@ struct TypeConversionTests {
"content",
uri: "https://example.com",
mimeType: "text/html",
)
),
),
])
let resourceVal = resourceResponse.toAnyAgentToolValue()
@ -334,8 +333,8 @@ struct TypeConversionTests {
}
}
@Test("Round-trip conversions")
func roundTripConversions() throws {
@Test
func `Round-trip conversions`() throws {
// AnyAgentToolValue -> Value -> AnyAgentToolValue
let originalVal = AnyAgentToolValue(object: [
"string": AnyAgentToolValue(string: "test"),

View File

@ -2,12 +2,11 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("AgentToolValue Protocol System Tests")
struct AgentToolValueTests {
// MARK: - Basic Type Conformance Tests
@Test("String conforms to AgentToolValue")
func stringConformance() throws {
@Test
func `String conforms to AgentToolValue`() throws {
let value = "Hello, World!"
let json = try value.toJSON()
#expect(json as? String == "Hello, World!")
@ -17,8 +16,8 @@ struct AgentToolValueTests {
#expect(String.agentValueType == .string)
}
@Test("Int conforms to AgentToolValue")
func intConformance() throws {
@Test
func `Int conforms to AgentToolValue`() throws {
let value = 42
let json = try value.toJSON()
#expect(json as? Int == 42)
@ -32,8 +31,8 @@ struct AgentToolValueTests {
#expect(fromDouble == 42)
}
@Test("Double conforms to AgentToolValue")
func doubleConformance() throws {
@Test
func `Double conforms to AgentToolValue`() throws {
let value = 3.141_59
let json = try value.toJSON()
#expect(json as? Double == 3.141_59)
@ -47,8 +46,8 @@ struct AgentToolValueTests {
#expect(fromInt == 42.0)
}
@Test("Bool conforms to AgentToolValue")
func boolConformance() throws {
@Test
func `Bool conforms to AgentToolValue`() throws {
let value = true
let json = try value.toJSON()
#expect(json as? Bool == true)
@ -58,8 +57,8 @@ struct AgentToolValueTests {
#expect(Bool.agentValueType == .boolean)
}
@Test("AgentNullValue works correctly")
func testNullValue() throws {
@Test
func `AgentNullValue works correctly`() throws {
let value = AgentNullValue()
let json = try value.toJSON()
#expect(json is NSNull)
@ -69,8 +68,8 @@ struct AgentToolValueTests {
#expect(AgentNullValue.agentValueType == .null)
}
@Test("Array conforms to AgentToolValue")
func arrayConformance() throws {
@Test
func `Array conforms to AgentToolValue`() throws {
let value = ["apple", "banana", "cherry"]
let json = try value.toJSON()
let jsonArray = json as? [Any]
@ -82,8 +81,8 @@ struct AgentToolValueTests {
#expect([String].agentValueType == .array)
}
@Test("Dictionary conforms to AgentToolValue")
func dictionaryConformance() throws {
@Test
func `Dictionary conforms to AgentToolValue`() throws {
let value = ["name": "John", "city": "NYC"]
let json = try value.toJSON()
let jsonDict = json as? [String: Any]
@ -97,8 +96,8 @@ struct AgentToolValueTests {
// MARK: - AnyAgentToolValue Tests
@Test("AnyAgentToolValue wraps basic types")
func anyAgentToolValueBasicTypes() throws {
@Test
func `AnyAgentToolValue wraps basic types`() {
// String
let stringValue = AnyAgentToolValue(string: "test")
#expect(stringValue.stringValue == "test")
@ -126,8 +125,8 @@ struct AgentToolValueTests {
#expect(nullValue.stringValue == nil)
}
@Test("AnyAgentToolValue wraps complex types")
func anyAgentToolValueComplexTypes() throws {
@Test
func `AnyAgentToolValue wraps complex types`() {
// Array
let array = [
AnyAgentToolValue(string: "a"),
@ -152,8 +151,8 @@ struct AgentToolValueTests {
#expect(objectValue.objectValue?["active"]?.boolValue == true)
}
@Test("AnyAgentToolValue JSON conversion")
func anyAgentToolValueJSON() throws {
@Test
func `AnyAgentToolValue JSON conversion`() throws {
// Test fromJSON with various types
let stringValue = try AnyAgentToolValue.fromJSON("hello")
#expect(stringValue.stringValue == "hello")
@ -180,8 +179,8 @@ struct AgentToolValueTests {
#expect(dictValue.objectValue?["num"]?.intValue == 123)
}
@Test("AnyAgentToolValue Codable conformance")
func anyAgentToolValueCodable() throws {
@Test
func `AnyAgentToolValue Codable conformance`() throws {
struct TestContainer: Codable {
let value: AnyAgentToolValue
}
@ -223,8 +222,8 @@ struct AgentToolValueTests {
// MARK: - AgentToolCall and AgentToolResult Tests
@Test("AgentToolCall uses AnyAgentToolValue")
func agentToolCall() throws {
@Test
func `AgentToolCall uses AnyAgentToolValue`() {
let arguments = [
"prompt": AnyAgentToolValue(string: "Hello"),
"temperature": AnyAgentToolValue(double: 0.7),
@ -244,8 +243,8 @@ struct AgentToolValueTests {
#expect(toolCall.arguments["maxTokens"]?.intValue == 100)
}
@Test("AgentToolCall legacy init with Any")
func agentToolCallLegacyInit() throws {
@Test
func `AgentToolCall legacy init with Any`() throws {
let arguments: [String: Any] = [
"text": "Hello",
"count": 42,
@ -263,8 +262,8 @@ struct AgentToolValueTests {
#expect(toolCall.arguments["enabled"]?.boolValue == true)
}
@Test("AgentToolResult uses AnyAgentToolValue")
func agentToolResult() {
@Test
func `AgentToolResult uses AnyAgentToolValue`() {
let successResult = AgentToolResult.success(
toolCallId: "call_123",
result: AnyAgentToolValue(string: "Success!"),
@ -286,8 +285,8 @@ struct AgentToolValueTests {
// MARK: - AgentToolArguments Tests
@Test("AgentToolArguments accessor methods")
func agentToolArgumentsAccessors() throws {
@Test
func `AgentToolArguments accessor methods`() throws {
let args = AgentToolArguments([
"string": AnyAgentToolValue(string: "text"),
"number": AnyAgentToolValue(double: 42.5),
@ -323,8 +322,8 @@ struct AgentToolValueTests {
#expect(args.optionalBooleanValue("missing") == nil)
}
@Test("AgentToolArguments error handling")
func agentToolArgumentsErrors() throws {
@Test
func `AgentToolArguments error handling`() throws {
let args = AgentToolArguments([
"text": AnyAgentToolValue(string: "hello"),
])
@ -342,15 +341,17 @@ struct AgentToolValueTests {
// MARK: - Type-Safe Tool Protocol Tests
@Test("AgentToolProtocol implementation")
func agentToolProtocol() async throws {
@Test
func `AgentToolProtocol implementation`() async throws {
// Define a concrete tool
struct WeatherTool: AgentToolProtocol {
struct Input: AgentToolValue, Equatable {
let location: String
let units: String
static var agentValueType: AgentValueType { .object }
static var agentValueType: AgentValueType {
.object
}
func toJSON() throws -> Any {
["location": self.location, "units": self.units]
@ -372,7 +373,9 @@ struct AgentToolValueTests {
let temperature: Double
let conditions: String
static var agentValueType: AgentValueType { .object }
static var agentValueType: AgentValueType {
.object
}
func toJSON() throws -> Any {
["temperature": self.temperature, "conditions": self.conditions]
@ -390,8 +393,14 @@ struct AgentToolValueTests {
}
}
var name: String { "get_weather" }
var description: String { "Get current weather" }
var name: String {
"get_weather"
}
var description: String {
"Get current weather"
}
var schema: AgentToolSchema {
AgentToolSchema(
properties: [
@ -434,8 +443,8 @@ struct AgentToolValueTests {
// MARK: - Edge Cases and Error Handling
@Test("Handle integer/double ambiguity")
func integerDoubleAmbiguity() throws {
@Test
func `Handle integer/double ambiguity`() throws {
// Test that whole numbers can be treated as integers
let wholeDouble = try AnyAgentToolValue.fromJSON(42.0)
#expect(wholeDouble.intValue == 42)
@ -452,8 +461,8 @@ struct AgentToolValueTests {
#expect(largeInt.intValue == safeInteger)
}
@Test("Handle nested structures")
func nestedStructures() throws {
@Test
func `Handle nested structures`() throws {
let nested = [
"level1": [
"level2": [
@ -469,8 +478,8 @@ struct AgentToolValueTests {
#expect(level3?["value"]?.stringValue == "deep")
}
@Test("Handle mixed-type arrays")
func mixedTypeArrays() throws {
@Test
func `Handle mixed-type arrays`() throws {
let mixed: [Any] = ["string", 42, true, NSNull(), ["nested": "object"]]
let value = try AnyAgentToolValue.fromJSON(mixed)

View File

@ -2,16 +2,15 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Async Ergonomics Tests")
struct AsyncErgonomicsTests {
@Test("Timeout error description")
func timeoutErrorDescription() throws {
@Test
func `Timeout error description`() {
let error = TimeoutError(timeout: 5.5)
#expect(error.errorDescription == "Operation timed out after 5.5 seconds")
}
@Test("Cancellation token basic operations")
func cancellationTokenBasic() async throws {
@Test
func `Cancellation token basic operations`() async {
let token = CancellationToken()
#expect(await token.cancelled == false)
@ -25,8 +24,8 @@ struct AsyncErgonomicsTests {
#expect(await token.cancelled == true)
}
@Test("Cancellation token with handlers")
func cancellationTokenHandlers() async throws {
@Test
func `Cancellation token with handlers`() async throws {
let token = CancellationToken()
class Flag: @unchecked Sendable {
@ -49,8 +48,8 @@ struct AsyncErgonomicsTests {
#expect(flag.value == true)
}
@Test("Retry configuration defaults")
func retryConfigurationDefaults() throws {
@Test
func `Retry configuration defaults`() {
let config = RetryConfiguration.default
#expect(config.maxAttempts == 3)
@ -60,8 +59,8 @@ struct AsyncErgonomicsTests {
#expect(config.timeout == nil)
}
@Test("Retry configuration presets")
func retryConfigurationPresets() throws {
@Test
func `Retry configuration presets`() {
let aggressive = RetryConfiguration.aggressive
#expect(aggressive.maxAttempts == 5)
#expect(aggressive.delay == 0.5)
@ -71,8 +70,8 @@ struct AsyncErgonomicsTests {
#expect(conservative.delay == 2.0)
}
@Test("Retry with cancellation - immediate success")
func retryWithCancellationImmediateSuccess() async throws {
@Test
func `Retry with cancellation - immediate success`() async throws {
let result = try await retryWithCancellation(
configuration: .init(maxAttempts: 3, delay: 0.01),
) {
@ -82,8 +81,8 @@ struct AsyncErgonomicsTests {
#expect(result == "Success")
}
@Test("With timeout basic functionality")
func withTimeoutBasic() async throws {
@Test
func `With timeout basic functionality`() async throws {
let result = try await withTimeout(0.1) {
"Quick result"
}
@ -91,8 +90,8 @@ struct AsyncErgonomicsTests {
#expect(result == "Quick result")
}
@Test("With timeout throws on timeout")
func withTimeoutThrows() async throws {
@Test
func `With timeout throws on timeout`() async throws {
do {
_ = try await withTimeout(0.01) {
try await Task<Never, Never>.sleep(nanoseconds: 1_000_000_000) // 1 second
@ -104,8 +103,8 @@ struct AsyncErgonomicsTests {
}
}
@Test("Async stream collect basic")
func asyncStreamCollectBasic() async throws {
@Test
func `Async stream collect basic`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
continuation.yield(1)
continuation.yield(2)
@ -117,8 +116,8 @@ struct AsyncErgonomicsTests {
#expect(results == [1, 2, 3])
}
@Test("Task group with auto cancellation")
func taskGroupAutoCancellation() async throws {
@Test
func `Task group with auto cancellation`() async throws {
class Flag: @unchecked Sendable {
var cancelled = false
}

View File

@ -3,14 +3,12 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAudio
@Suite("Audio Functions Tests")
struct AudioFunctionsTests {
enum AudioFunctionsTests {
// MARK: - Basic Transcription Function Tests
@Suite("Basic Transcription Functions Tests")
struct BasicTranscriptionFunctionsTests {
@Test("transcribe() convenience function works")
func transcribeConvenienceFunctionWorks() async throws {
@Test
func `transcribe() convenience function works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -20,8 +18,8 @@ struct AudioFunctionsTests {
}
}
@Test("transcribe() with full model specification works")
func transcribeWithFullModelSpecificationWorks() async throws {
@Test
func `transcribe() with full model specification works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -43,8 +41,8 @@ struct AudioFunctionsTests {
}
}
@Test("transcribe() from file URL works")
func transcribeFromFileURLWorks() async throws {
@Test
func `transcribe() from file URL works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -68,8 +66,8 @@ struct AudioFunctionsTests {
}
}
@Test("transcribe() with timestamps")
func transcribeWithTimestamps() async throws {
@Test
func `transcribe() with timestamps`() async {
await TestHelpers.withMockProviderEnvironment {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -95,8 +93,8 @@ struct AudioFunctionsTests {
}
}
@Test("transcribe() with abort signal")
func transcribeWithAbortSignal() async throws {
@Test
func `transcribe() with abort signal`() async {
await TestHelpers.withMockProviderEnvironment {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -120,10 +118,9 @@ struct AudioFunctionsTests {
// MARK: - Basic Speech Generation Function Tests
@Suite("Basic Speech Generation Functions Tests")
struct BasicSpeechGenerationFunctionsTests {
@Test("generateSpeech() convenience function works")
func generateSpeechConvenienceFunctionWorks() async throws {
@Test
func `generateSpeech() convenience function works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = try await generateSpeech("Hello, world!", configuration: config)
@ -133,8 +130,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeech() with voice selection")
func generateSpeechWithVoiceSelection() async throws {
@Test
func `generateSpeech() with voice selection`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = try await generateSpeech("Hello, world!", voice: .nova, configuration: config)
@ -144,8 +141,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeech() with full model specification works")
func generateSpeechWithFullModelSpecificationWorks() async throws {
@Test
func `generateSpeech() with full model specification works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let result = try await generateSpeech(
@ -162,8 +159,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeech() direct to file using convenience function")
func generateSpeechDirectToFile() async throws {
@Test
func `generateSpeech() direct to file using convenience function`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let tempDir = FileManager.default.temporaryDirectory
@ -187,8 +184,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeech() with abort signal")
func generateSpeechWithAbortSignal() async throws {
@Test
func `generateSpeech() with abort signal`() async {
await TestHelpers.withMockProviderEnvironment {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let abortSignal = AbortSignal()
@ -211,10 +208,9 @@ struct AudioFunctionsTests {
// MARK: - Batch Operations Tests
@Suite("Batch Operations Tests")
struct BatchOperationsTests {
@Test("transcribeBatch() function works")
func transcribeBatchFunctionWorks() async throws {
@Test
func `transcribeBatch() function works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)
@ -246,8 +242,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeechBatch() function works")
func generateSpeechBatchFunctionWorks() async throws {
@Test
func `generateSpeechBatch() function works`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let texts = ["Hello", "World"]
@ -267,24 +263,23 @@ struct AudioFunctionsTests {
// MARK: - Utility Functions Tests
@Suite("Utility Functions Tests")
struct UtilityFunctionsTests {
@Test("availableTranscriptionModels() returns models")
func availableTranscriptionModelsReturnsModels() {
@Test
func `availableTranscriptionModels() returns models`() {
let models = availableTranscriptionModels()
#expect(!models.isEmpty)
#expect(models.contains { $0.description == TranscriptionModel.openai(.whisper1).description })
}
@Test("availableSpeechModels() returns models")
func availableSpeechModelsReturnsModels() {
@Test
func `availableSpeechModels() returns models`() {
let models = availableSpeechModels()
#expect(!models.isEmpty)
#expect(models.contains { $0.description == SpeechModel.openai(.tts1).description })
}
@Test("capabilities() for transcription models")
func capabilitiesForTranscriptionModels() throws {
@Test
func `capabilities() for transcription models`() throws {
let config = TestHelpers.createTestConfiguration(apiKeys: ["openai": "test-key"])
let capabilities = try capabilities(for: TranscriptionModel.openai(.whisper1), configuration: config)
#expect(capabilities.supportsTimestamps == true)
@ -292,8 +287,8 @@ struct AudioFunctionsTests {
#expect(capabilities.supportedFormats.contains(.wav))
}
@Test("capabilities() for speech models")
func capabilitiesForSpeechModels() throws {
@Test
func `capabilities() for speech models`() throws {
let config = TestHelpers.createTestConfiguration(apiKeys: ["openai": "test-key"])
let capabilities = try capabilities(for: SpeechModel.openai(.tts1), configuration: config)
#expect(capabilities.supportsSpeedControl == true)
@ -304,10 +299,9 @@ struct AudioFunctionsTests {
// MARK: - Error Handling Tests
@Suite("Error Handling Tests")
struct ErrorHandlingTests {
@Test("transcribe() handles empty audio data")
func transcribeHandlesEmptyAudioData() async throws {
@Test
func `transcribe() handles empty audio data`() async {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let emptyAudioData = AudioData(data: Data(), format: .wav)
@ -317,8 +311,8 @@ struct AudioFunctionsTests {
}
}
@Test("generateSpeech() handles empty text")
func generateSpeechHandlesEmptyText() async throws {
@Test
func `generateSpeech() handles empty text`() async {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
await #expect(throws: TachikomaError.self) {
_ = try await generateSpeech("", using: .openai(.tts1), configuration: config)
@ -326,8 +320,8 @@ struct AudioFunctionsTests {
}
}
@Test("functions handle missing API keys")
func functionsHandleMissingAPIKeys() async throws {
@Test
func `functions handle missing API keys`() async {
guard TestHelpers.isMockAPIKey(ProcessInfo.processInfo.environment["OPENAI_API_KEY"]) else { return }
await TestHelpers.withEmptyTestConfiguration { config in
let audioData = AudioData(data: Data([0x01, 0x02]), format: .wav)
@ -345,10 +339,9 @@ struct AudioFunctionsTests {
// MARK: - Integration Tests
@Suite("Integration Tests")
struct IntegrationTests {
@Test("transcribe and generate speech pipeline")
func transcribeAndGenerateSpeechPipeline() async throws {
@Test
func `transcribe and generate speech pipeline`() async {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let originalAudioData = TestHelpers.sampleAudioData(configuration: config)
@ -368,8 +361,8 @@ struct AudioFunctionsTests {
}
}
@Test("multiple provider integration")
func multipleProviderIntegration() async throws {
@Test
func `multiple provider integration`() async throws {
try await TestHelpers.withStandardTestConfiguration { config in
let audioData = TestHelpers.sampleAudioData(configuration: config)

View File

@ -7,10 +7,10 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAudio
@Suite("Audio Provider Factories", .serialized)
@Suite(.serialized)
struct AudioProviderFactoryTests {
@Test("TranscriptionProviderFactory returns mock provider in test mode")
func transcriptionFactoryReturnsMockInTestMode() throws {
@Test
func `TranscriptionProviderFactory returns mock provider in test mode`() throws {
let previousTestMode = getenv("TACHIKOMA_TEST_MODE").flatMap { String(cString: $0) }
setenv("TACHIKOMA_TEST_MODE", "mock", 1)
defer {
@ -32,8 +32,8 @@ struct AudioProviderFactoryTests {
#expect(provider is MockTranscriptionProvider)
}
@Test("TranscriptionProviderFactory requires API key in test mode")
func transcriptionFactoryRequiresAPIKeyInTestMode() {
@Test
func `TranscriptionProviderFactory requires API key in test mode`() {
let previousTestMode = getenv("TACHIKOMA_TEST_MODE").flatMap { String(cString: $0) }
setenv("TACHIKOMA_TEST_MODE", "mock", 1)
defer {
@ -57,8 +57,8 @@ struct AudioProviderFactoryTests {
}
}
@Test("SpeechProviderFactory returns mock provider in test mode")
func speechFactoryReturnsMockInTestMode() throws {
@Test
func `SpeechProviderFactory returns mock provider in test mode`() throws {
let previousTestMode = getenv("TACHIKOMA_TEST_MODE").flatMap { String(cString: $0) }
setenv("TACHIKOMA_TEST_MODE", "mock", 1)
defer {
@ -80,8 +80,8 @@ struct AudioProviderFactoryTests {
#expect(provider is MockSpeechProvider)
}
@Test("AudioConfiguration reads configuration keys before environment")
func audioConfigurationPrefersExplicitConfiguration() {
@Test
func `AudioConfiguration reads configuration keys before environment`() {
let previousOpenAIKey = getenv("OPENAI_API_KEY").flatMap { String(cString: $0) }
unsetenv("OPENAI_API_KEY")
defer {
@ -97,8 +97,8 @@ struct AudioProviderFactoryTests {
#expect(resolvedKey == "configured-key")
}
@Test("AudioConfiguration falls back to environment variable")
func audioConfigurationFallsBackToEnvironment() {
@Test
func `AudioConfiguration falls back to environment variable`() {
let previousOpenAIKey = getenv("OPENAI_API_KEY").flatMap { String(cString: $0) }
setenv("OPENAI_API_KEY", "env-key-123", 1)
defer {

View File

@ -3,14 +3,12 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAudio
@Suite("Audio Types Tests")
struct AudioTypesTests {
// MARK: - AudioData Tests
@Suite("AudioData Tests")
struct AudioDataTests {
@Test("AudioData initialization with basic properties")
func audioDataBasicInit() {
@Test
func `AudioData initialization with basic properties`() {
let testData = Data([0x01, 0x02, 0x03, 0x04])
let audioData = AudioData(
data: testData,
@ -28,8 +26,8 @@ struct AudioTypesTests {
#expect(audioData.size == 4)
}
@Test("AudioData initialization with defaults")
func audioDataDefaultInit() {
@Test
func `AudioData initialization with defaults`() {
let testData = Data([0x01, 0x02])
let audioData = AudioData(data: testData)
@ -41,8 +39,8 @@ struct AudioTypesTests {
#expect(audioData.size == 2)
}
@Test("AudioData file URL initialization")
func audioDataFileInit() throws {
@Test
func `AudioData file URL initialization`() throws {
// Create a temporary file
let tempDir = FileManager.default.temporaryDirectory
let testFile = tempDir.appendingPathComponent("test_audio-\(UUID().uuidString).mp3")
@ -61,8 +59,8 @@ struct AudioTypesTests {
try? FileManager.default.removeItem(at: testFile)
}
@Test("AudioData file URL with unknown extension")
func audioDataUnknownExtension() throws {
@Test
func `AudioData file URL with unknown extension`() throws {
let tempDir = FileManager.default.temporaryDirectory
let testFile = tempDir.appendingPathComponent("test_audio-\(UUID().uuidString).unknown")
let testData = Data([0x01, 0x02])
@ -76,8 +74,8 @@ struct AudioTypesTests {
try? FileManager.default.removeItem(at: testFile)
}
@Test("AudioData write to file")
func audioDataWrite() throws {
@Test
func `AudioData write to file`() throws {
let testData = Data([0x01, 0x02, 0x03, 0x04])
let audioData = AudioData(data: testData, format: .flac)
@ -96,10 +94,9 @@ struct AudioTypesTests {
// MARK: - AudioFormat Tests
@Suite("AudioFormat Tests")
struct AudioFormatTests {
@Test("AudioFormat MIME types")
func audioFormatMimeTypes() {
@Test
func `AudioFormat MIME types`() {
#expect(AudioFormat.wav.mimeType == "audio/wav")
#expect(AudioFormat.mp3.mimeType == "audio/mpeg")
#expect(AudioFormat.flac.mimeType == "audio/flac")
@ -110,8 +107,8 @@ struct AudioTypesTests {
#expect(AudioFormat.ogg.mimeType == "audio/ogg")
}
@Test("AudioFormat lossless property")
func audioFormatLossless() {
@Test
func `AudioFormat lossless property`() {
// Lossless formats
#expect(AudioFormat.wav.isLossless == true)
#expect(AudioFormat.flac.isLossless == true)
@ -125,8 +122,8 @@ struct AudioTypesTests {
#expect(AudioFormat.ogg.isLossless == false)
}
@Test("AudioFormat all cases completeness")
func audioFormatAllCases() {
@Test
func `AudioFormat all cases completeness`() {
let allCases = AudioFormat.allCases
let expectedCases: [AudioFormat] = [.wav, .mp3, .flac, .opus, .m4a, .aac, .pcm, .ogg]
@ -139,10 +136,9 @@ struct AudioTypesTests {
// MARK: - VoiceOption Tests
@Suite("VoiceOption Tests")
struct VoiceOptionTests {
@Test("VoiceOption string values")
func voiceOptionStringValues() {
@Test
func `VoiceOption string values`() {
#expect(VoiceOption.alloy.stringValue == "alloy")
#expect(VoiceOption.echo.stringValue == "echo")
#expect(VoiceOption.fable.stringValue == "fable")
@ -154,8 +150,8 @@ struct AudioTypesTests {
#expect(customVoice.stringValue == "my-custom-voice")
}
@Test("VoiceOption defaults and recommendations")
func voiceOptionDefaults() {
@Test
func `VoiceOption defaults and recommendations`() {
#expect(VoiceOption.default == .alloy)
let femaleVoices = VoiceOption.female
@ -171,8 +167,8 @@ struct AudioTypesTests {
#expect(maleVoices.count == 3)
}
@Test("VoiceOption hashable and equatable")
func voiceOptionHashableEquatable() {
@Test
func `VoiceOption hashable and equatable`() {
let voice1 = VoiceOption.alloy
let voice2 = VoiceOption.alloy
let voice3 = VoiceOption.echo
@ -191,10 +187,9 @@ struct AudioTypesTests {
// MARK: - Result Types Tests
@Suite("Result Types Tests")
struct ResultTypesTests {
@Test("TranscriptionResult initialization")
func transcriptionResultInit() {
@Test
func `TranscriptionResult initialization`() {
let segments = [
TranscriptionSegment(text: "Hello", start: 0.0, end: 1.0),
TranscriptionSegment(text: "world", start: 1.0, end: 2.0),
@ -218,14 +213,14 @@ struct AudioTypesTests {
#expect(result.warnings?.first == "Test warning")
}
@Test("TranscriptionSegment duration calculation")
func transcriptionSegmentDuration() {
@Test
func `TranscriptionSegment duration calculation`() {
let segment = TranscriptionSegment(text: "test", start: 1.5, end: 3.7)
#expect(segment.duration == 2.2)
}
@Test("SpeechResult initialization")
func speechResultInit() {
@Test
func `SpeechResult initialization`() {
let audioData = AudioData(data: Data([0x01, 0x02]), format: .mp3)
let usage = SpeechUsage(charactersProcessed: 10, cost: 0.05)
@ -243,10 +238,9 @@ struct AudioTypesTests {
// MARK: - Request Types Tests
@Suite("Request Types Tests")
struct RequestTypesTests {
@Test("TranscriptionRequest initialization")
func transcriptionRequestInit() {
@Test
func `TranscriptionRequest initialization`() {
let audioData = AudioData(data: Data([0x01]), format: .wav)
let abortSignal = AbortSignal()
@ -269,8 +263,8 @@ struct AudioTypesTests {
#expect(request.headers["Custom-Header"] == "value")
}
@Test("SpeechRequest initialization")
func speechRequestInit() {
@Test
func `SpeechRequest initialization`() {
let abortSignal = AbortSignal()
let request = SpeechRequest(
@ -293,8 +287,8 @@ struct AudioTypesTests {
#expect(request.headers["API-Version"] == "v1")
}
@Test("Request default values")
func requestDefaults() {
@Test
func `Request default values`() {
let audioData = AudioData(data: Data(), format: .wav)
let transcriptionRequest = TranscriptionRequest(audio: audioData)
@ -317,10 +311,9 @@ struct AudioTypesTests {
// MARK: - AbortSignal Tests
@Suite("AbortSignal Tests")
struct AbortSignalTests {
@Test("AbortSignal basic functionality")
func abortSignalBasic() {
@Test
func `AbortSignal basic functionality`() {
let signal = AbortSignal()
#expect(signal.cancelled == false)
@ -330,8 +323,8 @@ struct AudioTypesTests {
#expect(signal.cancelled == true)
}
@Test("AbortSignal throwIfCancelled")
func abortSignalThrowIfCancelled() throws {
@Test
func `AbortSignal throwIfCancelled`() throws {
let signal = AbortSignal()
// Should not throw when not cancelled
@ -345,8 +338,8 @@ struct AudioTypesTests {
}
}
@Test("AbortSignal timeout functionality")
func abortSignalTimeout() async throws {
@Test
func `AbortSignal timeout functionality`() async throws {
let signal = AbortSignal.timeout(0.1) // 100ms timeout
#expect(signal.cancelled == false)
@ -357,8 +350,8 @@ struct AudioTypesTests {
#expect(signal.cancelled == true)
}
@Test("AbortSignal thread safety")
func abortSignalThreadSafety() async {
@Test
func `AbortSignal thread safety`() async {
let signal = AbortSignal()
// Start multiple concurrent tasks that check and cancel the signal
@ -379,8 +372,8 @@ struct AudioTypesTests {
// MARK: - Enum Tests
@Test("TimestampGranularity enum")
func timestampGranularityEnum() {
@Test
func `TimestampGranularity enum`() {
#expect(TimestampGranularity.word.rawValue == "word")
#expect(TimestampGranularity.segment.rawValue == "segment")
@ -390,8 +383,8 @@ struct AudioTypesTests {
#expect(allCases.count == 2)
}
@Test("TranscriptionResponseFormat enum")
func transcriptionResponseFormatEnum() {
@Test
func `TranscriptionResponseFormat enum`() {
#expect(TranscriptionResponseFormat.json.rawValue == "json")
#expect(TranscriptionResponseFormat.text.rawValue == "text")
#expect(TranscriptionResponseFormat.srt.rawValue == "srt")
@ -406,8 +399,8 @@ struct AudioTypesTests {
// MARK: - Error Types Tests
@Test("Audio error types")
func audioErrorTypes() {
@Test
func `Audio error types`() {
let operationCancelled = TachikomaError.operationCancelled
let noAudioData = TachikomaError.noAudioData
let unsupportedFormat = TachikomaError.unsupportedAudioFormat

View File

@ -3,14 +3,13 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAudio
@Suite("OpenAI Audio Provider Tests", .serialized)
@Suite(.serialized)
struct OpenAIAudioProviderTests {
// MARK: - OpenAI Transcription Provider Tests
@Suite("OpenAI Transcription Provider Tests")
struct OpenAITranscriptionProviderTests {
@Test("OpenAI transcription provider initialization")
func openAITranscriptionProviderInit() async throws {
@Test
func `OpenAI transcription provider initialization`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-api-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -25,8 +24,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider initialization fails without API key")
func openAITranscriptionProviderInitFailsWithoutAPIKey() async throws {
@Test
func `OpenAI transcription provider initialization fails without API key`() async {
guard TestHelpers.isMockAPIKey(ProcessInfo.processInfo.environment["OPENAI_API_KEY"]) else { return }
await TestHelpers.withEmptyTestConfiguration { config in
#expect(throws: TachikomaError.self) {
@ -35,8 +34,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider different models")
func openAITranscriptionProviderDifferentModels() async throws {
@Test
func `OpenAI transcription provider different models`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let whisperProvider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -52,8 +51,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider supported formats")
func openAITranscriptionProviderSupportedFormats() async throws {
@Test
func `OpenAI transcription provider supported formats`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -69,8 +68,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider transcribe function")
func openAITranscriptionProviderTranscribe() async throws {
@Test
func `OpenAI transcription provider transcribe function`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -90,8 +89,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider with timestamps")
func openAITranscriptionProviderTimestamps() async throws {
@Test
func `OpenAI transcription provider with timestamps`() async throws {
try await TestHelpers.withMockProviderEnvironment {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
@ -113,8 +112,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider with abort signal")
func openAITranscriptionProviderAbortSignal() async throws {
@Test
func `OpenAI transcription provider with abort signal`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -133,8 +132,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI transcription provider request validation")
func openAITranscriptionProviderRequestValidation() async throws {
@Test
func `OpenAI transcription provider request validation`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -163,10 +162,9 @@ struct OpenAIAudioProviderTests {
// MARK: - OpenAI Speech Provider Tests
@Suite("OpenAI Speech Provider Tests")
struct OpenAISpeechProviderTests {
@Test("OpenAI speech provider initialization")
func openAISpeechProviderInit() async throws {
@Test
func `OpenAI speech provider initialization`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-api-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
@ -177,8 +175,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider initialization fails without API key")
func openAISpeechProviderInitFailsWithoutAPIKey() async throws {
@Test
func `OpenAI speech provider initialization fails without API key`() async {
guard TestHelpers.isMockAPIKey(ProcessInfo.processInfo.environment["OPENAI_API_KEY"]) else { return }
await TestHelpers.withEmptyTestConfiguration { config in
#expect(throws: TachikomaError.self) {
@ -187,8 +185,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider different models")
func openAISpeechProviderDifferentModels() async throws {
@Test
func `OpenAI speech provider different models`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let tts1Provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
let tts1HDProvider = try SpeechProviderFactory.createProvider(
@ -211,8 +209,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider supported voices")
func openAISpeechProviderSupportedVoices() async throws {
@Test
func `OpenAI speech provider supported voices`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
@ -223,8 +221,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider generate speech function")
func openAISpeechProviderGenerateSpeech() async throws {
@Test
func `OpenAI speech provider generate speech function`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
let request = SpeechRequest(
@ -244,8 +242,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider with different voices")
func openAISpeechProviderDifferentVoices() async throws {
@Test
func `OpenAI speech provider with different voices`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
@ -269,8 +267,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider with speed control")
func openAISpeechProviderSpeedControl() async throws {
@Test
func `OpenAI speech provider with speed control`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
@ -295,8 +293,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider with voice instructions")
func openAISpeechProviderVoiceInstructions() async throws {
@Test
func `OpenAI speech provider with voice instructions`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1HD), configuration: config)
let request = SpeechRequest(
@ -315,8 +313,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider with abort signal")
func openAISpeechProviderAbortSignal() async throws {
@Test
func `OpenAI speech provider with abort signal`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1), configuration: config)
let abortSignal = AbortSignal()
@ -335,8 +333,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider request validation")
func openAISpeechProviderRequestValidation() async throws {
@Test
func `OpenAI speech provider request validation`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(
for: .openai(.tts1),
@ -367,8 +365,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI speech provider different output formats")
func openAISpeechProviderDifferentFormats() async throws {
@Test
func `OpenAI speech provider different output formats`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try SpeechProviderFactory.createProvider(
for: .openai(.tts1),
@ -393,10 +391,9 @@ struct OpenAIAudioProviderTests {
// MARK: - OpenAI API Configuration Tests
@Suite("OpenAI API Configuration Tests")
struct OpenAIAPIConfigurationTests {
@Test("OpenAI API key configuration from environment")
func openAIAPIKeyFromEnvironment() async throws {
@Test
func `OpenAI API key configuration from environment`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "env-test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -407,8 +404,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI custom base URL configuration")
func openAICustomBaseURL() async throws {
@Test
func `OpenAI custom base URL configuration`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// Set custom base URL via environment
setenv("OPENAI_BASE_URL", "https://custom-openai-api.example.com", 1)
@ -424,8 +421,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI organization ID configuration")
func openAIOrganizationID() async throws {
@Test
func `OpenAI organization ID configuration`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// Set organization ID via environment
setenv("OPENAI_ORGANIZATION", "org-test-12345", 1)
@ -441,8 +438,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI request timeout configuration")
func openAIRequestTimeout() async throws {
@Test
func `OpenAI request timeout configuration`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -469,10 +466,9 @@ struct OpenAIAudioProviderTests {
// MARK: - OpenAI Error Handling Tests
@Suite("OpenAI Error Handling Tests")
struct OpenAIErrorHandlingTests {
@Test("OpenAI provider handles network errors")
func openAIProviderNetworkErrors() async throws {
@Test
func `OpenAI provider handles network errors`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "invalid-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -493,8 +489,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI provider handles unsupported formats gracefully")
func openAIProviderUnsupportedFormats() async throws {
@Test
func `OpenAI provider handles unsupported formats gracefully`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -517,8 +513,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI provider handles rate limiting")
func openAIProviderRateLimiting() async throws {
@Test
func `OpenAI provider handles rate limiting`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let provider = try TranscriptionProviderFactory.createProvider(
for: .openai(.whisper1),
@ -549,8 +545,8 @@ struct OpenAIAudioProviderTests {
}
}
@Test("OpenAI provider error message formatting")
func openAIProviderErrorMessageFormatting() async throws {
@Test
func `OpenAI provider error message formatting`() async {
await TestHelpers.withEmptyTestConfiguration { config in
let originalKey = getenv("OPENAI_API_KEY").flatMap { String(cString: $0) }
unsetenv("OPENAI_API_KEY")

View File

@ -3,14 +3,12 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Audio System Integration Tests")
struct AudioSystemIntegrationTests {
enum AudioSystemIntegrationTests {
// MARK: - Basic Audio Function Tests
@Suite("Basic Audio Function Tests")
struct BasicAudioFunctionTests {
@Test("Audio transcription functions are available")
func audioTranscriptionFunctionsAvailable() async throws {
@Test
func `Audio transcription functions are available`() async throws {
// Test that core audio transcription functions exist and work
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
let audioData = AudioData(data: Data([0x01, 0x02, 0x03, 0x04]), format: .wav)
@ -23,8 +21,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("Audio speech generation functions are available")
func audioSpeechGenerationFunctionsAvailable() async throws {
@Test
func `Audio speech generation functions are available`() async throws {
// Test that core speech generation functions exist and work
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
// Test basic speech generation function exists
@ -36,8 +34,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("Audio model types work correctly")
func audioModelTypesWork() {
@Test
func `Audio model types work correctly`() {
// Test that audio model enums work as expected
let transcriptionModel = TranscriptionModel.openai(.whisper1)
#expect(transcriptionModel.modelId == "whisper-1")
@ -50,8 +48,8 @@ struct AudioSystemIntegrationTests {
#expect(speechModel.supportedFormats.contains(.mp3))
}
@Test("Audio data types work correctly")
func audioDataTypesWork() throws {
@Test
func `Audio data types work correctly`() {
// Test that audio data types work as expected
let testData = Data([0x01, 0x02, 0x03, 0x04])
let audioData = AudioData(data: testData, format: .wav, sampleRate: 44100, channels: 2, duration: 5.0)
@ -73,8 +71,8 @@ struct AudioSystemIntegrationTests {
#expect(VoiceOption.default == .alloy)
}
@Test("AbortSignal functionality works")
func abortSignalFunctionality() async throws {
@Test
func `AbortSignal functionality works`() async throws {
let signal = AbortSignal()
#expect(signal.cancelled == false)
@ -96,10 +94,9 @@ struct AudioSystemIntegrationTests {
// MARK: - Provider Integration Tests
@Suite("Provider Integration Tests")
struct ProviderIntegrationTests {
@Test("Transcription provider factory works")
func transcriptionProviderFactoryWorks() async throws {
@Test
func `Transcription provider factory works`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
let provider = try TranscriptionProviderFactory.createProvider(for: .openai(.whisper1))
@ -109,8 +106,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("Speech provider factory works")
func speechProviderFactoryWorks() async throws {
@Test
func `Speech provider factory works`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
let provider = try SpeechProviderFactory.createProvider(for: .openai(.tts1))
@ -120,8 +117,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("Provider factory fails without API key")
func providerFactoryFailsWithoutAPIKey() async throws {
@Test
func `Provider factory fails without API key`() async throws {
try await TestHelpers.withEmptyTestConfiguration { _ in
#expect(throws: TachikomaError.self) {
_ = try TranscriptionProviderFactory.createProvider(for: .openai(.whisper1))
@ -136,10 +133,9 @@ struct AudioSystemIntegrationTests {
// MARK: - Error Handling Tests
@Suite("Error Handling Tests")
struct ErrorHandlingTests {
@Test("Audio error types are defined")
func audioErrorTypesAreDefined() {
@Test
func `Audio error types are defined`() {
let operationCancelled = TachikomaError.operationCancelled
let noAudioData = TachikomaError.noAudioData
let unsupportedFormat = TachikomaError.unsupportedAudioFormat
@ -154,8 +150,8 @@ struct AudioSystemIntegrationTests {
#expect(speechFailed.localizedDescription.contains("Speech"))
}
@Test("Empty audio data is handled")
func emptyAudioDataIsHandled() async throws {
@Test
func `Empty audio data is handled`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
let emptyAudioData = AudioData(data: Data(), format: .wav)
@ -165,8 +161,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("Empty text for speech generation is handled")
func emptyTextForSpeechGenerationIsHandled() async throws {
@Test
func `Empty text for speech generation is handled`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { _ in
await #expect(throws: TachikomaError.self) {
_ = try await generateSpeech("", using: .openai(.tts1))
@ -177,10 +173,9 @@ struct AudioSystemIntegrationTests {
// MARK: - File I/O Tests
@Suite("File I/O Tests")
struct FileIOTests {
@Test("AudioData file operations work")
func audioDataFileOperationsWork() throws {
@Test
func `AudioData file operations work`() throws {
// Test creating AudioData from file
let tempDir = FileManager.default.temporaryDirectory
let testFile = tempDir.appendingPathComponent("test_audio.wav")
@ -204,8 +199,8 @@ struct AudioSystemIntegrationTests {
try? FileManager.default.removeItem(at: outputFile)
}
@Test("AudioData handles unknown file extensions")
func audioDataHandlesUnknownExtensions() throws {
@Test
func `AudioData handles unknown file extensions`() throws {
let tempDir = FileManager.default.temporaryDirectory
let testFile = tempDir.appendingPathComponent("test_audio.unknown")
let testData = Data([0x01, 0x02])
@ -221,10 +216,9 @@ struct AudioSystemIntegrationTests {
// MARK: - SpeechRecognizer Integration Tests
@Suite("SpeechRecognizer Integration Tests")
struct SpeechRecognizerIntegrationTests {
@Test("SpeechRecognizer can be created with Tachikoma integration")
func speechRecognizerCanBeCreatedWithTachikomaIntegration() async throws {
@Test
func `SpeechRecognizer can be created with Tachikoma integration`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
// Test creating SpeechRecognizer with Tachikoma backend
let recognizer = SpeechRecognizer()
@ -242,8 +236,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer modes have correct properties")
func speechRecognizerModesHaveCorrectProperties() {
@Test
func `SpeechRecognizer modes have correct properties`() {
// Test that recognition modes have proper descriptions and requirements
#expect(RecognitionMode.native.description.contains("Native"))
#expect(RecognitionMode.whisper.description.contains("Whisper"))
@ -257,8 +251,8 @@ struct AudioSystemIntegrationTests {
#expect(RecognitionMode.direct.requiresOpenAIKey == true)
}
@Test("SpeechRecognizer native mode works without API key")
func speechRecognizerNativeModeWorksWithoutAPIKey() async throws {
@Test
func `SpeechRecognizer native mode works without API key`() async throws {
try await TestHelpers.withEmptyTestConfiguration { _ in
let recognizer = SpeechRecognizer()
recognizer.recognitionMode = .native
@ -283,8 +277,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer Whisper mode requires API key")
func speechRecognizerWhisperModeRequiresAPIKey() async throws {
@Test
func `SpeechRecognizer Whisper mode requires API key`() async throws {
try await TestHelpers.withEmptyTestConfiguration { _ in
let recognizer = SpeechRecognizer()
recognizer.recognitionMode = .whisper
@ -304,8 +298,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer Tachikoma mode integrates with audio system")
func speechRecognizerTachikomaModeIntegratesWithAudioSystem() async throws {
@Test
func `SpeechRecognizer Tachikoma mode integrates with audio system`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
let recognizer = SpeechRecognizer()
recognizer.recognitionMode = .tachikoma
@ -330,8 +324,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer direct mode works with audio processing")
func speechRecognizerDirectModeWorksWithAudioProcessing() async throws {
@Test
func `SpeechRecognizer direct mode works with audio processing`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
let recognizer = SpeechRecognizer()
recognizer.recognitionMode = .direct
@ -364,8 +358,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer error handling works correctly")
func speechRecognizerErrorHandlingWorksCorrectly() async throws {
@Test
func `SpeechRecognizer error handling works correctly`() async throws {
let recognizer = SpeechRecognizer()
// Test initial state
@ -385,8 +379,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer integrates with Tachikoma transcription")
func speechRecognizerIntegratesWithTachikomaTranscription() async throws {
@Test
func `SpeechRecognizer integrates with Tachikoma transcription`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
// Test that we can use the same audio data with both systems
let testAudioData = AudioData(data: Data([0x01, 0x02, 0x03, 0x04]), format: .wav, duration: 1.0)
@ -405,8 +399,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer state management works correctly")
func speechRecognizerStateManagementWorksCorrectly() async throws {
@Test
func `SpeechRecognizer state management works correctly`() {
let recognizer = SpeechRecognizer()
// Test initial state
@ -430,8 +424,8 @@ struct AudioSystemIntegrationTests {
#expect(recognizer.recognitionMode == .direct)
}
@Test("SpeechRecognizer handles multiple start/stop cycles")
func speechRecognizerHandlesMultipleStartStopCycles() async throws {
@Test
func `SpeechRecognizer handles multiple start/stop cycles`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
let recognizer = SpeechRecognizer()
recognizer.recognitionMode = .native
@ -458,8 +452,8 @@ struct AudioSystemIntegrationTests {
}
}
@Test("SpeechRecognizer works with different audio formats")
func speechRecognizerWorksWithDifferentAudioFormats() async throws {
@Test
func `SpeechRecognizer works with different audio formats`() async throws {
try await TestHelpers.withStandardTestConfiguration { _ in
// Test that audio system supports formats that SpeechRecognizer might use
let wavAudio = AudioData(data: Data([0x01, 0x02]), format: .wav)

View File

@ -2,15 +2,14 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Tachikoma config guidance snapshot")
struct TachikomaConfigGuidanceSnapshotTests {
@Test("init guidance matches snapshot")
func initGuidanceMatchesSnapshot() {
@Test
func `init guidance matches snapshot`() throws {
let rendered = TKConfigMessages.initGuidance
.map { $0.replacingOccurrences(of: "{path}", with: "/tmp/config.json") }
.joined(separator: "\n")
let snapshot = try! String(contentsOfFile: "Tests/TachikomaTests/CLITests/__snapshots__/config_init.txt")
let snapshot = try String(contentsOfFile: "Tests/TachikomaTests/CLITests/__snapshots__/config_init.txt")
.trimmingCharacters(in: .whitespacesAndNewlines)
#expect(rendered.trimmingCharacters(in: .whitespacesAndNewlines) == snapshot)
}

View File

@ -1,23 +1,22 @@
import Testing
@testable import Tachikoma
@Suite
struct AnthropicMessageEncodingTests {
@Test
func encodesStringWithoutQuotes() {
func `encodes string without quotes`() {
let value = AnyAgentToolValue(string: "hello")
#expect(AnthropicMessageEncoding.encodeToolResult(value) == "hello")
}
@Test
func encodesBooleansAndNumbers() {
func `encodes booleans and numbers`() {
#expect(AnthropicMessageEncoding.encodeToolResult(AnyAgentToolValue(bool: true)) == "true")
#expect(AnthropicMessageEncoding.encodeToolResult(AnyAgentToolValue(int: 42)) == "42")
#expect(AnthropicMessageEncoding.encodeToolResult(AnyAgentToolValue(double: 3.5)) == "3.5")
}
@Test
func encodesObjectsAsJSON() {
func `encodes objects as JSON`() {
let object = AnyAgentToolValue(object: [
"name": AnyAgentToolValue(string: "Peekaboo"),
"count": AnyAgentToolValue(int: 2),
@ -26,7 +25,7 @@ struct AnthropicMessageEncodingTests {
}
@Test
func encodesArraysAndNullValues() {
func `encodes arrays and null values`() {
let array = AnyAgentToolValue(array: [AnyAgentToolValue(int: 1), AnyAgentToolValue(int: 2)])
#expect(AnthropicMessageEncoding.encodeToolResult(array) == "[1,2]")

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("AsyncSequence Conformance Tests")
struct AsyncSequenceTests {
@Test("StreamTextResult conforms to AsyncSequence")
func streamTextResultAsyncSequence() async throws {
@Test
func `StreamTextResult conforms to AsyncSequence`() async throws {
// Create a test stream
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
@ -33,8 +32,8 @@ struct AsyncSequenceTests {
#expect(contents == ["Hello", " ", "World"])
}
@Test("StreamTextResult can be iterated multiple ways")
func multipleIterationStyles() async throws {
@Test
func `StreamTextResult can be iterated multiple ways`() async throws {
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
for i in 1...3 {
@ -59,8 +58,8 @@ struct AsyncSequenceTests {
#expect(count == 4) // 3 text deltas + 1 done
}
@Test("StreamObjectResult conforms to AsyncSequence")
func streamObjectResultAsyncSequence() async throws {
@Test
func `StreamObjectResult conforms to AsyncSequence`() async throws {
struct TestData: Codable, Sendable, Equatable {
let value: Int
}
@ -101,8 +100,8 @@ struct AsyncSequenceTests {
#expect(deltaTypes == [.start, .partial, .partial, .complete, .done])
}
@Test("AsyncSequence works with standard operators")
func asyncSequenceOperators() async throws {
@Test
func `AsyncSequence works with standard operators`() async throws {
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
for i in 1...5 {
@ -137,8 +136,8 @@ struct AsyncSequenceTests {
#expect(numbers == 15) // 1+2+3+4+5
}
@Test("AsyncSequence handles errors properly")
func asyncSequenceErrorHandling() async throws {
@Test
func `AsyncSequence handles errors properly`() async throws {
struct TestError: Error {}
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
@ -166,8 +165,8 @@ struct AsyncSequenceTests {
#expect(receivedError)
}
@Test("AsyncSequence can be cancelled mid-iteration")
func asyncSequenceCancellation() async throws {
@Test
func `AsyncSequence can be cancelled mid-iteration`() async {
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
for i in 1...100 {
@ -204,8 +203,8 @@ struct AsyncSequenceTests {
#expect(count == 5)
}
@Test("StreamTextResult extension methods work with AsyncSequence")
func streamTextResultExtensions() async throws {
@Test
func `StreamTextResult extension methods work with AsyncSequence`() async throws {
let testStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
continuation.yield(TextStreamDelta(type: .textDelta, content: "Hello"))
@ -234,8 +233,8 @@ struct AsyncSequenceTests {
#expect(texts.contains(" World")) // From thinking channel
}
@Test("StreamObjectResult extension methods work with AsyncSequence")
func streamObjectResultExtensions() async throws {
@Test
func `StreamObjectResult extension methods work with AsyncSequence`() async throws {
struct TestItem: Codable, Sendable, Equatable {
let id: Int
let name: String

View File

@ -1,15 +1,14 @@
import Foundation
import Testing
@Suite("Basic Tests")
struct BasicTests {
@Test("Simple math test")
func simpleMath() {
@Test
func `Simple math test`() {
#expect(2 + 2 == 4)
}
@Test("String test")
func stringTest() {
@Test
func `String test`() {
#expect("hello".uppercased() == "HELLO")
}
}

View File

@ -3,10 +3,10 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("Configuration Architecture Tests", .serialized)
@Suite(.serialized)
struct ConfigurationArchitectureTests {
@Test("Auto instance is a true singleton")
func verifyAutoInstanceSingleton() async throws {
@Test
func `Auto instance is a true singleton`() throws {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -16,14 +16,14 @@ struct ConfigurationArchitectureTests {
}
// All should be exactly the same instance
let first = instances.first!
let first = try #require(instances.first)
for instance in instances {
#expect(instance === first, "All auto instances must be identical")
}
}
@Test("Configuration priority chain")
func verifyConfigPriorityChain() async throws {
@Test
func `Configuration priority chain`() {
// Clear default
TachikomaConfiguration.default = nil
@ -50,8 +50,8 @@ struct ConfigurationArchitectureTests {
TachikomaConfiguration.default = nil
}
@Test("README examples work correctly")
func verifyREADMEExamples() async throws {
@Test
func `README examples work correctly`() {
// Example 1: Zero configuration
_ = {
Task {
@ -95,8 +95,8 @@ struct ConfigurationArchitectureTests {
TachikomaConfiguration.default = nil
}
@Test("Thread safety under high concurrency")
func verifyThreadSafety() async throws {
@Test
func `Thread safety under high concurrency`() async {
TachikomaConfiguration.default = nil
// Concurrent writes and reads
@ -128,8 +128,8 @@ struct ConfigurationArchitectureTests {
TachikomaConfiguration.default = nil
}
@Test("Conversation maintains its configuration")
func verifyConversationConfig() async throws {
@Test
func `Conversation maintains its configuration`() {
TachikomaConfiguration.default = nil
let config1 = TachikomaConfiguration(loadFromEnvironment: false)

View File

@ -2,10 +2,10 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Configuration environment loading", .serialized)
@Suite(.serialized)
struct ConfigurationEnvironmentTests {
@Test("Provider.environmentValue falls back to process environment")
func providerEnvironmentValueFallback() {
@Test
func `Provider.environmentValue falls back to process environment`() {
let key = "TACHIKOMA_ENV_TEST_VALUE"
setenv(key, "env-success", 1)
defer { unsetenv(key) }
@ -14,8 +14,8 @@ struct ConfigurationEnvironmentTests {
#expect(value == "env-success")
}
@Test("TachikomaConfiguration picks up base URLs from environment")
func configurationLoadsBaseURLFromEnvironment() {
@Test
func `TachikomaConfiguration picks up base URLs from environment`() {
let key = "OPENAI_BASE_URL"
setenv(key, "https://env.example.com", 1)
defer { unsetenv(key) }

View File

@ -9,10 +9,9 @@ import Darwin
import Glibc
#endif
@Suite("Custom Provider Registry")
struct CustomProviderRegistryTests {
@Test("Loads providers from profile config with comments and env vars")
func loadProvidersFromProfile() throws {
@Test
func `Loads providers from profile config with comments and env vars`() throws {
let fm = FileManager.default
let tempHome = fm.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try fm.createDirectory(at: tempHome, withIntermediateDirectories: true)

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Debug Configuration Tests")
struct DebugConfigTests {
@Test("Debug configuration behavior")
func debugConfiguration() async {
@Test
func `Debug configuration behavior`() async {
let config = TachikomaConfiguration(loadFromEnvironment: false)
print("=== Testing instance-based configuration ===")
@ -31,8 +30,8 @@ struct DebugConfigTests {
#expect(Bool(true)) // Just to complete the test
}
@Test("Multiple configurations isolation")
func multipleConfigurationsIsolation() {
@Test
func `Multiple configurations isolation`() {
let config1 = TachikomaConfiguration(loadFromEnvironment: false)
let config2 = TachikomaConfiguration(loadFromEnvironment: false)

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Debug Exact Test")
struct DebugExactTests {
@Test("Replicate failing test exactly")
func replicateFailingTest() async throws {
@Test
func `Replicate failing test exactly`() async {
print("=== Starting exact replication ===")
await TestHelpers.withEmptyTestConfiguration { config in

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Direct Configuration Tests")
struct DirectConfigTests {
@Test("Direct configuration access")
func directConfigAccess() {
@Test
func `Direct configuration access`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Just test basic access without test helpers
@ -14,15 +13,15 @@ struct DirectConfigTests {
#expect(Bool(true)) // If we get here, no infinite loop
}
@Test("Provider enum direct access")
func providerEnumDirect() {
@Test
func `Provider enum direct access`() {
let provider = Provider.openai
#expect(provider.identifier == "openai")
#expect(provider.displayName == "OpenAI")
}
@Test("Configuration instance creation")
func configurationInstanceCreation() {
@Test
func `Configuration instance creation`() {
let config1 = TachikomaConfiguration()
let config2 = TachikomaConfiguration(loadFromEnvironment: false)
let config3 = TachikomaConfiguration(apiKeys: ["openai": "test"])

View File

@ -2,12 +2,11 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Generation Function Tests")
struct GenerationTests {
// MARK: - Basic Generation Tests (Placeholder Providers)
@Test("Generate Function - OpenAI Provider")
func generateFunctionOpenAI() async throws {
@Test
func `Generate Function - OpenAI Provider`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let result = try await generate(
"What is 2+2?",
@ -25,8 +24,8 @@ struct GenerationTests {
}
}
@Test("Generate Function - Anthropic Provider")
func generateFunctionAnthropic() async throws {
@Test
func `Generate Function - Anthropic Provider`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["anthropic": "test-key"]) { config in
let result = try await generate(
"Explain quantum physics",
@ -42,8 +41,8 @@ struct GenerationTests {
}
}
@Test("Generate Function - Default Model")
func generateFunctionDefaultModel() async throws {
@Test
func `Generate Function - Default Model`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["anthropic": "test-key"]) { config in
let result = try await generate("Hello world", configuration: config)
@ -52,8 +51,8 @@ struct GenerationTests {
}
}
@Test("Generate Function - With System Prompt")
func generateFunctionWithSystem() async throws {
@Test
func `Generate Function - With System Prompt`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let result = try await generate(
"Tell me a joke",
@ -73,8 +72,8 @@ struct GenerationTests {
// MARK: - Streaming Tests
@Test("Stream Function - Basic Streaming")
func streamFunctionBasic() async throws {
@Test
func `Stream Function - Basic Streaming`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let stream = try await stream(
"Count to 5",
@ -101,8 +100,8 @@ struct GenerationTests {
}
}
@Test("Stream Function - Anthropic Streaming")
func streamFunctionAnthropic() async throws {
@Test
func `Stream Function - Anthropic Streaming`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["anthropic": "test-key"]) { config in
let stream = try await stream(
"Write a haiku",
@ -135,8 +134,8 @@ struct GenerationTests {
// MARK: - Image Analysis Tests
@Test("Analyze Function - Vision Model")
func analyzeFunctionVision() async throws {
@Test
func `Analyze Function - Vision Model`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let testImageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
let result = try await analyze(
@ -154,8 +153,8 @@ struct GenerationTests {
}
}
@Test("Analyze Function - Non-Vision Model Error")
func analyzeFunctionNonVisionError() async {
@Test
func `Analyze Function - Non-Vision Model Error`() async {
_ = await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// GPT-4.1 doesn't support vision
await #expect(throws: TachikomaError.self) {
@ -169,8 +168,8 @@ struct GenerationTests {
}
}
@Test("Analyze Function - Default Vision Model")
func analyzeFunctionDefaultVision() async throws {
@Test
func `Analyze Function - Default Vision Model`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// Use base64 encoded test image (1x1 pixel PNG)
let testImageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
@ -191,8 +190,8 @@ struct GenerationTests {
// MARK: - Error Handling Tests
@Test("Generate Function - Missing API Key")
func generateFunctionMissingAPIKey() async {
@Test
func `Generate Function - Missing API Key`() async {
_ = await TestHelpers.withEmptyTestConfiguration { config in
await #expect(throws: TachikomaError.self) {
try await generate("Test", using: .openai(.gpt4o), configuration: config)
@ -200,8 +199,8 @@ struct GenerationTests {
}
}
@Test("Generate Function - Invalid Configuration")
func generateFunctionInvalidConfig() async throws {
@Test
func `Generate Function - Invalid Configuration`() async {
await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// Test with invalid base URL format
config.setBaseURL("not-a-url", for: .openai)
@ -221,8 +220,8 @@ struct GenerationTests {
// MARK: - Tool Integration Tests
@Test("Generate Function - Without Tools")
func generateFunctionWithoutTools() async throws {
@Test
func `Generate Function - Without Tools`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
// Test generation without tools
let result = try await generate(
@ -235,8 +234,8 @@ struct GenerationTests {
}
}
@Test("Generate Function - With Custom Tools")
func generateFunctionWithCustomTools() async throws {
@Test
func `Generate Function - With Custom Tools`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["anthropic": "test-key"]) { config in
// Create a simple test tool
let testTool = createTool(
@ -262,8 +261,8 @@ struct GenerationTests {
// MARK: - Image Input Type Tests
@Test("Image Input Types")
func imageInputTypes() {
@Test
func `Image Input Types`() {
let base64Image = ImageInput.base64("test-data")
let urlImage = ImageInput.url("https://example.com/image.jpg")
let fileImage = ImageInput.filePath("/path/to/image.png")

View File

@ -2,7 +2,6 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Grok Model Catalog Tests")
struct GrokModelCatalogTests {
private static let catalog: [Model.Grok] = [
.grok4,
@ -26,15 +25,15 @@ struct GrokModelCatalogTests {
}
}
@Test("CaseIterable reflects the official Grok catalog")
func catalogAlignment() {
@Test
func `CaseIterable reflects the official Grok catalog`() {
self.requireModernPlatforms {
#expect(Model.Grok.allCases == Self.catalog)
}
}
@Test("ModelSelector parses every Grok model identifier")
func selectorParsesCatalog() throws {
@Test
func `ModelSelector parses every Grok model identifier`() throws {
try self.requireModernPlatforms {
for model in Self.catalog {
let parsed = try ModelSelector.parseModel(model.modelId)
@ -43,8 +42,8 @@ struct GrokModelCatalogTests {
}
}
@Test("Available-model CLI listing matches catalog IDs")
func availableModelListingMatchesCatalog() {
@Test
func `Available-model CLI listing matches catalog IDs`() {
self.requireModernPlatforms {
let listed = Set(ModelSelector.availableModels(for: "grok"))
let expected = Set(Self.catalog.map(\.modelId))
@ -52,8 +51,8 @@ struct GrokModelCatalogTests {
}
}
@Test("Vision capability only flips on for vision/image Grok models")
func visionCapabilityMatchesModelType() {
@Test
func `Vision capability only flips on for vision/image Grok models`() {
self.requireModernPlatforms {
let visionModels: Set<Model.Grok> = [.grok2Vision, .grok2Image, .grokVisionBeta]

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("LanguageModel enums")
struct LanguageModelCoverageTests {
@Test("OpenAI enum exposes properties")
func openAIProperties() {
@Test
func `OpenAI enum exposes properties`() {
let models = LanguageModel.OpenAI.allCases
#expect(!models.isEmpty)
for model in models {
@ -18,8 +17,8 @@ struct LanguageModelCoverageTests {
}
}
@Test("Anthropic enum exposes properties")
func anthropicProperties() {
@Test
func `Anthropic enum exposes properties`() {
for model in LanguageModel.Anthropic.allCases {
#expect(!model.modelId.isEmpty)
_ = model.supportsVision
@ -27,8 +26,8 @@ struct LanguageModelCoverageTests {
}
}
@Test("Remaining provider enums expose properties")
func otherProviders() {
@Test
func `Remaining provider enums expose properties`() {
for model in LanguageModel.Google.allCases {
#expect(!model.rawValue.isEmpty)
_ = model.supportsVision
@ -66,8 +65,8 @@ struct LanguageModelCoverageTests {
}
}
@Test("LanguageModel top level switches")
func languageModelDescriptions() {
@Test
func `LanguageModel top level switches`() {
let baseModels: [LanguageModel] = [
.openai(.gpt51),
.anthropic(.opus45),
@ -100,10 +99,21 @@ struct LanguageModelCoverageTests {
}
private struct DummyProvider: ModelProvider {
var modelId: String { "dummy" }
var baseURL: String? { nil }
var apiKey: String? { nil }
var capabilities: ModelCapabilities { .init() }
var modelId: String {
"dummy"
}
var baseURL: String? {
nil
}
var apiKey: String? {
nil
}
var capabilities: ModelCapabilities {
.init()
}
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
.init(text: "dummy")

View File

@ -3,12 +3,11 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("Minimal Modern API Tests")
struct MinimalModernAPITests {
// MARK: - Model Tests
@Test("Model enum construction")
func modelEnumConstruction() {
@Test
func `Model enum construction`() {
// Test that model enums can be constructed
let openaiModel = Model.openai(.gpt4o)
let anthropicModel = Model.anthropic(.opus45)
@ -31,8 +30,8 @@ struct MinimalModernAPITests {
}
}
@Test("Model default value")
func modelDefaultValue() {
@Test
func `Model default value`() {
let defaultModel = Model.default
// Should compile without errors
switch defaultModel {
@ -45,8 +44,8 @@ struct MinimalModernAPITests {
// MARK: - Tool System Tests
@Test("AgentTool creation")
func agentToolCreation() {
@Test
func `AgentTool creation`() {
let tool = Tachikoma.createTool(
name: "test_tool",
description: "A test tool",
@ -60,8 +59,8 @@ struct MinimalModernAPITests {
#expect(tool.description == "A test tool")
}
@Test("AgentToolArguments parsing")
func agentToolArgumentsParsing() throws {
@Test
func `AgentToolArguments parsing`() throws {
let args = AgentToolArguments([
"name": AnyAgentToolValue(string: "test"),
"value": AnyAgentToolValue(int: 42),
@ -73,8 +72,8 @@ struct MinimalModernAPITests {
#expect(args.optionalStringValue("missing") ?? "default" == "default")
}
@Test("Built-in tools exist")
func builtInToolsExist() {
@Test
func `Built-in tools exist`() {
// Test that built-in tools are available
#expect(weatherTool.name == "get_weather")
#expect(timeTool.name == "get_current_time")
@ -87,8 +86,8 @@ struct MinimalModernAPITests {
extension MinimalModernAPITests {
// MARK: - Error Types
@Test("Tool error types")
func toolErrorTypes() {
@Test
func `Tool error types`() {
let toolError = AgentToolError.invalidInput("test")
#expect(toolError.errorDescription != nil)
@ -98,8 +97,8 @@ extension MinimalModernAPITests {
// MARK: - Conversation Tests
@Test("Conversation basic functionality")
func conversationBasic() {
@Test
func `Conversation basic functionality`() {
let conversation = Conversation()
#expect(conversation.messages.isEmpty)
@ -114,8 +113,8 @@ extension MinimalModernAPITests {
// MARK: - Basic Type Tests
@Test("ConversationMessage basic properties")
func conversationMessageBasic() {
@Test
func `ConversationMessage basic properties`() {
let message = ConversationMessage(
id: "test",
role: .user,

View File

@ -2,12 +2,10 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Model Capabilities Tests")
struct ModelCapabilitiesTests {
@Suite("Capability Detection")
enum ModelCapabilitiesTests {
struct CapabilityDetectionTests {
@Test("GPT-5 models exclude temperature and topP")
func gPT5ExcludesTemperature() {
@Test
func `GPT-5 models exclude temperature and topP`() {
let models: [LanguageModel] = [
.openai(.gpt52),
.openai(.gpt51),
@ -28,16 +26,16 @@ struct ModelCapabilitiesTests {
}
}
@Test("Gemini 3 Flash supports thinking config options")
func gemini3FlashThinkingConfig() {
@Test
func `Gemini 3 Flash supports thinking config options`() {
let capabilities = ModelCapabilityRegistry.shared.capabilities(for: .google(.gemini3Flash))
#expect(capabilities.supportsTopK)
#expect(capabilities.supportedProviderOptions.supportsThinkingConfig)
#expect(capabilities.supportedProviderOptions.supportsSafetySettings)
}
@Test("Reasoning models have forced temperature")
func reasoningModelsFixedTemperature() {
@Test
func `Reasoning models have forced temperature`() {
let model = LanguageModel.openai(.o4Mini)
let capabilities = ModelCapabilityRegistry.shared.capabilities(for: model)
@ -50,8 +48,8 @@ struct ModelCapabilitiesTests {
#expect(capabilities.supportedProviderOptions.supportsPreviousResponseId)
}
@Test("GPT-4 models support standard parameters")
func gPT4StandardParameters() {
@Test
func `GPT-4 models support standard parameters`() {
let models: [LanguageModel] = [
.openai(.gpt4o),
.openai(.gpt4oMini),
@ -73,8 +71,8 @@ struct ModelCapabilitiesTests {
}
}
@Test("Claude models support thinking")
func claudeThinkingSupport() {
@Test
func `Claude models support thinking`() {
let models: [LanguageModel] = [
.anthropic(.opus4),
.anthropic(.sonnet4),
@ -90,8 +88,8 @@ struct ModelCapabilitiesTests {
}
}
@Test("Google models support topK and thinking")
func googleCapabilities() {
@Test
func `Google models support topK and thinking`() {
let models: [LanguageModel] = [
.google(.gemini25Pro),
.google(.gemini25Flash),
@ -107,8 +105,8 @@ struct ModelCapabilitiesTests {
}
}
@Test("Mistral models support safe mode")
func mistralCapabilities() {
@Test
func `Mistral models support safe mode`() {
let models: [LanguageModel] = [
.mistral(.large2),
.mistral(.codestral),
@ -121,8 +119,8 @@ struct ModelCapabilitiesTests {
}
}
@Test("Groq models support speed level")
func groqCapabilities() {
@Test
func `Groq models support speed level`() {
let models: [LanguageModel] = [
.groq(.llama3170b),
.groq(.llama370b),
@ -135,8 +133,8 @@ struct ModelCapabilitiesTests {
}
}
@Test("Grok models support fun mode")
func grokCapabilities() {
@Test
func `Grok models support fun mode`() {
let models: [LanguageModel] = [
.grok(.grok4),
.grok(.grok3),
@ -151,10 +149,9 @@ struct ModelCapabilitiesTests {
}
}
@Suite("Settings Validation")
struct SettingsValidationTests {
@Test("Validate settings for GPT-5.1")
func validateGPT51Settings() {
@Test
func `Validate settings for GPT-5.1`() {
let settings = GenerationSettings(
maxTokens: 1000,
temperature: 0.7,
@ -180,8 +177,8 @@ struct ModelCapabilitiesTests {
#expect(validated.providerOptions.openai?.previousResponseId == "test-123") // Kept
}
@Test("Validate settings for O3 with forced temperature")
func validateO3Settings() {
@Test
func `Validate settings for O3 with forced temperature`() {
let settings = GenerationSettings(
temperature: 0.5,
topP: 0.8,
@ -201,8 +198,8 @@ struct ModelCapabilitiesTests {
#expect(validated.providerOptions.openai?.verbosity == nil) // Removed
}
@Test("Validate settings for GPT-4")
func validateGPT4Settings() {
@Test
func `Validate settings for GPT-4`() {
let settings = GenerationSettings(
maxTokens: 2000,
temperature: 0.8,
@ -232,8 +229,8 @@ struct ModelCapabilitiesTests {
#expect(validated.providerOptions.openai?.topLogprobs == 3)
}
@Test("Validate Anthropic options")
func validateAnthropicOptions() {
@Test
func `Validate Anthropic options`() {
let settings = GenerationSettings(
temperature: 0.7,
providerOptions: .init(
@ -257,10 +254,9 @@ struct ModelCapabilitiesTests {
}
}
@Suite("Custom Model Registration")
struct CustomModelTests {
@Test("Register custom model capabilities")
func registerCustomCapabilities() {
@Test
func `Register custom model capabilities`() {
let customCaps = ModelParameterCapabilities(
supportsTemperature: false,
supportsTopP: false,
@ -282,8 +278,8 @@ struct ModelCapabilitiesTests {
#expect(retrieved.excludedParameters.contains("temperature"))
}
@Test("OpenAI-compatible model registration")
func openAICompatibleRegistration() {
@Test
func `OpenAI-compatible model registration`() {
let capabilities = ModelParameterCapabilities(
supportsTemperature: true,
supportsTopK: true,
@ -307,10 +303,9 @@ struct ModelCapabilitiesTests {
}
}
@Suite("Thread Safety")
struct ThreadSafetyTests {
@Test("Concurrent capability access")
func concurrentAccess() async {
@Test
func `Concurrent capability access`() async {
let models: [LanguageModel] = [
.openai(.gpt51),
.openai(.gpt4o),
@ -347,7 +342,7 @@ struct ModelCapabilitiesTests {
}
}
// Helper for testing custom models
/// Helper for testing custom models
private struct TestModelProvider: ModelProvider {
let modelId: String
let baseURL: String? = nil

View File

@ -1,52 +1,51 @@
import Testing
@testable import Tachikoma
@Suite("LanguageModel parsing")
struct ModelParsingTests {
@Test("parse GPT-5 mini alias")
func parseGPT5Mini() {
@Test
func `parse GPT-5 mini alias`() {
let parsed = LanguageModel.parse(from: "gpt-5-mini")
#expect(parsed == .openai(.gpt5Mini))
}
@Test("parse GPT-5.1 base model")
func parseGPT51() {
@Test
func `parse GPT-5.1 base model`() {
let parsed = LanguageModel.parse(from: "gpt-5.1")
#expect(parsed == .openai(.gpt51))
}
@Test("parse GPT-5.2 base model")
func parseGPT52() {
@Test
func `parse GPT-5.2 base model`() {
let parsed = LanguageModel.parse(from: "gpt-5.2")
#expect(parsed == .openai(.gpt52))
}
@Test("parse GPT-5.1 nano alias")
func parseGPT51Nano() {
@Test
func `parse GPT-5.1 nano alias`() {
let parsed = LanguageModel.parse(from: "gpt51-nano")
#expect(parsed == .openai(.gpt5Nano))
}
@Test("parse Claude Sonnet 4.5 snapshot id")
func parseClaudeSonnetSnapshot() {
@Test
func `parse Claude Sonnet 4.5 snapshot id`() {
let parsed = LanguageModel.parse(from: "claude-sonnet-4-5-20250929")
#expect(parsed == .anthropic(.sonnet45))
}
@Test("parse shorthand Claude alias")
func parseClaudeAlias() {
@Test
func `parse shorthand Claude alias`() {
let parsed = LanguageModel.parse(from: "claude")
#expect(parsed == .anthropic(.sonnet45))
}
@Test("parse Gemini 3 Flash model id")
func parseGemini3Flash() {
@Test
func `parse Gemini 3 Flash model id`() {
let parsed = LanguageModel.parse(from: "gemini-3-flash")
#expect(parsed == .google(.gemini3Flash))
}
@Test("parse shorthand Gemini alias")
func parseGeminiAlias() {
@Test
func `parse shorthand Gemini alias`() {
let parsed = LanguageModel.parse(from: "gemini")
#expect(parsed == .google(.gemini3Flash))
}

View File

@ -6,14 +6,14 @@ import Testing
@testable import Tachikoma
#if os(Linux)
@Suite("OpenAICompatibleHelper Tests", .disabled("URLProtocol mocking unavailable on Linux"))
@Suite(.disabled("URLProtocol mocking unavailable on Linux"))
struct OpenAICompatibleHelperTests {}
#else
@Suite("OpenAICompatibleHelper Tests", .serialized)
@Suite(.serialized)
struct OpenAICompatibleHelperTests {
@Test("generateText encodes stop sequences, headers, and tool definitions")
func generateTextEncodesPayload() async throws {
@Test
func `generateText encodes stop sequences, headers, and tool definitions`() async throws {
let tool = AgentTool(
name: "lookup",
description: "Lookup a value",
@ -75,8 +75,8 @@ struct OpenAICompatibleHelperTests {
#expect(required == ["query"])
}
@Test("streamText emits deltas as SSE chunks arrive")
func streamTextEmitsDeltas() async throws {
@Test
func `streamText emits deltas as SSE chunks arrive`() async throws {
let request = ProviderRequest(
messages: [ModelMessage(role: .user, content: [.text("stream")])],
)
@ -121,8 +121,8 @@ struct OpenAICompatibleHelperTests {
#expect(deltas == "Hello world")
}
@Test("non-200 responses surface TachikomaError.apiError")
func apiErrorsSurface() async throws {
@Test
func `non-200 responses surface TachikomaError.apiError`() async {
await self.withMockedSession { urlRequest in
let errorJSON = """
{"error":{"message":"bad request","type":"invalid_request_error"}}

View File

@ -2,12 +2,10 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Provider Options Tests")
struct ProviderOptionsTests {
@Suite("Provider Options Creation")
enum ProviderOptionsTests {
struct CreationTests {
@Test("Create OpenAI options")
func openAIOptions() {
@Test
func `Create OpenAI options`() {
let options = OpenAIOptions(
parallelToolCalls: false,
responseFormat: .json,
@ -35,8 +33,8 @@ struct ProviderOptionsTests {
#expect(options.topLogprobs == 5)
}
@Test("Create Anthropic options")
func anthropicOptions() {
@Test
func `Create Anthropic options`() {
let options = AnthropicOptions(
thinking: .enabled(budgetTokens: 5000),
cacheControl: .persistent,
@ -57,8 +55,8 @@ struct ProviderOptionsTests {
#expect(options.metadata?["key"] == "value")
}
@Test("Create Google options")
func googleOptions() {
@Test
func `Create Google options`() {
let options = GoogleOptions(
thinkingConfig: .init(budgetTokens: 3000, includeThoughts: true),
safetySettings: .moderate,
@ -73,8 +71,8 @@ struct ProviderOptionsTests {
#expect(options.stopSequences == ["###"])
}
@Test("Create Mistral options")
func mistralOptions() {
@Test
func `Create Mistral options`() {
let options = MistralOptions(
safeMode: true,
randomSeed: 12345,
@ -84,14 +82,14 @@ struct ProviderOptionsTests {
#expect(options.randomSeed == 12345)
}
@Test("Create Groq options")
func groqOptions() {
@Test
func `Create Groq options`() {
let options = GroqOptions(speed: .ultraFast)
#expect(options.speed == .ultraFast)
}
@Test("Create Grok options")
func grokOptions() {
@Test
func `Create Grok options`() {
let options = GrokOptions(
funMode: true,
includeCurrentEvents: true,
@ -102,10 +100,9 @@ struct ProviderOptionsTests {
}
}
@Suite("Provider Options Container")
struct ContainerTests {
@Test("Create provider options container")
func providerOptionsContainer() {
@Test
func `Create provider options container`() {
let options = ProviderOptions(
openai: .init(verbosity: .high),
anthropic: .init(thinking: .disabled),
@ -123,8 +120,8 @@ struct ProviderOptionsTests {
#expect(options.grok?.funMode == false)
}
@Test("Empty provider options")
func emptyProviderOptions() {
@Test
func `Empty provider options`() {
let options = ProviderOptions()
#expect(options.openai == nil)
@ -136,10 +133,9 @@ struct ProviderOptionsTests {
}
}
@Suite("GenerationSettings Integration")
struct SettingsIntegrationTests {
@Test("Settings with provider options")
func settingsWithProviderOptions() {
@Test
func `Settings with provider options`() {
let settings = GenerationSettings(
maxTokens: 1000,
temperature: 0.7,
@ -157,8 +153,8 @@ struct ProviderOptionsTests {
#expect(settings.providerOptions.openai?.parallelToolCalls == true)
}
@Test("Settings with empty provider options")
func settingsWithEmptyProviderOptions() {
@Test
func `Settings with empty provider options`() {
let settings = GenerationSettings(
maxTokens: 500,
temperature: 0.5,
@ -170,10 +166,9 @@ struct ProviderOptionsTests {
}
}
@Suite("Codable Conformance")
struct CodableTests {
@Test("Encode and decode OpenAI options")
func openAIOptionsCodable() throws {
@Test
func `Encode and decode OpenAI options`() throws {
let original = OpenAIOptions(
parallelToolCalls: true,
seed: 42,
@ -193,8 +188,8 @@ struct ProviderOptionsTests {
#expect(decoded.seed == original.seed)
}
@Test("Encode and decode Anthropic thinking mode")
func anthropicThinkingCodable() throws {
@Test
func `Encode and decode Anthropic thinking mode`() throws {
let original = AnthropicOptions(
thinking: .enabled(budgetTokens: 3000),
)
@ -212,8 +207,8 @@ struct ProviderOptionsTests {
}
}
@Test("Encode and decode provider options container")
func providerOptionsCodable() throws {
@Test
func `Encode and decode provider options container`() throws {
let original = ProviderOptions(
openai: .init(verbosity: .low),
anthropic: .init(cacheControl: .ephemeral),
@ -231,8 +226,8 @@ struct ProviderOptionsTests {
#expect(decoded.google?.safetySettings == .relaxed)
}
@Test("Encode and decode GenerationSettings with provider options")
func generationSettingsCodable() throws {
@Test
func `Encode and decode GenerationSettings with provider options`() throws {
let original = GenerationSettings(
maxTokens: 2000,
temperature: 0.8,
@ -257,45 +252,44 @@ struct ProviderOptionsTests {
}
}
@Suite("Enum Value Tests")
struct EnumValueTests {
@Test("OpenAI verbosity values")
func openAIVerbosityValues() {
@Test
func `OpenAI verbosity values`() {
#expect(OpenAIOptions.Verbosity.low.rawValue == "low")
#expect(OpenAIOptions.Verbosity.medium.rawValue == "medium")
#expect(OpenAIOptions.Verbosity.high.rawValue == "high")
}
@Test("OpenAI reasoning effort values")
func openAIReasoningEffortValues() {
@Test
func `OpenAI reasoning effort values`() {
#expect(OpenAIOptions.ReasoningEffort.minimal.rawValue == "minimal")
#expect(OpenAIOptions.ReasoningEffort.low.rawValue == "low")
#expect(OpenAIOptions.ReasoningEffort.medium.rawValue == "medium")
#expect(OpenAIOptions.ReasoningEffort.high.rawValue == "high")
}
@Test("OpenAI response format values")
func openAIResponseFormatValues() {
@Test
func `OpenAI response format values`() {
#expect(OpenAIOptions.ResponseFormat.text.rawValue == "text")
#expect(OpenAIOptions.ResponseFormat.json.rawValue == "json_object")
#expect(OpenAIOptions.ResponseFormat.jsonSchema.rawValue == "json_schema")
}
@Test("Anthropic cache control values")
func anthropicCacheControlValues() {
@Test
func `Anthropic cache control values`() {
#expect(AnthropicOptions.CacheControl.ephemeral.rawValue == "ephemeral")
#expect(AnthropicOptions.CacheControl.persistent.rawValue == "persistent")
}
@Test("Google safety settings values")
func googleSafetySettingsValues() {
@Test
func `Google safety settings values`() {
#expect(GoogleOptions.SafetySettings.strict.rawValue == "strict")
#expect(GoogleOptions.SafetySettings.moderate.rawValue == "moderate")
#expect(GoogleOptions.SafetySettings.relaxed.rawValue == "relaxed")
}
@Test("Groq speed level values")
func groqSpeedLevelValues() {
@Test
func `Groq speed level values`() {
#expect(GroqOptions.SpeedLevel.normal.rawValue == "normal")
#expect(GroqOptions.SpeedLevel.fast.rawValue == "fast")
#expect(GroqOptions.SpeedLevel.ultraFast.rawValue == "ultra_fast")

View File

@ -10,7 +10,9 @@ import Testing
private func withTemporaryEnvironment<T: Sendable>(
_ updates: [String: String?],
_ body: @Sendable () throws -> T,
) async rethrows -> T {
) async rethrows
-> T
{
try await TestEnvironmentMutex.shared.withLock {
let saved = updates.keys.map { key in
(key, getenv(key).map { String(cString: $0) })
@ -38,12 +40,10 @@ private func withTemporaryEnvironment<T: Sendable>(
}
}
@Suite("Provider Enum Tests")
struct ProviderTests {
@Suite("Provider Properties Tests")
enum ProviderTests {
struct ProviderPropertiesTests {
@Test("Standard providers have correct identifiers")
func standardProviderIdentifiers() {
@Test
func `Standard providers have correct identifiers`() {
#expect(Provider.openai.identifier == "openai")
#expect(Provider.anthropic.identifier == "anthropic")
#expect(Provider.grok.identifier == "grok")
@ -54,14 +54,14 @@ struct ProviderTests {
#expect(Provider.azureOpenAI.identifier == "azure-openai")
}
@Test("Custom provider has correct identifier")
func customProviderIdentifier() {
@Test
func `Custom provider has correct identifier`() {
let customProvider = Provider.custom("my-custom-provider")
#expect(customProvider.identifier == "my-custom-provider")
}
@Test("Display names are human-readable")
func displayNames() {
@Test
func `Display names are human-readable`() {
#expect(Provider.openai.displayName == "OpenAI")
#expect(Provider.anthropic.displayName == "Anthropic")
#expect(Provider.grok.displayName == "Grok")
@ -73,8 +73,8 @@ struct ProviderTests {
#expect(Provider.custom("test").displayName == "Test")
}
@Test("Environment variables are correct")
func environmentVariables() {
@Test
func `Environment variables are correct`() {
#expect(Provider.openai.environmentVariable == "OPENAI_API_KEY")
#expect(Provider.anthropic.environmentVariable == "ANTHROPIC_API_KEY")
#expect(Provider.grok.environmentVariable == "X_AI_API_KEY")
@ -86,8 +86,8 @@ struct ProviderTests {
#expect(Provider.custom("test").environmentVariable.isEmpty)
}
@Test("Alternative environment variables")
func alternativeEnvironmentVariables() {
@Test
func `Alternative environment variables`() {
#expect(Provider.grok.alternativeEnvironmentVariables == ["XAI_API_KEY", "GROK_API_KEY"])
#expect(Provider.google.alternativeEnvironmentVariables == ["GOOGLE_API_KEY"])
#expect(Provider.openai.alternativeEnvironmentVariables.isEmpty)
@ -98,8 +98,8 @@ struct ProviderTests {
])
}
@Test("Default base URLs")
func defaultBaseURLs() {
@Test
func `Default base URLs`() {
#expect(Provider.openai.defaultBaseURL == "https://api.openai.com/v1")
#expect(Provider.anthropic.defaultBaseURL == "https://api.anthropic.com")
#expect(Provider.grok.defaultBaseURL == "https://api.x.ai/v1")
@ -111,8 +111,8 @@ struct ProviderTests {
#expect(Provider.custom("test").defaultBaseURL == nil)
}
@Test("API key requirements")
func apiKeyRequirements() {
@Test
func `API key requirements`() {
#expect(Provider.openai.requiresAPIKey == true)
#expect(Provider.anthropic.requiresAPIKey == true)
#expect(Provider.grok.requiresAPIKey == true)
@ -125,10 +125,9 @@ struct ProviderTests {
}
}
@Suite("Provider Factory Tests")
struct ProviderFactoryTests {
@Test("Create provider from identifier - standard providers")
func createStandardProviders() {
@Test
func `Create provider from identifier - standard providers`() {
#expect(Provider.from(identifier: "openai") == .openai)
#expect(Provider.from(identifier: "anthropic") == .anthropic)
#expect(Provider.from(identifier: "grok") == .grok)
@ -139,15 +138,15 @@ struct ProviderTests {
#expect(Provider.from(identifier: "azure-openai") == .azureOpenAI)
}
@Test("Create provider from identifier - case insensitive")
func createProvidersCase() {
@Test
func `Create provider from identifier - case insensitive`() {
#expect(Provider.from(identifier: "OpenAI") == .openai)
#expect(Provider.from(identifier: "ANTHROPIC") == .anthropic)
#expect(Provider.from(identifier: "Grok") == .grok)
}
@Test("Create provider from identifier - custom providers")
func createCustomProviders() {
@Test
func `Create provider from identifier - custom providers`() {
let provider1 = Provider.from(identifier: "custom-provider")
let provider2 = Provider.from(identifier: "unknown-provider")
@ -164,8 +163,8 @@ struct ProviderTests {
}
}
@Test("Standard providers list")
func standardProvidersList() {
@Test
func `Standard providers list`() {
let expected: [Provider] = [
.openai,
.anthropic,
@ -180,31 +179,30 @@ struct ProviderTests {
}
}
@Suite("Environment Variable Loading Tests")
struct EnvironmentVariableTests {
@Test("Load API key from primary environment variable")
func loadFromPrimaryEnvironment() {
@Test
func `Load API key from primary environment variable`() {
// We can't easily mock ProcessInfo.processInfo.environment in tests,
// so we'll test the logic indirectly through TachikomaConfiguration
}
@Test("Load API key from alternative environment variable")
func loadFromAlternativeEnvironment() {
@Test
func `Load API key from alternative environment variable`() {
// Test that Grok provider loads from XAI_API_KEY when X_AI_API_KEY is not available
let provider = Provider.grok
#expect(provider.environmentVariable == "X_AI_API_KEY")
#expect(provider.alternativeEnvironmentVariables == ["XAI_API_KEY", "GROK_API_KEY"])
}
@Test("Custom providers don't have environment variables")
func customProviderNoEnvironment() {
@Test
func `Custom providers don't have environment variables`() {
let customProvider = Provider.custom("test")
#expect(customProvider.environmentVariable.isEmpty)
#expect(customProvider.alternativeEnvironmentVariables.isEmpty)
}
@Test("Google ignores ADC credential paths as API keys")
func googleIgnoresADCCredentialPaths() async throws {
@Test
func `Google ignores ADC credential paths as API keys`() async {
let resolved = await withTemporaryEnvironment([
"GEMINI_API_KEY": nil,
"GOOGLE_API_KEY": nil,
@ -217,10 +215,9 @@ struct ProviderTests {
}
}
@Suite("Codable Tests")
struct CodableTests {
@Test("Provider encodes to identifier string")
func providerEncoding() throws {
@Test
func `Provider encodes to identifier string`() throws {
let encoder = JSONEncoder()
let openaiData = try encoder.encode(Provider.openai)
@ -232,8 +229,8 @@ struct ProviderTests {
#expect(customString == "\"my-provider\"")
}
@Test("Provider decodes from identifier string")
func providerDecoding() throws {
@Test
func `Provider decodes from identifier string`() throws {
let decoder = JSONDecoder()
let openaiData = "\"openai\"".utf8Data()
@ -250,17 +247,16 @@ struct ProviderTests {
}
}
@Suite("Equality Tests")
struct EqualityTests {
@Test("Standard providers equality")
func standardProvidersEquality() {
@Test
func `Standard providers equality`() {
#expect(Provider.openai == Provider.openai)
#expect(Provider.anthropic == Provider.anthropic)
#expect(Provider.openai != Provider.anthropic)
}
@Test("Custom providers equality")
func customProvidersEquality() {
@Test
func `Custom providers equality`() {
let custom1 = Provider.custom("test")
let custom2 = Provider.custom("test")
let custom3 = Provider.custom("different")
@ -271,10 +267,9 @@ struct ProviderTests {
}
}
@Suite("Hashable Tests")
struct HashableTests {
@Test("Provider hashable implementation")
func providerHashable() {
@Test
func `Provider hashable implementation`() {
let providers: Set<Provider> = [
.openai,
.anthropic,

View File

@ -2,23 +2,22 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Simple Configuration Tests")
struct SimpleConfigurationTests {
@Test("Provider enum basic functionality")
func providerEnumBasics() {
@Test
func `Provider enum basic functionality`() {
#expect(Provider.openai.identifier == "openai")
#expect(Provider.anthropic.identifier == "anthropic")
#expect(Provider.custom("test").identifier == "test")
}
@Test("Provider factory method")
func providerFactory() {
@Test
func `Provider factory method`() {
#expect(Provider.from(identifier: "openai") == .openai)
#expect(Provider.from(identifier: "custom-provider") == .custom("custom-provider"))
}
@Test("Configuration instance basic functionality")
func configurationInstance() {
@Test
func `Configuration instance basic functionality`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Set keys directly
@ -32,8 +31,8 @@ struct SimpleConfigurationTests {
#expect(config.getAPIKey(for: .anthropic) == nil)
}
@Test("Multiple configuration instances are isolated")
func multipleInstancesIsolated() {
@Test
func `Multiple configuration instances are isolated`() {
let config1 = TachikomaConfiguration(loadFromEnvironment: false)
let config2 = TachikomaConfiguration(loadFromEnvironment: false)

View File

@ -2,12 +2,11 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Stop Conditions Integration Tests")
struct StopConditionsIntegrationTests {
// MARK: - Provider Integration Tests
@Test("Stop conditions passed to OpenAI as native stop sequences")
func openAIProviderStopSequences() async throws {
@Test
func `Stop conditions passed to OpenAI as native stop sequences`() {
// Create a mock provider that can verify the request
_ = MockOpenAIProvider()
@ -37,8 +36,8 @@ struct StopConditionsIntegrationTests {
#expect(extractedComposite.isEmpty) // Current behavior
}
@Test("Stop conditions work with generateText end-to-end")
func generateTextWithStopConditions() async throws {
@Test
func `Stop conditions work with generateText end-to-end`() async throws {
// Use a mock provider that returns specific text
let mockProvider = MockTextProvider(responseText: "Count: 1 2 3 END 4 5 6")
@ -60,8 +59,8 @@ struct StopConditionsIntegrationTests {
#expect(response.finishReason == FinishReason.stop)
}
@Test("Streaming with multiple stop conditions")
func streamingWithMultipleStopConditions() async throws {
@Test
func `Streaming with multiple stop conditions`() async throws {
// Create a stream that emits text gradually
let stream = self.createMockStream(texts: [
"Hello ",
@ -94,8 +93,8 @@ struct StopConditionsIntegrationTests {
#expect(!result.contains("STOP"))
}
@Test("Token count stop condition with streaming")
func tokenCountStopWithStreaming() async throws {
@Test
func `Token count stop condition with streaming`() async throws {
// Create a stream with many tokens
let longTexts = (1...100).map { "Token \($0) " }
let stream = self.createMockStream(texts: longTexts)
@ -118,8 +117,8 @@ struct StopConditionsIntegrationTests {
#expect(!result.contains("Token 50"))
}
@Test("Timeout stop condition with streaming")
func timeoutStopWithStreaming() async throws {
@Test
func `Timeout stop condition with streaming`() async throws {
// Create a slow stream
let stream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
@ -149,8 +148,8 @@ struct StopConditionsIntegrationTests {
#expect(chunkCount < 10) // Should not have all chunks
}
@Test("Regex stop condition with complex patterns")
func regexStopConditionWithPatterns() async throws {
@Test
func `Regex stop condition with complex patterns`() async throws {
let texts = [
"Processing item 1...",
"Processing item 2...",
@ -173,8 +172,8 @@ struct StopConditionsIntegrationTests {
#expect(!result.contains("Should not see this"))
}
@Test("Predicate stop condition with custom logic")
func predicateStopCondition() async throws {
@Test
func `Predicate stop condition with custom logic`() async throws {
let stream = self.createMockStream(texts: [
"Step 1: Initialize",
"Step 2: Process",
@ -200,8 +199,8 @@ struct StopConditionsIntegrationTests {
#expect(!result.contains("Step 4"))
}
@Test("Composite stop conditions with ALL logic")
func allStopCondition() async throws {
@Test
func `Composite stop conditions with ALL logic`() async {
let condition = AllStopCondition([
PredicateStopCondition { text, _ in text.count > 20 }, // Length check
PredicateStopCondition { text, _ in text.contains("END") }, // Content check
@ -214,8 +213,8 @@ struct StopConditionsIntegrationTests {
#expect(await condition.shouldStop(text: "This is long enough and has END", delta: nil) == true)
}
@Test("Stop condition state management with reset")
func stopConditionReset() async throws {
@Test
func `Stop condition state management with reset`() async {
let condition = TokenCountStopCondition(maxTokens: 10)
// First use
@ -231,8 +230,8 @@ struct StopConditionsIntegrationTests {
#expect(await condition.shouldStop(text: "Short", delta: nil) == false)
}
@Test("Native provider stop sequence extraction")
func stopSequenceExtraction() async throws {
@Test
func `Native provider stop sequence extraction`() {
// Test single string condition
let singleCondition = StringStopCondition("STOP")
let singleSequences = extractStopSequences(from: singleCondition)
@ -271,10 +270,21 @@ struct StopConditionsIntegrationTests {
private struct MockTextProvider: ModelProvider {
let responseText: String
var modelId: String { "mock" }
var baseURL: String? { nil }
var apiKey: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
"mock"
}
var baseURL: String? {
nil
}
var apiKey: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
func generateText(request: ProviderRequest) async throws -> ProviderResponse {
var finalText = self.responseText
@ -316,10 +326,21 @@ private struct MockTextProvider: ModelProvider {
}
private struct MockOpenAIProvider: ModelProvider {
var modelId: String { "gpt-4" }
var baseURL: String? { nil }
var apiKey: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
"gpt-4"
}
var baseURL: String? {
nil
}
var apiKey: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
ProviderResponse(text: "Mock response", usage: nil, finishReason: .stop)
@ -336,7 +357,7 @@ private struct MockOpenAIProvider: ModelProvider {
}
}
// Helper function to extract stop sequences (mimics the internal implementation)
/// Helper function to extract stop sequences (mimics the internal implementation)
private func extractStopSequences(from stopCondition: (any StopCondition)?) -> [String] {
guard let stopCondition else { return [] }

View File

@ -1,12 +1,11 @@
import Testing
@testable import Tachikoma
@Suite("Stop Conditions Tests")
struct StopConditionsTests {
// MARK: - String Stop Condition Tests
@Test("StringStopCondition should stop on exact match")
func stringStopCondition() async throws {
@Test
func `StringStopCondition should stop on exact match`() async {
let condition = StringStopCondition("STOP")
// Should not stop before the stop string
@ -19,8 +18,8 @@ struct StopConditionsTests {
#expect(await condition.shouldStop(text: "Hello", delta: "STOP") == true)
}
@Test("StringStopCondition case-insensitive matching")
func stringStopConditionCaseInsensitive() async throws {
@Test
func `StringStopCondition case-insensitive matching`() async {
let condition = StringStopCondition("stop", caseSensitive: false)
// Should match regardless of case
@ -31,8 +30,8 @@ struct StopConditionsTests {
// MARK: - Regex Stop Condition Tests
@Test("RegexStopCondition should match patterns")
func regexStopCondition() async throws {
@Test
func `RegexStopCondition should match patterns`() async {
let condition = RegexStopCondition(pattern: "END.*SESSION")
// Should not match without pattern
@ -48,8 +47,8 @@ struct StopConditionsTests {
// MARK: - Token Count Stop Condition Tests
@Test("TokenCountStopCondition should stop after limit")
func tokenCountStopCondition() async throws {
@Test
func `TokenCountStopCondition should stop after limit`() async {
let condition = TokenCountStopCondition(maxTokens: 10)
// Reset to start fresh
@ -70,8 +69,8 @@ struct StopConditionsTests {
// MARK: - Timeout Stop Condition Tests
@Test("TimeoutStopCondition should stop after duration")
func timeoutStopCondition() async throws {
@Test
func `TimeoutStopCondition should stop after duration`() async throws {
let condition = TimeoutStopCondition(timeout: 0.1) // 100ms
// Should not stop immediately
@ -86,8 +85,8 @@ struct StopConditionsTests {
// MARK: - Predicate Stop Condition Tests
@Test("PredicateStopCondition with custom logic")
func predicateStopCondition() async throws {
@Test
func `PredicateStopCondition with custom logic`() async {
let condition = PredicateStopCondition { text, _ in
text.count > 20
}
@ -101,8 +100,8 @@ struct StopConditionsTests {
// MARK: - Composite Stop Conditions Tests
@Test("AnyStopCondition should stop when any condition is met")
func anyStopCondition() async throws {
@Test
func `AnyStopCondition should stop when any condition is met`() async {
let conditions: [any StopCondition] = [
StringStopCondition("STOP"),
TokenCountStopCondition(maxTokens: 100),
@ -117,8 +116,8 @@ struct StopConditionsTests {
#expect(await anyCondition.shouldStop(text: "Hello world", delta: nil) == false)
}
@Test("AllStopCondition should stop only when all conditions are met")
func allStopCondition() async throws {
@Test
func `AllStopCondition should stop only when all conditions are met`() async {
let conditions: [any StopCondition] = [
StringStopCondition("END"),
PredicateStopCondition { text, _ in text.count > 10 },
@ -135,8 +134,8 @@ struct StopConditionsTests {
// MARK: - Integration Tests with Generation
@Test("Stop conditions in generateText")
func stopConditionsInGenerateText() async throws {
@Test
func `Stop conditions in generateText`() {
// Create a mock provider that returns text with a stop marker
let mockText = "This is the response. STOP HERE. This should be truncated."
_ = createMockProvider(responseText: mockText)
@ -151,8 +150,8 @@ struct StopConditionsTests {
#expect(settings.stopConditions != nil)
}
@Test("Stop conditions with streaming")
func stopConditionsInStreamText() async throws {
@Test
func `Stop conditions with streaming`() async throws {
// Create a simple text stream
let stream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
@ -183,8 +182,8 @@ struct StopConditionsTests {
// MARK: - Builder Pattern Tests
@Test("StopConditionBuilder creates correct conditions")
func stopConditionBuilder() async throws {
@Test
func `StopConditionBuilder creates correct conditions`() async {
let condition = StopConditionBuilder()
.whenContains("END")
.afterTokens(100)
@ -198,8 +197,8 @@ struct StopConditionsTests {
#expect(await condition.shouldStop(text: "Test END", delta: nilDelta) == true)
}
@Test("GenerationSettings with stop conditions factory")
func generationSettingsWithStopConditions() {
@Test
func `GenerationSettings with stop conditions factory`() {
let settings = GenerationSettings.withStopConditions(
StringStopCondition("STOP"),
TokenCountStopCondition(maxTokens: 100),
@ -214,8 +213,8 @@ struct StopConditionsTests {
// MARK: - Pattern Detection Tests
@Test("ConsecutivePatternStopCondition detects repeating patterns")
func consecutivePatternStopCondition() async throws {
@Test
func `ConsecutivePatternStopCondition detects repeating patterns`() async {
let condition = ConsecutivePatternStopCondition(pattern: "loop", count: 3)
await condition.reset()
@ -230,8 +229,8 @@ struct StopConditionsTests {
#expect(await condition.shouldStop(text: "loop loop loop", delta: "loop") == true)
}
@Test("RepetitionStopCondition detects repeating content")
func repetitionStopCondition() async throws {
@Test
func `RepetitionStopCondition detects repeating content`() async {
let condition = RepetitionStopCondition(
windowSize: 50, // Increased to ensure we don't hit the window limit
threshold: 0.8,
@ -259,8 +258,8 @@ struct StopConditionsTests {
// MARK: - Edge Cases
@Test("Empty text and nil delta handling")
func emptyAndNilHandling() async throws {
@Test
func `Empty text and nil delta handling`() async {
let condition = StringStopCondition("STOP")
// Empty text with nil delta
@ -273,8 +272,8 @@ struct StopConditionsTests {
#expect(await condition.shouldStop(text: "", delta: "STOP") == true)
}
@Test("Reset functionality")
func resetFunctionality() async throws {
@Test
func `Reset functionality`() async {
let condition = TokenCountStopCondition(maxTokens: 5)
// Add tokens until stop
@ -300,14 +299,25 @@ private func createMockProvider(responseText: String) -> ModelProvider {
StopConditionTestMockProvider(responseText: responseText)
}
// Mock provider for testing (simplified)
/// Mock provider for testing (simplified)
private struct StopConditionTestMockProvider: ModelProvider {
let responseText: String
var modelId: String { "mock-model" }
var baseURL: String? { nil }
var apiKey: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
"mock-model"
}
var baseURL: String? {
nil
}
var apiKey: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
func generateText(request _: ProviderRequest) async throws -> ProviderResponse {
ProviderResponse(text: self.responseText)

View File

@ -1,28 +1,27 @@
import Testing
@testable import Tachikoma
@Suite("StreamObject Tests")
struct StreamObjectTests {
// Test struct for structured output
struct TestPerson: Codable, Sendable, Equatable {
/// Test struct for structured output
struct TestPerson: Codable, Equatable {
let name: String
let age: Int
let email: String?
}
struct TestResponse: Codable, Sendable, Equatable {
struct TestResponse: Codable, Equatable {
let items: [String]
let count: Int
let metadata: TestMetadata?
struct TestMetadata: Codable, Sendable, Equatable {
struct TestMetadata: Codable, Equatable {
let timestamp: String
let version: String
}
}
@Test("streamObject basic functionality")
func streamObjectBasic() async throws {
@Test
func `streamObject basic functionality`() {
// Since we can't easily mock the provider, we'll test the helper functions
// that streamObject uses internally
@ -39,8 +38,8 @@ struct StreamObjectTests {
#expect(fixed2 == "{\"name\":\"Bob\",\"age\":}\"")
}
@Test("ObjectStreamDelta types and structure")
func objectStreamDeltaTypes() throws {
@Test
func `ObjectStreamDelta types and structure`() {
// Test the ObjectStreamDelta structure
let startDelta = ObjectStreamDelta<TestPerson>(type: .start)
#expect(startDelta.type == .start)
@ -73,8 +72,8 @@ struct StreamObjectTests {
#expect(errorDelta.error != nil)
}
@Test("StreamObjectResult structure")
func streamObjectResultStructure() throws {
@Test
func `StreamObjectResult structure`() {
// Test StreamObjectResult initialization
let testStream = AsyncThrowingStream<ObjectStreamDelta<TestPerson>, Error> { continuation in
Task {
@ -99,8 +98,8 @@ struct StreamObjectTests {
#expect(result.schema == TestPerson.self)
}
@Test("StreamObjectResult AsyncSequence iteration")
func streamObjectResultAsyncSequence() async throws {
@Test
func `StreamObjectResult AsyncSequence iteration`() async throws {
let testStream = AsyncThrowingStream<ObjectStreamDelta<TestPerson>, Error> { continuation in
Task {
continuation.yield(ObjectStreamDelta(type: .start))
@ -136,8 +135,8 @@ struct StreamObjectTests {
#expect(deltaCount == 4) // start, partial, complete, done
}
@Test("partialObjects filter method")
func partialObjectsFilter() async throws {
@Test
func `partialObjects filter method`() async throws {
let testStream = AsyncThrowingStream<ObjectStreamDelta<TestPerson>, Error> { continuation in
Task {
continuation.yield(ObjectStreamDelta(type: .start))
@ -174,8 +173,8 @@ struct StreamObjectTests {
#expect(partialObjects[2].name == "Person 3")
}
@Test("finalObject method")
func testFinalObject() async throws {
@Test
func `finalObject method`() async throws {
let testStream = AsyncThrowingStream<ObjectStreamDelta<TestPerson>, Error> { continuation in
Task {
continuation.yield(ObjectStreamDelta(type: .start))
@ -205,8 +204,8 @@ struct StreamObjectTests {
#expect(finalObject.email == "complete@test.com")
}
@Test("finalObject throws when no complete object")
func finalObjectThrows() async throws {
@Test
func `finalObject throws when no complete object`() async throws {
let testStream = AsyncThrowingStream<ObjectStreamDelta<TestPerson>, Error> { continuation in
Task {
continuation.yield(ObjectStreamDelta(type: .start))

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("Stream Transform Pipeline Tests")
struct StreamTransformTests {
@Test("FilterTransform filters elements correctly")
func testFilterTransform() async throws {
@Test
func `FilterTransform filters elements correctly`() async throws {
let transform = FilterTransform<Int> { $0 % 2 == 0 }
#expect(try await transform.transform(2) == 2)
@ -13,16 +12,16 @@ struct StreamTransformTests {
#expect(try await transform.transform(5) == nil)
}
@Test("MapTransform transforms elements")
func testMapTransform() async throws {
@Test
func `MapTransform transforms elements`() async throws {
let transform = MapTransform<Int, String> { "\($0 * 2)" }
#expect(try await transform.transform(5) == "10")
#expect(try await transform.transform(3) == "6")
}
@Test("BufferTransform batches elements")
func bufferTransform() async throws {
@Test
func `BufferTransform batches elements`() async throws {
let transform = BufferTransform<String>(bufferSize: 3)
// First two elements shouldn't trigger flush
@ -42,8 +41,8 @@ struct StreamTransformTests {
#expect(remaining == ["d", "e"])
}
@Test("BufferTransform with time interval")
func bufferTransformWithInterval() async throws {
@Test
func `BufferTransform with time interval`() async throws {
let transform = BufferTransform<Int>(bufferSize: 10, flushInterval: 0.1)
// Add elements
@ -61,8 +60,8 @@ struct StreamTransformTests {
#expect(batch?.contains(3) == true)
}
@Test("ThrottleTransform limits rate")
func throttleTransform() async throws {
@Test
func `ThrottleTransform limits rate`() async throws {
let transform = ThrottleTransform<String>(interval: 0.1)
// First element passes through
@ -78,8 +77,8 @@ struct StreamTransformTests {
#expect(try await transform.transform("third") == "third")
}
@Test("TapTransform adds side effects")
func tapTransform() async throws {
@Test
func `TapTransform adds side effects`() async throws {
actor SideEffectsCollector {
var values: [Int] = []
func append(_ value: Int) {
@ -97,8 +96,8 @@ struct StreamTransformTests {
#expect(await collector.values == [1, 2, 3])
}
@Test("Stream filter extension works")
func streamFilterExtension() async throws {
@Test
func `Stream filter extension works`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
Task {
for i in 1...5 {
@ -118,8 +117,8 @@ struct StreamTransformTests {
#expect(results == [2, 4])
}
@Test("Stream map extension works")
func streamMapExtension() async throws {
@Test
func `Stream map extension works`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
Task {
for i in 1...3 {
@ -139,8 +138,8 @@ struct StreamTransformTests {
#expect(results == [1, 4, 9])
}
@Test("Stream tap extension works")
func streamTapExtension() async throws {
@Test
func `Stream tap extension works`() async throws {
actor TapCollector {
var values: [String] = []
func append(_ value: String) {
@ -170,8 +169,8 @@ struct StreamTransformTests {
#expect(await collector.values == ["a", "b", "c"])
}
@Test("Stream buffer extension works")
func streamBufferExtension() async throws {
@Test
func `Stream buffer extension works`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
Task {
for i in 1...7 {
@ -194,8 +193,8 @@ struct StreamTransformTests {
#expect(batches[2] == [7]) // Remaining element
}
@Test("Stream throttle extension works")
func streamThrottleExtension() async throws {
@Test
func `Stream throttle extension works`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
Task {
for i in 1...5 {
@ -218,8 +217,8 @@ struct StreamTransformTests {
#expect(results.contains(1)) // First element always passes
}
@Test("StreamTextResult filter extension works")
func streamTextResultFilter() async throws {
@Test
func `StreamTextResult filter extension works`() async throws {
let stream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
continuation.yield(TextStreamDelta(type: .textDelta, content: "Hello"))
@ -251,8 +250,8 @@ struct StreamTransformTests {
#expect(count == 2) // Only text deltas
}
@Test("StreamTextResult collectText works")
func streamTextResultCollectText() async throws {
@Test
func `StreamTextResult collectText works`() async throws {
let stream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
continuation.yield(TextStreamDelta(type: .textDelta, content: "Hello"))
@ -277,8 +276,8 @@ struct StreamTransformTests {
#expect(texts == ["Hello", " ", "World"])
}
@Test("StreamTextResult fullText works")
func streamTextResultFullText() async throws {
@Test
func `StreamTextResult fullText works`() async throws {
let stream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
continuation.yield(TextStreamDelta(type: .textDelta, content: "The"))
@ -300,8 +299,8 @@ struct StreamTransformTests {
#expect(fullText == "The quick brown fox")
}
@Test("CombinedTransform chains transforms")
func combinedTransform() async throws {
@Test
func `CombinedTransform chains transforms`() async throws {
let filterTransform = FilterTransform<Int> { $0 % 2 == 0 }
let mapTransform = MapTransform<Int, String> { "\($0 * 10)" }
@ -315,8 +314,8 @@ struct StreamTransformTests {
#expect(try await combined.transform(4) == "40")
}
@Test("Transform chain with complex pipeline")
func complexTransformPipeline() async throws {
@Test
func `Transform chain with complex pipeline`() async throws {
let stream = AsyncThrowingStream<Int, Error> { continuation in
Task {
for i in 1...10 {

View File

@ -3,25 +3,24 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("TachikomaConfiguration Tests", .serialized)
@Suite(.serialized)
struct TachikomaConfigurationTests {
@Suite("Instance Creation Tests")
struct InstanceCreationTests {
@Test("Default constructor loads from environment")
func defaultConstructorLoadsEnvironment() async throws {
@Test
func `Default constructor loads from environment`() {
let config = TachikomaConfiguration()
// Should not crash and create valid instance
#expect(config.configuredProviders.isEmpty || !config.configuredProviders.isEmpty) // Either is fine
}
@Test("Constructor with loadFromEnvironment=false")
func constructorWithoutEnvironmentLoading() async throws {
@Test
func `Constructor with loadFromEnvironment=false`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
#expect(config.configuredProviders.isEmpty)
}
@Test("Convenience constructor with API keys")
func convenienceConstructorWithAPIKeys() async throws {
@Test
func `Convenience constructor with API keys`() {
let config = TachikomaConfiguration(
apiKeys: [
"openai": "test-openai-key",
@ -34,8 +33,8 @@ struct TachikomaConfigurationTests {
#expect(config.getAPIKey(for: .groq) == nil)
}
@Test("Convenience constructor with API keys and base URLs")
func convenienceConstructorWithAPIKeysAndBaseURLs() async throws {
@Test
func `Convenience constructor with API keys and base URLs`() {
let config = TachikomaConfiguration(
apiKeys: ["openai": "test-key"],
baseURLs: ["openai": "https://custom.openai.com"],
@ -46,10 +45,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Type-Safe API Tests")
struct TypeSafeAPITests {
@Test("Set and get API key with Provider enum")
func setAndGetAPIKeyTypeSafe() async throws {
@Test
func `Set and get API key with Provider enum`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("test-openai-key", for: .openai)
@ -62,8 +60,8 @@ struct TachikomaConfigurationTests {
#expect(config.getAPIKey(for: .groq) == nil)
}
@Test("Has API key checks")
func hasAPIKeyChecks() async throws {
@Test
func `Has API key checks`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
#expect(!config.hasAPIKey(for: .openai))
@ -75,8 +73,8 @@ struct TachikomaConfigurationTests {
#expect(config.hasConfiguredAPIKey(for: .openai))
}
@Test("Remove API key")
func removeAPIKey() async throws {
@Test
func `Remove API key`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("test-key", for: .anthropic)
@ -86,8 +84,8 @@ struct TachikomaConfigurationTests {
#expect(!config.hasAPIKey(for: .anthropic))
}
@Test("Set and get base URL")
func setAndGetBaseURL() async throws {
@Test
func `Set and get base URL`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Test custom base URL
@ -101,8 +99,8 @@ struct TachikomaConfigurationTests {
#expect(config.getBaseURL(for: .custom("test")) == nil)
}
@Test("Remove base URL falls back to default")
func removeBaseURLFallback() async throws {
@Test
func `Remove base URL falls back to default`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Set custom URL
@ -115,10 +113,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("String-Based Compatibility API Tests")
struct StringBasedAPITests {
@Test("String-based API delegates to type-safe API")
func stringBasedAPIDelegation() async throws {
@Test
func `String-based API delegates to type-safe API`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Set via string API
@ -135,8 +132,8 @@ struct TachikomaConfigurationTests {
#expect(config.hasAPIKey(for: "openai"))
}
@Test("String-based API handles custom providers")
func stringBasedCustomProviders() async throws {
@Test
func `String-based API handles custom providers`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("custom-key", for: "my-custom-provider")
@ -147,8 +144,8 @@ struct TachikomaConfigurationTests {
#expect(config.hasAPIKey(for: "my-custom-provider"))
}
@Test("String-based API case insensitive")
func stringBasedCaseInsensitive() async throws {
@Test
func `String-based API case insensitive`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("test-key", for: "OpenAI")
@ -159,10 +156,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Environment Variable Loading Tests")
struct EnvironmentVariableTests {
@Test("Environment vs configured key priority")
func environmentVsConfiguredPriority() async throws {
@Test
func `Environment vs configured key priority`() {
// Create config that loads from environment
let config = TachikomaConfiguration()
@ -177,8 +173,8 @@ struct TachikomaConfigurationTests {
// Environment key availability depends on actual environment
}
@Test("Environment detection methods")
func environmentDetectionMethods() async throws {
@Test
func `Environment detection methods`() {
_ = TachikomaConfiguration()
// Provider enum should detect environment independently
@ -192,10 +188,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Configuration State Tests")
struct ConfigurationStateTests {
@Test("Configured providers list")
func configuredProvidersList() async throws {
@Test
func `Configured providers list`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
#expect(config.configuredProviders.isEmpty)
@ -211,8 +206,8 @@ struct TachikomaConfigurationTests {
#expect(providers.contains(.custom("test")))
}
@Test("Clear all configuration")
func clearAllConfiguration() async throws {
@Test
func `Clear all configuration`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("key1", for: .openai)
@ -228,8 +223,8 @@ struct TachikomaConfigurationTests {
#expect(config.getBaseURL(for: .openai) == "https://api.openai.com/v1") // Falls back to default
}
@Test("Configuration summary")
func configurationSummary() async throws {
@Test
func `Configuration summary`() {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("anthropic-key", for: .anthropic)
@ -241,10 +236,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Thread Safety Tests")
struct ThreadSafetyTests {
@Test("Concurrent access is thread-safe")
func concurrentAccess() async throws {
@Test
func `Concurrent access is thread-safe`() async {
let config = TachikomaConfiguration(loadFromEnvironment: false)
// Test concurrent reads and writes
@ -271,10 +265,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Default Configuration Tests")
struct DefaultConfigurationTests {
@Test("Default configuration usage")
func defaultConfigurationUsage() async throws {
@Test
func `Default configuration usage`() {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -296,8 +289,8 @@ struct TachikomaConfigurationTests {
TachikomaConfiguration.default = nil
}
@Test("Resolve helper function")
func resolveHelperFunction() async throws {
@Test
func `Resolve helper function`() {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -325,8 +318,8 @@ struct TachikomaConfigurationTests {
TachikomaConfiguration.default = nil
}
@Test("Auto instance is singleton")
func autoInstanceIsSingleton() async throws {
@Test
func `Auto instance is singleton`() async throws {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -346,7 +339,7 @@ struct TachikomaConfigurationTests {
}
// All should be the same instance
let first = instances.first!
let first = try #require(instances.first)
for instance in instances {
#expect(instance === first)
}
@ -355,8 +348,8 @@ struct TachikomaConfigurationTests {
TachikomaConfiguration.default = nil
}
@Test("Generation functions with different configurations")
func generationFunctionsWithConfigs() async throws {
@Test
func `Generation functions with different configurations`() {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -410,8 +403,8 @@ struct TachikomaConfigurationTests {
TachikomaConfiguration.default = nil
}
@Test("Conversation uses provided configuration")
func conversationUsesProvidedConfig() async throws {
@Test
func `Conversation uses provided configuration`() {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -445,8 +438,8 @@ struct TachikomaConfigurationTests {
TachikomaConfiguration.default = nil
}
@Test("Thread safety stress test")
func threadSafetyStressTest() async throws {
@Test
func `Thread safety stress test`() async {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -492,10 +485,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Instance Isolation Tests")
struct InstanceIsolationTests {
@Test("Multiple instances are isolated")
func multipleInstancesIsolated() async throws {
@Test
func `Multiple instances are isolated`() {
let config1 = TachikomaConfiguration(loadFromEnvironment: false)
let config2 = TachikomaConfiguration(loadFromEnvironment: false)
@ -510,8 +502,8 @@ struct TachikomaConfigurationTests {
#expect(config2.getAPIKey(for: .openai) == "key2") // Should be unaffected
}
@Test("Instance isolation with same provider keys")
func instanceIsolationSameProviders() async throws {
@Test
func `Instance isolation with same provider keys`() {
let config1 = TachikomaConfiguration(apiKeys: ["openai": "config1-key"])
let config2 = TachikomaConfiguration(apiKeys: ["openai": "config2-key"])
@ -525,10 +517,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Configuration Priority Tests")
struct ConfigurationPriorityTests {
@Test("Configuration priority chain")
func configurationPriorityChain() async throws {
@Test
func `Configuration priority chain`() {
// Clear any existing default
TachikomaConfiguration.default = nil
@ -555,10 +546,9 @@ struct TachikomaConfigurationTests {
}
}
@Suite("Provider Factory Override Tests")
struct ProviderFactoryOverrideTests {
@Test("Custom factory override takes precedence")
func customFactoryOverrideTakesPrecedence() async throws {
@Test
func `Custom factory override takes precedence`() throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("mock-key", for: .openai)
@ -574,8 +564,8 @@ struct TachikomaConfigurationTests {
#expect(capturedModel == .openai(.gpt4o))
}
@Test("makeProvider falls back to real provider without override")
func makeProviderUsesRealFactory() async throws {
@Test
func `makeProvider falls back to real provider without override`() throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("mock-key", for: .openai)

View File

@ -2,10 +2,10 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Test Helpers Tests", .serialized)
@Suite(.serialized)
struct TestHelpersTests {
@Test("Test helper create configuration")
func helperCreateConfiguration() {
@Test
func `helper create configuration`() {
let config = TestHelpers.createTestConfiguration(apiKeys: ["openai": "test-key"])
let expected = TestHelpers.standardTestKeys["openai"]
@ -13,8 +13,8 @@ struct TestHelpersTests {
#expect(config.hasConfiguredAPIKey(for: .openai))
}
@Test("Test helper with empty configuration")
func helperEmptyConfiguration() async {
@Test
func `helper with empty configuration`() async {
let result = await TestHelpers.withEmptyTestConfiguration { config in
config.getAPIKey(for: .openai)
}
@ -23,8 +23,8 @@ struct TestHelpersTests {
#expect(result == nil)
}
@Test("Test helper with standard test configuration")
func helperStandardConfiguration() async {
@Test
func `helper with standard test configuration`() async {
let result = await TestHelpers.withStandardTestConfiguration { config in
config.getAPIKey(for: .openai)
}
@ -32,8 +32,8 @@ struct TestHelpersTests {
#expect(result == "test-key")
}
@Test("Test helper with selective configuration")
func helperSelectiveConfiguration() async {
@Test
func `helper with selective configuration`() async {
let result = await TestHelpers.withSelectiveTestConfiguration(present: ["openai"]) { config in
(config.getAPIKey(for: .openai), config.getAPIKey(for: .anthropic))
}

View File

@ -2,8 +2,8 @@ import Foundation
import Testing
@testable import Tachikoma
@Test("Debug Grok streaming issue")
func grokStreamingDebug() async throws {
@Test
func `Debug Grok streaming issue`() async throws {
guard ProcessInfo.processInfo.environment["RUN_GROK_DEBUG_TESTS"] == "1" else {
print("Skipping Grok debug stream: RUN_GROK_DEBUG_TESTS not set")
return

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("Embeddings API")
struct EmbeddingsAPITests {
@Test("EmbeddingModel enum cases")
func embeddingModelCases() {
@Test
func `EmbeddingModel enum cases`() {
// OpenAI models
let ada = EmbeddingModel.openai(.ada002)
let small3 = EmbeddingModel.openai(.small3)
@ -43,58 +42,58 @@ struct EmbeddingsAPITests {
}
}
@Test("OpenAI embedding model raw values")
func openAIEmbeddingModelRawValues() {
@Test
func `OpenAI embedding model raw values`() {
#expect(EmbeddingModel.OpenAIEmbedding.ada002.rawValue == "text-embedding-ada-002")
#expect(EmbeddingModel.OpenAIEmbedding.small3.rawValue == "text-embedding-3-small")
#expect(EmbeddingModel.OpenAIEmbedding.large3.rawValue == "text-embedding-3-large")
}
@Test("Cohere embedding model raw values")
func cohereEmbeddingModelRawValues() {
@Test
func `Cohere embedding model raw values`() {
#expect(EmbeddingModel.CohereEmbedding.english3.rawValue == "embed-english-v3.0")
#expect(EmbeddingModel.CohereEmbedding.multilingual3.rawValue == "embed-multilingual-v3.0")
#expect(EmbeddingModel.CohereEmbedding.englishLight3.rawValue == "embed-english-light-v3.0")
#expect(EmbeddingModel.CohereEmbedding.multilingualLight3.rawValue == "embed-multilingual-light-v3.0")
}
@Test("Voyage embedding model raw values")
func voyageEmbeddingModelRawValues() {
@Test
func `Voyage embedding model raw values`() {
#expect(EmbeddingModel.VoyageEmbedding.voyage2.rawValue == "voyage-2")
#expect(EmbeddingModel.VoyageEmbedding.voyage2Code.rawValue == "voyage-code-2")
#expect(EmbeddingModel.VoyageEmbedding.voyage2Large.rawValue == "voyage-large-2")
}
@Test("EmbeddingInput text variant")
func embeddingInputText() {
@Test
func `EmbeddingInput text variant`() {
let input = EmbeddingInput.text("Hello, world!")
#expect(input.asTexts == ["Hello, world!"])
}
@Test("EmbeddingInput texts variant")
func embeddingInputTexts() {
@Test
func `EmbeddingInput texts variant`() {
let texts = ["First text", "Second text", "Third text"]
let input = EmbeddingInput.texts(texts)
#expect(input.asTexts == texts)
}
@Test("EmbeddingInput tokens variant")
func embeddingInputTokens() {
@Test
func `EmbeddingInput tokens variant`() {
let tokens = [1234, 5678, 9012]
let input = EmbeddingInput.tokens(tokens)
#expect(input.asTexts.isEmpty) // Tokens don't convert to texts
}
@Test("EmbeddingSettings default values")
func embeddingSettingsDefaults() {
@Test
func `EmbeddingSettings default values`() {
let settings = EmbeddingSettings.default
#expect(settings.dimensions == nil)
#expect(settings.normalizeEmbeddings == true)
#expect(settings.truncate == nil)
}
@Test("EmbeddingSettings custom values")
func embeddingSettingsCustom() {
@Test
func `EmbeddingSettings custom values`() {
let settings = EmbeddingSettings(
dimensions: 512,
normalizeEmbeddings: false,
@ -106,15 +105,15 @@ struct EmbeddingsAPITests {
#expect(settings.truncate == .end)
}
@Test("EmbeddingSettings truncation strategies")
func embeddingSettingsTruncationStrategies() {
@Test
func `EmbeddingSettings truncation strategies`() {
#expect(EmbeddingSettings.TruncationStrategy.start.rawValue == "start")
#expect(EmbeddingSettings.TruncationStrategy.end.rawValue == "end")
#expect(EmbeddingSettings.TruncationStrategy.none.rawValue == "none")
}
@Test("EmbeddingResult properties")
func embeddingResultProperties() {
@Test
func `EmbeddingResult properties`() {
let embeddings = [
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
@ -133,8 +132,8 @@ struct EmbeddingsAPITests {
#expect(result.metadata?.normalizedL2 == true)
}
@Test("EmbeddingResult convenience properties")
func embeddingResultConvenienceProperties() {
@Test
func `EmbeddingResult convenience properties`() {
let embeddings = [[0.1, 0.2, 0.3, 0.4]]
let result = EmbeddingResult(
@ -149,8 +148,8 @@ struct EmbeddingsAPITests {
#expect(result.dimensions == 4)
}
@Test("EmbeddingResult with empty embeddings")
func embeddingResultEmpty() {
@Test
func `EmbeddingResult with empty embeddings`() {
let result = EmbeddingResult(
embeddings: [],
model: "test-model",
@ -160,8 +159,8 @@ struct EmbeddingsAPITests {
#expect(result.dimensions == nil)
}
@Test("EmbeddingMetadata properties")
func embeddingMetadata() {
@Test
func `EmbeddingMetadata properties`() {
let metadata1 = EmbeddingMetadata(truncated: true, normalizedL2: false)
#expect(metadata1.truncated == true)
#expect(metadata1.normalizedL2 == false)
@ -171,8 +170,8 @@ struct EmbeddingsAPITests {
#expect(metadata2.normalizedL2 == false)
}
@Test("EmbeddingSettings Codable")
func embeddingSettingsCodable() throws {
@Test
func `EmbeddingSettings Codable`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -190,8 +189,8 @@ struct EmbeddingsAPITests {
#expect(decoded.truncate == original.truncate)
}
@Test("EmbeddingMetadata Codable")
func embeddingMetadataCodable() throws {
@Test
func `EmbeddingMetadata Codable`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -204,8 +203,8 @@ struct EmbeddingsAPITests {
#expect(decoded.normalizedL2 == original.normalizedL2)
}
@Test("EmbeddingModel to LanguageModel conversion")
func embeddingModelToLanguageModel() {
@Test
func `EmbeddingModel to LanguageModel conversion`() {
let openaiModel = EmbeddingModel.openai(.small3)
let languageModel = openaiModel.toLanguageModel()
@ -226,8 +225,8 @@ struct EmbeddingsAPITests {
}
}
@Test("EmbeddingRequest structure")
func embeddingRequest() {
@Test
func `EmbeddingRequest structure`() {
let request = EmbeddingRequest(
input: .text("Test input"),
settings: EmbeddingSettings(dimensions: 128),
@ -237,8 +236,8 @@ struct EmbeddingsAPITests {
#expect(request.settings.dimensions == 128)
}
@Test("EmbeddingProviderFactory produces expected providers")
func embeddingProviderFactoryProducesExpectedProviders() throws {
@Test
func `EmbeddingProviderFactory produces expected providers`() throws {
let configuration = TachikomaConfiguration()
let openAIProvider = try EmbeddingProviderFactory.createProvider(

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("Enhanced Tool System")
struct EnhancedToolSystemTests {
@Test("AgentToolCall supports namespace and recipient")
func toolCallNamespaceRecipient() {
@Test
func `AgentToolCall supports namespace and recipient`() {
let toolCall = AgentToolCall(
id: "call-123",
name: "search",
@ -20,8 +19,8 @@ struct EnhancedToolSystemTests {
#expect(toolCall.arguments["query"]?.stringValue == "test")
}
@Test("AgentToolCall works without namespace and recipient")
func toolCallWithoutNamespaceRecipient() {
@Test
func `AgentToolCall works without namespace and recipient`() {
let toolCall = AgentToolCall(
name: "calculate",
arguments: ["expression": AnyAgentToolValue(string: "2+2")],
@ -32,8 +31,8 @@ struct EnhancedToolSystemTests {
#expect(toolCall.name == "calculate")
}
@Test("AgentTool supports namespace and recipient")
func agentToolNamespaceRecipient() {
@Test
func `AgentTool supports namespace and recipient`() {
let tool = AgentTool(
name: "readFile",
description: "Read file contents",
@ -59,8 +58,8 @@ struct EnhancedToolSystemTests {
#expect(tool.description == "Read file contents")
}
@Test("AgentTool works without namespace and recipient")
func agentToolWithoutNamespaceRecipient() {
@Test
func `AgentTool works without namespace and recipient`() {
let tool = AgentTool(
name: "echo",
description: "Echo input",
@ -72,8 +71,8 @@ struct EnhancedToolSystemTests {
#expect(tool.name == "echo")
}
@Test("Tool organization by namespace")
func toolOrganizationByNamespace() {
@Test
func `Tool organization by namespace`() {
let tools = [
AgentTool(
name: "readFile",
@ -112,8 +111,8 @@ struct EnhancedToolSystemTests {
#expect(toolsByNamespace["web"]?.count == 1)
}
@Test("Tool routing by recipient")
func toolRoutingByRecipient() {
@Test
func `Tool routing by recipient`() {
let tools = [
AgentTool(
name: "query",
@ -141,8 +140,8 @@ struct EnhancedToolSystemTests {
#expect(replicaTool?.name == "query")
}
@Test("AgentToolCall Codable with namespace and recipient")
func toolCallCodableWithNamespaceRecipient() throws {
@Test
func `AgentToolCall Codable with namespace and recipient`() throws {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
let decoder = JSONDecoder()
@ -165,8 +164,8 @@ struct EnhancedToolSystemTests {
#expect(decoded.arguments["query"] == original.arguments["query"])
}
@Test("AgentToolCall Codable without namespace and recipient")
func toolCallCodableWithoutNamespaceRecipient() throws {
@Test
func `AgentToolCall Codable without namespace and recipient`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -183,11 +182,13 @@ struct EnhancedToolSystemTests {
#expect(decoded.name == "calculate")
}
@Test("Tool execution with namespace context")
func toolExecutionWithNamespaceContext() async throws {
@Test
func `Tool execution with namespace context`() async throws {
actor NamespaceCapture {
var namespace: String?
func set(_ value: String) { self.namespace = value }
func set(_ value: String) {
self.namespace = value
}
}
let capture = NamespaceCapture()
@ -210,8 +211,8 @@ struct EnhancedToolSystemTests {
#expect(await capture.namespace == "test-namespace")
}
@Test("Multiple tools with same name but different namespaces")
func multipleToolsSameNameDifferentNamespaces() {
@Test
func `Multiple tools with same name but different namespaces`() {
let webSearch = AgentTool(
name: "search",
description: "Web search",

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("Multi-Channel Response System")
struct MultiChannelResponseTests {
@Test("ResponseChannel enum has all expected cases")
func responseChannelCases() {
@Test
func `ResponseChannel enum has all expected cases`() {
let allCases = ResponseChannel.allCases
#expect(allCases.count == 4)
#expect(allCases.contains(.thinking))
@ -13,16 +12,16 @@ struct MultiChannelResponseTests {
#expect(allCases.contains(.final))
}
@Test("ResponseChannel raw values are correct")
func responseChannelRawValues() {
@Test
func `ResponseChannel raw values are correct`() {
#expect(ResponseChannel.thinking.rawValue == "thinking")
#expect(ResponseChannel.analysis.rawValue == "analysis")
#expect(ResponseChannel.commentary.rawValue == "commentary")
#expect(ResponseChannel.final.rawValue == "final")
}
@Test("ModelMessage supports channel property")
func modelMessageChannel() {
@Test
func `ModelMessage supports channel property`() {
let message = ModelMessage(
role: .assistant,
content: [.text("This is my reasoning")],
@ -34,8 +33,8 @@ struct MultiChannelResponseTests {
#expect(message.content.first == .text("This is my reasoning"))
}
@Test("ModelMessage supports metadata property")
func modelMessageMetadata() {
@Test
func `ModelMessage supports metadata property`() {
let metadata = MessageMetadata(
conversationId: "conv-123",
turnId: "turn-456",
@ -53,8 +52,8 @@ struct MultiChannelResponseTests {
#expect(message.metadata?.customData?["key"] == "value")
}
@Test("TextStreamDelta supports channel events")
func textStreamDeltaChannelEvents() {
@Test
func `TextStreamDelta supports channel events`() {
// Channel information is now passed via the channel property, not event types
let reasoningDelta = TextStreamDelta(
type: .reasoning,
@ -84,8 +83,8 @@ struct MultiChannelResponseTests {
#expect(textDelta.content == "Reasoning about the problem")
}
@Test("MessageMetadata equality")
func messageMetadataEquality() {
@Test
func `MessageMetadata equality`() {
let metadata1 = MessageMetadata(
conversationId: "123",
turnId: "456",
@ -108,8 +107,8 @@ struct MultiChannelResponseTests {
#expect(metadata1 != metadata3)
}
@Test("Legacy messages work without channel")
func legacyMessageCompatibility() {
@Test
func `Legacy messages work without channel`() {
// Old API still works
let message = ModelMessage.user("Hello")
@ -119,8 +118,8 @@ struct MultiChannelResponseTests {
#expect(message.content.first == .text("Hello"))
}
@Test("Convenience initializers preserve nil channel")
func convenienceInitializers() {
@Test
func `Convenience initializers preserve nil channel`() {
let systemMessage = ModelMessage.system("You are helpful")
let userMessage = ModelMessage.user("Hello")
let assistantMessage = ModelMessage.assistant("Hi there")
@ -130,8 +129,8 @@ struct MultiChannelResponseTests {
#expect(assistantMessage.channel == nil)
}
@Test("Channel-aware message creation")
func channelAwareMessageCreation() {
@Test
func `Channel-aware message creation`() {
let thinkingMessage = ModelMessage(
role: .assistant,
content: [.text("Let me think about this...")],
@ -155,8 +154,8 @@ struct MultiChannelResponseTests {
#expect(finalMessage.channel == .final)
}
@Test("Codable conformance for ResponseChannel")
func responseChannelCodable() throws {
@Test
func `Codable conformance for ResponseChannel`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -171,8 +170,8 @@ struct MultiChannelResponseTests {
#expect(jsonString == "\"thinking\"")
}
@Test("Codable conformance for MessageMetadata")
func messageMetadataCodable() throws {
@Test
func `Codable conformance for MessageMetadata`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("Reasoning Effort Levels")
struct ReasoningEffortTests {
@Test("ReasoningEffort enum has all expected cases")
func reasoningEffortCases() {
@Test
func `ReasoningEffort enum has all expected cases`() {
let allCases = ReasoningEffort.allCases
#expect(allCases.count == 3)
#expect(allCases.contains(.low))
@ -12,15 +11,15 @@ struct ReasoningEffortTests {
#expect(allCases.contains(.high))
}
@Test("ReasoningEffort raw values are correct")
func reasoningEffortRawValues() {
@Test
func `ReasoningEffort raw values are correct`() {
#expect(ReasoningEffort.low.rawValue == "low")
#expect(ReasoningEffort.medium.rawValue == "medium")
#expect(ReasoningEffort.high.rawValue == "high")
}
@Test("GenerationSettings supports reasoning effort")
func generationSettingsReasoningEffort() {
@Test
func `GenerationSettings supports reasoning effort`() {
let settings = GenerationSettings(
maxTokens: 1000,
temperature: 0.7,
@ -32,14 +31,14 @@ struct ReasoningEffortTests {
#expect(settings.temperature == 0.7)
}
@Test("GenerationSettings default has nil reasoning effort")
func generationSettingsDefaultReasoningEffort() {
@Test
func `GenerationSettings default has nil reasoning effort`() {
let settings = GenerationSettings.default
#expect(settings.reasoningEffort == nil)
}
@Test("RetryHandler adapts based on reasoning effort")
func retryHandlerReasoningEffortAdaptation() {
@Test
func `RetryHandler adapts based on reasoning effort`() {
// High effort should use aggressive policy
let highSettings = GenerationSettings(reasoningEffort: .high)
let highHandler = RetryHandler.from(settings: highSettings)
@ -65,8 +64,8 @@ struct ReasoningEffortTests {
#expect(Bool(true))
}
@Test("Codable conformance for ReasoningEffort")
func reasoningEffortCodable() throws {
@Test
func `Codable conformance for ReasoningEffort`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -81,8 +80,8 @@ struct ReasoningEffortTests {
}
}
@Test("GenerationSettings Codable with reasoning effort")
func generationSettingsCodableWithReasoningEffort() throws {
@Test
func `GenerationSettings Codable with reasoning effort`() throws {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
let decoder = JSONDecoder()
@ -103,8 +102,8 @@ struct ReasoningEffortTests {
#expect(decoded.reasoningEffort == original.reasoningEffort)
}
@Test("GenerationSettings Codable without reasoning effort")
func generationSettingsCodableWithoutReasoningEffort() throws {
@Test
func `GenerationSettings Codable without reasoning effort`() throws {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -122,8 +121,8 @@ struct ReasoningEffortTests {
#expect(decoded.temperature == 0.7)
}
@Test("All reasoning effort levels properly ordered")
func reasoningEffortOrdering() {
@Test
func `All reasoning effort levels properly ordered`() {
// Ensure we have the expected reasoning levels
let levels: [ReasoningEffort] = [.low, .medium, .high]

View File

@ -1,7 +1,7 @@
import Testing
@testable import Tachikoma
// Helper class for thread-safe mutable value in closures
/// Helper class for thread-safe mutable value in closures
final class Box<T>: @unchecked Sendable {
var value: T
init(value: T) {
@ -9,10 +9,9 @@ final class Box<T>: @unchecked Sendable {
}
}
@Suite("Response Caching")
struct ResponseCacheTests {
@Test("ResponseCache initialization")
func responseCacheInitialization() async {
@Test
func `ResponseCache initialization`() async {
let config = CacheConfiguration(maxEntries: 50, defaultTTL: 1800)
let cache = ResponseCache(configuration: config)
// Note: statistics() is not a public method, commenting out for now
@ -32,8 +31,8 @@ struct ResponseCacheTests {
#expect(cached == nil)
}
@Test("ResponseCache store and retrieve")
func responseCacheStoreRetrieve() async {
@Test
func `ResponseCache store and retrieve`() async {
let cache = ResponseCache()
let request = ProviderRequest(
@ -60,8 +59,8 @@ struct ResponseCacheTests {
#expect(cached?.finishReason == .stop)
}
@Test("ResponseCache cache miss")
func responseCacheMiss() async {
@Test
func `ResponseCache cache miss`() async {
let cache = ResponseCache()
let request = ProviderRequest(
@ -75,8 +74,8 @@ struct ResponseCacheTests {
#expect(cached == nil)
}
@Test("ResponseCache TTL expiration")
func responseCacheTTLExpiration() async throws {
@Test
func `ResponseCache TTL expiration`() async throws {
let config = CacheConfiguration(defaultTTL: 0.1) // 100ms TTL
let cache = ResponseCache(configuration: config)
@ -102,8 +101,8 @@ struct ResponseCacheTests {
#expect(cached2 == nil)
}
@Test("ResponseCache LRU eviction")
func responseCacheLRUEviction() async {
@Test
func `ResponseCache LRU eviction`() async {
let config = CacheConfiguration(maxEntries: 2) // Small cache
let cache = ResponseCache(configuration: config)
@ -151,8 +150,8 @@ struct ResponseCacheTests {
#expect(cached3?.text == "Response 3")
}
@Test("ResponseCache clear")
func responseCacheClear() async {
@Test
func `ResponseCache clear`() async {
let cache = ResponseCache()
// Store multiple entries
@ -180,8 +179,8 @@ struct ResponseCacheTests {
// #expect(stats.validEntries == 0)
}
@Test("ResponseCache statistics")
func responseCacheStatistics() async {
@Test
func `ResponseCache statistics`() async {
let config = CacheConfiguration(maxEntries: 100, defaultTTL: 3600)
let cache = ResponseCache(configuration: config)
@ -210,8 +209,8 @@ struct ResponseCacheTests {
// #expect(stats.newestEntry != nil)
}
@Test("CacheKey generation deterministic")
func cacheKeyDeterministic() {
@Test
func `CacheKey generation deterministic`() {
let messages = [
ModelMessage.user("Hello"),
ModelMessage.assistant("Hi there"),
@ -237,8 +236,8 @@ struct ResponseCacheTests {
#expect(key1.model == key2.model)
}
@Test("CacheKey differs for different requests")
func cacheKeyDifferent() {
@Test
func `CacheKey differs for different requests`() {
let request1 = ProviderRequest(
messages: [ModelMessage.user("Hello")],
tools: nil,
@ -268,8 +267,8 @@ struct ResponseCacheTests {
#expect(key1.hash != key3.hash)
}
@Test("CacheKey includes tools in hash")
func cacheKeyIncludesTools() {
@Test
func `CacheKey includes tools in hash`() {
let tool1 = AgentTool(
name: "tool1",
description: "First tool",
@ -312,8 +311,8 @@ struct ResponseCacheTests {
#expect(key2.hash != key3.hash)
}
@Test("CachedProvider wraps provider correctly")
func cachedProviderWrapper() async throws {
@Test
func `CachedProvider wraps provider correctly`() async {
let cache = ResponseCache()
// Create a mock provider
@ -330,8 +329,8 @@ struct ResponseCacheTests {
#expect(cachedProvider.apiKey == mockProvider.apiKey)
}
@Test("CachedProvider caches generateText")
func cachedProviderGenerateText() async throws {
@Test
func `CachedProvider caches generateText`() async throws {
let cache = ResponseCache()
// Use a simple counter that can be modified in the closure
@ -363,8 +362,8 @@ struct ResponseCacheTests {
#expect(callCount.value == 1) // Provider not called again
}
@Test("CachedProvider doesn't cache streaming")
func cachedProviderStreamText() async throws {
@Test
func `CachedProvider doesn't cache streaming`() async throws {
let cache = ResponseCache()
let callCount = Box(value: 0)
@ -401,10 +400,21 @@ private struct ResponseCacheMockProvider: ModelProvider {
var onGenerateText: (@Sendable (ProviderRequest) -> Void)?
var onStreamText: (@Sendable (ProviderRequest) -> Void)?
var modelId: String { "mock-model" }
var baseURL: String? { nil }
var apiKey: String? { nil }
var capabilities: ModelCapabilities { ModelCapabilities() }
var modelId: String {
"mock-model"
}
var baseURL: String? {
nil
}
var apiKey: String? {
nil
}
var capabilities: ModelCapabilities {
ModelCapabilities()
}
init(
model: LanguageModel,

View File

@ -1,9 +1,8 @@
import Testing
@testable import Tachikoma
@Suite("Retry Handler")
struct RetryHandlerTests {
// Helper actor for thread-safe counting
/// Helper actor for thread-safe counting
actor CallCounter {
private(set) var count = 0
@ -16,8 +15,8 @@ struct RetryHandlerTests {
}
}
@Test("RetryPolicy default values")
func retryPolicyDefaults() {
@Test
func `RetryPolicy default values`() {
let policy = RetryPolicy()
#expect(policy.maxAttempts == 3)
#expect(policy.baseDelay == 1.0)
@ -26,8 +25,8 @@ struct RetryHandlerTests {
#expect(policy.jitterRange == 0.9...1.1)
}
@Test("RetryPolicy aggressive configuration")
func retryPolicyAggressive() {
@Test
func `RetryPolicy aggressive configuration`() {
let policy = RetryPolicy.aggressive
#expect(policy.maxAttempts == 5)
#expect(policy.baseDelay == 0.5)
@ -35,8 +34,8 @@ struct RetryHandlerTests {
#expect(policy.exponentialBase == 1.5)
}
@Test("RetryPolicy conservative configuration")
func retryPolicyConservative() {
@Test
func `RetryPolicy conservative configuration`() {
let policy = RetryPolicy.conservative
#expect(policy.maxAttempts == 2)
#expect(policy.baseDelay == 2.0)
@ -44,8 +43,8 @@ struct RetryHandlerTests {
#expect(policy.exponentialBase == 2.0)
}
@Test("RetryPolicy delay calculation")
func retryPolicyDelayCalculation() {
@Test
func `RetryPolicy delay calculation`() {
let policy = RetryPolicy(
baseDelay: 1.0,
maxDelay: 10.0,
@ -74,8 +73,8 @@ struct RetryHandlerTests {
#expect(delay4 == 10.0) // 1.0 * 2^4 = 16.0, clamped to 10.0
}
@Test("RetryPolicy delay with jitter")
func retryPolicyDelayWithJitter() {
@Test
func `RetryPolicy delay with jitter`() {
let policy = RetryPolicy(
baseDelay: 1.0,
exponentialBase: 2.0,
@ -88,8 +87,8 @@ struct RetryHandlerTests {
#expect(delay <= 1.5) // 1.0 * 1.5
}
@Test("RetryPolicy default shouldRetry for rate limits")
func retryPolicyDefaultShouldRetryRateLimits() {
@Test
func `RetryPolicy default shouldRetry for rate limits`() {
let shouldRetry = RetryPolicy.defaultShouldRetry
// Should retry rate limits
@ -100,8 +99,8 @@ struct RetryHandlerTests {
#expect(shouldRetry(rateLimitNoRetryAfter) == true)
}
@Test("RetryPolicy default shouldRetry for network errors")
func retryPolicyDefaultShouldRetryNetworkErrors() {
@Test
func `RetryPolicy default shouldRetry for network errors`() {
let shouldRetry = RetryPolicy.defaultShouldRetry
// Should retry network errors
@ -109,8 +108,8 @@ struct RetryHandlerTests {
#expect(shouldRetry(networkError) == true)
}
@Test("RetryPolicy default shouldRetry for API errors")
func retryPolicyDefaultShouldRetryAPIErrors() {
@Test
func `RetryPolicy default shouldRetry for API errors`() {
let shouldRetry = RetryPolicy.defaultShouldRetry
// Should retry specific API errors
@ -125,8 +124,8 @@ struct RetryHandlerTests {
#expect(shouldRetry(TachikomaError.apiError("Model not found")) == false)
}
@Test("RetryPolicy default shouldRetry for non-retryable errors")
func retryPolicyDefaultShouldRetryNonRetryableErrors() {
@Test
func `RetryPolicy default shouldRetry for non-retryable errors`() {
let shouldRetry = RetryPolicy.defaultShouldRetry
// Should not retry authentication failures
@ -139,8 +138,8 @@ struct RetryHandlerTests {
#expect(shouldRetry(TachikomaError.unsupportedOperation("Not implemented")) == false)
}
@Test("RetryHandler execute with success")
func retryHandlerExecuteSuccess() async throws {
@Test
func `RetryHandler execute with success`() async throws {
let handler = RetryHandler()
let callCounter = CallCounter()
@ -153,8 +152,8 @@ struct RetryHandlerTests {
#expect(await callCounter.count == 1) // Should succeed on first try
}
@Test("RetryHandler execute with retries")
func retryHandlerExecuteWithRetries() async throws {
@Test
func `RetryHandler execute with retries`() async throws {
let policy = RetryPolicy(
maxAttempts: 3,
baseDelay: 0.01,
@ -190,8 +189,8 @@ struct RetryHandlerTests {
}
}
@Test("RetryHandler execute exhausts retries")
func retryHandlerExecuteExhaustsRetries() async throws {
@Test
func `RetryHandler execute exhausts retries`() async throws {
let policy = RetryPolicy(
maxAttempts: 2,
baseDelay: 0.01,
@ -210,8 +209,8 @@ struct RetryHandlerTests {
#expect(await callCounter.count == 2) // Should try maxAttempts times
}
@Test("RetryHandler execute with non-retryable error")
func retryHandlerExecuteNonRetryableError() async throws {
@Test
func `RetryHandler execute with non-retryable error`() async throws {
let handler = RetryHandler()
let callCounter = CallCounter()
@ -225,8 +224,8 @@ struct RetryHandlerTests {
#expect(await callCounter.count == 1) // Should not retry
}
@Test("RetryHandler executeStream success")
func retryHandlerExecuteStreamSuccess() async throws {
@Test
func `RetryHandler executeStream success`() async throws {
let handler = RetryHandler()
let callCounter = CallCounter()
@ -248,8 +247,8 @@ struct RetryHandlerTests {
#expect(await callCounter.count == 1)
}
@Test("RetryHandler from GenerationSettings")
func retryHandlerFromGenerationSettings() {
@Test
func `RetryHandler from GenerationSettings`() {
// High effort uses aggressive policy
let highSettings = GenerationSettings(reasoningEffort: .high)
let highHandler = RetryHandler.from(settings: highSettings)

View File

@ -1,10 +1,9 @@
import Testing
@testable import Tachikoma
@Suite("TachikomaCLI Placeholder Tests")
struct PlaceholderTests {
@Test("Placeholder test")
func placeholderTest() {
@Test
func `Placeholder test`() {
// This is a placeholder test to satisfy Package.swift test target requirements
#expect(Bool(true))
}

View File

@ -3,10 +3,9 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("Integration Tests")
struct IntegrationTests {
@Test("End-to-end UI message flow with streaming")
func uIMessageFlowWithStreaming() async throws {
@Test
func `End-to-end UI message flow with streaming`() async {
// Create UI messages
let uiMessages = [
UIMessage(role: .system, content: "You are a helpful assistant"),
@ -45,8 +44,8 @@ struct IntegrationTests {
#expect(assistantMessage.content == "The answer is 4")
}
@Test("Tool execution with simplified builder and error handling")
func toolExecutionWithErrorHandling() async throws {
@Test
func `Tool execution with simplified builder and error handling`() async throws {
struct MathInput: Codable, Sendable {
let operation: String
let a: Double
@ -115,8 +114,8 @@ struct IntegrationTests {
}
}
@Test("Async operations with timeout and cancellation")
func asyncOperationsWithTimeoutAndCancellation() async throws {
@Test
func `Async operations with timeout and cancellation`() async throws {
let token = CancellationToken()
let task = Task {
@ -143,8 +142,8 @@ struct IntegrationTests {
#expect(await token.cancelled)
}
@Test("Provider with feature parity and caching")
func providerWithFeatureParityAndCaching() async throws {
@Test
func `Provider with feature parity and caching`() async throws {
// Create a mock provider with limited capabilities
struct LimitedProvider: ModelProvider {
let modelId = "limited-model"
@ -219,8 +218,8 @@ struct IntegrationTests {
#expect(stats.hits >= 1)
}
@Test("Complete tool workflow with context and error recovery")
func completeToolWorkflow() async throws {
@Test
func `Complete tool workflow with context and error recovery`() async throws {
// Create a contextual tool
let searchTool = AgentTool.createWithContext(
name: "contextual_search",

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("LMStudio Provider Tests")
struct LMStudioProviderTests {
@Test("Provider initialization")
func providerInitialization() async throws {
@Test
func `Provider initialization`() {
let provider = LMStudioProvider(
baseURL: "http://localhost:1234/v1",
modelId: "gpt-oss-120b",
@ -25,8 +24,8 @@ struct LMStudioProviderTests {
#expect(capabilities.supportsStreaming == true)
}
@Test("Model enum integration")
func modelEnumIntegration() async throws {
@Test
func `Model enum integration`() {
let model1 = LanguageModel.lmstudio(.gptOSS120B)
let model2 = LanguageModel.lmstudio(.gptOSS20B)
let model3 = LanguageModel.lmstudio(.current)
@ -39,8 +38,8 @@ struct LMStudioProviderTests {
#expect(model1.contextLength == 128_000)
}
@Test("Convenience properties")
func convenienceProperties() async throws {
@Test
func `Convenience properties`() {
let model1 = LanguageModel.gptOSS120B // Ollama version
let model2 = LanguageModel.gptOSS120B_LMStudio // LMStudio version
@ -51,8 +50,8 @@ struct LMStudioProviderTests {
#expect(model2.modelId == "gpt-oss-120b")
}
@Test("Provider factory creation")
func providerFactoryCreation() async throws {
@Test
func `Provider factory creation`() throws {
let config = TachikomaConfiguration()
// Test LMStudio provider creation
@ -66,8 +65,8 @@ struct LMStudioProviderTests {
#expect(provider is LMStudioProvider)
}
@Test("Response channel parsing")
func responseChannelParsing() async throws {
@Test
func `Response channel parsing`() {
let parser = LocalModelResponseParser.self
// Test multi-channel response
@ -106,8 +105,8 @@ struct LMStudioProviderTests {
#expect(channels3[.final] == "Here's the answer without a final tag.")
}
@Test("Auto-detection (mock)")
func autoDetection() async throws {
@Test
func `Auto-detection (mock)`() async throws {
// This test would normally try to connect to LMStudio
// In CI/mock mode, it should handle the failure gracefully
@ -125,8 +124,8 @@ struct LMStudioProviderTests {
}
}
@Test("Request mapping")
func requestMapping() async throws {
@Test
func `Request mapping`() {
_ = LMStudioProvider() // Just verify it can be created
let request = ProviderRequest(

View File

@ -2,9 +2,8 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Provider Feature Parity Tests")
struct ProviderFeatureParityTests {
// Mock provider for testing
/// Mock provider for testing
struct MockProvider: ModelProvider {
let modelId = "mock-model"
let baseURL: String? = nil
@ -27,8 +26,8 @@ struct ProviderFeatureParityTests {
}
}
@Test("Provider configuration defaults")
func providerConfigurationDefaults() throws {
@Test
func `Provider configuration defaults`() {
let config = ProviderConfiguration()
#expect(config.maxTokens == 4096)
@ -40,8 +39,8 @@ struct ProviderFeatureParityTests {
#expect(config.requiresAlternatingRoles == false)
}
@Test("Provider configuration presets")
func providerConfigurationPresets() throws {
@Test
func `Provider configuration presets`() {
let openAI = ProviderConfiguration.openAI
#expect(openAI.supportsSystemRole == true)
#expect(openAI.requiresAlternatingRoles == false)
@ -59,8 +58,8 @@ struct ProviderFeatureParityTests {
#expect(ollama.maxToolCalls == 0) // No tool support by default
}
@Test("Provider feature detection")
func providerFeatureDetection() throws {
@Test
func `Provider feature detection`() {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: true,
@ -82,8 +81,8 @@ struct ProviderFeatureParityTests {
#expect(adapter.isFeatureSupported(ProviderFeature.systemMessages))
}
@Test("System message transformation")
func systemMessageTransformation() async throws {
@Test
func `System message transformation`() throws {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: false,
@ -116,8 +115,8 @@ struct ProviderFeatureParityTests {
#expect(validated[1].role == .user)
}
@Test("Alternating roles enforcement")
func alternatingRolesEnforcement() async throws {
@Test
func `Alternating roles enforcement`() throws {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: false,
@ -146,8 +145,8 @@ struct ProviderFeatureParityTests {
#expect(validated.count < messages.count)
}
@Test("Vision input validation")
func visionInputValidation() async throws {
@Test
func `Vision input validation`() throws {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: false, // Doesn't support vision
@ -187,8 +186,8 @@ struct ProviderFeatureParityTests {
}
}
@Test("Tool limit enforcement")
func toolLimitEnforcement() async throws {
@Test
func `Tool limit enforcement`() {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: false,
@ -222,8 +221,8 @@ struct ProviderFeatureParityTests {
#expect(config.maxToolCalls == 2)
}
@Test("Provider feature flags")
func providerFeatureFlags() throws {
@Test
func `Provider feature flags`() {
for feature in ProviderFeature.allCases {
// Verify all features have string representations
#expect(!feature.rawValue.isEmpty)
@ -235,8 +234,8 @@ struct ProviderFeatureParityTests {
#expect(ProviderFeature.allCases.contains(.visionInputs))
}
@Test("Provider adapter wrapping")
func providerAdapterWrapping() throws {
@Test
func `Provider adapter wrapping`() {
let provider = MockProvider(
capabilities: ModelCapabilities(
supportsVision: true,

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Anthropic interleaved defaults")
struct AnthropicInterleavedDefaultsTests {
@Test("Merged beta header includes required interleaved flags")
func mergedBetaHeaderIncludesRequiredFlags() {
@Test
func `Merged beta header includes required interleaved flags`() {
let header = AnthropicProvider.mergedBetaHeader(existing: nil)
#expect(header.contains("interleaved-thinking-2025-05-14"))
#expect(header.contains("fine-grained-tool-streaming-2025-05-14"))
@ -23,8 +22,8 @@ struct AnthropicInterleavedDefaultsTests {
#expect(parts.contains("fine-grained-tool-streaming-2025-05-14"))
}
@Test("Provider request includes beta header and thinking payload")
func providerRequestIncludesBetaHeaderAndThinkingPayload() throws {
@Test
func `Provider request includes beta header and thinking payload`() throws {
let config = TachikomaConfiguration(apiKeys: ["anthropic": "test-key"])
let provider = try AnthropicProvider(model: .opus45, configuration: config)
@ -58,8 +57,8 @@ struct AnthropicInterleavedDefaultsTests {
#expect(thinking["budget_tokens"] as? Int == 12000)
}
@Test("Provider respects custom baseURL")
func providerRespectsCustomBaseURL() throws {
@Test
func `Provider respects custom baseURL`() throws {
let config = TachikomaConfiguration(
apiKeys: ["anthropic": "test-key"],
baseURLs: ["anthropic": "https://entropic.example/v1"],
@ -71,8 +70,8 @@ struct AnthropicInterleavedDefaultsTests {
#expect(urlRequest.url?.absoluteString == "https://entropic.example/v1/messages")
}
@Test("Stream delta decodes thinking_delta payload")
func streamDeltaDecodesThinkingDeltaPayload() throws {
@Test
func `Stream delta decodes thinking_delta payload`() throws {
let data = try #require("{\"type\":\"thinking_delta\",\"thinking\":\"ok\"}".data(using: .utf8))
let delta = try JSONDecoder().decode(AnthropicStreamDelta.self, from: data)
#expect(delta.type == "thinking_delta")
@ -80,16 +79,16 @@ struct AnthropicInterleavedDefaultsTests {
#expect(delta.text == nil)
}
@Test("Stream delta decodes signature_delta payload")
func streamDeltaDecodesSignatureDeltaPayload() throws {
@Test
func `Stream delta decodes signature_delta payload`() throws {
let data = try #require("{\"type\":\"signature_delta\",\"signature\":\"sig\"}".data(using: .utf8))
let delta = try JSONDecoder().decode(AnthropicStreamDelta.self, from: data)
#expect(delta.type == "signature_delta")
#expect(delta.signature == "sig")
}
@Test("Signed thinking blocks are preserved for assistant messages")
func signedThinkingBlocksArePreservedForAssistantMessages() throws {
@Test
func `Signed thinking blocks are preserved for assistant messages`() throws {
let config = TachikomaConfiguration(apiKeys: ["anthropic": "test-key"])
let provider = try AnthropicProvider(model: .opus45, configuration: config)
@ -125,8 +124,8 @@ struct AnthropicInterleavedDefaultsTests {
#expect(assistant.first?["signature"] as? String == "sig")
}
@Test("Redacted thinking blocks preserve signature without text")
func redactedThinkingBlocksPreserveSignatureWithoutText() throws {
@Test
func `Redacted thinking blocks preserve signature without text`() throws {
let config = TachikomaConfiguration(apiKeys: ["anthropic": "test-key"])
let provider = try AnthropicProvider(model: .opus45, configuration: config)
@ -161,8 +160,8 @@ struct AnthropicInterleavedDefaultsTests {
#expect(assistant.first?["signature"] as? String == "sig-redacted")
}
@Test("Thinking stays enabled even without signed history")
func thinkingStaysEnabledEvenWithoutSignedHistory() throws {
@Test
func `Thinking stays enabled even without signed history`() throws {
let config = TachikomaConfiguration(apiKeys: ["anthropic": "test-key"])
let provider = try AnthropicProvider(model: .opus45, configuration: config)

View File

@ -32,8 +32,13 @@ private final class AzureTestURLProtocol: URLProtocol {
}
""".utf8Data()
override class func canInit(with _: URLRequest) -> Bool { true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override class func canInit(with _: URLRequest) -> Bool {
true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
Task { [request] in await AzureTestURLProtocol.storeMedia(request) }
@ -65,7 +70,7 @@ private final class AzureTestURLProtocol: URLProtocol {
}
}
@Suite("Azure OpenAI Provider", .serialized)
@Suite(.serialized)
struct AzureOpenAIProviderTests {
private func makeSession() -> URLSession {
let configuration = URLSessionConfiguration.ephemeral
@ -73,8 +78,8 @@ struct AzureOpenAIProviderTests {
return URLSession(configuration: configuration)
}
@Test("Builds Azure chat URL with api-version and api-key header")
func buildsAzureURLAndHeaders() async throws {
@Test
func `Builds Azure chat URL with api-version and api-key header`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("test-key", for: .azureOpenAI)
await AzureTestURLProtocol.reset()
@ -106,8 +111,8 @@ struct AzureOpenAIProviderTests {
#expect(sentRequest?.value(forHTTPHeaderField: "api-key") == "test-key")
}
@Test("Prefers bearer token auth and explicit endpoint")
func prefersBearerToken() async throws {
@Test
func `Prefers bearer token auth and explicit endpoint`() async throws {
setenv("AZURE_OPENAI_BEARER_TOKEN", "bearer-123", 1)
setenv("AZURE_OPENAI_ENDPOINT", "https://custom.azure.example.com", 1)
defer {

View File

@ -3,7 +3,7 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Provider Integration Tests", .enabled(if: ProcessInfo.processInfo.environment["INTEGRATION_TESTS"] != nil))
@Suite(.enabled(if: ProcessInfo.processInfo.environment["INTEGRATION_TESTS"] != nil))
struct ProviderIntegrationTests {
// MARK: - Test Configuration
@ -21,22 +21,34 @@ struct ProviderIntegrationTests {
return !value.isEmpty
}
private static var hasOpenAIKey: Bool { hasEnv("OPENAI_API_KEY") }
private static var hasAnthropicKey: Bool { hasEnv("ANTHROPIC_API_KEY") }
private static var hasOpenAIKey: Bool {
hasEnv("OPENAI_API_KEY")
}
private static var hasAnthropicKey: Bool {
hasEnv("ANTHROPIC_API_KEY")
}
private static var hasGoogleKey: Bool {
hasEnv("GEMINI_API_KEY") || hasEnv("GOOGLE_API_KEY")
}
private static var hasMistralKey: Bool { hasEnv("MISTRAL_API_KEY") }
private static var hasGroqKey: Bool { hasEnv("GROQ_API_KEY") }
private static var hasMistralKey: Bool {
hasEnv("MISTRAL_API_KEY")
}
private static var hasGroqKey: Bool {
hasEnv("GROQ_API_KEY")
}
private static var hasGrokKey: Bool {
hasEnv("X_AI_API_KEY") || hasEnv("XAI_API_KEY") || hasEnv("GROK_API_KEY")
}
// MARK: - OpenAI Integration Tests
@Test("OpenAI Provider - Real API Call", .enabled(if: Self.hasOpenAIKey))
func openAIIntegration() async throws {
@Test(.enabled(if: Self.hasOpenAIKey))
func `OpenAI Provider - Real API Call`() async throws {
let model = Model.openai(.gpt4oMini)
let config = TachikomaConfiguration()
do {
@ -58,8 +70,8 @@ struct ProviderIntegrationTests {
}
}
@Test("OpenAI Provider - Tool Calling", .enabled(if: Self.hasOpenAIKey))
func openAIToolCalling() async throws {
@Test(.enabled(if: Self.hasOpenAIKey))
func `OpenAI Provider - Tool Calling`() async throws {
let model = Model.openai(.gpt4oMini)
let config = TachikomaConfiguration()
@ -103,8 +115,8 @@ struct ProviderIntegrationTests {
}
}
@Test("OpenAI Provider - Streaming", .enabled(if: Self.hasOpenAIKey))
func openAIStreaming() async throws {
@Test(.enabled(if: Self.hasOpenAIKey))
func `OpenAI Provider - Streaming`() async throws {
let model = Model.openai(.gpt4oMini)
let config = TachikomaConfiguration()
@ -150,8 +162,8 @@ struct ProviderIntegrationTests {
// MARK: - Anthropic Integration Tests
@Test("Anthropic Provider - Real API Call", .enabled(if: Self.hasAnthropicKey))
func anthropicIntegration() async throws {
@Test(.enabled(if: Self.hasAnthropicKey))
func `Anthropic Provider - Real API Call`() async throws {
let model = Model.anthropic(.sonnet4)
do {
let response = try await generate(
@ -169,8 +181,8 @@ struct ProviderIntegrationTests {
}
}
@Test("Anthropic Provider - Tool Calling", .enabled(if: Self.hasAnthropicKey))
func anthropicToolCalling() async throws {
@Test(.enabled(if: Self.hasAnthropicKey))
func `Anthropic Provider - Tool Calling`() async throws {
let model = Model.anthropic(.sonnet4)
let config = TachikomaConfiguration()
@ -213,8 +225,8 @@ struct ProviderIntegrationTests {
// MARK: - Ollama Integration Tests
@Test("Ollama Provider - Real API Call")
func ollamaIntegration() async throws {
@Test
func `Ollama Provider - Real API Call`() async throws {
// Check if Ollama is running
let ollamaRunning = await isOllamaRunning()
guard ollamaRunning else {
@ -247,8 +259,8 @@ struct ProviderIntegrationTests {
// MARK: - Grok Integration Tests
@Test("Grok Provider - Real API Call", .enabled(if: Self.hasGrokKey))
func grokIntegration() async throws {
@Test(.enabled(if: Self.hasGrokKey))
func `Grok Provider - Real API Call`() async throws {
let model = Model.grok(.grok3)
do {
let response = try await generate(
@ -267,8 +279,8 @@ struct ProviderIntegrationTests {
// MARK: - Google Integration Tests
@Test("Google Provider - Real API Call", .enabled(if: Self.hasGoogleKey))
func googleIntegration() async throws {
@Test(.enabled(if: Self.hasGoogleKey))
func `Google Provider - Real API Call`() async throws {
let model = Model.google(.gemini25Flash)
do {
let response = try await generate(
@ -287,8 +299,8 @@ struct ProviderIntegrationTests {
// MARK: - Mistral Integration Tests
@Test("Mistral Provider - Real API Call", .enabled(if: Self.hasMistralKey))
func mistralIntegration() async throws {
@Test(.enabled(if: Self.hasMistralKey))
func `Mistral Provider - Real API Call`() async throws {
let model = Model.mistral(.small)
let response = try await generate(TestConfig.shortMessage, using: model, maxTokens: 50, temperature: 0.0)
@ -298,8 +310,8 @@ struct ProviderIntegrationTests {
// MARK: - Groq Integration Tests
@Test("Groq Provider - Real API Call", .enabled(if: Self.hasGroqKey))
func groqIntegration() async throws {
@Test(.enabled(if: Self.hasGroqKey))
func `Groq Provider - Real API Call`() async throws {
let model = Model.groq(.llama38b)
let response = try await generate(TestConfig.shortMessage, using: model, maxTokens: 50, temperature: 0.0)
@ -309,8 +321,8 @@ struct ProviderIntegrationTests {
// MARK: - Multi-Modal Integration Tests
@Test("Multi-Modal Provider - Vision Support", .enabled(if: Self.hasOpenAIKey))
func multiModalVision() async throws {
@Test(.enabled(if: Self.hasOpenAIKey))
func `Multi-Modal Provider - Vision Support`() async throws {
let model = Model.openai(.gpt4o)
let config = TachikomaConfiguration()
let provider = try ProviderFactory.createProvider(for: model, configuration: config)

View File

@ -2,12 +2,12 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Provider System Tests", .serialized)
@Suite(.serialized)
struct ProviderSystemTests {
// MARK: - Provider Factory Tests
@Test("Provider Factory - OpenAI Provider Creation")
func providerFactoryOpenAI() async throws {
@Test
func `Provider Factory - OpenAI Provider Creation`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["openai": "test-key"]) { config in
let model = Model.openai(.gpt4o)
let provider = try ProviderFactory.createProvider(for: model, configuration: config)
@ -19,8 +19,8 @@ struct ProviderSystemTests {
}
}
@Test("Provider Factory - Anthropic Provider Creation")
func providerFactoryAnthropic() async throws {
@Test
func `Provider Factory - Anthropic Provider Creation`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["anthropic": "test-key"]) { config in
let model = Model.anthropic(.opus4)
let provider = try ProviderFactory.createProvider(for: model, configuration: config)
@ -32,8 +32,8 @@ struct ProviderSystemTests {
}
}
@Test("Provider Factory - Grok Provider Creation")
func providerFactoryGrok() async throws {
@Test
func `Provider Factory - Grok Provider Creation`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["grok": "test-key"]) { config in
let model = Model.grok(.grok4FastReasoning)
let provider = try ProviderFactory.createProvider(for: model, configuration: config)
@ -44,8 +44,8 @@ struct ProviderSystemTests {
}
}
@Test("Provider Factory - Grok catalog coverage")
func providerFactoryGrokCatalog() async throws {
@Test
func `Provider Factory - Grok catalog coverage`() async throws {
try await TestHelpers.withTestConfiguration(apiKeys: ["grok": "test-key"]) { config in
for grokModel in Model.Grok.allCases {
let model = Model.grok(grokModel)
@ -55,8 +55,8 @@ struct ProviderSystemTests {
}
}
@Test("Provider Factory - Ollama Provider Creation")
func providerFactoryOllama() async throws {
@Test
func `Provider Factory - Ollama Provider Creation`() throws {
// No API key needed for Ollama
let config = TachikomaConfiguration(loadFromEnvironment: false)
let model = Model.ollama(.llama33)
@ -67,8 +67,8 @@ struct ProviderSystemTests {
#expect(provider.capabilities.supportsStreaming == true)
}
@Test("Provider Factory - Missing API Key Error")
func providerFactoryMissingAPIKey() async throws {
@Test
func `Provider Factory - Missing API Key Error`() async {
await TestHelpers.withEmptyTestConfiguration { config in
// Test the actual provider constructors directly since ProviderFactory
// uses MockProvider in test mode to avoid hitting real APIs
@ -100,8 +100,8 @@ struct ProviderSystemTests {
// MARK: - Model Capabilities Tests
@Test("Model Capabilities - Vision Support")
func modelCapabilitiesVision() {
@Test
func `Model Capabilities - Vision Support`() {
#expect(Model.openai(.gpt4o).supportsVision == true)
#expect(Model.openai(.gpt4oMini).supportsVision == true)
#expect(Model.openai(.gpt41).supportsVision == false)
@ -119,8 +119,8 @@ struct ProviderSystemTests {
#expect(Model.ollama(.custom("qwen2.5vl:latest")).supportsVision == true)
}
@Test("Model Capabilities - Tool Support")
func modelCapabilitiesTools() {
@Test
func `Model Capabilities - Tool Support`() {
#expect(Model.openai(.gpt4o).supportsTools == true)
#expect(Model.openai(.gpt41).supportsTools == true)
@ -134,8 +134,8 @@ struct ProviderSystemTests {
#expect(Model.ollama(.custom("qwen2.5vl:latest")).supportsTools == false)
}
@Test("Model Capabilities - Streaming Support")
func modelCapabilitiesStreaming() {
@Test
func `Model Capabilities - Streaming Support`() {
#expect(Model.openai(.gpt4o).supportsStreaming == true)
#expect(Model.anthropic(.opus4).supportsStreaming == true)
#expect(Model.grok(.grok4).supportsStreaming == true)
@ -144,8 +144,8 @@ struct ProviderSystemTests {
// MARK: - Generation Request Tests
@Test("Generation Request Basic Creation")
func generationRequestBasic() {
@Test
func `Generation Request Basic Creation`() {
let request = ProviderRequest(
messages: [ModelMessage(role: .user, content: [.text("Hello world")])],
tools: nil,
@ -160,8 +160,8 @@ struct ProviderSystemTests {
#expect(request.outputFormat == nil)
}
@Test("Generation Request With Images")
func generationRequestWithImages() {
@Test
func `Generation Request With Images`() {
let imageContent = ModelMessage.ContentPart.ImageContent(data: "test-base64-data")
let request = ProviderRequest(
messages: [
@ -186,8 +186,8 @@ struct ProviderSystemTests {
// MARK: - Stream Token Tests
@Test("Stream Token Types")
func streamTokenTypes() {
@Test
func `Stream Token Types`() {
let textToken = TextStreamDelta(type: .textDelta, content: "hello")
#expect(textToken.content == "hello")
#expect(textToken.type == .textDelta)
@ -205,8 +205,8 @@ struct ProviderSystemTests {
// MARK: - Usage Statistics Tests
@Test("Usage Statistics")
func usageStatistics() {
@Test
func `Usage Statistics`() {
let usage = Usage(inputTokens: 100, outputTokens: 50)
#expect(usage.inputTokens == 100)
@ -216,8 +216,8 @@ struct ProviderSystemTests {
// MARK: - Finish Reason Tests
@Test("Finish Reason Cases")
func finishReasonCases() {
@Test
func `Finish Reason Cases`() {
#expect(FinishReason.stop.rawValue == "stop")
#expect(FinishReason.length.rawValue == "length")
#expect(FinishReason.toolCalls.rawValue == "tool_calls")

View File

@ -6,14 +6,14 @@ import Testing
@testable import Tachikoma
#if os(Linux)
@Suite("OpenAI Responses API Tests", .disabled("URLProtocol mocking unavailable on Linux"))
@Suite(.disabled("URLProtocol mocking unavailable on Linux"))
struct OpenAIResponsesProviderTests {}
#else
@Suite("OpenAI Responses API Tests", .serialized)
@Suite(.serialized)
struct OpenAIResponsesProviderTests {
@Test("GPT-5+ uses Responses API provider")
func gPT5UsesResponsesProvider() throws {
@Test
func `GPT-5+ uses Responses API provider`() throws {
// Test that GPT-5 models use the OpenAIResponsesProvider
let config = self.openAIConfig()
@ -38,8 +38,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("GPT-5.1 text.verbosity parameter is set correctly")
func gPT51TextVerbosityParameter() throws {
@Test
func `GPT-5.1 text.verbosity parameter is set correctly`() throws {
// Test that the text.verbosity parameter is properly configured for GPT-5.1
let config = self.openAIConfig()
@ -69,8 +69,8 @@ struct OpenAIResponsesProviderTests {
#expect(provider.capabilities.supportsVision == true)
}
@Test("Reasoning models use Responses API")
func reasoningModelsUseResponsesAPI() throws {
@Test
func `Reasoning models use Responses API`() throws {
// Test that reasoning-oriented models also use the OpenAIResponsesProvider
let config = self.openAIConfig()
@ -96,8 +96,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Legacy models use standard OpenAI provider")
func legacyModelsUseStandardProvider() throws {
@Test
func `Legacy models use standard OpenAI provider`() throws {
// Test that non-GPT-5/reasoning models use the standard OpenAIProvider
let config = self.openAIConfig()
@ -116,8 +116,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("TextConfig encodes verbosity correctly")
func textConfigEncoding() throws {
@Test
func `TextConfig encodes verbosity correctly`() throws {
// Test that TextConfig properly encodes the verbosity parameter
let textConfig = TextConfig(verbosity: .high)
@ -128,8 +128,8 @@ struct OpenAIResponsesProviderTests {
#expect(json?["verbosity"] as? String == "high")
}
@Test("OpenAIResponsesRequest includes text config for GPT-5")
func responsesRequestTextConfig() throws {
@Test
func `OpenAIResponsesRequest includes text config for GPT-5`() throws {
// Test that the request properly includes text config
let textConfig = TextConfig(verbosity: .medium)
let request = OpenAIResponsesRequest(
@ -165,8 +165,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("GPT-5 tool call outputs are parsed")
func gpt5ToolCallParsing() throws {
@Test
func `GPT-5 tool call outputs are parsed`() throws {
let toolCall = OpenAIResponsesResponse.ResponsesToolCall(
id: "call_1",
type: "function",
@ -208,8 +208,8 @@ struct OpenAIResponsesProviderTests {
#expect(providerResponse.finishReason == .toolCalls)
}
@Test("Responses provider hits /v1/responses and encodes body")
func openAIResponsesRequestEncoding() async throws {
@Test
func `Responses provider hits /v1/responses and encodes body`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("live-openai", for: .openai)
@ -239,8 +239,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Responses payload uses data URL string for images")
func openAIResponsesImageDataURL() async throws {
@Test
func `Responses payload uses data URL string for images`() async throws {
let config = self.openAIConfig()
try await self.withMockedSession { request in
@ -271,8 +271,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Responses image_url accepts legacy object and normalizes to string")
func openAIResponsesLegacyImageObject() async throws {
@Test
func `Responses image_url accepts legacy object and normalizes to string`() throws {
// Craft a legacy-style payload (image_url object) and ensure decoder tolerates it.
let legacyJSON: [String: Any] = [
"type": "input_image",
@ -290,8 +290,8 @@ struct OpenAIResponsesProviderTests {
#expect(encodedJSON?["image_url"] as? String == "data:image/png;base64,LEGACY")
}
@Test("Responses provider emits tool schemas with parameters")
func openAIResponsesIncludesToolSchemas() async throws {
@Test
func `Responses provider emits tool schemas with parameters`() async throws {
let config = self.openAIConfig()
let tool = AgentTool(
name: "app",
@ -349,8 +349,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Function call history encodes into Responses input")
func openAIResponsesEncodesFunctionCalls() async throws {
@Test
func `Function call history encodes into Responses input`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("live-openai", for: .openai)
@ -399,8 +399,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Responses tool output is emitted even when result is empty")
func openAIResponsesEmitsToolOutputForEmptyResult() async throws {
@Test
func `Responses tool output is emitted even when result is empty`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("live-openai", for: .openai)
@ -441,8 +441,8 @@ struct OpenAIResponsesProviderTests {
}
}
@Test("Responses provider streams accumulated deltas")
func openAIResponsesStreaming() async throws {
@Test
func `Responses provider streams accumulated deltas`() async throws {
let config = TachikomaConfiguration(loadFromEnvironment: false)
config.setAPIKey("live-openai", for: .openai)
@ -638,7 +638,7 @@ private final class ResponsesTestURLProtocol: URLProtocol {
override func stopLoading() {}
}
// Helper to skip tests when API keys aren't available
/// Helper to skip tests when API keys aren't available
struct TestSkipped: Error {
let message: String

View File

@ -12,16 +12,16 @@ import Glibc
#endif
#if os(Linux)
@Suite("Provider Network E2E Tests", .disabled("URLProtocol mocking unavailable on Linux"))
@Suite(.disabled("URLProtocol mocking unavailable on Linux"))
struct ProviderEndToEndTests {}
#else
@Suite("Provider Network E2E Tests", .serialized, .enabled(if: !_isLiveSuite))
@Suite(.serialized, .enabled(if: !_isLiveSuite))
struct ProviderEndToEndTests {
// MARK: - OpenAI Responses (GPT-5)
@Test("OpenAI Responses provider returns text")
func openAIResponsesProvider() async throws {
@Test
func `OpenAI Responses provider returns text`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(
request,
@ -45,8 +45,8 @@ struct ProviderEndToEndTests {
// MARK: - OpenAI Chat Provider
@Test("OpenAI chat provider hits /chat/completions")
func openAIChatProvider() async throws {
@Test
func `OpenAI chat provider hits /chat/completions`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(request, endsWith: "/chat/completions")
return NetworkMocking.jsonResponse(
@ -65,8 +65,8 @@ struct ProviderEndToEndTests {
// MARK: - Anthropic
@Test("Anthropic provider decodes Claude responses")
func anthropicProvider() async throws {
@Test
func `Anthropic provider decodes Claude responses`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(request, endsWith: "/messages")
return NetworkMocking.jsonResponse(for: request, data: Self.anthropicPayload(text: "Claude says hello"))
@ -82,8 +82,8 @@ struct ProviderEndToEndTests {
// MARK: - Google Gemini
@Test("Google provider processes streamed SSE content")
func googleProvider() async throws {
@Test
func `Google provider processes streamed SSE content`() async throws {
try await NetworkMocking.withMockedNetwork { request in
#expect(request.url?.path.contains(":streamGenerateContent") == true)
return NetworkMocking.streamResponse(for: request, data: Self.googleStreamPayload(text: "Gemini streaming"))
@ -99,23 +99,23 @@ struct ProviderEndToEndTests {
// MARK: - OpenAI-compatible providers
@Test("Mistral provider uses OpenAI-compatible flow")
func mistralProvider() async throws {
@Test
func `Mistral provider uses OpenAI-compatible flow`() async throws {
try await self.assertOpenAICompatibleProvider(.mistral(.small), provider: .mistral)
}
@Test("Groq provider uses OpenAI-compatible flow")
func groqProvider() async throws {
@Test
func `Groq provider uses OpenAI-compatible flow`() async throws {
try await self.assertOpenAICompatibleProvider(.groq(.llama38b), provider: .groq)
}
@Test("Grok provider uses OpenAI-compatible flow")
func grokProvider() async throws {
@Test
func `Grok provider uses OpenAI-compatible flow`() async throws {
try await self.assertOpenAICompatibleProvider(.grok(.grok4FastReasoning), provider: .grok)
}
@Test("All Grok catalog models share the same OpenAI-compatible flow")
func grokCatalogUsesSameFlow() async throws {
@Test
func `All Grok catalog models share the same OpenAI-compatible flow`() async throws {
for grokModel in Model.Grok.allCases {
try await self.assertOpenAICompatibleProvider(.grok(grokModel), provider: .grok)
}
@ -123,8 +123,8 @@ struct ProviderEndToEndTests {
// MARK: - Ollama
@Test("Ollama provider handles local responses")
func ollamaProvider() async throws {
@Test
func `Ollama provider handles local responses`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(request, endsWith: "/api/chat")
return NetworkMocking.jsonResponse(for: request, data: Self.ollamaPayload(text: "Ollama local reply"))
@ -138,8 +138,8 @@ struct ProviderEndToEndTests {
}
}
@Test("Ollama provider encodes vision images as messages[].images")
func ollamaProviderEncodesImages() async throws {
@Test
func `Ollama provider encodes vision images as messages[].images`() async throws {
let imageBase64 = Data("test-image".utf8).base64EncodedString()
let image = ModelMessage.ContentPart.ImageContent(data: imageBase64, mimeType: "image/png")
@ -180,8 +180,8 @@ struct ProviderEndToEndTests {
// MARK: - LMStudio
@Test("LMStudio provider maps OpenAI-style responses")
func lmstudioProvider() async throws {
@Test
func `LMStudio provider maps OpenAI-style responses`() async throws {
try await NetworkMocking.withMockedNetwork { request in
let path = request.url?.path ?? ""
#expect(path.contains("chat/completions"))
@ -199,8 +199,8 @@ struct ProviderEndToEndTests {
// MARK: - Aggregators & Compatible Providers
@Test("OpenRouter provider uses OpenAI-compatible flow")
func openRouterProvider() async throws {
@Test
func `OpenRouter provider uses OpenAI-compatible flow`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(request, endsWith: "/chat/completions")
#expect(request.value(forHTTPHeaderField: "HTTP-Referer") == "https://peekaboo.app")
@ -216,8 +216,8 @@ struct ProviderEndToEndTests {
}
}
@Test("Together provider uses OpenAI-compatible flow")
func togetherProvider() async throws {
@Test
func `Together provider uses OpenAI-compatible flow`() async throws {
try await NetworkMocking.withMockedNetwork { request in
let path = request.url?.path ?? ""
#expect(path.hasSuffix("/chat/completions"))
@ -232,8 +232,8 @@ struct ProviderEndToEndTests {
}
}
@Test("Replicate provider uses OpenAI-compatible flow")
func replicateProvider() async throws {
@Test
func `Replicate provider uses OpenAI-compatible flow`() async throws {
try await NetworkMocking.withMockedNetwork { request in
let path = request.url?.path ?? ""
#expect(path.hasSuffix("/chat/completions"))
@ -251,8 +251,8 @@ struct ProviderEndToEndTests {
}
}
@Test("OpenAI-compatible provider hits custom base URL")
func openAICompatibleProvider() async throws {
@Test
func `OpenAI-compatible provider hits custom base URL`() async throws {
try await NetworkMocking.withMockedNetwork { request in
#expect(request.url?.absoluteString == "https://compatible.test/chat/completions")
return NetworkMocking.jsonResponse(
@ -273,8 +273,8 @@ struct ProviderEndToEndTests {
}
}
@Test("Anthropic-compatible provider decodes responses")
func anthropicCompatibleProvider() async throws {
@Test
func `Anthropic-compatible provider decodes responses`() async throws {
try await NetworkMocking.withMockedNetwork { request in
self.expectPath(request, endsWith: "/messages")
return NetworkMocking.jsonResponse(for: request, data: Self.anthropicPayload(text: "Compat Claude"))

View File

@ -3,10 +3,9 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("Simplified Tools Tests")
struct SimplifiedToolsTests {
@Test("Create simple tool with schema builder")
func toolSchemaBuilder() throws {
@Test
func `Create simple tool with schema builder`() {
let schema = ToolSchemaBuilder()
.string("name", description: "User's name", required: true)
.integer("age", description: "User's age", required: true)
@ -20,8 +19,8 @@ struct SimplifiedToolsTests {
#expect(schema.required.contains("age"))
}
@Test("SimplifiedToolBuilder with structured input")
func simplifiedToolBuilderWithCodable() async throws {
@Test
func `SimplifiedToolBuilder with structured input`() async throws {
struct CalculatorInput: Codable, Sendable {
let expression: String
}
@ -51,8 +50,8 @@ struct SimplifiedToolsTests {
#expect(result.doubleValue == 42.0 || result.objectValue?["result"]?.doubleValue == 42.0)
}
@Test("SimplifiedToolBuilder with context")
func testToolWithContext() async throws {
@Test
func `SimplifiedToolBuilder with context`() async throws {
struct SearchInput: Codable, Sendable {
let query: String
}
@ -92,8 +91,8 @@ struct SimplifiedToolsTests {
}
}
@Test("Simple tool without structured types")
func simpleToolWithoutCodable() async throws {
@Test
func `Simple tool without structured types`() async throws {
let tool = SimplifiedToolBuilder.simpleTool(
"echo",
description: "Echo the input",
@ -122,8 +121,8 @@ struct SimplifiedToolsTests {
#expect(result.objectValue?["echoed"]?.stringValue == "HELLO")
}
@Test("AgentTool.create with schema builder")
func agentToolCreate() async throws {
@Test
func `AgentTool.create with schema builder`() async throws {
let tool = AgentTool.create(
name: "weather",
description: "Get weather information",
@ -159,8 +158,8 @@ struct SimplifiedToolsTests {
#expect(result.objectValue?["units"]?.stringValue == "fahrenheit")
}
@Test("Tool schema builder with all parameter types")
func schemaBuilderAllTypes() throws {
@Test
func `Tool schema builder with all parameter types`() {
let schema = ToolSchemaBuilder()
.string("text", description: "Text field")
.number("price", description: "Price field")
@ -182,8 +181,8 @@ struct SimplifiedToolsTests {
#expect(properties["metadata"]?.type == .object)
}
@Test("Tool parameter property with enum values")
func toolParameterWithEnum() throws {
@Test
func `Tool parameter property with enum values`() {
let schema = ToolSchemaBuilder()
.string("status", description: "Status", required: true, enum: ["active", "inactive", "pending"])
.build()

View File

@ -2,10 +2,9 @@ import Testing
@testable import Tachikoma
@testable import TachikomaAgent
@Suite("Dynamic Tools System Tests")
struct DynamicToolsTests {
@Test("DynamicTool creates valid AgentTool")
func dynamicToolToAgentTool() async throws {
@Test
func `DynamicTool creates valid AgentTool`() async throws {
let schema = DynamicSchema(
type: .object,
properties: [
@ -36,8 +35,8 @@ struct DynamicToolsTests {
#expect(result.stringValue?.contains("Searched for:") == true)
}
@Test("DynamicSchema converts to AgentToolParameters")
func schemaConversion() throws {
@Test
func `DynamicSchema converts to AgentToolParameters`() {
let schema = DynamicSchema(
type: .object,
properties: [
@ -57,8 +56,8 @@ struct DynamicToolsTests {
#expect(parameters.properties["active"]?.type == .boolean)
}
@Test("SchemaProperty handles nested structures")
func nestedSchemaProperty() throws {
@Test
func `SchemaProperty handles nested structures`() {
let addressSchema = DynamicSchema.SchemaProperty(
type: .object,
description: "Address",
@ -80,8 +79,8 @@ struct DynamicToolsTests {
#expect(parameters.properties["address"]?.type == .object)
}
@Test("DynamicToolRegistry manages tools")
func dynamicToolRegistry() async throws {
@Test
func `DynamicToolRegistry manages tools`() async throws {
let registry = DynamicToolRegistry()
// Create a mock provider with a tool
@ -119,8 +118,8 @@ struct DynamicToolsTests {
#expect(remainingTools.isEmpty)
}
@Test("DynamicToolProvider discovers tools")
func dynamicToolProvider() async throws {
@Test
func `DynamicToolProvider discovers tools`() async throws {
let searchTool = DynamicTool(
name: "search_web",
description: "Search the web",
@ -213,8 +212,8 @@ struct DynamicToolsTests {
// SchemaBuilder doesn't exist in the expected form
}*/
@Test("Box type for recursive schemas")
func boxType() throws {
@Test
func `Box type for recursive schemas`() {
// Create a recursive structure (like a tree node)
let nodeSchema = DynamicSchema.SchemaProperty(
type: .object,
@ -233,8 +232,8 @@ struct DynamicToolsTests {
#expect(nodeSchema.type == .object)
}
@Test("Non-object schema conversion")
func nonObjectSchemaConversion() throws {
@Test
func `Non-object schema conversion`() {
// Test conversion of non-object schema
// Since non-object schemas aren't wrapped, we should test object schemas
let objectSchema = DynamicSchema(
@ -249,8 +248,8 @@ struct DynamicToolsTests {
#expect(parameters.required == ["value"])
}
@Test("Clear registry")
func clearRegistry() async throws {
@Test
func `Clear registry`() async throws {
let registry = DynamicToolRegistry()
// Add multiple tools

View File

@ -2,12 +2,11 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Tool System Tests")
struct ToolSystemTests {
// MARK: - AgentTool Tests
@Test("AgentTool Creation")
func agentToolCreation() throws {
@Test
func `AgentTool Creation`() {
// Create a simple tool using createTool helper
let addTool = createTool(
name: "add",
@ -37,8 +36,8 @@ struct ToolSystemTests {
#expect(addTool.parameters.required == ["a", "b"])
}
@Test("Tool Execution")
func toolExecution() async throws {
@Test
func `Tool Execution`() async throws {
// Create calculator tool
let calculatorTool = createTool(
name: "calculate",
@ -72,8 +71,8 @@ struct ToolSystemTests {
}
}
@Test("Built-in Tools")
func builtInTools() async throws {
@Test
func `Built-in Tools`() async throws {
// Test weatherTool
#expect(weatherTool.name == "get_weather")
@ -95,8 +94,8 @@ struct ToolSystemTests {
}
}
@Test("Tool Parameter Types")
func toolParameterTypes() {
@Test
func `Tool Parameter Types`() {
// Test all parameter types
let params = [
AgentToolParameterProperty(
@ -137,8 +136,8 @@ struct ToolSystemTests {
}
}
@Test("Tool Arguments")
func toolArguments() throws {
@Test
func `Tool Arguments`() throws {
let args = AgentToolArguments([
"string": AnyAgentToolValue(string: "hello"),
"int": AnyAgentToolValue(int: 42),
@ -168,8 +167,8 @@ struct ToolSystemTests {
}
}
@Test("Tool Error Handling")
func toolErrorHandling() throws {
@Test
func `Tool Error Handling`() throws {
let args = AgentToolArguments([:])
// Test missing required argument

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("UI Integration Tests")
struct UIIntegrationTests {
@Test("Convert UIMessage to ModelMessage")
func uIMessageToModelMessage() throws {
@Test
func `Convert UIMessage to ModelMessage`() throws {
// Create a UIMessage with various content
let uiMessage = try UIMessage(
role: .user,
@ -51,8 +50,8 @@ struct UIIntegrationTests {
}
}
@Test("Convert ModelMessage to UIMessage")
func modelMessageToUIMessage() throws {
@Test
func `Convert ModelMessage to UIMessage`() throws {
// Create a ModelMessage with various content
let modelMessage = try ModelMessage(
role: .assistant,
@ -80,8 +79,8 @@ struct UIIntegrationTests {
#expect(uiMessages[0].toolCalls?.count == 1)
}
@Test("StreamTextResult to UI Message Stream")
func streamToUIMessageStream() async throws {
@Test
func `StreamTextResult to UI Message Stream`() async {
// Create a mock stream
let textStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
@ -137,8 +136,8 @@ struct UIIntegrationTests {
}
}
@Test("Collect text from stream")
func collectTextFromStream() async throws {
@Test
func `Collect text from stream`() async throws {
// Create a mock stream
let textStream = AsyncThrowingStream<TextStreamDelta, Error> { continuation in
Task {
@ -168,8 +167,8 @@ struct UIIntegrationTests {
#expect(collectedText == "The quick brown fox")
}
@Test("UIStreamResponse collect message")
func uIStreamResponseCollectMessage() async throws {
@Test
func `UIStreamResponse collect message`() async {
let stream = AsyncStream<UIMessageChunk> { continuation in
continuation.yield(.text("Hello"))
continuation.yield(.text(" world"))
@ -195,8 +194,8 @@ struct UIIntegrationTests {
#expect(message.toolCalls?[0].name == "test")
}
@Test("UIAttachment with data URL")
func uIAttachmentDataURL() throws {
@Test
func `UIAttachment with data URL`() {
let imageData = Data("test image".utf8)
let attachment = UIAttachment(
type: .image,
@ -211,8 +210,8 @@ struct UIIntegrationTests {
#expect(attachment.name == "test.png")
}
@Test("Bidirectional conversion preserves content")
func bidirectionalConversion() throws {
@Test
func `Bidirectional conversion preserves content`() {
let original = UIMessage(
role: .user,
content: "Test message",

View File

@ -2,10 +2,9 @@ import Foundation
import Testing
@testable import Tachikoma
@Suite("Unified Errors Tests")
struct UnifiedErrorsTests {
@Test("Create unified error with recovery suggestion")
func unifiedErrorWithRecovery() throws {
@Test
func `Create unified error with recovery suggestion`() {
let error = TachikomaUnifiedError(
code: .authenticationFailed,
message: "Invalid API key provided",
@ -22,8 +21,8 @@ struct UnifiedErrorsTests {
#expect(description?.contains("Invalid API key provided") == true)
}
@Test("Convert legacy TachikomaError to unified error")
func legacyErrorConversion() throws {
@Test
func `Convert legacy TachikomaError to unified error`() {
let legacyError = TachikomaError.modelNotFound("gpt-5")
let unifiedError = legacyError.toUnifiedError()
@ -32,8 +31,8 @@ struct UnifiedErrorsTests {
#expect(unifiedError.recovery?.actions.contains(.selectDifferentModel) == true)
}
@Test("Convert ModelError to unified error")
func modelErrorConversion() throws {
@Test
func `Convert ModelError to unified error`() {
let modelError = ModelError.rateLimited(retryAfter: 60)
let unifiedError = modelError.toUnifiedError()
@ -45,8 +44,8 @@ struct UnifiedErrorsTests {
} == true)
}
@Test("Convert AgentToolError to unified error")
func agentToolErrorConversion() throws {
@Test
func `Convert AgentToolError to unified error`() {
let toolError = AgentToolError.missingParameter("expression")
let unifiedError = toolError.toUnifiedError()
@ -54,8 +53,8 @@ struct UnifiedErrorsTests {
#expect(unifiedError.message.contains("expression") == true)
}
@Test("Error details with metadata")
func errorDetails() throws {
@Test
func `Error details with metadata`() {
let details = ErrorDetails(
reason: "Token limit exceeded",
statusCode: 429,
@ -73,8 +72,8 @@ struct UnifiedErrorsTests {
#expect(details.metadata["tokens_used"] == "5000")
}
@Test("Recovery suggestion with actions")
func testRecoverySuggestion() throws {
@Test
func `Recovery suggestion with actions`() {
let recovery = RecoverySuggestion(
suggestion: "Reduce request size and try again",
actions: [
@ -88,8 +87,8 @@ struct UnifiedErrorsTests {
#expect(recovery.helpURL == "https://docs.example.com/errors")
}
@Test("Error code categories")
func errorCodeCategories() throws {
@Test
func `Error code categories`() {
#expect(ErrorCode.invalidRequest.category == .validation)
#expect(ErrorCode.authenticationFailed.category == .authentication)
#expect(ErrorCode.rateLimited.category == .rateLimit)
@ -99,10 +98,12 @@ struct UnifiedErrorsTests {
#expect(ErrorCode.parsingError.category == .parsing)
}
@Test("Generic error conversion")
func genericErrorConversion() throws {
@Test
func `Generic error conversion`() {
struct CustomError: Error, LocalizedError {
var errorDescription: String? { "Custom error occurred" }
var errorDescription: String? {
"Custom error occurred"
}
}
let customError = CustomError()
@ -113,8 +114,8 @@ struct UnifiedErrorsTests {
#expect(unifiedError.underlyingError != nil)
}
@Test("API call error conversion")
func aPICallErrorConversion() throws {
@Test
func `API call error conversion`() {
let apiError = APICallError(
statusCode: 500,
responseBody: "Internal server error",
@ -135,8 +136,8 @@ struct UnifiedErrorsTests {
#expect(unifiedError.details?.requestId == "req-456")
}
@Test("Retry error conversion")
func retryErrorConversion() throws {
@Test
func `Retry error conversion`() {
let retryError = RetryError(
reason: "All attempts failed",
lastError: TachikomaError.networkError(NSError(domain: "test", code: -1)),
@ -152,8 +153,8 @@ struct UnifiedErrorsTests {
#expect(unifiedError.underlyingError != nil)
}
@Test("Error with nil recovery")
func errorWithoutRecovery() throws {
@Test
func `Error with nil recovery`() {
let error = TachikomaUnifiedError(
code: .invalidParameter,
message: "Invalid parameter value",

Some files were not shown because too many files have changed in this diff Show More