From 27e37c62bc1483c0489d6db01344906472a1f8d3 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 11 Jun 2026 11:15:40 -0600 Subject: libpanto-go some libpanto-c coverage expansion was needed --- libpanto-go/README.md | 48 ++++ libpanto-go/go.mod | 3 + libpanto-go/panto.go | 709 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 760 insertions(+) create mode 100644 libpanto-go/README.md create mode 100644 libpanto-go/go.mod create mode 100644 libpanto-go/panto.go (limited to 'libpanto-go') 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 +#include +*/ +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)} +} -- cgit v1.3