summaryrefslogtreecommitdiff
path: root/libpanto-go
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto-go')
-rw-r--r--libpanto-go/panto.go19
-rw-r--r--libpanto-go/panto_test.go41
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{