Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new assistant methods #7

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Sources/AISwiftAssist/APIs/AssistantsAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ 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: ASAListFilesParameters?) async throws -> ASAAssistantFilesListResponse
}

public final class AssistantsAPI: HTTPClient, IAssistantsAPI {
Expand Down Expand Up @@ -82,6 +110,25 @@ 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: ASAListFilesParameters? = nil) async throws -> ASAAssistantFilesListResponse {
let endpoint = AssistantEndpoint.listFiles(assistantId, parameters)
return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFilesListResponse.self)
}
}

19 changes: 16 additions & 3 deletions Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ 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, ASAListFilesParameters?)
}

extension AssistantEndpoint: CustomEndpoint {
Expand All @@ -26,8 +30,9 @@ extension AssistantEndpoint: CustomEndpoint {
public var queryItems: [URLQueryItem]? {
var items: [URLQueryItem]?
switch self {
case .createAssistant, .deleteAssistant, .retrieveAssistant, .modifyAssistant: items = nil
case .createAssistant, .deleteAssistant, .retrieveAssistant, .modifyAssistant, .createFile, .retrieveFile, .deleteFile: items = nil
case .getAssistants(let params): items = Utils.createURLQueryItems(from: params)
case .listFiles(_, let params): items = Utils.createURLQueryItems(from: params)
}
return items
}
Expand All @@ -38,6 +43,10 @@ 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"
}
}

Expand All @@ -48,6 +57,10 @@ 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
}
}

Expand All @@ -61,8 +74,8 @@ extension AssistantEndpoint: CustomEndpoint {
switch self {
case .createAssistant(let createAssistant): return .init(object: createAssistant)
case .modifyAssistant(_, let request): return .init(object: request)
case .deleteAssistant, .retrieveAssistant, .getAssistants: return nil

case .createFile(_, let request): return .init(object: request)
case .deleteAssistant, .retrieveAssistant, .getAssistants, .retrieveFile, .deleteFile, .listFiles: return nil
}
}
}
27 changes: 27 additions & 0 deletions Sources/AISwiftAssist/Models/Main/ASAAssistantFile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// 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"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// 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"
}
}
34 changes: 34 additions & 0 deletions Sources/AISwiftAssist/Models/Request/ASAListFilesParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// File.swift
//
//
// Created by Alexey on 12/5/23.
//

import Foundation

/// Parameters for listing assistant files.
public struct ASAListFilesParameters: Encodable {
/// A limit on the number of objects to be returned.
/// Can range between 1 and 100. Defaults to 20.
public let limit: Int?

/// Sort order by the created_at timestamp of the objects.
/// 'asc' for ascending order and 'desc' for descending order.
public let order: String?

/// A cursor for use in pagination. 'after' is an object ID that defines
/// your place in the list, to fetch the next page of the list.
public let after: String?

/// 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) {
self.limit = limit
self.order = order
self.after = after
self.before = before
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// 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
}
}
92 changes: 92 additions & 0 deletions Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,97 @@ final class AssistantsAPITests: XCTestCase {
}
}

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)")
}
}

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 = ASAListFilesParameters()
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)")
}
}


}
Loading