From 1158865bf2ba25fcc1a50d712756649e06016d94 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Sat, 28 Feb 2026 03:16:10 -0800 Subject: [PATCH] feat: ListConversations (#60) * Add function to get available conversations_ids * Update bindings * linter fix * remove dynlib reference * destructor fix --- conversations/src/api.rs | 38 +++++++++++++++++++++++++++++++++++ conversations/src/context.rs | 4 ++++ nim-bindings/src/bindings.nim | 32 +++++++++++++++++++++++++++++ nim-bindings/src/libchat.nim | 12 +++++++++++ 4 files changed, 86 insertions(+) diff --git a/conversations/src/api.rs b/conversations/src/api.rs index 6689541..67cb483 100644 --- a/conversations/src/api.rs +++ b/conversations/src/api.rs @@ -139,6 +139,29 @@ pub fn create_new_private_convo( }; } +/// List existing conversations +/// +/// # Returns +/// Returns a struct with conversation ids of available conversations +/// The ListConvoResult must be freed. +#[ffi_export] +pub fn list_conversations(ctx: &mut ContextHandle) -> ListConvoResult { + match ctx.0.list_conversations() { + Ok(ids) => { + let ffi_ids: Vec = + ids.into_iter().map(|id| id.to_string().into()).collect(); + ListConvoResult { + error_code: ErrorCode::None as i32, + convo_ids: ffi_ids.into(), + } + } + Err(_) => ListConvoResult { + error_code: ErrorCode::UnknownError as i32, + convo_ids: repr_c::Vec::EMPTY, + }, + } +} + /// Sends content to an existing conversation /// /// # Returns @@ -330,3 +353,18 @@ pub struct NewConvoResult { pub fn destroy_convo_result(result: &mut NewConvoResult) { unsafe { std::ptr::drop_in_place(result) } } + +/// Result structure for create_new_private_convo +/// error_code is 0 on success, negative on error (see ErrorCode) +#[derive_ReprC] +#[repr(C)] +pub struct ListConvoResult { + pub error_code: i32, + pub convo_ids: repr_c::Vec, +} + +/// Free the result from create_new_private_convo +#[ffi_export] +pub fn destroy_list_result(result: ListConvoResult) { + drop(result); +} diff --git a/conversations/src/context.rs b/conversations/src/context.rs index 44dbce5..2b36f68 100644 --- a/conversations/src/context.rs +++ b/conversations/src/context.rs @@ -55,6 +55,10 @@ impl Context { (convo_id, payload_bytes) } + pub fn list_conversations(&self) -> Result, ChatError> { + Ok(self.store.conversation_ids()) + } + pub fn send_content( &mut self, convo_id: ConversationId, diff --git a/nim-bindings/src/bindings.nim b/nim-bindings/src/bindings.nim index 569e81a..81b7505 100644 --- a/nim-bindings/src/bindings.nim +++ b/nim-bindings/src/bindings.nim @@ -42,6 +42,12 @@ type len*: csize_t cap*: csize_t + ## Vector of Payloads returned by safer_ffi functions + VecString* = object + `ptr`*: ptr ReprCString + len*: csize_t + cap*: csize_t + ## Result structure for create_intro_bundle ## error_code is 0 on success, negative on error (see ErrorCode) CreateIntroResult* = object @@ -69,6 +75,12 @@ type convo_id*: ReprCString payloads*: VecPayload + ## Result from list_conversations + ## error_code is 0 on success, negative on error (see ErrorCode) + ListConvoResult* = object + error_code*: int32 + convo_ids*: VecString + # FFI function imports ## Creates a new libchat Context @@ -100,6 +112,13 @@ proc create_new_private_convo*( content: SliceUint8, ): NewConvoResult {.importc.} +## Get the available conversation identifers. +## Returns: ListConvoResult struct - check error_code field (0 = success, negative = error) +## The result must be freed with destroy_list_result() +proc list_conversations*( + ctx: ContextHandle, +): ListConvoResult {.importc.} + ## Sends content to an existing conversation ## Returns: SendContentResult struct - check error_code field (0 = success, negative = error) ## The result must be freed with destroy_send_content_result() @@ -125,6 +144,9 @@ proc destroy_intro_result*(result: CreateIntroResult) {.importc.} ## Free the result from create_new_private_convo proc destroy_convo_result*(result: NewConvoResult) {.importc.} +## Free the result from list_conversation +proc destroy_list_result*(result: ListConvoResult) {.importc.} + ## Free the result from send_content proc destroy_send_content_result*(result: SendContentResult) {.importc.} @@ -166,6 +188,15 @@ proc toReprCString*(s: string): ReprCString = else: ReprCString(`ptr`: cast[ptr char](unsafeAddr s[0]), len: csize_t(s.len), cap: 0) +## Convert a VecUint8 to a seq[string] +proc toSeq*(v: VecString): seq[string] = + if v.ptr == nil or v.len == 0: + return @[] + result = newSeq[string](v.len) + let arr = cast[ptr UncheckedArray[ReprCString]](v.ptr) + for i in 0 ..< int(v.len): + result[i] = $arr[i] + ## Convert a VecUint8 to a seq[byte] proc toSeq*(v: VecUint8): seq[byte] = if v.ptr == nil or v.len == 0: @@ -193,3 +224,4 @@ proc toBytes*(s: string): seq[byte] = return @[] result = newSeq[byte](s.len) copyMem(addr result[0], unsafeAddr s[0], s.len) + diff --git a/nim-bindings/src/libchat.nim b/nim-bindings/src/libchat.nim index 4097119..0e2494a 100644 --- a/nim-bindings/src/libchat.nim +++ b/nim-bindings/src/libchat.nim @@ -74,6 +74,18 @@ proc createNewPrivateConvo*(ctx: LibChat, bundle: seq[byte], content: seq[byte]) return ok(($res.convo_id, payloads)) +proc listConversations*(ctx: LibChat): Result[seq[string], string] = + if ctx.handle == nil: + return err("Context handle is nil") + let res = bindings.list_conversations(ctx.handle) + + if res.error_code != 0: + result = err("Failed to list conversations: " & $res.error_code) + destroy_list_result(res) + return + + ok(res.convo_ids.toSeq()) + ## Send content to an existing conversation proc sendContent*(ctx: LibChat, convoId: string, content: seq[byte]): Result[seq[PayloadResult], string] = if ctx.handle == nil: