返回 DeepSeek-Reasonix
lazy.go
根目录 / internal / browser / cdp / lazy.go
1 package cdp
2
3 import (
4 "context"
5 "sync"
6
7 "reasonix/internal/browser"
8 )
9
10 // Lazy is the executor a session is configured with but has not paid for yet.
11 // Attaching means launching or connecting to a browser, which no session
12 // should do just because the tools are registered, so the browser appears on
13 // the first tool call and not before.
14 type Lazy struct {
15 opts Options
16
17 mu sync.Mutex
18 exec *Executor
19 err error
20 }
21
22 var _ browser.Executor = (*Lazy)(nil)
23
24 // NewLazy returns an executor that attaches on first use.
25 func NewLazy(opts Options) *Lazy { return &Lazy{opts: opts} }
26
27 // ensure attaches once. A start failure is remembered and returned to every
28 // later call, so a missing browser costs one launch timeout and not one per
29 // tool call; a caller who cancelled their own context leaves no such verdict.
30 func (l *Lazy) ensure(ctx context.Context) (*Executor, error) {
31 l.mu.Lock()
32 defer l.mu.Unlock()
33 if l.exec != nil {
34 return l.exec, nil
35 }
36 if l.err != nil {
37 return nil, l.err
38 }
39 exec, err := New(ctx, l.opts)
40 if err != nil {
41 if ctx.Err() == nil {
42 l.err = err
43 }
44 return nil, err
45 }
46 l.exec = exec
47 return exec, nil
48 }
49
50 // Available keeps an unattached executor visible: the capability exists, and
51 // the first call is what reports why a browser could not be reached.
52 func (l *Lazy) Available(ctx context.Context) bool {
53 l.mu.Lock()
54 exec, err := l.exec, l.err
55 l.mu.Unlock()
56 if exec != nil {
57 return exec.Available(ctx)
58 }
59 return err == nil
60 }
61
62 // Shutdown releases the browser if one was ever attached.
63 func (l *Lazy) Shutdown() {
64 l.mu.Lock()
65 exec := l.exec
66 l.exec = nil
67 l.mu.Unlock()
68 if exec != nil {
69 exec.Shutdown()
70 }
71 }
72
73 func (l *Lazy) Tabs(ctx context.Context) ([]browser.Tab, error) {
74 exec, err := l.ensure(ctx)
75 if err != nil {
76 return nil, err
77 }
78 return exec.Tabs(ctx)
79 }
80
81 func (l *Lazy) Open(ctx context.Context, req browser.OpenRequest) (browser.Tab, error) {
82 exec, err := l.ensure(ctx)
83 if err != nil {
84 return browser.Tab{}, err
85 }
86 return exec.Open(ctx, req)
87 }
88
89 func (l *Lazy) Navigate(ctx context.Context, req browser.NavigateRequest) (browser.Tab, error) {
90 exec, err := l.ensure(ctx)
91 if err != nil {
92 return browser.Tab{}, err
93 }
94 return exec.Navigate(ctx, req)
95 }
96
97 func (l *Lazy) Snapshot(ctx context.Context, req browser.SnapshotRequest) (browser.Snapshot, error) {
98 exec, err := l.ensure(ctx)
99 if err != nil {
100 return browser.Snapshot{}, err
101 }
102 return exec.Snapshot(ctx, req)
103 }
104
105 func (l *Lazy) Screenshot(ctx context.Context, req browser.ScreenshotRequest) (browser.Screenshot, error) {
106 exec, err := l.ensure(ctx)
107 if err != nil {
108 return browser.Screenshot{}, err
109 }
110 return exec.Screenshot(ctx, req)
111 }
112
113 func (l *Lazy) Act(ctx context.Context, req browser.ActRequest) (browser.ActResult, error) {
114 exec, err := l.ensure(ctx)
115 if err != nil {
116 return browser.ActResult{}, err
117 }
118 return exec.Act(ctx, req)
119 }
120
121 func (l *Lazy) Downloads(ctx context.Context, req browser.DownloadsRequest) ([]browser.Download, error) {
122 exec, err := l.ensure(ctx)
123 if err != nil {
124 return nil, err
125 }
126 return exec.Downloads(ctx, req)
127 }
128
129 func (l *Lazy) Close(ctx context.Context, req browser.CloseRequest) error {
130 exec, err := l.ensure(ctx)
131 if err != nil {
132 return err
133 }
134 return exec.Close(ctx, req)
135 }
136
136 lines GO