refactor: generate task name fallback on coderd (#19447)

Instead of generating the fallback task name on the website, we instead
generate it on coderd.
This commit is contained in:
Danielle Maywood
2025-08-21 11:06:30 +01:00
committed by GitHub
parent 62fa731b34
commit 2521e732be
7 changed files with 47 additions and 21 deletions
+1 -1
View File
@@ -107,7 +107,7 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
return
}
taskName := req.Name
taskName := taskname.GenerateFallback()
if anthropicAPIKey := taskname.GetAnthropicAPIKeyFromEnv(); anthropicAPIKey != "" {
anthropicModel := taskname.GetAnthropicModelFromEnv()
+1 -7
View File
@@ -151,7 +151,6 @@ func TestTaskCreate(t *testing.T) {
var (
ctx = testutil.Context(t, testutil.WaitShort)
taskName = "task-foo-bar-baz"
taskPrompt = "Some task prompt"
)
@@ -176,7 +175,6 @@ func TestTaskCreate(t *testing.T) {
// When: We attempt to create a Task.
workspace, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
Name: taskName,
TemplateVersionID: template.ActiveVersionID,
Prompt: taskPrompt,
})
@@ -184,7 +182,7 @@ func TestTaskCreate(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
// Then: We expect a workspace to have been created.
assert.Equal(t, taskName, workspace.Name)
assert.NotEmpty(t, workspace.Name)
assert.Equal(t, template.ID, workspace.TemplateID)
// And: We expect it to have the "AI Prompt" parameter correctly set.
@@ -201,7 +199,6 @@ func TestTaskCreate(t *testing.T) {
var (
ctx = testutil.Context(t, testutil.WaitShort)
taskName = "task-foo-bar-baz"
taskPrompt = "Some task prompt"
)
@@ -217,7 +214,6 @@ func TestTaskCreate(t *testing.T) {
// When: We attempt to create a Task.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
Name: taskName,
TemplateVersionID: template.ActiveVersionID,
Prompt: taskPrompt,
})
@@ -235,7 +231,6 @@ func TestTaskCreate(t *testing.T) {
var (
ctx = testutil.Context(t, testutil.WaitShort)
taskName = "task-foo-bar-baz"
taskPrompt = "Some task prompt"
)
@@ -251,7 +246,6 @@ func TestTaskCreate(t *testing.T) {
// When: We attempt to create a Task with an invalid template version ID.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
Name: taskName,
TemplateVersionID: uuid.New(),
Prompt: taskPrompt,
})
+37 -9
View File
@@ -2,11 +2,15 @@ package taskname
import (
"context"
"fmt"
"io"
"math/rand/v2"
"os"
"strings"
"github.com/anthropics/anthropic-sdk-go"
anthropicoption "github.com/anthropics/anthropic-sdk-go/option"
"github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/xerrors"
"github.com/coder/aisdk-go"
@@ -20,19 +24,17 @@ const (
Requirements:
- Only lowercase letters, numbers, and hyphens
- Start with "task-"
- End with a random number between 0-99
- Maximum 32 characters total
- Maximum 28 characters total
- Descriptive of the main task
Examples:
- "Help me debug a Python script" → "task-python-debug-12"
- "Create a React dashboard component" → "task-react-dashboard-93"
- "Analyze sales data from Q3" → "task-analyze-q3-sales-37"
- "Set up CI/CD pipeline" → "task-setup-cicd-44"
- "Help me debug a Python script" → "task-python-debug"
- "Create a React dashboard component" → "task-react-dashboard"
- "Analyze sales data from Q3" → "task-analyze-q3-sales"
- "Set up CI/CD pipeline" → "task-setup-cicd"
If you cannot create a suitable name:
- Respond with "task-unnamed"
- Do not end with a random number`
- Respond with "task-unnamed"`
)
var (
@@ -67,6 +69,32 @@ func GetAnthropicModelFromEnv() anthropic.Model {
return anthropic.Model(os.Getenv("ANTHROPIC_MODEL"))
}
// generateSuffix generates a random hex string between `0000` and `ffff`.
func generateSuffix() string {
numMin := 0x00000
numMax := 0x10000
//nolint:gosec // We don't need a cryptographically secure random number generator for generating a task name suffix.
num := rand.IntN(numMax-numMin) + numMin
return fmt.Sprintf("%04x", num)
}
func GenerateFallback() string {
// We have a 32 character limit for the name.
// We have a 5 character prefix `task-`.
// We have a 5 character suffix `-ffff`.
// This leaves us with 22 characters for the middle.
//
// Unfortunately, `namesgenerator.GetRandomName(0)` will
// generate names that are longer than 22 characters, so
// we just trim these down to length.
name := strings.ReplaceAll(namesgenerator.GetRandomName(0), "_", "-")
name = name[:min(len(name), 22)]
name = strings.TrimSuffix(name, "-")
return fmt.Sprintf("task-%s-%s", name, generateSuffix())
}
func Generate(ctx context.Context, prompt string, opts ...Option) (string, error) {
o := options{}
for _, opt := range opts {
@@ -127,7 +155,7 @@ func Generate(ctx context.Context, prompt string, opts ...Option) (string, error
return "", ErrNoNameGenerated
}
return generatedName, nil
return fmt.Sprintf("%s-%s", generatedName, generateSuffix()), nil
}
func anthropicDataStream(ctx context.Context, client anthropic.Client, model anthropic.Model, input []aisdk.Message) (aisdk.DataStream, error) {
+8
View File
@@ -15,6 +15,14 @@ const (
anthropicEnvVar = "ANTHROPIC_API_KEY"
)
func TestGenerateFallback(t *testing.T) {
t.Parallel()
name := taskname.GenerateFallback()
err := codersdk.NameValid(name)
require.NoErrorf(t, err, "expected fallback to be valid workspace name, instead found %s", name)
}
func TestGenerateTaskName(t *testing.T) {
t.Parallel()
-1
View File
@@ -47,7 +47,6 @@ func (c *ExperimentalClient) AITaskPrompts(ctx context.Context, buildIDs []uuid.
}
type CreateTaskRequest struct {
Name string `json:"name"`
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
Prompt string `json:"prompt"`
-1
View File
@@ -478,7 +478,6 @@ export interface CreateProvisionerKeyResponse {
// From codersdk/aitasks.go
export interface CreateTaskRequest {
readonly name: string;
readonly template_version_id: string;
readonly template_version_preset_id?: string;
readonly prompt: string;
-2
View File
@@ -53,7 +53,6 @@ import { useAuthenticated } from "hooks";
import { useExternalAuth } from "hooks/useExternalAuth";
import { RedoIcon, RotateCcwIcon, SendIcon } from "lucide-react";
import { AI_PROMPT_PARAMETER_NAME, type Task } from "modules/tasks/tasks";
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus";
import { type FC, type ReactNode, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
@@ -741,7 +740,6 @@ export const data = {
}
const workspace = await API.experimental.createTask(userId, {
name: `task-${generateWorkspaceName()}`,
template_version_id: templateVersionId,
template_version_preset_id: preset_id || undefined,
prompt,