From dac822b7f44a8bf66a65868c3a6f4aa015d009f4 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 16 Dec 2025 17:14:59 +0200 Subject: [PATCH] refactor: remove deprecated AITaskPromptParameterName constant (#21023) This removes the deprecated AITaskPromptParameterName constant and all backward compatibility code that was added for v2.28. - Remove AITaskPromptParameterName constant from codersdk/aitasks.go - Remove backward compatibility code in coderd/aitasks.go that populated the "AI Prompt" parameter for templates that defined it - Remove the backward compatibility test (OK AIPromptBackCompat) - Update dbfake to no longer set the AI Prompt parameter - Remove AITaskPromptParameterName from frontend TypeScript types - Remove preset prompt read-only feature from TaskPrompt component - Update docs to reflect that pre-2.28 definition is no longer supported Task prompts are now exclusively stored in the tasks.prompt database column, as introduced in the migration that added the tasks table. --- coderd/aitasks.go | 22 ------- coderd/aitasks_test.go | 58 ++----------------- coderd/database/dbfake/dbfake.go | 6 +- codersdk/aitasks.go | 15 ----- codersdk/toolsdk/toolsdk_test.go | 1 - docs/ai-coder/ai-bridge/client-config.md | 14 ++++- docs/ai-coder/tasks-migration.md | 5 +- docs/tutorials/quickstart.md | 16 ++--- provisioner/terraform/provision_test.go | 21 ++----- site/src/api/typesGenerated.ts | 16 ----- .../tasks/TaskPrompt/TaskPrompt.stories.tsx | 8 +-- .../modules/tasks/TaskPrompt/TaskPrompt.tsx | 26 +-------- site/src/testHelpers/entities.ts | 3 +- 13 files changed, 37 insertions(+), 174 deletions(-) diff --git a/coderd/aitasks.go b/coderd/aitasks.go index e919b70d37..a99653945f 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -28,7 +28,6 @@ import ( "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/searchquery" "github.com/coder/coder/v2/coderd/util/ptr" - "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" ) @@ -131,31 +130,10 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) { } } - // Check if the template defines the AI Prompt parameter. - templateParams, err := api.Database.GetTemplateVersionParameters(ctx, req.TemplateVersionID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching template parameters.", - Detail: err.Error(), - }) - return - } - - var richParams []codersdk.WorkspaceBuildParameter - if _, hasAIPromptParam := slice.Find(templateParams, func(param database.TemplateVersionParameter) bool { - return param.Name == codersdk.AITaskPromptParameterName - }); hasAIPromptParam { - // Only add the AI Prompt parameter if the template defines it. - richParams = []codersdk.WorkspaceBuildParameter{ - {Name: codersdk.AITaskPromptParameterName, Value: req.Input}, - } - } - createReq := codersdk.CreateWorkspaceRequest{ Name: taskName, TemplateVersionID: req.TemplateVersionID, TemplateVersionPresetID: req.TemplateVersionPresetID, - RichParameterValues: richParams, } var owner workspaceOwner diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index 0ab974f17d..75c416054f 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -57,7 +57,7 @@ func TestTasks(t *testing.T) { o(&opt) } - // Create a template version that supports AI tasks with the AI Prompt parameter. + // Create a template version that supports AI tasks. taskAppID := uuid.New() version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, @@ -137,7 +137,7 @@ func TestTasks(t *testing.T) { got, ok := slice.Find(tasks, func(t codersdk.Task) bool { return t.ID == task.ID }) require.True(t, ok, "task should be found in the list") - assert.Equal(t, wantPrompt, got.InitialPrompt, "task prompt should match the AI Prompt parameter") + assert.Equal(t, wantPrompt, got.InitialPrompt, "task prompt should match the input") assert.Equal(t, task.WorkspaceID.UUID, got.WorkspaceID.UUID, "workspace id should match") assert.Equal(t, task.WorkspaceName, got.WorkspaceName, "workspace name should match") // Status should be populated via the tasks_with_status view. @@ -196,7 +196,7 @@ func TestTasks(t *testing.T) { assert.Equal(t, task.ID, updated.ID, "task ID should match") assert.Equal(t, task.Name, updated.Name, "task name should match") - assert.Equal(t, wantPrompt, updated.InitialPrompt, "task prompt should match the AI Prompt parameter") + assert.Equal(t, wantPrompt, updated.InitialPrompt, "task prompt should match the input") assert.Equal(t, task.WorkspaceID.UUID, updated.WorkspaceID.UUID, "workspace id should match") assert.Equal(t, task.WorkspaceName, updated.WorkspaceName, "workspace name should match") assert.Equal(t, ws.LatestBuild.BuildNumber, updated.WorkspaceBuildNumber, "workspace build number should match") @@ -971,56 +971,6 @@ func TestTasksCreate(t *testing.T) { require.Len(t, parameters, 0) }) - t.Run("OK AIPromptBackCompat", func(t *testing.T) { - t.Parallel() - - var ( - ctx = testutil.Context(t, testutil.WaitShort) - - taskPrompt = "Some task prompt" - ) - - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - user := coderdtest.CreateFirstUser(t, client) - - // Given: A template with an "AI Prompt" parameter - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionApply: echo.ApplyComplete, - ProvisionGraph: []*proto.Response{ - {Type: &proto.Response_Graph{Graph: &proto.GraphComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, - HasAiTasks: true, - }}}, - }, - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - - // When: We attempt to create a Task. - task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{ - TemplateVersionID: template.ActiveVersionID, - Input: taskPrompt, - }) - require.NoError(t, err) - require.True(t, task.WorkspaceID.Valid) - - ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) - require.NoError(t, err) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) - - // Then: We expect a workspace to have been created. - assert.NotEmpty(t, task.Name) - assert.Equal(t, template.ID, task.TemplateID) - - // And: We expect it to have the "AI Prompt" parameter correctly set. - parameters, err := client.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID) - require.NoError(t, err) - require.Len(t, parameters, 1) - assert.Equal(t, codersdk.AITaskPromptParameterName, parameters[0].Name) - assert.Equal(t, taskPrompt, parameters[0].Value) - }) - t.Run("CustomNames", func(t *testing.T) { t.Parallel() @@ -1147,7 +1097,7 @@ func TestTasksCreate(t *testing.T) { client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) user := coderdtest.CreateFirstUser(t, client) - // Given: A template without an "AI Prompt" parameter + // Given: A template without AI task support (no coder_ai_task resource) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 97558b4b8b..6161299e00 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -24,7 +24,6 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/telemetry" "github.com/coder/coder/v2/coderd/wspubsub" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionersdk" sdkproto "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -130,10 +129,7 @@ func (b WorkspaceBuildBuilder) WithTask(taskSeed database.TaskTable, appSeed *sd b.taskAppID, err = uuid.Parse(takeFirst(appSeed.Id, uuid.NewString())) require.NoError(b.t, err) - return b.Params(database.WorkspaceBuildParameter{ - Name: codersdk.AITaskPromptParameterName, - Value: b.taskSeed.Prompt, - }).WithAgent(func(a []*sdkproto.Agent) []*sdkproto.Agent { + return b.WithAgent(func(a []*sdkproto.Agent) []*sdkproto.Agent { a[0].Apps = []*sdkproto.App{ { Id: b.taskAppID.String(), diff --git a/codersdk/aitasks.go b/codersdk/aitasks.go index e2acbfe489..c4a0cb6141 100644 --- a/codersdk/aitasks.go +++ b/codersdk/aitasks.go @@ -10,23 +10,8 @@ import ( "github.com/google/uuid" "golang.org/x/xerrors" - - "github.com/coder/terraform-provider-coder/v2/provider" ) -// AITaskPromptParameterName is the name of the parameter used to pass prompts -// to AI tasks. -// -// Deprecated: This constant is deprecated and maintained only for backwards -// compatibility with older templates. Task prompts are now stored directly -// in the tasks.prompt database column. New code should access prompts via -// the Task.InitialPrompt field returned from task endpoints. -// -// This constant will be removed in a future major version. Templates should -// not rely on this parameter name, as the backend will continue to create it -// automatically for compatibility but reads from tasks.prompt. -const AITaskPromptParameterName = provider.TaskPromptParameterName - // CreateTaskRequest represents the request to create a new task. type CreateTaskRequest struct { TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"` diff --git a/codersdk/toolsdk/toolsdk_test.go b/codersdk/toolsdk/toolsdk_test.go index 0a8a94c8dc..b075cbd73a 100644 --- a/codersdk/toolsdk/toolsdk_test.go +++ b/codersdk/toolsdk/toolsdk_test.go @@ -1017,7 +1017,6 @@ func TestTools(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionGraph: []*proto.Response{ {Type: &proto.Response_Graph{Graph: &proto.GraphComplete{ - Parameters: []*proto.RichParameter{{Name: "AI Prompt", Type: "string"}}, HasAiTasks: true, }}}, }, diff --git a/docs/ai-coder/ai-bridge/client-config.md b/docs/ai-coder/ai-bridge/client-config.md index 7f63ad2297..05da2d0349 100644 --- a/docs/ai-coder/ai-bridge/client-config.md +++ b/docs/ai-coder/ai-bridge/client-config.md @@ -58,6 +58,8 @@ data "coder_workspace_owner" "me" {} data "coder_workspace" "me" {} +data "coder_task" "me" {} + resource "coder_agent" "dev" { arch = "amd64" os = "linux" @@ -71,15 +73,21 @@ resource "coder_agent" "dev" { # See https://registry.coder.com/modules/coder/claude-code for more information module "claude-code" { - count = local.has_ai_prompt ? data.coder_workspace.me.start_count : 0 + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 source = "dev.registry.coder.com/coder/claude-code/coder" - version = ">= 3.4.0" + version = ">= 4.0.0" agent_id = coder_agent.dev.id workdir = "/home/coder/project" claude_api_key = data.coder_workspace_owner.me.session_token # Use the Coder session token to authenticate with AI Bridge - ai_prompt = data.coder_parameter.ai_prompt.value + ai_prompt = data.coder_task.me.prompt ... # other claude-code configuration } + +# The coder_ai_task resource associates the task to the app. +resource "coder_ai_task" "task" { + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 + app_id = module.claude-code[0].task_app_id +} ``` ## External and Desktop Clients diff --git a/docs/ai-coder/tasks-migration.md b/docs/ai-coder/tasks-migration.md index 6cd02ba2e7..1bc41ff530 100644 --- a/docs/ai-coder/tasks-migration.md +++ b/docs/ai-coder/tasks-migration.md @@ -8,7 +8,8 @@ Prior to Coder version 2.28.0, the definition of a Coder task was different to t Note that 2 and 3 were generally handled by the `coder/agentapi` Terraform module. -The pre-2.28.0 definition will be supported until the release of 2.29.0. You will need to update your Tasks-enabled templates to continue using Tasks after this release. +> [!IMPORTANT] +> The pre-2.28.0 definition is no longer supported as of Coder 2.30.0. You must update your Tasks-enabled templates to use the new format described below. You can view an [example migration here](https://github.com/coder/coder/pull/20420). Alternatively, follow the steps below: @@ -114,7 +115,7 @@ resource "coder_ai_task" "task" { In v2.28 and above, the following changes were made: -- The explicitly named "AI Prompt" parameter is deprecated. The task prompt is now available in the `coder_ai_task` resource (provider version 2.12 and above) and `coder_task` data source (provider version 2.13 and above). +- The explicitly named "AI Prompt" parameter is no longer supported. The task prompt is now available in the `coder_ai_task` resource (provider version 2.12 and above) and `coder_task` data source (provider version 2.13 and above). - Modules no longer define the `coder_ai_task` resource. These must be defined explicitly in the template. - The `sidebar_app` field of the `coder_ai_task` resource is now deprecated. In its place, use `app_id`. diff --git a/docs/tutorials/quickstart.md b/docs/tutorials/quickstart.md index 2b7b2c2e38..a2105fac0f 100644 --- a/docs/tutorials/quickstart.md +++ b/docs/tutorials/quickstart.md @@ -268,16 +268,16 @@ Coder maintains the [Tasks on Docker](https://registry.coder.com/templates/coder coder template push tasks-docker -d . --variable anthropic_api_key="your-api-key" ``` -1. **Create the new Workspace** - 1. In your Coder Deployment, click **Workspaces** in the upper left hand corner - 1. Click **New workspace** and choose **tasks-docker** - 1. Fill in the Workspace name. Add in an AI Prompt for Claude Code like "Make the background yellow". Click **Create workspace** +1. **Create a Task** + 1. In your Coder deployment, click **Tasks** in the navigation + 1. In the "Prompt your AI agent to start a task" box, enter a prompt like "Make the background yellow" + 1. Select the **tasks-docker** template from the dropdown and click the submit button 1. **See Tasks in action** - 1. Once your workspace is running, click **View tasks** with your workspace. This will bring you to the Tasks view where you can see Claude Code (left panel), preview the sample application, and interact with the code in code-server. You might need to wait for Claude Code to finish changing the background color of the application. - 1. Navigate to the **Tasks** tab in the upper left hand corner + 1. Your task will appear in the table below. Click on it to open the task view where you can follow the initialization + 1. Once active, you'll see Claude Code on the left panel and can preview the sample application or interact with the code in code-server on the right. You might need to wait for Claude Code to finish changing the background color of the application. 1. Try typing in a new request to Claude Code: "make the background red" - 1. Let's exit out of this specific Task view, so we can see all the running tasks - 1. You can start a new task by prompting in the "Prompt your AI agent to start a task" box. You can select which template to run this from, so tasks-docker here, and that will spin up a new Workspace + 1. Click the back arrow to return to the task overview (you can also see all your tasks in the sidebar) + 1. You can start a new task from the prompt box at the top of the page ![Tasks changing background color of demo application](../images/screenshots/quickstart-tasks-background-change.png) diff --git a/provisioner/terraform/provision_test.go b/provisioner/terraform/provision_test.go index ee71838d98..ccd8986d0f 100644 --- a/provisioner/terraform/provision_test.go +++ b/provisioner/terraform/provision_test.go @@ -20,8 +20,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/v2/provider" - "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" @@ -941,18 +939,15 @@ func TestProvision(t *testing.T) { { Name: "ai-task-multiple-allowed-in-plan", Files: map[string]string{ - "main.tf": fmt.Sprintf(`terraform { + "main.tf": `terraform { required_providers { coder = { source = "coder/coder" - version = ">= 2.7.0" + version = ">= 2.13.0" } } } - data "coder_parameter" "prompt" { - name = "%s" - type = "string" - } + data "coder_task" "me" {} resource "coder_ai_task" "a" { sidebar_app { id = "7128be08-8722-44cb-bbe1-b5a391c4d94b" # fake ID, irrelevant here anyway but needed for validation @@ -963,7 +958,7 @@ func TestProvision(t *testing.T) { id = "7128be08-8722-44cb-bbe1-b5a391c4d94b" # fake ID, irrelevant here anyway but needed for validation } } - `, provider.TaskPromptParameterName), + `, }, Request: &proto.PlanRequest{}, Response: &proto.GraphComplete{ @@ -977,14 +972,6 @@ func TestProvision(t *testing.T) { Type: "coder_ai_task", }, }, - Parameters: []*proto.RichParameter{ - { - Name: provider.TaskPromptParameterName, - Type: "string", - Required: true, - FormType: proto.ParameterFormType_INPUT, - }, - }, AiTasks: []*proto.AITask{ { Id: "a", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 47da5bb36d..93dee1e18f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -108,22 +108,6 @@ export interface AIConfig { readonly bridge?: AIBridgeConfig; } -// From codersdk/aitasks.go -/** - * AITaskPromptParameterName is the name of the parameter used to pass prompts - * to AI tasks. - * - * Deprecated: This constant is deprecated and maintained only for backwards - * compatibility with older templates. Task prompts are now stored directly - * in the tasks.prompt database column. New code should access prompts via - * the Task.InitialPrompt field returned from task endpoints. - * - * This constant will be removed in a future major version. Templates should - * not rely on this parameter name, as the backend will continue to create it - * automatically for compatibility but reads from tasks.prompt. - */ -export const AITaskPromptParameterName = "AI Prompt"; - // From codersdk/allowlist.go /** * APIAllowListTarget represents a single allow-list entry using the canonical diff --git a/site/src/modules/tasks/TaskPrompt/TaskPrompt.stories.tsx b/site/src/modules/tasks/TaskPrompt/TaskPrompt.stories.tsx index bd4a765b9c..d7f9085748 100644 --- a/site/src/modules/tasks/TaskPrompt/TaskPrompt.stories.tsx +++ b/site/src/modules/tasks/TaskPrompt/TaskPrompt.stories.tsx @@ -1,7 +1,7 @@ import { - MockAIPromptPresets, MockPresets, MockTask, + MockTaskPresets, MockTasks, MockTemplate, MockTemplateVersion, @@ -72,11 +72,9 @@ export const WithPresets: Story = { }, }; -export const ReadOnlyPresetPrompt: Story = { +export const WithAIPresets: Story = { beforeEach: () => { - spyOn(API, "getTemplateVersionPresets").mockResolvedValue( - MockAIPromptPresets, - ); + spyOn(API, "getTemplateVersionPresets").mockResolvedValue(MockTaskPresets); }, }; diff --git a/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx b/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx index 97eda88955..7580963eef 100644 --- a/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx +++ b/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx @@ -7,7 +7,6 @@ import type { Template, TemplateVersionExternalAuth, } from "api/typesGenerated"; -import { AITaskPromptParameterName } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; @@ -162,19 +161,6 @@ const CreateTaskForm: FC = ({ templates, onSuccess }) => { const defaultPreset = presets?.find((p) => p.Default); setSelectedPresetId(defaultPreset?.ID ?? presets?.[0]?.ID); }, [presets]); - const selectedPreset = presets?.find((p) => p.ID === selectedPresetId); - - // Read-only prompt if defined in preset - const presetPrompt = selectedPreset?.Parameters?.find( - (param) => param.Name === AITaskPromptParameterName, - )?.Value; - const isPromptReadOnly = !!presetPrompt; - useEffect(() => { - if (presetPrompt) { - setPrompt(presetPrompt); - } - }, [presetPrompt]); - // External Auth const { externalAuth, @@ -250,21 +236,13 @@ const CreateTaskForm: FC = ({ templates, onSuccess }) => { className="border border-border border-solid rounded-3xl p-3 bg-surface-secondary" disabled={createTaskMutation.isPending} > -