StopWorkspace returns a tool that stops the workspace associated with the current chat. The tool is idempotent when the workspace is already stopped. db must not be nil and chatID must not be uuid.Nil.
(db database.Store, chatID uuid.UUID, options StopWorkspaceOptions)
| 38 | // with the current chat. The tool is idempotent when the workspace is |
| 39 | // already stopped. db must not be nil and chatID must not be uuid.Nil. |
| 40 | func StopWorkspace(db database.Store, chatID uuid.UUID, options StopWorkspaceOptions) fantasy.AgentTool { |
| 41 | return fantasy.NewAgentTool( |
| 42 | "stop_workspace", |
| 43 | "Stop the chat's workspace and wait for the stop build to complete. "+ |
| 44 | "If another workspace build is already in progress, this waits "+ |
| 45 | "for that build first, then stops the workspace if needed. "+ |
| 46 | "After waiting, this tool is idempotent if the workspace is "+ |
| 47 | "already stopped or the in-progress build stopped it. Use "+ |
| 48 | "this when the "+ |
| 49 | "user explicitly asks to stop the workspace, or when a "+ |
| 50 | "workspace-agent error tells you to stop and then start the "+ |
| 51 | "workspace. Stopping a workspace terminates running processes "+ |
| 52 | "and may discard unsaved in-memory state. This tool does not "+ |
| 53 | "delete the workspace.", |
| 54 | func(ctx context.Context, _ stopWorkspaceArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) { |
| 55 | if options.StopFn == nil { |
| 56 | return fantasy.NewTextErrorResponse("workspace stopper is not configured"), nil |
| 57 | } |
| 58 | |
| 59 | // Serialize with create_workspace and start_workspace to |
| 60 | // prevent lifecycle races. |
| 61 | if options.WorkspaceMu != nil { |
| 62 | options.WorkspaceMu.Lock() |
| 63 | defer options.WorkspaceMu.Unlock() |
| 64 | } |
| 65 | |
| 66 | chat, err := db.GetChatByID(ctx, chatID) |
| 67 | if err != nil { |
| 68 | return fantasy.NewTextErrorResponse( |
| 69 | xerrors.Errorf("load chat: %w", err).Error(), |
| 70 | ), nil |
| 71 | } |
| 72 | if !chat.WorkspaceID.Valid { |
| 73 | return fantasy.NewTextErrorResponse( |
| 74 | "chat has no workspace; use create_workspace first", |
| 75 | ), nil |
| 76 | } |
| 77 | |
| 78 | ws, err := db.GetWorkspaceByID(ctx, chat.WorkspaceID.UUID) |
| 79 | if err != nil { |
| 80 | return fantasy.NewTextErrorResponse( |
| 81 | xerrors.Errorf("load workspace: %w", err).Error(), |
| 82 | ), nil |
| 83 | } |
| 84 | if ws.Deleted { |
| 85 | return fantasy.NewTextErrorResponse( |
| 86 | "workspace was deleted; use create_workspace to make a new one", |
| 87 | ), nil |
| 88 | } |
| 89 | |
| 90 | build, job, err := latestWorkspaceBuildAndJob(ctx, db, ws.ID) |
| 91 | if err != nil { |
| 92 | return fantasy.NewTextErrorResponse(err.Error()), nil |
| 93 | } |
| 94 | |
| 95 | // If a build is already in progress, wait for it before |
| 96 | // deciding whether a stop build is still needed. |
| 97 | switch job.JobStatus { |