mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-27 18:20:46 +00:00
126 lines
3.9 KiB
Go
126 lines
3.9 KiB
Go
package caps
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/internal/usage/pricing"
|
|
)
|
|
|
|
const TraceMetadataKey = "usage_caps"
|
|
|
|
type TraceMetadata struct {
|
|
Decision string `json:"decision"`
|
|
Reason string `json:"reason,omitempty"`
|
|
ReservationKey string `json:"reservation_key,omitempty"`
|
|
ProviderName string `json:"provider_name,omitempty"`
|
|
ProviderType string `json:"provider_type,omitempty"`
|
|
ModelID string `json:"model_id,omitempty"`
|
|
EstimatedTokens int64 `json:"estimated_tokens"`
|
|
EstimatedCostMicros int64 `json:"estimated_cost_micros"`
|
|
ActualTokens int64 `json:"actual_tokens"`
|
|
ActualCostMicros int64 `json:"actual_cost_micros"`
|
|
ReconcileStatus string `json:"reconcile_status,omitempty"`
|
|
PolicyCount int `json:"policy_count"`
|
|
PolicyIDs []string `json:"policy_ids,omitempty"`
|
|
}
|
|
|
|
func (r *Reservation) TraceMetadata() TraceMetadata {
|
|
if r == nil {
|
|
return TraceMetadata{}
|
|
}
|
|
decision := r.decision
|
|
if decision == "" {
|
|
if r.skipped {
|
|
decision = store.UsageCapEventSkip
|
|
} else {
|
|
decision = store.UsageCapEventAllow
|
|
}
|
|
}
|
|
policyCount := 0
|
|
policyIDs := make([]string, 0)
|
|
if r.result != nil {
|
|
policyCount = len(r.result.Policies)
|
|
for _, policy := range r.result.Policies {
|
|
if policy.ID != uuid.Nil {
|
|
policyIDs = append(policyIDs, policy.ID.String())
|
|
}
|
|
}
|
|
}
|
|
if r.blockedPolicyID != uuid.Nil {
|
|
policyIDs = append(policyIDs, r.blockedPolicyID.String())
|
|
if policyCount == 0 {
|
|
policyCount = 1
|
|
}
|
|
}
|
|
return TraceMetadata{
|
|
Decision: decision,
|
|
Reason: r.reason,
|
|
ReservationKey: r.key,
|
|
ProviderName: r.providerName,
|
|
ProviderType: r.providerType,
|
|
ModelID: r.modelID,
|
|
EstimatedTokens: r.usage.TotalTokens(),
|
|
EstimatedCostMicros: r.estimatedCostMicros,
|
|
ActualTokens: r.actualTokens,
|
|
ActualCostMicros: r.actualCostMicros,
|
|
ReconcileStatus: r.reconcileStatus,
|
|
PolicyCount: policyCount,
|
|
PolicyIDs: policyIDs,
|
|
}
|
|
}
|
|
|
|
func (m TraceMetadata) Empty() bool {
|
|
return m.Decision == "" && m.Reason == "" && m.ReservationKey == "" &&
|
|
m.ProviderName == "" && m.ProviderType == "" && m.ModelID == "" &&
|
|
m.EstimatedTokens == 0 && m.EstimatedCostMicros == 0 &&
|
|
m.ActualTokens == 0 && m.ActualCostMicros == 0 &&
|
|
m.ReconcileStatus == "" && m.PolicyCount == 0 && len(m.PolicyIDs) == 0
|
|
}
|
|
|
|
func MergeTraceMetadata(existing json.RawMessage, entries []TraceMetadata) json.RawMessage {
|
|
clean := make([]TraceMetadata, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if !entry.Empty() {
|
|
clean = append(clean, entry)
|
|
}
|
|
}
|
|
if len(clean) == 0 {
|
|
return existing
|
|
}
|
|
payload := map[string]any{}
|
|
if len(existing) > 0 {
|
|
_ = json.Unmarshal(existing, &payload)
|
|
}
|
|
payload[TraceMetadataKey] = map[string]any{"attempts": clean}
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return existing
|
|
}
|
|
return json.RawMessage(data)
|
|
}
|
|
|
|
func skippedReservation(req Request, reason string) *Reservation {
|
|
return &Reservation{
|
|
skipped: true, decision: store.UsageCapEventSkip, reason: reason,
|
|
providerName: req.ProviderName, modelID: req.ModelID,
|
|
}
|
|
}
|
|
|
|
func skippedScopedReservation(req Request, scope store.UsageCapScope, reason string) *Reservation {
|
|
r := skippedReservation(req, reason)
|
|
r.providerType = scope.ProviderType
|
|
r.modelID = scope.ModelID
|
|
return r
|
|
}
|
|
|
|
func blockedReservation(req Request, scope store.UsageCapScope, key string, usage pricing.BillableUsage, costMicros int64, policyID uuid.UUID, reason string) *Reservation {
|
|
return &Reservation{
|
|
key: key, usage: usage, estimatedCostMicros: costMicros,
|
|
decision: store.UsageCapEventBlock, reason: reason,
|
|
blockedPolicyID: policyID,
|
|
providerName: req.ProviderName, providerType: scope.ProviderType, modelID: scope.ModelID,
|
|
}
|
|
}
|