diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AISwiftAssist.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AISwiftAssist.xcscheme
new file mode 100644
index 0000000..a1b43d2
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AISwiftAssist.xcscheme
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Package.swift b/Package.swift
index 970cd47..6242aee 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version: 5.9
+// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
@@ -7,6 +7,7 @@ let package = Package(
name: "AISwiftAssist",
platforms: [
.iOS(.v13),
+ .macOS(.v12),
.watchOS(.v8)
],
products: [
@@ -24,5 +25,6 @@ let package = Package(
name: "AISwiftAssistTests",
dependencies: ["AISwiftAssist"]
),
- ]
+ ],
+ swiftLanguageModes: [.v6]
)
diff --git a/Sources/AISwiftAssist/APIs/AssistantsAPI.swift b/Sources/AISwiftAssist/APIs/AssistantsAPI.swift
index 98bd620..1b54eab 100644
--- a/Sources/AISwiftAssist/APIs/AssistantsAPI.swift
+++ b/Sources/AISwiftAssist/APIs/AssistantsAPI.swift
@@ -8,7 +8,7 @@
import Foundation
/// Build assistants that can call models and use tools to perform tasks. [Link for Assistants](https://platform.openai.com/docs/api-reference/assistants)
-public protocol IAssistantsAPI: AnyObject {
+public protocol IAssistantsAPI: AnyObject, Sendable {
/// Returns a list of assistants.
/// - Parameter parameters: Parameters for the list of assistants.
@@ -36,49 +36,20 @@ public protocol IAssistantsAPI: AnyObject {
/// - Parameter assistantId: The ID of the assistant to delete.
/// - Returns: Deletion status
func delete(by assistantId: String) async throws -> ASADeleteModelResponse
-
- /// Create an assistant file by attaching a File to an assistant.
- /// - Parameters:
- /// - assistantId: The ID of the assistant for which to create a File.
- /// - request: The request object containing the File ID.
- /// - Returns: An assistant file object.
- func createFile(for assistantId: String, with request: ASACreateAssistantFileRequest) async throws -> ASAAssistantFile
-
- /// Retrieves an assistant file.
- /// - Parameters:
- /// - assistantId: The ID of the assistant who the file belongs to.
- /// - fileId: The ID of the file to retrieve.
- /// - Returns: The assistant file object matching the specified ID.
- func retrieveFile(for assistantId: String, fileId: String) async throws -> ASAAssistantFile
- /// Delete an assistant file.
- /// - Parameters:
- /// - assistantId: The ID of the assistant that the file belongs to.
- /// - fileId: The ID of the file to delete.
- /// - Returns: Deletion status.
- func deleteFile(for assistantId: String, fileId: String) async throws -> ASADeleteModelResponse
-
- /// Returns a list of assistant files.
- /// - Parameters:
- /// - assistantId: The ID of the assistant the file belongs to.
- /// - parameters: Parameters for the list of assistant files.
- /// - Returns: A list of assistant file objects.
- func listFiles(for assistantId: String, with parameters: ASAListAssistantsParameters?) async throws -> ASAAssistantFilesListResponse
}
-public final class AssistantsAPI: HTTPClient, IAssistantsAPI {
+public actor AssistantsAPI: HTTPClient, IAssistantsAPI {
let urlSession: URLSession
- public init(apiKey: String,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path,
- urlSession: URLSession = .shared) {
- Constants.apiKey = apiKey
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
+ public init(
+ config: AISwiftAssistConfig,
+ constants: AISwiftAssistConstants = .default,
+ urlSession: URLSession = .shared
+ ) {
+ Constants.config = config
+ Constants.constants = constants
self.urlSession = urlSession
}
@@ -110,25 +81,5 @@ public final class AssistantsAPI: HTTPClient, IAssistantsAPI {
let endpoint = AssistantEndpoint.deleteAssistant(assistantId)
return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
}
-
- public func createFile(for assistantId: String, with request: ASACreateAssistantFileRequest) async throws -> ASAAssistantFile {
- let endpoint = AssistantEndpoint.createFile(assistantId, request)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFile.self)
- }
-
- public func retrieveFile(for assistantId: String, fileId: String) async throws -> ASAAssistantFile {
- let endpoint = AssistantEndpoint.retrieveFile(assistantId, fileId)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFile.self)
- }
-
- public func deleteFile(for assistantId: String, fileId: String) async throws -> ASADeleteModelResponse {
- let endpoint = AssistantEndpoint.deleteFile(assistantId, fileId)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
- }
-
- public func listFiles(for assistantId: String, with parameters: ASAListAssistantsParameters? = nil) async throws -> ASAAssistantFilesListResponse {
- let endpoint = AssistantEndpoint.listFiles(assistantId, parameters)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFilesListResponse.self)
- }
}
diff --git a/Sources/AISwiftAssist/APIs/MessagesAPI.swift b/Sources/AISwiftAssist/APIs/MessagesAPI.swift
index 6a6a967..46d4855 100644
--- a/Sources/AISwiftAssist/APIs/MessagesAPI.swift
+++ b/Sources/AISwiftAssist/APIs/MessagesAPI.swift
@@ -8,7 +8,14 @@
import Foundation
/// Create messages within threads [Link for Messages](https://platform.openai.com/docs/api-reference/messages)
-public protocol IMessagesAPI: AnyObject {
+public protocol IMessagesAPI: AnyObject, Sendable {
+
+ /// Returns a list of messages for a given thread.
+ /// - Parameters:
+ /// - threadId: The ID of the thread the messages belong to.
+ /// - parameters: Parameters for the list of messages.
+ /// - Returns: A list of message objects.
+ func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse
/// Create a message.
/// - Parameters:
@@ -31,44 +38,20 @@ public protocol IMessagesAPI: AnyObject {
/// - modifyMessage: Object with parameters for modifying a message.
/// - Returns: The modified message object.
func modify(by threadId: String, messageId: String, modifyMessage: ASAModifyMessageRequest) async throws -> ASAMessage
-
- /// Returns a list of messages for a given thread.
- /// - Parameters:
- /// - threadId: The ID of the thread the messages belong to.
- /// - parameters: Parameters for the list of messages.
- /// - Returns: A list of message objects.
- func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse
-
- /// Retrieves a file associated with a message.
- /// - Parameters:
- /// - threadId: The ID of the thread to which the message and file belong.
- /// - messageId: The ID of the message the file belongs to.
- /// - fileId: The ID of the file being retrieved.
- /// - Returns: The message file object.
- func retrieveFile(by threadId: String, messageId: String, fileId: String) async throws -> ASAMessageFile
- /// Returns a list of files associated with a message.
- /// - Parameters:
- /// - threadId: The ID of the thread that the message and files belong to.
- /// - messageId: The ID of the message that the files belong to.
- /// - parameters: Optional parameters for pagination and sorting.
- /// - Returns: A list of message file objects.
- func listFiles(by threadId: String, messageId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessageFilesListResponse
}
-public final class MessagesAPI: HTTPClient, IMessagesAPI {
+public actor MessagesAPI: HTTPClient, IMessagesAPI {
let urlSession: URLSession
- public init(apiKey: String,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path,
- urlSession: URLSession = .shared) {
- Constants.apiKey = apiKey
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
+ public init(
+ config: AISwiftAssistConfig,
+ constants: AISwiftAssistConstants = .default,
+ urlSession: URLSession = .shared
+ ) {
+ Constants.config = config
+ Constants.constants = constants
self.urlSession = urlSession
}
@@ -76,6 +59,11 @@ public final class MessagesAPI: HTTPClient, IMessagesAPI {
self.urlSession = urlSession
}
+ public func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse {
+ let endpoint = MessagesEndpoint.listMessages(threadId, parameters)
+ return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessagesListResponse.self)
+ }
+
public func create(by threadId: String, createMessage: ASACreateMessageRequest) async throws -> ASAMessage {
let endpoint = MessagesEndpoint.createMessage(threadId, createMessage)
return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessage.self)
@@ -91,18 +79,4 @@ public final class MessagesAPI: HTTPClient, IMessagesAPI {
return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessage.self)
}
- public func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse {
- let endpoint = MessagesEndpoint.listMessages(threadId, parameters)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessagesListResponse.self)
- }
-
- public func retrieveFile(by threadId: String, messageId: String, fileId: String) async throws -> ASAMessageFile {
- let endpoint = MessagesEndpoint.retrieveFile(threadId, messageId, fileId)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessageFile.self)
- }
-
- public func listFiles(by threadId: String, messageId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessageFilesListResponse {
- let endpoint = MessagesEndpoint.listFiles(threadId, messageId, parameters)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessageFilesListResponse.self)
- }
}
diff --git a/Sources/AISwiftAssist/APIs/ModelsAPI.swift b/Sources/AISwiftAssist/APIs/ModelsAPI.swift
index 4cb0bbe..bf3e4aa 100644
--- a/Sources/AISwiftAssist/APIs/ModelsAPI.swift
+++ b/Sources/AISwiftAssist/APIs/ModelsAPI.swift
@@ -7,56 +7,54 @@
import Foundation
-/// Describes an OpenAI model offering that can be used with the API. [Link for Models](https://platform.openai.com/docs/api-reference/models)
-public protocol IModelsAPI: AnyObject {
-
- /// Lists the currently available models, and provides basic information about each one such as the owner and availability.
- /// - Returns: A list of model objects.
- func get() async throws -> ASAModelsListResponse
-
- /// Retrieves a model instance, providing basic information about the model such as the owner and permissioning.
- /// - Parameter modelId: The ID of the model to use for this request
- /// - Returns: The model object matching the specified ID.
- func retrieve(by modelId: String) async throws -> ASAModel
-
- /// Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.
- /// - Parameter modelId: The model to delete
- /// - Returns: Deletion status.
- func delete(by modelId: String) async throws -> ASADeleteModelResponse
-}
-
-public final class ModelsAPI: HTTPClient, IModelsAPI {
-
- let urlSession: URLSession
-
- public init(apiKey: String,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path,
- urlSession: URLSession = .shared) {
- Constants.apiKey = apiKey
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
- self.urlSession = urlSession
- }
-
- public init(urlSession: URLSession = .shared) {
- self.urlSession = urlSession
- }
-
- public func get() async throws -> ASAModelsListResponse {
- let endpoint = ModelsEndpoint.getModels
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModelsListResponse.self)
- }
-
- public func retrieve(by modelId: String) async throws -> ASAModel {
- let endpoint = ModelsEndpoint.retrieveModel(modelId)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModel.self)
- }
-
- public func delete(by modelId: String) async throws -> ASADeleteModelResponse {
- let endpoint = ModelsEndpoint.deleteModel(modelId)
- return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
- }
-}
+///// Describes an OpenAI model offering that can be used with the API. [Link for Models](https://platform.openai.com/docs/api-reference/models)
+//public protocol IModelsAPI: AnyObject, Sendable {
+//
+// /// Lists the currently available models, and provides basic information about each one such as the owner and availability.
+// /// - Returns: A list of model objects.
+// func get() async throws -> ASAModelsListResponse
+//
+// /// Retrieves a model instance, providing basic information about the model such as the owner and permissioning.
+// /// - Parameter modelId: The ID of the model to use for this request
+// /// - Returns: The model object matching the specified ID.
+// func retrieve(by modelId: String) async throws -> ASAModel
+//
+// /// Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.
+// /// - Parameter modelId: The model to delete
+// /// - Returns: Deletion status.
+// func delete(by modelId: String) async throws -> ASADeleteModelResponse
+//}
+//
+//public actor ModelsAPI: HTTPClient, IModelsAPI {
+//
+// let urlSession: URLSession
+//
+// public init(
+// config: AISwiftAssistConfig,
+// constants: AISwiftAssistConstants = .default,
+// urlSession: URLSession = .shared
+// ) {
+// Constants.config = config
+// Constants.constants = constants
+// self.urlSession = urlSession
+// }
+//
+// public init(urlSession: URLSession = .shared) {
+// self.urlSession = urlSession
+// }
+//
+// public func get() async throws -> ASAModelsListResponse {
+// let endpoint = ModelsEndpoint.getModels
+// return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModelsListResponse.self)
+// }
+//
+// public func retrieve(by modelId: String) async throws -> ASAModel {
+// let endpoint = ModelsEndpoint.retrieveModel(modelId)
+// return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModel.self)
+// }
+//
+// public func delete(by modelId: String) async throws -> ASADeleteModelResponse {
+// let endpoint = ModelsEndpoint.deleteModel(modelId)
+// return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
+// }
+//}
diff --git a/Sources/AISwiftAssist/APIs/RunsAPI.swift b/Sources/AISwiftAssist/APIs/RunsAPI.swift
index 823dc53..5461494 100644
--- a/Sources/AISwiftAssist/APIs/RunsAPI.swift
+++ b/Sources/AISwiftAssist/APIs/RunsAPI.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents an execution run on a thread. [Link for Runs](https://platform.openai.com/docs/api-reference/runs)
-public protocol IRunsAPI: AnyObject {
+public protocol IRunsAPI: AnyObject, Sendable {
/// Create a run.
/// - Parameters:
@@ -77,19 +77,17 @@ public protocol IRunsAPI: AnyObject {
func listRunSteps(by threadId: String, runId: String, parameters: ASAListRunStepsParameters?) async throws -> ASARunStepsListResponse
}
-public final class RunsAPI: HTTPClient, IRunsAPI {
+public actor RunsAPI: HTTPClient, IRunsAPI {
let urlSession: URLSession
- public init(apiKey: String,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path,
- urlSession: URLSession = .shared) {
- Constants.apiKey = apiKey
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
+ public init(
+ config: AISwiftAssistConfig,
+ constants: AISwiftAssistConstants = .default,
+ urlSession: URLSession = .shared
+ ) {
+ Constants.config = config
+ Constants.constants = constants
self.urlSession = urlSession
}
diff --git a/Sources/AISwiftAssist/APIs/ThreadsAPI.swift b/Sources/AISwiftAssist/APIs/ThreadsAPI.swift
index 531f7c5..9b884e5 100644
--- a/Sources/AISwiftAssist/APIs/ThreadsAPI.swift
+++ b/Sources/AISwiftAssist/APIs/ThreadsAPI.swift
@@ -8,7 +8,7 @@
import Foundation
/// Create threads that assistants can interact with. [Link for Threads](https://platform.openai.com/docs/api-reference/threads)
-public protocol IThreadsAPI: AnyObject {
+public protocol IThreadsAPI: AnyObject, Sendable {
/// Create a thread.
/// - Parameter createThreads: Object with parameters for creating a thread.
@@ -33,19 +33,17 @@ public protocol IThreadsAPI: AnyObject {
func delete(threadId: String) async throws -> ASADeleteModelResponse
}
-public final class ThreadsAPI: HTTPClient, IThreadsAPI {
+public actor ThreadsAPI: HTTPClient, IThreadsAPI {
let urlSession: URLSession
- public init(apiKey: String,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path,
- urlSession: URLSession = .shared) {
- Constants.apiKey = apiKey
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
+ public init(
+ config: AISwiftAssistConfig,
+ constants: AISwiftAssistConstants = .default,
+ urlSession: URLSession = .shared
+ ) {
+ Constants.config = config
+ Constants.constants = constants
self.urlSession = urlSession
}
diff --git a/Sources/AISwiftAssist/Base/Constants.swift b/Sources/AISwiftAssist/Base/Constants.swift
index a4b4af5..9fab8ad 100644
--- a/Sources/AISwiftAssist/Base/Constants.swift
+++ b/Sources/AISwiftAssist/Base/Constants.swift
@@ -7,10 +7,7 @@
import Foundation
-public enum Constants {
- public static var baseScheme: String = "https"
- public static var baseHost: String = "api.openai.com"
- public static var path: String = "/v1/"
- public static var apiKey: String = ""
- public static var organizationId: String?
+public enum Constants: Sendable {
+ nonisolated(unsafe) public static var config: AISwiftAssistConfig = .empty
+ nonisolated(unsafe) public static var constants: AISwiftAssistConstants = .default
}
diff --git a/Sources/AISwiftAssist/Base/HTTPClient.swift b/Sources/AISwiftAssist/Base/HTTPClient.swift
index 6687cf5..c78c082 100644
--- a/Sources/AISwiftAssist/Base/HTTPClient.swift
+++ b/Sources/AISwiftAssist/Base/HTTPClient.swift
@@ -7,7 +7,7 @@
import Foundation
-protocol HTTPClient: AnyObject {
+protocol HTTPClient: AnyObject, Sendable {
func sendRequest(session: URLSession,
endpoint: any Endpoint,
responseModel: T.Type) async throws -> T
@@ -35,9 +35,9 @@ extension HTTPClient {
request.httpMethod = endpoint.method.rawValue
request.allHTTPHeaderFields = endpoint.header
if request.allHTTPHeaderFields == nil {
- request.allHTTPHeaderFields = ["Authorization": "Bearer \(Constants.apiKey)"]
+ request.allHTTPHeaderFields = ["Authorization": "Bearer \(Constants.config.apiKey)"]
} else {
- request.allHTTPHeaderFields?["Authorization"] = "Bearer \(Constants.apiKey)"
+ request.allHTTPHeaderFields?["Authorization"] = "Bearer \(Constants.config.apiKey)"
}
request.httpBody = endpoint.body?.data
diff --git a/Sources/AISwiftAssist/Base/HTTPRequestError.swift b/Sources/AISwiftAssist/Base/HTTPRequestError.swift
index 8a3b7b8..7c20439 100644
--- a/Sources/AISwiftAssist/Base/HTTPRequestError.swift
+++ b/Sources/AISwiftAssist/Base/HTTPRequestError.swift
@@ -7,13 +7,13 @@
import Foundation
-public struct ValidatorErrorResponse: Codable {
+public struct ValidatorErrorResponse: Codable, Sendable {
public let code: Int
public let desc: String
}
/// Types of HTTP Request Errors
-public enum HTTPRequestError: Error {
+public enum HTTPRequestError: Error, Sendable {
/// Model decoding error
case decode(String)
/// URL validation error
diff --git a/Sources/AISwiftAssist/Base/HTTPRequestMethods.swift b/Sources/AISwiftAssist/Base/HTTPRequestMethods.swift
index 89d27a8..dae7043 100644
--- a/Sources/AISwiftAssist/Base/HTTPRequestMethods.swift
+++ b/Sources/AISwiftAssist/Base/HTTPRequestMethods.swift
@@ -7,7 +7,7 @@
import Foundation
-enum HTTPRequestMethods: String {
+enum HTTPRequestMethods: String, Sendable {
case get = "GET"
case post = "POST"
case put = "PUT"
diff --git a/Sources/AISwiftAssist/Client/AISwiftAssistClient.swift b/Sources/AISwiftAssist/Client/AISwiftAssistClient.swift
index dc86b74..e1483be 100644
--- a/Sources/AISwiftAssist/Client/AISwiftAssistClient.swift
+++ b/Sources/AISwiftAssist/Client/AISwiftAssistClient.swift
@@ -7,52 +7,24 @@
import Foundation
-public final class AISwiftAssistClient {
-
- public let assistantsApi: IAssistantsAPI
- public let messagesApi: IMessagesAPI
- public let modelsApi: IModelsAPI
- public let runsApi: IRunsAPI
- public let threadsApi: IThreadsAPI
-
- public init(config: AISwiftAssistConfig,
- baseScheme: String = Constants.baseScheme,
- baseHost: String = Constants.baseHost,
- path: String = Constants.path) {
- Constants.apiKey = config.apiKey
- Constants.organizationId = config.organizationId
- Constants.baseScheme = baseScheme
- Constants.baseHost = baseHost
- Constants.path = path
- self.assistantsApi = AssistantsAPI(urlSession: .shared)
- self.messagesApi = MessagesAPI(urlSession: .shared)
- self.modelsApi = ModelsAPI(urlSession: .shared)
- self.runsApi = RunsAPI(urlSession: .shared)
- self.threadsApi = ThreadsAPI(urlSession: .shared)
+public actor AISwiftAssistClient: Sendable {
+
+ public nonisolated let assistantsApi: any IAssistantsAPI
+ public nonisolated let messagesApi: any IMessagesAPI
+ public nonisolated let runsApi: any IRunsAPI
+ public nonisolated let threadsApi: any IThreadsAPI
+
+ public init(
+ config: AISwiftAssistConfig,
+ constants: AISwiftAssistConstants = .default,
+ urlSession: URLSession = .shared
+ ) {
+ Constants.config = config
+ Constants.constants = constants
+ self.assistantsApi = AssistantsAPI(urlSession: urlSession)
+ self.messagesApi = MessagesAPI(urlSession: urlSession)
+ self.runsApi = RunsAPI(urlSession: urlSession)
+ self.threadsApi = ThreadsAPI(urlSession: urlSession)
}
}
-
-extension AISwiftAssistClient {
- /// Creates an assistant and thread based on the provided parameters.
- public func createAssistantAndThread(with params: AssistantCreationParams) async throws -> AssistantAndThreadConfig {
- let modelsResponse = try await modelsApi.get()
- guard let model = modelsResponse.data.first(where: { $0.id == params.model.rawValue }) else {
- throw NSError(domain: "AISwiftAssistClient", code: 0, userInfo: [NSLocalizedDescriptionKey: "Model not found"])
- }
-
- let createAssistantRequest = ASACreateAssistantRequest(asaModel: model,
- name: params.name,
- description: params.description,
- instructions: params.instructions,
- tools: params.tools,
- fileIds: params.fileIds,
- metadata: params.metadata)
- let assistant = try await assistantsApi.create(by: createAssistantRequest)
-
- let threadRequest = ASACreateThreadRequest(messages: nil)
- let thread = try await threadsApi.create(by: threadRequest)
-
- return AssistantAndThreadConfig(assistant: assistant, thread: thread)
- }
-}
diff --git a/Sources/AISwiftAssist/Client/AISwiftAssistConfig.swift b/Sources/AISwiftAssist/Client/AISwiftAssistConfig.swift
index 76dd776..c241dc6 100644
--- a/Sources/AISwiftAssist/Client/AISwiftAssistConfig.swift
+++ b/Sources/AISwiftAssist/Client/AISwiftAssistConfig.swift
@@ -7,15 +7,18 @@
import Foundation
-public struct AISwiftAssistConfig {
+public struct AISwiftAssistConfig: Sendable {
public let apiKey: String
public let organizationId: String?
- public init(apiKey: String,
- organizationId: String? = nil) {
+ public init(
+ apiKey: String,
+ organizationId: String? = nil
+ ) {
self.apiKey = apiKey
self.organizationId = organizationId
}
+ public static let empty: AISwiftAssistConfig = .init(apiKey: "")
}
diff --git a/Sources/AISwiftAssist/Client/AISwiftAssistConstants.swift b/Sources/AISwiftAssist/Client/AISwiftAssistConstants.swift
new file mode 100644
index 0000000..ec0a109
--- /dev/null
+++ b/Sources/AISwiftAssist/Client/AISwiftAssistConstants.swift
@@ -0,0 +1,34 @@
+//
+// AISwiftAssistConfig 2.swift
+// AISwiftAssist
+//
+// Created by Alexey on 3/6/25.
+//
+
+public struct AISwiftAssistConstants: Sendable {
+
+ public enum AssistantsVersion: String, Sendable {
+ case v1 = "assistants=v1" /// deprecated
+ case v2 = "assistants=v2"
+ }
+
+ public let baseScheme: String
+ public let baseHost: String
+ public let path: String
+ /// The version of the Assistants API to use.
+ public let version: AssistantsVersion
+
+ public init(
+ baseScheme: String = "https",
+ baseHost: String = "api.openai.com",
+ path: String = "/v1/",
+ version: AssistantsVersion = .v2
+ ) {
+ self.baseScheme = baseScheme
+ self.baseHost = baseHost
+ self.path = path
+ self.version = version
+ }
+
+ public static let `default`: AISwiftAssistConstants = .init()
+}
diff --git a/Sources/AISwiftAssist/Client/Models/ASAOpenAIModel.swift b/Sources/AISwiftAssist/Client/Models/ASAOpenAIModel.swift
index f644a82..ea807e1 100644
--- a/Sources/AISwiftAssist/Client/Models/ASAOpenAIModel.swift
+++ b/Sources/AISwiftAssist/Client/Models/ASAOpenAIModel.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents various models created and used by OpenAI and its partners.
-public enum ASAOpenAIModel: String {
+public enum ASAOpenAIModel: String, Sendable {
/// Model "text-search-babbage-doc-001" created by openai-dev, a document search model based on the Babbage architecture.
case textSearchBabbageDoc001 = "text-search-babbage-doc-001"
@@ -186,5 +186,117 @@ public enum ASAOpenAIModel: String {
/// Model "dall-e-3" created by system, an advanced version of the DALL-E image generation model.
case dallE3 = "dall-e-3"
-}
+ // MARK: - Additional Models
+
+ /// Model "gpt-4.5-preview" created by system.
+ case gpt4_5Preview = "gpt-4.5-preview"
+
+ /// Model "gpt-4.5-preview-2025-02-27" created by system.
+ case gpt4_5Preview2025_02_27 = "gpt-4.5-preview-2025-02-27"
+
+ /// Model "gpt-4o-mini-audio-preview-2024-12-17" created by system.
+ case gpt4oMiniAudioPreview2024_12_17 = "gpt-4o-mini-audio-preview-2024-12-17"
+
+ /// Model "gpt-4o-audio-preview-2024-10-01" created by system.
+ case gpt4oAudioPreview2024_10_01 = "gpt-4o-audio-preview-2024-10-01"
+
+ /// Model "gpt-4o-audio-preview" created by system.
+ case gpt4oAudioPreview = "gpt-4o-audio-preview"
+
+ /// Model "gpt-4o-mini-realtime-preview-2024-12-17" created by system.
+ case gpt4oMiniRealtimePreview2024_12_17 = "gpt-4o-mini-realtime-preview-2024-12-17"
+
+ /// Model "gpt-4o-mini-realtime-preview" created by system.
+ case gpt4oMiniRealtimePreview = "gpt-4o-mini-realtime-preview"
+
+ /// Model "o1-mini-2024-09-12" created by system.
+ case o1Mini2024_09_12 = "o1-mini-2024-09-12"
+
+ /// Model "o1-mini" created by system.
+ case o1Mini = "o1-mini"
+
+ /// Model "gpt-4o-mini-audio-preview" created by system.
+ case gpt4oMiniAudioPreview = "gpt-4o-mini-audio-preview"
+
+ /// Model "whisper-1" created by openai-internal.
+ case whisper1 = "whisper-1"
+
+ /// Model "omni-moderation-latest" created by system.
+ case omniModerationLatest = "omni-moderation-latest"
+
+ /// Model "gpt-4o-2024-05-13" created by system.
+ case gpt4o2024_05_13 = "gpt-4o-2024-05-13"
+
+ /// Model "omni-moderation-2024-09-26" created by system.
+ case omniModeration2024_09_26 = "omni-moderation-2024-09-26"
+
+ /// Model "gpt-4o-realtime-preview-2024-10-01" created by system.
+ case gpt4oRealtimePreview2024_10_01 = "gpt-4o-realtime-preview-2024-10-01"
+
+ /// Model "gpt-4o-2024-08-06" created by system.
+ case gpt4o2024_08_06 = "gpt-4o-2024-08-06"
+
+ /// Model "chatgpt-4o-latest" created by system.
+ case chatgpt4oLatest = "chatgpt-4o-latest"
+
+ /// Model "tts-1-hd-1106" created by system.
+ case tts1Hd1106 = "tts-1-hd-1106"
+
+ /// Model "text-embedding-3-large" created by system.
+ case textEmbedding3Large = "text-embedding-3-large"
+
+ /// Model "gpt-4o-audio-preview-2024-12-17" created by system.
+ case gpt4oAudioPreview2024_12_17 = "gpt-4o-audio-preview-2024-12-17"
+
+ /// Model "gpt-4o" created by system.
+ case gpt4o = "gpt-4o"
+
+ /// Model "o1" created by system.
+ case o1 = "o1"
+
+ /// Model "gpt-4" created by openai.
+ case gpt4 = "gpt-4"
+
+ /// Model "gpt-4o-2024-11-20" created by system.
+ case gpt4o2024_11_20 = "gpt-4o-2024-11-20"
+
+ /// Model "o1-2024-12-17" created by system.
+ case o1_2024_12_17 = "o1-2024-12-17"
+
+ /// Model "o1-preview" created by system.
+ case o1Preview = "o1-preview"
+
+ /// Model "o1-preview-2024-09-12" created by system.
+ case o1Preview2024_09_12 = "o1-preview-2024-09-12"
+
+ /// Model "gpt-4o-mini-2024-07-18" created by system.
+ case gpt4oMini2024_07_18 = "gpt-4o-mini-2024-07-18"
+
+ /// Model "gpt-4o-mini" created by system.
+ case gpt4oMini = "gpt-4o-mini"
+
+ /// Model "gpt-4-turbo" created by system.
+ case gpt4Turbo = "gpt-4-turbo"
+
+ /// Model "o3-mini-2025-01-31" created by system.
+ case o3Mini2025_01_31 = "o3-mini-2025-01-31"
+
+ /// Model "gpt-3.5-turbo-0125" created by system.
+ case gpt3_5Turbo0125 = "gpt-3.5-turbo-0125"
+
+ /// Model "gpt-4o-realtime-preview-2024-12-17" created by system.
+ case gpt4oRealtimePreview2024_12_17 = "gpt-4o-realtime-preview-2024-12-17"
+
+ /// Model "text-embedding-3-small" created by system.
+ case textEmbedding3Small = "text-embedding-3-small"
+
+ /// Model "gpt-4-0125-preview" created by system.
+ case gpt4_0125Preview = "gpt-4-0125-preview"
+
+ /// Model "gpt-4-turbo-preview" created by system.
+ case gpt4TurboPreview = "gpt-4-turbo-preview"
+
+ /// Model "o3-mini" created by system.
+ case o3Mini = "o3-mini"
+}
diff --git a/Sources/AISwiftAssist/Client/Models/AssistantAndThreadConfig.swift b/Sources/AISwiftAssist/Client/Models/AssistantAndThreadConfig.swift
index a948f79..f51111f 100644
--- a/Sources/AISwiftAssist/Client/Models/AssistantAndThreadConfig.swift
+++ b/Sources/AISwiftAssist/Client/Models/AssistantAndThreadConfig.swift
@@ -7,7 +7,7 @@
import Foundation
-public struct AssistantAndThreadConfig {
+public struct AssistantAndThreadConfig: Sendable {
public let assistant: ASAAssistant
public let thread: ASAThread
}
diff --git a/Sources/AISwiftAssist/Client/Models/AssistantCreationParams.swift b/Sources/AISwiftAssist/Client/Models/AssistantCreationParams.swift
index e81a0ed..32bf9e1 100644
--- a/Sources/AISwiftAssist/Client/Models/AssistantCreationParams.swift
+++ b/Sources/AISwiftAssist/Client/Models/AssistantCreationParams.swift
@@ -7,23 +7,31 @@
import Foundation
-public struct AssistantCreationParams {
-
- public let model: ASAOpenAIModel
- public let name: String
- public let description: String
- public let instructions: String
- public let tools: [ASACreateAssistantRequest.Tool]?
- public let fileIds: [String]?
- public let metadata: [String: String]?
-
- public init(model: ASAOpenAIModel, name: String, description: String, instructions: String, tools: [ASACreateAssistantRequest.Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
- self.model = model
- self.name = name
- self.description = description
- self.instructions = instructions
- self.tools = tools
- self.fileIds = fileIds
- self.metadata = metadata
- }
-}
+//public struct AssistantCreationParams: Sendable {
+//
+// public let model: ASAOpenAIModel
+// public let name: String
+// public let description: String
+// public let instructions: String
+// public let tools: [ASACreateAssistantRequest.Tool]?
+// public let fileIds: [String]?
+// public let metadata: [String: String]?
+//
+// public init(
+// model: ASAOpenAIModel,
+// name: String,
+// description: String,
+// instructions: String,
+// tools: [ASACreateAssistantRequest.Tool]? = nil,
+// fileIds: [String]? = nil,
+// metadata: [String : String]? = nil
+// ) {
+// self.model = model
+// self.name = name
+// self.description = description
+// self.instructions = instructions
+// self.tools = tools
+// self.fileIds = fileIds
+// self.metadata = metadata
+// }
+//}
diff --git a/Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift b/Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift
index 330171c..1ae6999 100644
--- a/Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift
+++ b/Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift
@@ -13,26 +13,21 @@ enum AssistantEndpoint {
case retrieveAssistant(String)
case modifyAssistant(String, ASAModifyAssistantRequest)
case deleteAssistant(String)
- case createFile(String, ASACreateAssistantFileRequest)
- case retrieveFile(String, String)
- case deleteFile(String, String)
- case listFiles(String, ASAListAssistantsParameters?)
}
extension AssistantEndpoint: CustomEndpoint {
public var url: URL? {
var urlComponents: URLComponents = .default
urlComponents.queryItems = queryItems
- urlComponents.path = Constants.path + path
+ urlComponents.path = Constants.constants.path + path
return urlComponents.url
}
public var queryItems: [URLQueryItem]? {
var items: [URLQueryItem]?
switch self {
- case .createAssistant, .deleteAssistant, .retrieveAssistant, .modifyAssistant, .createFile, .retrieveFile, .deleteFile: items = nil
+ case .createAssistant, .deleteAssistant, .retrieveAssistant, .modifyAssistant: items = nil
case .getAssistants(let params): items = Utils.createURLQueryItems(from: params)
- case .listFiles(_, let params): items = Utils.createURLQueryItems(from: params)
}
return items
}
@@ -43,10 +38,7 @@ extension AssistantEndpoint: CustomEndpoint {
case .retrieveAssistant(let assistantId): return "assistants/\(assistantId)"
case .modifyAssistant(let assistantId, _): return "assistants/\(assistantId)"
case .deleteAssistant(let assistantId): return "assistants/\(assistantId)"
- case .createFile(let assistantId, _): return "assistants/\(assistantId)/files"
- case .retrieveFile(let assistantId, let fileId): return "assistants/\(assistantId)/files/\(fileId)"
- case .deleteFile(let assistantId, let fileId): return "assistants/\(assistantId)/files/\(fileId)"
- case .listFiles(let assistantId, _): return "assistants/\(assistantId)/files"
+
}
}
@@ -57,15 +49,11 @@ extension AssistantEndpoint: CustomEndpoint {
case .retrieveAssistant: return .get
case .modifyAssistant: return .post
case .deleteAssistant: return .delete
- case .createFile: return .post
- case .retrieveFile: return .get
- case .deleteFile: return .delete
- case .listFiles: return .get
}
}
public var header: [String : String]? {
- let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
+ var headers: [String: String] = ["OpenAI-Beta": Constants.constants.version.rawValue,
"Content-Type": "application/json"]
return headers
}
@@ -74,8 +62,7 @@ extension AssistantEndpoint: CustomEndpoint {
switch self {
case .createAssistant(let createAssistant): return .init(object: createAssistant)
case .modifyAssistant(_, let request): return .init(object: request)
- case .createFile(_, let request): return .init(object: request)
- case .deleteAssistant, .retrieveAssistant, .getAssistants, .retrieveFile, .deleteFile, .listFiles: return nil
+ case .deleteAssistant, .retrieveAssistant, .getAssistants: return nil
}
}
}
diff --git a/Sources/AISwiftAssist/Endpoints/MessagesEndpoint.swift b/Sources/AISwiftAssist/Endpoints/MessagesEndpoint.swift
index 11c446e..e6daed6 100644
--- a/Sources/AISwiftAssist/Endpoints/MessagesEndpoint.swift
+++ b/Sources/AISwiftAssist/Endpoints/MessagesEndpoint.swift
@@ -20,7 +20,7 @@ extension MessagesEndpoint: CustomEndpoint {
public var url: URL? {
var urlComponents: URLComponents = .default
urlComponents.queryItems = queryItems
- urlComponents.path = Constants.path + path
+ urlComponents.path = Constants.constants.path + path
return urlComponents.url
}
@@ -64,7 +64,7 @@ extension MessagesEndpoint: CustomEndpoint {
}
public var header: [String : String]? {
- let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
+ let headers: [String: String] = ["OpenAI-Beta": Constants.constants.version.rawValue,
"Content-Type": "application/json"]
return headers
}
diff --git a/Sources/AISwiftAssist/Endpoints/ModelsEndpoint.swift b/Sources/AISwiftAssist/Endpoints/ModelsEndpoint.swift
index 51d1e98..d84f484 100644
--- a/Sources/AISwiftAssist/Endpoints/ModelsEndpoint.swift
+++ b/Sources/AISwiftAssist/Endpoints/ModelsEndpoint.swift
@@ -17,7 +17,7 @@ extension ModelsEndpoint: CustomEndpoint {
public var url: URL? {
var urlComponents: URLComponents = .default
urlComponents.queryItems = queryItems
- urlComponents.path = Constants.path + path
+ urlComponents.path = Constants.constants.path + path
return urlComponents.url
}
diff --git a/Sources/AISwiftAssist/Endpoints/RunsEndpoint.swift b/Sources/AISwiftAssist/Endpoints/RunsEndpoint.swift
index 04a55c3..dca1b38 100644
--- a/Sources/AISwiftAssist/Endpoints/RunsEndpoint.swift
+++ b/Sources/AISwiftAssist/Endpoints/RunsEndpoint.swift
@@ -24,7 +24,7 @@ extension RunsEndpoint: CustomEndpoint {
public var url: URL? {
var urlComponents: URLComponents = .default
urlComponents.queryItems = queryItems
- urlComponents.path = Constants.path + path
+ urlComponents.path = Constants.constants.path + path
return urlComponents.url
}
@@ -85,7 +85,7 @@ extension RunsEndpoint: CustomEndpoint {
}
public var header: [String : String]? {
- let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
+ let headers: [String: String] = ["OpenAI-Beta": Constants.constants.version.rawValue,
"Content-Type": "application/json"]
return headers
}
diff --git a/Sources/AISwiftAssist/Endpoints/ThreadsEndpoint.swift b/Sources/AISwiftAssist/Endpoints/ThreadsEndpoint.swift
index 0886a1c..d109f46 100644
--- a/Sources/AISwiftAssist/Endpoints/ThreadsEndpoint.swift
+++ b/Sources/AISwiftAssist/Endpoints/ThreadsEndpoint.swift
@@ -19,7 +19,7 @@ extension ThreadsEndpoint: CustomEndpoint {
public var url: URL? {
var urlComponents: URLComponents = .default
urlComponents.queryItems = queryItems
- urlComponents.path = Constants.path + path
+ urlComponents.path = Constants.constants.path + path
return urlComponents.url
}
@@ -50,7 +50,7 @@ extension ThreadsEndpoint: CustomEndpoint {
}
public var header: [String : String]? {
- let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
+ let headers: [String: String] = ["OpenAI-Beta": Constants.constants.version.rawValue,
"Content-Type": "application/json"]
return headers
}
diff --git a/Sources/AISwiftAssist/Extensions/URLComponents.swift b/Sources/AISwiftAssist/Extensions/URLComponents.swift
index 4e885cb..0e404ad 100644
--- a/Sources/AISwiftAssist/Extensions/URLComponents.swift
+++ b/Sources/AISwiftAssist/Extensions/URLComponents.swift
@@ -10,8 +10,8 @@ import Foundation
extension URLComponents {
static var `default`: Self {
var components: Self = .init()
- components.scheme = Constants.baseScheme
- components.host = Constants.baseHost
+ components.scheme = Constants.constants.baseScheme
+ components.host = Constants.constants.baseHost
return components
}
diff --git a/Sources/AISwiftAssist/Models/AnyCodable.swift b/Sources/AISwiftAssist/Models/AnyCodable.swift
new file mode 100644
index 0000000..6ff5b0c
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/AnyCodable.swift
@@ -0,0 +1,67 @@
+//
+// AnyCodable.swift
+// AISwiftAssist
+//
+// Created by Alexey on 3/7/25.
+//
+
+
+import Foundation
+
+public struct AnyCodable: Codable, @unchecked Sendable {
+ public let value: Any
+
+ public init(_ value: Any) {
+ self.value = value
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ if let int = try? container.decode(Int.self) {
+ self.value = int
+ } else if let double = try? container.decode(Double.self) {
+ self.value = double
+ } else if let bool = try? container.decode(Bool.self) {
+ self.value = bool
+ } else if let string = try? container.decode(String.self) {
+ self.value = string
+ } else if container.decodeNil() {
+ self.value = ()
+ } else if let array = try? container.decode([AnyCodable].self) {
+ self.value = array
+ } else if let dict = try? container.decode([String: AnyCodable].self) {
+ self.value = dict
+ } else {
+ throw DecodingError.dataCorruptedError(
+ in: container,
+ debugDescription: "Unsupported type"
+ )
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+
+ switch value {
+ case let int as Int:
+ try container.encode(int)
+ case let double as Double:
+ try container.encode(double)
+ case let bool as Bool:
+ try container.encode(bool)
+ case let string as String:
+ try container.encode(string)
+ case Optional.none:
+ try container.encodeNil()
+ case let array as [Any]:
+ try container.encode(array.map { AnyCodable($0) })
+ case let dict as [String: Any]:
+ try container.encode(dict.mapValues { AnyCodable($0) })
+ default:
+ throw EncodingError.invalidValue(
+ value,
+ EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Unsupported type")
+ )
+ }
+ }
+}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAAssistant.swift b/Sources/AISwiftAssist/Models/Main/ASAAssistant.swift
index 9d49b5a..ee34bca 100644
--- a/Sources/AISwiftAssist/Models/Main/ASAAssistant.swift
+++ b/Sources/AISwiftAssist/Models/Main/ASAAssistant.swift
@@ -7,54 +7,339 @@
import Foundation
-/// Represents an assistant that can call the model and use tools.
-public struct ASAAssistant: Codable {
- /// The identifier of the assistant, which can be referenced in API endpoints.
+/// Represents an assistant capable of calling the model and utilizing tools.
+public struct ASAAssistant: Codable, Sendable {
+
+ /// Unique identifier of the assistant (used in API endpoints).
public let id: String
- /// The object type, which is always 'assistant'.
+ /// Object type, always "assistant".
public let objectType: String
- /// The Unix timestamp (in seconds) for when the assistant was created.
+ /// Unix timestamp of assistant creation (in seconds).
public let createdAt: Int
- /// Optional: The name of the assistant. The maximum length is 256 characters.
+ /// Optional: Assistant's name (max 256 characters).
public let name: String?
- /// Optional: The description of the assistant. The maximum length is 512 characters.
+ /// Optional: Description of the assistant (max 512 characters).
public let description: String?
- /// ID of the model to use. You can use the List models API to see all of your available models.
+ /// ID of the model used.
public let model: String
- /// Optional: The system instructions that the assistant uses. The maximum length is 32768 characters.
+ /// Optional: System instructions used by the assistant (max 256000 characters).
public let instructions: String?
- /// A list of tools enabled on the assistant. There can be a maximum of 128 tools per assistant.
- /// Tools can be of types code_interpreter, retrieval, or function.
+ /// Tools available to the assistant (max 128).
public let tools: [Tool]
- /// A list of file IDs attached to this assistant. There can be a maximum of 20 files attached to the assistant.
- /// Files are ordered by their creation date in ascending order.
- public let fileIds: [String]
+ /// Optional: Resources utilized by the assistant's tools.
+ public let toolResources: ToolResources?
- /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
- /// about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
+ /// Optional: Sampling temperature for output generation (range 0–2, default is 1).
+ public let temperature: Double?
+
+ /// Optional: Nucleus sampling parameter (range 0–1, default is 1).
+ public let topP: Double?
+
+ /// Optional: Format of the response generated by the model (e.g., auto, text, json_object).
+ /// Using this can enforce specific output formats like JSON.
+ public let responseFormat: ResponseFormat?
+
+ /// Optional: Metadata (up to 16 key-value pairs).
public let metadata: [String: String]?
- public enum CodingKeys: String, CodingKey {
- case id
- case objectType = "object"
- case createdAt = "created_at"
- case name, description, model, instructions, tools
- case fileIds = "file_ids"
- case metadata
+ enum CodingKeys: String, CodingKey {
+ case id, objectType = "object", createdAt = "created_at"
+ case name, description, model, instructions, tools, metadata, temperature, topP = "top_p", responseFormat = "response_format", toolResources = "tool_resources"
}
- /// Represents a tool enabled on the assistant.
- public struct Tool: Codable {
- /// The type of the tool (e.g., code_interpreter, retrieval, function).
- public let type: String
+ public init(
+ id: String,
+ objectType: String,
+ createdAt: Int,
+ name: String?,
+ description: String?,
+ model: String,
+ instructions: String?,
+ tools: [Tool],
+ toolResources: ToolResources?,
+ temperature: Double?,
+ topP: Double?,
+ responseFormat: ResponseFormat?,
+ metadata: [String : String]?
+ ) {
+ self.id = id
+ self.objectType = objectType
+ self.createdAt = createdAt
+ self.name = name
+ self.description = description
+ self.model = model
+ self.instructions = instructions
+ self.tools = tools
+ self.toolResources = toolResources
+ self.temperature = temperature
+ self.topP = topP
+ self.responseFormat = responseFormat
+ self.metadata = metadata
+ }
+
+ /// Tool available to the assistant.
+ public struct Tool: Codable, Sendable {
+ public let type: ToolType
+ public let function: FunctionTool?
+ public let fileSearch: FileSearchTool?
+
+ enum CodingKeys: String, CodingKey {
+ case type, function, fileSearch = "file_search"
+ }
+
+ public struct FunctionTool: Codable, Sendable {
+ public let name: String
+ public let description: String?
+ public let parameters: [String: AnyCodable]?
+ public let strict: Bool?
+ }
+
+ public struct FileSearchTool: Codable, Sendable {
+ public let maxNumResults: Int?
+ public let rankingOptions: RankingOptions?
+
+ enum CodingKeys: String, CodingKey {
+ case maxNumResults = "max_num_results"
+ case rankingOptions = "ranking_options"
+ }
+
+ public struct RankingOptions: Codable, Sendable {
+ public let ranker: String?
+ public let scoreThreshold: Double?
+
+ enum CodingKeys: String, CodingKey {
+ case ranker, scoreThreshold = "score_threshold"
+ }
+ }
+ }
+ }
+
+ /// Supported types of tools.
+ public enum ToolType: String, Codable, Sendable {
+ case codeInterpreter = "code_interpreter"
+ case fileSearch = "file_search"
+ case function
+ }
+
+ /// Resources used by the assistant's tools.
+ public struct ToolResources: Codable, Sendable {
+ public let codeInterpreter: CodeInterpreterResources?
+ public let fileSearch: FileSearchResources?
+
+ enum CodingKeys: String, CodingKey {
+ case codeInterpreter = "code_interpreter"
+ case fileSearch = "file_search"
+ }
+ }
+
+ /// Resources for the code interpreter tool.
+ public struct CodeInterpreterResources: Codable, Sendable {
+ public let fileIds: [String]
+
+ enum CodingKeys: String, CodingKey {
+ case fileIds = "file_ids"
+ }
+ }
+
+ /// Resources for the file search tool.
+ public struct FileSearchResources: Codable, Sendable {
+ public let vectorStoreIds: [String]?
+ public let vectorStores: [VectorStore]?
+
+ enum CodingKeys: String, CodingKey {
+ case vectorStoreIds = "vector_store_ids"
+ case vectorStores = "vector_stores"
+ }
+
+ /// Vector store for file searches.
+ public struct VectorStore: Codable, Sendable {
+ public let fileIds: [String]?
+ public let chunkingStrategy: ChunkingStrategy?
+
+ enum CodingKeys: String, CodingKey {
+ case fileIds = "file_ids"
+ case chunkingStrategy = "chunking_strategy"
+ }
+
+ /// Strategy for chunking files.
+ public struct ChunkingStrategy: Codable, Sendable {
+ public let type: ChunkingType
+ public let staticChunking: StaticChunking?
+
+ public enum ChunkingType: String, Codable, Sendable {
+ case auto
+ case `static`
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case staticChunking = "static"
+ }
+
+ /// Parameters for static file chunking.
+ public struct StaticChunking: Codable, Sendable {
+ public let maxChunkSizeTokens: Int
+ public let chunkOverlapTokens: Int
+
+ enum CodingKeys: String, CodingKey {
+ case maxChunkSizeTokens = "max_chunk_size_tokens"
+ case chunkOverlapTokens = "chunk_overlap_tokens"
+ }
+ }
+ }
+ }
+ }
+
+ public struct JSONSchema: Codable, Sendable {
+ public let name: String
+ public let description: String?
+ public let schema: [String: AnyCodable]?
+ public let strict: Bool?
+ }
+
+ public enum ResponseFormat: Codable, Sendable {
+ case auto
+ case text
+ case jsonObject
+ case jsonSchema(JSONSchema)
+
+ enum CodingKeys: String, CodingKey {
+ case type, jsonSchema = "json_schema"
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decode(String.self, forKey: .type)
+ switch type {
+ case "auto": self = .auto
+ case "text": self = .text
+ case "json_object": self = .jsonObject
+ case "json_schema":
+ let schema = try container.decode(JSONSchema.self, forKey: .jsonSchema)
+ self = .jsonSchema(schema)
+ default:
+ self = .auto
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case .auto: try container.encode("auto", forKey: .type)
+ case .text: try container.encode("text", forKey: .type)
+ case .jsonObject: try container.encode("json_object", forKey: .type)
+ case .jsonSchema(let schema):
+ try container.encode("json_schema", forKey: .type)
+ try container.encode(schema, forKey: .jsonSchema)
+ }
+ }
}
}
+extension ASAAssistant {
+ // MARK: - Mock Data
+
+ static let assistantMinimal: Self = .init(
+ id: "asst_minimal_001",
+ objectType: "assistant",
+ createdAt: Int(Date().timeIntervalSince1970),
+ name: nil,
+ description: nil,
+ model: "gpt-4",
+ instructions: nil,
+ tools: [],
+ toolResources: nil,
+ temperature: nil,
+ topP: nil,
+ responseFormat: nil,
+ metadata: nil
+ )
+
+ /// Средняя конфигурация с основными полями.
+ static let assistantMedium: Self = .init(
+ id: "asst_medium_001",
+ objectType: "assistant",
+ createdAt: Int(Date().timeIntervalSince1970),
+ name: "Medium Assistant",
+ description: "Assistant with moderate complexity",
+ model: "gpt-4-turbo",
+ instructions: "Answer general queries.",
+ tools: [
+ Tool(type: .codeInterpreter, function: nil, fileSearch: nil)
+ ],
+ toolResources: ToolResources(
+ codeInterpreter: CodeInterpreterResources(fileIds: ["file_123"]),
+ fileSearch: nil
+ ),
+ temperature: 0.7,
+ topP: 0.9,
+ responseFormat: .auto,
+ metadata: ["env": "staging"]
+ )
+
+ /// Полная конфигурация с максимальным набором полей.
+ static let assistantFull: Self = .init(
+ id: "asst_full_001",
+ objectType: "assistant",
+ createdAt: Int(Date().timeIntervalSince1970),
+ name: "Full Assistant",
+ description: "Fully configured assistant for advanced tasks",
+ model: "gpt-4o",
+ instructions: "You are a comprehensive assistant handling complex tasks and queries.",
+ tools: [
+ Tool(
+ type: .function,
+ function: Tool.FunctionTool(
+ name: "calculate",
+ description: "Performs basic arithmetic calculations.",
+ parameters: ["operation": AnyCodable("add"), "values": AnyCodable([1, 2])],
+ strict: true
+ ),
+ fileSearch: nil
+ ),
+ Tool(
+ type: .fileSearch,
+ function: nil,
+ fileSearch: Tool.FileSearchTool(
+ maxNumResults: 10,
+ rankingOptions: Tool.FileSearchTool.RankingOptions(
+ ranker: "auto",
+ scoreThreshold: 0.8
+ )
+ )
+ ),
+ Tool(
+ type: .codeInterpreter,
+ function: nil,
+ fileSearch: nil
+ )
+ ],
+ toolResources: ToolResources(
+ codeInterpreter: CodeInterpreterResources(
+ fileIds: ["file_456", "file_789"]
+ ),
+ fileSearch: FileSearchResources(
+ vectorStoreIds: ["vector_store_1"],
+ vectorStores: nil
+ )
+ ),
+ temperature: 0.5,
+ topP: 0.85,
+ responseFormat: .jsonObject,
+ metadata: ["version": "1.0", "env": "production"]
+ )
+
+ static let mocks: [Self] = [
+ assistantMinimal,
+ assistantMedium,
+ assistantFull
+ ]
+}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAAssistantFile.swift b/Sources/AISwiftAssist/Models/Main/ASAAssistantFile.swift
deleted file mode 100644
index 822f8b3..0000000
--- a/Sources/AISwiftAssist/Models/Main/ASAAssistantFile.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/5/23.
-//
-
-import Foundation
-
-/// Represents an assistant file that can be used by the assistant.
-public struct ASAAssistantFile: Codable {
- /// The identifier of the assistant file.
- public let id: String
-
- /// The object type, which is always 'assistant.file'.
- public let objectType: String
-
- /// The Unix timestamp (in seconds) for when the assistant file was created.
- public let createdAt: Int
-
- /// The identifier of the assistant to which this file belongs.
- public let assistantId: String
-
- enum CodingKeys: String, CodingKey {
- case id, objectType = "object", createdAt = "created_at", assistantId = "assistant_id"
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAFile.swift b/Sources/AISwiftAssist/Models/Main/ASAFile.swift
deleted file mode 100644
index 35fa7be..0000000
--- a/Sources/AISwiftAssist/Models/Main/ASAFile.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-
-/// Represents a document that has been uploaded to OpenAI.
-public struct ASAFile: Codable {
- /// The file identifier, which can be referenced in the API endpoints.
- public let id: String
-
- /// The size of the file, in bytes.
- public let bytes: Int
-
- /// The Unix timestamp (in seconds) for when the file was created.
- public let createdAt: Int
-
- /// The name of the file.
- public let filename: String
-
- /// The object type, which is always 'file'.
- public let object: String
-
- /// The intended purpose of the file. Supported values are 'fine-tune', 'fine-tune-results', 'assistants', and 'assistants_output'.
- public let purpose: String
-
- enum CodingKeys: String, CodingKey {
- case id, bytes
- case createdAt = "created_at"
- case filename, object, purpose
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAMessage.swift b/Sources/AISwiftAssist/Models/Main/ASAMessage.swift
new file mode 100644
index 0000000..2e78111
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/Main/ASAMessage.swift
@@ -0,0 +1,203 @@
+//
+// File.swift
+//
+//
+// Created by Alexey on 11/15/23.
+//
+
+import Foundation
+
+/// Represents a message within a thread.
+public struct ASAMessage: Codable, Sendable {
+ /// Unique identifier of the message.
+ public let id: String
+
+ /// Object type, always "thread.message".
+ public let object: String
+
+ /// Unix timestamp (in seconds) when the message was created.
+ public let createdAt: Int
+
+ /// ID of the thread this message belongs to.
+ public let threadId: String
+
+ /// The status of the message (in_progress, incomplete, completed).
+ public let status: Status
+
+ /// Details about why the message is incomplete (if applicable).
+ public let incompleteDetails: IncompleteDetails?
+
+ /// Unix timestamp (in seconds) when the message was completed.
+ public let completedAt: Int?
+
+ /// Unix timestamp (in seconds) when the message was marked incomplete.
+ public let incompleteAt: Int?
+
+ /// Role of the entity that produced the message (user or assistant).
+ public let role: Role
+
+ /// Content of the message (array of text and/or images).
+ public let content: [Content]
+
+ /// ID of the assistant that authored this message (if applicable).
+ public let assistantId: String?
+
+ /// ID of the run associated with the creation of this message.
+ public let runId: String?
+
+ /// Attachments (files attached to the message and their tools).
+ public let attachments: [Attachment]?
+
+ /// Metadata (max 16 key-value pairs).
+ public let metadata: [String: String]?
+
+ enum CodingKeys: String, CodingKey {
+ case id, object, status, role, content, attachments, metadata
+ case createdAt = "created_at"
+ case threadId = "thread_id"
+ case incompleteDetails = "incomplete_details"
+ case completedAt = "completed_at"
+ case incompleteAt = "incomplete_at"
+ case assistantId = "assistant_id"
+ case runId = "run_id"
+ }
+
+ /// Status of the message.
+ public enum Status: String, Codable, Sendable {
+ case inProgress = "in_progress"
+ case incomplete
+ case completed
+ }
+
+ /// Details about why the message is incomplete.
+ public struct IncompleteDetails: Codable, Sendable {
+ /// Reason the message is incomplete.
+ public let reason: String
+ }
+
+ /// Role type of the message sender.
+ public enum Role: String, Codable, Sendable {
+ case user
+ case assistant
+ }
+
+ /// Content of the message.
+ public enum Content: Codable, Sendable {
+ case text(TextContent)
+ case imageFile(ImageFileContent)
+ case imageUrl(ImageURLContent)
+
+ enum CodingKeys: String, CodingKey {
+ case type, text, imageFile = "image_file", imageUrl = "image_url"
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decode(String.self, forKey: .type)
+
+ switch type {
+ case "text":
+ self = .text(try container.decode(TextContent.self, forKey: .text))
+ case "image_file":
+ self = .imageFile(try container.decode(ImageFileContent.self, forKey: .imageFile))
+ case "image_url":
+ self = .imageUrl(try container.decode(ImageURLContent.self, forKey: .imageUrl))
+ default:
+ throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unknown content type")
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case .text(let textContent):
+ try container.encode("text", forKey: .type)
+ try container.encode(textContent, forKey: .text)
+ case .imageFile(let imageFileContent):
+ try container.encode("image_file", forKey: .type)
+ try container.encode(imageFileContent, forKey: .imageFile)
+ case .imageUrl(let imageUrlContent):
+ try container.encode("image_url", forKey: .type)
+ try container.encode(imageUrlContent, forKey: .imageUrl)
+ }
+ }
+ }
+
+ /// Represents text content.
+ public struct TextContent: Codable, Sendable {
+ public let value: String
+ public let annotations: [Annotation]?
+ }
+
+ /// Represents an image file content.
+ public struct ImageFileContent: Codable, Sendable {
+ public let fileId: String
+ public let detail: String?
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ case detail
+ }
+ }
+
+ /// Represents an image URL content.
+ public struct ImageURLContent: Codable, Sendable {
+ public let url: URL
+ public let detail: String?
+ }
+
+ /// Annotation details within text content.
+ public struct Annotation: Codable, Sendable {
+ public let type: String
+ public let text: String
+ public let startIndex: Int
+ public let endIndex: Int
+ public let fileCitation: FileCitation?
+ public let filePath: FilePath?
+
+ enum CodingKeys: String, CodingKey {
+ case type, text
+ case startIndex = "start_index"
+ case endIndex = "end_index"
+ case fileCitation = "file_citation"
+ case filePath = "file_path"
+ }
+ }
+
+ /// Citation pointing to a specific file quote.
+ public struct FileCitation: Codable, Sendable {
+ public let fileId: String
+ public let quote: String
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ case quote
+ }
+ }
+
+ /// URL for the file generated by the assistant.
+ public struct FilePath: Codable, Sendable {
+ public let fileId: String
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ }
+ }
+
+ /// Represents a file attachment.
+ public struct Attachment: Codable, Sendable {
+ public let fileId: String
+ public let tools: [ToolType]
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ case tools
+ }
+ }
+
+ /// Type of tool associated with an attachment.
+ public enum ToolType: String, Codable, Sendable {
+ case codeInterpreter = "code_interpreter"
+ case fileSearch = "file_search"
+ }
+}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAMessageFile.swift b/Sources/AISwiftAssist/Models/Main/ASAMessageFile.swift
deleted file mode 100644
index dd4063f..0000000
--- a/Sources/AISwiftAssist/Models/Main/ASAMessageFile.swift
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/5/23.
-//
-
-import Foundation
-
-/// Represents a file attached to a message.
-public struct ASAMessageFile: Codable {
- /// The identifier of the file, which can be referenced in API endpoints.
- public let id: String
-
- /// The object type, which is always 'thread.message.file'.
- public let object: String
-
- /// The Unix timestamp (in seconds) for when the message file was created.
- public let createdAt: Int
-
- /// The ID of the message that the file is attached to.
- public let messageId: String
-
- enum CodingKeys: String, CodingKey {
- case id, object
- case createdAt = "created_at"
- case messageId = "message_id"
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/ASAModel.swift b/Sources/AISwiftAssist/Models/Main/ASAModel.swift
deleted file mode 100644
index cc90f7f..0000000
--- a/Sources/AISwiftAssist/Models/Main/ASAModel.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-
-public struct ASAModel: Codable {
- // The model identifier, which can be referenced in the API endpoints.
- public let id: String
-
- // The Unix timestamp (in seconds) when the model was created.
- public let created: Int
-
- // The object type, which is always "model".
- public let object: String
-
- // The organization that owns the model.
- public let ownedBy: String
-
- enum CodingKeys: String, CodingKey {
- case id, created, object
- case ownedBy = "owned_by"
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/ASARun.swift b/Sources/AISwiftAssist/Models/Main/ASARun.swift
index ead7e5a..2e56145 100644
--- a/Sources/AISwiftAssist/Models/Main/ASARun.swift
+++ b/Sources/AISwiftAssist/Models/Main/ASARun.swift
@@ -8,131 +8,270 @@
import Foundation
/// Represents an execution run on a thread.
-public struct ASARun: Codable {
- /// The identifier of the run, which can be referenced in API endpoints.
+public struct ASARun: Codable, Sendable {
+
+ /// The identifier, which can be referenced in API endpoints.
public let id: String
- /// The object type, which is always 'thread.run'.
+ /// The object type, always `thread.run`.
public let object: String
- /// The Unix timestamp (in seconds) for when the run was created.
+ /// The Unix timestamp (in seconds) when the run was created.
public let createdAt: Int
- /// The ID of the thread that was executed on as a part of this run.
+ /// The ID of the thread that was executed as part of this run.
public let threadId: String
/// The ID of the assistant used for execution of this run.
public let assistantId: String
- /// The status of the run, which can be either queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired.
- public let status: String
+ /// The status of the run.
+ public let status: Status
- /// Details on the action required to continue the run. Will be null if no action is required.
+ /// Details on the action required to continue the run. Null if no action is required.
public let requiredAction: RequiredAction?
- /// The last error associated with this run. Will be null if there are no errors.
+ /// The last error associated with this run. Null if there are no errors.
public let lastError: LastError?
- /// The Unix timestamp (in seconds) for when the run will expire.
+ /// The Unix timestamp (in seconds) when the run will expire.
public let expiresAt: Int?
- /// The Unix timestamp (in seconds) for when the run was started. Null if not started.
+ /// The Unix timestamp (in seconds) when the run started.
public let startedAt: Int?
- /// The Unix timestamp (in seconds) for when the run was cancelled. Null if not cancelled.
+ /// The Unix timestamp (in seconds) when the run was cancelled.
public let cancelledAt: Int?
- /// The Unix timestamp (in seconds) for when the run failed. Null if not failed.
+ /// The Unix timestamp (in seconds) when the run failed.
public let failedAt: Int?
- /// The Unix timestamp (in seconds) for when the run was completed. Null if not completed.
+ /// The Unix timestamp (in seconds) when the run was completed.
public let completedAt: Int?
- /// The model that the assistant used for this run.
+ /// The model used by the assistant for this run.
public let model: String
- /// The instructions that the assistant used for this run.
+ /// Instructions used by the assistant for this run.
public let instructions: String?
- /// The list of tools that the assistant used for this run.
- /// Tools can be of types code_interpreter, retrieval, or function.
+ /// Tools used by the assistant for this run.
public let tools: [Tool]
- /// The list of File IDs the assistant used for this run.
- public let fileIds: [String]
-
- /// Set of 16 key-value pairs that can be attached to the run. Useful for storing additional information.
+ /// Set of key-value pairs with additional information about the object.
public let metadata: [String: String]?
- /// Represents the required action details for the run to continue.
- public struct RequiredAction: Codable {
- /// For now, this is always 'submit_tool_outputs'.
- public let type: String
+ /// Usage statistics related to the run. Null if the run is not in a terminal state.
+ public let usage: Usage?
+
+ /// The sampling temperature used for this run. Defaults to 1 if not set.
+ public let temperature: Double?
+
+ /// The nucleus sampling value used for this run. Defaults to 1 if not set.
+ public let topP: Double?
+
+ /// Maximum number of completion tokens allowed for this run.
+ public let maxCompletionTokens: Int?
+
+ /// Maximum number of prompt tokens allowed for this run.
+ public let maxPromptTokens: Int?
+
+ /// Controls how a thread will be truncated prior to the run.
+ public let truncationStrategy: TruncationStrategy?
+
+ /// Specifies the format that the model must output.
+ public let responseFormat: ResponseFormat?
+
+ /// Controls which (if any) tool is called by the model.
+ public let toolChoice: ToolChoice?
- /// Details on the tool outputs needed for this run to continue.
+ /// Enables parallel function calling during tool use.
+ public let parallelToolCalls: Bool?
+
+ /// Details on why the run is incomplete. Null if the run is not incomplete.
+ public let incompleteDetails: IncompleteDetails?
+
+ public enum Status: String, Codable, Sendable {
+ case queued
+ case inProgress = "in_progress"
+ case requiresAction = "requires_action"
+ case cancelling
+ case cancelled
+ case failed
+ case completed
+ case incomplete
+ case expired
+ }
+
+ public struct RequiredAction: Codable, Sendable {
+ public let type: String
public let submitToolOutputs: SubmitToolOutputs
+ public struct SubmitToolOutputs: Codable, Sendable {
+ public let toolCalls: [ToolCall]
+
+ public struct ToolCall: Codable, Sendable {
+ public let id: String
+ public let type: String
+ public let function: Function
+
+ public struct Function: Codable, Sendable {
+ public let name: String
+ public let arguments: String
+ }
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case toolCalls = "tool_calls"
+ }
+ }
+
enum CodingKeys: String, CodingKey {
case type
case submitToolOutputs = "submit_tool_outputs"
}
+ }
- /// Represents the tool outputs needed for this run to continue.
- public struct SubmitToolOutputs: Codable {
- /// A list of the relevant tool calls.
- public let toolCalls: [ToolCall]
+ public struct LastError: Codable, Sendable {
+ public let code: String
+ public let message: String
+ }
- /// Represents a single tool call.
- public struct ToolCall: Codable {
- /// The ID of the tool call.
- public let id: String
+ public struct Tool: Codable, Sendable {
+ public let type: ToolType
+ public let function: FunctionTool?
+ public let fileSearch: FileSearchTool?
- /// The type of tool call the output is required for. For now, this is always 'function'.
- public let type: String
+ public enum ToolType: String, Codable, Sendable {
+ case codeInterpreter = "code_interpreter"
+ case fileSearch = "file_search"
+ case function
+ }
- /// The function definition.
- public let function: Function
+ public struct FunctionTool: Codable, Sendable {
+ public let name: String
+ public let description: String?
+ public let parameters: [String: AnyCodable]?
+ public let strict: Bool?
+ }
- /// Represents the function definition.
- public struct Function: Codable {
- /// The name of the function.
- public let name: String
+ public struct FileSearchTool: Codable, Sendable {
+ public let maxNumResults: Int?
+ public let rankingOptions: RankingOptions?
- /// The arguments that the model expects you to pass to the function.
- public let arguments: String
+ public struct RankingOptions: Codable, Sendable {
+ public let ranker: String?
+ public let scoreThreshold: Double?
+
+ enum CodingKeys: String, CodingKey {
+ case ranker
+ case scoreThreshold = "score_threshold"
}
}
+
+ enum CodingKeys: String, CodingKey {
+ case maxNumResults = "max_num_results"
+ case rankingOptions = "ranking_options"
+ }
}
}
- public struct LastError: Codable {
- /// One of 'server_error' or 'rate_limit_exceeded'.
- public let code: String
+ public struct Usage: Codable, Sendable {
+ public let completionTokens: Int
+ public let promptTokens: Int
+ public let totalTokens: Int
- /// A human-readable description of the error.
- public let message: String
+ enum CodingKeys: String, CodingKey {
+ case completionTokens = "completion_tokens"
+ case promptTokens = "prompt_tokens"
+ case totalTokens = "total_tokens"
+ }
}
- /// Represents a tool enabled on the assistant.
- public struct Tool: Codable {
- /// The type of the tool (e.g., code_interpreter, retrieval, function).
+ public struct TruncationStrategy: Codable, Sendable {
public let type: String
+ public let lastMessages: Int?
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case lastMessages = "last_messages"
+ }
+ }
+
+ public enum ResponseFormat: Codable, Sendable {
+ case auto
+ case text
+ case jsonObject
+ case jsonSchema(JSONSchema)
+
+ public struct JSONSchema: Codable, Sendable {
+ public let name: String
+ public let description: String?
+ public let schema: [String: AnyCodable]?
+ public let strict: Bool?
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case jsonSchema = "json_schema"
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decode(String.self, forKey: .type)
+ switch type {
+ case "auto": self = .auto
+ case "text": self = .text
+ case "json_object": self = .jsonObject
+ case "json_schema":
+ let schema = try container.decode(JSONSchema.self, forKey: .jsonSchema)
+ self = .jsonSchema(schema)
+ default:
+ self = .auto
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case .auto: try container.encode("auto", forKey: .type)
+ case .text: try container.encode("text", forKey: .type)
+ case .jsonObject: try container.encode("json_object", forKey: .type)
+ case .jsonSchema(let schema):
+ try container.encode("json_schema", forKey: .type)
+ try container.encode(schema, forKey: .jsonSchema)
+ }
+ }
+ }
+
+ public enum ToolChoice: Codable, Sendable {
+ case none
+ case auto
+ case required
+ case specificTool(SpecificTool)
+
+ public struct SpecificTool: Codable, Sendable {
+ public let type: String
+ public let function: SpecificFunction?
+
+ public struct SpecificFunction: Codable, Sendable {
+ public let name: String
+ }
+ }
+ }
+
+ public struct IncompleteDetails: Codable, Sendable {
+ public let reason: String
}
enum CodingKeys: String, CodingKey {
- case id, object
- case createdAt = "created_at"
- case threadId = "thread_id"
- case assistantId = "assistant_id"
- case status, requiredAction = "required_action"
- case lastError = "last_error"
- case expiresAt = "expires_at"
- case startedAt = "started_at"
- case cancelledAt = "cancelled_at"
- case failedAt = "failed_at"
- case completedAt = "completed_at"
- case model, instructions, tools
- case fileIds = "file_ids"
- case metadata
+ case id, object, createdAt = "created_at", threadId = "thread_id", assistantId = "assistant_id",
+ status, requiredAction = "required_action", lastError = "last_error",
+ expiresAt = "expires_at", startedAt = "started_at", cancelledAt = "cancelled_at",
+ failedAt = "failed_at", completedAt = "completed_at", model, instructions, tools, metadata,
+ usage, temperature, topP = "top_p", maxCompletionTokens = "max_completion_tokens",
+ maxPromptTokens = "max_prompt_tokens", truncationStrategy = "truncation_strategy",
+ responseFormat = "response_format", toolChoice = "tool_choice",
+ parallelToolCalls = "parallel_tool_calls", incompleteDetails = "incomplete_details"
}
}
diff --git a/Sources/AISwiftAssist/Models/Main/ASARunStep.swift b/Sources/AISwiftAssist/Models/Main/ASARunStep.swift
index 2448b65..eed2b7f 100644
--- a/Sources/AISwiftAssist/Models/Main/ASARunStep.swift
+++ b/Sources/AISwiftAssist/Models/Main/ASARunStep.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents a step in the execution of a run.
-public struct ASARunStep: Codable {
+public struct ASARunStep: Codable, Sendable {
/// The identifier of the run step, which can be referenced in API endpoints.
let id: String
@@ -55,13 +55,13 @@ public struct ASARunStep: Codable {
let metadata: [String: String]?
/// A structure to represent the step details.
- struct StepDetails: Codable {
+ struct StepDetails: Codable, Sendable {
let type: String // This can be 'message_creation' or 'tool_calls'.
let messageCreation: MessageCreation?
let toolCalls: [ToolCall]?
/// A structure to represent the message creation details.
- struct MessageCreation: Codable {
+ struct MessageCreation: Codable, Sendable {
let messageId: String
enum CodingKeys: String, CodingKey {
@@ -70,7 +70,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the tool calls.
- struct ToolCall: Codable {
+ struct ToolCall: Codable, Sendable {
let id: String
let type: String // This can be 'code_interpreter', 'retrieval', or 'function'.
let details: ToolCallDetails?
@@ -80,7 +80,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the details of a tool call.
- struct ToolCallDetails: Codable {
+ struct ToolCallDetails: Codable, Sendable {
let codeInterpreter: CodeInterpreter?
let retrieval: Retrieval?
let function: FunctionCall?
@@ -92,7 +92,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the code interpreter tool call.
- struct CodeInterpreter: Codable {
+ struct CodeInterpreter: Codable, Sendable {
let id: String
let type: String // This will always be 'code_interpreter'.
let input: String
@@ -103,7 +103,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the outputs of a code interpreter tool call.
- struct CodeInterpreterOutput: Codable {
+ struct CodeInterpreterOutput: Codable, Sendable {
let type: String // Can be 'logs' or 'image'.
let logs: String?
let image: ImageOutput?
@@ -113,7 +113,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the image output.
- struct ImageOutput: Codable {
+ struct ImageOutput: Codable, Sendable {
let fileId: String
enum CodingKeys: String, CodingKey {
@@ -124,12 +124,12 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the retrieval tool call.
- struct Retrieval: Codable {
+ struct Retrieval: Codable, Sendable {
// For now, it's always an empty object.
}
/// A structure to represent the function tool call.
- struct FunctionCall: Codable {
+ struct FunctionCall: Codable, Sendable {
let name: String
let arguments: String
let output: String?
@@ -149,7 +149,7 @@ public struct ASARunStep: Codable {
}
/// A structure to represent the last error.
- struct LastError: Codable {
+ struct LastError: Codable, Sendable {
/// One of `server_error` or `rate_limit_exceeded`.
let code: String
diff --git a/Sources/AISwiftAssist/Models/Main/ASAThread.swift b/Sources/AISwiftAssist/Models/Main/ASAThread.swift
index e44e18f..4874eee 100644
--- a/Sources/AISwiftAssist/Models/Main/ASAThread.swift
+++ b/Sources/AISwiftAssist/Models/Main/ASAThread.swift
@@ -6,25 +6,57 @@
//
import Foundation
+
/// Represents a thread that contains messages.
-public struct ASAThread: Codable {
+public struct ASAThread: Codable, Sendable {
/// The identifier of the thread, which can be referenced in API endpoints.
public let id: String
- /// The object type, which is always 'thread'.
+ /// The object type, always 'thread'.
public let object: String
- /// The Unix timestamp (in seconds) for when the thread was created.
+ /// The Unix timestamp (in seconds) when the thread was created.
public let createdAt: Int
- /// Optional: Set of 16 key-value pairs that can be attached to the thread.
- /// This can be useful for storing additional information about the thread in a structured format.
- /// Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long.
+ /// Resources available to the assistant's tools in this thread.
+ public let toolResources: ToolResources?
+
+ /// Optional metadata (max 16 key-value pairs).
public let metadata: [String: String]?
enum CodingKeys: String, CodingKey {
case id, object
case createdAt = "created_at"
+ case toolResources = "tool_resources"
case metadata
}
+
+ public struct ToolResources: Codable, Sendable {
+ public let codeInterpreter: CodeInterpreter?
+ public let fileSearch: FileSearch?
+
+ enum CodingKeys: String, CodingKey {
+ case codeInterpreter = "code_interpreter"
+ case fileSearch = "file_search"
+ }
+ }
+
+ public struct CodeInterpreter: Codable, Sendable {
+ /// A list of file IDs (max 20 files).
+ public let fileIds: [String]
+
+ enum CodingKeys: String, CodingKey {
+ case fileIds = "file_ids"
+ }
+ }
+
+ public struct FileSearch: Codable, Sendable {
+ /// A list of vector store IDs (max 1).
+ public let vectorStoreIds: [String]
+
+ enum CodingKeys: String, CodingKey {
+ case vectorStoreIds = "vector_store_ids"
+ }
+ }
+
}
diff --git a/Sources/AISwiftAssist/Models/Main/Message/ASAImageContent.swift b/Sources/AISwiftAssist/Models/Main/Message/ASAImageContent.swift
deleted file mode 100644
index 3013ccb..0000000
--- a/Sources/AISwiftAssist/Models/Main/Message/ASAImageContent.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/13/23.
-//
-
-import Foundation
-
-/// Represents an image file in the content of a message.
-public struct ASAImageContent: Codable {
- /// The File ID of the image in the message content.
- public let file_id: String
-
- enum CodingKeys: String, CodingKey {
- case file_id
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/Message/ASAMessage.swift b/Sources/AISwiftAssist/Models/Main/Message/ASAMessage.swift
deleted file mode 100644
index d6c523d..0000000
--- a/Sources/AISwiftAssist/Models/Main/Message/ASAMessage.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-
-/// Represents a message within a thread.
-public struct ASAMessage: Codable {
- /// The identifier of the message, which can be referenced in API endpoints.
- public let id: String
-
- /// The object type, which is always 'thread.message'.
- public let object: String
-
- /// The Unix timestamp (in seconds) for when the message was created.
- public let createdAt: Int
-
- /// The thread ID that this message belongs to.
- public let threadId: String
-
- /// The entity that produced the message. One of 'user' or 'assistant'.
- public let role: String
-
- /// The content of the message in array of text and/or images.
- public let content: [ASAMessageContent]
-
- /// If applicable, the ID of the assistant that authored this message.
- public let assistantId: String?
-
- /// If applicable, the ID of the run associated with the authoring of this message.
- public let runId: String?
-
- /// A list of file IDs that the assistant should use. Useful for tools like retrieval and code_interpreter that can access files.
- /// A maximum of 10 files can be attached to a message.
- public let fileIds: [String]
-
- /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
- /// about the object in a structured format. Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long.
- public let metadata: [String: String]?
-
- enum CodingKeys: String, CodingKey {
- case id, object
- case createdAt = "created_at"
- case threadId = "thread_id"
- case role, content
- case assistantId = "assistant_id"
- case runId = "run_id"
- case fileIds = "file_ids"
- case metadata
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Main/Message/ASAMessageContent.swift b/Sources/AISwiftAssist/Models/Main/Message/ASAMessageContent.swift
deleted file mode 100644
index d877733..0000000
--- a/Sources/AISwiftAssist/Models/Main/Message/ASAMessageContent.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/13/23.
-//
-
-import Foundation
-
-public enum ASAMessageContent: Codable {
- case image(ASAImageContent)
- case text(ASATextContent)
-
- enum CodingKeys: String, CodingKey {
- case type, imageFile = "image_file", text
- }
-
- public init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- let type = try container.decode(String.self, forKey: .type)
-
- switch type {
- case "image_file":
- let imageContent = try container.decode(ASAImageContent.self, forKey: .imageFile)
- self = .image(imageContent)
- case "text":
- let textContent = try container.decode(ASATextContent.self, forKey: .text)
- self = .text(textContent)
- default:
- throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid type")
- }
- }
-
- public func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
-
- switch self {
- case .image(let imageContent):
- try container.encode("image_file", forKey: .type)
- try container.encode(imageContent, forKey: .imageFile)
- case .text(let textContent):
- try container.encode("text", forKey: .type)
- try container.encode(textContent, forKey: .text)
- }
- }
-}
-
-
diff --git a/Sources/AISwiftAssist/Models/Main/Message/ASATextContent.swift b/Sources/AISwiftAssist/Models/Main/Message/ASATextContent.swift
deleted file mode 100644
index e137d24..0000000
--- a/Sources/AISwiftAssist/Models/Main/Message/ASATextContent.swift
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/13/23.
-//
-
-import Foundation
-
-/// Represents the text content of a message.
-public struct ASATextContent: Codable {
- /// The value of the text.
- public let value: String
-
- /// Optional: Annotations
- public let annotations: [ASAAnnotation]?
-}
-
-public struct ASAAnnotation: Codable {
- let type: String
- let text: String
- let startIndex: Int
- let endIndex: Int
- let fileCitation: ASAFileCitation?
- let filePath: ASAFilePath?
-
- enum CodingKeys: String, CodingKey {
- case type, text, startIndex = "start_index", endIndex = "end_index", fileCitation = "file_citation", filePath = "file_path"
- }
-}
-
-public struct ASAFileCitation: Codable {
- public let fileId: String
- public let quote: String
-
- enum CodingKeys: String, CodingKey {
- case fileId = "file_id", quote
- }
-}
-
-public struct ASAFilePath: Codable {
- public let fileId: String
-
- enum CodingKeys: String, CodingKey {
- case fileId = "file_id"
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateAssistantFileRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateAssistantFileRequest.swift
deleted file mode 100644
index e285b87..0000000
--- a/Sources/AISwiftAssist/Models/Request/ASACreateAssistantFileRequest.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/5/23.
-//
-
-import Foundation
-
-/// Represents a request to create an assistant file.
-public struct ASACreateAssistantFileRequest: Codable {
- /// A File ID (with purpose="assistants") that the assistant should use.
- /// Useful for tools like retrieval and code_interpreter that can access files.
- public let fileId: String
-
- public init(fileId: String) {
- self.fileId = fileId
- }
-
- enum CodingKeys: String, CodingKey {
- case fileId = "file_id"
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateAssistantRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateAssistantRequest.swift
deleted file mode 100644
index 5e03b99..0000000
--- a/Sources/AISwiftAssist/Models/Request/ASACreateAssistantRequest.swift
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-
-public struct ASACreateAssistantRequest: Codable {
- /// ID of the model to use. You can use the List models API to see all of your available models.
- public let model: String
-
- /// Optional: The name of the assistant. The maximum length is 256 characters.
- public let name: String?
-
- /// Optional: The description of the assistant. The maximum length is 512 characters.
- public let description: String?
-
- /// Optional: The system instructions that the assistant uses. The maximum length is 32768 characters.
- public let instructions: String?
-
- /// Optional: A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.
- /// Tools can be of types code_interpreter, retrieval, or function.
- public let tools: [Tool]?
-
- /// Optional: A list of file IDs attached to this assistant. There can be a maximum of 20 files attached to the assistant.
- /// Files are ordered by their creation date in ascending order.
- public let fileIds: [String]?
-
- /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
- /// about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
- public let metadata: [String: String]?
-
- public enum CodingKeys: String, CodingKey {
- case model, name, description, instructions, tools
- case fileIds = "file_ids"
- case metadata
- }
-
- public init(model: String, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
- self.model = model
- self.name = name
- self.description = description
- self.instructions = instructions
- self.tools = tools
- self.fileIds = fileIds
- self.metadata = metadata
- }
-
- public init(asaModel: ASAModel, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
- self.model = asaModel.id
- self.name = name
- self.description = description
- self.instructions = instructions
- self.tools = tools
- self.fileIds = fileIds
- self.metadata = metadata
- }
-
- /// Represents a tool enabled on the assistant.
- public struct Tool: Codable {
- /// The type of the tool (e.g., code_interpreter, retrieval, function).
- let type: String
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateMessageRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateMessageRequest.swift
deleted file mode 100644
index f3ff362..0000000
--- a/Sources/AISwiftAssist/Models/Request/ASACreateMessageRequest.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-
-/// A request structure for creating a message in a thread.
-public struct ASACreateMessageRequest: Codable {
-
- /// The role of the entity that is creating the message. Currently only 'user' is supported.
- public let role: String
-
- /// The content of the message.
- public let content: String
-
- /// Optional: A list of File IDs that the message should use. A maximum of 10 files can be attached to a message.
- public let fileIds: [String]?
-
- /// Optional: Set of 16 key-value pairs that can be attached to the message. Useful for storing additional information.
- public let metadata: [String: String]?
-
- enum CodingKeys: String, CodingKey {
- case role, content
- case fileIds = "file_ids"
- case metadata
- }
-
- public init(role: String, content: String, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
- self.role = role
- self.content = content
- self.fileIds = fileIds
- self.metadata = metadata
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateRunRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateRunRequest.swift
deleted file mode 100644
index 0569bd5..0000000
--- a/Sources/AISwiftAssist/Models/Request/ASACreateRunRequest.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/16/23.
-//
-
-import Foundation
-
-/// A request structure for creating a run in a thread.
-public struct ASACreateRunRequest: Codable {
-
- /// The ID of the assistant to use to execute this run.
- public let assistantId: String
-
- /// Optional: The ID of the Model to be used to execute this run.
- public let model: String?
-
- /// Optional: Override the default system message of the assistant.
- public let instructions: String?
-
- /// Optional: Override the tools the assistant can use for this run.
- public let tools: [Tool]?
-
- /// Optional: Set of 16 key-value pairs that can be attached to the run.
- public let metadata: [String: String]?
-
- /// Represents a tool that can be used by the assistant during the run.
- public struct Tool: Codable {
- /// The type of tool being defined: 'code_interpreter', 'retrieval', 'function'.
- public let type: String
-
- /// Additional details for the 'function' tool type.
- public let function: Function?
-
- /// Represents a function tool's details.
- public struct Function: Codable {
- /// Optional: A description of what the function does.
- public let description: String?
-
- /// The name of the function to be called.
- public let name: String
-
- /// The parameters the functions accepts, described as a JSON Schema object.
- public let parameters: [String: String]
-
- enum CodingKeys: String, CodingKey {
- case description, name, parameters
- }
- }
-
- enum CodingKeys: String, CodingKey {
- case type, function
- }
- }
-
- enum CodingKeys: String, CodingKey {
- case assistantId = "assistant_id"
- case model, instructions, tools, metadata
- }
-
- public init(assistantId: String, model: String? = nil, instructions: String? = nil, tools: [ASACreateRunRequest.Tool]? = nil, metadata: [String : String]? = nil) {
- self.assistantId = assistantId
- self.model = model
- self.instructions = instructions
- self.tools = tools
- self.metadata = metadata
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateThreadRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateThreadRequest.swift
index 4f52a77..a089d5d 100644
--- a/Sources/AISwiftAssist/Models/Request/ASACreateThreadRequest.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASACreateThreadRequest.swift
@@ -8,31 +8,88 @@
import Foundation
/// A request structure for creating a thread.
-public struct ASACreateThreadRequest: Codable {
+public struct ASACreateThreadRequest: Codable, Sendable {
/// Optional: A list of messages to start the thread with.
public let messages: [Message]?
- public struct Message: Codable {
- /// Required: The role of the entity that is creating the message. Currently, only 'user' is supported.
+ /// Optional: Resources available to the assistant's tools in this thread.
+ public let toolResources: ASAThread.ToolResources?
+
+ /// Optional: A helper to create a vector store and attach it to this thread (max 1).
+ public let vectorStores: [VectorStore]?
+
+ /// Optional: Metadata associated with the thread (max 16 key-value pairs).
+ public let metadata: [String: String]?
+
+ public struct Message: Codable, Sendable {
+ /// Required: The role of the entity that is creating the message ('user' or 'assistant').
public let role: String
- /// Required: The content of the message.
+ /// Required: The content of the message (can be string or array, simplified to string here).
public let content: String
- /// Optional: A list of File IDs that the message should use. A maximum of 10 files can be attached to a message.
+ /// Optional: A list of file attachments.
+ public let attachments: [Attachment]?
+
+ /// Optional: Metadata for the message (max 16 key-value pairs).
+ public let metadata: [String: String]?
+
+ enum CodingKeys: String, CodingKey {
+ case role, content, attachments, metadata
+ }
+ }
+
+ public struct Attachment: Codable, Sendable {
+ /// The ID of the file to attach.
+ public let fileId: String
+
+ /// Tools to add the file to.
+ public let tools: [Tool]?
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ case tools
+ }
+ }
+
+ public struct Tool: Codable, Sendable {
+ /// Type of tool (e.g., 'file_search').
+ public let type: String
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ }
+ }
+
+ public struct VectorStore: Codable, Sendable {
+ /// A list of file IDs to add to the vector store (max 10000 files).
public let fileIds: [String]?
- /// Optional: Set of 16 key-value pairs that can be attached to the message. Useful for storing additional information.
+ /// Optional metadata for the vector store (max 16 key-value pairs).
public let metadata: [String: String]?
enum CodingKeys: String, CodingKey {
- case role, content
case fileIds = "file_ids"
case metadata
}
}
- public init(messages: [Message]? = nil) {
+ enum CodingKeys: String, CodingKey {
+ case messages
+ case toolResources = "tool_resources"
+ case vectorStores = "vector_stores"
+ case metadata
+ }
+
+ public init(
+ messages: [Message]? = nil,
+ toolResources: ASAThread.ToolResources? = nil,
+ vectorStores: [VectorStore]? = nil,
+ metadata: [String: String]? = nil
+ ) {
self.messages = messages
+ self.toolResources = toolResources
+ self.vectorStores = vectorStores
+ self.metadata = metadata
}
}
diff --git a/Sources/AISwiftAssist/Models/Request/ASACreateThreadRunRequest.swift b/Sources/AISwiftAssist/Models/Request/ASACreateThreadRunRequest.swift
index 35bcdc3..2ea47ce 100644
--- a/Sources/AISwiftAssist/Models/Request/ASACreateThreadRunRequest.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASACreateThreadRunRequest.swift
@@ -8,7 +8,7 @@
import Foundation
/// A request structure for creating a thread and run.
-public struct ASACreateThreadRunRequest: Codable {
+public struct ASACreateThreadRunRequest: Codable, Sendable {
/// The ID of the assistant to use to execute this run.
public let assistantId: String
@@ -16,12 +16,12 @@ public struct ASACreateThreadRunRequest: Codable {
public let thread: Thread
/// Represents a thread containing messages and other parameters.
- public struct Thread: Codable {
+ public struct Thread: Codable, Sendable {
/// The messages to be processed in this thread.
public let messages: [Message]
/// Represents a single message in a thread.
- public struct Message: Codable {
+ public struct Message: Codable, Sendable {
/// The role of the message sender, e.g., 'user' or 'system'.
public let role: String
diff --git a/Sources/AISwiftAssist/Models/Request/ASAListRunStepsParameters.swift b/Sources/AISwiftAssist/Models/Request/ASAListRunStepsParameters.swift
index 3dfafc7..03ec038 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAListRunStepsParameters.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASAListRunStepsParameters.swift
@@ -8,7 +8,7 @@
import Foundation
/// Parameters for listing run steps in a thread.
-public struct ASAListRunStepsParameters: Codable {
+public struct ASAListRunStepsParameters: Codable, Sendable {
/// A limit on the number of objects to be returned.
public let limit: Int?
diff --git a/Sources/AISwiftAssist/Models/Request/ASAModifyAssistantRequest.swift b/Sources/AISwiftAssist/Models/Request/ASAModifyAssistantRequest.swift
deleted file mode 100644
index a5c3a57..0000000
--- a/Sources/AISwiftAssist/Models/Request/ASAModifyAssistantRequest.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/15/23.
-//
-
-import Foundation
-/// A request structure for modifying an existing assistant.
-public struct ASAModifyAssistantRequest: Codable {
-
- /// Optional: ID of the model to use.
- public let model: String?
-
- /// Optional: The name of the assistant. Maximum length is 256 characters.
- public let name: String?
-
- /// Optional: The description of the assistant. Maximum length is 512 characters.
- public let description: String?
-
- /// Optional: The system instructions that the assistant uses. Maximum length is 32768 characters.
- public let instructions: String?
-
- /// Optional: A list of tools enabled on the assistant. Maximum of 128 tools per assistant.
- public let tools: [ASAAssistant.Tool]?
-
- /// Optional: A list of file IDs attached to the assistant. Maximum of 20 files attached to the assistant.
- public let fileIds: [String]?
-
- /// Optional: A map of key-value pairs for storing additional information. Maximum of 16 pairs.
- public let metadata: [String: String]?
-
- enum CodingKeys: String, CodingKey {
- case model, name, description, instructions, tools
- case fileIds = "file_ids"
- case metadata
- }
-
- public init(model: String? = nil, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [ASAAssistant.Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
- self.model = model
- self.name = name
- self.description = description
- self.instructions = instructions
- self.tools = tools
- self.fileIds = fileIds
- self.metadata = metadata
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Request/ASAModifyRunRequest.swift b/Sources/AISwiftAssist/Models/Request/ASAModifyRunRequest.swift
index 1a35b10..299db3d 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAModifyRunRequest.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASAModifyRunRequest.swift
@@ -8,7 +8,7 @@
import Foundation
/// A request structure for modifying a run.
-public struct ASAModifyRunRequest: Codable {
+public struct ASAModifyRunRequest: Codable, Sendable {
/// Optional: Set of 16 key-value pairs that can be attached to the run.
public let metadata: [String: String]?
diff --git a/Sources/AISwiftAssist/Models/Request/ASAModifyThreadRequest.swift b/Sources/AISwiftAssist/Models/Request/ASAModifyThreadRequest.swift
index ba02df0..9cf24f3 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAModifyThreadRequest.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASAModifyThreadRequest.swift
@@ -8,11 +8,21 @@
import Foundation
/// A request structure for modifying a thread.
-public struct ASAModifyThreadRequest: Codable {
- /// Optional: Set of 16 key-value pairs that can be attached to the thread.
+public struct ASAModifyThreadRequest: Codable, Sendable {
+ /// Optional: Resources available to the assistant's tools in this thread.
+ public let toolResources: ASAThread.ToolResources?
+
+ /// Optional: Metadata associated with the thread (max 16 key-value pairs).
public let metadata: [String: String]?
enum CodingKeys: String, CodingKey {
+ case toolResources = "tool_resources"
case metadata
}
+
+ public init(toolResources: ASAThread.ToolResources? = nil,
+ metadata: [String: String]? = nil) {
+ self.toolResources = toolResources
+ self.metadata = metadata
+ }
}
diff --git a/Sources/AISwiftAssist/Models/Request/ASAToolOutput.swift b/Sources/AISwiftAssist/Models/Request/ASAToolOutput.swift
index e91f979..00d9419 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAToolOutput.swift
+++ b/Sources/AISwiftAssist/Models/Request/ASAToolOutput.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents a tool output for submission.
-public struct ASAToolOutput: Codable {
+public struct ASAToolOutput: Codable, Sendable {
/// The ID of the tool call.
public let toolCallId: String
diff --git a/Sources/AISwiftAssist/Models/Request/Assistant/ASACreateAssistantRequest.swift b/Sources/AISwiftAssist/Models/Request/Assistant/ASACreateAssistantRequest.swift
new file mode 100644
index 0000000..f5acc25
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/Request/Assistant/ASACreateAssistantRequest.swift
@@ -0,0 +1,180 @@
+//
+// File.swift
+//
+//
+// Created by Alexey on 11/15/23.
+//
+
+import Foundation
+
+/// Represents an assistant capable of calling the model and utilizing tools.
+public struct ASACreateAssistantRequest: Codable, Sendable {
+ /// Optional: Assistant's name (max 256 characters).
+ public let name: String?
+
+ /// Optional: Description of the assistant (max 512 characters).
+ public let description: String?
+
+ /// ID of the model used.
+ public let model: String
+
+ /// Optional: System instructions used by the assistant (max 256000 characters).
+ public let instructions: String?
+
+ /// Optional: Effort level for model reasoning (low, medium, high). o1 and o3-mini models only
+ public let reasoningEffort: String?
+
+ /// Tools available to the assistant (max 128).
+ public let tools: [ASAAssistant.Tool]
+
+ /// Optional: Resources utilized by the assistant's tools.
+ public let toolResources: ASAAssistant.ToolResources?
+
+ /// Optional: Sampling temperature for output generation (range 0–2, default is 1).
+ public let temperature: Double?
+
+ /// Optional: Nucleus sampling parameter (range 0–1, default is 1).
+ public let topP: Double?
+
+ /// Optional: Format of the response generated by the model (e.g., auto, text, json_object).
+ /// Using this can enforce specific output formats like JSON.
+ public let responseFormat: ASAAssistant.ResponseFormat?
+
+ /// Optional: Metadata (up to 16 key-value pairs).
+ public let metadata: [String: String]?
+
+ public enum CodingKeys: String, CodingKey {
+ case name, description, model, instructions, tools, metadata
+ case reasoningEffort = "reasoning_effort"
+ case toolResources = "tool_resources"
+ case temperature
+ case topP = "top_p"
+ case responseFormat = "response_format"
+ }
+
+ public init(
+ name: String? = nil,
+ description: String? = nil,
+ model: String,
+ instructions: String? = nil,
+ reasoningEffort: String? = nil,
+ tools: [ASAAssistant.Tool] = [],
+ toolResources: ASAAssistant.ToolResources? = nil,
+ temperature: Double? = nil,
+ topP: Double? = nil,
+ responseFormat: ASAAssistant.ResponseFormat? = nil,
+ metadata: [String : String]? = nil
+ ) {
+ self.name = name
+ self.description = description
+ self.model = model
+ self.instructions = instructions
+ self.reasoningEffort = reasoningEffort
+ self.tools = tools
+ self.toolResources = toolResources
+ self.temperature = temperature
+ self.topP = topP
+ self.responseFormat = responseFormat
+ self.metadata = metadata
+ }
+}
+
+extension ASACreateAssistantRequest {
+ // MARK: - Mock Data
+
+ /// Minimal mock assistant data (only required fields).
+ static let assistantMinimal: Self = .init(
+ name: nil,
+ description: nil,
+ model: "gpt-4",
+ instructions: nil,
+ reasoningEffort: nil,
+ tools: [],
+ toolResources: nil,
+ temperature: nil,
+ topP: nil,
+ responseFormat: nil,
+ metadata: nil
+ )
+
+ /// Medium complexity mock assistant data.
+ static let assistantMedium: Self = .init(
+ name: "Medium Assistant",
+ description: "An assistant with moderate complexity",
+ model: "gpt-4-turbo",
+ instructions: "Answer general queries.",
+ reasoningEffort: "medium",
+ tools: [
+ ASAAssistant.Tool(type: .codeInterpreter, function: nil, fileSearch: nil)
+ ],
+ toolResources: ASAAssistant.ToolResources(
+ codeInterpreter: ASAAssistant.CodeInterpreterResources(fileIds: ["file_123"]),
+ fileSearch: nil
+ ),
+ temperature: 0.7,
+ topP: 0.9,
+ responseFormat: .auto,
+ metadata: ["env": "staging"]
+ )
+
+ /// Fully populated mock assistant data (maximum configuration).
+ static let assistantFull: Self = .init(
+ name: "Full Assistant",
+ description: "Fully configured assistant for advanced tasks",
+ model: "gpt-4o",
+ instructions: "You are a comprehensive assistant handling complex tasks and queries.",
+ reasoningEffort: "high",
+ tools: [
+ ASAAssistant.Tool(
+ type: .function,
+ function: ASAAssistant.Tool.FunctionTool(
+ name: "calculate",
+ description: "Performs calculations",
+ parameters: ["operation": AnyCodable("add"), "values": AnyCodable([1, 2])],
+ strict: true
+ ),
+ fileSearch: nil
+ ),
+ ASAAssistant.Tool(
+ type: .fileSearch,
+ function: nil,
+ fileSearch: ASAAssistant.Tool.FileSearchTool(
+ maxNumResults: 10,
+ rankingOptions: ASAAssistant.Tool.FileSearchTool.RankingOptions(
+ ranker: "auto",
+ scoreThreshold: 0.8
+ )
+ )
+ )
+ ],
+ toolResources: ASAAssistant.ToolResources(
+ codeInterpreter: ASAAssistant.CodeInterpreterResources(fileIds: ["file_456", "file_789"]),
+ fileSearch: ASAAssistant.FileSearchResources(
+ vectorStoreIds: ["vector_store_1"],
+ vectorStores: [
+ ASAAssistant.FileSearchResources.VectorStore(
+ fileIds: ["file_001", "file_002"],
+ chunkingStrategy: ASAAssistant.FileSearchResources.VectorStore.ChunkingStrategy(
+ type: .static,
+ staticChunking: ASAAssistant.FileSearchResources.VectorStore.ChunkingStrategy.StaticChunking(
+ maxChunkSizeTokens: 1000,
+ chunkOverlapTokens: 500
+ )
+ )
+ )
+ ]
+ )
+ ),
+ temperature: 0.5,
+ topP: 0.85,
+ responseFormat: .jsonObject,
+ metadata: ["version": "1.0", "env": "production"]
+ )
+
+ /// Collection of all mock assistant configurations.
+ static let mocks: [Self] = [
+ assistantMinimal,
+ assistantMedium,
+ assistantFull
+ ]
+}
diff --git a/Sources/AISwiftAssist/Models/Request/ASAListAssistantsParameters.swift b/Sources/AISwiftAssist/Models/Request/Assistant/ASAListAssistantsParameters.swift
similarity index 81%
rename from Sources/AISwiftAssist/Models/Request/ASAListAssistantsParameters.swift
rename to Sources/AISwiftAssist/Models/Request/Assistant/ASAListAssistantsParameters.swift
index 147ad37..cb3154a 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAListAssistantsParameters.swift
+++ b/Sources/AISwiftAssist/Models/Request/Assistant/ASAListAssistantsParameters.swift
@@ -8,7 +8,7 @@
import Foundation
/// Parameters for listing assistants.
-public struct ASAListAssistantsParameters: Encodable {
+public struct ASAListAssistantsParameters: Encodable, Sendable {
/// Optional: A limit on the number of objects to be returned. Can range between 1 and 100. Defaults to 20.
public let limit: Int?
@@ -22,7 +22,12 @@ public struct ASAListAssistantsParameters: Encodable {
/// Optional: A cursor for use in pagination. 'before' is an object ID that defines your place in the list, to fetch the previous page of the list.
public let before: String?
- public init(limit: Int? = nil, order: String? = nil, after: String? = nil, before: String? = nil) {
+ public init(
+ limit: Int? = nil,
+ order: String? = nil,
+ after: String? = nil,
+ before: String? = nil
+ ) {
self.limit = limit
self.order = order
self.after = after
diff --git a/Sources/AISwiftAssist/Models/Request/Assistant/ASAModifyAssistantRequest.swift b/Sources/AISwiftAssist/Models/Request/Assistant/ASAModifyAssistantRequest.swift
new file mode 100644
index 0000000..d86ff1b
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/Request/Assistant/ASAModifyAssistantRequest.swift
@@ -0,0 +1,82 @@
+//
+// File.swift
+//
+//
+// Created by Alexey on 11/15/23.
+//
+
+import Foundation
+
+/// A request structure for modifying an existing assistant.
+public struct ASAModifyAssistantRequest: Codable, Sendable {
+
+ /// Optional: ID of the model to use.
+ public let model: String?
+
+ /// Optional: Effort level for model reasoning (low, medium, high). o1 and o3-mini models only
+ public let reasoningEffort: String?
+
+ /// Optional: The name of the assistant. Maximum length is 256 characters.
+ public let name: String?
+
+ /// Optional: The description of the assistant. Maximum length is 512 characters.
+ public let description: String?
+
+ /// Optional: The system instructions that the assistant uses. Maximum length is 32768 characters.
+ public let instructions: String?
+
+ /// Optional: A list of tools enabled on the assistant. Maximum of 128 tools per assistant.
+ public let tools: [ASAAssistant.Tool]?
+
+ /// Optional: Resources utilized by the assistant's tools.
+ public let toolResources: ASAAssistant.ToolResources?
+
+ /// Optional: A map of key-value pairs for storing additional information. Maximum of 16 pairs.
+ public let metadata: [String: String]?
+
+ /// Optional: Sampling temperature for output generation (range 0–2, default is 1).
+ public let temperature: Double?
+
+ /// Optional: Nucleus sampling parameter (range 0–1, default is 1).
+ public let topP: Double?
+
+ /// Optional: Format of the response generated by the model (e.g., auto, text, json_object).
+ /// Using this can enforce specific output formats like JSON.
+ public let responseFormat: ASAAssistant.ResponseFormat?
+
+ enum CodingKeys: String, CodingKey {
+ case model, name, description, instructions, tools
+ case reasoningEffort = "reasoning_effort"
+ case toolResources = "tool_resources"
+ case temperature
+ case topP = "top_p"
+ case responseFormat = "response_format"
+ case metadata
+ }
+
+ public init(
+ model: String? = nil,
+ reasoningEffort: String? = nil,
+ name: String? = nil,
+ description: String? = nil,
+ instructions: String? = nil,
+ tools: [ASAAssistant.Tool]? = nil,
+ toolResources: ASAAssistant.ToolResources? = nil,
+ metadata: [String : String]? = nil,
+ temperature: Double? = nil,
+ topP: Double? = nil,
+ responseFormat: ASAAssistant.ResponseFormat? = nil
+ ) {
+ self.model = model
+ self.reasoningEffort = reasoningEffort
+ self.name = name
+ self.description = description
+ self.instructions = instructions
+ self.tools = tools
+ self.toolResources = toolResources
+ self.metadata = metadata
+ self.temperature = temperature
+ self.topP = topP
+ self.responseFormat = responseFormat
+ }
+}
diff --git a/Sources/AISwiftAssist/Models/Request/Messages/ASACreateMessageRequest.swift b/Sources/AISwiftAssist/Models/Request/Messages/ASACreateMessageRequest.swift
new file mode 100644
index 0000000..4c344c3
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/Request/Messages/ASACreateMessageRequest.swift
@@ -0,0 +1,37 @@
+//
+// File.swift
+//
+//
+// Created by Alexey on 11/15/23.
+//
+
+import Foundation
+
+/// Represents a request to create a message within a thread.
+public struct ASACreateMessageRequest: Codable, Sendable {
+ /// Role of the entity creating the message (user or assistant).
+ public let role: ASAMessage.Role
+
+ /// Content of the message (text, image URLs, or image files).
+ public let content: [ASAMessage.Content]
+
+ /// Optional attachments associated with the message.
+ public let attachments: [ASAMessage.Attachment]?
+
+ /// Optional metadata (max 16 key-value pairs).
+ public let metadata: [String: String]?
+
+ enum CodingKeys: String, CodingKey {
+ case role, content, attachments, metadata
+ }
+
+ public init(role: ASAMessage.Role,
+ content: [ASAMessage.Content],
+ attachments: [ASAMessage.Attachment]? = nil,
+ metadata: [String: String]? = nil) {
+ self.role = role
+ self.content = content
+ self.attachments = attachments
+ self.metadata = metadata
+ }
+}
diff --git a/Sources/AISwiftAssist/Models/Request/ASAListMessagesParameters.swift b/Sources/AISwiftAssist/Models/Request/Messages/ASAListMessagesParameters.swift
similarity index 93%
rename from Sources/AISwiftAssist/Models/Request/ASAListMessagesParameters.swift
rename to Sources/AISwiftAssist/Models/Request/Messages/ASAListMessagesParameters.swift
index 00ae785..c84446a 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAListMessagesParameters.swift
+++ b/Sources/AISwiftAssist/Models/Request/Messages/ASAListMessagesParameters.swift
@@ -8,7 +8,7 @@
import Foundation
/// Parameters for listing messages in a thread.
-public struct ASAListMessagesParameters: Codable {
+public struct ASAListMessagesParameters: Codable, Sendable {
/// Optional: A limit on the number of objects to be returned. Limit can range between 1 and 100.
public let limit: Int?
diff --git a/Sources/AISwiftAssist/Models/Request/ASAModifyMessageRequest.swift b/Sources/AISwiftAssist/Models/Request/Messages/ASAModifyMessageRequest.swift
similarity index 88%
rename from Sources/AISwiftAssist/Models/Request/ASAModifyMessageRequest.swift
rename to Sources/AISwiftAssist/Models/Request/Messages/ASAModifyMessageRequest.swift
index 5c290e0..124f0a2 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAModifyMessageRequest.swift
+++ b/Sources/AISwiftAssist/Models/Request/Messages/ASAModifyMessageRequest.swift
@@ -8,7 +8,7 @@
import Foundation
/// A request structure for modifying a message in a thread.
-public struct ASAModifyMessageRequest: Codable {
+public struct ASAModifyMessageRequest: Codable, Sendable {
/// Optional: Set of 16 key-value pairs that can be attached to the message.
public let metadata: [String: String]?
diff --git a/Sources/AISwiftAssist/Models/Request/Run/ASACreateRunRequest.swift b/Sources/AISwiftAssist/Models/Request/Run/ASACreateRunRequest.swift
new file mode 100644
index 0000000..3b6f32e
--- /dev/null
+++ b/Sources/AISwiftAssist/Models/Request/Run/ASACreateRunRequest.swift
@@ -0,0 +1,265 @@
+//
+// File.swift
+//
+//
+// Created by Alexey on 11/16/23.
+//
+
+import Foundation
+import Foundation
+
+/// Represents a request for creating a run on a thread.
+public struct ASACreateRunRequest: Codable, Sendable {
+
+ /// The ID of the assistant to use to execute this run.
+ public let assistantId: String
+
+ /// Optional. The model ID to override the assistant's default model.
+ public let model: String?
+
+ /// Optional. Overrides the assistant's instructions for this run.
+ public let instructions: String?
+
+ /// Optional. Appends additional instructions at the end of the run's instructions.
+ public let additionalInstructions: String?
+
+ /// Optional. Additional messages added to the thread before creating the run.
+ public let additionalMessages: [AdditionalMessage]?
+
+ /// Optional. Overrides the tools available to the assistant for this run.
+ public let tools: [ASARun.Tool]?
+
+ /// Optional. Controls which (if any) tool is called by the model.
+ public let toolChoice: ASARun.ToolChoice?
+
+ /// Optional. Enables parallel function calling during tool use. Defaults to `true`.
+ public let parallelToolCalls: Bool?
+
+ /// Optional. The sampling temperature (0–2). Defaults to `1`.
+ public let temperature: Double?
+
+ /// Optional. Alternative to temperature sampling, controlling nucleus sampling (0–1). Defaults to `1`.
+ public let topP: Double?
+
+ /// Optional. Maximum number of completion tokens allowed for this run.
+ public let maxCompletionTokens: Int?
+
+ /// Optional. Maximum number of prompt tokens allowed for this run.
+ public let maxPromptTokens: Int?
+
+ /// Optional. Controls how the thread is truncated before running.
+ public let truncationStrategy: ASARun.TruncationStrategy?
+
+ /// Optional. Constrains reasoning effort. Supported values: `low`, `medium`, `high`. Defaults to `medium`. (o-series models only)
+ public let reasoningEffort: String?
+
+ /// Optional. Specifies the format of the model's output.
+ public let responseFormat: ASARun.ResponseFormat?
+
+ /// Optional. Streams events during the run as server-sent events if set to `true`.
+ public let stream: Bool?
+
+ /// Optional. Additional structured data (up to 16 key-value pairs) attached to the run.
+ public let metadata: [String: String]?
+
+ // MARK: - Additional Types
+
+ /// Represents an additional message to add to the thread.
+ public struct AdditionalMessage: Codable, Sendable {
+
+ /// The role of the message sender (`user` or `assistant`).
+ public let role: String
+
+ /// The message content (text or content parts).
+ public let content: Content
+
+ /// Optional. Files attached to the message.
+ public let attachments: [Attachment]?
+
+ // MARK: - Nested types
+
+ /// Represents content of the message.
+ public enum Content: Codable, Sendable {
+ case text(String)
+ case parts([ContentPart])
+
+ public init(from decoder: Decoder) throws {
+ if let text = try? decoder.singleValueContainer().decode(String.self) {
+ self = .text(text)
+ } else {
+ let parts = try decoder.singleValueContainer().decode([ContentPart].self)
+ self = .parts(parts)
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .text(let text):
+ try container.encode(text)
+ case .parts(let parts):
+ try container.encode(parts)
+ }
+ }
+ }
+
+ /// Represents a content part (text or image).
+ public enum ContentPart: Codable, Sendable {
+ case text(TextContent)
+ case imageFile(ImageFileContent)
+ case imageURL(ImageURLContent)
+
+ // Nested structures
+ public struct TextContent: Codable, Sendable {
+ public let type: String // Always "text"
+ public let text: String
+ }
+
+ public struct ImageFileContent: Codable, Sendable {
+ public let type: String // Always "image_file"
+ public let imageFile: ImageFile
+
+ public struct ImageFile: Codable, Sendable {
+ public let fileId: String
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ }
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case imageFile = "image_file"
+ }
+ }
+
+ public struct ImageURLContent: Codable, Sendable {
+ public let type: String // Always "image_url"
+ public let imageURL: ImageURL
+
+ public struct ImageURL: Codable, Sendable {
+ public let url: String
+ public let detail: String?
+
+ enum CodingKeys: String, CodingKey {
+ case url, detail
+ }
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ case imageURL = "image_url"
+ }
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decode(String.self, forKey: .type)
+ switch type {
+ case "text":
+ self = .text(try TextContent(from: decoder))
+ case "image_file":
+ self = .imageFile(try ImageFileContent(from: decoder))
+ case "image_url":
+ self = .imageURL(try ImageURLContent(from: decoder))
+ default:
+ throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unknown type")
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ switch self {
+ case .text(let content):
+ try content.encode(to: encoder)
+ case .imageFile(let content):
+ try content.encode(to: encoder)
+ case .imageURL(let content):
+ try content.encode(to: encoder)
+ }
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case type
+ }
+ }
+
+ /// Represents an attachment of a file to the message.
+ public struct Attachment: Codable, Sendable {
+ /// ID of the attached file.
+ public let fileId: String
+
+ /// Tools associated with this attachment.
+ public let tools: [AttachmentTool]?
+
+ public struct AttachmentTool: Codable, Sendable {
+ /// Type of tool: `code_interpreter`, `file_search`.
+ public let type: String
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case fileId = "file_id"
+ case tools
+ }
+ }
+ }
+
+ // MARK: - Initializer
+
+ public init(
+ assistantId: String,
+ model: String? = nil,
+ instructions: String? = nil,
+ additionalInstructions: String? = nil,
+ additionalMessages: [AdditionalMessage]? = nil,
+ tools: [ASARun.Tool]? = nil,
+ toolChoice: ASARun.ToolChoice? = nil,
+ parallelToolCalls: Bool? = true,
+ temperature: Double? = 1.0,
+ topP: Double? = 1.0,
+ maxCompletionTokens: Int? = nil,
+ maxPromptTokens: Int? = nil,
+ truncationStrategy: ASARun.TruncationStrategy? = nil,
+ reasoningEffort: String? = "medium",
+ responseFormat: ASARun.ResponseFormat? = .auto,
+ stream: Bool? = nil,
+ metadata: [String: String]? = nil
+ ) {
+ self.assistantId = assistantId
+ self.model = model
+ self.instructions = instructions
+ self.additionalInstructions = additionalInstructions
+ self.additionalMessages = additionalMessages
+ self.tools = tools
+ self.toolChoice = toolChoice
+ self.parallelToolCalls = parallelToolCalls
+ self.temperature = temperature
+ self.topP = topP
+ self.maxCompletionTokens = maxCompletionTokens
+ self.maxPromptTokens = maxPromptTokens
+ self.truncationStrategy = truncationStrategy
+ self.reasoningEffort = reasoningEffort
+ self.responseFormat = responseFormat
+ self.stream = stream
+ self.metadata = metadata
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case assistantId = "assistant_id"
+ case model
+ case instructions
+ case additionalInstructions = "additional_instructions"
+ case additionalMessages = "additional_messages"
+ case tools
+ case toolChoice = "tool_choice"
+ case parallelToolCalls = "parallel_tool_calls"
+ case temperature
+ case topP = "top_p"
+ case maxCompletionTokens = "max_completion_tokens"
+ case maxPromptTokens = "max_prompt_tokens"
+ case truncationStrategy = "truncation_strategy"
+ case reasoningEffort = "reasoning_effort"
+ case responseFormat = "response_format"
+ case stream
+ case metadata
+ }
+}
diff --git a/Sources/AISwiftAssist/Models/Request/ASAListRunsParameters.swift b/Sources/AISwiftAssist/Models/Request/Run/ASAListRunsParameters.swift
similarity index 92%
rename from Sources/AISwiftAssist/Models/Request/ASAListRunsParameters.swift
rename to Sources/AISwiftAssist/Models/Request/Run/ASAListRunsParameters.swift
index f47a263..7755c8b 100644
--- a/Sources/AISwiftAssist/Models/Request/ASAListRunsParameters.swift
+++ b/Sources/AISwiftAssist/Models/Request/Run/ASAListRunsParameters.swift
@@ -8,7 +8,7 @@
import Foundation
/// Parameters for listing runs in a thread.
-public struct ASAListRunsParameters: Codable {
+public struct ASAListRunsParameters: Codable, Sendable {
/// Optional: A limit on the number of objects to be returned.
public let limit: Int?
diff --git a/Sources/AISwiftAssist/Models/Response/ASAAssistantFilesListResponse.swift b/Sources/AISwiftAssist/Models/Response/ASAAssistantFilesListResponse.swift
deleted file mode 100644
index 0cbc0a1..0000000
--- a/Sources/AISwiftAssist/Models/Response/ASAAssistantFilesListResponse.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/5/23.
-//
-
-import Foundation
-
-/// Represents a response containing a list of assistant files.
-public struct ASAAssistantFilesListResponse: Codable {
- /// The object type, which is always 'list'.
- public let object: String
-
- /// The list of assistant files.
- public let data: [ASAAssistantFile]
-
- /// The ID of the first file in the list.
- public let firstId: String
-
- /// The ID of the last file in the list.
- public let lastId: String
-
- /// Boolean indicating if there are more files available.
- public let hasMore: Bool
-
- enum CodingKeys: String, CodingKey {
- case data, firstId = "first_id", lastId = "last_id", hasMore = "has_more", object
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Response/ASADeleteModelResponse.swift b/Sources/AISwiftAssist/Models/Response/ASADeleteModelResponse.swift
index cde3920..a4ec4b1 100644
--- a/Sources/AISwiftAssist/Models/Response/ASADeleteModelResponse.swift
+++ b/Sources/AISwiftAssist/Models/Response/ASADeleteModelResponse.swift
@@ -7,7 +7,7 @@
import Foundation
-public struct ASADeleteModelResponse: Codable {
+public struct ASADeleteModelResponse: Codable, Sendable {
/// The model identifier, which can be referenced in the API endpoints.
public let id: String
diff --git a/Sources/AISwiftAssist/Models/Response/ASAMessageFilesListResponse.swift b/Sources/AISwiftAssist/Models/Response/ASAMessageFilesListResponse.swift
deleted file mode 100644
index 7128909..0000000
--- a/Sources/AISwiftAssist/Models/Response/ASAMessageFilesListResponse.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 12/5/23.
-//
-
-import Foundation
-
-/// Represents a response containing a list of assistant files.
-public struct ASAMessageFilesListResponse: Codable {
- /// The object type, which is always 'list'.
- public let object: String
-
- /// The list of assistant files.
- public let data: [ASAMessageFile]
-
- /// The ID of the first file in the list.
- public let firstId: String
-
- /// The ID of the last file in the list.
- public let lastId: String
-
- /// Boolean indicating if there are more files available.
- public let hasMore: Bool
-
- enum CodingKeys: String, CodingKey {
- case data, firstId = "first_id", lastId = "last_id", hasMore = "has_more", object
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Response/ASAModelsListResponse.swift b/Sources/AISwiftAssist/Models/Response/ASAModelsListResponse.swift
deleted file mode 100644
index 54cac26..0000000
--- a/Sources/AISwiftAssist/Models/Response/ASAModelsListResponse.swift
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/17/23.
-//
-
-import Foundation
-
-public struct ASAModelsListResponse: Codable {
-
- /// The object type, which is always "list".
- public let object: String
-
- /// The deletion status of the model.
- public let data: [ASAModel]
-
- enum CodingKeys: String, CodingKey {
- case object, data
- }
-}
diff --git a/Sources/AISwiftAssist/Models/Response/ASARunStepsListResponse.swift b/Sources/AISwiftAssist/Models/Response/ASARunStepsListResponse.swift
index b6f725b..1648d79 100644
--- a/Sources/AISwiftAssist/Models/Response/ASARunStepsListResponse.swift
+++ b/Sources/AISwiftAssist/Models/Response/ASARunStepsListResponse.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents a response containing a list of run steps.
-public struct ASARunStepsListResponse: Codable {
+public struct ASARunStepsListResponse: Codable, Sendable {
/// The object type, which is always 'list'.
public let object: String
diff --git a/Sources/AISwiftAssist/Models/Response/ASAAssistantsListResponse.swift b/Sources/AISwiftAssist/Models/Response/Assistant/ASAAssistantsListResponse.swift
similarity index 92%
rename from Sources/AISwiftAssist/Models/Response/ASAAssistantsListResponse.swift
rename to Sources/AISwiftAssist/Models/Response/Assistant/ASAAssistantsListResponse.swift
index b81619e..64eed4c 100644
--- a/Sources/AISwiftAssist/Models/Response/ASAAssistantsListResponse.swift
+++ b/Sources/AISwiftAssist/Models/Response/Assistant/ASAAssistantsListResponse.swift
@@ -8,7 +8,7 @@
import Foundation
/// A response structure for listing assistants.
-public struct ASAAssistantsListResponse: Codable {
+public struct ASAAssistantsListResponse: Codable, Sendable {
/// The object type, which is always 'list'.
public let object: String
diff --git a/Sources/AISwiftAssist/Models/Response/ASAMessagesListResponse.swift b/Sources/AISwiftAssist/Models/Response/Messages/ASAMessagesListResponse.swift
similarity index 92%
rename from Sources/AISwiftAssist/Models/Response/ASAMessagesListResponse.swift
rename to Sources/AISwiftAssist/Models/Response/Messages/ASAMessagesListResponse.swift
index 6bdebe7..87ae6a0 100644
--- a/Sources/AISwiftAssist/Models/Response/ASAMessagesListResponse.swift
+++ b/Sources/AISwiftAssist/Models/Response/Messages/ASAMessagesListResponse.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents a response containing a list of messages.
-public struct ASAMessagesListResponse: Codable {
+public struct ASAMessagesListResponse: Codable, Sendable {
/// The object type, which is always 'list'.
public let object: String
diff --git a/Sources/AISwiftAssist/Models/Response/ASARunsListResponse.swift b/Sources/AISwiftAssist/Models/Response/Runs/ASARunsListResponse.swift
similarity index 92%
rename from Sources/AISwiftAssist/Models/Response/ASARunsListResponse.swift
rename to Sources/AISwiftAssist/Models/Response/Runs/ASARunsListResponse.swift
index 9e6a066..e5daed1 100644
--- a/Sources/AISwiftAssist/Models/Response/ASARunsListResponse.swift
+++ b/Sources/AISwiftAssist/Models/Response/Runs/ASARunsListResponse.swift
@@ -8,7 +8,7 @@
import Foundation
/// Represents a response containing a list of runs.
-public struct ASARunsListResponse: Codable {
+public struct ASARunsListResponse: Codable, Sendable {
/// The object type, which is always 'list'.
public let object: String
diff --git a/Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift b/Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift
index d0962dc..93b7b2b 100644
--- a/Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift
+++ b/Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift
@@ -5,278 +5,98 @@
// Created by Alexey on 11/19/23.
//
-import XCTest
+import Foundation
+import Testing
@testable import AISwiftAssist
-final class AssistantsAPITests: XCTestCase {
+struct AssistantsAPITests {
- var assistantsAPI: IAssistantsAPI!
-
- override func setUp() {
- super.setUp()
+ private let api: any IAssistantsAPI = {
let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [MockURLProtocol.self]
- let mockURLSession = URLSession(configuration: configuration)
- assistantsAPI = AssistantsAPI(urlSession: mockURLSession)
- }
-
- override func tearDown() {
- assistantsAPI = nil
- super.tearDown()
- }
-
- func testGetAssistants() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.list.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let response: ASAAssistantsListResponse = try await assistantsAPI.get(with: nil)
-
- XCTAssertNotNil(response)
- XCTAssertEqual(response.object, "list")
- XCTAssertEqual(response.data.count, 3)
- XCTAssertEqual(response.firstId, "asst_abc123")
- XCTAssertEqual(response.lastId, "asst_abc789")
- XCTAssertFalse(response.hasMore)
-
- // Checks for specific assistants
- XCTAssertEqual(response.data[0].id, "asst_abc123")
- XCTAssertEqual(response.data[0].objectType, "assistant")
- XCTAssertEqual(response.data[0].createdAt, 1698982736)
- XCTAssertEqual(response.data[0].name, "Coding Tutor")
- XCTAssertEqual(response.data[0].model, "gpt-4")
- XCTAssertEqual(response.data[0].instructions, "You are a helpful assistant designed to make me better at coding!")
-
- XCTAssertEqual(response.data[1].id, "asst_abc456")
- XCTAssertEqual(response.data[1].objectType, "assistant")
- XCTAssertEqual(response.data[1].createdAt, 1698982718)
- XCTAssertEqual(response.data[1].name, "My Assistant")
- XCTAssertEqual(response.data[1].model, "gpt-4")
- XCTAssertEqual(response.data[1].instructions, "You are a helpful assistant designed to make me better at coding!")
-
- XCTAssertEqual(response.data[2].id, "asst_abc789")
- XCTAssertEqual(response.data[2].objectType, "assistant")
- XCTAssertEqual(response.data[2].createdAt, 1698982643)
- XCTAssertNil(response.data[2].name)
- XCTAssertEqual(response.data[2].model, "gpt-4")
- XCTAssertNil(response.data[2].instructions)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testCreateAssistant() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.create.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let createRequest = ASACreateAssistantRequest(model: "gpt-4",
- name: "Math Tutor",
- instructions: "You are a personal math tutor. When asked a question, write and run Python code to answer the question.")
-
- let assistant: ASAAssistant = try await assistantsAPI.create(by: createRequest)
-
- // Checks
- XCTAssertEqual(assistant.id, "asst_abc123")
- XCTAssertEqual(assistant.objectType, "assistant")
- XCTAssertEqual(assistant.createdAt, 1698984975)
- XCTAssertEqual(assistant.name, "Math Tutor")
- XCTAssertEqual(assistant.model, "gpt-4")
- XCTAssertEqual(assistant.instructions, "You are a personal math tutor. When asked a question, write and run Python code to answer the question.")
- XCTAssertEqual(assistant.tools.count, 1)
- XCTAssertEqual(assistant.tools.first?.type, "code_interpreter")
- XCTAssertTrue(assistant.fileIds.isEmpty)
- XCTAssertTrue(assistant.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testRetrieveAssistant() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.retrieve.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let assistantId = "asst_abc123"
- let assistant: ASAAssistant = try await assistantsAPI.retrieve(by: assistantId)
-
- // Checks
- XCTAssertEqual(assistant.id, assistantId)
- XCTAssertEqual(assistant.objectType, "assistant")
- XCTAssertEqual(assistant.createdAt, 1699009709)
- XCTAssertEqual(assistant.name, "HR Helper")
- XCTAssertEqual(assistant.model, "gpt-4")
- XCTAssertEqual(assistant.instructions, "You are an HR bot, and you have access to files to answer employee questions about company policies.")
- XCTAssertEqual(assistant.tools.count, 1)
- XCTAssertEqual(assistant.tools.first?.type, "retrieval")
- XCTAssertEqual(assistant.fileIds, ["file-abc123"])
- XCTAssertTrue(assistant.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testModifyAssistant() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.modify.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let assistantId = "asst_abc123"
- let modifyRequest = ASAModifyAssistantRequest(
- instructions: "You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.",
- fileIds: ["file-abc123", "file-abc456"]
- )
-
- let modifiedAssistant: ASAAssistant = try await assistantsAPI.modify(by: assistantId,
- modifyAssistant: modifyRequest)
-
- // Checks
- XCTAssertEqual(modifiedAssistant.id, assistantId)
- XCTAssertEqual(modifiedAssistant.objectType, "assistant")
- XCTAssertEqual(modifiedAssistant.createdAt, 1699009709)
- XCTAssertEqual(modifiedAssistant.name, "HR Helper")
- XCTAssertEqual(modifiedAssistant.model, "gpt-4")
- XCTAssertEqual(modifiedAssistant.instructions, modifyRequest.instructions)
- XCTAssertEqual(modifiedAssistant.tools.count, 1)
- XCTAssertEqual(modifiedAssistant.tools.first?.type, "retrieval")
- XCTAssertEqual(modifiedAssistant.fileIds, modifyRequest.fileIds)
- XCTAssertTrue(modifiedAssistant.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testDeleteAssistant() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.delete.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let assistantId = "asst_abc123"
- let deleteResponse: ASADeleteModelResponse = try await assistantsAPI.delete(by: assistantId)
-
- // Checks
- XCTAssertEqual(deleteResponse.id, assistantId)
- XCTAssertEqual(deleteResponse.object, "assistant.deleted")
- XCTAssertTrue(deleteResponse.deleted)
- } catch {
- XCTFail("Error: \(error)")
- }
+ let session = URLSession(configuration: configuration)
+ let api = AssistantsAPI(urlSession: session)
+ return api
+ }()
+
+ @Test
+ func testGetAssistants_Success() async throws {
+
+
+ await MockURLProtocol.setHandler ({ request in
+ let response = HTTPURLResponse(
+ url: request.url!,
+ statusCode: 200,
+ httpVersion: nil,
+ headerFields: ["Content-Type": "application/json"]
+ )!
+ return (response, Self.list.data(using: .utf8)!)
+ }, for: "getAssistants")
+
+ let result = try await api.get(with: nil)
+
+ #expect(result.data.count == 7)
+ #expect(result.data.first?.id == "asst_advanced_full")
+ #expect(result.data.first?.name == "Advanced Assistant")
+ #expect(result.data.first?.model == "gpt-4-turbo")
+ #expect(result.hasMore == false)
}
- func testCreateFile() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.createFile.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let createRequest = ASACreateAssistantFileRequest(fileId: "file-abc123")
- let file: ASAAssistantFile = try await assistantsAPI.createFile(for: "asst_abc123", with: createRequest)
-
- // Checks
- XCTAssertEqual(file.id, "file-abc123")
- XCTAssertEqual(file.objectType, "assistant.file")
- XCTAssertEqual(file.createdAt, 1699055364)
- XCTAssertEqual(file.assistantId, "asst_abc123")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testRetrieveFile() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.retrieveFile.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let file: ASAAssistantFile = try await assistantsAPI.retrieveFile(for: "asst_abc123", fileId: "file-abc123")
-
- // Checks
- XCTAssertEqual(file.id, "file-abc123")
- XCTAssertEqual(file.objectType, "assistant.file")
- XCTAssertEqual(file.createdAt, 1699055364)
- XCTAssertEqual(file.assistantId, "asst_abc123")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testDeleteFile() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.deleteFile.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let deleteResponse: ASADeleteModelResponse = try await assistantsAPI.deleteFile(for: "asst_abc123", fileId: "file-abc123")
-
- // Checks
- XCTAssertEqual(deleteResponse.id, "file-abc123")
- XCTAssertEqual(deleteResponse.object, "assistant.file.deleted")
- XCTAssertTrue(deleteResponse.deleted)
- } catch {
- XCTFail("Error: \(error)")
- }
+ @Test
+ func testRetrieveAssistant_Success() async throws {
+ let configuration = URLSessionConfiguration.ephemeral
+ configuration.protocolClasses = [MockURLProtocol.self]
+ let session = URLSession(configuration: configuration)
+ let assistantAPI = AssistantsAPI(urlSession: session)
+
+ await MockURLProtocol.setHandler ({ request in
+ let response = HTTPURLResponse(
+ url: request.url!,
+ statusCode: 200,
+ httpVersion: nil,
+ headerFields: ["Content-Type": "application/json"]
+ )!
+ return (response, Self.retrieve.data(using: .utf8)!)
+ }, for: "retrieveAssistant")
+
+ let assistant = try await assistantAPI.retrieve(by: "asst_advanced_full")
+
+ #expect(assistant.id == "asst_advanced_full")
+ #expect(assistant.name == "Advanced Assistant")
+ #expect(assistant.model == "gpt-4-turbo")
+ #expect(assistant.tools.count == 3)
}
- func testListFiles() async {
- do {
- // Simulate server response
- let mockData = AssistantsAPITests.listFiles.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listParameters = ASAListAssistantsParameters()
- let fileList: ASAAssistantFilesListResponse = try await assistantsAPI.listFiles(for: "asst_abc123", with: listParameters)
-
- // Checks
- XCTAssertEqual(fileList.object, "list")
- XCTAssertEqual(fileList.data.count, 2)
- XCTAssertEqual(fileList.firstId, "file-abc123")
- XCTAssertEqual(fileList.lastId, "file-abc456")
- XCTAssertFalse(fileList.hasMore)
- XCTAssertEqual(fileList.data[0].id, "file-abc123")
- XCTAssertEqual(fileList.data[1].id, "file-abc456")
- } catch {
- XCTFail("Error: \(error)")
- }
+ @Test
+ func testModifyAssistant_Success() async throws {
+ let configuration = URLSessionConfiguration.ephemeral
+ configuration.protocolClasses = [MockURLProtocol.self]
+ let session = URLSession(configuration: configuration)
+ let assistantAPI = AssistantsAPI(urlSession: session)
+
+ await MockURLProtocol.setHandler ({ request in
+ let response = HTTPURLResponse(
+ url: request.url!,
+ statusCode: 200,
+ httpVersion: nil,
+ headerFields: ["Content-Type": "application/json"]
+ )!
+ return (response, Self.modify.data(using: .utf8)!)
+ }, for: "modifyAssistant")
+
+ let modifyRequest = ASAModifyAssistantRequest(
+ model: "gpt-4",
+ name: "Advanced",
+ instructions: "You are a professional assistant with advanced functionality, tool support, and strict output controls."
+ )
+
+ let assistant = try await assistantAPI.modify(
+ by: "asst_advanced_full",
+ modifyAssistant: modifyRequest
+ )
+
+ #expect(assistant.id == "asst_advanced_full")
+ #expect(assistant.name == "Advanced")
+ #expect(assistant.model == "gpt-4")
}
}
diff --git a/Tests/AISwiftAssistTests/APIs/MessagesAPITests.swift b/Tests/AISwiftAssistTests/APIs/MessagesAPITests.swift
deleted file mode 100644
index fe44858..0000000
--- a/Tests/AISwiftAssistTests/APIs/MessagesAPITests.swift
+++ /dev/null
@@ -1,305 +0,0 @@
-//
-// MessagesAPITests.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import XCTest
-@testable import AISwiftAssist
-
-final class MessagesAPITests: XCTestCase {
-
- var messagesAPI: IMessagesAPI!
-
- override func setUp() {
- super.setUp()
- let configuration = URLSessionConfiguration.default
- configuration.protocolClasses = [MockURLProtocol.self]
- let mockURLSession = URLSession(configuration: configuration)
- messagesAPI = MessagesAPI(urlSession: mockURLSession)
- }
-
- override func tearDown() {
- messagesAPI = nil
- super.tearDown()
- }
-
- func testCreateMessage() async {
- do {
- let mockData = Self.create.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let createMessageRequest = ASACreateMessageRequest(role: "user", content: "How does AI work? Explain it in simple terms.")
- let message: ASAMessage = try await messagesAPI.create(by: "thread123", createMessage: createMessageRequest)
-
- XCTAssertEqual(message.id, "12345")
- XCTAssertEqual(message.object, "thread.message")
- XCTAssertEqual(message.createdAt, 1639530000)
- XCTAssertEqual(message.threadId, "thread123")
- XCTAssertEqual(message.role, "assistant")
- XCTAssertEqual(message.content.count, 2)
-
- if let firstContent = message.content.first {
- switch firstContent {
- case .text(let textContent):
- XCTAssertEqual(textContent.value, "This is a text message with annotations.")
- XCTAssertEqual(textContent.annotations?.count, 2)
- default:
- XCTFail("First content is not of type text.")
- }
- } else {
- XCTFail("First content is empty")
- }
-
- if message.content.count > 1 {
- switch message.content[1] {
- case .image(let imageContent):
- XCTAssertEqual(imageContent.file_id, "image789")
- default:
- XCTFail("Second content is not of type image.")
- }
- } else {
- XCTFail("Second content is missing.")
- }
-
- XCTAssertEqual(message.assistantId, "assistant123")
- XCTAssertEqual(message.runId, "run123")
- XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
- XCTAssertEqual(message.metadata?["key1"], "value1")
- XCTAssertEqual(message.metadata?["key2"], "value2")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-
- func testRetrieveMessage() async {
- do {
- let mockData = Self.retrieve.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let message: ASAMessage = try await messagesAPI.retrieve(by: "thread123", messageId: "12345")
-
- XCTAssertEqual(message.id, "12345")
- XCTAssertEqual(message.object, "thread.message")
- XCTAssertEqual(message.createdAt, 1639530000)
- XCTAssertEqual(message.threadId, "thread123")
- XCTAssertEqual(message.role, "assistant")
- XCTAssertEqual(message.content.count, 2)
-
- if let firstContent = message.content.first {
- switch firstContent {
- case .text(let textContent):
- XCTAssertEqual(textContent.value, "This is a text message with annotations.")
- XCTAssertEqual(textContent.annotations?.count, 2)
-
- if let firstAnnotation = textContent.annotations?.first {
- XCTAssertEqual(firstAnnotation.type, "file_citation")
- XCTAssertEqual(firstAnnotation.text, "document link")
- XCTAssertEqual(firstAnnotation.startIndex, 0)
- XCTAssertEqual(firstAnnotation.endIndex, 23)
- XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "file123")
- XCTAssertEqual(firstAnnotation.fileCitation?.quote, "A quote from the file")
- } else {
- XCTFail("First annotation not found.")
- }
- default:
- XCTFail("First content is not of type text.")
- }
- } else {
- XCTFail("Content is empty")
- }
-
- if let secondContent = message.content.last {
- switch secondContent {
- case .image(let imageContent):
- XCTAssertEqual(imageContent.file_id, "image789")
- default:
- XCTFail("Second content is not of type image.")
- }
- } else {
- XCTFail("Second content is missing.")
- }
-
- XCTAssertEqual(message.assistantId, "assistant123")
- XCTAssertEqual(message.runId, "run123")
- XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
- XCTAssertEqual(message.metadata?["key1"], "value1")
- XCTAssertEqual(message.metadata?["key2"], "value2")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-
- func testModifyMessage() async {
- do {
- let mockData = Self.modify.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let modifyMessageRequest = ASAModifyMessageRequest(metadata: ["modified": "true",
- "user": "abc123"])
- let message: ASAMessage = try await messagesAPI.modify(by: "thread123",
- messageId: "12345",
- modifyMessage: modifyMessageRequest)
-
- XCTAssertEqual(message.id, "12345")
- XCTAssertEqual(message.object, "thread.message")
- XCTAssertEqual(message.createdAt, 1639530000)
- XCTAssertEqual(message.threadId, "thread123")
- XCTAssertEqual(message.role, "assistant")
-
- if let firstContent = message.content.first {
- switch firstContent {
- case .text(let textContent):
- XCTAssertEqual(textContent.value, "This is a text message with annotations.")
- XCTAssertEqual(textContent.annotations?.count, 2)
- default:
- XCTFail("First content is not of type text.")
- }
- } else {
- XCTFail("Content is empty")
- }
-
- if message.content.count > 1 {
- switch message.content[1] {
- case .image(let imageContent):
- XCTAssertEqual(imageContent.file_id, "image789")
- default:
- XCTFail("Second content is not of type image.")
- }
- } else {
- XCTFail("Second content is missing.")
- }
-
- XCTAssertEqual(message.assistantId, "assistant123")
- XCTAssertEqual(message.runId, "run123")
- XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
- XCTAssertEqual(message.metadata?["key1"], "value1")
- XCTAssertEqual(message.metadata?["key2"], "value2")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-
- func testListMessages() async {
- do {
- let mockData = Self.list.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listResponse: ASAMessagesListResponse = try await messagesAPI.getMessages(by: "thread_abc123", parameters: nil)
-
- XCTAssertEqual(listResponse.object, "list")
- XCTAssertEqual(listResponse.data.count, 3)
- XCTAssertEqual(listResponse.data[0].id, "msg_SM7CyvQn3UnrAyOh96TGN5jR")
- XCTAssertEqual(listResponse.data[0].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
- XCTAssertEqual(listResponse.data[0].role, "assistant")
-
- // Тестирование содержимого первого сообщения
- if let firstContent = listResponse.data[0].content.first {
- switch firstContent {
- case .text(let textContent):
- XCTAssertEqual(textContent.value, "I now have full annotations for every date mentioned in the file")
- XCTAssertEqual(textContent.annotations?.count, 4)
-
- if let firstAnnotation = textContent.annotations?.first {
- XCTAssertEqual(firstAnnotation.type, "file_citation")
- XCTAssertEqual(firstAnnotation.text, "【21†source】")
- XCTAssertEqual(firstAnnotation.startIndex, 136)
- XCTAssertEqual(firstAnnotation.endIndex, 147)
- XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "")
- XCTAssertEqual(firstAnnotation.fileCitation?.quote, "adfadlfkjamdf")
- } else {
- XCTFail("First annotation not found.")
- }
-
- if let firstAnnotation = textContent.annotations?.last {
- XCTAssertEqual(firstAnnotation.type, "file_citation")
- XCTAssertEqual(firstAnnotation.text, "【33†source】")
- XCTAssertEqual(firstAnnotation.startIndex, 378)
- XCTAssertEqual(firstAnnotation.endIndex, 389)
- XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "")
- XCTAssertEqual(firstAnnotation.fileCitation?.quote, "DXB")
- } else {
- XCTFail("First annotation not found.")
- }
- default:
- XCTFail("First content is not of type text.")
- }
- } else {
- XCTFail("Content is empty")
- }
-
- XCTAssertEqual(listResponse.data[1].id, "msg_oacFAKp8WbIKYnV2Wmsyh5aE")
- XCTAssertEqual(listResponse.data[1].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
- XCTAssertEqual(listResponse.data[1].role, "assistant")
-
- XCTAssertEqual(listResponse.data[2].id, "msg_V8hf7PCvWceW4DpQKpQV83Ia")
- XCTAssertEqual(listResponse.data[2].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
- XCTAssertEqual(listResponse.data[2].role, "user")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-
- func testRetrieveFile() async {
- do {
- let mockData = Self.retrieveFile.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let file: ASAMessageFile = try await messagesAPI.retrieveFile(by: "thread_abc123", messageId: "msg_abc123", fileId: "file-abc123")
-
- XCTAssertEqual(file.id, "file-abc123")
- XCTAssertEqual(file.object, "thread.message.file")
- XCTAssertEqual(file.createdAt, 1698107661)
- XCTAssertEqual(file.messageId, "message_QLoItBbqwyAJEzlTy4y9kOMM")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testListFiles() async {
- do {
- let mockData = Self.listFiles.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listResponse: ASAMessageFilesListResponse = try await messagesAPI.listFiles(by: "thread_abc123", messageId: "msg_abc123", parameters: nil)
-
- XCTAssertEqual(listResponse.object, "list")
- XCTAssertEqual(listResponse.data.count, 2)
- XCTAssertEqual(listResponse.data[0].id, "file-abc123")
- XCTAssertEqual(listResponse.data[0].createdAt, 1698107661)
- XCTAssertEqual(listResponse.data[0].messageId, "message_QLoItBbqwyAJEzlTy4y9kOMM")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-}
diff --git a/Tests/AISwiftAssistTests/APIs/ModelsAPITests.swift b/Tests/AISwiftAssistTests/APIs/ModelsAPITests.swift
deleted file mode 100644
index f386526..0000000
--- a/Tests/AISwiftAssistTests/APIs/ModelsAPITests.swift
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// ModelsAPITests.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import XCTest
-@testable import AISwiftAssist
-
-final class ModelsAPITests: XCTestCase {
-
- var modelsAPI: IModelsAPI!
-
- override func setUp() {
- super.setUp()
- let configuration = URLSessionConfiguration.default
- configuration.protocolClasses = [MockURLProtocol.self]
- let mockURLSession = URLSession(configuration: configuration)
- modelsAPI = ModelsAPI(urlSession: mockURLSession)
- }
-
- override func tearDown() {
- modelsAPI = nil
- super.tearDown()
- }
-
- func testListModels() async {
- do {
- // Simulate server response
- let mockData = Self.list.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listResponse: ASAModelsListResponse = try await modelsAPI.get()
-
- XCTAssertEqual(listResponse.data[0].id, "model-id-0")
- XCTAssertEqual(listResponse.data[0].object, "model")
- XCTAssertEqual(listResponse.data[0].created, 1686935002)
- XCTAssertEqual(listResponse.data[0].ownedBy, "organization-owner")
-
- XCTAssertEqual(listResponse.data[1].id, "model-id-1")
- XCTAssertEqual(listResponse.data[1].object, "model")
- XCTAssertEqual(listResponse.data[1].created, 1686935002)
- XCTAssertEqual(listResponse.data[1].ownedBy, "organization-owner")
-
- XCTAssertEqual(listResponse.data[2].id, "model-id-2")
- XCTAssertEqual(listResponse.data[2].object, "model")
- XCTAssertEqual(listResponse.data[2].created, 1686935002)
- XCTAssertEqual(listResponse.data[2].ownedBy, "openai")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testRetrieveModel() async {
- do {
- // Simulate server response
- let mockData = Self.retrieve.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let modelId = "gpt-3.5-turbo-instruct"
- let model: ASAModel = try await modelsAPI.retrieve(by: modelId)
-
- XCTAssertEqual(model.id, modelId)
- XCTAssertEqual(model.object, "model")
- XCTAssertEqual(model.created, 1686935002)
- XCTAssertEqual(model.ownedBy, "openai")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testDeleteModel() async {
- do {
- // Simulate server response
- let mockData = Self.delete.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let modelId = "ft:gpt-3.5-turbo:acemeco:suffix:abc123"
- let deleteResponse: ASADeleteModelResponse = try await modelsAPI.delete(by: modelId)
-
- XCTAssertEqual(deleteResponse.id, "ft:gpt-3.5-turbo:acemeco:suffix:abc123")
- XCTAssertEqual(deleteResponse.object, "model")
- XCTAssertTrue(deleteResponse.deleted)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-}
diff --git a/Tests/AISwiftAssistTests/APIs/RunsAPITests.swift b/Tests/AISwiftAssistTests/APIs/RunsAPITests.swift
deleted file mode 100644
index 868582a..0000000
--- a/Tests/AISwiftAssistTests/APIs/RunsAPITests.swift
+++ /dev/null
@@ -1,358 +0,0 @@
-//
-// RunsAPITests.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import XCTest
-@testable import AISwiftAssist
-
-final class RunsAPITests: XCTestCase {
-
- var runsAPI: IRunsAPI!
-
- override func setUp() {
- super.setUp()
- let configuration = URLSessionConfiguration.default
- configuration.protocolClasses = [MockURLProtocol.self]
- let mockURLSession = URLSession(configuration: configuration)
- runsAPI = RunsAPI(urlSession: mockURLSession)
- }
-
- override func tearDown() {
- runsAPI = nil
- super.tearDown()
- }
-
- func testCreateRun() async {
- do {
- let mockData = Self.create.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let createRunRequest = ASACreateRunRequest(assistantId: "asst_abc123")
- let run: ASARun = try await runsAPI.create(by: "thread_abc123",
- createRun: createRunRequest)
-
- XCTAssertEqual(run.id, "run_abc123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.createdAt, 1699063290)
- XCTAssertEqual(run.assistantId, "asst_abc123")
- XCTAssertEqual(run.threadId, "thread_abc123")
- XCTAssertEqual(run.status, "queued")
- XCTAssertEqual(run.startedAt, 1699063290)
- XCTAssertNil(run.expiresAt)
- XCTAssertNil(run.cancelledAt)
- XCTAssertNil(run.failedAt)
- XCTAssertEqual(run.completedAt, 1699063291)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-4")
- XCTAssertNil(run.instructions)
- XCTAssertEqual(run.tools.count, 1)
- XCTAssertEqual(run.tools.first?.type, "code_interpreter")
- XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
- XCTAssertTrue(run.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
- func testRetrieveRun() async {
- do {
- let mockData = Self.retrieve.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let run: ASARun = try await runsAPI.retrieve(by: "thread_abc123",
- runId: "run_abc123")
-
- XCTAssertEqual(run.id, "run_abc123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.createdAt, 1699075072)
- XCTAssertEqual(run.assistantId, "asst_abc123")
- XCTAssertEqual(run.threadId, "thread_abc123")
- XCTAssertEqual(run.status, "completed")
- XCTAssertEqual(run.startedAt, 1699075072)
- XCTAssertNil(run.expiresAt)
- XCTAssertNil(run.cancelledAt)
- XCTAssertNil(run.failedAt)
- XCTAssertEqual(run.completedAt, 1699075073)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-3.5-turbo")
- XCTAssertNil(run.instructions)
- XCTAssertEqual(run.tools.count, 1)
- XCTAssertEqual(run.tools.first?.type, "code_interpreter")
- XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
- XCTAssertTrue(run.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testModifyRun() async {
- do {
- let mockData = Self.modify.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let modifyRunRequest = ASAModifyRunRequest(metadata: ["user_id": "user_abc123"])
- let run: ASARun = try await runsAPI.modify(by: "thread_abc123",
- runId: "run_abc123",
- modifyRun: modifyRunRequest)
-
- XCTAssertEqual(run.id, "run_abc123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.createdAt, 1699075072)
- XCTAssertEqual(run.assistantId, "asst_abc123")
- XCTAssertEqual(run.threadId, "thread_abc123")
- XCTAssertEqual(run.status, "completed")
- XCTAssertEqual(run.startedAt, 1699075072)
- XCTAssertNil(run.expiresAt)
- XCTAssertNil(run.cancelledAt)
- XCTAssertNil(run.failedAt)
- XCTAssertEqual(run.completedAt, 1699075073)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-3.5-turbo")
- XCTAssertNil(run.instructions)
- XCTAssertEqual(run.tools.count, 1)
- XCTAssertEqual(run.tools.first?.type, "code_interpreter")
- XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
- XCTAssertEqual(run.metadata?["user_id"], "user_abc123")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testListRuns() async {
- do {
- let mockData = Self.list.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listResponse: ASARunsListResponse = try await runsAPI.listRuns(by: "thread_abc123",
- parameters: nil)
-
- XCTAssertEqual(listResponse.object, "list")
- XCTAssertEqual(listResponse.data.count, 2)
-
- let firstRun = listResponse.data[0]
- XCTAssertEqual(firstRun.id, "run_abc123")
- XCTAssertEqual(firstRun.object, "thread.run")
- XCTAssertEqual(firstRun.createdAt, 1699075072)
- XCTAssertEqual(firstRun.assistantId, "asst_abc123")
- XCTAssertEqual(firstRun.threadId, "thread_abc123")
- XCTAssertEqual(firstRun.status, "completed")
- XCTAssertEqual(firstRun.startedAt, 1699075072)
- XCTAssertNil(firstRun.expiresAt)
- XCTAssertNil(firstRun.cancelledAt)
- XCTAssertNil(firstRun.failedAt)
- XCTAssertEqual(firstRun.completedAt, 1699075073)
- XCTAssertNil(firstRun.lastError)
- XCTAssertEqual(firstRun.model, "gpt-3.5-turbo")
- XCTAssertNil(firstRun.instructions)
- XCTAssertEqual(firstRun.tools.count, 1)
- XCTAssertEqual(firstRun.tools[0].type, "code_interpreter")
- XCTAssertEqual(firstRun.fileIds, ["file-abc123", "file-abc456"])
- XCTAssertTrue(firstRun.metadata?.isEmpty ?? true)
-
- XCTAssertEqual(listResponse.firstId, "run_abc123")
- XCTAssertEqual(listResponse.lastId, "run_abc456")
- XCTAssertFalse(listResponse.hasMore)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testSubmitToolOutputs() async {
- do {
- let mockData = RunsAPITests.submitToolOutputs.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/submit_tool_outputs")
- XCTAssertEqual(request.httpMethod, "POST")
- // Проверка тела запроса может быть добавлена здесь
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let toolOutputs = [ASAToolOutput(toolCallId: "call_abc123", output: "28C")]
- let run: ASARun = try await runsAPI.submitToolOutputs(by: "thread_abc123", runId: "run_abc123", toolOutputs: toolOutputs)
-
- XCTAssertEqual(run.id, "run_abc123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.createdAt, 1699063291)
- XCTAssertEqual(run.threadId, "thread_abc123")
- XCTAssertEqual(run.assistantId, "asst_abc123")
- XCTAssertEqual(run.status, "completed")
- XCTAssertEqual(run.startedAt, 1699063292)
- XCTAssertEqual(run.expiresAt, 1699066891)
- XCTAssertNil(run.cancelledAt)
- XCTAssertNil(run.failedAt)
- XCTAssertEqual(run.completedAt, 1699063391)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-3.5-turbo")
- XCTAssertEqual(run.instructions, "You are a helpful assistant.")
- XCTAssertEqual(run.tools.count, 1)
- XCTAssertEqual(run.tools.first?.type, "function")
- XCTAssertEqual(run.fileIds, ["file-abc123"])
- XCTAssertEqual(run.metadata?["additional_info"], "test")
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
- func testCancelRun() async {
- do {
-
- let mockData = RunsAPITests.cancelRun.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/cancel")
- XCTAssertEqual(request.httpMethod, "POST")
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let run: ASARun = try await runsAPI.cancelRun(by: "thread_abc123", runId: "run_abc123")
-
- XCTAssertEqual(run.id, "run_abc123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.status, "cancelled")
- XCTAssertEqual(run.createdAt, 1699075072)
- XCTAssertEqual(run.assistantId, "asst_abc123")
- XCTAssertEqual(run.threadId, "thread_abc123")
- XCTAssertEqual(run.status, "cancelled")
- XCTAssertEqual(run.startedAt, 1699075072)
- XCTAssertEqual(run.expiresAt, 1699075672)
- XCTAssertEqual(run.cancelledAt, 1699075092)
- XCTAssertNil(run.failedAt)
- XCTAssertNil(run.completedAt)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-3.5-turbo")
- XCTAssertEqual(run.instructions, "Provide instructions")
- XCTAssertTrue(run.tools.isEmpty)
- XCTAssertEqual(run.fileIds, ["file-abc123"])
- XCTAssertEqual(run.metadata?["key"], "value")
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
- func testCreateThreadAndRun() async {
- do {
- let mockData = RunsAPITests.createThreadAndRun.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- XCTAssertEqual(request.url?.path, "/v1/threads/runs")
- XCTAssertEqual(request.httpMethod, "POST")
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let createThreadRunRequest = ASACreateThreadRunRequest(assistantId: "", thread: ASACreateThreadRunRequest.Thread(messages: []))
- let run: ASARun = try await runsAPI.createThreadAndRun(createThreadRun: createThreadRunRequest)
-
- XCTAssertEqual(run.id, "run_xyz123")
- XCTAssertEqual(run.object, "thread.run")
- XCTAssertEqual(run.createdAt, 1699080000)
- XCTAssertEqual(run.threadId, "thread_xyz123")
- XCTAssertEqual(run.assistantId, "asst_xyz123")
- XCTAssertEqual(run.status, "in_progress")
- XCTAssertEqual(run.startedAt, 1699080001)
- XCTAssertEqual(run.expiresAt, 1699080600)
- XCTAssertNil(run.cancelledAt)
- XCTAssertNil(run.failedAt)
- XCTAssertNil(run.completedAt)
- XCTAssertNil(run.lastError)
- XCTAssertEqual(run.model, "gpt-3.5-turbo")
- XCTAssertEqual(run.instructions, "Explain deep learning to a 5 year old.")
- XCTAssertEqual(run.tools.count, 1)
- XCTAssertEqual(run.fileIds, ["file-xyz123"])
- XCTAssertEqual(run.metadata?["session"], "1")
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
- func testRetrieveRunStep() async {
- do {
- let mockData = RunsAPITests.retrieveRunStep.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/steps/step_xyz123")
- XCTAssertEqual(request.httpMethod, "GET")
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let runStep: ASARunStep = try await runsAPI.retrieveRunStep(by: "thread_abc123", runId: "run_abc123", stepId: "step_xyz123")
-
- XCTAssertEqual(runStep.id, "step_abc123")
- XCTAssertEqual(runStep.object, "thread.run.step")
- XCTAssertEqual(runStep.createdAt, 1699063291)
- XCTAssertEqual(runStep.runId, "run_abc123")
- XCTAssertEqual(runStep.assistantId, "asst_abc123")
- XCTAssertEqual(runStep.threadId, "thread_abc123")
- XCTAssertEqual(runStep.type, "message_creation")
- XCTAssertEqual(runStep.status, "completed")
- XCTAssertNil(runStep.cancelledAt)
- XCTAssertEqual(runStep.completedAt, 1699063291)
- XCTAssertNil(runStep.expiredAt)
- XCTAssertNil(runStep.failedAt)
- XCTAssertNil(runStep.lastError)
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
- func testListRunSteps() async {
- do {
- let mockData = RunsAPITests.listRunSteps.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/steps")
- XCTAssertEqual(request.httpMethod, "GET")
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let listResponse: ASARunStepsListResponse = try await runsAPI.listRunSteps(by: "thread_abc123", runId: "run_abc123", parameters: nil)
-
- XCTAssertEqual(listResponse.object, "list")
- XCTAssertEqual(listResponse.data.count, 1)
-
- let firstStep = listResponse.data[0]
- XCTAssertEqual(firstStep.id, "step_xyz123")
- XCTAssertEqual(firstStep.object, "thread.run.step")
- XCTAssertEqual(firstStep.createdAt, 1699080100)
- XCTAssertEqual(firstStep.runId, "run_xyz123")
- XCTAssertEqual(firstStep.assistantId, "asst_xyz123")
- XCTAssertEqual(firstStep.threadId, "thread_xyz123")
- XCTAssertEqual(firstStep.type, "message_creation")
- XCTAssertEqual(firstStep.status, "completed")
- XCTAssertNil(firstStep.cancelledAt)
- XCTAssertEqual(firstStep.completedAt, 1699080200)
- XCTAssertNil(firstStep.expiredAt)
- XCTAssertNil(firstStep.failedAt)
- XCTAssertNil(firstStep.lastError)
-
- } catch {
- XCTFail("Error: \(error.localizedDescription)")
- }
- }
-
-
-}
diff --git a/Tests/AISwiftAssistTests/APIs/ThreadsAPITests.swift b/Tests/AISwiftAssistTests/APIs/ThreadsAPITests.swift
deleted file mode 100644
index 9c4884f..0000000
--- a/Tests/AISwiftAssistTests/APIs/ThreadsAPITests.swift
+++ /dev/null
@@ -1,128 +0,0 @@
-//
-// ThreadsAPITests.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import XCTest
-@testable import AISwiftAssist
-
-final class ThreadsAPITests: XCTestCase {
-
- var threadsAPI: IThreadsAPI!
-
- override func setUp() {
- super.setUp()
- let configuration = URLSessionConfiguration.default
- configuration.protocolClasses = [MockURLProtocol.self]
- let mockURLSession = URLSession(configuration: configuration)
- threadsAPI = ThreadsAPI(urlSession: mockURLSession)
- }
-
- override func tearDown() {
- threadsAPI = nil
- super.tearDown()
- }
-
- func testCreateThread() async {
- do {
- // Simulate server response
- let mockData = Self.create.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let message = ASACreateThreadRequest.Message(role: "user",
- content: "Hello World",
- fileIds: nil,
- metadata: nil)
- let createRequest = ASACreateThreadRequest(messages: [message])
- let thread: ASAThread = try await threadsAPI.create(by: createRequest)
-
- // Checks
- XCTAssertEqual(thread.id, "thread_abc123")
- XCTAssertEqual(thread.object, "thread")
- XCTAssertEqual(thread.createdAt, 1699012949)
- XCTAssertTrue(thread.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testRetrieveThread() async {
- do {
- // Simulate server response
- let mockData = Self.retrieve.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let threadId = "thread_abc123"
- let thread: ASAThread = try await threadsAPI.retrieve(threadId: threadId)
-
- // Checks
- XCTAssertEqual(thread.id, threadId)
- XCTAssertEqual(thread.object, "thread")
- XCTAssertEqual(thread.createdAt, 1699014083)
- XCTAssertTrue(thread.metadata?.isEmpty ?? true)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testModifyThread() async {
- do {
- // Simulate server response
- let mockData = Self.modify.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let threadId = "thread_abc123"
- let modifyRequest = ASAModifyThreadRequest(metadata: ["modified": "true",
- "user": "abc123"])
-
- let modifiedThread: ASAThread = try await threadsAPI.modify(threadId: threadId,
- with: modifyRequest)
-
- // Checks
- XCTAssertEqual(modifiedThread.id, threadId)
- XCTAssertEqual(modifiedThread.object, "thread")
- XCTAssertEqual(modifiedThread.createdAt, 1699014083)
- XCTAssertEqual(modifiedThread.metadata?["modified"], "true")
- XCTAssertEqual(modifiedThread.metadata?["user"], "abc123")
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
- func testDeleteThread() async {
- do {
- // Simulate server response
- let mockData = Self.delete.data(using: .utf8)!
-
- MockURLProtocol.requestHandler = { request in
- let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
- return (response, mockData)
- }
-
- let threadId = "thread_abc123"
- let deleteResponse: ASADeleteModelResponse = try await threadsAPI.delete(threadId: threadId)
-
- // Checks
- XCTAssertEqual(deleteResponse.id, threadId)
- XCTAssertEqual(deleteResponse.object, "thread.deleted")
- XCTAssertTrue(deleteResponse.deleted)
- } catch {
- XCTFail("Error: \(error)")
- }
- }
-
-}
diff --git a/Tests/AISwiftAssistTests/Mosks/AssistantMocks.swift b/Tests/AISwiftAssistTests/Mosks/AssistantMocks.swift
index 5a4acff..361fcee 100644
--- a/Tests/AISwiftAssistTests/Mosks/AssistantMocks.swift
+++ b/Tests/AISwiftAssistTests/Mosks/AssistantMocks.swift
@@ -9,122 +9,170 @@ import Foundation
extension AssistantsAPITests {
static let list: String =
- """
+ """
{
"object": "list",
"data": [
{
- "id": "asst_abc123",
+ "id": "asst_advanced_full",
"object": "assistant",
- "created_at": 1698982736,
- "name": "Coding Tutor",
- "description": null,
+ "created_at": 1709800000,
+ "name": "Advanced Assistant",
+ "description": "Fully featured assistant with comprehensive tools and strict output control.",
+ "model": "gpt-4-turbo",
+ "instructions": "You are a professional assistant with advanced functionality, tool support, and strict output controls.",
+ "tools": [
+ {"type": "code_interpreter"},
+ {"type": "file_search"},
+ {"type": "function", "function": {"name": "fetchData", "description": "Fetches data from external sources."}}
+ ],
+ "file_ids": ["file_adv_01", "file_adv_02"],
+ "metadata": {"role": "advanced", "version": "1.0"}
+ },
+ {
+ "id": "asst_professional",
+ "object": "assistant",
+ "created_at": 1709800500,
+ "name": "Professional Assistant",
+ "description": "Professional assistant designed for complex tasks and detailed analysis.",
+ "model": "gpt-4-turbo",
+ "instructions": "You provide detailed and professional assistance for complex and analytical tasks.",
+ "tools": [{"type": "code_interpreter"}, {"type": "file_search"}],
+ "file_ids": ["file_pro_01"],
+ "metadata": {"role": "professional", "version": "1.0"}
+ },
+ {
+ "id": "asst_standard",
+ "object": "assistant",
+ "created_at": 1709801000,
+ "name": "Standard Assistant",
+ "description": "A standard assistant suitable for everyday tasks.",
+ "model": "gpt-4",
+ "instructions": "You are a helpful assistant equipped with basic tools for everyday tasks.",
+ "tools": [{"type": "file_search"}],
+ "file_ids": ["file_std_01"],
+ "metadata": {"role": "standard", "version": "0.9"}
+ },
+ {
+ "id": "asst_intermediate",
+ "object": "assistant",
+ "created_at": 1709801200,
+ "name": "Intermediate Assistant",
+ "description": "Intermediate assistant with moderate capabilities.",
"model": "gpt-4",
- "instructions": "You are a helpful assistant designed to make me better at coding!",
+ "instructions": "You help users with moderate complexity tasks efficiently.",
"tools": [],
"file_ids": [],
- "metadata": {}
+ "metadata": {"role": "intermediate", "version": "0.8"}
},
{
- "id": "asst_abc456",
+ "id": "asst_basic",
"object": "assistant",
- "created_at": 1698982718,
- "name": "My Assistant",
+ "created_at": 1709801500,
+ "name": "Basic Assistant",
+ "description": "Simple assistant for basic queries.",
+ "model": "gpt-3.5-turbo",
+ "instructions": "You assist with basic queries and simple tasks.",
+ "tools": [],
+ "file_ids": [],
+ "metadata": {"role": "basic", "version": "0.7"}
+ },
+ {
+ "id": "asst_simple",
+ "object": "assistant",
+ "created_at": 1709801800,
+ "name": "Simple Assistant",
"description": null,
- "model": "gpt-4",
- "instructions": "You are a helpful assistant designed to make me better at coding!",
+ "model": "gpt-3.5-turbo",
+ "instructions": "You are a simple and friendly assistant for straightforward tasks.",
"tools": [],
"file_ids": [],
"metadata": {}
},
{
- "id": "asst_abc789",
+ "id": "asst_minimal",
"object": "assistant",
- "created_at": 1698982643,
- "name": null,
+ "created_at": 1709802000,
+ "name": "Minimal Assistant",
"description": null,
- "model": "gpt-4",
+ "model": "gpt-3.5-turbo",
"instructions": null,
"tools": [],
"file_ids": [],
"metadata": {}
}
],
- "first_id": "asst_abc123",
- "last_id": "asst_abc789",
+ "first_id": "asst_advanced_full",
+ "last_id": "asst_minimal",
"has_more": false
}
"""
+
static let create: String =
"""
- {
- "id": "asst_abc123",
- "object": "assistant",
- "created_at": 1698984975,
- "name": "Math Tutor",
- "description": null,
- "model": "gpt-4",
- "instructions": "You are a personal math tutor. When asked a question, write and run Python code to answer the question.",
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [],
- "metadata": {}
- }
+ {
+ "id": "asst_advanced_full",
+ "object": "assistant",
+ "created_at": 1709800000,
+ "name": "Advanced Assistant",
+ "description": "Fully featured assistant with comprehensive tools and strict output control.",
+ "model": "gpt-4-turbo",
+ "instructions": "You are a professional assistant with advanced functionality, tool support, and strict output controls.",
+ "tools": [
+ {"type": "code_interpreter"},
+ {"type": "file_search"},
+ {"type": "function", "function": {"name": "fetchData", "description": "Fetches data from external sources."}}
+ ],
+ "file_ids": ["file_adv_01", "file_adv_02"],
+ "metadata": {"role": "advanced", "version": "1.0"}
+ }
"""
static let retrieve: String =
"""
- {
- "id": "asst_abc123",
- "object": "assistant",
- "created_at": 1699009709,
- "name": "HR Helper",
- "description": null,
- "model": "gpt-4",
- "instructions": "You are an HR bot, and you have access to files to answer employee questions about company policies.",
- "tools": [
- {
- "type": "retrieval"
- }
- ],
- "file_ids": [
- "file-abc123"
- ],
- "metadata": {}
- }
+ {
+ "id": "asst_advanced_full",
+ "object": "assistant",
+ "created_at": 1709800000,
+ "name": "Advanced Assistant",
+ "description": "Fully featured assistant with comprehensive tools and strict output control.",
+ "model": "gpt-4-turbo",
+ "instructions": "You are a professional assistant with advanced functionality, tool support, and strict output controls.",
+ "tools": [
+ {"type": "code_interpreter"},
+ {"type": "file_search"},
+ {"type": "function", "function": {"name": "fetchData", "description": "Fetches data from external sources."}}
+ ],
+ "file_ids": ["file_adv_01", "file_adv_02"],
+ "metadata": {"role": "advanced", "version": "1.0"}
+ }
"""
static let modify: String =
"""
- {
- "id": "asst_abc123",
- "object": "assistant",
- "created_at": 1699009709,
- "name": "HR Helper",
- "description": null,
- "model": "gpt-4",
- "instructions": "You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.",
- "tools": [
- {
- "type": "retrieval"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {}
- }
+ {
+ "id": "asst_advanced_full",
+ "object": "assistant",
+ "created_at": 1709800000,
+ "name": "Advanced",
+ "description": "Fully featured assistant with comprehensive tools and strict output control.",
+ "model": "gpt-4",
+ "instructions": "You are a professional assistant with advanced functionality, tool support, and strict output controls.",
+ "tools": [
+ {"type": "code_interpreter"},
+ {"type": "file_search"},
+ {"type": "function", "function": {"name": "fetchData", "description": "Fetches data from external sources."}}
+ ],
+ "file_ids": ["file_adv_01", "file_adv_02"],
+ "metadata": {"role": "advanced", "version": "1.0"}
+ }
"""
static let delete: String =
"""
{
- "id": "asst_abc123",
+ "id": "asst_advanced_full",
"object": "assistant.deleted",
"deleted": true
}
diff --git a/Tests/AISwiftAssistTests/Mosks/MessagesMocks.swift b/Tests/AISwiftAssistTests/Mosks/MessagesMocks.swift
deleted file mode 100644
index ed481ee..0000000
--- a/Tests/AISwiftAssistTests/Mosks/MessagesMocks.swift
+++ /dev/null
@@ -1,321 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import Foundation
-
-extension MessagesAPITests {
-
- static let list: String =
- """
- {
- "object": "list",
- "data": [
- {
- "id": "msg_SM7CyvQn3UnrAyOh96TGN5jR",
- "object": "thread.message",
- "created_at": 1702586152,
- "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
- "role": "assistant",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "I now have full annotations for every date mentioned in the file",
- "annotations": [
- {
- "type": "file_citation",
- "text": "【21†source】",
- "start_index": 136,
- "end_index": 147,
- "file_citation": {
- "file_id": "",
- "quote": "adfadlfkjamdf"
- }
- },
- {
- "type": "file_citation",
- "text": "【22†source】",
- "start_index": 197,
- "end_index": 208,
- "file_citation": {
- "file_id": "",
- "quote": "01.12.202318"
- }
- },
- {
- "type": "file_citation",
- "text": "【29†source】",
- "start_index": 287,
- "end_index": 298,
- "file_citation": {
- "file_id": "",
- "quote": "ajfnailfbnloabiufnliajnsfl"
- }
- },
- {
- "type": "file_citation",
- "text": "【33†source】",
- "start_index": 378,
- "end_index": 389,
- "file_citation": {
- "file_id": "",
- "quote": "DXB"
- }
- }
- ]
- }
- }
- ],
- "file_ids": [],
- "assistant_id": "asst_wAI24kgbPjUpBqnEbsyjS8iO",
- "run_id": "run_RapQjuYYvH1gpyOQB2DSAurf",
- "metadata": {}
- },
- {
- "id": "msg_oacFAKp8WbIKYnV2Wmsyh5aE",
- "object": "thread.message",
- "created_at": 1702560337,
- "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
- "role": "assistant",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "Привет! Как я могу помочь вам сегодня?",
- "annotations": []
- }
- }
- ],
- "file_ids": [],
- "assistant_id": "asst_wAI24kgbPjUpBqnEbsyjS8iO",
- "run_id": "run_9P57TYv8dxBPQkHK8plrNztk",
- "metadata": {}
- },
- {
- "id": "msg_V8hf7PCvWceW4DpQKpQV83Ia",
- "object": "thread.message",
- "created_at": 1702560334,
- "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
- "role": "user",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "привет",
- "annotations": []
- }
- }
- ],
- "file_ids": [],
- "assistant_id": null,
- "run_id": null,
- "metadata": {}
- }
- ],
- "first_id": "msg_SM7CyvQn3UnrAyOh96TGN5jR",
- "last_id": "msg_V8hf7PCvWceW4DpQKpQV83Ia",
- "has_more": false
- }
- """
-
- static let modify: String =
- """
- {
- "id": "12345",
- "object": "thread.message",
- "created_at": 1639530000,
- "thread_id": "thread123",
- "role": "assistant",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "This is a text message with annotations.",
- "annotations": [
-
- {
- "type": "file_citation",
- "text": "document link",
- "file_citation": {
- "file_id": "file123",
- "quote": "A quote from the file"
- },
- "start_index": 0,
- "end_index": 23
- },
- {
- "type": "file_path",
- "text": "path to file",
- "file_path": {
- "file_id": "file456"
- },
- "start_index": 24,
- "end_index": 37
- }
- ]
- }
- },
- {
- "type": "image_file",
- "image_file": {
- "file_id": "image789"
- }
- }
- ],
- "assistant_id": "assistant123",
- "run_id": "run123",
- "file_ids": ["file123", "file456", "image789"],
- "metadata": {
- "key1": "value1",
- "key2": "value2"
- }
- }
- """
-
- static let retrieve: String =
- """
- {
- "id": "12345",
- "object": "thread.message",
- "created_at": 1639530000,
- "thread_id": "thread123",
- "role": "assistant",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "This is a text message with annotations.",
- "annotations": [
-
- {
- "type": "file_citation",
- "text": "document link",
- "file_citation": {
- "file_id": "file123",
- "quote": "A quote from the file"
- },
- "start_index": 0,
- "end_index": 23
- },
- {
- "type": "file_path",
- "text": "path to file",
- "file_path": {
- "file_id": "file456"
- },
- "start_index": 24,
- "end_index": 37
- }
- ]
- }
- },
- {
- "type": "image_file",
- "image_file": {
- "file_id": "image789"
- }
- }
- ],
- "assistant_id": "assistant123",
- "run_id": "run123",
- "file_ids": ["file123", "file456", "image789"],
- "metadata": {
- "key1": "value1",
- "key2": "value2"
- }
- }
- """
-
- static let create: String =
- """
- {
- "id": "12345",
- "object": "thread.message",
- "created_at": 1639530000,
- "thread_id": "thread123",
- "role": "assistant",
- "content": [
- {
- "type": "text",
- "text": {
- "value": "This is a text message with annotations.",
- "annotations": [
-
- {
- "type": "file_citation",
- "text": "document link",
- "file_citation": {
- "file_id": "file123",
- "quote": "A quote from the file"
- },
- "start_index": 0,
- "end_index": 23
- },
- {
- "type": "file_path",
- "text": "path to file",
- "file_path": {
- "file_id": "file456"
- },
- "start_index": 24,
- "end_index": 37
- }
- ]
- }
- },
- {
- "type": "image_file",
- "image_file": {
- "file_id": "image789"
- }
- }
- ],
- "assistant_id": "assistant123",
- "run_id": "run123",
- "file_ids": ["file123", "file456", "image789"],
- "metadata": {
- "key1": "value1",
- "key2": "value2"
- }
- }
- """
-
- static let retrieveFile: String =
- """
- {
- "id": "file-abc123",
- "object": "thread.message.file",
- "created_at": 1698107661,
- "message_id": "message_QLoItBbqwyAJEzlTy4y9kOMM",
- "file_id": "file-abc123"
- }
- """
-
- static let listFiles: String =
- """
- {
- "object": "list",
- "data": [
- {
- "id": "file-abc123",
- "object": "thread.message.file",
- "created_at": 1698107661,
- "message_id": "message_QLoItBbqwyAJEzlTy4y9kOMM"
- },
- {
- "id": "file-abc456",
- "object": "thread.message.file",
- "created_at": 1698107662,
- "message_id": "message_QLoItBbqwyAJEzlTy4y9kONN"
- }
- ],
- "first_id": "file-abc123",
- "last_id": "file-abc456",
- "has_more": false
- }
- """
-}
diff --git a/Tests/AISwiftAssistTests/Mosks/ModelsMocks.swift b/Tests/AISwiftAssistTests/Mosks/ModelsMocks.swift
deleted file mode 100644
index d842772..0000000
--- a/Tests/AISwiftAssistTests/Mosks/ModelsMocks.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import Foundation
-
-extension ModelsAPITests {
- static let list: String =
- """
- {
- "object": "list",
- "data": [
- {
- "id": "model-id-0",
- "object": "model",
- "created": 1686935002,
- "owned_by": "organization-owner"
- },
- {
- "id": "model-id-1",
- "object": "model",
- "created": 1686935002,
- "owned_by": "organization-owner"
- },
- {
- "id": "model-id-2",
- "object": "model",
- "created": 1686935002,
- "owned_by": "openai"
- }
- ]
- }
- """
-
- static let retrieve: String =
- """
- {
- "id": "gpt-3.5-turbo-instruct",
- "object": "model",
- "created": 1686935002,
- "owned_by": "openai"
- }
- """
-
- static let delete: String =
- """
- {
- "id": "ft:gpt-3.5-turbo:acemeco:suffix:abc123",
- "object": "model",
- "deleted": true
- }
- """
-}
diff --git a/Tests/AISwiftAssistTests/Mosks/RunsMocks.swift b/Tests/AISwiftAssistTests/Mosks/RunsMocks.swift
deleted file mode 100644
index b76360c..0000000
--- a/Tests/AISwiftAssistTests/Mosks/RunsMocks.swift
+++ /dev/null
@@ -1,310 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import Foundation
-
-extension RunsAPITests {
- static let create: String =
- """
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699063290,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "queued",
- "started_at": 1699063290,
- "expires_at": null,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699063291,
- "last_error": null,
- "model": "gpt-4",
- "instructions": null,
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {}
- }
- """
-
- static let retrieve: String =
- """
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699075072,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "completed",
- "started_at": 1699075072,
- "expires_at": null,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699075073,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": null,
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {}
- }
- """
-
- static let modify: String =
- """
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699075072,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "completed",
- "started_at": 1699075072,
- "expires_at": null,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699075073,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": null,
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {
- "user_id": "user_abc123"
- }
- }
- """
-
- static let list: String =
- """
-{
- "object": "list",
- "data": [
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699075072,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "completed",
- "started_at": 1699075072,
- "expires_at": null,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699075073,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": null,
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {}
- },
- {
- "id": "run_abc456",
- "object": "thread.run",
- "created_at": 1699063290,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "completed",
- "started_at": 1699063290,
- "expires_at": null,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699063291,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": null,
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": [
- "file-abc123",
- "file-abc456"
- ],
- "metadata": {}
- }
- ],
- "first_id": "run_abc123",
- "last_id": "run_abc456",
- "has_more": false
-}
-"""
- static let submitToolOutputs: String =
- """
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699063291,
- "thread_id": "thread_abc123",
- "assistant_id": "asst_abc123",
- "status": "completed",
- "started_at": 1699063292,
- "expires_at": 1699066891,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": 1699063391,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": "You are a helpful assistant.",
- "tools": [
- {
- "type": "function",
- "function": {
- "name": "get_weather",
- "description": "Determine weather in my location",
- "parameters": {
- "location": "San Francisco, CA",
- "unit": "c"
- }
- }
- }
- ],
- "file_ids": ["file-abc123"],
- "metadata": {
- "additional_info": "test"
- }
- }
- """
- static let cancelRun: String =
- """
- {
- "id": "run_abc123",
- "object": "thread.run",
- "created_at": 1699075072,
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "status": "cancelled",
- "started_at": 1699075072,
- "expires_at": 1699075672,
- "cancelled_at": 1699075092,
- "failed_at": null,
- "completed_at": null,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": "Provide instructions",
- "tools": [],
- "file_ids": ["file-abc123"],
- "metadata": {"key": "value"}
- }
- """
-
- static let createThreadAndRun: String =
- """
- {
- "id": "run_xyz123",
- "object": "thread.run",
- "created_at": 1699080000,
- "thread_id": "thread_xyz123",
- "assistant_id": "asst_xyz123",
- "status": "in_progress",
- "started_at": 1699080001,
- "expires_at": 1699080600,
- "cancelled_at": null,
- "failed_at": null,
- "completed_at": null,
- "last_error": null,
- "model": "gpt-3.5-turbo",
- "instructions": "Explain deep learning to a 5 year old.",
- "tools": [
- {
- "type": "code_interpreter"
- }
- ],
- "file_ids": ["file-xyz123"],
- "metadata": {"session": "1"}
- }
- """
-
- static let retrieveRunStep: String =
- """
- {
- "id": "step_abc123",
- "object": "thread.run.step",
- "created_at": 1699063291,
- "run_id": "run_abc123",
- "assistant_id": "asst_abc123",
- "thread_id": "thread_abc123",
- "type": "message_creation",
- "status": "completed",
- "cancelled_at": null,
- "completed_at": 1699063291,
- "expired_at": null,
- "failed_at": null,
- "last_error": null,
- "step_details": {
- "type": "message_creation",
- "message_creation": {
- "message_id": "msg_abc123"
- }
- }
- }
- """
-
- static let listRunSteps: String =
- """
- {
- "object": "list",
- "data": [
- {
- "id": "step_xyz123",
- "object": "thread.run.step",
- "created_at": 1699080100,
- "run_id": "run_xyz123",
- "assistant_id": "asst_xyz123",
- "thread_id": "thread_xyz123",
- "type": "message_creation",
- "status": "completed",
- "cancelled_at": null,
- "completed_at": 1699080200,
- "expired_at": null,
- "failed_at": null,
- "last_error": null,
- "step_details": {
- "type": "message_creation",
- "message_creation": {
- "message_id": "msg_xyz123"
- }
- }
- },
- ],
- "first_id": "step_xyz123",
- "last_id": "step_xyz456",
- "has_more": false
- }
- """
-
-}
diff --git a/Tests/AISwiftAssistTests/Mosks/ThreadsMocks.swift b/Tests/AISwiftAssistTests/Mosks/ThreadsMocks.swift
deleted file mode 100644
index d809ac9..0000000
--- a/Tests/AISwiftAssistTests/Mosks/ThreadsMocks.swift
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Alexey on 11/20/23.
-//
-
-import Foundation
-
-extension ThreadsAPITests {
- static let create: String =
- """
- {
- "id": "thread_abc123",
- "object": "thread",
- "created_at": 1699012949,
- "metadata": {}
- }
- """
-
- static let retrieve: String =
- """
- {
- "id": "thread_abc123",
- "object": "thread",
- "created_at": 1699014083,
- "metadata": {}
- }
- """
-
- static let modify: String =
- """
- {
- "id": "thread_abc123",
- "object": "thread",
- "created_at": 1699014083,
- "metadata": {
- "modified": "true",
- "user": "abc123"
- }
- }
- """
-
- static let delete: String =
- """
- {
- "id": "thread_abc123",
- "object": "thread.deleted",
- "deleted": true
- }
- """
-}
diff --git a/Tests/AISwiftAssistTests/Resourses/MockURLProtocol.swift b/Tests/AISwiftAssistTests/Resourses/MockURLProtocol.swift
index d670477..eef0464 100644
--- a/Tests/AISwiftAssistTests/Resourses/MockURLProtocol.swift
+++ b/Tests/AISwiftAssistTests/Resourses/MockURLProtocol.swift
@@ -7,48 +7,66 @@
import Foundation
-class MockURLProtocol: URLProtocol {
- // Handler to test the request and return a mock response.
- static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data?))?
+actor RequestHandlerStorage {
+ private var requestHandlers: [String: (@Sendable (URLRequest) async throws -> (HTTPURLResponse, Data))] = [:]
+
+ func setHandler(_ handler: @Sendable @escaping (URLRequest) async throws -> (HTTPURLResponse, Data), for key: String) async {
+ requestHandlers[key] = handler
+ }
+
+ func executeHandler(for request: URLRequest, for key: String) async throws -> (HTTPURLResponse, Data) {
+ guard let handler = requestHandlers[key] else {
+ throw MockURLProtocolError.noRequestHandler
+ }
+ return try await handler(request)
+ }
+}
+
+final class MockURLProtocol: URLProtocol, @unchecked Sendable {
+
+ private static let requestHandlerStorage = RequestHandlerStorage()
+
+ static func setHandler(_ handler: @Sendable @escaping (URLRequest) async throws -> (HTTPURLResponse, Data), for key: String) async {
+ await requestHandlerStorage.setHandler ({ request in
+ try await handler(request)
+ }, for: key)
+ }
+
+ func executeHandler(for request: URLRequest, key: String) async throws -> (HTTPURLResponse, Data) {
+ return try await Self.requestHandlerStorage.executeHandler(for: request, for: key)
+ }
override class func canInit(with request: URLRequest) -> Bool {
- // To check if this protocol can handle the given request.
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
- // Return the original request as the canonical version.
return request
}
override func startLoading() {
- guard let handler = MockURLProtocol.requestHandler else {
- fatalError("Handler is unavailable.")
- }
-
- do {
- // Call handler with received request and capture the tuple of response and data.
- let (response, data) = try handler(request)
-
- // Send received response to the client.
- client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
-
- if let data = data {
- // Send received data to the client.
+ Task {
+ do {
+ guard let key = request.value(forHTTPHeaderField: "ForTest") else {
+ client?.urlProtocol(self, didFailWithError: MockURLProtocolError.invalidURL)
+ return
+ }
+ let (response, data) = try await self.executeHandler(for: request, key: key)
+ client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
+ client?.urlProtocolDidFinishLoading(self)
+ } catch {
+ client?.urlProtocol(self, didFailWithError: error)
}
-
- // Notify request has been finished.
- client?.urlProtocolDidFinishLoading(self)
- } catch {
- // Notify received error.
- client?.urlProtocol(self, didFailWithError: error)
}
- }
- override func stopLoading() {
- // This is called if the request gets canceled or completed.
}
+
+ override func stopLoading() {}
}
+enum MockURLProtocolError: Error {
+ case noRequestHandler
+ case invalidURL
+}