Skip to content

Commit e8d50a4

Browse files
authored
Merge pull request #861 from krissetto/dont-reload-from-disk-if-oci-ref
Fix api endpoint handling when using oci references
2 parents 7ffb6f4 + 50536b7 commit e8d50a4

3 files changed

Lines changed: 335 additions & 16 deletions

File tree

cmd/root/api.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,33 @@ func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error {
8585

8686
var opts []server.Opt
8787

88-
stat, err := os.Stat(resolvedPath)
89-
if err != nil {
90-
return fmt.Errorf("failed to stat agents path: %w", err)
91-
}
92-
if stat.IsDir() {
93-
opts = append(opts, server.WithAgentsDir(resolvedPath))
94-
} else {
95-
opts = append(opts, server.WithAgentsDir(filepath.Dir(resolvedPath)))
88+
if !agentfile.IsOCIReference(agentsPath) {
89+
// Local files/dirs: set agentsPath for single-file reload, agentsDir for write ops
90+
opts = append(opts, server.WithAgentsPath(agentsPath))
91+
92+
stat, err := os.Stat(resolvedPath)
93+
if err != nil {
94+
return fmt.Errorf("failed to stat agents path: %w", err)
95+
}
96+
if stat.IsDir() {
97+
opts = append(opts, server.WithAgentsDir(resolvedPath))
98+
} else {
99+
opts = append(opts, server.WithAgentsDir(filepath.Dir(resolvedPath)))
100+
}
96101
}
97102

98103
teams, err := teamloader.LoadTeams(ctx, resolvedPath, &f.runConfig)
99104
if err != nil {
100105
return fmt.Errorf("failed to load teams: %w", err)
101106
}
107+
108+
// For OCI refs: clean up the temp file immediately after loading
109+
// We don't need it anymore since teams are now in memory
110+
if agentfile.IsOCIReference(agentsPath) {
111+
_ = os.Remove(resolvedPath)
112+
slog.Debug("Cleaned up temporary OCI file", "path", resolvedPath)
113+
}
114+
102115
defer func() {
103116
for _, team := range teams {
104117
if err := team.StopToolSets(ctx); err != nil {

pkg/server/server.go

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Server struct {
4949
teams map[string]*team.Team
5050
teamsMu sync.RWMutex
5151
agentsDir string
52+
agentsPath string // For local files: specific file path to reload (instead of scanning agentsDir)
5253
rootFS *os.Root
5354
}
5455

@@ -67,6 +68,13 @@ func WithAgentsDir(dir string) Opt {
6768
}
6869
}
6970

71+
func WithAgentsPath(agentPath string) Opt {
72+
return func(s *Server) error {
73+
s.agentsPath = agentPath
74+
return nil
75+
}
76+
}
77+
7078
func New(sessionStore session.Store, runConfig *config.RuntimeConfig, teams map[string]*team.Team, opts ...Opt) (*Server, error) {
7179
e := echo.New()
7280
e.Use(middleware.CORS())
@@ -156,6 +164,12 @@ func (s *Server) Serve(ctx context.Context, ln net.Listener) error {
156164
return nil
157165
}
158166

167+
// hasAgentsDir returns true if the server has a local agents directory.
168+
// Returns false for OCI reference-based servers, which load from memory.
169+
func (s *Server) hasAgentsDir() bool {
170+
return s.agentsDir != "" && s.rootFS != nil
171+
}
172+
159173
// getTeam retrieves a team by key with read lock
160174
func (s *Server) getTeam(key string) (*team.Team, bool) {
161175
s.teamsMu.RLock()
@@ -259,6 +273,10 @@ func (s *Server) getAgentConfigYAML(c echo.Context) error {
259273
}
260274

261275
func (s *Server) editAgentConfigYAML(c echo.Context) error {
276+
if !s.hasAgentsDir() {
277+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
278+
}
279+
262280
agentID := c.Param("id")
263281
p := addYamlExt(agentID)
264282

@@ -311,6 +329,10 @@ func (s *Server) editAgentConfigYAML(c echo.Context) error {
311329
}
312330

313331
func (s *Server) editAgentConfig(c echo.Context) error {
332+
if !s.hasAgentsDir() {
333+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
334+
}
335+
314336
var req api.EditAgentConfigRequest
315337
if err := c.Bind(&req); err != nil {
316338
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
@@ -397,6 +419,10 @@ func (s *Server) editAgentConfig(c echo.Context) error {
397419
}
398420

399421
func (s *Server) createAgent(c echo.Context) error {
422+
if !s.hasAgentsDir() {
423+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
424+
}
425+
400426
var req api.CreateAgentRequest
401427
if err := c.Bind(&req); err != nil {
402428
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
@@ -427,6 +453,10 @@ func (s *Server) createAgent(c echo.Context) error {
427453
}
428454

429455
func (s *Server) createAgentConfig(c echo.Context) error {
456+
if !s.hasAgentsDir() {
457+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
458+
}
459+
430460
var req api.CreateAgentConfigRequest
431461
if err := c.Bind(&req); err != nil {
432462
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
@@ -519,6 +549,10 @@ func (s *Server) createAgentConfig(c echo.Context) error {
519549
}
520550

521551
func (s *Server) importAgent(c echo.Context) error {
552+
if !s.hasAgentsDir() {
553+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
554+
}
555+
522556
var req api.ImportAgentRequest
523557
if err := c.Bind(&req); err != nil {
524558
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
@@ -613,6 +647,10 @@ func (s *Server) importAgent(c echo.Context) error {
613647
}
614648

615649
func (s *Server) exportAgents(c echo.Context) error {
650+
if !s.hasAgentsDir() {
651+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
652+
}
653+
616654
// Create zip file in the agents directory
617655
zipFileName := fmt.Sprintf("agents_export_%d.zip", time.Now().Unix())
618656
zipPath := filepath.Join(s.agentsDir, zipFileName)
@@ -793,6 +831,10 @@ func (s *Server) pushAgent(c echo.Context) error {
793831
}
794832

795833
func (s *Server) deleteAgent(c echo.Context) error {
834+
if !s.hasAgentsDir() {
835+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "write operations not supported for read-only OCI references")
836+
}
837+
796838
var req api.DeleteAgentRequest
797839
if err := c.Bind(&req); err != nil {
798840
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
@@ -851,8 +893,12 @@ func (s *Server) deleteAgent(c echo.Context) error {
851893

852894
func (s *Server) getAgents(c echo.Context) error {
853895
// Refresh agents from disk to get the latest configurations
854-
if err := s.refreshAgentsFromDisk(c.Request().Context()); err != nil {
855-
slog.Error("Failed to refresh agents from disk", "error", err)
896+
// Only refresh for local files/dirs (hasAgentsDir == true)
897+
// OCI refs are immutable and loaded into memory, updates handled via ReloadTeams()
898+
if s.hasAgentsDir() {
899+
if err := s.refreshAgentsFromDisk(c.Request().Context()); err != nil {
900+
slog.Error("Failed to refresh agents from disk", "error", err)
901+
}
856902
}
857903

858904
// DO NOT, under any circumstance, replace this with "var agents []api.Agent",
@@ -904,13 +950,21 @@ func (s *Server) getAgents(c echo.Context) error {
904950
}
905951

906952
func (s *Server) refreshAgentsFromDisk(ctx context.Context) error {
907-
if s.agentsDir == "" {
953+
// Use agentsPath if set (for specific files), otherwise fall back to agentsDir
954+
// Note: OCI references are handled via ReloadTeams() and never call this function
955+
// (see getAgents() which skips refresh for OCI refs)
956+
pathToLoad := s.agentsPath
957+
if pathToLoad == "" {
958+
pathToLoad = s.agentsDir
959+
}
960+
961+
if pathToLoad == "" {
908962
return nil
909963
}
910964

911-
slog.Debug("Refreshing agents from disk", "agentsDir", s.agentsDir)
965+
slog.Debug("Refreshing agents from disk", "path", pathToLoad)
912966

913-
newTeams, err := teamloader.LoadTeams(ctx, s.agentsDir, s.runConfig)
967+
newTeams, err := teamloader.LoadTeams(ctx, pathToLoad, s.runConfig)
914968
if err != nil {
915969
return fmt.Errorf("failed to load teams: %w", err)
916970
}
@@ -1147,9 +1201,28 @@ func (s *Server) runAgent(c echo.Context) error {
11471201
rc := s.runConfig
11481202
rc.WorkingDir = sess.WorkingDir
11491203

1150-
t, err := teamloader.Load(c.Request().Context(), filepath.Join(s.agentsDir, p), rc)
1151-
if err != nil {
1152-
return echo.NewHTTPError(http.StatusInternalServerError, "failed to load agent for session")
1204+
// Load team - either reload from disk (local) or use in-memory team (OCI refs)
1205+
var t *team.Team
1206+
1207+
if s.hasAgentsDir() {
1208+
// Has local agents path or directory: reload from disk to pick up changes
1209+
loadPath := s.agentsPath
1210+
if loadPath == "" {
1211+
loadPath = filepath.Join(s.agentsDir, p)
1212+
}
1213+
1214+
var loadErr error
1215+
t, loadErr = teamloader.Load(c.Request().Context(), loadPath, rc)
1216+
if loadErr != nil {
1217+
return echo.NewHTTPError(http.StatusInternalServerError, "failed to load agent for session")
1218+
}
1219+
} else {
1220+
// No local directory: use the already-loaded team from memory (OCI refs)
1221+
var exists bool
1222+
t, exists = s.getTeam(p)
1223+
if !exists {
1224+
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("agent not found: %s", agentFilename))
1225+
}
11531226
}
11541227

11551228
agent, err := t.Agent(currentAgent)

0 commit comments

Comments
 (0)