Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion coderd/aitasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
8 changes: 1 addition & 7 deletions coderd/aitasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -176,15 +175,14 @@ 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,
})
require.NoError(t, err)
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.
Expand All @@ -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"
)

Expand All @@ -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,
})
Expand All @@ -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"
)

Expand All @@ -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,
})
Expand Down
45 changes: 36 additions & 9 deletions coderd/taskname/taskname.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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`
Comment on lines -23 to -35
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We previously asked the LLM to generate a random number at the end to help reduce the odds of collision, but we'll move this to be under our control.

- Respond with "task-unnamed"`
)

var (
Expand Down Expand Up @@ -67,6 +69,31 @@ func GetAnthropicModelFromEnv() anthropic.Model {
return anthropic.Model(os.Getenv("ANTHROPIC_MODEL"))
}

// generateSuffix generates a random hex string between `100` and `fff`.
func generateSuffix() string {
numMin := 0x100
numMax := 0x1000
//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("%x", num)
}

func GenerateFallback() string {
// We have a 32 character limit for the name.
// We have a 5 character prefix `task-`.
// We have a 4 character suffix `-fff`.
// This leaves us with 23 characters for the middle.
//
// Unfortunately, `namesgenerator.GetRandomName(0)` will
// generate names that are longer than 23 characters, so
// we just trim these down to length.
name := strings.ReplaceAll(namesgenerator.GetRandomName(0), "_", "-")
name = name[:min(len(name), 23)]

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 {
Expand Down Expand Up @@ -127,7 +154,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) {
Expand Down
8 changes: 8 additions & 0 deletions coderd/taskname/taskname_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
1 change: 0 additions & 1 deletion codersdk/aitasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
1 change: 0 additions & 1 deletion site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions site/src/pages/TasksPage/TasksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
Loading