Repository navigation
Expand file tree
/
Copy pathexecutor.go
More file actions
176 lines (166 loc) · 4.71 KB
/
Copy pathexecutor.go
File metadata and controls
176 lines (166 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package mcode
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent"
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)
var errTurnCancelled = errors.New("mcode: Turn cancelled before submission")
type executor struct {
mu sync.Mutex
connection *connection
req proto.PromptRequestPayload
opts launchOptions
nativeSession, model string
active *Session
starting, closed, invalid bool
}
var _ agent.Executor = (*executor)(nil)
var _ agent.Turn = (*Session)(nil)
// NewExecutorFactory fixes the deployment workspace once; nil selects none.
func NewExecutorFactory(runtimeRoot string, config *WorkspaceConfig) agent.ExecutorFactory {
var frozen *WorkspaceConfig
if config != nil {
value := *config
value.AllowedDomains = append([]string(nil), config.AllowedDomains...)
frozen = &value
}
return func(ctx context.Context, req proto.PromptRequestPayload) (agent.Executor, error) {
if ctx == nil {
ctx = context.Background()
}
if req.RunID != "" || len(req.Input) != 0 {
return nil, fmt.Errorf("mcode: Executor configuration cannot contain Turn input")
}
var err error
var opts launchOptions
binary := defaultBinary()
if frozen == nil {
opts, err = prepareOptions(runtimeRoot, req)
} else {
binary = frozen.Binary
opts, err = prepareWorkspaceOptions(runtimeRoot, *frozen, req)
}
if err != nil {
return nil, err
}
resource, err := newExecutor(ctx, req, opts, binary)
if resource == nil {
return nil, err
}
return resource, err
}
}
func newExecutor(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string) (*executor, error) {
bootstrap, err := launch(ctx, req, opts, binary)
if err != nil {
return nil, err
}
e := &executor{connection: bootstrap.connection, req: req, opts: opts}
err = bootstrap.prepareNative()
e.connection.setCurrent(nil)
close(bootstrap.finished)
if err == nil {
err = ctx.Err()
}
if err != nil {
cleanup, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if closeErr := e.Close(cleanup); closeErr != nil {
return e, err
}
return nil, err
}
e.nativeSession, e.model = bootstrap.sessionID, bootstrap.nativeModel
return e, nil
}
func (e *executor) StartTurn(ctx context.Context, runID string, input proto.MessageInput, out chan<- proto.Envelope) (agent.Turn, error) {
if ctx == nil {
ctx = context.Background()
}
if strings.TrimSpace(runID) == "" || input.Validate() != nil || out == nil || ctx.Err() != nil {
return nil, fmt.Errorf("mcode: Turn requires live context, identity, input and output")
}
text, err := input.TextOnly()
if err != nil {
return nil, err
}
e.mu.Lock()
if e.closed || e.invalid || e.starting || e.active != nil {
e.mu.Unlock()
return nil, fmt.Errorf("mcode: Executor is unavailable")
}
e.starting = true
e.mu.Unlock()
// No model input is sent and no output channel retained until this fence.
barrier, cancel := context.WithTimeout(ctx, 60*time.Second)
err = e.connection.barrier(barrier, e.nativeSession, e.model)
cancel()
e.mu.Lock()
defer e.mu.Unlock()
e.starting = false
if err != nil || e.closed || ctx.Err() != nil {
if err != nil {
e.invalid = true
return nil, err
}
return nil, fmt.Errorf("mcode: Executor closed before input")
}
select {
case <-e.connection.exited:
e.invalid = true
return nil, fmt.Errorf("mcode: native process exited")
default:
}
req := e.req
req.RunID = runID
s := newTurnSession(e.connection.process.Context(), req, e.opts, e.connection, out)
s.executor, s.sessionID, s.nativeModel = e, e.nativeSession, e.model
s.settled, s.inputDone = make(chan struct{}), make(chan struct{})
s.outputContext, s.outputCancel = context.WithCancel(context.Background())
e.active = s
e.connection.setCurrent(s)
go s.runExecutorTurn(text)
go func() {
select {
case <-ctx.Done():
cancellation, stop := context.WithTimeout(context.Background(), 10*time.Second)
defer stop()
_ = s.Cancel(cancellation)
case <-s.settled:
}
}()
return s, nil
}
// Close keeps the native owner reachable until process, pipes and active Turn
// settlement all finish. Repeating it waits on those same owners.
func (e *executor) Close(ctx context.Context) error {
if ctx == nil {
ctx = context.Background()
}
e.mu.Lock()
e.closed = true
current := e.active
if current != nil {
current.outputCancel()
}
e.connection.process.Cancel()
e.mu.Unlock()
select {
case <-e.connection.exited:
case <-ctx.Done():
return ctx.Err()
}
if current != nil {
select {
case <-current.settled:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}