Update nim bindings

This commit is contained in:
Jazz Turner-Baggs 2026-02-06 13:41:20 -08:00
parent 98c74a120e
commit 9aad91a054
No known key found for this signature in database
2 changed files with 74 additions and 49 deletions

View File

@ -68,10 +68,23 @@ type
## Result structure for create_intro_bundle ## Result structure for create_intro_bundle
## error_code is 0 on success, negative on error (see ErrorCode) ## error_code is 0 on success, negative on error (see ErrorCode)
PayloadResult* = object CreateIntroResult* = object
error_code*: int32
intro_bytes*: VecUint8
## Result structure for send_content
## error_code is 0 on success, negative on error (see ErrorCode)
SendContentResult* = object
error_code*: int32 error_code*: int32
payloads*: VecPayload payloads*: VecPayload
## Result structure for handle_payload
## error_code is 0 on success, negative on error (see ErrorCode)
HandlePayloadResult* = object
error_code*: int32
convo_id*: ReprCString
content*: VecUint8
## Result from create_new_private_convo ## Result from create_new_private_convo
## error_code is 0 on success, negative on error (see ErrorCode) ## error_code is 0 on success, negative on error (see ErrorCode)
NewConvoResult* = object NewConvoResult* = object
@ -91,11 +104,11 @@ proc create_context*(): ContextHandle {.importc, dynlib: CONVERSATIONS_LIB.}
proc destroy_context*(ctx: ContextHandle) {.importc, dynlib: CONVERSATIONS_LIB.} proc destroy_context*(ctx: ContextHandle) {.importc, dynlib: CONVERSATIONS_LIB.}
## Creates an intro bundle for sharing with other users ## Creates an intro bundle for sharing with other users
## Returns: Number of bytes written to bundle_out, or negative error code ## Returns: CreateIntroResult struct - check error_code field (0 = success, negative = error)
## The result must be freed with destroy_intro_result()
proc create_intro_bundle*( proc create_intro_bundle*(
ctx: ContextHandle, ctx: ContextHandle,
bundle_out: SliceUint8, ): CreateIntroResult {.importc, dynlib: CONVERSATIONS_LIB.}
): int32 {.importc, dynlib: CONVERSATIONS_LIB.}
## Creates a new private conversation ## Creates a new private conversation
## Returns: NewConvoResult struct - check error_code field (0 = success, negative = error) ## Returns: NewConvoResult struct - check error_code field (0 = success, negative = error)
@ -107,30 +120,35 @@ proc create_new_private_convo*(
): NewConvoResult {.importc, dynlib: CONVERSATIONS_LIB.} ): NewConvoResult {.importc, dynlib: CONVERSATIONS_LIB.}
## Sends content to an existing conversation ## Sends content to an existing conversation
## Returns: PayloadResult struct - check error_code field (0 = success, negative = error) ## Returns: SendContentResult struct - check error_code field (0 = success, negative = error)
## The result must be freed with destroy_payload_result() ## The result must be freed with destroy_send_content_result()
proc send_content*( proc send_content*(
ctx: ContextHandle, ctx: ContextHandle,
convo_id: SliceUint8, convo_id: ReprCString,
content: SliceUint8, content: SliceUint8,
): PayloadResult {.importc, dynlib: CONVERSATIONS_LIB.} ): SendContentResult {.importc, dynlib: CONVERSATIONS_LIB.}
## Handles an incoming payload and writes content to caller-provided buffers ## Handles an incoming payload
## Returns: Number of bytes written to content_out on success (>= 0), negative error code on failure ## Returns: HandlePayloadResult struct - check error_code field (0 = success, negative = error)
## conversation_id_out_len is set to the number of bytes written to conversation_id_out ## This call does not always generate content. If content is zero bytes long then there
## is no data, and the convo_id should be ignored.
## The result must be freed with destroy_handle_payload_result()
proc handle_payload*( proc handle_payload*(
ctx: ContextHandle, ctx: ContextHandle,
payload: SliceUint8, payload: SliceUint8,
conversation_id_out: SliceUint8, ): HandlePayloadResult {.importc, dynlib: CONVERSATIONS_LIB.}
conversation_id_out_len: ptr uint32,
content_out: SliceUint8, ## Free the result from create_intro_bundle
): int32 {.importc, dynlib: CONVERSATIONS_LIB.} proc destroy_intro_result*(result: CreateIntroResult) {.importc, dynlib: CONVERSATIONS_LIB.}
## Free the result from create_new_private_convo ## Free the result from create_new_private_convo
proc destroy_convo_result*(result: NewConvoResult) {.importc, dynlib: CONVERSATIONS_LIB.} proc destroy_convo_result*(result: NewConvoResult) {.importc, dynlib: CONVERSATIONS_LIB.}
## Free the PayloadResult ## Free the result from send_content
proc destroy_payload_result*(result: PayloadResult) {.importc, dynlib: CONVERSATIONS_LIB.} proc destroy_send_content_result*(result: SendContentResult) {.importc, dynlib: CONVERSATIONS_LIB.}
## Free the result from handle_payload
proc destroy_handle_payload_result*(result: HandlePayloadResult) {.importc, dynlib: CONVERSATIONS_LIB.}
# ============================================================================ # ============================================================================
# Helper functions # Helper functions
@ -157,6 +175,16 @@ proc `$`*(s: ReprCString): string =
result = newString(s.len) result = newString(s.len)
copyMem(addr result[0], s.ptr, s.len) copyMem(addr result[0], s.ptr, s.len)
## Create a ReprCString from a Nim string for passing to FFI functions.
## WARNING: The returned ReprCString borrows from the input string.
## The input string must remain valid for the duration of the FFI call.
## cap is set to 0 to prevent Rust from attempting to deallocate Nim memory.
proc toReprCString*(s: string): ReprCString =
if s.len == 0:
ReprCString(`ptr`: nil, len: 0, cap: 0)
else:
ReprCString(`ptr`: cast[ptr char](unsafeAddr s[0]), len: csize_t(s.len), cap: 0)
## Convert a VecUint8 to a seq[byte] ## Convert a VecUint8 to a seq[byte]
proc toSeq*(v: VecUint8): seq[byte] = proc toSeq*(v: VecUint8): seq[byte] =
if v.ptr == nil or v.len == 0: if v.ptr == nil or v.len == 0:
@ -173,6 +201,11 @@ proc `[]`*(v: VecPayload, i: int): Payload =
proc len*(v: VecPayload): int = proc len*(v: VecPayload): int =
int(v.len) int(v.len)
## Iterator for VecPayload
iterator items*(v: VecPayload): Payload =
for i in 0 ..< v.len:
yield v[int(i)]
## Convert a string to seq[byte] ## Convert a string to seq[byte]
proc toBytes*(s: string): seq[byte] = proc toBytes*(s: string): seq[byte] =
if s.len == 0: if s.len == 0:

View File

@ -1,4 +1,5 @@
import std/options import std/options
import std/sequtils
import results import results
import bindings import bindings
@ -31,22 +32,21 @@ proc getBuffer*(ctx: LibChat): seq[byte] =
newSeq[byte](ctx.buffer_size) newSeq[byte](ctx.buffer_size)
## Generate a Introduction Bundle ## Generate a Introduction Bundle
proc createIntroductionBundle*(ctx: LibChat): Result[string, string] = proc createIntroductionBundle*(ctx: LibChat): Result[seq[byte], string] =
if ctx.handle == nil: if ctx.handle == nil:
return err("Context handle is nil") return err("Context handle is nil")
var buffer = ctx.getBuffer() let res = create_intro_bundle(ctx.handle)
var slice = buffer.toSlice()
let len = create_intro_bundle(ctx.handle, slice)
if len < 0: if res.error_code != ErrNone:
return err("Failed to create intro bundle: " & $len) result = err("Failed to create private convo: " & $res.error_code)
destroy_intro_result(res)
return
buffer.setLen(len) return ok(res.intro_bytes.toSeq())
return ok(cast[string](buffer))
## Create a Private Convo ## Create a Private Convo
proc createNewPrivateConvo*(ctx: LibChat, bundle: string, content: seq[byte]): Result[(string, seq[PayloadResult]), string] = proc createNewPrivateConvo*(ctx: LibChat, bundle: seq[byte], content: seq[byte]): Result[(string, seq[PayloadResult]), string] =
if ctx.handle == nil: if ctx.handle == nil:
return err("Context handle is nil") return err("Context handle is nil")
@ -92,25 +92,22 @@ proc sendContent*(ctx: LibChat, convoId: string, content: seq[byte]): Result[seq
let res = bindings.send_content( let res = bindings.send_content(
ctx.handle, ctx.handle,
convoId.toSlice(), convoId.toReprCString,
content.toSlice() content.toSlice()
) )
if res.error_code != 0: if res.error_code != 0:
result = err("Failed to send content: " & $res.error_code) result = err("Failed to send content: " & $res.error_code)
destroy_payload_result(res) destroy_send_content_result(res)
return return
# Convert payloads to Nim types
var payloads = newSeq[PayloadResult](res.payloads.len)
for i in 0 ..< res.payloads.len:
let p = res.payloads[int(i)]
payloads[int(i)] = PayloadResult(
address: $p.address,
data: p.data.toSeq()
)
destroy_payload_result(res) let payloads = res.payloads.toSeq().mapIt(PayloadResult(
address: $it.address,
data: it.data.toSeq()
))
destroy_send_content_result(res)
return ok(payloads) return ok(payloads)
type type
@ -130,26 +127,21 @@ proc handlePayload*(ctx: LibChat, payload: seq[byte]): Result[Option[ContentResu
var contentBuf = newSeq[byte](ctx.buffer_size) var contentBuf = newSeq[byte](ctx.buffer_size)
var conversationIdLen: uint32 = 0 var conversationIdLen: uint32 = 0
let bytesWritten = bindings.handle_payload( let res = bindings.handle_payload(
ctx.handle, ctx.handle,
payload.toSlice(), payload.toSlice(),
conversationIdBuf.toSlice(),
addr conversationIdLen,
contentBuf.toSlice()
) )
if bytesWritten < 0: if res.error_code != ErrNone:
return err("Failed to handle payload: " & $bytesWritten) return err("Failed to handle payload: " & $res.error_code)
if bytesWritten == 0: let content = res.content.toSeq()
if content.len == 0:
return ok(none(ContentResult)) return ok(none(ContentResult))
conversationIdBuf.setLen(conversationIdLen)
contentBuf.setLen(bytesWritten)
return ok(some(ContentResult( return ok(some(ContentResult(
conversationId: cast[string](conversationIdBuf), conversationId: $res.convo_id,
data: contentBuf data: content
))) )))