CreateWorkspace returns a tool that creates a new workspace from a template. The tool is idempotent: if the chat already has a workspace that is building or running, it returns the existing workspace instead of creating a new one. A mutex prevents parallel calls from creating duplicate workspaces. d
(db database.Store, organizationID, chatID uuid.UUID, options CreateWorkspaceOptions)
| 87 | // calls from creating duplicate workspaces. |
| 88 | // db must not be nil and chatID must not be uuid.Nil. |
| 89 | func CreateWorkspace(db database.Store, organizationID, chatID uuid.UUID, options CreateWorkspaceOptions) fantasy.AgentTool { |
| 90 | return fantasy.NewAgentTool( |
| 91 | "create_workspace", |
| 92 | "Create a new workspace from a template only when workspace-backed "+ |
| 93 | "file inspection, command execution, or file editing is required, "+ |
| 94 | "or when the user explicitly asks for one. Do not use this as a "+ |
| 95 | "default first step for requests answerable from conversation "+ |
| 96 | "context, provider tools, or external MCP tools. Requires a "+ |
| 97 | "template_id (from list_templates). Optionally provide "+ |
| 98 | "a name and parameter values (from read_template). "+ |
| 99 | "If no name is given, one will be generated. "+ |
| 100 | "Provide a preset_id (from read_template) to apply "+ |
| 101 | "preset parameters and potentially claim a prebuilt "+ |
| 102 | "workspace for faster startup. "+ |
| 103 | "This tool is idempotent. If the chat already has a "+ |
| 104 | "workspace that is building or running, the existing "+ |
| 105 | "workspace is returned.", |
| 106 | func(ctx context.Context, args createWorkspaceArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) { |
| 107 | if options.CreateFn == nil { |
| 108 | return fantasy.NewTextErrorResponse("workspace creator is not configured"), nil |
| 109 | } |
| 110 | |
| 111 | templateIDStr := strings.TrimSpace(args.TemplateID) |
| 112 | if templateIDStr == "" { |
| 113 | return fantasy.NewTextErrorResponse("template_id is required; use list_templates to find one"), nil |
| 114 | } |
| 115 | templateID, err := uuid.Parse(templateIDStr) |
| 116 | if err != nil { |
| 117 | return fantasy.NewTextErrorResponse( |
| 118 | xerrors.Errorf("invalid template_id: %w", err).Error(), |
| 119 | ), nil |
| 120 | } |
| 121 | |
| 122 | if !isTemplateAllowed(options.AllowedTemplateIDs, templateID) { |
| 123 | return fantasy.NewTextErrorResponse("template not available for chat workspaces; use list_templates to find allowed templates"), nil |
| 124 | } |
| 125 | |
| 126 | // Serialize workspace creation to prevent parallel |
| 127 | // tool calls from creating duplicate workspaces. |
| 128 | if options.WorkspaceMu != nil { |
| 129 | options.WorkspaceMu.Lock() |
| 130 | defer options.WorkspaceMu.Unlock() |
| 131 | } |
| 132 | |
| 133 | ownerID := options.OwnerID |
| 134 | |
| 135 | // Check for an existing workspace on the chat. |
| 136 | check := options.checkExistingWorkspace(ctx, db, chatID) |
| 137 | if check.BuildErr != nil { |
| 138 | return buildFailureToolResponse( |
| 139 | ctx, |
| 140 | options.Logger, |
| 141 | db, |
| 142 | ownerID, |
| 143 | organizationID, |
| 144 | check.BuildAction, |
| 145 | check.BuildID, |
| 146 | check.BuildErr, |