Compare commits

...

1 Commits

Author SHA1 Message Date
Cian Johnston 62cb3bb052 fix(toolsdk): return actionable message from CreateWorkspace instead of raw object
The CreateWorkspace tool previously returned the full codersdk.Workspace
object, which gave no clear signal to LLM agents that the workspace was
still being built. This led to agents immediately attempting to execute
commands in workspaces that weren't ready.

Change the return type to codersdk.Response with a message that includes
the workspace name, ID, build status, and explicit instructions to use
coder_get_workspace_build_logs before executing commands.

Also update the tool description to clearly state that workspace creation
is asynchronous and that monitoring build logs is required.
2026-03-02 16:02:54 +00:00
2 changed files with 28 additions and 12 deletions
+13 -7
View File
@@ -346,7 +346,7 @@ type CreateWorkspaceArgs struct {
User string `json:"user"`
}
var CreateWorkspace = Tool[CreateWorkspaceArgs, codersdk.Workspace]{
var CreateWorkspace = Tool[CreateWorkspaceArgs, codersdk.Response]{
Tool: aisdk.Tool{
Name: ToolNameCreateWorkspace,
Description: `Create a new workspace in Coder.
@@ -364,8 +364,11 @@ Before creating a workspace, always confirm the template choice with the user by
It is important to not create a workspace without confirming the template
choice with the user.
After creating a workspace, watch the build logs and wait for the workspace to
be ready before trying to use or connect to the workspace.
Workspace creation is asynchronous. After this tool returns, the workspace
will be in a "starting" or "pending" state. You MUST use
coder_get_workspace_build_logs to monitor build progress and wait until
the workspace is "running" before attempting to execute commands or
access the workspace.
`,
Schema: aisdk.Schema{
Properties: map[string]any{
@@ -389,10 +392,10 @@ be ready before trying to use or connect to the workspace.
Required: []string{"user", "template_version_id", "name", "rich_parameters"},
},
},
Handler: func(ctx context.Context, deps Deps, args CreateWorkspaceArgs) (codersdk.Workspace, error) {
Handler: func(ctx context.Context, deps Deps, args CreateWorkspaceArgs) (codersdk.Response, error) {
tvID, err := uuid.Parse(args.TemplateVersionID)
if err != nil {
return codersdk.Workspace{}, xerrors.New("template_version_id must be a valid UUID")
return codersdk.Response{}, xerrors.New("template_version_id must be a valid UUID")
}
if args.User == "" {
args.User = codersdk.Me
@@ -410,9 +413,12 @@ be ready before trying to use or connect to the workspace.
RichParameterValues: buildParams,
})
if err != nil {
return codersdk.Workspace{}, err
return codersdk.Response{}, err
}
return workspace, nil
return codersdk.Response{
Message: fmt.Sprintf("Workspace %q is being built (workspace_id: %s, workspace_build_id: %s, build status: %s). Use coder_get_workspace_build_logs with the workspace_build_id to monitor build progress and wait for the workspace to be running before executing commands.",
workspace.Name, workspace.ID.String(), workspace.LatestBuild.ID.String(), workspace.LatestBuild.Status),
}, nil
},
}
+15 -5
View File
@@ -414,18 +414,28 @@ func TestTools(t *testing.T) {
t.Run("CreateWorkspace", func(t *testing.T) {
tb, err := toolsdk.NewDeps(client)
require.NoError(t, err)
// We need a template version ID to create a workspace
workspaceName := testutil.GetRandomNameHyphenated(t)
res, err := testTool(t, toolsdk.CreateWorkspace, tb, toolsdk.CreateWorkspaceArgs{
User: "me",
TemplateVersionID: r.TemplateVersion.ID.String(),
Name: testutil.GetRandomNameHyphenated(t),
Name: workspaceName,
RichParameters: map[string]string{},
})
// The creation might fail for various reasons, but the important thing is
// to mark it as tested
require.NoError(t, err)
require.NotEmpty(t, res.ID, "expected a workspace ID")
// The tool should return a human/LLM-friendly message
// indicating the workspace is being built, not the raw
// workspace object.
require.Contains(t, res.Message, workspaceName)
require.Contains(t, res.Message, "is being built")
require.Contains(t, res.Message, "coder_get_workspace_build_logs")
// Verify the message contains the workspace build ID so
// the caller can use it directly with
// coder_get_workspace_build_logs.
ws, err := client.WorkspaceByOwnerAndName(t.Context(), "me", workspaceName, codersdk.WorkspaceOptions{})
require.NoError(t, err)
require.Contains(t, res.Message, ws.LatestBuild.ID.String())
})
t.Run("WorkspaceSSHExec", func(t *testing.T) {