summaryrefslogtreecommitdiff
path: root/libpanto-go
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-11 11:15:40 -0600
committert <t@tjp.lol>2026-06-11 11:15:52 -0600
commit27e37c62bc1483c0489d6db01344906472a1f8d3 (patch)
tree0c80d2c9e1fd1020e56d34d389d9c743085c2a50 /libpanto-go
parente2477f7d2d2a4eab870a2bd61534cfd146907915 (diff)
libpanto-go
some libpanto-c coverage expansion was needed
Diffstat (limited to 'libpanto-go')
-rw-r--r--libpanto-go/README.md48
-rw-r--r--libpanto-go/go.mod3
-rw-r--r--libpanto-go/panto.go709
3 files changed, 760 insertions, 0 deletions
diff --git a/libpanto-go/README.md b/libpanto-go/README.md
new file mode 100644
index 0000000..a19e788
--- /dev/null
+++ b/libpanto-go/README.md
@@ -0,0 +1,48 @@
+# libpanto-go
+
+Idiomatic Go bindings for `libpanto-c` via cgo.
+
+## Build
+
+Build the C ABI first:
+
+```sh
+cd ../libpanto-c
+zig build
+```
+
+Then use the Go package from this directory:
+
+```sh
+go test ./...
+```
+
+At runtime, make sure `../libpanto-c/zig-out/lib` (or the installed equivalent) is on your platform's dynamic library path.
+
+## Example
+
+```go
+panto.Init()
+defer panto.Deinit()
+
+agent, err := panto.NewAgent(panto.Config{
+ Provider: panto.ProviderConfig{
+ OpenAIChat: &panto.OpenAIChatConfig{
+ APIKey: os.Getenv("OPENAI_API_KEY"),
+ BaseURL: "https://api.openai.com/v1",
+ Model: "gpt-4.1-mini",
+ },
+ },
+})
+if err != nil { /* handle */ }
+defer agent.Close()
+
+stream, err := agent.Run("hello")
+if err != nil { /* handle */ }
+defer stream.Close()
+
+for ev := range stream.Iter() {
+ // ev.Tag identifies the active payload.
+}
+if err := stream.Err(); err != nil { /* handle */ }
+```
diff --git a/libpanto-go/go.mod b/libpanto-go/go.mod
new file mode 100644
index 0000000..805022f
--- /dev/null
+++ b/libpanto-go/go.mod
@@ -0,0 +1,3 @@
+module github.com/travisjeffery/pantograph/libpanto-go
+
+go 1.24
diff --git a/libpanto-go/panto.go b/libpanto-go/panto.go
new file mode 100644
index 0000000..afb66a9
--- /dev/null
+++ b/libpanto-go/panto.go
@@ -0,0 +1,709 @@
+package panto
+
+/*
+#cgo CFLAGS: -I../libpanto-c/include
+#cgo LDFLAGS: -L../libpanto-c/zig-out/lib -lpanto
+#include <panto.h>
+#include <stdlib.h>
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "iter"
+ "runtime"
+ "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
+
+const (
+ OpenAIChat APIStyle = iota
+ AnthropicMessages
+)
+const (
+ ReasoningDefault ReasoningEffort = iota
+ ReasoningOff
+ ReasoningMinimal
+ ReasoningLow
+ ReasoningMedium
+ ReasoningHigh
+)
+const (
+ ThinkingDisabled Thinking = iota
+ ThinkingEnabled
+ ThinkingAdaptive
+)
+const (
+ EffortLow Effort = iota
+ EffortMedium
+ EffortHigh
+ EffortXHigh
+ EffortMax
+)
+const (
+ RoleSystem MessageRole = iota
+ RoleUser
+ RoleAssistant
+)
+const (
+ BlockText ContentBlockTag = iota
+ BlockThinking
+ BlockToolUse
+ BlockToolResult
+ BlockSystem
+ BlockCompactionSummary
+)
+const (
+ SystemAppend SystemMode = iota
+ SystemReplace
+)
+const (
+ EventMessageStart EventTag = iota
+ EventBlockStart
+ EventToolDetails
+ EventContentDelta
+ EventBlockComplete
+ EventMessageComplete
+ EventProviderRetry
+ EventToolDispatchStart
+ EventToolDispatchComplete
+ EventTurnComplete
+)
+
+type Config struct {
+ Provider ProviderConfig
+ Compaction CompactionConfig
+ Retry RetryConfig
+}
+type ProviderConfig struct {
+ OpenAIChat *OpenAIChatConfig
+ AnthropicMessages *AnthropicMessagesConfig
+}
+type OpenAIChatConfig struct {
+ APIKey, BaseURL, Model string
+ Reasoning ReasoningEffort
+ MaxTokens uint32
+}
+type AnthropicMessagesConfig struct {
+ APIKey, BaseURL, Model, APIVersion string
+ MaxTokens uint32
+ Thinking Thinking
+ Effort Effort
+ ThinkingBudgetTokens *uint32
+ ThinkingInterleaved bool
+}
+type CompactionConfig struct {
+ KeepVerbatim uint32
+ Model *ProviderConfig
+ CompactionPrompt *string
+}
+type RetryConfig struct {
+ MaxAttempts uint
+ InitialDelayMS, MaxDelayMS uint64
+ Multiplier float64
+ Jitter bool
+}
+type Usage struct{ Input, Output, CacheRead, CacheWrite, Reasoning uint64 }
+type CompactionResult struct {
+ Compacted bool
+ KeptTurns, SummarizedMessages uint
+}
+
+type Event struct {
+ Tag EventTag
+ MessageStart MessageRole
+ BlockStart BlockStart
+ ToolDetails ToolDetails
+ ContentDelta ContentDelta
+ BlockComplete BlockComplete
+ MessageComplete MessageComplete
+ ProviderRetry ProviderRetry
+ ToolDispatchStart ToolDispatchStart
+ ToolDispatchComplete ToolDispatchComplete
+}
+type BlockStart struct {
+ BlockType ContentBlockTag
+ Index uint
+}
+type ToolDetails struct {
+ Index uint
+ ID, Name string
+}
+type ContentDelta struct {
+ Index uint
+ Delta string
+}
+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 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 SessionStore interface {
+ cptr() *C.PantoSessionStore
+ Close()
+}
+type NullStore struct{ ptr *C.PantoSessionStore }
+type FileSystemJSONLStore struct{ ptr *C.PantoSessionStore }
+
+type cStrings []*C.char
+
+func (cs cStrings) free() {
+ for _, s := range cs {
+ C.free(unsafe.Pointer(s))
+ }
+}
+func Init() { C.panto_init() }
+func Deinit() { C.panto_deinit() }
+func lastError() error {
+ s := C.panto_last_error()
+ if s.ptr == nil || s.len == 0 {
+ return errors.New("libpanto error")
+ }
+ return errors.New(C.GoStringN((*C.char)(unsafe.Pointer(s.ptr)), C.int(s.len)))
+}
+
+func NewNullStore() (*NullStore, error) {
+ var out *C.PantoSessionStore
+ if C.panto_null_store_create(&out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ s := &NullStore{out}
+ runtime.SetFinalizer(s, (*NullStore).Close)
+ return s, nil
+}
+func (s *NullStore) cptr() *C.PantoSessionStore { return s.ptr }
+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))
+ cm := C.CString(metadata)
+ defer C.free(unsafe.Pointer(cm))
+ var out *C.PantoSessionStore
+ if C.panto_file_system_jsonl_store_create(cd, cm, &out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ s := &FileSystemJSONLStore{out}
+ runtime.SetFinalizer(s, (*FileSystemJSONLStore).Close)
+ return s, nil
+}
+func (s *FileSystemJSONLStore) cptr() *C.PantoSessionStore { return s.ptr }
+func (s *FileSystemJSONLStore) Close() {
+ if s != nil && s.ptr != nil {
+ C.panto_session_store_destroy(s.ptr)
+ s.ptr = nil
+ }
+}
+
+func CreateSession(store SessionStore) (*Session, error) {
+ var out *C.PantoSession
+ if C.panto_session_store_create_session(store.cptr(), &out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ s := &Session{out}
+ runtime.SetFinalizer(s, (*Session).Close)
+ return s, nil
+}
+func ResolveSession(store SessionStore, id string) (*Session, bool, error) {
+ cid := C.CString(id)
+ defer C.free(unsafe.Pointer(cid))
+ var r C.PantoSessionResult
+ if C.panto_session_store_resolve(store.cptr(), cid, &r) != C.PANTO_OK {
+ return nil, false, lastError()
+ }
+ if !bool(r.found) {
+ return nil, false, nil
+ }
+ s := &Session{r.session}
+ runtime.SetFinalizer(s, (*Session).Close)
+ return s, true, nil
+}
+func LatestSession(store SessionStore) (*Session, bool, error) {
+ var r C.PantoSessionResult
+ if C.panto_session_store_latest(store.cptr(), &r) != C.PANTO_OK {
+ return nil, false, lastError()
+ }
+ if !bool(r.found) {
+ return nil, false, nil
+ }
+ s := &Session{r.session}
+ runtime.SetFinalizer(s, (*Session).Close)
+ return s, true, nil
+}
+func ListSessions(store SessionStore) ([]SessionInfo, error) {
+ var l C.PantoSessionInfoList
+ if C.panto_session_store_list(store.cptr(), &l) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ defer C.panto_session_info_list_destroy(l)
+ out := make([]SessionInfo, int(l.len))
+ if l.ptr != nil {
+ xs := unsafe.Slice(l.ptr, int(l.len))
+ for i, x := range xs {
+ out[i] = sessionInfo(x)
+ }
+ }
+ 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 {
+ return nil, lastError()
+ }
+ c := &Conversation{ptr: out, owned: true}
+ runtime.SetFinalizer(c, (*Conversation).Close)
+ return c, nil
+}
+
+func NewConversation() (*Conversation, error) {
+ var out *C.PantoConversation
+ if C.panto_conversation_create(&out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ c := &Conversation{ptr: out, owned: true}
+ 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)
+ c.ptr = nil
+ }
+}
+func (c *Conversation) MessageCount() uint { return uint(C.panto_conversation_message_count(c.ptr)) }
+func (c *Conversation) Message(i uint) Message { return Message{conv: c, index: i} }
+func (c *Conversation) AddSystemMessage(text string) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ if C.panto_conversation_add_system_message(c.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (c *Conversation) ReplaceSystemMessage(text string) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ if C.panto_conversation_replace_system_message(c.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (c *Conversation) AddUserText(text string) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ if C.panto_conversation_add_user_text(c.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (c *Conversation) AddAssistantText(text string, u *Usage) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ var cu C.PantoUsage
+ has := false
+ if u != nil {
+ cu = cUsage(*u)
+ has = true
+ }
+ if C.panto_conversation_add_assistant_text(c.ptr, ct, C.bool(has), cu) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (c *Conversation) AddCompactionSummary(text string) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ if C.panto_conversation_add_compaction_summary(c.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+
+type Message struct {
+ conv *Conversation
+ index uint
+}
+
+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
+ }
+ 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}
+}
+
+type ContentBlock struct {
+ conv *Conversation
+ msg, index uint
+}
+
+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 NewAgent(cfg Config) (*Agent, error) {
+ st, err := NewNullStore()
+ if err != nil {
+ return nil, err
+ }
+ sess, err := CreateSession(st)
+ st.Close()
+ if err != nil {
+ return nil, err
+ }
+ return NewAgentWithSession(cfg, sess, nil)
+}
+func NewAgentWithSession(cfg Config, session *Session, conv *Conversation) (*Agent, error) {
+ ccfg, strings, err := cfg.c()
+ if err != nil {
+ return nil, err
+ }
+ defer strings.free()
+ var cconv *C.PantoConversation
+ if conv != nil {
+ cconv = conv.ptr
+ conv.owned = false
+ runtime.SetFinalizer(conv, nil)
+ }
+ var out *C.PantoAgent
+ if C.panto_agent_create(&ccfg, session.ptr, cconv, &out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ a := &Agent{out}
+ runtime.SetFinalizer(a, (*Agent).Close)
+ return a, nil
+}
+func (a *Agent) Close() {
+ if a != nil && a.ptr != nil {
+ C.panto_agent_destroy(a.ptr)
+ a.ptr = nil
+ }
+}
+func (a *Agent) SessionID() string { return goSlice(C.panto_agent_session_id(a.ptr)) }
+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 {
+ return err
+ }
+ defer strings.free()
+ if C.panto_agent_set_config(a.ptr, &ccfg) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (a *Agent) Compact(overrideSystemPrompt, extraInstructions *string) (CompactionResult, error) {
+ var co, ce *C.char
+ if overrideSystemPrompt != nil {
+ co = C.CString(*overrideSystemPrompt)
+ defer C.free(unsafe.Pointer(co))
+ }
+ if extraInstructions != nil {
+ ce = C.CString(*extraInstructions)
+ defer C.free(unsafe.Pointer(ce))
+ }
+ var out C.PantoCompactionResult
+ 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
+}
+func (a *Agent) Run(userText string) (*Stream, error) {
+ ct := C.CString(userText)
+ defer C.free(unsafe.Pointer(ct))
+ var out *C.PantoStream
+ if C.panto_agent_run(a.ptr, ct, &out) != C.PANTO_OK {
+ return nil, lastError()
+ }
+ s := &Stream{ptr: out}
+ 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))
+ if C.panto_agent_add_system_message(a.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+func (a *Agent) SetSystemPrompt(text string) error {
+ ct := C.CString(text)
+ defer C.free(unsafe.Pointer(ct))
+ if C.panto_agent_set_system_prompt(a.ptr, ct) != C.PANTO_OK {
+ return lastError()
+ }
+ return nil
+}
+
+func (s *Stream) Close() {
+ if s != nil && s.ptr != nil {
+ C.panto_stream_destroy(s.ptr)
+ s.ptr = nil
+ }
+}
+func (s *Stream) Next() (Event, bool, error) {
+ if s.err != nil {
+ return Event{}, false, s.err
+ }
+ if s.done {
+ return Event{}, false, nil
+ }
+ var cev C.PantoEvent
+ status := C.panto_stream_next(s.ptr, &cev)
+ switch status {
+ case C.PANTO_NEXT_EVENT:
+ defer C.panto_event_free(&cev)
+ ev := marshalEvent(&cev)
+ if ev.Tag == EventTurnComplete {
+ s.done = true
+ }
+ return ev, true, nil
+ case C.PANTO_NEXT_DONE:
+ s.done = true
+ return Event{}, false, nil
+ case C.PANTO_NEXT_ERROR:
+ s.err = lastError()
+ return Event{}, false, s.err
+ default:
+ s.err = fmt.Errorf("unknown panto next status %d", int(status))
+ return Event{}, false, s.err
+ }
+}
+func (s *Stream) Err() error { return s.err }
+func (s *Stream) Iter() iter.Seq[Event] {
+ return func(yield func(Event) bool) {
+ for {
+ ev, ok, err := s.Next()
+ if err != nil || !ok {
+ return
+ }
+ if !yield(ev) {
+ return
+ }
+ if ev.Tag == EventTurnComplete {
+ return
+ }
+ }
+ }
+}
+func (s *Stream) Chan() <-chan Event {
+ ch := make(chan Event)
+ go func() {
+ defer close(ch)
+ for ev := range s.Iter() {
+ ch <- ev
+ }
+ }()
+ return ch
+}
+
+func (cfg Config) c() (C.PantoConfig, cStrings, error) {
+ var cs cStrings
+ provider, pstrings, err := cfg.Provider.c()
+ cs = append(cs, pstrings...)
+ if err != nil {
+ cs.free()
+ return C.PantoConfig{}, nil, err
+ }
+ out := C.PantoConfig{provider: provider}
+ out.compaction.keep_verbatim = C.uint32_t(cfg.Compaction.KeepVerbatim)
+ if cfg.Compaction.Model != nil {
+ m, ms, err := cfg.Compaction.Model.c()
+ cs = append(cs, ms...)
+ if err != nil {
+ cs.free()
+ return C.PantoConfig{}, nil, err
+ }
+ out.compaction.has_model = true
+ out.compaction.model = m
+ }
+ if cfg.Compaction.CompactionPrompt != nil {
+ c := C.CString(*cfg.Compaction.CompactionPrompt)
+ cs = append(cs, c)
+ out.compaction.has_compaction_prompt = true
+ out.compaction.compaction_prompt = c
+ }
+ out.retry.max_attempts = C.size_t(cfg.Retry.MaxAttempts)
+ out.retry.initial_delay_ms = C.uint64_t(cfg.Retry.InitialDelayMS)
+ out.retry.max_delay_ms = C.uint64_t(cfg.Retry.MaxDelayMS)
+ out.retry.multiplier = C.double(cfg.Retry.Multiplier)
+ 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
+ if p.OpenAIChat != nil == (p.AnthropicMessages != nil) {
+ return out, nil, errors.New("exactly one provider config must be set")
+ }
+ if p.OpenAIChat != nil {
+ out.tag = C.PANTO_OPENAI_CHAT
+ setOpenAI(&out, p.OpenAIChat, &cs)
+ return out, cs, nil
+ }
+ out.tag = C.PANTO_ANTHROPIC_MESSAGES
+ 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)
+ c := (*C.PantoOpenAIChatConfig)(unsafe.Pointer(&out.data[0]))
+ c.api_key = apiKey
+ c.base_url = baseURL
+ c.model = model
+ 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)
+ c := (*C.PantoAnthropicMessagesConfig)(unsafe.Pointer(&out.data[0]))
+ c.api_key = apiKey
+ c.base_url = baseURL
+ c.model = model
+ c.api_version = ver
+ c.max_tokens = C.uint32_t(cfg.MaxTokens)
+ c.thinking = C.PantoThinking(cfg.Thinking)
+ c.effort = C.PantoEffort(cfg.Effort)
+ c.thinking_interleaved = C.bool(cfg.ThinkingInterleaved)
+ if cfg.ThinkingBudgetTokens != nil {
+ c.has_thinking_budget_tokens = true
+ c.thinking_budget_tokens = C.uint32_t(*cfg.ThinkingBudgetTokens)
+ }
+}
+
+func marshalEvent(cev *C.PantoEvent) Event {
+ ev := Event{Tag: EventTag(cev.tag)}
+ switch cev.tag {
+ case C.PANTO_EVENT_MESSAGE_START:
+ ev.MessageStart = MessageRole(*(*C.PantoMessageRole)(unsafe.Pointer(&cev.data[0])))
+ case C.PANTO_EVENT_BLOCK_START:
+ b := (*C.PantoBlockStart)(unsafe.Pointer(&cev.data[0]))
+ ev.BlockStart = BlockStart{BlockType: ContentBlockTag(b.block_type), Index: uint(b.index)}
+ case C.PANTO_EVENT_TOOL_DETAILS:
+ t := (*C.PantoToolDetails)(unsafe.Pointer(&cev.data[0]))
+ ev.ToolDetails = ToolDetails{Index: uint(t.index), ID: goSlice(t.id), Name: goSlice(t.name)}
+ case C.PANTO_EVENT_CONTENT_DELTA:
+ d := (*C.PantoContentDelta)(unsafe.Pointer(&cev.data[0]))
+ ev.ContentDelta = ContentDelta{Index: uint(d.index), Delta: goSlice(d.delta)}
+ case C.PANTO_EVENT_BLOCK_COMPLETE:
+ b := (*C.PantoBlockComplete)(unsafe.Pointer(&cev.data[0]))
+ ev.BlockComplete = BlockComplete{Index: uint(b.index), BlockType: ContentBlockTag(b.block_type)}
+ case C.PANTO_EVENT_MESSAGE_COMPLETE:
+ m := (*C.PantoMessageComplete)(unsafe.Pointer(&cev.data[0]))
+ if m.has_usage {
+ u := usage(m.usage)
+ ev.MessageComplete.Usage = &u
+ }
+ 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)}
+ if r.has_status_code {
+ v := uint16(r.status_code)
+ ev.ProviderRetry.StatusCode = &v
+ }
+ if r.has_retry_after_ms {
+ v := uint64(r.retry_after_ms)
+ ev.ProviderRetry.RetryAfterMS = &v
+ }
+ case C.PANTO_EVENT_TOOL_DISPATCH_START:
+ t := (*C.PantoToolDispatchStart)(unsafe.Pointer(&cev.data[0]))
+ ev.ToolDispatchStart = ToolDispatchStart{Count: uint(t.count)}
+ case C.PANTO_EVENT_TOOL_DISPATCH_COMPLETE:
+ t := (*C.PantoToolDispatchComplete)(unsafe.Pointer(&cev.data[0]))
+ ev.ToolDispatchComplete = ToolDispatchComplete{MessageIndex: uint(t.message_index)}
+ }
+ 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)}
+}
+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)}
+}
+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)}
+}