feat(codersdk/toolsdk): expose template version presets to agents#24695
feat(codersdk/toolsdk): expose template version presets to agents#24695maschwenk wants to merge 1 commit into
Conversation
Adds a coder_template_version_presets tool so agents can discover presets, and a template_version_preset_id argument on the create_workspace tool so they can use one. The backend and the create_task tool already honored the preset ID; this closes the agent-facing gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/coder-agents-review |
There was a problem hiding this comment.
Clean, focused plumbing PR. The new ListTemplateVersionPresets tool follows the exact pattern of its sibling, the CreateWorkspace wiring is correct, and the CreateTask/WithPreset test fix catches a real pre-existing bug. Two items need attention before merge.
1 P1, 1 P2, 2 P3, 2 Nits. Details inline.
The P1 is a test that will never pass: CreateWorkspace/WithPreset calls AwaitWorkspaceBuildJobCompleted in a test environment without a provisioner daemon. Eight reviewers independently confirmed it by running the test. The preset ID is set at build creation time, so the fix is simple: assert on res.LatestBuild.TemplateVersionPresetID directly.
The P2 is pre-existing but newly consequential: codersdk.Preset has no json:"..." struct tags, so the tool returns PascalCase JSON (ID, Name, DesiredPrebuildInstances) while every other tool in the SDK returns snake_case. An LLM composing data from ListTemplateVersionParameters and ListTemplateVersionPresets sees two different naming conventions.
"The assertion code exists at lines 545-547, but it never executes because the test fails before reaching it. The gap between stated coverage and actual coverage is the process failure." (Mafu-san)
🤖 This review was automatically generated with Coder Agents.
| require.NoError(t, err) | ||
| require.NotEmpty(t, res.ID, "expected a workspace ID") | ||
|
|
||
| build := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, res.LatestBuild.ID) |
There was a problem hiding this comment.
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)
🤖
| }, | ||
| } | ||
|
|
||
| var ListTemplateVersionPresets = Tool[ListTemplateVersionParametersArgs, []codersdk.Preset]{ |
There was a problem hiding this comment.
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)
🤖
| @@ -1072,11 +1108,10 @@ func TestTools(t *testing.T) { | |||
| { | |||
There was a problem hiding this comment.
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)
🤖
| 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") |
There was a problem hiding this comment.
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)
🤖
| }, | ||
| "template_version_preset_id": map[string]any{ | ||
| "type": "string", | ||
| "description": "Optional ID of the template version preset to create the workspace from.", |
There was a problem hiding this comment.
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)
🤖
| }, | ||
| } | ||
|
|
||
| var ListTemplateVersionPresets = Tool[ListTemplateVersionParametersArgs, []codersdk.Preset]{ |
There was a problem hiding this comment.
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)
🤖
|
|
|
@johnstcn ❤️ |
|
@maschwenk I've cherry-picked your commit into #24694 and will continue iterating there. Will add you for review when it's ready. |
The chat tools (`read_template`, `create_workspace`) did not surface or respect template version presets. Presets were invisible to the LLM and preset parameter defaults were never applied at workspace creation. The `toolsdk` MCP surface had the same gap (ref #24695, now subsumed here). ## What this changes - **`read_template`** returns presets with `id`, `name`, `default`, `description`, `icon`, `parameters`, and `desired_prebuild_instances` (when set), so the LLM can pick the right preset and prefer prebuilt-backed ones. - **`create_workspace`** accepts a `preset_id`. The wsbuilder applies preset parameter defaults and may claim a prebuilt workspace. - **`start_workspace`** does *not* accept a preset. Presets are a creation-time choice; subsequent starts use the workspace's existing version and parameters. Users who need a specific preset or version on an existing chat can create the workspace out-of-band (CLI / UI / API) with the desired configuration and attach the chat to it. - **`toolsdk`** gains `GetTemplate` (with presets including `desired_prebuild_instances`), preset support on `CreateWorkspace`, and preset + `rich_parameters` support on `CreateWorkspaceBuild`. The `template_version_preset_id` description warns about preset/version affinity. > 🤖 Generated with [Coder Agents](https://coder.com/agents) and reviewed by a human. Co-authored-by: Max schwenk <maschwenk@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The chat tools (`read_template`, `create_workspace`) did not surface or respect template version presets. Presets were invisible to the LLM and preset parameter defaults were never applied at workspace creation. The `toolsdk` MCP surface had the same gap (ref #24695, now subsumed here). ## What this changes - **`read_template`** returns presets with `id`, `name`, `default`, `description`, `icon`, `parameters`, and `desired_prebuild_instances` (when set), so the LLM can pick the right preset and prefer prebuilt-backed ones. - **`create_workspace`** accepts a `preset_id`. The wsbuilder applies preset parameter defaults and may claim a prebuilt workspace. - **`start_workspace`** does *not* accept a preset. Presets are a creation-time choice; subsequent starts use the workspace's existing version and parameters. Users who need a specific preset or version on an existing chat can create the workspace out-of-band (CLI / UI / API) with the desired configuration and attach the chat to it. - **`toolsdk`** gains `GetTemplate` (with presets including `desired_prebuild_instances`), preset support on `CreateWorkspace`, and preset + `rich_parameters` support on `CreateWorkspaceBuild`. The `template_version_preset_id` description warns about preset/version affinity. > 🤖 Generated with [Coder Agents](https://coder.com/agents) and reviewed by a human. (cherry picked from commit 04cc983) <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. --> Co-authored-by: Max schwenk <maschwenk@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds agent-facing plumbing so the Coder Agent can actually use template
version presets end-to-end. The backend and the
create_tasktoolalready honored
template_version_preset_id; this closes the agentsurface so the agent can discover a preset and pass it into workspace
creation on its own.
Two changes in
codersdk/toolsdk:coder_template_version_presetsread-only tool that wrapscodersdk.Client.TemplateVersionPresetsso agents can list presetsfor a template version.
create_workspacenow accepts an optionaltemplate_version_preset_idand forwards it to
codersdk.CreateWorkspaceRequest.Tests cover the new list tool, split
CreateWorkspaceinto with/withoutpreset subtests (asserting the preset propagates to the resulting
build), and fix the preexisting
CreateTask/WithPresetcase to use theAI-task template version so the preset actually resolves.