Skip to content

Commit 50c6c1d

Browse files
committed
refactoring and consolidation
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 37db1ff commit 50c6c1d

17 files changed

Lines changed: 242 additions & 63 deletions

File tree

core/cli/agent_worker.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,18 @@ func (cmd *AgentWorkerCMD) Run(ctx *cliContext.Context) error {
139139

140140
// Subscribe to MCP tool execution requests (load-balanced across workers).
141141
// The frontend routes model-level MCP tool calls here via NATS request-reply.
142-
natsClient.QueueSubscribeReply(messaging.SubjectMCPToolExecute, messaging.QueueAgentWorkers, func(data []byte, reply func([]byte)) {
142+
if _, err := natsClient.QueueSubscribeReply(messaging.SubjectMCPToolExecute, messaging.QueueAgentWorkers, func(data []byte, reply func([]byte)) {
143143
handleMCPToolRequest(data, reply)
144-
})
144+
}); err != nil {
145+
return fmt.Errorf("subscribing to %s: %w", messaging.SubjectMCPToolExecute, err)
146+
}
145147

146148
// Subscribe to MCP discovery requests (load-balanced across workers).
147-
natsClient.QueueSubscribeReply(messaging.SubjectMCPDiscovery, messaging.QueueAgentWorkers, func(data []byte, reply func([]byte)) {
149+
if _, err := natsClient.QueueSubscribeReply(messaging.SubjectMCPDiscovery, messaging.QueueAgentWorkers, func(data []byte, reply func([]byte)) {
148150
handleMCPDiscoveryRequest(data, reply)
149-
})
151+
}); err != nil {
152+
return fmt.Errorf("subscribing to %s: %w", messaging.SubjectMCPDiscovery, err)
153+
}
150154

151155
// Subscribe to MCP CI job execution (load-balanced across agent workers).
152156
// In distributed mode, MCP CI jobs are routed here because the frontend
@@ -157,21 +161,25 @@ func (cmd *AgentWorkerCMD) Run(ctx *cliContext.Context) error {
157161
}
158162
mcpCIJobTimeout = cmp.Or(mcpCIJobTimeout, config.DefaultMCPCIJobTimeout)
159163

160-
natsClient.QueueSubscribe(messaging.SubjectMCPCIJobsNew, messaging.QueueWorkers, func(data []byte) {
164+
if _, err := natsClient.QueueSubscribe(messaging.SubjectMCPCIJobsNew, messaging.QueueWorkers, func(data []byte) {
161165
handleMCPCIJob(shutdownCtx, data, apiURL, cmd.APIToken, natsClient, mcpCIJobTimeout)
162-
})
166+
}); err != nil {
167+
return fmt.Errorf("subscribing to %s: %w", messaging.SubjectMCPCIJobsNew, err)
168+
}
163169

164170
// Subscribe to backend stop events to clean up cached MCP sessions.
165171
// In the main application this is done via ml.OnModelUnload, but the agent
166172
// worker has no model loader — we listen for the NATS stop event instead.
167-
natsClient.Subscribe(messaging.SubjectNodeBackendStop(nodeID), func(data []byte) {
173+
if _, err := natsClient.Subscribe(messaging.SubjectNodeBackendStop(nodeID), func(data []byte) {
168174
var req struct {
169175
Backend string `json:"backend"`
170176
}
171177
if json.Unmarshal(data, &req) == nil && req.Backend != "" {
172178
mcpTools.CloseMCPSessions(req.Backend)
173179
}
174-
})
180+
}); err != nil {
181+
return fmt.Errorf("subscribing to %s: %w", messaging.SubjectNodeBackendStop(nodeID), err)
182+
}
175183

176184
xlog.Info("Agent worker ready, waiting for jobs", "subject", cmd.Subject, "queue", cmd.Queue)
177185

core/config/distributed_config.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,5 @@ func (c DistributedConfig) MCPCIJobTimeoutOrDefault() time.Duration {
184184

185185
// MaxUploadSizeOrDefault returns the configured max upload size or the default.
186186
func (c DistributedConfig) MaxUploadSizeOrDefault() int64 {
187-
if c.MaxUploadSize > 0 {
188-
return c.MaxUploadSize
189-
}
190-
return DefaultMaxUploadSize
187+
return cmp.Or(c.MaxUploadSize, DefaultMaxUploadSize)
191188
}

core/http/auth/middleware.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,14 @@ func isAPIPath(path string) bool {
545545
strings.HasPrefix(path, "/system") ||
546546
strings.HasPrefix(path, "/ws/") ||
547547
strings.HasPrefix(path, "/generated-") ||
548+
strings.HasPrefix(path, "/chat/") ||
549+
strings.HasPrefix(path, "/completions") ||
550+
strings.HasPrefix(path, "/edits") ||
551+
strings.HasPrefix(path, "/embeddings") ||
552+
strings.HasPrefix(path, "/audio/") ||
553+
strings.HasPrefix(path, "/images/") ||
554+
strings.HasPrefix(path, "/messages") ||
555+
strings.HasPrefix(path, "/responses") ||
548556
path == "/metrics"
549557
}
550558

core/http/endpoints/localai/cors_proxy.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package localai
33
import (
44
"fmt"
55
"io"
6+
"net"
67
"net/http"
78
"net/url"
89
"strings"
@@ -13,6 +14,33 @@ import (
1314
"github.com/mudler/xlog"
1415
)
1516

17+
var privateNetworks []*net.IPNet
18+
19+
func init() {
20+
for _, cidr := range []string{
21+
"10.0.0.0/8",
22+
"172.16.0.0/12",
23+
"192.168.0.0/16",
24+
"127.0.0.0/8",
25+
"169.254.0.0/16",
26+
"::1/128",
27+
"fc00::/7",
28+
"fe80::/10",
29+
} {
30+
_, network, _ := net.ParseCIDR(cidr)
31+
privateNetworks = append(privateNetworks, network)
32+
}
33+
}
34+
35+
func isPrivateIP(ip net.IP) bool {
36+
for _, network := range privateNetworks {
37+
if network.Contains(ip) {
38+
return true
39+
}
40+
}
41+
return false
42+
}
43+
1644
var corsProxyClient = &http.Client{
1745
Timeout: 10 * time.Minute,
1846
}
@@ -36,6 +64,16 @@ func CORSProxyEndpoint(appConfig *config.ApplicationConfig) echo.HandlerFunc {
3664
return c.JSON(http.StatusBadRequest, map[string]string{"error": "only http and https schemes are supported"})
3765
}
3866

67+
ips, err := net.LookupIP(parsed.Hostname())
68+
if err != nil {
69+
return c.JSON(http.StatusBadRequest, map[string]string{"error": "cannot resolve hostname"})
70+
}
71+
for _, ip := range ips {
72+
if isPrivateIP(ip) {
73+
return c.JSON(http.StatusForbidden, map[string]string{"error": "requests to private networks are not allowed"})
74+
}
75+
}
76+
3977
xlog.Debug("CORS proxy request", "method", c.Request().Method, "target", targetURL)
4078

4179
proxyReq, err := http.NewRequestWithContext(
@@ -53,6 +91,8 @@ func CORSProxyEndpoint(appConfig *config.ApplicationConfig) echo.HandlerFunc {
5391
"Host": true, "Connection": true, "Keep-Alive": true,
5492
"Transfer-Encoding": true, "Upgrade": true, "Origin": true,
5593
"Referer": true,
94+
"Authorization": true, "Cookie": true,
95+
"X-Api-Key": true, "Proxy-Authorization": true,
5696
}
5797
for key, values := range c.Request().Header {
5898
if skipHeaders[key] {

core/http/routes/anthropic.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,11 @@ func setAnthropicRequestContext(appConfig *config.ApplicationConfig) echo.Middle
7575
reqCtx := c.Request().Context()
7676
c1, cancel := context.WithCancel(appConfig.Context)
7777

78-
// Cancel when request context is cancelled (client disconnects)
79-
go func() {
80-
select {
81-
case <-reqCtx.Done():
82-
cancel()
83-
case <-c1.Done():
84-
// Already cancelled
85-
}
78+
// Bridge request cancellation to c1 without spawning a goroutine.
79+
stop := context.AfterFunc(reqCtx, cancel)
80+
defer func() {
81+
stop() // deregister callback if it hasn't fired
82+
cancel() // release c1 resources (idempotent)
8683
}()
8784

8885
// Add the correlation ID to the new context

core/http/routes/ui.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"net/http"
8+
"net/url"
89
"slices"
910
"sync"
1011
"time"
@@ -21,7 +22,15 @@ import (
2122

2223
var backendLogsUpgrader = websocket.Upgrader{
2324
CheckOrigin: func(r *http.Request) bool {
24-
return true
25+
origin := r.Header.Get("Origin")
26+
if origin == "" {
27+
return true // no origin header = same-origin or non-browser
28+
}
29+
u, err := url.Parse(origin)
30+
if err != nil {
31+
return false
32+
}
33+
return u.Host == r.Host
2534
},
2635
}
2736

core/services/advisorylock/leader_loop.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func RunLeaderLoop(ctx context.Context, db *gorm.DB, lockKey int64, interval tim
2121
case <-ctx.Done():
2222
return
2323
case <-ticker.C:
24-
_, err := TryWithLock(db, lockKey, func() error {
24+
_, err := TryWithLockCtx(ctx, db, lockKey, func() error {
2525
fn()
2626
return nil
2727
})

0 commit comments

Comments
 (0)