From 1a09887c0b6838b16603f36b8ecd9ed184c8e00e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 22 Jan 2026 17:19:24 +0100 Subject: [PATCH 1/9] WIP Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 93 ++++++++++++ .../NextcloudKit+AssistantV2.swift | 137 +++++++++++++++++- 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 Sources/NextcloudKit/Models/Assistant/v2/Chat.swift diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift new file mode 100644 index 00000000..3fe2fcfd --- /dev/null +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import SwiftyJSON + +// MARK: - ChatMessage + +public struct ChatMessage: Codable, Identifiable, Equatable { + public let id: Int + public let sessionId: Int + public let role: String + public let content: String + public let timestamp: Int + + public var isFromHuman: Bool { + role == "human" + } + + enum CodingKeys: String, CodingKey { + case id + case sessionId = "session_id" + case role + case content + case timestamp + } +} + +// MARK: - ChatMessageRequest + +public struct ChatMessageRequest: Encodable { + public let sessionId: String + public let role: String + public let content: String + public let timestamp: Int + + var bodyMap: [String: Any] { + return [ + "sessionId": sessionId, + "role": role, + "content": content, + "timestamp": timestamp + ] + } + + enum CodingKeys: String, CodingKey { + case sessionId + case role + case content + case timestamp + } +} + +// MARK: - Conversation + +public struct Conversation: Codable, Identifiable, Equatable { + public let id: Int + public let title: String? + public let timestamp: Int + + enum CodingKeys: String, CodingKey { + case id + case title + case timestamp + } +} + +// MARK: - Session + +public struct AssistantSession: Codable, Equatable { + public let id: Int + public let userId: String? + public let title: String? + public let timestamp: Int + + enum CodingKeys: String, CodingKey { + case id + case userId = "user_id" + case title + case timestamp + } +} + +// MARK: - CreateConversation + +public struct CreateConversation: Codable, Equatable { + public let session: AssistantSession + + enum CodingKeys: String, CodingKey { + case session + } +} diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 20dbead4..c3e33544 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -314,6 +314,141 @@ public extension NextcloudKit { } } } -} + /// Retrieves all chat sessions. Each session has messages. + /// + /// Parameters: + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. + func getAssistantChatSessions(account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ sessions: [AssistantSession]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/sessions" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode([AssistantSession].self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously retrieves all chat sessions. Each session has messages. + /// + /// - Parameters: + /// - account: The account performing the query. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, task list, response, and error. + func getAssistantChatSessionsAsync(account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + sessions: [AssistantSession]?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + getAssistantChatSessions( + account: account, + options: options, + taskHandler: taskHandler) { account, sessions, responseData, error in + continuation.resume(returning: ( + account: account, + sessions: sessions, + responseData: responseData, + error: error + )) + } + } + } + + /// Retrieves all messages for a given chat session. + /// + /// Parameters: + /// - sessionId: The chat session from which to fetch all messages. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. + func getAssistantChatMessages(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ chatMessages: [ChatMessage]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/messages" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + nkSession.sessionData.request(url, method: .get, parameters: ["sessionId": sessionId], encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode([ChatMessage].self, from: data) + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously retrieves all messages for a given chat session. + /// + /// - Parameters: + /// - sessionId: The chat session from which to fetch all messages. + /// - account: The account performing the query. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, sessions, response, and error. + func getAssistantChatMessagesAsync(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + chatMessage: [ChatMessage]?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + getAssistantChatMessages(sessionId: sessionId, + account: account, + options: options, + taskHandler: taskHandler) { account, chatMessage, responseData, error in + continuation.resume(returning: ( + account: account, + chatMessage: chatMessage, + responseData: responseData, + error: error + )) + } + } + } + +} From dc83f459a77b51f6e257a8702053ceb520dbbd4e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 23 Jan 2026 15:26:03 +0100 Subject: [PATCH 2/9] Send message Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 9 ++- .../NextcloudKit+AssistantV2.swift | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index 3fe2fcfd..7d972344 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -30,11 +30,18 @@ public struct ChatMessage: Codable, Identifiable, Equatable { // MARK: - ChatMessageRequest public struct ChatMessageRequest: Encodable { - public let sessionId: String + public let sessionId: Int public let role: String public let content: String public let timestamp: Int + public init(sessionId: Int, role: String, content: String, timestamp: Int) { + self.sessionId = sessionId + self.role = role + self.content = content + self.timestamp = timestamp + } + var bodyMap: [String: Any] { return [ "sessionId": sessionId, diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index c3e33544..77f9223e 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -451,4 +451,74 @@ public extension NextcloudKit { } } + /// Creates a new message in a chat session. + /// + /// Parameters: + /// - messageRequest: The message request containing sessionId, role, content, and timestamp. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, created message, raw response, and NKError. + func createAssistantChatMessage(messageRequest: ChatMessageRequest, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ chatMessage: ChatMessage?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/new_message" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + nkSession.sessionData.request(url, method: .put, parameters: messageRequest.bodyMap, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(ChatMessage.self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously creates a new message in a chat session. + /// + /// - Parameters: + /// - messageRequest: The message request containing sessionId, role, content, and timestamp. + /// - account: The account performing the request. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, created message, response, and error. + func createAssistantChatMessageAsync(messageRequest: ChatMessageRequest, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + chatMessage: ChatMessage?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + createAssistantChatMessage(messageRequest: messageRequest, + account: account, + options: options, + taskHandler: taskHandler) { account, chatMessage, responseData, error in + continuation.resume(returning: ( + account: account, + chatMessage: chatMessage, + responseData: responseData, + error: error + )) + } + } + } + } From 25e03358d27f65bd47552f737b2f3a004d9991f3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 29 Jan 2026 18:44:03 +0100 Subject: [PATCH 3/9] WIP Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 15 +- .../NextcloudKit+AssistantV2.swift | 173 +++++++++++++++++- 2 files changed, 179 insertions(+), 9 deletions(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index 7d972344..1f4a0da9 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -78,7 +78,7 @@ public struct Conversation: Codable, Identifiable, Equatable { public struct AssistantSession: Codable, Equatable { public let id: Int public let userId: String? - public let title: String? + private let title: String? public let timestamp: Int enum CodingKeys: String, CodingKey { @@ -87,6 +87,19 @@ public struct AssistantSession: Codable, Equatable { case title case timestamp } + + public var validTitle: String { + return title ?? createTitle() + + func createTitle() -> String { + let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) + let formatter = DateFormatter() + formatter.locale = .current + formatter.timeZone = .current + formatter.dateFormat = "MMM d yyyy, HH:mm" + return formatter.string(from: date) + } + } } // MARK: - CreateConversation diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 77f9223e..21b966b0 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -322,10 +322,10 @@ public extension NextcloudKit { /// - options: Optional HTTP request configuration. /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. - func getAssistantChatSessions(account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ sessions: [AssistantSession]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + func getAssistantChatConversations(account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ sessions: [AssistantSession]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/apps/assistant/chat/sessions" guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), @@ -357,9 +357,9 @@ public extension NextcloudKit { /// - options: Optional configuration. /// - taskHandler: Callback to access the associated URLSessionTask. /// - Returns: A tuple with named values for account, task list, response, and error. - func getAssistantChatSessionsAsync(account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func getAssistantChatConversationsAsync(account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, sessions: [AssistantSession]?, @@ -367,7 +367,7 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - getAssistantChatSessions( + getAssistantChatConversations( account: account, options: options, taskHandler: taskHandler) { account, sessions, responseData, error in @@ -521,4 +521,161 @@ public extension NextcloudKit { } } + /// Creates a new chat conversation/session. + /// + /// Parameters: + /// - title: Optional title for the conversation. + /// - timestamp: The timestamp for the conversation creation. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, created conversation, raw response, and NKError. + func createAssistantChatConversation(title: String?, + timestamp: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ conversation: CreateConversation?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/new_session" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + var parameters: [String: Any] = ["timestamp": timestamp] + if let title = title { + parameters["title"] = title + } + + nkSession.sessionData.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(CreateConversation.self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously creates a new chat conversation/session. + /// + /// - Parameters: + /// - title: Optional title for the conversation. + /// - timestamp: The timestamp for the conversation creation. + /// - account: The account performing the request. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, created conversation, response, and error. + func createAssistantChatConversationAsync(title: String?, + timestamp: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + conversation: CreateConversation?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + createAssistantChatConversation(title: title, + timestamp: timestamp, + account: account, + options: options, + taskHandler: taskHandler) { account, conversation, responseData, error in + continuation.resume(returning: ( + account: account, + conversation: conversation, + responseData: responseData, + error: error + )) + } + } + } + + /// Checks the generation status of a chat message task. + /// + /// Parameters: + /// - taskId: The ID of the generation task to check. + /// - sessionId: The chat session ID. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, chat message (if ready), raw response, and NKError. + func checkAssistantChatGeneration(taskId: String, + sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ chatMessage: ChatMessage?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/check_generation" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + let parameters: [String: Any] = ["taskId": taskId, "sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(ChatMessage.self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously checks the generation status of a chat message task. + /// + /// - Parameters: + /// - taskId: The ID of the generation task to check. + /// - sessionId: The chat session ID. + /// - account: The account performing the request. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, chat message (if ready), response, and error. + func checkAssistantChatGenerationAsync(taskId: String, + sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + chatMessage: ChatMessage?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + checkAssistantChatGeneration(taskId: taskId, + sessionId: sessionId, + account: account, + options: options, + taskHandler: taskHandler) { account, chatMessage, responseData, error in + continuation.resume(returning: ( + account: account, + chatMessage: chatMessage, + responseData: responseData, + error: error + )) + } + } + } + } From 7f7f5f69f04793b94adb68d0eecb92129c49eb59 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 29 Jan 2026 19:03:17 +0100 Subject: [PATCH 4/9] WIP Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 10 +++ .../NextcloudKit+AssistantV2.swift | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index 1f4a0da9..f58568ce 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -111,3 +111,13 @@ public struct CreateConversation: Codable, Equatable { case session } } + +// MARK: - SessionTask + +public struct SessionTask: Codable, Equatable { + public let taskId: String + + enum CodingKeys: String, CodingKey { + case taskId = "task_id" + } +} diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 21b966b0..7e8e8673 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -678,4 +678,76 @@ public extension NextcloudKit { } } + /// Triggers generation for a chat session. + /// + /// Parameters: + /// - sessionId: The chat session ID to generate for. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, session task, raw response, and NKError. + func generateAssistantChatSession(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ sessionTask: SessionTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/generate" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + let parameters: [String: Any] = ["sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(SessionTask.self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously triggers generation for a chat session. + /// + /// - Parameters: + /// - sessionId: The chat session ID to generate for. + /// - account: The account performing the request. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, session task, response, and error. + func generateAssistantChatSessionAsync(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + sessionTask: SessionTask?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + generateAssistantChatSession(sessionId: sessionId, + account: account, + options: options, + taskHandler: taskHandler) { account, sessionTask, responseData, error in + continuation.resume(returning: ( + account: account, + sessionTask: sessionTask, + responseData: responseData, + error: error + )) + } + } + } + } From 4045ea593822a8fed876ca47ee412ba30a2acd0c Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 30 Jan 2026 14:20:07 +0100 Subject: [PATCH 5/9] Use native decodable instead of SwiftyJson Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 4 +- .../Models/Assistant/v2/TaskList.swift | 72 ++++++++----------- .../Models/Assistant/v2/TaskTypes.swift | 56 ++++++--------- .../NextcloudKit+AssistantV2.swift | 70 +++++++----------- 4 files changed, 80 insertions(+), 122 deletions(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index f58568ce..c3c9eede 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -115,9 +115,9 @@ public struct CreateConversation: Codable, Equatable { // MARK: - SessionTask public struct SessionTask: Codable, Equatable { - public let taskId: String + public let taskId: Int enum CodingKeys: String, CodingKey { - case taskId = "task_id" + case taskId } } diff --git a/Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift b/Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift index b17978a8..a988026b 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift @@ -2,30 +2,41 @@ // SPDX-FileCopyrightText: 2025 Milen Pivchev // SPDX-License-Identifier: GPL-3.0-or-later -import SwiftyJSON +import Foundation + +// MARK: - OCS Response Wrappers + +public struct OCSTaskListResponse: Codable { + public let ocs: OCSTaskListOCS + + public struct OCSTaskListOCS: Codable { + public let data: OCSTaskListData + } + + public struct OCSTaskListData: Codable { + public let tasks: [AssistantTask] + } +} + +public struct OCSTaskResponse: Codable { + public let ocs: OCSTaskOCS + + public struct OCSTaskOCS: Codable { + public let data: OCSTaskData + } + + public struct OCSTaskData: Codable { + public let task: AssistantTask + } +} + +// MARK: - Task Models public struct TaskList: Codable { public var tasks: [AssistantTask] - static func deserialize(from data: JSON) -> TaskList? { - let tasks = data.arrayValue.map { taskJson in - AssistantTask( - id: taskJson["id"].int64Value, - type: taskJson["type"].string, - status: taskJson["status"].string, - userId: taskJson["userId"].string, - appId: taskJson["appId"].string, - input: TaskInput(input: taskJson["input"]["input"].string), - output: TaskOutput(output: taskJson["output"]["output"].string), - completionExpectedAt: taskJson["completionExpectedAt"].int, - progress: taskJson["progress"].int, - lastUpdated: taskJson["lastUpdated"].int, - scheduledAt: taskJson["scheduledAt"].int, - endedAt: taskJson["endedAt"].int - ) - } - - return TaskList(tasks: tasks) + public init(tasks: [AssistantTask]) { + self.tasks = tasks } } @@ -57,25 +68,6 @@ public struct AssistantTask: Codable { self.scheduledAt = scheduledAt self.endedAt = endedAt } - - static func deserialize(from data: JSON) -> AssistantTask? { - let task = AssistantTask( - id: data["id"].int64Value, - type: data["type"].string, - status: data["status"].string, - userId: data["userId"].string, - appId: data["appId"].string, - input: TaskInput(input: data["input"]["input"].string), - output: TaskOutput(output: data["output"]["output"].string), - completionExpectedAt: data["completionExpectedAt"].int, - progress: data["progress"].int, - lastUpdated: data["lastUpdated"].int, - scheduledAt: data["scheduledAt"].int, - endedAt: data["endedAt"].int - ) - - return task - } } public struct TaskInput: Codable { @@ -93,5 +85,3 @@ public struct TaskOutput: Codable { self.output = output } } - - diff --git a/Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift b/Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift index 70b502a7..b9201f55 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift @@ -2,44 +2,34 @@ // SPDX-FileCopyrightText: 2025 Milen Pivchev // SPDX-License-Identifier: GPL-3.0-or-later -import SwiftyJSON +import Foundation + +// MARK: - OCS Response Wrapper + +public struct OCSTaskTypesResponse: Codable { + public let ocs: OCSTaskTypesOCS + + public struct OCSTaskTypesOCS: Codable { + public let data: OCSTaskTypesData + } + + public struct OCSTaskTypesData: Codable { + public let types: [String: TaskTypeData] + } +} + +// MARK: - Task Type Models public struct TaskTypes: Codable { public let types: [TaskTypeData] - static func deserialize(from data: JSON) -> TaskTypes? { - var taskTypes: [TaskTypeData] = [] - - for (key, subJson) in data { - let taskTypeData = TaskTypeData( - id: key, - name: subJson["name"].string, - description: subJson["description"].string, - inputShape: subJson["inputShape"].dictionary != nil ? TaskInputShape( - input: subJson["inputShape"]["input"].dictionary != nil ? Shape( - name: subJson["inputShape"]["input"]["name"].stringValue, - description: subJson["inputShape"]["input"]["description"].stringValue, - type: subJson["inputShape"]["input"]["type"].stringValue - ) : nil - ) : nil, - outputShape: subJson["outputShape"].dictionary != nil ? TaskOutputShape( - output: subJson["outputShape"]["output"].dictionary != nil ? Shape( - name: subJson["outputShape"]["output"]["name"].stringValue, - description: subJson["outputShape"]["output"]["description"].stringValue, - type: subJson["outputShape"]["output"]["type"].stringValue - ) : nil - ) : nil - ) - - taskTypes.append(taskTypeData) - } - - return TaskTypes(types: taskTypes) + public init(types: [TaskTypeData]) { + self.types = types } } public struct TaskTypeData: Codable { - public let id: String? + public var id: String? public let name: String? public let description: String? public let inputShape: TaskInputShape? @@ -81,9 +71,3 @@ public struct Shape: Codable { self.type = type } } - - - - - - diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 7e8e8673..45e99b14 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -4,7 +4,6 @@ import Foundation import Alamofire -import SwiftyJSON public extension NextcloudKit { /// Retrieves the list of supported task types for a specific account and task category. @@ -36,19 +35,20 @@ public extension NextcloudKit { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) options.queue.async { completion(account, nil, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let data = json["ocs"]["data"]["types"] - let statusCode = json["ocs"]["meta"]["statuscode"].int ?? NKError.internalError - if 200..<300 ~= statusCode { - let dict = TaskTypes.deserialize(from: data) - let result = dict?.types.map({$0}) - let filteredResult = result? - .filter({ $0.inputShape?.input?.type == supportedTaskType && $0.outputShape?.output?.type == supportedTaskType }) - .sorted(by: {$0.id! < $1.id!}) - options.queue.async { completion(account, filteredResult, response, .success) } + case .success(let data): + let decoder = JSONDecoder() + if let result = try? decoder.decode(OCSTaskTypesResponse.self, from: data) { + var types = result.ocs.data.types.map { (key, value) -> TaskTypeData in + var taskType = value + taskType.id = key + return taskType + } + types = types + .filter { $0.inputShape?.input?.type == supportedTaskType && $0.outputShape?.output?.type == supportedTaskType } + .sorted { ($0.id ?? "") < ($1.id ?? "") } + options.queue.async { completion(account, types, response, .success) } } else { - options.queue.async { completion(account, nil, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode)) } + options.queue.async { completion(account, nil, response, .success) } } } } @@ -120,16 +120,11 @@ public extension NextcloudKit { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) options.queue.async { completion(account, nil, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let data = json["ocs"]["data"]["task"] - let statusCode = json["ocs"]["meta"]["statuscode"].int ?? NKError.internalError - if 200..<300 ~= statusCode { - let result = AssistantTask.deserialize(from: data) - options.queue.async { completion(account, result, response, .success) } - } else { - options.queue.async { completion(account, nil, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode)) } - } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(OCSTaskResponse.self, from: data) + + options.queue.async { completion(account, result?.ocs.data.task, response, .success) } } } } @@ -198,16 +193,11 @@ public extension NextcloudKit { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) options.queue.async { completion(account, nil, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let data = json["ocs"]["data"]["tasks"] - let statusCode = json["ocs"]["meta"]["statuscode"].int ?? NKError.internalError - if 200..<300 ~= statusCode { - let result = TaskList.deserialize(from: data) - options.queue.async { completion(account, result, response, .success) } - } else { - options.queue.async { completion(account, nil, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode)) } - } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(OCSTaskListResponse.self, from: data) + + options.queue.async { completion(account, result.map { TaskList(tasks: $0.ocs.data.tasks) }, response, .success) } } } } @@ -273,14 +263,8 @@ public extension NextcloudKit { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) options.queue.async { completion(account, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let statusCode = json["ocs"]["meta"]["statuscode"].int ?? NKError.internalError - if 200..<300 ~= statusCode { - options.queue.async { completion(account, response, .success) } - } else { - options.queue.async { completion(account, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode)) } - } + case .success: + options.queue.async { completion(account, response, .success) } } } } @@ -610,7 +594,7 @@ public extension NextcloudKit { /// - options: Optional HTTP request configuration. /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - completion: Completion handler returning the account, chat message (if ready), raw response, and NKError. - func checkAssistantChatGeneration(taskId: String, + func checkAssistantChatGeneration(taskId: Int, sessionId: Int, account: String, options: NKRequestOptions = NKRequestOptions(), @@ -651,7 +635,7 @@ public extension NextcloudKit { /// - options: Optional configuration. /// - taskHandler: Callback to access the associated URLSessionTask. /// - Returns: A tuple with named values for account, chat message (if ready), response, and error. - func checkAssistantChatGenerationAsync(taskId: String, + func checkAssistantChatGenerationAsync(taskId: Int, sessionId: Int, account: String, options: NKRequestOptions = NKRequestOptions(), From e8d30aceaa4d702ec22f04f5bc6263d123ea07b6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 9 Feb 2026 11:52:21 +0100 Subject: [PATCH 6/9] WIP Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 20 +++-- .../NextcloudKit+AssistantV2.swift | 78 ++++++++++++++++++- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index c3c9eede..e42778ac 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import SwiftyJSON // MARK: - ChatMessage @@ -18,6 +17,14 @@ public struct ChatMessage: Codable, Identifiable, Equatable { role == "human" } + public init(id: Int, sessionId: Int, role: String, content: String, timestamp: Int) { + self.id = id + self.sessionId = sessionId + self.role = role + self.content = content + self.timestamp = timestamp + } + enum CodingKeys: String, CodingKey { case id case sessionId = "session_id" @@ -34,12 +41,14 @@ public struct ChatMessageRequest: Encodable { public let role: String public let content: String public let timestamp: Int + public let firstHumanMessage: Bool - public init(sessionId: Int, role: String, content: String, timestamp: Int) { + public init(sessionId: Int, role: String, content: String, timestamp: Int, firstHumanMessage: Bool) { self.sessionId = sessionId self.role = role self.content = content self.timestamp = timestamp + self.firstHumanMessage = firstHumanMessage } var bodyMap: [String: Any] { @@ -56,6 +65,7 @@ public struct ChatMessageRequest: Encodable { case role case content case timestamp + case firstHumanMessage } } @@ -75,7 +85,7 @@ public struct Conversation: Codable, Identifiable, Equatable { // MARK: - Session -public struct AssistantSession: Codable, Equatable { +public struct AssistantConversation: Codable, Equatable, Hashable { public let id: Int public let userId: String? private let title: String? @@ -105,10 +115,10 @@ public struct AssistantSession: Codable, Equatable { // MARK: - CreateConversation public struct CreateConversation: Codable, Equatable { - public let session: AssistantSession + public let conversation: AssistantConversation enum CodingKeys: String, CodingKey { - case session + case conversation = "session" } } diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 45e99b14..527a844f 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -309,7 +309,7 @@ public extension NextcloudKit { func getAssistantChatConversations(account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ sessions: [AssistantSession]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + completion: @escaping (_ account: String, _ sessions: [AssistantConversation]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/apps/assistant/chat/sessions" guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), @@ -327,7 +327,7 @@ public extension NextcloudKit { options.queue.async { completion(account, nil, response, error) } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode([AssistantSession].self, from: data) + let result = try? decoder.decode([AssistantConversation].self, from: data) options.queue.async { completion(account, result, response, .success) } } @@ -346,7 +346,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - sessions: [AssistantSession]?, + sessions: [AssistantConversation]?, responseData: AFDataResponse?, error: NKError ) { @@ -734,4 +734,76 @@ public extension NextcloudKit { } } + /// Checks if a chat session exists and retrieves its details. + /// + /// Parameters: + /// - sessionId: The ID of the chat session to check. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - completion: Completion handler returning the account, session (if found), raw response, and NKError. + func checkAssistantChatSession(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, + completion: @escaping (_ account: String, _ session: AssistantConversation?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + let endpoint = "/ocs/v2.php/apps/assistant/chat/check_session" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { completion(account, nil, nil, .urlError) } + } + + let parameters: [String: Any] = ["sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(AssistantConversation.self, from: data) + + options.queue.async { completion(account, result, response, .success) } + } + } + } + + /// Asynchronously checks if a chat session exists and retrieves its details. + /// + /// - Parameters: + /// - sessionId: The ID of the chat session to check. + /// - account: The account performing the request. + /// - options: Optional configuration. + /// - taskHandler: Callback to access the associated URLSessionTask. + /// - Returns: A tuple with named values for account, session (if found), response, and error. + func checkAssistantChatSessionAsync(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + ) async -> ( + account: String, + session: AssistantConversation?, + responseData: AFDataResponse?, + error: NKError + ) { + await withCheckedContinuation { continuation in + checkAssistantChatSession(sessionId: sessionId, + account: account, + options: options, + taskHandler: taskHandler) { account, session, responseData, error in + continuation.resume(returning: ( + account: account, + session: session, + responseData: responseData, + error: error + )) + } + } + } + } From 08eefb83e539cd2a5c2e5734ddb9a3b2ad10664f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 9 Feb 2026 12:02:17 +0100 Subject: [PATCH 7/9] WIP Signed-off-by: Milen Pivchev --- .../NextcloudKit+AssistantV2.swift | 1001 +++++++---------- 1 file changed, 386 insertions(+), 615 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 527a844f..73478152 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -9,62 +9,16 @@ public extension NextcloudKit { /// Retrieves the list of supported task types for a specific account and task category. /// Typically used to discover available AI or text processing capabilities. /// - /// Parameters: - /// - account: The Nextcloud account making the request. - /// - supportedTaskType: Type of tasks to retrieve, default is "Text". - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the URLSessionTask. - /// - completion: Completion handler returning the account, list of supported types, raw response, and NKError. - func textProcessingGetTypesV2(account: String, - supportedTaskType: String = "Text", - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ types: [TaskTypeData]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "ocs/v2.php/taskprocessing/tasktypes" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - if let result = try? decoder.decode(OCSTaskTypesResponse.self, from: data) { - var types = result.ocs.data.types.map { (key, value) -> TaskTypeData in - var taskType = value - taskType.id = key - return taskType - } - types = types - .filter { $0.inputShape?.input?.type == supportedTaskType && $0.outputShape?.output?.type == supportedTaskType } - .sorted { ($0.id ?? "") < ($1.id ?? "") } - options.queue.async { completion(account, types, response, .success) } - } else { - options.queue.async { completion(account, nil, response, .success) } - } - } - } - } - - /// Asynchronously retrieves the supported task types for the given account and category. /// - Parameters: - /// - account: Account performing the request. - /// - supportedTaskType: The task category to filter by (default: "Text"). - /// - options: Optional configuration. - /// - taskHandler: Callback for the underlying URLSessionTask. + /// - account: The Nextcloud account making the request. + /// - supportedTaskType: Type of tasks to retrieve, default is "Text". + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the URLSessionTask. /// - Returns: A tuple with named values for account, supported types, response, and error. - func textProcessingGetTypesV2Async(account: String, - supportedTaskType: String = "Text", - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func textProcessingGetTypesV2(account: String, + supportedTaskType: String = "Text", + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, types: [TaskTypeData]?, @@ -72,16 +26,45 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - textProcessingGetTypesV2(account: account, - supportedTaskType: supportedTaskType, - options: options, - taskHandler: taskHandler) { account, types, responseData, error in - continuation.resume(returning: ( - account: account, - types: types, - responseData: responseData, - error: error - )) + let endpoint = "ocs/v2.php/taskprocessing/tasktypes" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, types: nil, responseData: nil, error: .urlError)) + } + } + + nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, types: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + if let result = try? decoder.decode(OCSTaskTypesResponse.self, from: data) { + var types = result.ocs.data.types.map { (key, value) -> TaskTypeData in + var taskType = value + taskType.id = key + return taskType + } + types = types + .filter { $0.inputShape?.input?.type == supportedTaskType && $0.outputShape?.output?.type == supportedTaskType } + .sorted { ($0.id ?? "") < ($1.id ?? "") } + options.queue.async { + continuation.resume(returning: (account: account, types: types, responseData: response, error: .success)) + } + } else { + options.queue.async { + continuation.resume(returning: (account: account, types: nil, responseData: response, error: .success)) + } + } + } } } } @@ -89,59 +72,18 @@ public extension NextcloudKit { /// Schedules a new text processing task for a specific account and task type. /// Useful for initiating assistant-based text analysis, generation, or transformation. /// - /// Parameters: - /// - input: The input text to be processed. - /// - taskType: The specific task type to execute (e.g., summarization, sentiment analysis). - /// - account: The Nextcloud account initiating the task. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, scheduled task, raw response, and NKError. - func textProcessingScheduleV2(input: String, - taskType: TaskTypeData, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ task: AssistantTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/taskprocessing/schedule" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - let inputField: [String: String] = ["input": input] - let parameters: [String: Any] = ["input": inputField, "type": taskType.id ?? "", "appId": "assistant", "customId": ""] - - nkSession.sessionData.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(OCSTaskResponse.self, from: data) - - options.queue.async { completion(account, result?.ocs.data.task, response, .success) } - } - } - } - - /// Asynchronously schedules a new text processing task using the specified task type. /// - Parameters: - /// - input: Input text to be processed. - /// - taskType: Type of task to be executed. - /// - account: The account performing the scheduling. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - input: The input text to be processed. + /// - taskType: The specific task type to execute (e.g., summarization, sentiment analysis). + /// - account: The Nextcloud account initiating the task. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, scheduled task, response, and error. - func textProcessingScheduleV2Async(input: String, - taskType: TaskTypeData, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func textProcessingScheduleV2(input: String, + taskType: TaskTypeData, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, task: AssistantTask?, @@ -149,17 +91,35 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - textProcessingScheduleV2(input: input, - taskType: taskType, - account: account, - options: options, - taskHandler: taskHandler) { account, task, responseData, error in - continuation.resume(returning: ( - account: account, - task: task, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/taskprocessing/schedule" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, task: nil, responseData: nil, error: .urlError)) + } + } + + let inputField: [String: String] = ["input": input] + let parameters: [String: Any] = ["input": inputField, "type": taskType.id ?? "", "appId": "assistant", "customId": ""] + + nkSession.sessionData.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, task: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(OCSTaskResponse.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, task: result?.ocs.data.task, responseData: response, error: .success)) + } + } } } } @@ -167,52 +127,16 @@ public extension NextcloudKit { /// Retrieves all scheduled text processing tasks of a specific type for the given account. /// Useful for listing and tracking tasks like summarization, transcription, or classification. /// - /// Parameters: - /// - taskType: Identifier of the task type to filter tasks (e.g., "Text"). - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. - func textProcessingGetTasksV2(taskType: String, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ tasks: TaskList?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/taskprocessing/tasks?taskType=\(taskType)" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(OCSTaskListResponse.self, from: data) - - options.queue.async { completion(account, result.map { TaskList(tasks: $0.ocs.data.tasks) }, response, .success) } - } - } - } - - /// Asynchronously retrieves a list of scheduled text processing tasks for a specific type. /// - Parameters: - /// - taskType: Type of the tasks to query. - /// - account: The account performing the query. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - taskType: Identifier of the task type to filter tasks (e.g., "Text"). + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, task list, response, and error. - func textProcessingGetTasksV2Async(taskType: String, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func textProcessingGetTasksV2(taskType: String, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, tasks: TaskList?, @@ -220,16 +144,32 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - textProcessingGetTasksV2(taskType: taskType, - account: account, - options: options, - taskHandler: taskHandler) { account, tasks, responseData, error in - continuation.resume(returning: ( - account: account, - tasks: tasks, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/taskprocessing/tasks?taskType=\(taskType)" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, tasks: nil, responseData: nil, error: .urlError)) + } + } + + nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, tasks: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(OCSTaskListResponse.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, tasks: result.map { TaskList(tasks: $0.ocs.data.tasks) }, responseData: response, error: .success)) + } + } } } } @@ -237,113 +177,60 @@ public extension NextcloudKit { /// Deletes a scheduled text processing task with a specific identifier. /// Useful for canceling tasks that are no longer needed or invalid. /// - /// Parameters: - /// - taskId: The unique identifier of the task to delete. - /// - account: The Nextcloud account executing the deletion. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, raw response, and NKError. - func textProcessingDeleteTaskV2(taskId: Int64, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/taskprocessing/task/\(taskId)" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .delete, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, response, error) } - case .success: - options.queue.async { completion(account, response, .success) } - } - } - } - - /// Asynchronously deletes a text processing task by ID for the specified account. /// - Parameters: - /// - taskId: ID of the task to be deleted. - /// - account: The account performing the operation. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - taskId: The unique identifier of the task to delete. + /// - account: The Nextcloud account executing the deletion. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, response, and error. - func textProcessingDeleteTaskV2Async(taskId: Int64, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func textProcessingDeleteTaskV2(taskId: Int64, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, responseData: AFDataResponse?, error: NKError ) { await withCheckedContinuation { continuation in - textProcessingDeleteTaskV2(taskId: taskId, - account: account, - options: options, - taskHandler: taskHandler) { account, responseData, error in - continuation.resume(returning: ( - account: account, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/taskprocessing/task/\(taskId)" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, responseData: nil, error: .urlError)) + } } - } - } - - /// Retrieves all chat sessions. Each session has messages. - /// - /// Parameters: - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. - func getAssistantChatConversations(account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ sessions: [AssistantConversation]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/sessions" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode([AssistantConversation].self, from: data) - options.queue.async { completion(account, result, response, .success) } + nkSession.sessionData.request(url, method: .delete, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, responseData: response, error: error)) + } + case .success: + options.queue.async { + continuation.resume(returning: (account: account, responseData: response, error: .success)) + } + } } } } - /// Asynchronously retrieves all chat sessions. Each session has messages. + /// Retrieves all chat sessions. Each session has messages. /// /// - Parameters: - /// - account: The account performing the query. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. - /// - Returns: A tuple with named values for account, task list, response, and error. - func getAssistantChatConversationsAsync(account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - Returns: A tuple with named values for account, sessions, response, and error. + func getAssistantChatConversations(account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, sessions: [AssistantConversation]?, @@ -351,139 +238,97 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - getAssistantChatConversations( - account: account, - options: options, - taskHandler: taskHandler) { account, sessions, responseData, error in - continuation.resume(returning: ( - account: account, - sessions: sessions, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/sessions" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, sessions: nil, responseData: nil, error: .urlError)) } - } - } - - /// Retrieves all messages for a given chat session. - /// - /// Parameters: - /// - sessionId: The chat session from which to fetch all messages. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, list of tasks, raw response, and NKError. - func getAssistantChatMessages(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ chatMessages: [ChatMessage]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/messages" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .get, parameters: ["sessionId": sessionId], encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode([ChatMessage].self, from: data) + } - options.queue.async { completion(account, result, response, .success) } + nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, sessions: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode([AssistantConversation].self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, sessions: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously retrieves all messages for a given chat session. + /// Retrieves all messages for a given chat session. /// /// - Parameters: /// - sessionId: The chat session from which to fetch all messages. - /// - account: The account performing the query. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. - /// - Returns: A tuple with named values for account, sessions, response, and error. - func getAssistantChatMessagesAsync(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. + /// - Returns: A tuple with named values for account, chat messages, response, and error. + func getAssistantChatMessages(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - chatMessage: [ChatMessage]?, + chatMessages: [ChatMessage]?, responseData: AFDataResponse?, error: NKError ) { await withCheckedContinuation { continuation in - getAssistantChatMessages(sessionId: sessionId, - account: account, - options: options, - taskHandler: taskHandler) { account, chatMessage, responseData, error in - continuation.resume(returning: ( - account: account, - chatMessage: chatMessage, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/messages" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, chatMessages: nil, responseData: nil, error: .urlError)) + } } - } - } - - /// Creates a new message in a chat session. - /// - /// Parameters: - /// - messageRequest: The message request containing sessionId, role, content, and timestamp. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, created message, raw response, and NKError. - func createAssistantChatMessage(messageRequest: ChatMessageRequest, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ chatMessage: ChatMessage?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/new_message" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - nkSession.sessionData.request(url, method: .put, parameters: messageRequest.bodyMap, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(ChatMessage.self, from: data) - options.queue.async { completion(account, result, response, .success) } + nkSession.sessionData.request(url, method: .get, parameters: ["sessionId": sessionId], encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessages: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode([ChatMessage].self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessages: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously creates a new message in a chat session. + /// Creates a new message in a chat session. /// /// - Parameters: /// - messageRequest: The message request containing sessionId, role, content, and timestamp. - /// - account: The account performing the request. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, created message, response, and error. - func createAssistantChatMessageAsync(messageRequest: ChatMessageRequest, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func createAssistantChatMessage(messageRequest: ChatMessageRequest, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, chatMessage: ChatMessage?, @@ -491,74 +336,46 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - createAssistantChatMessage(messageRequest: messageRequest, - account: account, - options: options, - taskHandler: taskHandler) { account, chatMessage, responseData, error in - continuation.resume(returning: ( - account: account, - chatMessage: chatMessage, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/new_message" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, chatMessage: nil, responseData: nil, error: .urlError)) + } } - } - } - /// Creates a new chat conversation/session. - /// - /// Parameters: - /// - title: Optional title for the conversation. - /// - timestamp: The timestamp for the conversation creation. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, created conversation, raw response, and NKError. - func createAssistantChatConversation(title: String?, - timestamp: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ conversation: CreateConversation?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/new_session" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - var parameters: [String: Any] = ["timestamp": timestamp] - if let title = title { - parameters["title"] = title - } - - nkSession.sessionData.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(CreateConversation.self, from: data) - - options.queue.async { completion(account, result, response, .success) } + nkSession.sessionData.request(url, method: .put, parameters: messageRequest.bodyMap, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessage: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(ChatMessage.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously creates a new chat conversation/session. + /// Creates a new chat conversation/session. /// /// - Parameters: /// - title: Optional title for the conversation. /// - timestamp: The timestamp for the conversation creation. - /// - account: The account performing the request. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, created conversation, response, and error. - func createAssistantChatConversationAsync(title: String?, + func createAssistantChatConversation(title: String?, timestamp: Int, account: String, options: NKRequestOptions = NKRequestOptions(), @@ -570,76 +387,55 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - createAssistantChatConversation(title: title, - timestamp: timestamp, - account: account, - options: options, - taskHandler: taskHandler) { account, conversation, responseData, error in - continuation.resume(returning: ( - account: account, - conversation: conversation, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/new_session" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, conversation: nil, responseData: nil, error: .urlError)) + } } - } - } - - /// Checks the generation status of a chat message task. - /// - /// Parameters: - /// - taskId: The ID of the generation task to check. - /// - sessionId: The chat session ID. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, chat message (if ready), raw response, and NKError. - func checkAssistantChatGeneration(taskId: Int, - sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ chatMessage: ChatMessage?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/check_generation" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - let parameters: [String: Any] = ["taskId": taskId, "sessionId": sessionId] - nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(ChatMessage.self, from: data) + var parameters: [String: Any] = ["timestamp": timestamp] + if let title = title { + parameters["title"] = title + } - options.queue.async { completion(account, result, response, .success) } + nkSession.sessionData.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, conversation: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(CreateConversation.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, conversation: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously checks the generation status of a chat message task. + /// Checks the generation status of a chat message task. /// /// - Parameters: /// - taskId: The ID of the generation task to check. /// - sessionId: The chat session ID. - /// - account: The account performing the request. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, chat message (if ready), response, and error. - func checkAssistantChatGenerationAsync(taskId: Int, - sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func checkAssistantChatGeneration(taskId: Int, + sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, chatMessage: ChatMessage?, @@ -647,72 +443,50 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - checkAssistantChatGeneration(taskId: taskId, - sessionId: sessionId, - account: account, - options: options, - taskHandler: taskHandler) { account, chatMessage, responseData, error in - continuation.resume(returning: ( - account: account, - chatMessage: chatMessage, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/check_generation" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, chatMessage: nil, responseData: nil, error: .urlError)) + } } - } - } - /// Triggers generation for a chat session. - /// - /// Parameters: - /// - sessionId: The chat session ID to generate for. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, session task, raw response, and NKError. - func generateAssistantChatSession(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ sessionTask: SessionTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/generate" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - let parameters: [String: Any] = ["sessionId": sessionId] - - nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(SessionTask.self, from: data) - - options.queue.async { completion(account, result, response, .success) } + let parameters: [String: Any] = ["taskId": taskId, "sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessage: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(ChatMessage.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously triggers generation for a chat session. + /// Triggers generation for a chat session. /// /// - Parameters: /// - sessionId: The chat session ID to generate for. - /// - account: The account performing the request. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, session task, response, and error. - func generateAssistantChatSessionAsync(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func generateAssistantChatSession(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, sessionTask: SessionTask?, @@ -720,71 +494,50 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - generateAssistantChatSession(sessionId: sessionId, - account: account, - options: options, - taskHandler: taskHandler) { account, sessionTask, responseData, error in - continuation.resume(returning: ( - account: account, - sessionTask: sessionTask, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/generate" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, sessionTask: nil, responseData: nil, error: .urlError)) + } } - } - } - - /// Checks if a chat session exists and retrieves its details. - /// - /// Parameters: - /// - sessionId: The ID of the chat session to check. - /// - account: The Nextcloud account performing the request. - /// - options: Optional HTTP request configuration. - /// - taskHandler: Optional closure to access the underlying URLSessionTask. - /// - completion: Completion handler returning the account, session (if found), raw response, and NKError. - func checkAssistantChatSession(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ session: AssistantConversation?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - let endpoint = "/ocs/v2.php/apps/assistant/chat/check_session" - guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), - let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), - let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, nil, nil, .urlError) } - } - - let parameters: [String: Any] = ["sessionId": sessionId] - - nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in - task.taskDescription = options.taskDescription - taskHandler(task) - }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - switch response.result { - case .failure(let error): - let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, nil, response, error) } - case .success(let data): - let decoder = JSONDecoder() - let result = try? decoder.decode(AssistantConversation.self, from: data) - options.queue.async { completion(account, result, response, .success) } + let parameters: [String: Any] = ["sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, sessionTask: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(SessionTask.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, sessionTask: result, responseData: response, error: .success)) + } + } } } } - /// Asynchronously checks if a chat session exists and retrieves its details. + /// Checks if a chat session exists and retrieves its details. /// /// - Parameters: /// - sessionId: The ID of the chat session to check. - /// - account: The account performing the request. - /// - options: Optional configuration. - /// - taskHandler: Callback to access the associated URLSessionTask. + /// - account: The Nextcloud account performing the request. + /// - options: Optional HTTP request configuration. + /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, session (if found), response, and error. - func checkAssistantChatSessionAsync(sessionId: Int, - account: String, - options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } + func checkAssistantChatSession(sessionId: Int, + account: String, + options: NKRequestOptions = NKRequestOptions(), + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, session: AssistantConversation?, @@ -792,16 +545,34 @@ public extension NextcloudKit { error: NKError ) { await withCheckedContinuation { continuation in - checkAssistantChatSession(sessionId: sessionId, - account: account, - options: options, - taskHandler: taskHandler) { account, session, responseData, error in - continuation.resume(returning: ( - account: account, - session: session, - responseData: responseData, - error: error - )) + let endpoint = "/ocs/v2.php/apps/assistant/chat/check_session" + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), + let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint), + let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { + return options.queue.async { + continuation.resume(returning: (account: account, session: nil, responseData: nil, error: .urlError)) + } + } + + let parameters: [String: Any] = ["sessionId": sessionId] + + nkSession.sessionData.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in + task.taskDescription = options.taskDescription + taskHandler(task) + }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + switch response.result { + case .failure(let error): + let error = NKError(error: error, afResponse: response, responseData: response.data) + options.queue.async { + continuation.resume(returning: (account: account, session: nil, responseData: response, error: error)) + } + case .success(let data): + let decoder = JSONDecoder() + let result = try? decoder.decode(AssistantConversation.self, from: data) + options.queue.async { + continuation.resume(returning: (account: account, session: result, responseData: response, error: .success)) + } + } } } } From 7f82439a7f5f2943d8db998b65f194711950a0b3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 9 Feb 2026 15:10:00 +0100 Subject: [PATCH 8/9] WIP Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 18 ++++++++++++++++++ Sources/NextcloudKit/NKInterceptor.swift | 2 +- .../NextcloudKit+AssistantV2.swift | 5 ++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index e42778ac..c9264527 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -122,6 +122,24 @@ public struct CreateConversation: Codable, Equatable { } } +// MARK: - Session + +public struct AssistantSession: Codable, Equatable { + public let messageTaskId: Int? + public let titleTaskId: Int? + public let sessionTitle: String? + public let sessionAgencyPendingActions: String? + public let taskId: Int? + + enum CodingKeys: String, CodingKey { + case messageTaskId + case titleTaskId + case sessionTitle + case sessionAgencyPendingActions + case taskId + } +} + // MARK: - SessionTask public struct SessionTask: Codable, Equatable { diff --git a/Sources/NextcloudKit/NKInterceptor.swift b/Sources/NextcloudKit/NKInterceptor.swift index af3d3289..5deedca8 100644 --- a/Sources/NextcloudKit/NKInterceptor.swift +++ b/Sources/NextcloudKit/NKInterceptor.swift @@ -12,7 +12,7 @@ final class NKInterceptor: RequestInterceptor, Sendable { self.nkCommonInstance = nkCommonInstance } - func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + func adapt(_ urlRequest: URLRequest, for session: AssistantSession, completion: @escaping (Result) -> Void) { // Log request URL in verbose mode if NKLogFileManager.shared.logLevel == .verbose, let url = urlRequest.url?.absoluteString { diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 73478152..01920b7e 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -540,7 +540,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - session: AssistantConversation?, + session: AssistantSession?, responseData: AFDataResponse?, error: NKError ) { @@ -568,7 +568,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode(AssistantConversation.self, from: data) + let result = try? decoder.decode(AssistantSession.self, from: data) options.queue.async { continuation.resume(returning: (account: account, session: result, responseData: response, error: .success)) } @@ -576,5 +576,4 @@ public extension NextcloudKit { } } } - } From c382804aadb7a180cfa4b45f2d3686977fa1d7c2 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 12 Feb 2026 18:09:14 +0100 Subject: [PATCH 9/9] Refactor Signed-off-by: Milen Pivchev --- .../Models/Assistant/v2/Chat.swift | 22 ++++--------------- .../NextcloudKit+AssistantV2.swift | 22 +++++++++---------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift index c9264527..94633c10 100644 --- a/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift +++ b/Sources/NextcloudKit/Models/Assistant/v2/Chat.swift @@ -6,7 +6,7 @@ import Foundation // MARK: - ChatMessage -public struct ChatMessage: Codable, Identifiable, Equatable { +public struct AssistantChatMessage: Codable, Identifiable, Equatable { public let id: Int public let sessionId: Int public let role: String @@ -36,7 +36,7 @@ public struct ChatMessage: Codable, Identifiable, Equatable { // MARK: - ChatMessageRequest -public struct ChatMessageRequest: Encodable { +public struct AssistantChatMessageRequest: Encodable { public let sessionId: Int public let role: String public let content: String @@ -69,20 +69,6 @@ public struct ChatMessageRequest: Encodable { } } -// MARK: - Conversation - -public struct Conversation: Codable, Identifiable, Equatable { - public let id: Int - public let title: String? - public let timestamp: Int - - enum CodingKeys: String, CodingKey { - case id - case title - case timestamp - } -} - // MARK: - Session public struct AssistantConversation: Codable, Equatable, Hashable { @@ -114,7 +100,7 @@ public struct AssistantConversation: Codable, Equatable, Hashable { // MARK: - CreateConversation -public struct CreateConversation: Codable, Equatable { +public struct AssistantCreatedConversation: Codable, Equatable { public let conversation: AssistantConversation enum CodingKeys: String, CodingKey { @@ -142,7 +128,7 @@ public struct AssistantSession: Codable, Equatable { // MARK: - SessionTask -public struct SessionTask: Codable, Equatable { +public struct AssistantSessionTask: Codable, Equatable { public let taskId: Int enum CodingKeys: String, CodingKey { diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index 01920b7e..da1d3162 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -282,7 +282,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - chatMessages: [ChatMessage]?, + chatMessages: [AssistantChatMessage]?, responseData: AFDataResponse?, error: NKError ) { @@ -308,7 +308,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode([ChatMessage].self, from: data) + let result = try? decoder.decode([AssistantChatMessage].self, from: data) options.queue.async { continuation.resume(returning: (account: account, chatMessages: result, responseData: response, error: .success)) } @@ -325,13 +325,13 @@ public extension NextcloudKit { /// - options: Optional HTTP request configuration. /// - taskHandler: Optional closure to access the underlying URLSessionTask. /// - Returns: A tuple with named values for account, created message, response, and error. - func createAssistantChatMessage(messageRequest: ChatMessageRequest, + func createAssistantChatMessage(messageRequest: AssistantChatMessageRequest, account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - chatMessage: ChatMessage?, + chatMessage: AssistantChatMessage?, responseData: AFDataResponse?, error: NKError ) { @@ -357,7 +357,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode(ChatMessage.self, from: data) + let result = try? decoder.decode(AssistantChatMessage.self, from: data) options.queue.async { continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) } @@ -382,7 +382,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - conversation: CreateConversation?, + conversation: AssistantCreatedConversation?, responseData: AFDataResponse?, error: NKError ) { @@ -413,7 +413,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode(CreateConversation.self, from: data) + let result = try? decoder.decode(AssistantCreatedConversation.self, from: data) options.queue.async { continuation.resume(returning: (account: account, conversation: result, responseData: response, error: .success)) } @@ -438,7 +438,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - chatMessage: ChatMessage?, + chatMessage: AssistantChatMessage?, responseData: AFDataResponse?, error: NKError ) { @@ -466,7 +466,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode(ChatMessage.self, from: data) + let result = try? decoder.decode(AssistantChatMessage.self, from: data) options.queue.async { continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) } @@ -489,7 +489,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> ( account: String, - sessionTask: SessionTask?, + sessionTask: AssistantSessionTask?, responseData: AFDataResponse?, error: NKError ) { @@ -517,7 +517,7 @@ public extension NextcloudKit { } case .success(let data): let decoder = JSONDecoder() - let result = try? decoder.decode(SessionTask.self, from: data) + let result = try? decoder.decode(AssistantSessionTask.self, from: data) options.queue.async { continuation.resume(returning: (account: account, sessionTask: result, responseData: response, error: .success)) }