diff options
Diffstat (limited to 'libpanto-go')
| -rw-r--r-- | libpanto-go/panto.go | 19 | ||||
| -rw-r--r-- | libpanto-go/panto_test.go | 41 |
2 files changed, 60 insertions, 0 deletions
diff --git a/libpanto-go/panto.go b/libpanto-go/panto.go index 95e0cbd..5bfc5d8 100644 --- a/libpanto-go/panto.go +++ b/libpanto-go/panto.go @@ -621,6 +621,25 @@ func (b *MessageBuilder) AddSystem(text string, mode SystemMode) error { return nil } +// SetIdentity attaches the wire identity (provider style/baseURL/model + +// reasoning) that produced this message. This 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. Prefer +// this over per-block origins on AddThinking when rebuilding a conversation for +// resumption: set it from PersistentMessage's APIStyle/BaseURL/Model and the +// thinking signatures replay correctly without copying the identity onto every +// thinking block. +func (b *MessageBuilder) SetIdentity(style APIStyle, baseURL, model string, reasoning ReasoningEffort) error { + cb := C.CString(baseURL) + defer C.free(unsafe.Pointer(cb)) + cm := C.CString(model) + defer C.free(unsafe.Pointer(cm)) + if C.panto_message_builder_set_identity(b.ptr, C.PantoAPIStyle(style), cb, cm, C.PantoReasoningEffort(reasoning)) != C.PANTO_OK { + return lastError() + } + return nil +} + // Destroy frees an uncommitted builder. Safe to call after AddMessage (no-op). func (b *MessageBuilder) Destroy() { if b != nil && b.ptr != nil { diff --git a/libpanto-go/panto_test.go b/libpanto-go/panto_test.go index 5cfed01..fd9df70 100644 --- a/libpanto-go/panto_test.go +++ b/libpanto-go/panto_test.go @@ -128,6 +128,47 @@ func TestMessageBuilderRoundTrip(t *testing.T) { } } +// TestMessageBuilderSetIdentity rebuilds a message with a per-message wire +// identity (and no per-block thinking origin), the resumption path that lets +// callers avoid copying the identity onto every thinking block. +func TestMessageBuilderSetIdentity(t *testing.T) { + Init() + defer Deinit() + + conv, err := NewConversation() + if err != nil { + t.Fatalf("NewConversation: %v", err) + } + defer conv.Close() + + ab, err := NewMessageBuilder(RoleAssistant) + if err != nil { + t.Fatalf("NewMessageBuilder(assistant): %v", err) + } + // Thinking block with a signature but NO per-block origin. + if err := ab.AddThinking("reasoned", "sig", OpenAIChat, "", ""); err != nil { + t.Fatalf("AddThinking: %v", err) + } + if err := ab.AddText("answer"); err != nil { + t.Fatalf("AddText: %v", err) + } + // Provenance is carried at the message level instead. + if err := ab.SetIdentity(AnthropicMessages, "https://api.anthropic.com", "claude", ReasoningDefault); err != nil { + t.Fatalf("SetIdentity: %v", err) + } + if err := conv.AddMessage(ab, &Usage{Input: 10, Output: 5}); err != nil { + t.Fatalf("AddMessage: %v", err) + } + + asst := conv.Message(0).Snapshot() + if asst.Role != RoleAssistant || len(asst.Content) != 2 { + t.Fatalf("assistant message: role=%v blocks=%d", asst.Role, len(asst.Content)) + } + if b := asst.Content[0]; b.Tag != BlockThinking || b.Text != "reasoned" { + t.Fatalf("thinking block: %+v", b) + } +} + func testConfig() Config { return Config{ Provider: ProviderConfig{ |
