summaryrefslogtreecommitdiff
path: root/libpanto-go/panto.go
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-07 12:28:16 -0600
committert <t@tjp.lol>2026-07-07 13:10:56 -0600
commit91c270db024c036a360f12052b2bd10e4b04a926 (patch)
tree1896fbde88dfe0a4118f4f7f24569aee0f63b583 /libpanto-go/panto.go
parentf83578fdc9264019a1a1cef8c5484a161167d3dd (diff)
move libpanto-go code into a panto subdirectory for package namingHEADmain
Diffstat (limited to 'libpanto-go/panto.go')
-rw-r--r--libpanto-go/panto.go1592
1 files changed, 0 insertions, 1592 deletions
diff --git a/libpanto-go/panto.go b/libpanto-go/panto.go
deleted file mode 100644
index abaa84e..0000000
--- a/libpanto-go/panto.go
+++ /dev/null
@@ -1,1592 +0,0 @@
-package panto
-
-/*
-#cgo CFLAGS: -I../libpanto-c/include
-#cgo LDFLAGS: -L../zig-out/lib -lpanto
-#cgo darwin LDFLAGS: -Wl,-rpath,${SRCDIR}/../zig-out/lib
-#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 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"
-
-import (
- "errors"
- "fmt"
- "iter"
- "runtime"
- "unsafe"
-)
-
-type (
- APIStyle int
- ReasoningEffort int
- Thinking int
- Effort int
- MessageRole int
- ContentBlockTag int
- EventTag int
- SystemMode int
-)
-
-// These enums mirror libpanto-c's C enums. Each constant wraps its C value
-// (rather than a hand-kept iota sequence) so the Go side can never silently
-// drift from the ABI: if a variant is inserted in panto.h, the values shift
-// with it instead of desyncing. Keep one Go constant per C value.
-const (
- OpenAIChat APIStyle = APIStyle(C.PANTO_OPENAI_CHAT)
- AnthropicMessages APIStyle = APIStyle(C.PANTO_ANTHROPIC_MESSAGES)
- OpenAIResponses APIStyle = APIStyle(C.PANTO_OPENAI_RESPONSES)
- OpenAICodexResponses APIStyle = APIStyle(C.PANTO_OPENAI_CODEX_RESPONSES)
-)
-
-const (
- ReasoningDefault ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_DEFAULT)
- ReasoningOff ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_OFF)
- ReasoningMinimal ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_MINIMAL)
- ReasoningLow ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_LOW)
- ReasoningMedium ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_MEDIUM)
- ReasoningHigh ReasoningEffort = ReasoningEffort(C.PANTO_REASONING_HIGH)
-)
-
-const (
- ThinkingDisabled Thinking = Thinking(C.PANTO_THINKING_DISABLED)
- ThinkingEnabled Thinking = Thinking(C.PANTO_THINKING_ENABLED)
- ThinkingAdaptive Thinking = Thinking(C.PANTO_THINKING_ADAPTIVE)
-)
-
-const (
- EffortLow Effort = Effort(C.PANTO_EFFORT_LOW)
- EffortMedium Effort = Effort(C.PANTO_EFFORT_MEDIUM)
- EffortHigh Effort = Effort(C.PANTO_EFFORT_HIGH)
- EffortXHigh Effort = Effort(C.PANTO_EFFORT_XHIGH)
- EffortMax Effort = Effort(C.PANTO_EFFORT_MAX)
-)
-
-const (
- RoleSystem MessageRole = MessageRole(C.PANTO_ROLE_SYSTEM)
- RoleUser MessageRole = MessageRole(C.PANTO_ROLE_USER)
- RoleAssistant MessageRole = MessageRole(C.PANTO_ROLE_ASSISTANT)
-)
-
-const (
- BlockText ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_TEXT)
- BlockThinking ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_THINKING)
- BlockToolUse ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_TOOL_USE)
- BlockToolResult ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_TOOL_RESULT)
- BlockSystem ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_SYSTEM)
- BlockCompactionSummary ContentBlockTag = ContentBlockTag(C.PANTO_BLOCK_COMPACTION_SUMMARY)
-)
-
-const (
- SystemAppend SystemMode = SystemMode(C.PANTO_SYSTEM_APPEND)
- SystemReplace SystemMode = SystemMode(C.PANTO_SYSTEM_REPLACE)
-)
-
-const (
- EventMessageStart EventTag = EventTag(C.PANTO_EVENT_MESSAGE_START)
- EventBlockStart EventTag = EventTag(C.PANTO_EVENT_BLOCK_START)
- EventToolDetails EventTag = EventTag(C.PANTO_EVENT_TOOL_DETAILS)
- EventContentDelta EventTag = EventTag(C.PANTO_EVENT_CONTENT_DELTA)
- EventBlockComplete EventTag = EventTag(C.PANTO_EVENT_BLOCK_COMPLETE)
- EventMessageComplete EventTag = EventTag(C.PANTO_EVENT_MESSAGE_COMPLETE)
- EventProviderRetry EventTag = EventTag(C.PANTO_EVENT_PROVIDER_RETRY)
- EventToolDispatchStart EventTag = EventTag(C.PANTO_EVENT_TOOL_DISPATCH_START)
- EventToolDispatchResult EventTag = EventTag(C.PANTO_EVENT_TOOL_DISPATCH_RESULT)
- EventToolDispatchComplete EventTag = EventTag(C.PANTO_EVENT_TOOL_DISPATCH_COMPLETE)
- EventTurnComplete EventTag = EventTag(C.PANTO_EVENT_TURN_COMPLETE)
-)
-
-type Config struct {
- Provider ProviderConfig
- Compaction CompactionConfig
- Retry RetryConfig
-}
-type ProviderConfig struct {
- OpenAIChat *OpenAIChatConfig
- AnthropicMessages *AnthropicMessagesConfig
- OpenAIResponses *OpenAIResponsesConfig
-}
-type OpenAIChatConfig struct {
- APIKey, BaseURL, Model string
- Reasoning ReasoningEffort
- MaxTokens uint32
-}
-type OpenAIResponsesConfig 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 }
- CompactionResult struct {
- Compacted bool
- KeptTurns, SummarizedMessages uint
- }
-)
-
-// Event is one streaming-progress notification from a Stream. The concrete
-// type is one of the *Event structs below; type-switch on it, or branch on
-// EventType() when only the kind matters. The terminal event of a turn is
-// TurnCompleteEvent (EventType() == EventTurnComplete).
-type Event interface {
- // EventType reports the kind of event, mirroring the concrete type. It
- // lets callers branch without a type switch (e.g. skip everything until
- // EventMessageComplete) and is the cheap terminal check used by Iter.
- EventType() EventTag
-}
-
-// MessageStartEvent: an assistant message began streaming.
-type MessageStartEvent struct{ Role MessageRole }
-
-// BlockStartEvent: a content block opened.
-type BlockStartEvent struct {
- BlockType ContentBlockTag
- Index uint
-}
-
-// ToolDetailsEvent: the tool identity (id + name) for a ToolUse block resolved.
-type ToolDetailsEvent struct {
- Index uint
- ID, Name string
-}
-
-// ContentDeltaEvent: streaming content for the open block (text/thinking/args).
-type ContentDeltaEvent struct {
- Index uint
- Delta string
-}
-
-// BlockCompleteEvent: a content block closed.
-type BlockCompleteEvent struct {
- Index uint
- BlockType ContentBlockTag
-}
-
-// MessageCompleteEvent: one assistant message finished streaming. The message
-// is the current tail of Agent.Conversation(); MessageIndex is its index there
-// and BlockCount its number of content blocks, so the full content can be read
-// back via Agent.Conversation().Message(MessageIndex) without marshalling every
-// block onto the event. Usage is the wire-reported token usage, if any.
-type MessageCompleteEvent struct {
- Role MessageRole
- MessageIndex uint
- BlockCount uint
- Usage *Usage
-}
-
-// ProviderRetryEvent: a provider retry was scheduled before the next attempt.
-type ProviderRetryEvent struct {
- Attempt, MaxAttempts uint
- DelayMS uint64
- ErrorName string
- StatusCode *uint16
- RetryAfterMS *uint64
- Message string
- Compaction bool
-}
-
-// ToolDispatchStartEvent: the agent began dispatching the just-completed
-// message's tool calls.
-type ToolDispatchStartEvent struct{ Count uint }
-
-// ToolDispatchResultEvent: a single tool's result became available, before the
-// aggregate ToolDispatchCompleteEvent. MessageIndex is the index of the
-// ToolResult-carrying message in the conversation.
-type ToolDispatchResultEvent struct{ MessageIndex uint }
-
-// ToolDispatchCompleteEvent: the agent appended a user(ToolResult) message.
-type ToolDispatchCompleteEvent struct{ MessageIndex uint }
-
-// TurnCompleteEvent: the turn terminal. Emitted exactly once; every Next after
-// it returns nil.
-type TurnCompleteEvent struct{}
-
-func (MessageStartEvent) EventType() EventTag { return EventMessageStart }
-func (BlockStartEvent) EventType() EventTag { return EventBlockStart }
-func (ToolDetailsEvent) EventType() EventTag { return EventToolDetails }
-func (ContentDeltaEvent) EventType() EventTag { return EventContentDelta }
-func (BlockCompleteEvent) EventType() EventTag { return EventBlockComplete }
-func (MessageCompleteEvent) EventType() EventTag { return EventMessageComplete }
-func (ProviderRetryEvent) EventType() EventTag { return EventProviderRetry }
-func (ToolDispatchStartEvent) EventType() EventTag { return EventToolDispatchStart }
-func (ToolDispatchResultEvent) EventType() EventTag { return EventToolDispatchResult }
-func (ToolDispatchCompleteEvent) EventType() EventTag { return EventToolDispatchComplete }
-func (TurnCompleteEvent) EventType() EventTag { return EventTurnComplete }
-
-type (
- Agent struct {
- ptr *C.PantoAgent
- retainedSession *Session
- retainedConv *Conversation
- retainedStoreCloser func()
- }
- Stream struct {
- ptr *C.PantoStream
- err error
- done bool
- }
-)
-
-type Conversation struct {
- ptr *C.PantoConversation
- owned bool
-}
-type (
- Session struct{ ptr *C.PantoSession }
- SessionInfo struct {
- ID, Created, Modified, LastUserMessage, BaseURL, Model string
- MessageCount uint
- APIStyle APIStyle
- Reasoning ReasoningEffort
- }
-)
-
-type SessionStore interface {
- 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
- // Per-message wire identity: the provider that produced this message. May
- // drift within a conversation (compaction on another model, mid-chat
- // switch), so persist these per message rather than from static config.
- APIStyle APIStyle
- BaseURL string
- Model string
- Reasoning ReasoningEffort
-}
-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
- // Thinking-block signature provenance. Populated for blocks delivered to
- // AppendMessages; empty otherwise. To replay a thinking block faithfully,
- // persist all four and feed them back via ThinkingBlock — Anthropic drops
- // a thinking block whose signature lacks a matching origin.
- ThinkingSignature string
- ThinkingSignatureStyle APIStyle
- ThinkingSignatureBaseURL string
- ThinkingSignatureModel string
-}
-
-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) 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))
- 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) 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)
- s.ptr = nil
- }
-}
-
-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()
- }
- s := &Session{out}
- runtime.SetFinalizer(s, (*Session).Close)
- return s, nil
-}
-
-// NewSession mints a *Session bound to store, carrying info (at least a
-// store-assigned ID). Unlike CreateSession/ResolveSession/LatestSession it does
-// NOT dispatch into the store's vtable, so a custom SessionStore can call it
-// from its own Create/Resolve/Latest to build the *Session it must return
-// without recursing back into itself. The session's bound store is used for
-// later load/append; pass the store the session belongs to.
-func NewSession(store cSessionStore, info SessionInfo) (*Session, error) {
- ci := cSessionInfo(info)
- defer freeCSessionInfo(ci)
- var out *C.PantoSession
- if C.panto_session_create(store.cptr(), ci, &out) != C.PANTO_OK {
- return nil, lastError()
- }
- s := &Session{out}
- runtime.SetFinalizer(s, (*Session).Close)
- return s, nil
-}
-
-// NewNullSession mints a standalone *Session backed by a null store — load and
-// append are no-ops. Useful as a placeholder when a store performs its own
-// persistence outside the session's bound store.
-func NewNullSession() (*Session, error) {
- var out *C.PantoSession
- if C.panto_session_create_null(&out) != C.PANTO_OK {
- return nil, lastError()
- }
- s := &Session{out}
- runtime.SetFinalizer(s, (*Session).Close)
- return s, nil
-}
-
-func ResolveSession(store cSessionStore, 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 cSessionStore) (*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 cSessionStore) ([]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
-}
-
-// Block is one immutable content block — text, thinking, tool use, tool
-// result, or system text. Build it with the constructors below (TextBlock,
-// ToolResultBlock, …) and assemble blocks into a message with UserMessage /
-// AssistantMessage. This is the single content-block vocabulary used both to
-// open a turn (Agent.Run) and to rebuild a persisted message for resumption
-// (Conversation.AddMessage).
-type Block struct {
- tag ContentBlockTag
- text string
- toolID string
- toolName string
- input string
- isError bool
- systemMode SystemMode
- thinkingSignature string
- thinkingSigStyle APIStyle
- thinkingSigBaseURL string
- thinkingSigModel string
-}
-
-// TextBlock is a plain text block.
-func TextBlock(text string) Block { return Block{tag: BlockText, text: text} }
-
-// ThinkingBlock is an assistant reasoning block. signature/baseURL/model carry
-// the signature provenance (see OwnedContentBlock); pass "" for signature when
-// the provider emitted none, in which case style/baseURL/model are ignored.
-func ThinkingBlock(text, signature string, style APIStyle, baseURL, model string) Block {
- return Block{tag: BlockThinking, text: text, thinkingSignature: signature, thinkingSigStyle: style, thinkingSigBaseURL: baseURL, thinkingSigModel: model}
-}
-
-// ToolUseBlock is an assistant tool call.
-func ToolUseBlock(id, name, input string) Block {
- return Block{tag: BlockToolUse, toolID: id, toolName: name, input: input}
-}
-
-// ToolResultBlock is a user-role tool result keyed by the originating tool-use
-// id. Used to resume a turn whose tool calls the embedder handled itself.
-func ToolResultBlock(toolUseID, text string, isError bool) Block {
- return Block{tag: BlockToolResult, toolID: toolUseID, text: text, isError: isError}
-}
-
-// SystemBlock is a system-prompt block (append or replace mode).
-func SystemBlock(text string, mode SystemMode) Block {
- return Block{tag: BlockSystem, text: text, systemMode: mode}
-}
-
-// MessageIdentity is the wire identity (provider style/baseURL/model +
-// reasoning) that produced a message. Attach it to an InputMessage when
-// rebuilding a conversation for resumption: it is the per-message source of
-// truth for replaying thinking signatures and is preserved through compaction,
-// so a kept-verbatim turn is not re-stamped with the compaction model. Set it
-// from a PersistentMessage's APIStyle/BaseURL/Model/Reasoning.
-type MessageIdentity struct {
- APIStyle APIStyle
- BaseURL string
- Model string
- Reasoning ReasoningEffort
-}
-
-// InputMessage is one role-tagged message assembled from value Blocks, with
-// optional usage and wire identity. Build it with UserMessage / AssistantMessage
-// (or set the fields directly), then pass it to Agent.Run (user role) or
-// Conversation.AddMessage (any role, for replay).
-type InputMessage struct {
- Role MessageRole
- Usage *Usage
- Identity *MessageIdentity
- Blocks []Block
-}
-
-// UserMessage assembles a user-role message — the argument to Agent.Run. A
-// plain chat turn is a single TextBlock; a turn resuming after embedder-handled
-// tool calls is one or more ToolResultBlocks.
-func UserMessage(blocks ...Block) InputMessage {
- return InputMessage{Role: RoleUser, Blocks: blocks}
-}
-
-// AssistantMessage assembles an assistant-role message with optional usage —
-// used to rebuild a persisted turn via Conversation.AddMessage.
-func AssistantMessage(usage *Usage, blocks ...Block) InputMessage {
- return InputMessage{Role: RoleAssistant, Usage: usage, Blocks: blocks}
-}
-
-// WithIdentity returns a copy of the message tagged with the given wire
-// identity (see MessageIdentity).
-func (m InputMessage) WithIdentity(id MessageIdentity) InputMessage {
- m.Identity = &id
- return m
-}
-
-// buildCBuilder materializes an InputMessage as a transient C message builder,
-// pushing each block in order and attaching identity if present. The returned
-// builder is owned by the caller and must be consumed by exactly one of
-// panto_agent_run / panto_conversation_add_message (both of which destroy it).
-// On error it is destroyed here and nil is returned.
-func buildCBuilder(msg InputMessage) (*C.PantoMessageBuilder, error) {
- p := C.panto_message_builder_create(C.PantoMessageRole(msg.Role))
- if p == nil {
- return nil, lastError()
- }
- committed := false
- defer func() {
- if !committed {
- C.panto_message_builder_destroy(p)
- }
- }()
- if msg.Identity != nil {
- cb := C.CString(msg.Identity.BaseURL)
- defer C.free(unsafe.Pointer(cb))
- cm := C.CString(msg.Identity.Model)
- defer C.free(unsafe.Pointer(cm))
- if C.panto_message_builder_set_identity(p, C.PantoAPIStyle(msg.Identity.APIStyle), cb, cm, C.PantoReasoningEffort(msg.Identity.Reasoning)) != C.PANTO_OK {
- return nil, lastError()
- }
- }
- for _, b := range msg.Blocks {
- if err := pushBlock(p, b); err != nil {
- return nil, err
- }
- }
- committed = true
- return p, nil
-}
-
-func pushBlock(p *C.PantoMessageBuilder, b Block) error {
- switch b.tag {
- case BlockText:
- ct := C.CString(b.text)
- defer C.free(unsafe.Pointer(ct))
- if C.panto_message_builder_add_text(p, ct) != C.PANTO_OK {
- return lastError()
- }
- case BlockThinking:
- ct := C.CString(b.text)
- defer C.free(unsafe.Pointer(ct))
- cs := C.CString(b.thinkingSignature)
- defer C.free(unsafe.Pointer(cs))
- cb := C.CString(b.thinkingSigBaseURL)
- defer C.free(unsafe.Pointer(cb))
- cm := C.CString(b.thinkingSigModel)
- defer C.free(unsafe.Pointer(cm))
- if C.panto_message_builder_add_thinking(p, ct, cs, C.PantoAPIStyle(b.thinkingSigStyle), cb, cm) != C.PANTO_OK {
- return lastError()
- }
- case BlockToolUse:
- cid := C.CString(b.toolID)
- defer C.free(unsafe.Pointer(cid))
- cn := C.CString(b.toolName)
- defer C.free(unsafe.Pointer(cn))
- ci := C.CString(b.input)
- defer C.free(unsafe.Pointer(ci))
- if C.panto_message_builder_add_tool_use(p, cid, cn, ci) != C.PANTO_OK {
- return lastError()
- }
- case BlockToolResult:
- cid := C.CString(b.toolID)
- defer C.free(unsafe.Pointer(cid))
- ct := C.CString(b.text)
- defer C.free(unsafe.Pointer(ct))
- if C.panto_message_builder_add_tool_result(p, cid, ct, C.bool(b.isError)) != C.PANTO_OK {
- return lastError()
- }
- case BlockSystem:
- ct := C.CString(b.text)
- defer C.free(unsafe.Pointer(ct))
- if C.panto_message_builder_add_system(p, ct, C.PantoSystemMode(b.systemMode)) != C.PANTO_OK {
- return lastError()
- }
- default:
- return fmt.Errorf("unknown content block tag %d", int(b.tag))
- }
- return nil
-}
-
-// AddMessage commits msg as one message of msg.Role (with its optional usage
-// and identity) — the block-level path for rebuilding a persisted conversation
-// containing tool calls or thinking that the text-only AddUserText/
-// AddAssistantText cannot.
-func (c *Conversation) AddMessage(msg InputMessage) error {
- b, err := buildCBuilder(msg)
- if err != nil {
- return err
- }
- var cu C.PantoUsage
- has := false
- if msg.Usage != nil {
- cu = cUsage(*msg.Usage)
- has = true
- }
- // panto_conversation_add_message consumes (destroys) the builder on every
- // outcome, so there is nothing to free here afterwards.
- if C.panto_conversation_add_message(c.ptr, b, C.bool(has), cu) != 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}
-}
-
-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
-}
-
-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) {
- return NewAgentWithSession(cfg, nil, 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 csession *C.PantoSession
- if session != nil {
- csession = session.ptr
- runtime.SetFinalizer(session, nil)
- }
- 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, csession, cconv, &out) != C.PANTO_OK {
- return nil, lastError()
- }
- a := &Agent{ptr: out, retainedSession: session, retainedConv: conv}
- 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
- }
- if a != nil {
- if a.retainedSession != nil {
- a.retainedSession.ptr = nil
- a.retainedSession = nil
- }
- if a.retainedConv != nil {
- a.retainedConv.ptr = nil
- a.retainedConv = nil
- }
- if a.retainedStoreCloser != nil {
- a.retainedStoreCloser()
- a.retainedStoreCloser = 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
-}
-
-// Run opens a turn from a user-role message and returns a resumable Stream.
-// Build the message with UserMessage: a single TextBlock for a plain prompt,
-// or ToolResultBlocks to resume a turn whose tool calls the embedder handled
-// itself after breaking out of a prior stream. The message must be user-role.
-func (a *Agent) Run(msg InputMessage) (*Stream, error) {
- if msg.Role != RoleUser {
- return nil, fmt.Errorf("Run requires a user-role message, got role %d", int(msg.Role))
- }
- b, err := buildCBuilder(msg)
- if err != nil {
- return nil, err
- }
- // panto_agent_run consumes (destroys) the builder on every outcome.
- var out *C.PantoStream
- if C.panto_agent_run(a.ptr, b, &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
- }
-}
-
-// Reopen resets a failed stream back to its turn-open boundary so the caller
-// can resume Next() after changing the agent config (e.g. catch a terminal
-// bad-request, swap the provider config via Agent.SetConfig, and retry the
-// SAME turn without re-appending the user message). It is valid only on a
-// stream whose Next() has surfaced a terminal error; otherwise it returns an
-// error. On success the stream's local error/done state is cleared so the
-// iteration can continue.
-func (s *Stream) Reopen() error {
- if s == nil || s.ptr == nil {
- return fmt.Errorf("reopen on a closed stream")
- }
- if C.panto_stream_reopen(s.ptr) != C.PANTO_OK {
- return lastError()
- }
- s.err = nil
- s.done = false
- return nil
-}
-
-func (s *Stream) Next() (Event, bool, error) {
- if s.err != nil {
- return nil, false, s.err
- }
- if s.done {
- return nil, 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.EventType() == EventTurnComplete {
- s.done = true
- }
- return ev, true, nil
- case C.PANTO_NEXT_DONE:
- s.done = true
- return nil, false, nil
- case C.PANTO_NEXT_ERROR:
- s.err = lastError()
- return nil, false, s.err
- default:
- s.err = fmt.Errorf("unknown panto next status %d", int(status))
- return nil, 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.EventType() == 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
- n := 0
- if p.OpenAIChat != nil {
- n++
- }
- if p.AnthropicMessages != nil {
- n++
- }
- if p.OpenAIResponses != nil {
- n++
- }
- if n != 1 {
- 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
- }
- if p.OpenAIResponses != nil {
- out.tag = C.PANTO_OPENAI_RESPONSES
- setOpenAIResponses(&out, p.OpenAIResponses, &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 setOpenAIResponses(out *C.PantoProviderConfig, cfg *OpenAIResponsesConfig, 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.PantoOpenAIResponsesConfig)(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 {
- switch cev.tag {
- case C.PANTO_EVENT_MESSAGE_START:
- return MessageStartEvent{Role: MessageRole(*(*C.PantoMessageRole)(unsafe.Pointer(&cev.data[0])))}
- case C.PANTO_EVENT_BLOCK_START:
- b := (*C.PantoBlockStart)(unsafe.Pointer(&cev.data[0]))
- return BlockStartEvent{BlockType: ContentBlockTag(b.block_type), Index: uint(b.index)}
- case C.PANTO_EVENT_TOOL_DETAILS:
- t := (*C.PantoToolDetails)(unsafe.Pointer(&cev.data[0]))
- return ToolDetailsEvent{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]))
- return ContentDeltaEvent{Index: uint(d.index), Delta: goSlice(d.delta)}
- case C.PANTO_EVENT_BLOCK_COMPLETE:
- b := (*C.PantoBlockComplete)(unsafe.Pointer(&cev.data[0]))
- return BlockCompleteEvent{Index: uint(b.index), BlockType: ContentBlockTag(b.block_type)}
- case C.PANTO_EVENT_MESSAGE_COMPLETE:
- m := (*C.PantoMessageComplete)(unsafe.Pointer(&cev.data[0]))
- ev := MessageCompleteEvent{
- Role: MessageRole(m.role),
- MessageIndex: uint(m.message_index),
- BlockCount: uint(m.block_count),
- }
- if m.has_usage {
- u := usage(m.usage)
- ev.Usage = &u
- }
- return ev
- case C.PANTO_EVENT_PROVIDER_RETRY:
- r := (*C.PantoProviderRetry)(unsafe.Pointer(&cev.data[0]))
- ev := ProviderRetryEvent{
- 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.StatusCode = &v
- }
- if r.has_retry_after_ms {
- v := uint64(r.retry_after_ms)
- ev.RetryAfterMS = &v
- }
- return ev
- case C.PANTO_EVENT_TOOL_DISPATCH_START:
- t := (*C.PantoToolDispatchStart)(unsafe.Pointer(&cev.data[0]))
- return ToolDispatchStartEvent{Count: uint(t.count)}
- case C.PANTO_EVENT_TOOL_DISPATCH_RESULT:
- t := (*C.PantoToolDispatchComplete)(unsafe.Pointer(&cev.data[0]))
- return ToolDispatchResultEvent{MessageIndex: uint(t.message_index)}
- case C.PANTO_EVENT_TOOL_DISPATCH_COMPLETE:
- t := (*C.PantoToolDispatchComplete)(unsafe.Pointer(&cev.data[0]))
- return ToolDispatchCompleteEvent{MessageIndex: uint(t.message_index)}
- case C.PANTO_EVENT_TURN_COMPLETE:
- return TurnCompleteEvent{}
- }
- return TurnCompleteEvent{}
-}
-
-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),
- }
-}
-
-// Tool callback support.
-type (
- ToolDecl struct{ Name, Description, SchemaJSON string }
- ResultPart struct {
- Text string
- // Media carries raw bytes, not base64. libpanto detects/resizes/encodes
- // supported images and PDFs for the provider; MediaType is an optional
- // MIME hint.
- MediaType string
- Media []byte
- }
- ResultParts []ResultPart
- ToolFunc func(input string) (ResultParts, error)
- Tool struct {
- Decl ToolDecl
- Invoke ToolFunc
- }
-)
-
-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 pantoGoCallbackDestroy
-func pantoGoCallbackDestroy(ctx unsafe.Pointer) { dropCallback(ctx) }
-
-// RegisterTool registers a single tool. Note: libpanto invokes each tool call
-// on its own thread, so Invoke may run concurrently — guard any shared state.
-//
-// TODO: back all Go tool registrations with one process-global Zig ToolSource so
-// libpanto delivers the whole turn's batch on a single thread and we fan out onto
-// goroutines instead of libpanto threads. Pure efficiency (cheaper than N pthreads
-// per turn); the public API stays exactly this. Punted as not worth the work yet.
-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 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 {
- if p.Media == nil {
- 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
- continue
- }
-
- xs[i].tag = C.PANTO_RESULT_MEDIA
- m := (*C.PantoMediaPart)(unsafe.Pointer(&xs[i].data[0]))
- m.data = C.PantoSlice{ptr: (*C.uint8_t)(C.CBytes(p.Media)), len: C.size_t(len(p.Media))}
- if p.MediaType != "" {
- m.media_type = cSlice(p.MediaType)
- m.has_media_type = C.bool(true)
- }
- }
- 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),
- Content: goOwnedBlocks(m.content, m.content_len),
- APIStyle: APIStyle(m.api_style),
- BaseURL: goSlice(m.base_url),
- Model: goSlice(m.model),
- Reasoning: ReasoningEffort(m.reasoning),
- }
- }
- if err := impl.AppendMessages(goSlice(sessionID), out); err != nil {
- return C.PANTO_ERROR
- }
- return C.PANTO_OK
-}
-
-func goOwnedBlocks(ptr *C.PantoOwnedContentBlock, n C.size_t) []OwnedContentBlock {
- if ptr == nil || n == 0 {
- return nil
- }
- xs := unsafe.Slice(ptr, int(n))
- out := make([]OwnedContentBlock, int(n))
- for i, b := range xs {
- out[i] = OwnedContentBlock{
- Tag: ContentBlockTag(b.tag),
- Text: goSlice(b.text),
- ToolID: goSlice(b.tool_id),
- ToolName: goSlice(b.tool_name),
- IsError: bool(b.is_error),
- SystemMode: SystemMode(b.system_mode),
- ThinkingSignature: goSlice(b.thinking_signature),
- ThinkingSignatureStyle: APIStyle(b.thinking_signature_api_style),
- ThinkingSignatureBaseURL: goSlice(b.thinking_signature_base_url),
- ThinkingSignatureModel: goSlice(b.thinking_signature_model),
- }
- }
- return out
-}
-
-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 freeCSessionInfo(i C.PantoSessionInfo) {
- 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))
-}
-
-func freeCSessionInfoList(l C.PantoSessionInfoList) {
- if l.ptr == nil {
- return
- }
- xs := unsafe.Slice(l.ptr, int(l.len))
- for _, i := range xs {
- freeCSessionInfo(i)
- }
- C.free(unsafe.Pointer(l.ptr))
-}