Conversation
Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
… assistant-improvements
Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
… assistant-improvements
There was a problem hiding this comment.
Pull request overview
This PR updates the Assistant V2 client APIs by migrating parsing from SwiftyJSON to Codable, converting several endpoints to async/await-style APIs, and adding new Assistant chat/session endpoints.
Changes:
- Replace SwiftyJSON parsing for Assistant V2 task types/tasks with
Codableresponse models. - Refactor Assistant V2 endpoints to async functions and add new Assistant chat/session APIs.
- Introduce new v2 chat models (
ChatMessage, conversations, session status/task models).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/NextcloudKit/NextcloudKit+AssistantV2.swift | Refactors V2 assistant endpoints to async and adds chat-related endpoints; switches to Codable decoding. |
| Sources/NextcloudKit/NKInterceptor.swift | Modifies Alamofire interceptor adapt signature (currently breaks protocol conformance). |
| Sources/NextcloudKit/Models/Assistant/v2/TaskTypes.swift | Adds Codable OCS wrapper + updates task type model to support decoding. |
| Sources/NextcloudKit/Models/Assistant/v2/TaskList.swift | Adds Codable OCS wrappers for task list/task response and removes SwiftyJSON parsing helpers. |
| Sources/NextcloudKit/Models/Assistant/v2/Chat.swift | Adds new Codable chat-related models used by the new Assistant chat endpoints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let result = try? decoder.decode([ChatMessage].self, from: data) | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, chatMessages: result, responseData: response, error: .success)) |
There was a problem hiding this comment.
Same pattern here: try? decode + always returning .success means unexpected payloads become silent successes with chatMessages: nil. Prefer do/catch and return an error (e.g. .invalidData) when decoding fails (and confirm whether an OCS envelope is expected for this endpoint).
| let result = try? decoder.decode([ChatMessage].self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessages: result, responseData: response, error: .success)) | |
| do { | |
| let result = try decoder.decode([ChatMessage].self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessages: result, responseData: response, error: .success)) | |
| } | |
| } catch { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessages: nil, responseData: response, error: .invalidData)) | |
| } |
| let result = try? decoder.decode(CreateConversation.self, from: data) | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, conversation: result, responseData: response, error: .success)) |
There was a problem hiding this comment.
Decoding CreateConversation is optional and decode failures still return .success with conversation: nil. Prefer explicit decoding with do/catch and return .invalidData (and/or log) when decoding fails to avoid silent failures.
| let result = try? decoder.decode(CreateConversation.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, conversation: result, responseData: response, error: .success)) | |
| do { | |
| let result = try decoder.decode(CreateConversation.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, conversation: result, responseData: response, error: .success)) | |
| } | |
| } catch { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, conversation: nil, responseData: response, error: .invalidData)) | |
| } |
| public struct OCSTaskTypesOCS: Codable { | ||
| public let data: OCSTaskTypesData | ||
| } | ||
|
|
There was a problem hiding this comment.
The new OCSTaskTypesResponse omits the standard OCS meta block (statuscode/message). Since OCS endpoints can return HTTP 200 with meta.statuscode indicating an error, callers can’t validate the OCS-level status with this model. Consider adding a meta field to the wrapper (and checking it in the request code) to preserve previous behavior.
| public struct OCSTaskTypesOCS: Codable { | |
| public let data: OCSTaskTypesData | |
| } | |
| public struct OCSTaskTypesOCS: Codable { | |
| public let meta: OCSMeta? | |
| public let data: OCSTaskTypesData | |
| } | |
| public struct OCSMeta: Codable { | |
| public let status: String? | |
| public let statuscode: Int? | |
| public let message: String? | |
| } |
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
OCSTaskListResponse / OCSTaskResponse wrappers don’t include the OCS meta block. If the server returns an OCS error with HTTP 200, the client can’t detect it via these models. Add meta (statuscode/message) and validate it in the calling code to avoid reporting false successes.
| } | ||
|
|
||
| func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) { | ||
| func adapt(_ urlRequest: URLRequest, for session: AssistantSession, completion: @escaping (Result<URLRequest, Error>) -> Void) { |
There was a problem hiding this comment.
RequestInterceptor.adapt must take an Alamofire.Session parameter. Changing the signature to AssistantSession breaks protocol conformance and will not compile. Use Session (or qualify as Alamofire.Session) and avoid name collisions with the new AssistantSession model type.
| func adapt(_ urlRequest: URLRequest, for session: AssistantSession, completion: @escaping (Result<URLRequest, Error>) -> Void) { | |
| func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) { |
| let result = try? decoder.decode(ChatMessage.self, from: data) | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) |
There was a problem hiding this comment.
Decoding failures are currently treated as .success with chatMessage: nil. This should be surfaced as an error (e.g. .invalidData) and/or logged so clients can react appropriately when the server payload changes or is malformed.
| let result = try? decoder.decode(ChatMessage.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) | |
| do { | |
| let result = try decoder.decode(ChatMessage.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) | |
| } | |
| } catch { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: nil, responseData: response, error: .invalidData)) | |
| } |
| let result = try? decoder.decode(SessionTask.self, from: data) | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, sessionTask: result, responseData: response, error: .success)) |
There was a problem hiding this comment.
SessionTask decoding uses try? and the function always returns .success, so decode failures become silent successes (sessionTask: nil). Handle decode errors explicitly (return .invalidData and/or log the decoding error).
| let result = try? decoder.decode(SessionTask.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, sessionTask: result, responseData: response, error: .success)) | |
| do { | |
| let result = try decoder.decode(SessionTask.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, sessionTask: result, responseData: response, error: .success)) | |
| } | |
| } catch { | |
| let decodingError = NKError(error: error, afResponse: response, responseData: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, sessionTask: nil, responseData: response, error: decodingError)) | |
| } |
| } else { | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, types: nil, responseData: response, error: .success)) | ||
| } |
There was a problem hiding this comment.
On JSON decoding failure, this returns error: .success with types: nil, which will silently hide server/schema issues. Return an error (e.g. .invalidData) and/or log the decoding error so callers can handle it.
| 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)) |
There was a problem hiding this comment.
As written, decoding OCSTaskListResponse can fail and the function will still return error: .success (tasks will be nil). Treat decode failures as errors (e.g. .invalidData) and consider validating the OCS meta.statuscode in the body as well.
| 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)) | |
| do { | |
| let result = try decoder.decode(OCSTaskListResponse.self, from: data) | |
| let statusCode = result.ocs.meta.statuscode | |
| if statusCode < 200 || statusCode >= 300 { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, tasks: nil, responseData: response, error: .invalidData)) | |
| } | |
| } else { | |
| let taskList = TaskList(tasks: result.ocs.data.tasks) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, tasks: taskList, responseData: response, error: .success)) | |
| } | |
| } | |
| } catch { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, tasks: nil, responseData: response, error: .invalidData)) | |
| } |
| let result = try? decoder.decode(ChatMessage.self, from: data) | ||
| options.queue.async { | ||
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) |
There was a problem hiding this comment.
If decoding the created ChatMessage fails, this still returns .success with chatMessage: nil. Return .invalidData (and/or log) on decode failures so callers can distinguish server errors/contract changes from a real success.
| let result = try? decoder.decode(ChatMessage.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) | |
| do { | |
| let result = try decoder.decode(ChatMessage.self, from: data) | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: result, responseData: response, error: .success)) | |
| } | |
| } catch { | |
| options.queue.async { | |
| continuation.resume(returning: (account: account, chatMessage: nil, responseData: response, error: .invalidData)) | |
| } |
… assistant-improvements
No description provided.