summaryrefslogtreecommitdiff
path: root/libpanto-go
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto-go')
-rw-r--r--libpanto-go/panto.go611
1 files changed, 558 insertions, 53 deletions
diff --git a/libpanto-go/panto.go b/libpanto-go/panto.go
index afb66a9..786ff1c 100644
--- a/libpanto-go/panto.go
+++ b/libpanto-go/panto.go
@@ -5,6 +5,32 @@ package panto
#cgo LDFLAGS: -L../libpanto-c/zig-out/lib -lpanto
#include <panto.h>
#include <stdlib.h>
+extern PantoStatus pantoGoStoreCreate(void *ctx, PantoSession **out);
+extern PantoStatus pantoGoStoreList(void *ctx, PantoSessionInfoList *out);
+extern void pantoGoStoreFreeSessionInfos(void *ctx, PantoSessionInfoList infos);
+extern PantoStatus pantoGoStoreResolve(void *ctx, PantoSlice id, PantoSessionResult *out);
+extern PantoStatus pantoGoStoreLatest(void *ctx, PantoSessionResult *out);
+extern PantoStatus pantoGoStoreLoad(void *ctx, PantoSlice id, PantoConversation **out);
+extern PantoStatus pantoGoStoreAppendMessages(void *ctx, PantoSlice session_id, PantoPersistentMessage *messages, size_t len);
+static PantoStatus pantoGoStoreAppendMessagesConst(void *ctx, PantoSlice session_id, const PantoPersistentMessage *messages, size_t len) {
+ return pantoGoStoreAppendMessages(ctx, session_id, (PantoPersistentMessage*)messages, len);
+}
+extern PantoStatus pantoGoToolInvoke(void *ctx, PantoSlice input, PantoResultParts *out);
+extern PantoStatus pantoGoToolSourceInvokeBatch(void *ctx, PantoToolCall *calls, PantoToolCallResult *results, size_t len);
+extern void pantoGoCallbackDestroy(void *ctx);
+static PantoSessionStoreVTable panto_go_store_vtable = {
+ pantoGoStoreCreate,
+ pantoGoStoreList,
+ pantoGoStoreFreeSessionInfos,
+ pantoGoStoreResolve,
+ pantoGoStoreLatest,
+ pantoGoStoreLoad,
+ pantoGoStoreAppendMessagesConst,
+ pantoGoCallbackDestroy,
+};
+static PantoStatus panto_go_session_store_create(void *ctx, PantoSessionStore **out) {
+ return panto_session_store_create(ctx, &panto_go_store_vtable, out);
+}
*/
import "C"
@@ -16,19 +42,22 @@ import (
"unsafe"
)
-type APIStyle int
-type ReasoningEffort int
-type Thinking int
-type Effort int
-type MessageRole int
-type ContentBlockTag int
-type EventTag int
-type SystemMode int
+type (
+ APIStyle int
+ ReasoningEffort int
+ Thinking int
+ Effort int
+ MessageRole int
+ ContentBlockTag int
+ EventTag int
+ SystemMode int
+)
const (
OpenAIChat APIStyle = iota
AnthropicMessages
)
+
const (
ReasoningDefault ReasoningEffort = iota
ReasoningOff
@@ -37,11 +66,13 @@ const (
ReasoningMedium
ReasoningHigh
)
+
const (
ThinkingDisabled Thinking = iota
ThinkingEnabled
ThinkingAdaptive
)
+
const (
EffortLow Effort = iota
EffortMedium
@@ -49,11 +80,13 @@ const (
EffortXHigh
EffortMax
)
+
const (
RoleSystem MessageRole = iota
RoleUser
RoleAssistant
)
+
const (
BlockText ContentBlockTag = iota
BlockThinking
@@ -62,10 +95,12 @@ const (
BlockSystem
BlockCompactionSummary
)
+
const (
SystemAppend SystemMode = iota
SystemReplace
)
+
const (
EventMessageStart EventTag = iota
EventBlockStart
@@ -112,11 +147,13 @@ type RetryConfig struct {
Multiplier float64
Jitter bool
}
-type Usage struct{ Input, Output, CacheRead, CacheWrite, Reasoning uint64 }
-type CompactionResult struct {
- Compacted bool
- KeptTurns, SummarizedMessages uint
-}
+type (
+ Usage struct{ Input, Output, CacheRead, CacheWrite, Reasoning uint64 }
+ CompactionResult struct {
+ Compacted bool
+ KeptTurns, SummarizedMessages uint
+ }
+)
type Event struct {
Tag EventTag
@@ -146,43 +183,85 @@ type BlockComplete struct {
Index uint
BlockType ContentBlockTag
}
-type MessageComplete struct{ Usage *Usage }
-type ProviderRetry struct {
- Attempt, MaxAttempts uint
- DelayMS uint64
- ErrorName string
- StatusCode *uint16
- RetryAfterMS *uint64
- Message string
- Compaction bool
-}
-type ToolDispatchStart struct{ Count uint }
-type ToolDispatchComplete struct{ MessageIndex uint }
+type (
+ MessageComplete struct{ Usage *Usage }
+ ProviderRetry struct {
+ Attempt, MaxAttempts uint
+ DelayMS uint64
+ ErrorName string
+ StatusCode *uint16
+ RetryAfterMS *uint64
+ Message string
+ Compaction bool
+ }
+)
+
+type (
+ ToolDispatchStart struct{ Count uint }
+ ToolDispatchComplete struct{ MessageIndex uint }
+)
+
+type (
+ Agent struct{ ptr *C.PantoAgent }
+ Stream struct {
+ ptr *C.PantoStream
+ err error
+ done bool
+ }
+)
-type Agent struct{ ptr *C.PantoAgent }
-type Stream struct {
- ptr *C.PantoStream
- err error
- done bool
-}
type Conversation struct {
ptr *C.PantoConversation
owned bool
}
-type Session struct{ ptr *C.PantoSession }
-type SessionInfo struct {
- ID, Created, Modified, LastUserMessage, BaseURL, Model string
- MessageCount uint
- APIStyle APIStyle
- Reasoning ReasoningEffort
-}
+type (
+ Session struct{ ptr *C.PantoSession }
+ SessionInfo struct {
+ ID, Created, Modified, LastUserMessage, BaseURL, Model string
+ MessageCount uint
+ APIStyle APIStyle
+ Reasoning ReasoningEffort
+ }
+)
type SessionStore interface {
- cptr() *C.PantoSessionStore
- Close()
+ Create() (*Session, error)
+ List() ([]SessionInfo, error)
+ Resolve(id string) (*Session, bool, error)
+ Latest() (*Session, bool, error)
+ Load(id string) (*Conversation, bool, error)
+ AppendMessages(sessionID string, messages []PersistentMessage) error
+}
+type (
+ cSessionStore interface{ cptr() *C.PantoSessionStore }
+ NullStore struct{ ptr *C.PantoSessionStore }
+ FileSystemJSONLStore struct{ ptr *C.PantoSessionStore }
+ GoSessionStore struct {
+ ptr *C.PantoSessionStore
+ impl SessionStore
+ }
+)
+
+type PersistentMessage struct {
+ Role MessageRole
+ Usage *Usage
+ Metadata string
+ Content []OwnedContentBlock
+}
+type OwnedMessage struct {
+ Role MessageRole
+ Usage *Usage
+ Metadata string
+ Content []OwnedContentBlock
+}
+type OwnedContentBlock struct {
+ Tag ContentBlockTag
+ Text string
+ ToolID string
+ ToolName string
+ IsError bool
+ SystemMode SystemMode
}
-type NullStore struct{ ptr *C.PantoSessionStore }
-type FileSystemJSONLStore struct{ ptr *C.PantoSessionStore }
type cStrings []*C.char
@@ -210,13 +289,27 @@ func NewNullStore() (*NullStore, error) {
runtime.SetFinalizer(s, (*NullStore).Close)
return s, nil
}
-func (s *NullStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *NullStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *NullStore) Create() (*Session, error) { return CreateSession(s) }
+func (s *NullStore) List() ([]SessionInfo, error) { return ListSessions(s) }
+func (s *NullStore) Resolve(id string) (*Session, bool, error) { return ResolveSession(s, id) }
+func (s *NullStore) Latest() (*Session, bool, error) { return LatestSession(s) }
+func (s *NullStore) Load(id string) (*Conversation, bool, error) {
+ sess, ok, err := ResolveSession(s, id)
+ if err != nil || !ok {
+ return nil, ok, err
+ }
+ c, err := sess.Load()
+ return c, err == nil, err
+}
+func (s *NullStore) AppendMessages(sessionID string, messages []PersistentMessage) error { return nil }
func (s *NullStore) Close() {
if s != nil && s.ptr != nil {
C.panto_session_store_destroy(s.ptr)
s.ptr = nil
}
}
+
func NewFileSystemJSONLStore(dir, metadata string) (*FileSystemJSONLStore, error) {
cd := C.CString(dir)
defer C.free(unsafe.Pointer(cd))
@@ -230,7 +323,26 @@ func NewFileSystemJSONLStore(dir, metadata string) (*FileSystemJSONLStore, error
runtime.SetFinalizer(s, (*FileSystemJSONLStore).Close)
return s, nil
}
-func (s *FileSystemJSONLStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *FileSystemJSONLStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *FileSystemJSONLStore) Create() (*Session, error) { return CreateSession(s) }
+func (s *FileSystemJSONLStore) List() ([]SessionInfo, error) { return ListSessions(s) }
+func (s *FileSystemJSONLStore) Resolve(id string) (*Session, bool, error) {
+ return ResolveSession(s, id)
+}
+func (s *FileSystemJSONLStore) Latest() (*Session, bool, error) { return LatestSession(s) }
+func (s *FileSystemJSONLStore) Load(id string) (*Conversation, bool, error) {
+ sess, ok, err := ResolveSession(s, id)
+ if err != nil || !ok {
+ return nil, ok, err
+ }
+ c, err := sess.Load()
+ return c, err == nil, err
+}
+
+func (s *FileSystemJSONLStore) AppendMessages(sessionID string, messages []PersistentMessage) error {
+ return nil
+}
+
func (s *FileSystemJSONLStore) Close() {
if s != nil && s.ptr != nil {
C.panto_session_store_destroy(s.ptr)
@@ -238,7 +350,7 @@ func (s *FileSystemJSONLStore) Close() {
}
}
-func CreateSession(store SessionStore) (*Session, error) {
+func CreateSession(store cSessionStore) (*Session, error) {
var out *C.PantoSession
if C.panto_session_store_create_session(store.cptr(), &out) != C.PANTO_OK {
return nil, lastError()
@@ -247,7 +359,8 @@ func CreateSession(store SessionStore) (*Session, error) {
runtime.SetFinalizer(s, (*Session).Close)
return s, nil
}
-func ResolveSession(store SessionStore, id string) (*Session, bool, error) {
+
+func ResolveSession(store cSessionStore, id string) (*Session, bool, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var r C.PantoSessionResult
@@ -261,7 +374,8 @@ func ResolveSession(store SessionStore, id string) (*Session, bool, error) {
runtime.SetFinalizer(s, (*Session).Close)
return s, true, nil
}
-func LatestSession(store SessionStore) (*Session, bool, error) {
+
+func LatestSession(store cSessionStore) (*Session, bool, error) {
var r C.PantoSessionResult
if C.panto_session_store_latest(store.cptr(), &r) != C.PANTO_OK {
return nil, false, lastError()
@@ -273,7 +387,8 @@ func LatestSession(store SessionStore) (*Session, bool, error) {
runtime.SetFinalizer(s, (*Session).Close)
return s, true, nil
}
-func ListSessions(store SessionStore) ([]SessionInfo, error) {
+
+func ListSessions(store cSessionStore) ([]SessionInfo, error) {
var l C.PantoSessionInfoList
if C.panto_session_store_list(store.cptr(), &l) != C.PANTO_OK {
return nil, lastError()
@@ -288,12 +403,14 @@ func ListSessions(store SessionStore) ([]SessionInfo, error) {
}
return out, nil
}
+
func (s *Session) Close() {
if s != nil && s.ptr != nil {
C.panto_session_destroy(s.ptr)
s.ptr = nil
}
}
+
func (s *Session) Load() (*Conversation, error) {
var out *C.PantoConversation
if C.panto_session_load(s.ptr, &out) != C.PANTO_OK {
@@ -313,6 +430,7 @@ func NewConversation() (*Conversation, error) {
runtime.SetFinalizer(c, (*Conversation).Close)
return c, nil
}
+
func (c *Conversation) Close() {
if c != nil && c.owned && c.ptr != nil {
C.panto_conversation_destroy(c.ptr)
@@ -329,6 +447,7 @@ func (c *Conversation) AddSystemMessage(text string) error {
}
return nil
}
+
func (c *Conversation) ReplaceSystemMessage(text string) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -337,6 +456,7 @@ func (c *Conversation) ReplaceSystemMessage(text string) error {
}
return nil
}
+
func (c *Conversation) AddUserText(text string) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -345,6 +465,7 @@ func (c *Conversation) AddUserText(text string) error {
}
return nil
}
+
func (c *Conversation) AddAssistantText(text string, u *Usage) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -359,6 +480,7 @@ func (c *Conversation) AddAssistantText(text string, u *Usage) error {
}
return nil
}
+
func (c *Conversation) AddCompactionSummary(text string) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -376,6 +498,7 @@ type Message struct {
func (m Message) Role() MessageRole {
return MessageRole(C.panto_conversation_message_role(m.conv.ptr, C.size_t(m.index)))
}
+
func (m Message) Usage() *Usage {
if !bool(C.panto_conversation_message_has_usage(m.conv.ptr, C.size_t(m.index))) {
return nil
@@ -383,13 +506,23 @@ func (m Message) Usage() *Usage {
u := usage(C.panto_conversation_message_usage(m.conv.ptr, C.size_t(m.index)))
return &u
}
+
func (m Message) BlockCount() uint {
return uint(C.panto_conversation_message_block_count(m.conv.ptr, C.size_t(m.index)))
}
+
func (m Message) Block(i uint) ContentBlock {
return ContentBlock{conv: m.conv, msg: m.index, index: i}
}
+func (m Message) Snapshot() OwnedMessage {
+ blocks := make([]OwnedContentBlock, m.BlockCount())
+ for i := range blocks {
+ blocks[i] = m.Block(uint(i)).Snapshot()
+ }
+ return OwnedMessage{Role: m.Role(), Usage: m.Usage(), Content: blocks}
+}
+
type ContentBlock struct {
conv *Conversation
msg, index uint
@@ -398,22 +531,38 @@ type ContentBlock struct {
func (b ContentBlock) Tag() ContentBlockTag {
return ContentBlockTag(C.panto_conversation_block_tag(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+
func (b ContentBlock) Text() string {
return goSlice(C.panto_conversation_block_text(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+
func (b ContentBlock) ToolID() string {
return goSlice(C.panto_conversation_block_tool_id(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+
func (b ContentBlock) ToolName() string {
return goSlice(C.panto_conversation_block_tool_name(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+
func (b ContentBlock) ToolResultIsError() bool {
return bool(C.panto_conversation_block_tool_result_is_error(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+
func (b ContentBlock) SystemMode() SystemMode {
return SystemMode(C.panto_conversation_block_system_mode(b.conv.ptr, C.size_t(b.msg), C.size_t(b.index)))
}
+func (b ContentBlock) Snapshot() OwnedContentBlock {
+ return OwnedContentBlock{
+ Tag: b.Tag(),
+ Text: b.Text(),
+ ToolID: b.ToolID(),
+ ToolName: b.ToolName(),
+ IsError: b.ToolResultIsError(),
+ SystemMode: b.SystemMode(),
+ }
+}
+
func NewAgent(cfg Config) (*Agent, error) {
st, err := NewNullStore()
if err != nil {
@@ -426,6 +575,7 @@ func NewAgent(cfg Config) (*Agent, error) {
}
return NewAgentWithSession(cfg, sess, nil)
}
+
func NewAgentWithSession(cfg Config, session *Session, conv *Conversation) (*Agent, error) {
ccfg, strings, err := cfg.c()
if err != nil {
@@ -446,6 +596,7 @@ func NewAgentWithSession(cfg Config, session *Session, conv *Conversation) (*Age
runtime.SetFinalizer(a, (*Agent).Close)
return a, nil
}
+
func (a *Agent) Close() {
if a != nil && a.ptr != nil {
C.panto_agent_destroy(a.ptr)
@@ -456,6 +607,7 @@ func (a *Agent) SessionID() string { return goSlice(C.panto_agent_session_id(a.p
func (a *Agent) Conversation() *Conversation {
return &Conversation{ptr: C.panto_agent_conversation(a.ptr), owned: false}
}
+
func (a *Agent) SetConfig(cfg Config) error {
ccfg, strings, err := cfg.c()
if err != nil {
@@ -467,6 +619,7 @@ func (a *Agent) SetConfig(cfg Config) error {
}
return nil
}
+
func (a *Agent) Compact(overrideSystemPrompt, extraInstructions *string) (CompactionResult, error) {
var co, ce *C.char
if overrideSystemPrompt != nil {
@@ -481,8 +634,13 @@ func (a *Agent) Compact(overrideSystemPrompt, extraInstructions *string) (Compac
if C.panto_agent_compact(a.ptr, co, ce, &out) != C.PANTO_OK {
return CompactionResult{}, lastError()
}
- return CompactionResult{Compacted: bool(out.compacted), KeptTurns: uint(out.kept_turns), SummarizedMessages: uint(out.summarized_messages)}, nil
+ return CompactionResult{
+ Compacted: bool(out.compacted),
+ KeptTurns: uint(out.kept_turns),
+ SummarizedMessages: uint(out.summarized_messages),
+ }, nil
}
+
func (a *Agent) Run(userText string) (*Stream, error) {
ct := C.CString(userText)
defer C.free(unsafe.Pointer(ct))
@@ -494,6 +652,7 @@ func (a *Agent) Run(userText string) (*Stream, error) {
runtime.SetFinalizer(s, (*Stream).Close)
return s, nil
}
+
func (a *Agent) AddSystemMessage(text string) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -502,6 +661,7 @@ func (a *Agent) AddSystemMessage(text string) error {
}
return nil
}
+
func (a *Agent) SetSystemPrompt(text string) error {
ct := C.CString(text)
defer C.free(unsafe.Pointer(ct))
@@ -517,6 +677,7 @@ func (s *Stream) Close() {
s.ptr = nil
}
}
+
func (s *Stream) Next() (Event, bool, error) {
if s.err != nil {
return Event{}, false, s.err
@@ -562,6 +723,7 @@ func (s *Stream) Iter() iter.Seq[Event] {
}
}
}
+
func (s *Stream) Chan() <-chan Event {
ch := make(chan Event)
go func() {
@@ -606,6 +768,7 @@ func (cfg Config) c() (C.PantoConfig, cStrings, error) {
out.retry.jitter = C.bool(cfg.Retry.Jitter)
return out, cs, nil
}
+
func (p ProviderConfig) c() (C.PantoProviderConfig, cStrings, error) {
var out C.PantoProviderConfig
var cs cStrings
@@ -621,6 +784,7 @@ func (p ProviderConfig) c() (C.PantoProviderConfig, cStrings, error) {
setAnthropic(&out, p.AnthropicMessages, &cs)
return out, cs, nil
}
+
func setOpenAI(out *C.PantoProviderConfig, cfg *OpenAIChatConfig, cs *cStrings) {
apiKey, baseURL, model := C.CString(cfg.APIKey), C.CString(cfg.BaseURL), C.CString(cfg.Model)
*cs = append(*cs, apiKey, baseURL, model)
@@ -631,6 +795,7 @@ func setOpenAI(out *C.PantoProviderConfig, cfg *OpenAIChatConfig, cs *cStrings)
c.reasoning = C.PantoReasoningEffort(cfg.Reasoning)
c.max_tokens = C.uint32_t(cfg.MaxTokens)
}
+
func setAnthropic(out *C.PantoProviderConfig, cfg *AnthropicMessagesConfig, cs *cStrings) {
apiKey, baseURL, model, ver := C.CString(cfg.APIKey), C.CString(cfg.BaseURL), C.CString(cfg.Model), C.CString(cfg.APIVersion)
*cs = append(*cs, apiKey, baseURL, model, ver)
@@ -674,7 +839,14 @@ func marshalEvent(cev *C.PantoEvent) Event {
}
case C.PANTO_EVENT_PROVIDER_RETRY:
r := (*C.PantoProviderRetry)(unsafe.Pointer(&cev.data[0]))
- ev.ProviderRetry = ProviderRetry{Attempt: uint(r.attempt), MaxAttempts: uint(r.max_attempts), DelayMS: uint64(r.delay_ms), ErrorName: goSlice(r.error_name), Message: goSlice(r.message), Compaction: bool(r.compaction)}
+ ev.ProviderRetry = ProviderRetry{
+ Attempt: uint(r.attempt),
+ MaxAttempts: uint(r.max_attempts),
+ DelayMS: uint64(r.delay_ms),
+ ErrorName: goSlice(r.error_name),
+ Message: goSlice(r.message),
+ Compaction: bool(r.compaction),
+ }
if r.has_status_code {
v := uint16(r.status_code)
ev.ProviderRetry.StatusCode = &v
@@ -692,18 +864,351 @@ func marshalEvent(cev *C.PantoEvent) Event {
}
return ev
}
+
func goSlice(s C.PantoSlice) string {
if s.ptr == nil || s.len == 0 {
return ""
}
return C.GoStringN((*C.char)(unsafe.Pointer(s.ptr)), C.int(s.len))
}
+
func usage(u C.PantoUsage) Usage {
- return Usage{Input: uint64(u.input), Output: uint64(u.output), CacheRead: uint64(u.cache_read), CacheWrite: uint64(u.cache_write), Reasoning: uint64(u.reasoning)}
+ return Usage{
+ Input: uint64(u.input),
+ Output: uint64(u.output),
+ CacheRead: uint64(u.cache_read),
+ CacheWrite: uint64(u.cache_write),
+ Reasoning: uint64(u.reasoning),
+ }
}
+
func cUsage(u Usage) C.PantoUsage {
- return C.PantoUsage{input: C.uint64_t(u.Input), output: C.uint64_t(u.Output), cache_read: C.uint64_t(u.CacheRead), cache_write: C.uint64_t(u.CacheWrite), reasoning: C.uint64_t(u.Reasoning)}
+ return C.PantoUsage{
+ input: C.uint64_t(u.Input),
+ output: C.uint64_t(u.Output),
+ cache_read: C.uint64_t(u.CacheRead),
+ cache_write: C.uint64_t(u.CacheWrite),
+ reasoning: C.uint64_t(u.Reasoning),
+ }
}
+
func sessionInfo(i C.PantoSessionInfo) SessionInfo {
- return SessionInfo{ID: goSlice(i.id), Created: goSlice(i.created), Modified: goSlice(i.modified), LastUserMessage: goSlice(i.last_user_message), BaseURL: goSlice(i.base_url), Model: goSlice(i.model), MessageCount: uint(i.message_count), APIStyle: APIStyle(i.api_style), Reasoning: ReasoningEffort(i.reasoning)}
+ return SessionInfo{
+ ID: goSlice(i.id),
+ Created: goSlice(i.created),
+ Modified: goSlice(i.modified),
+ LastUserMessage: goSlice(i.last_user_message),
+ BaseURL: goSlice(i.base_url),
+ Model: goSlice(i.model),
+ MessageCount: uint(i.message_count),
+ APIStyle: APIStyle(i.api_style),
+ Reasoning: ReasoningEffort(i.reasoning),
+ }
+}
+
+// Tool callback support.
+type (
+ ToolDecl struct{ Name, Description, SchemaJSON string }
+ ResultPart struct{ Text string }
+ ResultParts []ResultPart
+ ToolFunc func(input string) (ResultParts, error)
+ Tool struct {
+ Decl ToolDecl
+ Invoke ToolFunc
+ }
+)
+
+type (
+ ToolCall struct{ ToolName, Input string }
+ ToolSourceFunc func(calls []ToolCall) ([]ResultParts, []error, error)
+ ToolSource struct {
+ Name string
+ Tools []ToolDecl
+ InvokeBatch ToolSourceFunc
+ }
+)
+
+var (
+ callbackHandles = map[uintptr]any{}
+ nextCallbackHandle uintptr = 1
+)
+
+func saveCallback(v any) unsafe.Pointer {
+ h := nextCallbackHandle
+ nextCallbackHandle++
+ callbackHandles[h] = v
+ return unsafe.Pointer(h)
+}
+func dropCallback(p unsafe.Pointer) { delete(callbackHandles, uintptr(p)) }
+
+//export pantoGoToolInvoke
+func pantoGoToolInvoke(ctx unsafe.Pointer, input C.PantoSlice, out *C.PantoResultParts) C.PantoStatus {
+ t := callbackHandles[uintptr(ctx)].(Tool)
+ parts, err := t.Invoke(goSlice(input))
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ *out = makeCResultParts(parts)
+ return C.PANTO_OK
+}
+
+//export pantoGoToolSourceInvokeBatch
+func pantoGoToolSourceInvokeBatch(ctx unsafe.Pointer, calls *C.PantoToolCall, results *C.PantoToolCallResult, n C.size_t) C.PantoStatus {
+ src := callbackHandles[uintptr(ctx)].(ToolSource)
+ cc := unsafe.Slice(calls, int(n))
+ goCalls := make([]ToolCall, int(n))
+ for i, c := range cc {
+ goCalls[i] = ToolCall{ToolName: goSlice(c.tool_name), Input: goSlice(c.input)}
+ }
+ parts, errs, err := src.InvokeBatch(goCalls)
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ rr := unsafe.Slice(results, int(n))
+ for i := range rr {
+ if i < len(errs) && errs[i] != nil {
+ rr[i].ok = false
+ continue
+ }
+ rr[i].ok = true
+ if i < len(parts) {
+ rr[i].parts = makeCResultParts(parts[i])
+ }
+ }
+ return C.PANTO_OK
+}
+
+//export pantoGoCallbackDestroy
+func pantoGoCallbackDestroy(ctx unsafe.Pointer) { dropCallback(ctx) }
+
+func (a *Agent) RegisterTool(t Tool) error {
+ ctx := saveCallback(t)
+ d, free := cToolDecl(t.Decl)
+ defer free()
+ if C.panto_agent_register_tool(a.ptr, d, ctx, (C.PantoToolInvokeFn)(C.pantoGoToolInvoke), (C.PantoToolDestroyFn)(C.pantoGoCallbackDestroy)) != C.PANTO_OK {
+ dropCallback(ctx)
+ return lastError()
+ }
+ return nil
+}
+
+func (a *Agent) RegisterToolSource(src ToolSource) error {
+ ctx := saveCallback(src)
+ cname := cSlice(src.Name)
+ defer C.free(unsafe.Pointer(cname.ptr))
+ decls := make([]C.PantoToolDecl, len(src.Tools))
+ frees := make([]func(), 0, len(src.Tools))
+ for i, d := range src.Tools {
+ cd, fr := cToolDecl(d)
+ decls[i] = cd
+ frees = append(frees, fr)
+ }
+ defer func() {
+ for _, f := range frees {
+ f()
+ }
+ }()
+ var ptr *C.PantoToolDecl
+ if len(decls) > 0 {
+ ptr = &decls[0]
+ }
+ if C.panto_agent_register_tool_source(a.ptr, cname, ptr, C.size_t(len(decls)), ctx, (C.PantoToolSourceInvokeBatchFn)(C.pantoGoToolSourceInvokeBatch), (C.PantoToolDestroyFn)(C.pantoGoCallbackDestroy)) != C.PANTO_OK {
+ dropCallback(ctx)
+ return lastError()
+ }
+ return nil
+}
+
+func cToolDecl(d ToolDecl) (C.PantoToolDecl, func()) {
+ n := cSlice(d.Name)
+ desc := cSlice(d.Description)
+ schema := cSlice(d.SchemaJSON)
+ return C.PantoToolDecl{name: n, description: desc, schema_json: schema}, func() {
+ C.free(unsafe.Pointer(n.ptr))
+ C.free(unsafe.Pointer(desc.ptr))
+ C.free(unsafe.Pointer(schema.ptr))
+ }
+}
+
+func cSlice(s string) C.PantoSlice {
+ cs := C.CString(s)
+ return C.PantoSlice{ptr: (*C.uint8_t)(unsafe.Pointer(cs)), len: C.size_t(len(s))}
+}
+
+func makeCResultParts(parts ResultParts) C.PantoResultParts {
+ if len(parts) == 0 {
+ return C.PantoResultParts{}
+ }
+ arr := (*C.PantoResultPart)(C.calloc(C.size_t(len(parts)), C.size_t(unsafe.Sizeof(C.PantoResultPart{}))))
+ xs := unsafe.Slice(arr, len(parts))
+ for i, p := range parts {
+ cs := cSlice(p.Text)
+ xs[i].tag = C.PANTO_RESULT_TEXT
+ (*C.PantoSlice)(unsafe.Pointer(&xs[i].data[0])).ptr = cs.ptr
+ (*C.PantoSlice)(unsafe.Pointer(&xs[i].data[0])).len = cs.len
+ }
+ return C.PantoResultParts{ptr: arr, len: C.size_t(len(parts))}
+}
+
+func NewGoSessionStore(impl SessionStore) (*GoSessionStore, error) {
+ ctx := saveCallback(impl)
+ var out *C.PantoSessionStore
+ if C.panto_go_session_store_create(ctx, &out) != C.PANTO_OK {
+ dropCallback(ctx)
+ return nil, lastError()
+ }
+ s := &GoSessionStore{ptr: out, impl: impl}
+ runtime.SetFinalizer(s, (*GoSessionStore).Close)
+ return s, nil
+}
+func (s *GoSessionStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *GoSessionStore) Create() (*Session, error) { return CreateSession(s) }
+func (s *GoSessionStore) List() ([]SessionInfo, error) { return ListSessions(s) }
+func (s *GoSessionStore) Resolve(id string) (*Session, bool, error) { return ResolveSession(s, id) }
+func (s *GoSessionStore) Latest() (*Session, bool, error) { return LatestSession(s) }
+func (s *GoSessionStore) Load(id string) (*Conversation, bool, error) { return s.impl.Load(id) }
+func (s *GoSessionStore) AppendMessages(sessionID string, messages []PersistentMessage) error {
+ return s.impl.AppendMessages(sessionID, messages)
+}
+
+func (s *GoSessionStore) Close() {
+ if s != nil && s.ptr != nil {
+ C.panto_session_store_destroy(s.ptr)
+ s.ptr = nil
+ }
+}
+
+//export pantoGoStoreCreate
+func pantoGoStoreCreate(ctx unsafe.Pointer, out **C.PantoSession) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ s, err := impl.Create()
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ *out = s.ptr
+ runtime.SetFinalizer(s, nil)
+ return C.PANTO_OK
+}
+
+//export pantoGoStoreList
+func pantoGoStoreList(ctx unsafe.Pointer, out *C.PantoSessionInfoList) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ infos, err := impl.List()
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ if len(infos) == 0 {
+ *out = C.PantoSessionInfoList{}
+ return C.PANTO_OK
+ }
+ arr := (*C.PantoSessionInfo)(C.calloc(C.size_t(len(infos)), C.size_t(unsafe.Sizeof(C.PantoSessionInfo{}))))
+ xs := unsafe.Slice(arr, len(infos))
+ for i, info := range infos {
+ xs[i] = cSessionInfo(info)
+ }
+ *out = C.PantoSessionInfoList{ptr: arr, len: C.size_t(len(infos))}
+ return C.PANTO_OK
+}
+
+//export pantoGoStoreFreeSessionInfos
+func pantoGoStoreFreeSessionInfos(ctx unsafe.Pointer, infos C.PantoSessionInfoList) {
+ _ = ctx
+ freeCSessionInfoList(infos)
+}
+
+//export pantoGoStoreResolve
+func pantoGoStoreResolve(ctx unsafe.Pointer, id C.PantoSlice, out *C.PantoSessionResult) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ s, ok, err := impl.Resolve(goSlice(id))
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ if !ok {
+ *out = C.PantoSessionResult{}
+ return C.PANTO_OK
+ }
+ *out = C.PantoSessionResult{found: true, session: s.ptr}
+ runtime.SetFinalizer(s, nil)
+ return C.PANTO_OK
+}
+
+//export pantoGoStoreLatest
+func pantoGoStoreLatest(ctx unsafe.Pointer, out *C.PantoSessionResult) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ s, ok, err := impl.Latest()
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ if !ok {
+ *out = C.PantoSessionResult{}
+ return C.PANTO_OK
+ }
+ *out = C.PantoSessionResult{found: true, session: s.ptr}
+ runtime.SetFinalizer(s, nil)
+ return C.PANTO_OK
+}
+
+//export pantoGoStoreLoad
+func pantoGoStoreLoad(ctx unsafe.Pointer, id C.PantoSlice, out **C.PantoConversation) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ c, ok, err := impl.Load(goSlice(id))
+ if err != nil {
+ return C.PANTO_ERROR
+ }
+ if !ok {
+ return C.PANTO_ERROR
+ }
+ *out = c.ptr
+ c.owned = false
+ runtime.SetFinalizer(c, nil)
+ return C.PANTO_OK
+}
+
+//export pantoGoStoreAppendMessages
+func pantoGoStoreAppendMessages(ctx unsafe.Pointer, sessionID C.PantoSlice, messages *C.PantoPersistentMessage, n C.size_t) C.PantoStatus {
+ impl := callbackHandles[uintptr(ctx)].(SessionStore)
+ xs := unsafe.Slice(messages, int(n))
+ out := make([]PersistentMessage, int(n))
+ for i, m := range xs {
+ var u *Usage
+ if m.has_usage {
+ uu := usage(m.usage)
+ u = &uu
+ }
+ out[i] = PersistentMessage{Role: MessageRole(m.role), Usage: u, Metadata: goSlice(m.metadata)}
+ }
+ if err := impl.AppendMessages(goSlice(sessionID), out); err != nil {
+ return C.PANTO_ERROR
+ }
+ return C.PANTO_OK
+}
+
+func cSessionInfo(i SessionInfo) C.PantoSessionInfo {
+ return C.PantoSessionInfo{
+ id: cSlice(i.ID),
+ created: cSlice(i.Created),
+ modified: cSlice(i.Modified),
+ last_user_message: cSlice(i.LastUserMessage),
+ base_url: cSlice(i.BaseURL),
+ model: cSlice(i.Model),
+ message_count: C.size_t(i.MessageCount),
+ api_style: C.PantoAPIStyle(i.APIStyle),
+ reasoning: C.PantoReasoningEffort(i.Reasoning),
+ }
+}
+
+func freeCSessionInfoList(l C.PantoSessionInfoList) {
+ if l.ptr == nil {
+ return
+ }
+ xs := unsafe.Slice(l.ptr, int(l.len))
+ for _, i := range xs {
+ C.free(unsafe.Pointer(i.id.ptr))
+ C.free(unsafe.Pointer(i.created.ptr))
+ C.free(unsafe.Pointer(i.modified.ptr))
+ C.free(unsafe.Pointer(i.last_user_message.ptr))
+ C.free(unsafe.Pointer(i.base_url.ptr))
+ C.free(unsafe.Pointer(i.model.ptr))
+ }
+ C.free(unsafe.Pointer(l.ptr))
}