Files
oai-swift/oAI/Providers/OpenRouterModels.swift

332 lines
8.8 KiB
Swift

//
// OpenRouterModels.swift
// oAI
//
// OpenRouter API request and response models
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.
import Foundation
// MARK: - API Request
struct OpenRouterChatRequest: Codable {
let model: String
let messages: [APIMessage]
var stream: Bool
let maxTokens: Int?
let temperature: Double?
let topP: Double?
let tools: [Tool]?
let toolChoice: String?
let modalities: [String]?
struct APIMessage: Codable {
let role: String
let content: MessageContent
enum MessageContent: Codable {
case string(String)
case array([ContentItem])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let str = try? container.decode(String.self) {
self = .string(str)
} else if let arr = try? container.decode([ContentItem].self) {
self = .array(arr)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid content")
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let str):
try container.encode(str)
case .array(let arr):
try container.encode(arr)
}
}
}
enum ContentItem: Codable {
case text(String)
case image(ImageContent)
struct TextContent: Codable {
let type: String // "text"
let text: String
}
struct ImageContent: Codable {
let type: String // "image_url"
let imageUrl: ImageURL
struct ImageURL: Codable {
let url: String
}
enum CodingKeys: String, CodingKey {
case type
case imageUrl = "image_url"
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let textContent = try? container.decode(TextContent.self), textContent.type == "text" {
self = .text(textContent.text)
} else if let image = try? container.decode(ImageContent.self) {
self = .image(image)
} else if let str = try? container.decode(String.self) {
self = .text(str)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid content item")
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text):
try container.encode(TextContent(type: "text", text: text))
case .image(let image):
try container.encode(image)
}
}
}
}
enum CodingKeys: String, CodingKey {
case model
case messages
case stream
case maxTokens = "max_tokens"
case temperature
case topP = "top_p"
case tools
case toolChoice = "tool_choice"
case modalities
}
}
// MARK: - API Response
struct OpenRouterChatResponse: Codable {
let id: String
let model: String
let choices: [Choice]
let usage: Usage?
let created: Int
struct Choice: Codable {
let index: Int
let message: MessageContent
let finishReason: String?
struct MessageContent: Codable {
let role: String
let content: String?
let toolCalls: [APIToolCall]?
let images: [ImageOutput]?
enum CodingKeys: String, CodingKey {
case role
case content
case toolCalls = "tool_calls"
case images
}
}
enum CodingKeys: String, CodingKey {
case index
case message
case finishReason = "finish_reason"
}
}
struct ImageOutput: Codable {
let imageUrl: ImageURL
struct ImageURL: Codable {
let url: String
}
enum CodingKeys: String, CodingKey {
case imageUrl = "image_url"
}
}
struct Usage: Codable {
let promptTokens: Int
let completionTokens: Int
let totalTokens: Int
enum CodingKeys: String, CodingKey {
case promptTokens = "prompt_tokens"
case completionTokens = "completion_tokens"
case totalTokens = "total_tokens"
}
}
}
// MARK: - Streaming Response
struct OpenRouterStreamChunk: Codable {
let id: String
let model: String
let choices: [StreamChoice]
let usage: OpenRouterChatResponse.Usage?
struct StreamChoice: Codable {
let index: Int
let delta: Delta
let finishReason: String?
struct Delta: Codable {
let role: String?
let content: String?
let images: [OpenRouterChatResponse.ImageOutput]?
}
enum CodingKeys: String, CodingKey {
case index
case delta
case finishReason = "finish_reason"
}
}
}
// MARK: - Models List
struct OpenRouterModelsResponse: Codable {
let data: [ModelData]
struct ModelData: Codable {
let id: String
let name: String
let description: String?
let contextLength: Int
let pricing: PricingData
let architecture: Architecture?
let supportedParameters: [String]?
let outputModalities: [String]?
struct PricingData: Codable {
let prompt: String
let completion: String
}
struct Architecture: Codable {
let modality: String?
let tokenizer: String?
let instructType: String?
enum CodingKeys: String, CodingKey {
case modality
case tokenizer
case instructType = "instruct_type"
}
}
enum CodingKeys: String, CodingKey {
case id
case name
case description
case contextLength = "context_length"
case pricing
case architecture
case supportedParameters = "supported_parameters"
case outputModalities = "output_modalities"
}
}
}
// MARK: - Credits Response
struct OpenRouterCreditsResponse: Codable {
let data: CreditsData
struct CreditsData: Codable {
let totalCredits: Double?
let totalUsage: Double?
enum CodingKeys: String, CodingKey {
case totalCredits = "total_credits"
case totalUsage = "total_usage"
}
}
}
// MARK: - Tool Call Models
struct APIToolCall: Codable {
let id: String
let type: String
let function: FunctionCall
struct FunctionCall: Codable {
let name: String
let arguments: String
}
}
/// Message shape for encoding assistant messages that contain tool calls
struct AssistantToolCallMessage: Encodable {
let role: String
let content: String?
let toolCalls: [APIToolCall]
enum CodingKeys: String, CodingKey {
case role
case content
case toolCalls = "tool_calls"
}
}
/// Message shape for encoding tool result messages back to the API
struct ToolResultMessage: Encodable {
let role: String // "tool"
let toolCallId: String
let name: String
let content: String
enum CodingKeys: String, CodingKey {
case role
case toolCallId = "tool_call_id"
case name
case content
}
}
// MARK: - Error Response
struct OpenRouterErrorResponse: Codable {
let error: ErrorDetail
struct ErrorDetail: Codable {
let message: String
let type: String?
let code: String?
}
}