返回 DeepSeek-Reasonix
startup.go
根目录 / internal / plugin / startup.go
1 package plugin
2
3 import (
4 "errors"
5 "fmt"
6 "strings"
7 "time"
8
9 "reasonix/internal/secrets"
10 )
11
12 // defaultStartupTimeout bounds the real initialize + tools/list handshake.
13 // The model-facing wait stays shorter so a slow but healthy server can finish
14 // in the background without holding an interactive turn open.
15 const defaultStartupTimeout = 30 * time.Second
16
17 // DefaultStartupTimeout is the default background handshake safety cap used by
18 // config and host adapters when no explicit server override is present.
19 func DefaultStartupTimeout() time.Duration { return defaultStartupTimeout }
20
21 // DefaultStartupWaitBudget is the maximum time one interactive tool call waits
22 // for a shared background handshake before asking the model to retry later.
23 func DefaultStartupWaitBudget() time.Duration { return defaultStartTimeout }
24
25 func (s Spec) startupTimeout() time.Duration {
26 if s.StartupTimeout > 0 {
27 return s.StartupTimeout
28 }
29 if s.DefaultStartupTimeout > 0 {
30 return s.DefaultStartupTimeout
31 }
32 return defaultStartupTimeout
33 }
34
35 // ResolvedStartupTimeout returns the server override, global default, or the
36 // built-in background handshake cap, in that order.
37 func (s Spec) ResolvedStartupTimeout() time.Duration { return s.startupTimeout() }
38
39 type startupFailure struct {
40 Stage string
41 Elapsed time.Duration
42 Stderr string
43 Err error
44 }
45
46 func (e *startupFailure) Error() string {
47 if e == nil {
48 return "MCP startup failed"
49 }
50 stage := strings.TrimSpace(e.Stage)
51 if stage == "" {
52 stage = "unknown"
53 }
54 msg := fmt.Sprintf("MCP startup %s failed after %s: %v", stage, formatElapsed(e.Elapsed), e.Err)
55 if stderr := strings.TrimSpace(e.Stderr); stderr != "" {
56 msg += "; stderr: " + stderr
57 }
58 return msg
59 }
60
61 func (e *startupFailure) Unwrap() error {
62 if e == nil {
63 return nil
64 }
65 return e.Err
66 }
67
68 func newStartupFailure(stage string, started time.Time, stderr string, err error) error {
69 if err == nil {
70 return nil
71 }
72 var existing *startupFailure
73 if errors.As(err, &existing) {
74 return err
75 }
76 elapsed := max(time.Since(started), 0)
77 return &startupFailure{
78 Stage: strings.TrimSpace(stage),
79 Elapsed: elapsed,
80 Stderr: secrets.RedactCredentials(strings.TrimSpace(stderr)),
81 Err: err,
82 }
83 }
84
85 func startupFailureDetails(err error) (stage string, elapsed time.Duration, stderr string) {
86 var startupErr *startupFailure
87 if !errors.As(err, &startupErr) || startupErr == nil {
88 return "", 0, ""
89 }
90 return startupErr.Stage, startupErr.Elapsed, startupErr.Stderr
91 }
92
93 func formatElapsed(elapsed time.Duration) string {
94 if elapsed < time.Millisecond {
95 return elapsed.String()
96 }
97 return elapsed.Round(time.Millisecond).String()
98 }
99
100 type startupDiagnosticTransport interface {
101 startupStderr() string
102 }
103
104 func (c *Client) startupStderr() string {
105 if c == nil || c.t == nil {
106 return ""
107 }
108 if diagnostic, ok := c.t.(startupDiagnosticTransport); ok {
109 return diagnostic.startupStderr()
110 }
111 return ""
112 }
113
113 lines GO