Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions codersdk/toolsdk/toolsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
ToolNameListWorkspaces = "coder_list_workspaces"
ToolNameListTemplates = "coder_list_templates"
ToolNameListTemplateVersionParams = "coder_template_version_parameters"
ToolNameListTemplateVersionPresets = "coder_template_version_presets"
ToolNameGetAuthenticatedUser = "coder_get_authenticated_user"
ToolNameCreateWorkspaceBuild = "coder_create_workspace_build"
ToolNameCreateTemplateVersion = "coder_create_template_version"
Expand Down Expand Up @@ -310,6 +311,7 @@ var All = []GenericTool{
DeleteTemplate.Generic(),
ListTemplates.Generic(),
ListTemplateVersionParameters.Generic(),
ListTemplateVersionPresets.Generic(),
ListWorkspaces.Generic(),
GetAuthenticatedUser.Generic(),
GetTemplateVersionLogs.Generic(),
Expand Down Expand Up @@ -441,10 +443,11 @@ This returns more data than list_workspaces to reduce token usage.`,
}

type CreateWorkspaceArgs struct {
Name string `json:"name"`
RichParameters map[string]string `json:"rich_parameters"`
TemplateVersionID string `json:"template_version_id"`
User string `json:"user"`
Name string `json:"name"`
RichParameters map[string]string `json:"rich_parameters"`
TemplateVersionID string `json:"template_version_id"`
TemplateVersionPresetID string `json:"template_version_preset_id"`
User string `json:"user"`
}

var CreateWorkspace = Tool[CreateWorkspaceArgs, codersdk.Workspace]{
Expand Down Expand Up @@ -478,6 +481,10 @@ be ready before trying to use or connect to the workspace.
"type": "string",
"description": "ID of the template version to create the workspace from.",
},
"template_version_preset_id": map[string]any{
"type": "string",
"description": "Optional ID of the template version preset to create the workspace from.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [DEREM-5] "to create the workspace from" misrepresents what a preset does.

The template_version_id description correctly says "to create the workspace from" because the workspace IS created from a template version. The preset description copies the same phrasing, but a preset pre-fills parameter values; it is not the thing you create from. An LLM reading both descriptions side by side could confuse presets with template versions.

Suggestion: "Optional preset to apply when creating the workspace."

(Gon, Leorio)

🤖

},
"name": map[string]any{
"type": "string",
"description": "Name of the workspace to create.",
Expand All @@ -496,6 +503,14 @@ be ready before trying to use or connect to the workspace.
if err != nil {
return codersdk.Workspace{}, xerrors.New("template_version_id must be a valid UUID")
}

var tvPresetID uuid.UUID
if args.TemplateVersionPresetID != "" {
tvPresetID, err = uuid.Parse(args.TemplateVersionPresetID)
if err != nil {
return codersdk.Workspace{}, xerrors.New("template_version_preset_id must be a valid UUID")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [DEREM-4] Error discards the UUID parse failure. Every other UUID parse error in this file wraps with %w.

xerrors.New("template_version_preset_id must be a valid UUID") drops the err from uuid.Parse. The ListTemplateVersionPresets handler ten lines below (line 665) does the right thing: xerrors.Errorf("template_version_id must be a valid UUID: %w", err). When an agent passes "abc-xyz", the caller sees "must be a valid UUID" with no indication of what was wrong.

The pre-existing template_version_id error at line 504 has the same issue (it is the pattern this line copied from), but it is not part of this diff.

Fix: xerrors.Errorf("template_version_preset_id must be a valid UUID: %w", err)

(Leorio)

🤖

}
}
if args.User == "" {
args.User = codersdk.Me
}
Expand All @@ -506,11 +521,15 @@ be ready before trying to use or connect to the workspace.
Value: v,
})
}
workspace, err := deps.coderClient.CreateUserWorkspace(ctx, args.User, codersdk.CreateWorkspaceRequest{
req := codersdk.CreateWorkspaceRequest{
TemplateVersionID: tvID,
Name: args.Name,
RichParameterValues: buildParams,
})
}
if tvPresetID != uuid.Nil {
req.TemplateVersionPresetID = tvPresetID
}
workspace, err := deps.coderClient.CreateUserWorkspace(ctx, args.User, req)
if err != nil {
return codersdk.Workspace{}, err
}
Expand Down Expand Up @@ -626,6 +645,33 @@ var ListTemplateVersionParameters = Tool[ListTemplateVersionParametersArgs, []co
},
}

var ListTemplateVersionPresets = Tool[ListTemplateVersionParametersArgs, []codersdk.Preset]{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [DEREM-2] codersdk.Preset has no json:"..." struct tags, so this tool returns PascalCase JSON field names.

codersdk.Preset (presets.go:13-21) has no json tags on any field. When Generic() serializes the return value, Go's encoder produces {"ID":"...","Name":"...","DesiredPrebuildInstances":null,...}. The sibling tool ListTemplateVersionParameters returns codersdk.TemplateVersionParameter, which has proper json:"name", json:"description", etc. The OpenAPI spec also says "id" and "name" (lowercase), so the actual API output disagrees with the spec.

This is pre-existing in codersdk/presets.go, but this tool is the first agent-facing consumer where an LLM composes field names from the response. Adding json tags to Preset and PresetParameter would fix it across the board. Could be done in this PR or as a follow-up, but the tool is not usable as intended until the field names are predictable.

(Melody)

🤖

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [DEREM-6] ListTemplateVersionPresets reuses ListTemplateVersionParametersArgs as its type parameter. The type name says "ParametersArgs" but this is a presets tool.

Both tools take only TemplateVersionID, so the reuse works. A shared TemplateVersionIDArgs or a dedicated ListTemplateVersionPresetsArgs (identical struct, distinct name) would be more accurate.

(Netero, Bisky)

🤖

Tool: aisdk.Tool{
Name: ToolNameListTemplateVersionPresets,
Description: "Get the presets for a template version. Use these to choose a template_version_preset_id for task or workspace creation.",
Schema: aisdk.Schema{
Properties: map[string]any{
"template_version_id": map[string]any{
"type": "string",
},
},
Required: []string{"template_version_id"},
},
},
MCPAnnotations: mcpReadOnlyAnnotations,
Handler: func(ctx context.Context, deps Deps, args ListTemplateVersionParametersArgs) ([]codersdk.Preset, error) {
templateVersionID, err := uuid.Parse(args.TemplateVersionID)
if err != nil {
return nil, xerrors.Errorf("template_version_id must be a valid UUID: %w", err)
}
presets, err := deps.coderClient.TemplateVersionPresets(ctx, templateVersionID)
if err != nil {
return nil, err
}
return presets, nil
},
}

var GetAuthenticatedUser = Tool[NoArgs, codersdk.User]{
Tool: aisdk.Tool{
Name: ToolNameGetAuthenticatedUser,
Expand Down
59 changes: 47 additions & 12 deletions codersdk/toolsdk/toolsdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ func TestTools(t *testing.T) {
}
return agents
}).Do()
preset := dbgen.Preset(t, store, database.InsertPresetParams{
TemplateVersionID: r.TemplateVersion.ID,
Name: testutil.GetRandomNameHyphenated(t),
CreatedAt: r.TemplateVersion.CreatedAt,
Description: "Preset for agent tool tests.",
})

// Given: a client configured with the agent token.
agentClient := agentsdk.New(client.URL, agentsdk.WithFixedToken(r.AgentToken))
Expand Down Expand Up @@ -384,6 +390,20 @@ func TestTools(t *testing.T) {
require.Empty(t, params)
})

t.Run("ListTemplateVersionPresets", func(t *testing.T) {
tb, err := toolsdk.NewDeps(memberClient)
require.NoError(t, err)
presets, err := testTool(t, toolsdk.ListTemplateVersionPresets, tb, toolsdk.ListTemplateVersionParametersArgs{
TemplateVersionID: r.TemplateVersion.ID.String(),
})

require.NoError(t, err)
require.Len(t, presets, 1)
require.Equal(t, preset.ID, presets[0].ID)
require.Equal(t, preset.Name, presets[0].Name)
require.Equal(t, preset.Description, presets[0].Description)
})

t.Run("GetWorkspaceAgentLogs", func(t *testing.T) {
tb, err := toolsdk.NewDeps(memberClient)
require.NoError(t, err)
Expand Down Expand Up @@ -500,18 +520,34 @@ 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
res, err := testTool(t, toolsdk.CreateWorkspace, tb, toolsdk.CreateWorkspaceArgs{
User: "me",
TemplateVersionID: r.TemplateVersion.ID.String(),
Name: testutil.GetRandomNameHyphenated(t),
RichParameters: map[string]string{},
t.Run("WithoutPreset", func(t *testing.T) {
res, err := testTool(t, toolsdk.CreateWorkspace, tb, toolsdk.CreateWorkspaceArgs{
User: "me",
TemplateVersionID: r.TemplateVersion.ID.String(),
Name: testutil.GetRandomNameHyphenated(t),
RichParameters: map[string]string{},
})

require.NoError(t, err)
require.NotEmpty(t, res.ID, "expected a workspace ID")
})

// 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")
t.Run("WithPreset", func(t *testing.T) {
res, err := testTool(t, toolsdk.CreateWorkspace, tb, toolsdk.CreateWorkspaceArgs{
User: "me",
TemplateVersionID: r.TemplateVersion.ID.String(),
TemplateVersionPresetID: preset.ID.String(),
Name: testutil.GetRandomNameHyphenated(t),
RichParameters: map[string]string{},
})

require.NoError(t, err)
require.NotEmpty(t, res.ID, "expected a workspace ID")

build := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, res.LatestBuild.ID)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 [DEREM-1] CreateWorkspace/WithPreset will never pass. AwaitWorkspaceBuildJobCompleted polls a build that no provisioner will ever pick up.

The test server is created with coderdtest.NewWithDatabase(t, nil) (line 156), which starts no provisioner daemon. The existing CreateWorkspaceBuild subtests know this and cancel their builds with an explicit comment: "Important: cancel the build. We don't run any provisioners, so this will remain in the 'pending' state indefinitely." This subtest ignores that constraint.

The TemplateVersionPresetID is set on the build record at insertion time, before any provisioner runs (coderd/wsbuilder/wsbuilder.go:520-523). The CreateUserWorkspace response already contains it. Replace the await with:

require.NotNil(t, res.LatestBuild.TemplateVersionPresetID)
require.Equal(t, preset.ID, *res.LatestBuild.TemplateVersionPresetID)

(Bisky P0, Hisoka P0, Ryosuke P0, Mafu-san P1, Mafuuu P1, Pariston P1, Chopper P1, Kite P1)

🤖

require.NotNil(t, build.TemplateVersionPresetID)
require.Equal(t, preset.ID, *build.TemplateVersionPresetID)
})
})

t.Run("WorkspaceSSHExec", func(t *testing.T) {
Expand Down Expand Up @@ -1072,11 +1108,10 @@ func TestTools(t *testing.T) {
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [DEREM-3] CreateTask/WithPreset now exercises the success path but discards the result. The preset propagation is never verified.

The fix to use aiTV.TemplateVersion.ID is correct, and this test now actually reaches the preset code path. But the returned task is discarded (_, err = testTool(...)) and only require.NoError is checked. If CreateTask.Handler silently dropped the preset field, this test would still pass.

The table-driven structure here only checks error vs no-error, so adding a preset assertion would require minor restructuring. Not a correctness bug, but the test name promises preset coverage that the assertion does not deliver.

(Bisky P2, Hisoka P3, Mafuuu P3)

🤖

name: "WithPreset",
args: toolsdk.CreateTaskArgs{
TemplateVersionID: r.TemplateVersion.ID.String(),
TemplateVersionID: aiTV.TemplateVersion.ID.String(),
TemplateVersionPresetID: presetID.String(),
Input: "not enough barrel rolls",
},
error: "Template does not have a valid \"coder_ai_task\" resource.",
},
}

Expand Down
Loading