Skip to content

Commit

Permalink
feat: Support Box AI endpoints (box/box-openapi#416) (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
box-sdk-build authored May 6, 2024
1 parent e5069af commit 175ab82
Show file tree
Hide file tree
Showing 16 changed files with 548 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .codegen.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "engineHash": "afd7974", "specHash": "1698c95", "version": "0.1.0" }
{ "engineHash": "afd7974", "specHash": "63d1af0", "version": "0.1.0" }
128 changes: 128 additions & 0 deletions BoxSdkGen.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Sources/Client/BoxClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ public class BoxClient {

public let integrationMappings: IntegrationMappingsManager

public let ai: AiManager

public init(auth: Authentication, networkSession: NetworkSession = NetworkSession(baseUrls: BaseUrls())) {
self.auth = auth
self.networkSession = networkSession
Expand Down Expand Up @@ -212,6 +214,7 @@ public class BoxClient {
self.workflows = WorkflowsManager(auth: self.auth, networkSession: self.networkSession)
self.signTemplates = SignTemplatesManager(auth: self.auth, networkSession: self.networkSession)
self.integrationMappings = IntegrationMappingsManager(auth: self.auth, networkSession: self.networkSession)
self.ai = AiManager(auth: self.auth, networkSession: self.networkSession)
}

/// Create a new client to impersonate user with the provided ID. All calls made with the new client will be made in context of the impersonated user, leaving the original client unmodified.
Expand Down
39 changes: 39 additions & 0 deletions Sources/Managers/Ai/AiManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

public class AiManager {
public let auth: Authentication?

public let networkSession: NetworkSession

public init(auth: Authentication? = nil, networkSession: NetworkSession = NetworkSession()) {
self.auth = auth
self.networkSession = networkSession
}

/// Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context.
///
/// - Parameters:
/// - requestBody: Request body of createAiAsk method
/// - headers: Headers of createAiAsk method
/// - Returns: The `AiResponse`.
/// - Throws: The `GeneralError`.
public func createAiAsk(requestBody: AiAsk, headers: CreateAiAskHeaders = CreateAiAskHeaders()) async throws -> AiResponse {
let headersMap: [String: String] = Utils.Dictionary.prepareParams(map: Utils.Dictionary.merge([:], headers.extraHeaders))
let response: FetchResponse = try await NetworkClient.shared.fetch(url: "\(self.networkSession.baseUrls.baseUrl)\("/v2/ai/ask")", options: FetchOptions(method: "POST", headers: headersMap, data: try requestBody.serialize(), contentType: "application/json", responseFormat: "json", auth: self.auth, networkSession: self.networkSession))
return try AiResponse.deserialize(from: response.data)
}

/// Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text.
///
/// - Parameters:
/// - requestBody: Request body of createAiTextGen method
/// - headers: Headers of createAiTextGen method
/// - Returns: The `AiResponse`.
/// - Throws: The `GeneralError`.
public func createAiTextGen(requestBody: AiTextGen, headers: CreateAiTextGenHeaders = CreateAiTextGenHeaders()) async throws -> AiResponse {
let headersMap: [String: String] = Utils.Dictionary.prepareParams(map: Utils.Dictionary.merge([:], headers.extraHeaders))
let response: FetchResponse = try await NetworkClient.shared.fetch(url: "\(self.networkSession.baseUrls.baseUrl)\("/v2/ai/text_gen")", options: FetchOptions(method: "POST", headers: headersMap, data: try requestBody.serialize(), contentType: "application/json", responseFormat: "json", auth: self.auth, networkSession: self.networkSession))
return try AiResponse.deserialize(from: response.data)
}

}
15 changes: 15 additions & 0 deletions Sources/Managers/Ai/CreateAiAskHeaders.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

public class CreateAiAskHeaders {
/// Extra headers that will be included in the HTTP request.
public let extraHeaders: [String: String?]?

/// Initializer for a CreateAiAskHeaders.
///
/// - Parameters:
/// - extraHeaders: Extra headers that will be included in the HTTP request.
public init(extraHeaders: [String: String?]? = [:]) {
self.extraHeaders = extraHeaders
}

}
15 changes: 15 additions & 0 deletions Sources/Managers/Ai/CreateAiTextGenHeaders.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

public class CreateAiTextGenHeaders {
/// Extra headers that will be included in the HTTP request.
public let extraHeaders: [String: String?]?

/// Initializer for a CreateAiTextGenHeaders.
///
/// - Parameters:
/// - extraHeaders: Extra headers that will be included in the HTTP request.
public init(extraHeaders: [String: String?]? = [:]) {
self.extraHeaders = extraHeaders
}

}
46 changes: 46 additions & 0 deletions Sources/Schemas/AiAsk.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation

/// AI Ask request object
public class AiAsk: Codable {
private enum CodingKeys: String, CodingKey {
case mode
case prompt
case items
}

/// The mode specifies if this request is for a single or multiple items.
public let mode: AiAskModeField

/// The prompt provided by the client to be answered by the LLM.
public let prompt: String

/// The items to be processed by the LLM, often files.
public let items: [AiAskItemsField]

/// Initializer for a AiAsk.
///
/// - Parameters:
/// - mode: The mode specifies if this request is for a single or multiple items.
/// - prompt: The prompt provided by the client to be answered by the LLM.
/// - items: The items to be processed by the LLM, often files.
public init(mode: AiAskModeField, prompt: String, items: [AiAskItemsField]) {
self.mode = mode
self.prompt = prompt
self.items = items
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
mode = try container.decode(AiAskModeField.self, forKey: .mode)
prompt = try container.decode(String.self, forKey: .prompt)
items = try container.decode([AiAskItemsField].self, forKey: .items)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(mode, forKey: .mode)
try container.encode(prompt, forKey: .prompt)
try container.encode(items, forKey: .items)
}

}
45 changes: 45 additions & 0 deletions Sources/Schemas/AiAskItemsField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation

public class AiAskItemsField: Codable {
private enum CodingKeys: String, CodingKey {
case id
case type
case content
}

/// The id of the item
public let id: String

/// The type of the item
public let type: AiAskItemsTypeField

/// The content of the item, often the text representation.
public let content: String?

/// Initializer for a AiAskItemsField.
///
/// - Parameters:
/// - id: The id of the item
/// - type: The type of the item
/// - content: The content of the item, often the text representation.
public init(id: String, type: AiAskItemsTypeField = AiAskItemsTypeField.file, content: String? = nil) {
self.id = id
self.type = type
self.content = content
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
type = try container.decode(AiAskItemsTypeField.self, forKey: .type)
content = try container.decodeIfPresent(String.self, forKey: .content)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(type, forKey: .type)
try container.encodeIfPresent(content, forKey: .content)
}

}
5 changes: 5 additions & 0 deletions Sources/Schemas/AiAskItemsTypeField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public enum AiAskItemsTypeField: String, CodableStringEnum {
case file
}
6 changes: 6 additions & 0 deletions Sources/Schemas/AiAskModeField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public enum AiAskModeField: String, CodableStringEnum {
case multipleItemQa = "multiple_item_qa"
case singleItemQa = "single_item_qa"
}
46 changes: 46 additions & 0 deletions Sources/Schemas/AiResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation

/// AI response
public class AiResponse: Codable {
private enum CodingKeys: String, CodingKey {
case answer
case createdAt = "created_at"
case completionReason = "completion_reason"
}

/// The answer provided by the LLM.
public let answer: String

/// The ISO date formatted timestamp of when the answer to the prompt was created.
public let createdAt: String

/// The reason the response finishes.
public let completionReason: String?

/// Initializer for a AiResponse.
///
/// - Parameters:
/// - answer: The answer provided by the LLM.
/// - createdAt: The ISO date formatted timestamp of when the answer to the prompt was created.
/// - completionReason: The reason the response finishes.
public init(answer: String, createdAt: String, completionReason: String? = nil) {
self.answer = answer
self.createdAt = createdAt
self.completionReason = completionReason
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
answer = try container.decode(String.self, forKey: .answer)
createdAt = try container.decode(String.self, forKey: .createdAt)
completionReason = try container.decodeIfPresent(String.self, forKey: .completionReason)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(answer, forKey: .answer)
try container.encode(createdAt, forKey: .createdAt)
try container.encodeIfPresent(completionReason, forKey: .completionReason)
}

}
46 changes: 46 additions & 0 deletions Sources/Schemas/AiTextGen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation

/// AI Text Gen Request object
public class AiTextGen: Codable {
private enum CodingKeys: String, CodingKey {
case prompt
case items
case dialogueHistory = "dialogue_history"
}

/// The prompt provided by the client to be answered by the LLM.
public let prompt: String

/// The items to be processed by the LLM, often files.
public let items: [AiTextGenItemsField]

/// The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response.
public let dialogueHistory: [AiTextGenDialogueHistoryField]?

/// Initializer for a AiTextGen.
///
/// - Parameters:
/// - prompt: The prompt provided by the client to be answered by the LLM.
/// - items: The items to be processed by the LLM, often files.
/// - dialogueHistory: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response.
public init(prompt: String, items: [AiTextGenItemsField], dialogueHistory: [AiTextGenDialogueHistoryField]? = nil) {
self.prompt = prompt
self.items = items
self.dialogueHistory = dialogueHistory
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
prompt = try container.decode(String.self, forKey: .prompt)
items = try container.decode([AiTextGenItemsField].self, forKey: .items)
dialogueHistory = try container.decodeIfPresent([AiTextGenDialogueHistoryField].self, forKey: .dialogueHistory)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(prompt, forKey: .prompt)
try container.encode(items, forKey: .items)
try container.encodeIfPresent(dialogueHistory, forKey: .dialogueHistory)
}

}
45 changes: 45 additions & 0 deletions Sources/Schemas/AiTextGenDialogueHistoryField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation

public class AiTextGenDialogueHistoryField: Codable {
private enum CodingKeys: String, CodingKey {
case prompt
case answer
case createdAt = "created_at"
}

/// The prompt previously provided by the client and answered by the LLM.
public let prompt: String?

/// The answer previously provided by the LLM.
public let answer: String?

/// The ISO date formatted timestamp of when the previous answer to the prompt was created.
public let createdAt: String?

/// Initializer for a AiTextGenDialogueHistoryField.
///
/// - Parameters:
/// - prompt: The prompt previously provided by the client and answered by the LLM.
/// - answer: The answer previously provided by the LLM.
/// - createdAt: The ISO date formatted timestamp of when the previous answer to the prompt was created.
public init(prompt: String? = nil, answer: String? = nil, createdAt: String? = nil) {
self.prompt = prompt
self.answer = answer
self.createdAt = createdAt
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
prompt = try container.decodeIfPresent(String.self, forKey: .prompt)
answer = try container.decodeIfPresent(String.self, forKey: .answer)
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(prompt, forKey: .prompt)
try container.encodeIfPresent(answer, forKey: .answer)
try container.encodeIfPresent(createdAt, forKey: .createdAt)
}

}
45 changes: 45 additions & 0 deletions Sources/Schemas/AiTextGenItemsField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation

public class AiTextGenItemsField: Codable {
private enum CodingKeys: String, CodingKey {
case id
case type
case content
}

/// The id of the item.
public let id: String?

/// The type of the item.
public let type: AiTextGenItemsTypeField?

/// The content to use as context for generating new text or editing existing text.
public let content: String?

/// Initializer for a AiTextGenItemsField.
///
/// - Parameters:
/// - id: The id of the item.
/// - type: The type of the item.
/// - content: The content to use as context for generating new text or editing existing text.
public init(id: String? = nil, type: AiTextGenItemsTypeField? = nil, content: String? = nil) {
self.id = id
self.type = type
self.content = content
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id)
type = try container.decodeIfPresent(AiTextGenItemsTypeField.self, forKey: .type)
content = try container.decodeIfPresent(String.self, forKey: .content)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(id, forKey: .id)
try container.encodeIfPresent(type, forKey: .type)
try container.encodeIfPresent(content, forKey: .content)
}

}
5 changes: 5 additions & 0 deletions Sources/Schemas/AiTextGenItemsTypeField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public enum AiTextGenItemsTypeField: String, CodableStringEnum {
case file
}
Loading

0 comments on commit 175ab82

Please sign in to comment.