Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

165 lines
6.1 KiB
Go

package storesqlite
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
)
type EditRetryCheckpoint string
const (
EditRetryCheckpointPrepared EditRetryCheckpoint = "prepared"
EditRetryCheckpointRollbackDispatched EditRetryCheckpoint = "rollback_dispatched"
EditRetryCheckpointRollbackConfirmed EditRetryCheckpoint = "rollback_confirmed"
EditRetryCheckpointReplacementDispatched EditRetryCheckpoint = "replacement_dispatched"
EditRetryCheckpointRollbackAborted EditRetryCheckpoint = "rollback_aborted"
)
type EditRetryReasonCode = canonical.EditRetryReasonCode
const (
EditRetryReasonProviderUnsupported = canonical.EditRetryReasonProviderUnsupported
EditRetryReasonTurnNotFound = canonical.EditRetryReasonTurnNotFound
EditRetryReasonTurnNotLatest = canonical.EditRetryReasonTurnNotLatest
EditRetryReasonTurnNotSettled = canonical.EditRetryReasonTurnNotSettled
EditRetryReasonHistoryRevisionConflict = canonical.EditRetryReasonHistoryRevisionConflict
EditRetryReasonOperationConflict = canonical.EditRetryReasonOperationConflict
EditRetryReasonRecoveryRequired = canonical.EditRetryReasonRecoveryRequired
EditRetryReasonProviderOutcomeUnknown = canonical.EditRetryReasonProviderOutcomeUnknown
EditRetryReasonReplacementNotProvenAbsent = canonical.EditRetryReasonReplacementNotProvenAbsent
)
// EditRetryOperationPayload is the typed durable edit-retry checkpoint.
// RuntimeOperation keeps payload_json as its shared storage representation;
// edit-retry transitions must decode through this type before interpreting it.
type EditRetryOperationPayload struct {
ClientOperationID string `json:"clientOperationId"`
EditedText string `json:"editedText"`
ReplacementTurnID string `json:"replacementTurnId"`
ClientSubmitID string `json:"clientSubmitId"`
ExpectedRevision int64 `json:"expectedHistoryRevision"`
Checkpoint EditRetryCheckpoint `json:"step"`
BeforeProviderIDs []string `json:"beforeProviderTurnIds,omitempty"`
ProviderSessionID string `json:"providerSessionId,omitempty"`
RedispatchProofIDs []string `json:"recoveryRedispatchProofProviderTurnIds,omitempty"`
RedispatchProofSID string `json:"recoveryRedispatchProofProviderSessionId,omitempty"`
RedispatchProofAt int64 `json:"recoveryRedispatchProofAtUnixMs,omitempty"`
DispatchAttempt int64 `json:"replacementDispatchAttempt,omitempty"`
DiscardedMessages int64 `json:"lastDiscardedLocalMessageCount,omitempty"`
DiscardedOutcome string `json:"lastDiscardedLocalOutcome,omitempty"`
DiscardedError string `json:"lastDiscardedLocalError,omitempty"`
}
func (payload EditRetryOperationPayload) Validate(operationID string) error {
if strings.TrimSpace(payload.ClientOperationID) == "" ||
strings.TrimSpace(payload.EditedText) == "" ||
strings.TrimSpace(payload.ReplacementTurnID) == "" ||
strings.TrimSpace(payload.ClientSubmitID) != "edit-retry:"+strings.TrimSpace(operationID) ||
payload.ExpectedRevision < 0 {
return errors.New("edit retry identity payload is invalid")
}
switch payload.Checkpoint {
case EditRetryCheckpointPrepared:
if payload.BeforeProviderIDs != nil {
return errors.New("prepared edit retry must not carry provider history")
}
case EditRetryCheckpointRollbackDispatched,
EditRetryCheckpointRollbackConfirmed,
EditRetryCheckpointReplacementDispatched:
if err := validateEditRetryProviderIDs(payload.BeforeProviderIDs); err != nil {
return err
}
case EditRetryCheckpointRollbackAborted:
return nil
default:
return errors.New("edit retry checkpoint is invalid")
}
return nil
}
func EncodeEditRetryOperationPayload(payload EditRetryOperationPayload) (map[string]any, error) {
encoded, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("encode edit retry operation payload: %w", err)
}
var result map[string]any
if err := json.Unmarshal(encoded, &result); err != nil {
return nil, fmt.Errorf("decode encoded edit retry operation payload: %w", err)
}
return result, nil
}
func DecodeEditRetryOperationPayload(payload map[string]any) (EditRetryOperationPayload, error) {
encoded, err := json.Marshal(payload)
if err != nil {
return EditRetryOperationPayload{}, fmt.Errorf("encode stored edit retry operation payload: %w", err)
}
decoder := json.NewDecoder(strings.NewReader(string(encoded)))
decoder.DisallowUnknownFields()
var result EditRetryOperationPayload
if err := decoder.Decode(&result); err != nil {
return EditRetryOperationPayload{}, fmt.Errorf("decode edit retry operation payload: %w", err)
}
return result, nil
}
func validateEditRetryOperationPayload(operationID string, payload map[string]any) error {
decoded, err := DecodeEditRetryOperationPayload(payload)
if err != nil {
return err
}
return decoded.Validate(operationID)
}
func editRetryPayloadMap(operationID string, payload EditRetryOperationPayload) (map[string]any, error) {
if err := payload.Validate(operationID); err != nil {
return nil, err
}
return EncodeEditRetryOperationPayload(payload)
}
func validateEditRetryProviderIDs(values []string) error {
if values == nil {
return errors.New("edit retry provider history is invalid")
}
for _, value := range values {
if strings.TrimSpace(value) == "" {
return errors.New("edit retry provider history is invalid")
}
}
return nil
}
func editRetryReason(code EditRetryReasonCode, legacy string) (EditRetryReasonCode, error) {
if code == "" {
code = EditRetryReasonCode(strings.TrimSpace(legacy))
}
if err := code.Validate(); err != nil {
return "", err
}
return code, nil
}
func payloadInt64(payload map[string]any, key string) int64 {
switch value := payload[key].(type) {
case int:
return int64(value)
case int64:
return value
case uint64:
if value <= uint64(^uint64(0)>>1) {
return int64(value)
}
case float64:
return int64(value)
case json.Number:
result, _ := value.Int64()
return result
}
return -1
}