返回 DeepSeek-Reasonix
model_runtime_continuation.go
根目录 / internal / config / model_runtime_continuation.go
1 package config
2
3 import (
4 "fmt"
5 "net/url"
6 "reflect"
7 "slices"
8 )
9
10 // ValidateModelRuntimeContinuation checks every route reachable from an old
11 // frozen resolver, not only its foreground model. Endpoint edits are allowed
12 // by an explicit one-turn choice; revoked access/credentials never are.
13 func ValidateModelRuntimeContinuation(old, current *Config) error {
14 if old == nil || current == nil {
15 return fmt.Errorf("configuration cannot be verified")
16 }
17 if !reflect.DeepEqual(old.Permissions, current.Permissions) || !reflect.DeepEqual(old.Sandbox, current.Sandbox) || !reflect.DeepEqual(old.Secrets, current.Secrets) || !reflect.DeepEqual(old.Plugins, current.Plugins) {
18 return fmt.Errorf("permissions or runtime policy changed")
19 }
20 if !reflect.DeepEqual(old.Tools, current.Tools) || !reflect.DeepEqual(old.Network, current.Network) || !reflect.DeepEqual(old.Skills, current.Skills) {
21 return fmt.Errorf("tool or network policy changed")
22 }
23 for _, pair := range [][2]int{{old.Agent.MaxSubagentDepth, current.Agent.MaxSubagentDepth}, {old.Agent.MaxSubagentConcurrency, current.Agent.MaxSubagentConcurrency}, {old.Agent.MaxParallelWriters, current.Agent.MaxParallelWriters}} {
24 // Zero-value defaults require rebuilding rather than guessing a bound.
25 if pair[0] != pair[1] && (pair[0] <= 0 || pair[1] <= 0 || pair[1] < pair[0]) {
26 return fmt.Errorf("model execution limits changed")
27 }
28 }
29 for _, pair := range [][2]float64{{old.Agent.TaskCostBudget, current.Agent.TaskCostBudget}, {old.Agent.TaskTimeBudgetMinutes, current.Agent.TaskTimeBudgetMinutes}, {float64(old.Agent.GoalTokenBudget), float64(current.Agent.GoalTokenBudget)}} {
30 if pair[1] > 0 && (pair[0] <= 0 || pair[1] < pair[0]) {
31 return fmt.Errorf("model execution budgets were tightened")
32 }
33 }
34 for _, route := range old.Providers {
35 if old.Desktop.ProviderAccess != nil && !slices.Contains(old.Desktop.ProviderAccess, route.Name) {
36 continue
37 }
38 next, ok := current.Provider(route.Name)
39 if !ok || (current.Desktop.ProviderAccess != nil && !slices.Contains(current.Desktop.ProviderAccess, route.Name)) {
40 return fmt.Errorf("provider access was removed")
41 }
42 if route.APIKey() != next.APIKey() {
43 return fmt.Errorf("provider credentials changed")
44 }
45 if err := validateContinuationRoute(route, *next); err != nil {
46 return err
47 }
48 for _, model := range route.ModelList() {
49 if !slices.Contains(next.ModelList(), model) {
50 return fmt.Errorf("a model in the current runtime is no longer available")
51 }
52 }
53 }
54 return nil
55 }
56
57 func validateContinuationRoute(before, after ProviderEntry) error {
58 if before.Kind != after.Kind || before.AuthHeader != after.AuthHeader || !reflect.DeepEqual(before.Headers, after.Headers) || !reflect.DeepEqual(before.ExtraBody, after.ExtraBody) {
59 return fmt.Errorf("provider authentication parameters changed")
60 }
61 for _, pair := range [][2]string{{before.BaseURL, after.BaseURL}, {before.ChatURL, after.ChatURL}, {before.RequestURL, after.RequestURL}} {
62 if !continuationEndpointAllowed(pair[0], pair[1]) {
63 return fmt.Errorf("provider authentication parameters changed")
64 }
65 }
66 // Overrides may include per-model cost caps and capability restrictions.
67 // Require a rebuild when their interpretation changed rather than assuming
68 // that only the foreground model's defaults constrain an old resolver.
69 if before.MaxOutputTokens != after.MaxOutputTokens || before.ContextWindow != after.ContextWindow || !reflect.DeepEqual(before.ModelOverrides, after.ModelOverrides) || !reflect.DeepEqual(before.SupportedEfforts, after.SupportedEfforts) {
70 return fmt.Errorf("model limits or capability restrictions changed")
71 }
72 return nil
73 }
74
75 // Hosts may change with explicit confirmation. URLs carrying credentials or
76 // query parameters cannot be classified as an address-only edit safely.
77 func continuationEndpointAllowed(before, after string) bool {
78 if before == after {
79 return true
80 }
81 a, errA := url.Parse(before)
82 b, errB := url.Parse(after)
83 return errA == nil && errB == nil && a.User == nil && b.User == nil && a.RawQuery == b.RawQuery && a.Path == b.Path
84 }
85
85 lines GO