libchat/nim-bindings/src/libchat.nim
Jazz Turner-Baggs 1f0354f8e2
Add FFI functions for send_content and handle_payload (#29)
* Add api calls for handle_payload and send_content

* Add handle_payload and send_content to FFI
2026-01-28 10:50:41 -08:00

160 lines
4.0 KiB
Nim

import std/options
import results
import bindings
type
LibChat* = object
handle: ContextHandle
buffer_size: int
PayloadResult* = object
address*: string
data*: seq[uint8]
## Create a new conversations context
proc newConversationsContext*(): LibChat =
result.handle = create_context()
result.buffer_size = 256
if result.handle.isNil:
raise newException(IOError, "Failed to create context")
## Destroy the context and free resources
proc destroy*(ctx: var LibChat) =
if not ctx.handle.isNil:
destroy_context(ctx.handle)
ctx.handle = nil
## Helper proc to create buffer of sufficient size
proc getBuffer*(ctx: LibChat): seq[byte] =
newSeq[byte](ctx.buffer_size)
## Generate a Introduction Bundle
proc createIntroductionBundle*(ctx: LibChat): Result[string, string] =
if ctx.handle == nil:
return err("Context handle is nil")
var buffer = ctx.getBuffer()
var slice = buffer.toSlice()
let len = create_intro_bundle(ctx.handle, slice)
if len < 0:
return err("Failed to create intro bundle: " & $len)
buffer.setLen(len)
return ok(cast[string](buffer))
## Create a Private Convo
proc createNewPrivateConvo*(ctx: LibChat, bundle: string, content: string): Result[(ConvoHandle, seq[PayloadResult]), string] =
if ctx.handle == nil:
return err("Context handle is nil")
if bundle.len == 0:
return err("bundle is zero length")
if content.len == 0:
return err("content is zero length")
let res = bindings.create_new_private_convo(
ctx.handle,
bundle.toSlice(),
content.toSlice()
)
if res.error_code != 0:
result = err("Failed to create private convo: " & $res.error_code)
destroy_convo_result(res)
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()
)
let convoId = res.convo_id
# Free the result
destroy_convo_result(res)
return ok((convoId, payloads))
## Send content to an existing conversation
proc sendContent*(ctx: LibChat, convoHandle: ConvoHandle, content: string): Result[seq[PayloadResult], string] =
if ctx.handle == nil:
return err("Context handle is nil")
if content.len == 0:
return err("content is zero length")
let res = bindings.send_content(
ctx.handle,
convoHandle,
content.toSlice()
)
if res.error_code != 0:
result = err("Failed to send content: " & $res.error_code)
destroy_payload_result(res)
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)
return ok(payloads)
type
ContentResult* = object
conversationId*: string
data*: seq[uint8]
## Handle an incoming payload and decrypt content
proc handlePayload*(ctx: LibChat, payload: seq[byte]): Result[Option[ContentResult], string] =
if ctx.handle == nil:
return err("Context handle is nil")
if payload.len == 0:
return err("payload is zero length")
var conversationIdBuf = newSeq[byte](ctx.buffer_size)
var contentBuf = newSeq[byte](ctx.buffer_size)
var conversationIdLen: uint32 = 0
let bytesWritten = bindings.handle_payload(
ctx.handle,
payload.toSlice(),
conversationIdBuf.toSlice(),
addr conversationIdLen,
contentBuf.toSlice()
)
if bytesWritten < 0:
return err("Failed to handle payload: " & $bytesWritten)
if bytesWritten == 0:
return ok(none(ContentResult))
conversationIdBuf.setLen(conversationIdLen)
contentBuf.setLen(bytesWritten)
return ok(some(ContentResult(
conversationId: cast[string](conversationIdBuf),
data: contentBuf
)))
proc `=destroy`(x: var LibChat) =
# Automatically free handle when the destructor is called
if x.handle != nil:
x.destroy()