Files
promptkit/internal/domain/exchange.go

93 lines
2.6 KiB
Go

package domain
// Exchange is an immutable logical conversation ending in an assistant message.
type Exchange struct {
parent *Exchange
runID string
parentRunID string
messages []RenderedMessage
count int
}
// CaptureExchange creates a new exchange without modifying its parent.
func CaptureExchange(parent *Exchange, runID string, messages []RenderedMessage, output string) *Exchange {
turn := make([]RenderedMessage, len(messages)+1)
copyRenderedMessages(turn, messages)
turn[len(messages)] = RenderedMessage{Role: RoleAssistant, Content: output}
exchange := &Exchange{parent: parent, runID: runID, messages: turn, count: len(turn)}
if parent != nil {
exchange.parentRunID = parent.runID
exchange.count += parent.count
}
return exchange
}
// RunID returns the identity of the run that produced this exchange.
func (e *Exchange) RunID() string {
if e == nil {
return ""
}
return e.runID
}
// ParentRunID returns the immediate source run identity, if present.
func (e *Exchange) ParentRunID() string {
if e == nil {
return ""
}
return e.parentRunID
}
// MessageCount returns the number of messages in the logical history.
func (e *Exchange) MessageCount() int {
if e == nil {
return 0
}
return e.count
}
// Messages returns a detached copy of the complete logical history.
func (e *Exchange) Messages() []RenderedMessage {
if e == nil {
return nil
}
return e.MessagesWithSuffix()
}
// MessagesWithSuffix returns independently owned history followed by each part.
// A nil exchange supplies no history.
func (e *Exchange) MessagesWithSuffix(parts ...[]RenderedMessage) []RenderedMessage {
count := e.MessageCount()
for _, part := range parts {
count += len(part)
}
messages := make([]RenderedMessage, count)
e.visitChunks(func(start int, chunk []RenderedMessage) {
copyRenderedMessages(messages[start:], chunk)
})
offset := e.MessageCount()
for _, part := range parts {
copyRenderedMessages(messages[offset:], part)
offset += len(part)
}
return messages
}
// VisitMessages visits indexed history entries without materializing a slice.
// Entries and their cache metadata are borrowed read-only values. Visits need
// not be in index order; callers can fill a destination using the supplied index.
func (e *Exchange) VisitMessages(visit func(int, RenderedMessage)) {
e.visitChunks(func(start int, chunk []RenderedMessage) {
for index, message := range chunk {
visit(start+index, message)
}
})
}
func (e *Exchange) visitChunks(visit func(int, []RenderedMessage)) {
for current := e; current != nil; current = current.parent {
start := current.count - len(current.messages)
visit(start, current.messages)
}
}