feat: model selection at spawn (POST /api/spawn model -> LVMH_MODEL -> bridge createAgentSession); ops prepare resets ops session (fresh context per prepare)
This commit is contained in:
+14
-7
@@ -81,6 +81,9 @@ var repoPathRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)+$`)
|
||||
// branchRe guards spawn branch names against git option injection.
|
||||
var branchRe = regexp.MustCompile(`^[A-Za-z0-9._/-]+$`)
|
||||
|
||||
// modelSpecRe matches spawn "model" specs: provider/model-id.
|
||||
var modelSpecRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$`)
|
||||
|
||||
// validRepoPath accepts "group/project" style paths (at least two segments).
|
||||
func validRepoPath(repo string) bool { return repoPathRe.MatchString(repo) }
|
||||
|
||||
@@ -236,7 +239,7 @@ func (s *Spawner) deleteJob(sessionID string) {
|
||||
}
|
||||
|
||||
// Start launches the async spawn pipeline and returns the new sessionId.
|
||||
func (s *Spawner) Start(ctx context.Context, repo, branch string) (SpawnResult, error) {
|
||||
func (s *Spawner) Start(ctx context.Context, repo, branch, model string) (SpawnResult, error) {
|
||||
exists, err := s.imageExists(ctx)
|
||||
if err != nil {
|
||||
return SpawnResult{}, fmt.Errorf("docker unavailable: %w", err)
|
||||
@@ -250,7 +253,7 @@ func (s *Spawner) Start(ctx context.Context, repo, branch string) (SpawnResult,
|
||||
}
|
||||
sessionID := newUUID()
|
||||
s.setJob(sessionID, repo, stateCloning, "", "")
|
||||
go s.runJob(repo, branch, sessionID)
|
||||
go s.runJob(repo, branch, model, sessionID)
|
||||
return SpawnResult{SessionID: sessionID, ImageUsed: s.resolveImage(repo)}, nil
|
||||
}
|
||||
|
||||
@@ -289,7 +292,7 @@ func (s *Spawner) slugLock(slug string) *sync.Mutex {
|
||||
}
|
||||
|
||||
// runJob is the async clone→build→create→start pipeline.
|
||||
func (s *Spawner) runJob(repo, branch, sessionID string) {
|
||||
func (s *Spawner) runJob(repo, branch, model, sessionID string) {
|
||||
slug := repoSlug(repo)
|
||||
lock := s.slugLock(slug)
|
||||
lock.Lock()
|
||||
@@ -305,7 +308,7 @@ func (s *Spawner) runJob(repo, branch, sessionID string) {
|
||||
return
|
||||
}
|
||||
s.setJob(sessionID, repo, stateCreating, "", "")
|
||||
containerID, image, err := s.createAndStart(s.ctx, repo, slug, sessionID)
|
||||
containerID, image, err := s.createAndStart(s.ctx, repo, slug, model, sessionID)
|
||||
if err != nil {
|
||||
s.setJob(sessionID, repo, stateError, "", err.Error())
|
||||
return
|
||||
@@ -364,7 +367,7 @@ func giteaTokenEnv(store *Store) string {
|
||||
}
|
||||
|
||||
// workerEnv assembles the env for a spawned worker container.
|
||||
func workerEnv(s *Spawner, repo, sessionID string) []string {
|
||||
func workerEnv(s *Spawner, repo, model, sessionID string) []string {
|
||||
env := []string{
|
||||
envProviderAPIKey + "=" + os.Getenv(envProviderAPIKey),
|
||||
envToken + "=" + daemonToken,
|
||||
@@ -379,6 +382,10 @@ func workerEnv(s *Spawner, repo, sessionID string) []string {
|
||||
"PLAYWRIGHT_BROWSERS_PATH=/pw-browsers",
|
||||
envLVMHRepo + "=" + repo,
|
||||
}
|
||||
// Optional initial model selection (provider/model-id).
|
||||
if model != "" {
|
||||
env = append(env, "LVMH_MODEL="+model)
|
||||
}
|
||||
// Gitea token (write scope) so agents can push and open PRs.
|
||||
if e := giteaTokenEnv(s.store); e != "" {
|
||||
env = append(env, e)
|
||||
@@ -515,7 +522,7 @@ func extractBuildError(body []byte) string {
|
||||
// createAndStart provisions volumes, creates and starts the worker container.
|
||||
// A repo-registered custom image (see /api/repos) overrides the default
|
||||
// worker image; the ops agent builds and registers those.
|
||||
func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID string) (string, string, error) {
|
||||
func (s *Spawner) createAndStart(ctx context.Context, repo, slug, model, sessionID string) (string, string, error) {
|
||||
image := s.resolveImage(repo)
|
||||
if image != imageRefWorker {
|
||||
exists, err := s.imageRefExists(ctx, image)
|
||||
@@ -581,7 +588,7 @@ func (s *Spawner) createAndStart(ctx context.Context, repo, slug, sessionID stri
|
||||
}
|
||||
cfg := &container.Config{
|
||||
Image: image,
|
||||
Env: workerEnv(s, repo, sessionID),
|
||||
Env: workerEnv(s, repo, model, sessionID),
|
||||
Labels: map[string]string{labelSession: sessionID},
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
|
||||
Reference in New Issue
Block a user