toolsdk

package
v2.28.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2025 License: AGPL-3.0 Imports: 21 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ToolNameReportTask                  = "coder_report_task"
	ToolNameGetWorkspace                = "coder_get_workspace"
	ToolNameCreateWorkspace             = "coder_create_workspace"
	ToolNameListWorkspaces              = "coder_list_workspaces"
	ToolNameListTemplates               = "coder_list_templates"
	ToolNameListTemplateVersionParams   = "coder_template_version_parameters"
	ToolNameGetAuthenticatedUser        = "coder_get_authenticated_user"
	ToolNameCreateWorkspaceBuild        = "coder_create_workspace_build"
	ToolNameCreateTemplateVersion       = "coder_create_template_version"
	ToolNameGetWorkspaceAgentLogs       = "coder_get_workspace_agent_logs"
	ToolNameGetWorkspaceBuildLogs       = "coder_get_workspace_build_logs"
	ToolNameGetTemplateVersionLogs      = "coder_get_template_version_logs"
	ToolNameUpdateTemplateActiveVersion = "coder_update_template_active_version"
	ToolNameUploadTarFile               = "coder_upload_tar_file"
	ToolNameCreateTemplate              = "coder_create_template"
	ToolNameDeleteTemplate              = "coder_delete_template"
	ToolNameWorkspaceBash               = "coder_workspace_bash"
	ToolNameChatGPTSearch               = "search"
	ToolNameChatGPTFetch                = "fetch"
	ToolNameWorkspaceLS                 = "coder_workspace_ls"
	ToolNameWorkspaceReadFile           = "coder_workspace_read_file"
	ToolNameWorkspaceWriteFile          = "coder_workspace_write_file"
	ToolNameWorkspaceEditFile           = "coder_workspace_edit_file"
	ToolNameWorkspaceEditFiles          = "coder_workspace_edit_files"
	ToolNameWorkspacePortForward        = "coder_workspace_port_forward"
	ToolNameWorkspaceListApps           = "coder_workspace_list_apps"
	ToolNameCreateTask                  = "coder_create_task"
	ToolNameDeleteTask                  = "coder_delete_task"
	ToolNameListTasks                   = "coder_list_tasks"
	ToolNameGetTaskStatus               = "coder_get_task_status"
	ToolNameSendTaskInput               = "coder_send_task_input"
	ToolNameGetTaskLogs                 = "coder_get_task_logs"
)

Tool name constants to avoid hardcoded strings

Variables

View Source
var All = []GenericTool{
	CreateTemplate.Generic(),
	CreateTemplateVersion.Generic(),
	CreateWorkspace.Generic(),
	CreateWorkspaceBuild.Generic(),
	DeleteTemplate.Generic(),
	ListTemplates.Generic(),
	ListTemplateVersionParameters.Generic(),
	ListWorkspaces.Generic(),
	GetAuthenticatedUser.Generic(),
	GetTemplateVersionLogs.Generic(),
	GetWorkspace.Generic(),
	GetWorkspaceAgentLogs.Generic(),
	GetWorkspaceBuildLogs.Generic(),
	ReportTask.Generic(),
	UploadTarFile.Generic(),
	UpdateTemplateActiveVersion.Generic(),
	WorkspaceBash.Generic(),
	ChatGPTSearch.Generic(),
	ChatGPTFetch.Generic(),
	WorkspaceLS.Generic(),
	WorkspaceReadFile.Generic(),
	WorkspaceWriteFile.Generic(),
	WorkspaceEditFile.Generic(),
	WorkspaceEditFiles.Generic(),
	WorkspacePortForward.Generic(),
	WorkspaceListApps.Generic(),
	CreateTask.Generic(),
	DeleteTask.Generic(),
	ListTasks.Generic(),
	GetTaskStatus.Generic(),
	SendTaskInput.Generic(),
	GetTaskLogs.Generic(),
}

All is a list of all tools that can be used in the Coder CLI. When you add a new tool, be sure to include it here!

View Source
var ChatGPTFetch = Tool[FetchArgs, FetchResult]{
	Tool: aisdk.Tool{
		Name: ToolNameChatGPTFetch,
		Description: `Fetch a template or workspace.

		ID is a unique identifier for the template or workspace. It is a combination of the type and the ID.

		# Examples

		Fetch a template with ID "56f13b5e-be0f-4a17-bdb2-aaacc3353ea7".

		` + "```" + `json
		{
			"id": "template:56f13b5e-be0f-4a17-bdb2-aaacc3353ea7"
		}
		` + "```" + `

		Fetch a workspace with ID "fcb6fc42-ba88-4175-9508-88e6a554a61a".

		` + "```" + `json
		{
			"id": "workspace:fcb6fc42-ba88-4175-9508-88e6a554a61a"
		}
		` + "```" + `
		`,

		Schema: aisdk.Schema{
			Properties: map[string]any{
				"id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args FetchArgs) (FetchResult, error) {
		objectID, err := parseObjectID(args.ID)
		if err != nil {
			return FetchResult{}, err
		}
		switch objectID.Type {
		case ObjectTypeTemplate:
			return fetchTemplate(ctx, deps, objectID.ID)
		case ObjectTypeWorkspace:
			return fetchWorkspace(ctx, deps, objectID.ID)
		}
		return FetchResult{}, xerrors.Errorf("reached unreachable code with object ID: %s", args.ID)
	},
}

Implements the "fetch" tool as described in https://platform.openai.com/docs/mcp#fetch-tool. From my experiments with ChatGPT, it seems that it does not see the description that is provided in the tool definition. ChatGPT sees "fetch" as a very simple tool that can take an ID returned by the "search" tool and return the full details of the object.

View Source
var ChatGPTSearch = Tool[SearchArgs, SearchResult]{
	Tool: aisdk.Tool{
		Name: ToolNameChatGPTSearch,

		Description: `Search for templates, workspaces, and files in workspaces.

To pick what you want to search for, use the following query formats:

- ` + "`" + `templates/<template-query>` + "`" + `: List templates. The query accepts the following, optional parameters delineated by whitespace:
	- "name:<name>" - Fuzzy search by template name (substring matching). Example: "name:docker"
	- "organization:<organization>" - Filter by organization ID or name. Example: "organization:coder"
	- "deprecated:<true|false>" - Filter by deprecated status. Example: "deprecated:true"
	- "deleted:<true|false>" - Filter by deleted status. Example: "deleted:true"
	- "has-ai-task:<true|false>" - Filter by whether the template has an AI task. Example: "has-ai-task:true"
- ` + "`" + `workspaces/<workspace-query>` + "`" + `: List workspaces. The query accepts the following, optional parameters delineated by whitespace:
	- "owner:<username>" - Filter by workspace owner (username or "me"). Example: "owner:alice" or "owner:me"
	- "template:<template-name>" - Filter by template name. Example: "template:web-development"
	- "name:<workspace-name>" - Filter by workspace name (substring matching). Example: "name:project"
	- "organization:<organization>" - Filter by organization ID or name. Example: "organization:engineering"
	- "status:<status>" - Filter by workspace/build status. Values: starting, stopping, deleting, deleted, stopped, started, running, pending, canceling, canceled, failed. Example: "status:running"
	- "has-agent:<agent-status>" - Filter by agent connectivity status. Values: connecting, connected, disconnected, timeout. Example: "has-agent:connected"
	- "dormant:<true|false>" - Filter dormant workspaces. Example: "dormant:true"
	- "outdated:<true|false>" - Filter workspaces using outdated template versions. Example: "outdated:true"
	- "last_used_after:<timestamp>" - Filter workspaces last used after a specific date. Example: "last_used_after:2023-12-01T00:00:00Z"
	- "last_used_before:<timestamp>" - Filter workspaces last used before a specific date. Example: "last_used_before:2023-12-31T23:59:59Z"
	- "has-ai-task:<true|false>" - Filter workspaces with AI tasks. Example: "has-ai-task:true"
	- "param:<name>" or "param:<name>=<value>" - Match workspaces by build parameters. Example: "param:environment=production" or "param:gpu"

# Examples

## Listing templates

List all templates without any filters.

` + "```" + `json
{
	"query": "templates"
}
` + "```" + `

List all templates with a "docker" substring in the name.

` + "```" + `json
{
	"query": "templates/name:docker"
}
` + "```" + `

List templates in a specific organization.

` + "```" + `json
{
	"query": "templates/organization:engineering"
}
` + "```" + `

List deprecated templates.

` + "```" + `json
{
	"query": "templates/deprecated:true"
}
` + "```" + `

List templates that have AI tasks.

` + "```" + `json
{
	"query": "templates/has-ai-task:true"
}
` + "```" + `

List templates with multiple filters - non-deprecated templates with "web" in the name.

` + "```" + `json
{
	"query": "templates/name:web deprecated:false"
}
` + "```" + `

List deleted templates (requires appropriate permissions).

` + "```" + `json
{
	"query": "templates/deleted:true"
}
` + "```" + `

## Listing workspaces

List all workspaces belonging to the current user.

` + "```" + `json
{
	"query": "workspaces/owner:me"
}
` + "```" + `

or 

` + "```" + `json
{
	"query": "workspaces"
}
` + "```" + `

List all workspaces belonging to a user with username "josh".

` + "```" + `json
{
	"query": "workspaces/owner:josh"
}
` + "```" + `

List all running workspaces.

` + "```" + `json
{
	"query": "workspaces/status:running"
}
` + "```" + `

List workspaces using a specific template.

` + "```" + `json
{
	"query": "workspaces/template:web-development"
}
` + "```" + `

List dormant workspaces.

` + "```" + `json
{
	"query": "workspaces/dormant:true"
}
` + "```" + `

List workspaces with connected agents.

` + "```" + `json
{
	"query": "workspaces/has-agent:connected"
}
` + "```" + `

List workspaces with multiple filters - running workspaces owned by "alice".

` + "```" + `json
{
	"query": "workspaces/owner:alice status:running"
}
` + "```" + `
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"query": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"query"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args SearchArgs) (SearchResult, error) {
		query, err := parseSearchQuery(args.Query)
		if err != nil {
			return SearchResult{}, err
		}
		switch query.Type {
		case SearchQueryTypeTemplates:
			results, err := searchTemplates(ctx, deps, query.Query)
			if err != nil {
				return SearchResult{}, err
			}
			return SearchResult{Results: results}, nil
		case SearchQueryTypeWorkspaces:
			searchQuery := query.Query
			if searchQuery == "" {
				searchQuery = "owner:me"
			}
			results, err := searchWorkspaces(ctx, deps, searchQuery)
			if err != nil {
				return SearchResult{}, err
			}
			return SearchResult{Results: results}, nil
		}
		return SearchResult{}, xerrors.Errorf("reached unreachable code with query: %s", args.Query)
	},
}

Implements the "search" tool as described in https://platform.openai.com/docs/mcp#search-tool. From my experiments with ChatGPT, it has access to the description that is provided in the tool definition. This is in contrast to the "fetch" tool, where ChatGPT does not have access to the description.

View Source
var CreateTask = Tool[CreateTaskArgs, codersdk.Task]{
	Tool: aisdk.Tool{
		Name:        ToolNameCreateTask,
		Description: `Create a task.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"input": map[string]any{
					"type":        "string",
					"description": "Input/prompt for the task.",
				},
				"template_version_id": map[string]any{
					"type":        "string",
					"description": "ID of the template version to create the task from.",
				},
				"template_version_preset_id": map[string]any{
					"type":        "string",
					"description": "Optional ID of the template version preset to create the task from.",
				},
				"user": map[string]any{
					"type":        "string",
					"description": userDescription("create a task"),
				},
			},
			Required: []string{"input", "template_version_id"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args CreateTaskArgs) (codersdk.Task, error) {
		if args.Input == "" {
			return codersdk.Task{}, xerrors.New("input is required")
		}

		tvID, err := uuid.Parse(args.TemplateVersionID)
		if err != nil {
			return codersdk.Task{}, 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.Task{}, xerrors.New("template_version_preset_id must be a valid UUID")
			}
		}

		if args.User == "" {
			args.User = codersdk.Me
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)
		task, err := expClient.CreateTask(ctx, args.User, codersdk.CreateTaskRequest{
			Input:                   args.Input,
			TemplateVersionID:       tvID,
			TemplateVersionPresetID: tvPresetID,
		})
		if err != nil {
			return codersdk.Task{}, xerrors.Errorf("create task: %w", err)
		}

		return task, nil
	},
}
View Source
var CreateTemplate = Tool[CreateTemplateArgs, codersdk.Template]{
	Tool: aisdk.Tool{
		Name:        ToolNameCreateTemplate,
		Description: "Create a new template in Coder. First, you must create a template version.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"name": map[string]any{
					"type": "string",
				},
				"display_name": map[string]any{
					"type": "string",
				},
				"description": map[string]any{
					"type": "string",
				},
				"icon": map[string]any{
					"type":        "string",
					"description": "A URL to an icon to use.",
				},
				"version_id": map[string]any{
					"type":        "string",
					"description": "The ID of the version to use.",
				},
			},
			Required: []string{"name", "display_name", "description", "version_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args CreateTemplateArgs) (codersdk.Template, error) {
		me, err := deps.coderClient.User(ctx, "me")
		if err != nil {
			return codersdk.Template{}, err
		}
		versionID, err := uuid.Parse(args.VersionID)
		if err != nil {
			return codersdk.Template{}, xerrors.Errorf("version_id must be a valid UUID: %w", err)
		}
		template, err := deps.coderClient.CreateTemplate(ctx, me.OrganizationIDs[0], codersdk.CreateTemplateRequest{
			Name:        args.Name,
			DisplayName: args.DisplayName,
			Description: args.Description,
			VersionID:   versionID,
		})
		if err != nil {
			return codersdk.Template{}, err
		}
		return template, nil
	},
}
View Source
var CreateTemplateVersion = Tool[CreateTemplateVersionArgs, codersdk.TemplateVersion]{
	Tool: aisdk.Tool{
		Name: ToolNameCreateTemplateVersion,
		Description: `Create a new template version. This is a precursor to creating a template, or you can update an existing template.

Templates are Terraform defining a development environment. The provisioned infrastructure must run
an Agent that connects to the Coder Control Plane to provide a rich experience.

Here are some strict rules for creating a template version:
- YOU MUST NOT use "variable" or "output" blocks in the Terraform code.
- YOU MUST ALWAYS check template version logs after creation to ensure the template was imported successfully.

When a template version is created, a Terraform Plan occurs that ensures the infrastructure
_could_ be provisioned, but actual provisioning occurs when a workspace is created.

<terraform-spec>
The Coder Terraform Provider can be imported like:

` + "```" + `hcl
terraform {
  required_providers {
    coder = {
      source = "coder/coder"
    }
  }
}
` + "```" + `

A destroy does not occur when a user stops a workspace, but rather the transition changes:

` + "```" + `hcl
data "coder_workspace" "me" {}
` + "```" + `

This data source provides the following fields:
- id: The UUID of the workspace.
- name: The name of the workspace.
- transition: Either "start" or "stop".
- start_count: A computed count based on the transition field. If "start", this will be 1.

Access workspace owner information with:

` + "```" + `hcl
data "coder_workspace_owner" "me" {}
` + "```" + `

This data source provides the following fields:
- id: The UUID of the workspace owner.
- name: The name of the workspace owner.
- full_name: The full name of the workspace owner.
- email: The email of the workspace owner.
- session_token: A token that can be used to authenticate the workspace owner. It is regenerated every time the workspace is started.
- oidc_access_token: A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string.

Parameters are defined in the template version. They are rendered in the UI on the workspace creation page:

` + "```" + `hcl
resource "coder_parameter" "region" {
  name = "region"
  type = "string"
  default = "us-east-1"
}
` + "```" + `

This resource accepts the following properties:
- name: The name of the parameter.
- default: The default value of the parameter.
- type: The type of the parameter. Must be one of: "string", "number", "bool", or "list(string)".
- display_name: The displayed name of the parameter as it will appear in the UI.
- description: The description of the parameter as it will appear in the UI.
- ephemeral: The value of an ephemeral parameter will not be preserved between consecutive workspace builds.
- form_type: The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error].
- icon: A URL to an icon to display in the UI.
- mutable: Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution!
- option: Each option block defines a value for a user to select from. (see below for nested schema)
  Required:
  - name: The name of the option.
  - value: The value of the option.
  Optional:
  - description: The description of the option as it will appear in the UI.
  - icon: A URL to an icon to display in the UI.

A Workspace Agent runs on provisioned infrastructure to provide access to the workspace:

` + "```" + `hcl
resource "coder_agent" "dev" {
  arch = "amd64"
  os = "linux"
}
` + "```" + `

This resource accepts the following properties:
- arch: The architecture of the agent. Must be one of: "amd64", "arm64", or "armv7".
- os: The operating system of the agent. Must be one of: "linux", "windows", or "darwin".
- auth: The authentication method for the agent. Must be one of: "token", "google-instance-identity", "aws-instance-identity", or "azure-instance-identity". It is insecure to pass the agent token via exposed variables to Virtual Machines. Instance Identity enables provisioned VMs to authenticate by instance ID on start.
- dir: The starting directory when a user creates a shell session. Defaults to "$HOME".
- env: A map of environment variables to set for the agent.
- startup_script: A script to run after the agent starts. This script MUST exit eventually to signal that startup has completed. Use "&" or "screen" to run processes in the background.

This resource provides the following fields:
- id: The UUID of the agent.
- init_script: The script to run on provisioned infrastructure to fetch and start the agent.
- token: Set the environment variable CODER_AGENT_TOKEN to this value to authenticate the agent.

The agent MUST be installed and started using the init_script. A utility like curl or wget to fetch the agent binary must exist in the provisioned infrastructure.

Expose terminal or HTTP applications running in a workspace with:

` + "```" + `hcl
resource "coder_app" "dev" {
  agent_id = coder_agent.dev.id
  slug = "my-app-name"
  display_name = "My App"
  icon = "https://my-app.com/icon.svg"
  url = "http://127.0.0.1:3000"
}
` + "```" + `

This resource accepts the following properties:
- agent_id: The ID of the agent to attach the app to.
- slug: The slug of the app.
- display_name: The displayed name of the app as it will appear in the UI.
- icon: A URL to an icon to display in the UI.
- url: An external url if external=true or a URL to be proxied to from inside the workspace. This should be of the form http://localhost:PORT[/SUBPATH]. Either command or url may be specified, but not both.
- command: A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either command or url may be specified, but not both.
- external: Whether this app is an external app. If true, the url will be opened in a new tab.
</terraform-spec>

The Coder Server may not be authenticated with the infrastructure provider a user requests. In this scenario,
the user will need to provide credentials to the Coder Server before the workspace can be provisioned.

Here are examples of provisioning the Coder Agent on specific infrastructure providers:

<aws-ec2-instance>
// The agent is configured with "aws-instance-identity" auth.
terraform {
  required_providers {
    cloudinit = {
      source = "hashicorp/cloudinit"
    }
    aws = {
      source = "hashicorp/aws"
    }
  }
}

data "cloudinit_config" "user_data" {
  gzip          = false
  base64_encode = false
  boundary = "//"
  part {
    filename     = "cloud-config.yaml"
    content_type = "text/cloud-config"

	// Here is the content of the cloud-config.yaml.tftpl file:
	// #cloud-config
	// cloud_final_modules:
	//   - [scripts-user, always]
	// hostname: ${hostname}
	// users:
	//   - name: ${linux_user}
	//     sudo: ALL=(ALL) NOPASSWD:ALL
	//     shell: /bin/bash
    content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
      hostname   = local.hostname
      linux_user = local.linux_user
    })
  }

  part {
    filename     = "userdata.sh"
    content_type = "text/x-shellscript"

	// Here is the content of the userdata.sh.tftpl file:
	// #!/bin/bash
	// sudo -u '${linux_user}' sh -c '${init_script}'
    content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", {
      linux_user = local.linux_user

      init_script = try(coder_agent.dev[0].init_script, "")
    })
  }
}

resource "aws_instance" "dev" {
  ami               = data.aws_ami.ubuntu.id
  availability_zone = "${data.coder_parameter.region.value}a"
  instance_type     = data.coder_parameter.instance_type.value

  user_data = data.cloudinit_config.user_data.rendered
  tags = {
    Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
  }
  lifecycle {
    ignore_changes = [ami]
  }
}
</aws-ec2-instance>

<gcp-vm-instance>
// The agent is configured with "google-instance-identity" auth.
terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
    }
  }
}

resource "google_compute_instance" "dev" {
  zone         = module.gcp_region.value
  count        = data.coder_workspace.me.start_count
  name         = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root"
  machine_type = "e2-medium"
  network_interface {
    network = "default"
    access_config {
      // Ephemeral public IP
    }
  }
  boot_disk {
    auto_delete = false
    source      = google_compute_disk.root.name
  }
  // In order to use google-instance-identity, a service account *must* be provided.
  service_account {
    email  = data.google_compute_default_service_account.default.email
    scopes = ["cloud-platform"]
  }
  # ONLY FOR WINDOWS:
  # metadata = {
  #   windows-startup-script-ps1 = coder_agent.main.init_script
  # }
  # The startup script runs as root with no $HOME environment set up, so instead of directly
  # running the agent init script, create a user (with a homedir, default shell and sudo
  # permissions) and execute the init script as that user.
  #
  # The agent MUST be started in here.
  metadata_startup_script = <<EOMETA
#!/usr/bin/env sh
set -eux

# If user does not exist, create it and set up passwordless sudo
if ! id -u "${local.linux_user}" >/dev/null 2>&1; then
  useradd -m -s /bin/bash "${local.linux_user}"
  echo "${local.linux_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder-user
fi

exec sudo -u "${local.linux_user}" sh -c '${coder_agent.main.init_script}'
EOMETA
}
</gcp-vm-instance>

<azure-vm-instance>
// The agent is configured with "azure-instance-identity" auth.
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
    }
    cloudinit = {
      source = "hashicorp/cloudinit"
    }
  }
}

data "cloudinit_config" "user_data" {
  gzip          = false
  base64_encode = true

  boundary = "//"

  part {
    filename     = "cloud-config.yaml"
    content_type = "text/cloud-config"

	// Here is the content of the cloud-config.yaml.tftpl file:
	// #cloud-config
	// cloud_final_modules:
	// - [scripts-user, always]
	// bootcmd:
	//   # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117
	//   - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done
	// device_aliases:
	//   homedir: /dev/disk/azure/scsi1/lun10
	// disk_setup:
	//   homedir:
	//     table_type: gpt
	//     layout: true
	// fs_setup:
	//   - label: coder_home
	//     filesystem: ext4
	//     device: homedir.1
	// mounts:
	//   - ["LABEL=coder_home", "/home/${username}"]
	// hostname: ${hostname}
	// users:
	//   - name: ${username}
	//     sudo: ["ALL=(ALL) NOPASSWD:ALL"]
	//     groups: sudo
	//     shell: /bin/bash
	// packages:
	//   - git
	// write_files:
	//   - path: /opt/coder/init
	//     permissions: "0755"
	//     encoding: b64
	//     content: ${init_script}
	//   - path: /etc/systemd/system/coder-agent.service
	//     permissions: "0644"
	//     content: |
	//       [Unit]
	//       Description=Coder Agent
	//       After=network-online.target
	//       Wants=network-online.target

	//       [Service]
	//       User=${username}
	//       ExecStart=/opt/coder/init
	//       Restart=always
	//       RestartSec=10
	//       TimeoutStopSec=90
	//       KillMode=process

	//       OOMScoreAdjust=-900
	//       SyslogIdentifier=coder-agent

	//       [Install]
	//       WantedBy=multi-user.target
	// runcmd:
	//   - chown ${username}:${username} /home/${username}
	//   - systemctl enable coder-agent
	//   - systemctl start coder-agent
    content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
      username    = "coder" # Ensure this user/group does not exist in your VM image
      init_script = base64encode(coder_agent.main.init_script)
      hostname    = lower(data.coder_workspace.me.name)
    })
  }
}

resource "azurerm_linux_virtual_machine" "main" {
  count               = data.coder_workspace.me.start_count
  name                = "vm"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  size                = data.coder_parameter.instance_type.value
  // cloud-init overwrites this, so the value here doesn't matter
  admin_username = "adminuser"
  admin_ssh_key {
    public_key = tls_private_key.dummy.public_key_openssh
    username   = "adminuser"
  }

  network_interface_ids = [
    azurerm_network_interface.main.id,
  ]
  computer_name = lower(data.coder_workspace.me.name)
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-focal"
    sku       = "20_04-lts-gen2"
    version   = "latest"
  }
  user_data = data.cloudinit_config.user_data.rendered
}
</azure-vm-instance>

<docker-container>
terraform {
  required_providers {
    coder = {
      source = "kreuzwerker/docker"
    }
  }
}

// The agent is configured with "token" auth.

resource "docker_container" "workspace" {
  count = data.coder_workspace.me.start_count
  image = "codercom/enterprise-base:ubuntu"
  # Uses lower() to avoid Docker restriction on container names.
  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
  # Hostname makes the shell more user friendly: coder@my-workspace:~$
  hostname = data.coder_workspace.me.name
  # Use the docker gateway if the access URL is 127.0.0.1.
  entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
  env        = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
  host {
    host = "host.docker.internal"
    ip   = "host-gateway"
  }
  volumes {
    container_path = "/home/coder"
    volume_name    = docker_volume.home_volume.name
    read_only      = false
  }
}
</docker-container>

<kubernetes-pod>
// The agent is configured with "token" auth.

resource "kubernetes_deployment" "main" {
  count = data.coder_workspace.me.start_count
  depends_on = [
    kubernetes_persistent_volume_claim.home
  ]
  wait_for_rollout = false
  metadata {
    name      = "coder-${data.coder_workspace.me.id}"
  }

  spec {
    replicas = 1
    strategy {
      type = "Recreate"
    }

    template {
      spec {
        security_context {
          run_as_user     = 1000
          fs_group        = 1000
          run_as_non_root = true
        }

        container {
          name              = "dev"
          image             = "codercom/enterprise-base:ubuntu"
          image_pull_policy = "Always"
          command           = ["sh", "-c", coder_agent.main.init_script]
          security_context {
            run_as_user = "1000"
          }
          env {
            name  = "CODER_AGENT_TOKEN"
            value = coder_agent.main.token
          }
        }
      }
    }
  }
}
</kubernetes-pod>

The file_id provided is a reference to a tar file you have uploaded containing the Terraform.
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"template_id": map[string]any{
					"type": "string",
				},
				"file_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"file_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args CreateTemplateVersionArgs) (codersdk.TemplateVersion, error) {
		me, err := deps.coderClient.User(ctx, "me")
		if err != nil {
			return codersdk.TemplateVersion{}, err
		}
		fileID, err := uuid.Parse(args.FileID)
		if err != nil {
			return codersdk.TemplateVersion{}, xerrors.Errorf("file_id must be a valid UUID: %w", err)
		}
		var templateID uuid.UUID
		if args.TemplateID != "" {
			tid, err := uuid.Parse(args.TemplateID)
			if err != nil {
				return codersdk.TemplateVersion{}, xerrors.Errorf("template_id must be a valid UUID: %w", err)
			}
			templateID = tid
		}
		templateVersion, err := deps.coderClient.CreateTemplateVersion(ctx, me.OrganizationIDs[0], codersdk.CreateTemplateVersionRequest{
			Message:       "Created by AI",
			StorageMethod: codersdk.ProvisionerStorageMethodFile,
			FileID:        fileID,
			Provisioner:   codersdk.ProvisionerTypeTerraform,
			TemplateID:    templateID,
		})
		if err != nil {
			return codersdk.TemplateVersion{}, err
		}
		return templateVersion, nil
	},
}
View Source
var CreateWorkspace = Tool[CreateWorkspaceArgs, codersdk.Workspace]{
	Tool: aisdk.Tool{
		Name: ToolNameCreateWorkspace,
		Description: `Create a new workspace in Coder.

If a user is asking to "test a template", they are typically referring
to creating a workspace from a template to ensure the infrastructure
is provisioned correctly and the agent can connect to the control plane.

Before creating a workspace, always confirm the template choice with the user by:

	1. Listing the available templates that match their request.
	2. Recommending the most relevant option.
	2. Asking the user to confirm which template to use.

It is important to not create a workspace without confirming the template
choice with the user.

After creating a workspace, watch the build logs and wait for the workspace to
be ready before trying to use or connect to the workspace.
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"user": map[string]any{
					"type":        "string",
					"description": userDescription("create a workspace"),
				},
				"template_version_id": map[string]any{
					"type":        "string",
					"description": "ID of the template version to create the workspace from.",
				},
				"name": map[string]any{
					"type":        "string",
					"description": "Name of the workspace to create.",
				},
				"rich_parameters": map[string]any{
					"type":        "object",
					"description": "Key/value pairs of rich parameters to pass to the template version to create the workspace.",
				},
			},
			Required: []string{"user", "template_version_id", "name", "rich_parameters"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args CreateWorkspaceArgs) (codersdk.Workspace, error) {
		tvID, err := uuid.Parse(args.TemplateVersionID)
		if err != nil {
			return codersdk.Workspace{}, xerrors.New("template_version_id must be a valid UUID")
		}
		if args.User == "" {
			args.User = codersdk.Me
		}
		var buildParams []codersdk.WorkspaceBuildParameter
		for k, v := range args.RichParameters {
			buildParams = append(buildParams, codersdk.WorkspaceBuildParameter{
				Name:  k,
				Value: v,
			})
		}
		workspace, err := deps.coderClient.CreateUserWorkspace(ctx, args.User, codersdk.CreateWorkspaceRequest{
			TemplateVersionID:   tvID,
			Name:                args.Name,
			RichParameterValues: buildParams,
		})
		if err != nil {
			return codersdk.Workspace{}, err
		}
		return workspace, nil
	},
}
View Source
var CreateWorkspaceBuild = Tool[CreateWorkspaceBuildArgs, codersdk.WorkspaceBuild]{
	Tool: aisdk.Tool{
		Name: ToolNameCreateWorkspaceBuild,
		Description: `Create a new workspace build for an existing workspace. Use this to start, stop, or delete.

After creating a workspace build, watch the build logs and wait for the
workspace build to complete before trying to start another build or use or
connect to the workspace.
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace_id": map[string]any{
					"type": "string",
				},
				"transition": map[string]any{
					"type":        "string",
					"description": "The transition to perform. Must be one of: start, stop, delete",
					"enum":        []string{"start", "stop", "delete"},
				},
				"template_version_id": map[string]any{
					"type":        "string",
					"description": "(Optional) The template version ID to use for the workspace build. If not provided, the previously built version will be used.",
				},
			},
			Required: []string{"workspace_id", "transition"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args CreateWorkspaceBuildArgs) (codersdk.WorkspaceBuild, error) {
		workspaceID, err := uuid.Parse(args.WorkspaceID)
		if err != nil {
			return codersdk.WorkspaceBuild{}, xerrors.Errorf("workspace_id must be a valid UUID: %w", err)
		}
		var templateVersionID uuid.UUID
		if args.TemplateVersionID != "" {
			tvID, err := uuid.Parse(args.TemplateVersionID)
			if err != nil {
				return codersdk.WorkspaceBuild{}, xerrors.Errorf("template_version_id must be a valid UUID: %w", err)
			}
			templateVersionID = tvID
		}
		cbr := codersdk.CreateWorkspaceBuildRequest{
			Transition: codersdk.WorkspaceTransition(args.Transition),
		}
		if templateVersionID != uuid.Nil {
			cbr.TemplateVersionID = templateVersionID
		}
		return deps.coderClient.CreateWorkspaceBuild(ctx, workspaceID, cbr)
	},
}
View Source
var DeleteTask = Tool[DeleteTaskArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name:        ToolNameDeleteTask,
		Description: `Delete a task.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"task_id": map[string]any{
					"type":        "string",
					"description": taskIDDescription("delete"),
				},
			},
			Required: []string{"task_id"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args DeleteTaskArgs) (codersdk.Response, error) {
		if args.TaskID == "" {
			return codersdk.Response{}, xerrors.New("task_id is required")
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)

		task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
		if err != nil {
			return codersdk.Response{}, xerrors.Errorf("resolve task: %w", err)
		}

		err = expClient.DeleteTask(ctx, task.OwnerName, task.ID)
		if err != nil {
			return codersdk.Response{}, xerrors.Errorf("delete task: %w", err)
		}

		return codersdk.Response{
			Message: "Task deleted successfully",
		}, nil
	},
}
View Source
var DeleteTemplate = Tool[DeleteTemplateArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name:        ToolNameDeleteTemplate,
		Description: "Delete a template. This is irreversible.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"template_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"template_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args DeleteTemplateArgs) (codersdk.Response, error) {
		templateID, err := uuid.Parse(args.TemplateID)
		if err != nil {
			return codersdk.Response{}, xerrors.Errorf("template_id must be a valid UUID: %w", err)
		}
		err = deps.coderClient.DeleteTemplate(ctx, templateID)
		if err != nil {
			return codersdk.Response{}, err
		}
		return codersdk.Response{
			Message: "Template deleted successfully.",
		}, nil
	},
}
View Source
var GetAuthenticatedUser = Tool[NoArgs, codersdk.User]{
	Tool: aisdk.Tool{
		Name:        ToolNameGetAuthenticatedUser,
		Description: "Get the currently authenticated user, similar to the `whoami` command.",
		Schema: aisdk.Schema{
			Properties: map[string]any{},
			Required:   []string{},
		},
	},
	Handler: func(ctx context.Context, deps Deps, _ NoArgs) (codersdk.User, error) {
		return deps.coderClient.User(ctx, "me")
	},
}
View Source
var GetTaskLogs = Tool[GetTaskLogsArgs, codersdk.TaskLogsResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameGetTaskLogs,
		Description: `Get the logs of a task.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"task_id": map[string]any{
					"type":        "string",
					"description": taskIDDescription("query"),
				},
			},
			Required: []string{"task_id"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args GetTaskLogsArgs) (codersdk.TaskLogsResponse, error) {
		if args.TaskID == "" {
			return codersdk.TaskLogsResponse{}, xerrors.New("task_id is required")
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)

		task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
		if err != nil {
			return codersdk.TaskLogsResponse{}, err
		}

		logs, err := expClient.TaskLogs(ctx, task.OwnerName, task.ID)
		if err != nil {
			return codersdk.TaskLogsResponse{}, xerrors.Errorf("get task logs %q: %w", args.TaskID, err)
		}

		return logs, nil
	},
}
View Source
var GetTaskStatus = Tool[GetTaskStatusArgs, GetTaskStatusResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameGetTaskStatus,
		Description: `Get the status of a task.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"task_id": map[string]any{
					"type":        "string",
					"description": taskIDDescription("get"),
				},
			},
			Required: []string{"task_id"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args GetTaskStatusArgs) (GetTaskStatusResponse, error) {
		if args.TaskID == "" {
			return GetTaskStatusResponse{}, xerrors.New("task_id is required")
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)

		task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
		if err != nil {
			return GetTaskStatusResponse{}, xerrors.Errorf("resolve task %q: %w", args.TaskID, err)
		}

		return GetTaskStatusResponse{
			Status: task.Status,
			State:  task.CurrentState,
		}, nil
	},
}
View Source
var GetTemplateVersionLogs = Tool[GetTemplateVersionLogsArgs, []string]{
	Tool: aisdk.Tool{
		Name:        ToolNameGetTemplateVersionLogs,
		Description: "Get the logs of a template version. This is useful to check whether a template version successfully imports or not.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"template_version_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"template_version_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args GetTemplateVersionLogsArgs) ([]string, error) {
		templateVersionID, err := uuid.Parse(args.TemplateVersionID)
		if err != nil {
			return nil, xerrors.Errorf("template_version_id must be a valid UUID: %w", err)
		}

		logs, closer, err := deps.coderClient.TemplateVersionLogsAfter(ctx, templateVersionID, 0)
		if err != nil {
			return nil, err
		}
		defer closer.Close()
		var acc []string
		for log := range logs {
			acc = append(acc, log.Output)
		}
		return acc, nil
	},
}
View Source
var GetWorkspace = Tool[GetWorkspaceArgs, codersdk.Workspace]{
	Tool: aisdk.Tool{
		Name: ToolNameGetWorkspace,
		Description: `Get a workspace by ID.

This returns more data than list_workspaces to reduce token usage.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"workspace_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args GetWorkspaceArgs) (codersdk.Workspace, error) {
		wsID, err := uuid.Parse(args.WorkspaceID)
		if err != nil {
			return codersdk.Workspace{}, xerrors.New("workspace_id must be a valid UUID")
		}
		return deps.coderClient.Workspace(ctx, wsID)
	},
}
View Source
var GetWorkspaceAgentLogs = Tool[GetWorkspaceAgentLogsArgs, []string]{
	Tool: aisdk.Tool{
		Name: ToolNameGetWorkspaceAgentLogs,
		Description: `Get the logs of a workspace agent.

		More logs may appear after this call. It does not wait for the agent to finish.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace_agent_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"workspace_agent_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args GetWorkspaceAgentLogsArgs) ([]string, error) {
		workspaceAgentID, err := uuid.Parse(args.WorkspaceAgentID)
		if err != nil {
			return nil, xerrors.Errorf("workspace_agent_id must be a valid UUID: %w", err)
		}
		logs, closer, err := deps.coderClient.WorkspaceAgentLogsAfter(ctx, workspaceAgentID, 0, false)
		if err != nil {
			return nil, err
		}
		defer closer.Close()
		var acc []string
		for logChunk := range logs {
			for _, log := range logChunk {
				acc = append(acc, log.Output)
			}
		}
		return acc, nil
	},
}
View Source
var GetWorkspaceBuildLogs = Tool[GetWorkspaceBuildLogsArgs, []string]{
	Tool: aisdk.Tool{
		Name: ToolNameGetWorkspaceBuildLogs,
		Description: `Get the logs of a workspace build.

		Useful for checking whether a workspace builds successfully or not.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace_build_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"workspace_build_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args GetWorkspaceBuildLogsArgs) ([]string, error) {
		workspaceBuildID, err := uuid.Parse(args.WorkspaceBuildID)
		if err != nil {
			return nil, xerrors.Errorf("workspace_build_id must be a valid UUID: %w", err)
		}
		logs, closer, err := deps.coderClient.WorkspaceBuildLogsAfter(ctx, workspaceBuildID, 0)
		if err != nil {
			return nil, err
		}
		defer closer.Close()
		var acc []string
		for log := range logs {
			acc = append(acc, log.Output)
		}
		return acc, nil
	},
}
View Source
var ListTasks = Tool[ListTasksArgs, ListTasksResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameListTasks,
		Description: `List tasks.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"status": map[string]any{
					"type":        "string",
					"description": "Optional filter by task status.",
				},
				"user": map[string]any{
					"type":        "string",
					"description": userDescription("list tasks"),
				},
			},
			Required: []string{},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args ListTasksArgs) (ListTasksResponse, error) {
		if args.User == "" {
			args.User = codersdk.Me
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)
		tasks, err := expClient.Tasks(ctx, &codersdk.TasksFilter{
			Owner:  args.User,
			Status: args.Status,
		})
		if err != nil {
			return ListTasksResponse{}, xerrors.Errorf("list tasks: %w", err)
		}

		return ListTasksResponse{
			Tasks: tasks,
		}, nil
	},
}
View Source
var ListTemplateVersionParameters = Tool[ListTemplateVersionParametersArgs, []codersdk.TemplateVersionParameter]{
	Tool: aisdk.Tool{
		Name:        ToolNameListTemplateVersionParams,
		Description: "Get the parameters for a template version. You can refer to these as workspace parameters to the user, as they are typically important for creating a workspace.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"template_version_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"template_version_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args ListTemplateVersionParametersArgs) ([]codersdk.TemplateVersionParameter, error) {
		templateVersionID, err := uuid.Parse(args.TemplateVersionID)
		if err != nil {
			return nil, xerrors.Errorf("template_version_id must be a valid UUID: %w", err)
		}
		parameters, err := deps.coderClient.TemplateVersionRichParameters(ctx, templateVersionID)
		if err != nil {
			return nil, err
		}
		return parameters, nil
	},
}
View Source
var ListTemplates = Tool[NoArgs, []MinimalTemplate]{
	Tool: aisdk.Tool{
		Name:        ToolNameListTemplates,
		Description: "Lists templates for the authenticated user.",
		Schema: aisdk.Schema{
			Properties: map[string]any{},
			Required:   []string{},
		},
	},
	Handler: func(ctx context.Context, deps Deps, _ NoArgs) ([]MinimalTemplate, error) {
		templates, err := deps.coderClient.Templates(ctx, codersdk.TemplateFilter{})
		if err != nil {
			return nil, err
		}
		minimalTemplates := make([]MinimalTemplate, len(templates))
		for i, template := range templates {
			minimalTemplates[i] = MinimalTemplate{
				DisplayName:     template.DisplayName,
				ID:              template.ID.String(),
				Name:            template.Name,
				Description:     template.Description,
				ActiveVersionID: template.ActiveVersionID,
				ActiveUserCount: template.ActiveUserCount,
			}
		}
		return minimalTemplates, nil
	},
}
View Source
var ListWorkspaces = Tool[ListWorkspacesArgs, []MinimalWorkspace]{
	Tool: aisdk.Tool{
		Name:        ToolNameListWorkspaces,
		Description: "Lists workspaces for the authenticated user.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"owner": map[string]any{
					"type":        "string",
					"description": "The owner of the workspaces to list. Use \"me\" to list workspaces for the authenticated user. If you do not specify an owner, \"me\" will be assumed by default.",
				},
			},
			Required: []string{},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args ListWorkspacesArgs) ([]MinimalWorkspace, error) {
		owner := args.Owner
		if owner == "" {
			owner = codersdk.Me
		}
		workspaces, err := deps.coderClient.Workspaces(ctx, codersdk.WorkspaceFilter{
			Owner: owner,
		})
		if err != nil {
			return nil, err
		}
		minimalWorkspaces := make([]MinimalWorkspace, len(workspaces.Workspaces))
		for i, workspace := range workspaces.Workspaces {
			minimalWorkspaces[i] = MinimalWorkspace{
				ID:                      workspace.ID.String(),
				Name:                    workspace.Name,
				TemplateID:              workspace.TemplateID.String(),
				TemplateName:            workspace.TemplateName,
				TemplateDisplayName:     workspace.TemplateDisplayName,
				TemplateIcon:            workspace.TemplateIcon,
				TemplateActiveVersionID: workspace.TemplateActiveVersionID,
				Outdated:                workspace.Outdated,
			}
		}
		return minimalWorkspaces, nil
	},
}
View Source
var ReportTask = Tool[ReportTaskArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name: ToolNameReportTask,
		Description: `Report progress on your work.

The user observes your work through a Task UI. To keep them updated
on your progress, or if you need help - use this tool.

Good Tasks
- "Cloning the repository <repository-url>"
- "Working on <feature-name>"
- "Figuring our why <issue> is happening"

Bad Tasks
- "I'm working on it"
- "I'm trying to fix it"
- "I'm trying to implement <feature-name>"

Use the "state" field to indicate your progress. Periodically report
progress with state "working" to keep the user updated. It is not possible to send too many updates!

ONLY report an "idle" or "failure" state if you have FULLY completed the task.
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"summary": map[string]any{
					"type":        "string",
					"description": "A concise summary of your current progress on the task. This must be less than 160 characters in length and must not include newlines or other control characters.",
				},
				"link": map[string]any{
					"type":        "string",
					"description": "A link to a relevant resource, such as a PR or issue.",
				},
				"state": map[string]any{
					"type":        "string",
					"description": "The state of your task. This can be one of the following: working, idle, or failure. Select the state that best represents your current progress.",
					"enum": []string{
						string(codersdk.WorkspaceAppStatusStateWorking),
						string(codersdk.WorkspaceAppStatusStateIdle),
						string(codersdk.WorkspaceAppStatusStateFailure),
					},
				},
			},
			Required: []string{"summary", "link", "state"},
		},
	},
	UserClientOptional: true,
	Handler: func(_ context.Context, deps Deps, args ReportTaskArgs) (codersdk.Response, error) {
		if len(args.Summary) > 160 {
			return codersdk.Response{}, xerrors.New("summary must be less than 160 characters")
		}

		if deps.report == nil {
			return codersdk.Response{}, xerrors.New("task reporting not available. Please ensure a task reporter is configured.")
		}
		err := deps.report(args)
		if err != nil {
			return codersdk.Response{}, err
		}
		return codersdk.Response{
			Message: "Thanks for reporting!",
		}, nil
	},
}
View Source
var SendTaskInput = Tool[SendTaskInputArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name:        ToolNameSendTaskInput,
		Description: `Send input to a running task.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"task_id": map[string]any{
					"type":        "string",
					"description": taskIDDescription("prompt"),
				},
				"input": map[string]any{
					"type":        "string",
					"description": "The input to send to the task.",
				},
			},
			Required: []string{"task_id", "input"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args SendTaskInputArgs) (codersdk.Response, error) {
		if args.TaskID == "" {
			return codersdk.Response{}, xerrors.New("task_id is required")
		}

		if args.Input == "" {
			return codersdk.Response{}, xerrors.New("input is required")
		}

		expClient := codersdk.NewExperimentalClient(deps.coderClient)

		task, err := expClient.TaskByIdentifier(ctx, args.TaskID)
		if err != nil {
			return codersdk.Response{}, xerrors.Errorf("resolve task %q: %w", args.TaskID, err)
		}

		err = expClient.TaskSend(ctx, task.OwnerName, task.ID, codersdk.TaskSendRequest{
			Input: args.Input,
		})
		if err != nil {
			return codersdk.Response{}, xerrors.Errorf("send task input %q: %w", args.TaskID, err)
		}

		return codersdk.Response{
			Message: "Input sent to task successfully.",
		}, nil
	},
}
View Source
var UpdateTemplateActiveVersion = Tool[UpdateTemplateActiveVersionArgs, string]{
	Tool: aisdk.Tool{
		Name:        ToolNameUpdateTemplateActiveVersion,
		Description: "Update the active version of a template. This is helpful when iterating on templates.",
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"template_id": map[string]any{
					"type": "string",
				},
				"template_version_id": map[string]any{
					"type": "string",
				},
			},
			Required: []string{"template_id", "template_version_id"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args UpdateTemplateActiveVersionArgs) (string, error) {
		templateID, err := uuid.Parse(args.TemplateID)
		if err != nil {
			return "", xerrors.Errorf("template_id must be a valid UUID: %w", err)
		}
		templateVersionID, err := uuid.Parse(args.TemplateVersionID)
		if err != nil {
			return "", xerrors.Errorf("template_version_id must be a valid UUID: %w", err)
		}
		err = deps.coderClient.UpdateActiveTemplateVersion(ctx, templateID, codersdk.UpdateActiveTemplateVersion{
			ID: templateVersionID,
		})
		if err != nil {
			return "", err
		}
		return "Successfully updated active version!", nil
	},
}
View Source
var UploadTarFile = Tool[UploadTarFileArgs, codersdk.UploadResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameUploadTarFile,
		Description: `Create and upload a tar file by key/value mapping of file names to file contents. Use this to create template versions. Reference the tool description of "create_template_version" to understand template requirements.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"files": map[string]any{
					"type":        "object",
					"description": "A map of file names to file contents.",
				},
			},
			Required: []string{"files"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args UploadTarFileArgs) (codersdk.UploadResponse, error) {
		pipeReader, pipeWriter := io.Pipe()
		done := make(chan struct{})
		go func() {
			defer func() {
				_ = pipeWriter.Close()
				close(done)
			}()
			tarWriter := tar.NewWriter(pipeWriter)
			for name, content := range args.Files {
				header := &tar.Header{
					Name: name,
					Size: int64(len(content)),
					Mode: 0o644,
				}
				if err := tarWriter.WriteHeader(header); err != nil {
					_ = pipeWriter.CloseWithError(err)
					return
				}
				if _, err := tarWriter.Write([]byte(content)); err != nil {
					_ = pipeWriter.CloseWithError(err)
					return
				}
			}
			if err := tarWriter.Close(); err != nil {
				_ = pipeWriter.CloseWithError(err)
			}
		}()

		resp, err := deps.coderClient.Upload(ctx, codersdk.ContentTypeTar, pipeReader)
		if err != nil {
			_ = pipeReader.CloseWithError(err)
			<-done
			return codersdk.UploadResponse{}, err
		}
		<-done
		return resp, nil
	},
}
View Source
var WorkspaceBash = Tool[WorkspaceBashArgs, WorkspaceBashResult]{
	Tool: aisdk.Tool{
		Name: ToolNameWorkspaceBash,
		Description: `Execute a bash command in a Coder workspace.

This tool provides the same functionality as the 'coder ssh <workspace> <command>' CLI command.
It automatically starts the workspace if it's stopped and waits for the agent to be ready.
The output is trimmed of leading and trailing whitespace.

The workspace parameter supports various formats:
- workspace (uses current user)
- owner/workspace
- owner--workspace
- workspace.agent (specific agent)
- owner/workspace.agent

The timeout_ms parameter specifies the command timeout in milliseconds (defaults to 60000ms, maximum of 300000ms).
If the command times out, all output captured up to that point is returned with a cancellation message.

For background commands (background: true), output is captured until the timeout is reached, then the command
continues running in the background. The captured output is returned as the result.

For file operations (list, write, edit), always prefer the dedicated file tools.
Do not use bash commands (ls, cat, echo, heredoc, etc.) to list, write, or read
files when the file tools are available. The bash tool should be used for:

	- Running commands and scripts
	- Installing packages
	- Starting services
	- Executing programs

Examples:
- workspace: "john/dev-env", command: "git status", timeout_ms: 30000
- workspace: "my-workspace", command: "npm run dev", background: true, timeout_ms: 10000
- workspace: "my-workspace.main", command: "docker ps"`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": "The workspace name in format [owner/]workspace[.agent]. If owner is not specified, the authenticated user is used.",
				},
				"command": map[string]any{
					"type":        "string",
					"description": "The bash command to execute in the workspace.",
				},
				"timeout_ms": map[string]any{
					"type":        "integer",
					"description": "Command timeout in milliseconds. Defaults to 60000ms (60 seconds) if not specified.",
					"default":     60000,
					"minimum":     1,
				},
				"background": map[string]any{
					"type":        "boolean",
					"description": "Whether to run the command in the background. Output is captured until timeout, then the command continues running in the background.",
				},
			},
			Required: []string{"workspace", "command"},
		},
	},
	Handler: func(ctx context.Context, deps Deps, args WorkspaceBashArgs) (res WorkspaceBashResult, err error) {
		if args.Workspace == "" {
			return WorkspaceBashResult{}, xerrors.New("workspace name cannot be empty")
		}
		if args.Command == "" {
			return WorkspaceBashResult{}, xerrors.New("command cannot be empty")
		}

		ctx, cancel := context.WithTimeoutCause(ctx, 5*time.Minute, xerrors.New("MCP handler timeout after 5 min"))
		defer cancel()

		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return WorkspaceBashResult{}, err
		}
		defer conn.Close()

		sshClient, err := conn.SSHClient(ctx)
		if err != nil {
			return WorkspaceBashResult{}, xerrors.Errorf("failed to create SSH client: %w", err)
		}
		defer sshClient.Close()

		session, err := sshClient.NewSession()
		if err != nil {
			return WorkspaceBashResult{}, xerrors.Errorf("failed to create SSH session: %w", err)
		}
		defer session.Close()

		timeoutMs := args.TimeoutMs
		defaultTimeoutMs := 60000
		if timeoutMs <= 0 {
			timeoutMs = defaultTimeoutMs
		}
		command := args.Command
		if args.Background {

			command = fmt.Sprintf("nohup %s </dev/null 2>&1", args.Command)
		}

		commandCtx, commandCancel := context.WithTimeout(ctx, time.Duration(timeoutMs)*time.Millisecond)
		defer commandCancel()

		output, err := executeCommandWithTimeout(commandCtx, session, command)
		outputStr := strings.TrimSpace(string(output))

		if err != nil {

			if errors.Is(context.Cause(commandCtx), context.DeadlineExceeded) {
				if args.Background {
					outputStr += "\nCommand continues running in background"
				} else {
					outputStr += "\nCommand canceled due to timeout"
				}
				return WorkspaceBashResult{
					Output:   outputStr,
					ExitCode: 124,
				}, nil
			}

			exitCode := 1
			var exitErr *gossh.ExitError
			if errors.As(err, &exitErr) {
				exitCode = exitErr.ExitStatus()
			}

			return WorkspaceBashResult{
				Output:   outputStr,
				ExitCode: exitCode,
			}, nil
		}

		return WorkspaceBashResult{
			Output:   outputStr,
			ExitCode: 0,
		}, nil
	},
}
View Source
var WorkspaceEditFile = Tool[WorkspaceEditFileArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspaceEditFile,
		Description: `Edit a file in a workspace.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"path": map[string]any{
					"type":        "string",
					"description": "The absolute path of the file to write in the workspace.",
				},
				"edits": map[string]any{
					"type":        "array",
					"description": "An array of edit operations.",
					"items": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"search": map[string]any{
								"type":        "string",
								"description": "The old string to replace.",
							},
							"replace": map[string]any{
								"type":        "string",
								"description": "The new string that replaces the old string.",
							},
						},
						"required": []string{"search", "replace"},
					},
				},
			},
			Required: []string{"path", "workspace", "edits"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceEditFileArgs) (codersdk.Response, error) {
		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return codersdk.Response{}, err
		}
		defer conn.Close()

		err = conn.EditFiles(ctx, workspacesdk.FileEditRequest{
			Files: []workspacesdk.FileEdits{
				{
					Path:  args.Path,
					Edits: args.Edits,
				},
			},
		})
		if err != nil {
			return codersdk.Response{}, err
		}

		return codersdk.Response{
			Message: "File edited successfully.",
		}, nil
	},
}
View Source
var WorkspaceEditFiles = Tool[WorkspaceEditFilesArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspaceEditFiles,
		Description: `Edit one or more files in a workspace.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"files": map[string]any{
					"type":        "array",
					"description": "An array of files to edit.",
					"items": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"path": map[string]any{
								"type":        "string",
								"description": "The absolute path of the file to write in the workspace.",
							},
							"edits": map[string]any{
								"type":        "array",
								"description": "An array of edit operations.",
								"items": map[string]any{
									"type": "object",
									"properties": map[string]any{
										"search": map[string]any{
											"type":        "string",
											"description": "The old string to replace.",
										},
										"replace": map[string]any{
											"type":        "string",
											"description": "The new string that replaces the old string.",
										},
									},
									"required": []string{"search", "replace"},
								},
							},
						},
						"required": []string{"path", "edits"},
					},
				},
			},
			Required: []string{"workspace", "files"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceEditFilesArgs) (codersdk.Response, error) {
		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return codersdk.Response{}, err
		}
		defer conn.Close()

		err = conn.EditFiles(ctx, workspacesdk.FileEditRequest{Files: args.Files})
		if err != nil {
			return codersdk.Response{}, err
		}

		return codersdk.Response{
			Message: "File(s) edited successfully.",
		}, nil
	},
}
View Source
var WorkspaceLS = Tool[WorkspaceLSArgs, WorkspaceLSResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspaceLS,
		Description: `List directories in a workspace.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"path": map[string]any{
					"type":        "string",
					"description": "The absolute path of the directory in the workspace to list.",
				},
			},
			Required: []string{"path", "workspace"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceLSArgs) (WorkspaceLSResponse, error) {
		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return WorkspaceLSResponse{}, err
		}
		defer conn.Close()

		res, err := conn.LS(ctx, args.Path, workspacesdk.LSRequest{})
		if err != nil {
			return WorkspaceLSResponse{}, err
		}

		contents := make([]WorkspaceLSFile, len(res.Contents))
		for i, f := range res.Contents {
			contents[i] = WorkspaceLSFile{
				Path:  f.AbsolutePathString,
				IsDir: f.IsDir,
			}
		}
		return WorkspaceLSResponse{Contents: contents}, nil
	},
}
View Source
var WorkspaceListApps = Tool[WorkspaceListAppsArgs, WorkspaceListAppsResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspaceListApps,
		Description: `List the URLs of Coder apps running in a workspace for a single agent.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
			},
			Required: []string{"workspace"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceListAppsArgs) (WorkspaceListAppsResponse, error) {
		workspaceName := NormalizeWorkspaceInput(args.Workspace)
		_, workspaceAgent, err := findWorkspaceAndAgent(ctx, deps.coderClient, workspaceName)
		if err != nil {
			return WorkspaceListAppsResponse{}, xerrors.Errorf("failed to find workspace: %w", err)
		}

		var res WorkspaceListAppsResponse
		for _, app := range workspaceAgent.Apps {
			name := app.DisplayName
			if name == "" {
				name = app.Slug
			}
			res.Apps = append(res.Apps, WorkspaceListApp{
				Name: name,
				URL:  app.URL,
			})
		}

		return res, nil
	},
}
View Source
var WorkspacePortForward = Tool[WorkspacePortForwardArgs, WorkspacePortForwardResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspacePortForward,
		Description: `Fetch URLs that forward to the specified port.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"port": map[string]any{
					"type":        "number",
					"description": "The port to forward.",
				},
			},
			Required: []string{"workspace", "port"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspacePortForwardArgs) (WorkspacePortForwardResponse, error) {
		workspaceName := NormalizeWorkspaceInput(args.Workspace)
		workspace, workspaceAgent, err := findWorkspaceAndAgent(ctx, deps.coderClient, workspaceName)
		if err != nil {
			return WorkspacePortForwardResponse{}, xerrors.Errorf("failed to find workspace: %w", err)
		}
		res, err := deps.coderClient.AppHost(ctx)
		if err != nil {
			return WorkspacePortForwardResponse{}, xerrors.Errorf("failed to get app host: %w", err)
		}
		if res.Host == "" {
			return WorkspacePortForwardResponse{}, xerrors.New("no app host for forwarding has been configured")
		}
		url := appurl.ApplicationURL{
			AppSlugOrPort: strconv.Itoa(args.Port),
			AgentName:     workspaceAgent.Name,
			WorkspaceName: workspace.Name,
			Username:      workspace.OwnerName,
		}
		return WorkspacePortForwardResponse{
			URL: deps.coderClient.URL.Scheme + "://" + strings.Replace(res.Host, "*", url.String(), 1),
		}, nil
	},
}
View Source
var WorkspaceReadFile = Tool[WorkspaceReadFileArgs, WorkspaceReadFileResponse]{
	Tool: aisdk.Tool{
		Name:        ToolNameWorkspaceReadFile,
		Description: `Read from a file in a workspace.`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"path": map[string]any{
					"type":        "string",
					"description": "The absolute path of the file to read in the workspace.",
				},
				"offset": map[string]any{
					"type":        "integer",
					"description": "A byte offset indicating where in the file to start reading. Defaults to zero. An empty string indicates the end of the file has been reached.",
				},
				"limit": map[string]any{
					"type":        "integer",
					"description": "The number of bytes to read. Cannot exceed 1 MiB. Defaults to the full size of the file or 1 MiB, whichever is lower.",
				},
			},
			Required: []string{"path", "workspace"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceReadFileArgs) (WorkspaceReadFileResponse, error) {
		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return WorkspaceReadFileResponse{}, err
		}
		defer conn.Close()

		limit := args.Limit
		if limit == 0 {
			limit = maxFileLimit
		} else if limit > maxFileLimit {
			return WorkspaceReadFileResponse{}, xerrors.Errorf("limit must be %d or less, got %d", maxFileLimit, limit)
		}

		reader, mimeType, err := conn.ReadFile(ctx, args.Path, args.Offset, limit)
		if err != nil {
			return WorkspaceReadFileResponse{}, err
		}
		defer reader.Close()

		bs, err := io.ReadAll(reader)
		if err != nil {
			return WorkspaceReadFileResponse{}, xerrors.Errorf("read response body: %w", err)
		}

		return WorkspaceReadFileResponse{Content: bs, MimeType: mimeType}, nil
	},
}
View Source
var WorkspaceWriteFile = Tool[WorkspaceWriteFileArgs, codersdk.Response]{
	Tool: aisdk.Tool{
		Name: ToolNameWorkspaceWriteFile,
		Description: `Write a file in a workspace.

If a file write fails due to syntax errors or encoding issues, do NOT switch
to using bash commands as a workaround. Instead:

	1. Read the error message carefully to identify the issue
	2. Fix the content encoding/syntax
	3. Retry with this tool

The content parameter expects base64-encoded bytes. Ensure your source content
is correct before encoding it. If you encounter errors, decode and verify the
content you are trying to write, then re-encode it properly.
`,
		Schema: aisdk.Schema{
			Properties: map[string]any{
				"workspace": map[string]any{
					"type":        "string",
					"description": workspaceDescription,
				},
				"path": map[string]any{
					"type":        "string",
					"description": "The absolute path of the file to write in the workspace.",
				},
				"content": map[string]any{
					"type":        "string",
					"description": "The base64-encoded bytes to write to the file.",
				},
			},
			Required: []string{"path", "workspace", "content"},
		},
	},
	UserClientOptional: true,
	Handler: func(ctx context.Context, deps Deps, args WorkspaceWriteFileArgs) (codersdk.Response, error) {
		conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
		if err != nil {
			return codersdk.Response{}, err
		}
		defer conn.Close()

		reader := bytes.NewReader(args.Content)
		err = conn.WriteFile(ctx, args.Path, reader)
		if err != nil {
			return codersdk.Response{}, err
		}

		return codersdk.Response{
			Message: "File written successfully.",
		}, nil
	},
}

Functions

func NormalizeWorkspaceInput added in v2.25.0

func NormalizeWorkspaceInput(input string) string

NormalizeWorkspaceInput converts workspace name input to standard format. Handles the following input formats:

  • workspace → workspace
  • workspace.agent → workspace.agent
  • owner/workspace → owner/workspace
  • owner--workspace → owner/workspace
  • owner/workspace.agent → owner/workspace.agent
  • owner--workspace.agent → owner/workspace.agent
  • agent.workspace.owner → owner/workspace.agent (Coder Connect format)

func WithTaskReporter added in v2.24.0

func WithTaskReporter(fn func(ReportTaskArgs) error) func(*Deps)

Types

type CreateTaskArgs added in v2.28.0

type CreateTaskArgs struct {
	Input                   string `json:"input"`
	TemplateVersionID       string `json:"template_version_id"`
	TemplateVersionPresetID string `json:"template_version_preset_id"`
	User                    string `json:"user"`
}

type CreateTemplateArgs

type CreateTemplateArgs struct {
	Description string `json:"description"`
	DisplayName string `json:"display_name"`
	Icon        string `json:"icon"`
	Name        string `json:"name"`
	VersionID   string `json:"version_id"`
}

type CreateTemplateVersionArgs

type CreateTemplateVersionArgs struct {
	FileID     string `json:"file_id"`
	TemplateID string `json:"template_id"`
}

type CreateWorkspaceArgs

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

type CreateWorkspaceBuildArgs

type CreateWorkspaceBuildArgs struct {
	TemplateVersionID string `json:"template_version_id"`
	Transition        string `json:"transition"`
	WorkspaceID       string `json:"workspace_id"`
}

type DeleteTaskArgs added in v2.28.0

type DeleteTaskArgs struct {
	TaskID string `json:"task_id"`
}

type DeleteTemplateArgs

type DeleteTemplateArgs struct {
	TemplateID string `json:"template_id"`
}

type Deps

type Deps struct {
	// contains filtered or unexported fields
}

Deps provides access to tool dependencies.

func NewDeps

func NewDeps(client *codersdk.Client, opts ...func(*Deps)) (Deps, error)

func (Deps) ServerURL added in v2.26.0

func (d Deps) ServerURL() string

type FetchArgs added in v2.26.0

type FetchArgs struct {
	ID string `json:"id"`
}

type FetchResult added in v2.26.0

type FetchResult struct {
	ID       string            `json:"id"`
	Title    string            `json:"title"`
	Text     string            `json:"text"`
	URL      string            `json:"url"`
	Metadata map[string]string `json:"metadata,omitempty"`
}

type GenericHandlerFunc

type GenericHandlerFunc func(context.Context, Deps, json.RawMessage) (json.RawMessage, error)

GenericHandlerFunc is a function that handles a tool call.

func WithCleanContext

func WithCleanContext(h GenericHandlerFunc) GenericHandlerFunc

WithCleanContext wraps a HandlerFunc to provide it with a new context. This ensures that no data is passed using context.Value. If a deadline is set on the parent context, it will be passed to the child context.

func WithRecover

WithRecover wraps a HandlerFunc to recover from panics and return an error.

type GenericTool

type GenericTool struct {
	aisdk.Tool
	Handler GenericHandlerFunc

	// UserClientOptional indicates whether this tool can function without a valid
	// user authentication token. If true, the tool will be available even when
	// running in an unauthenticated mode with just an agent token.
	UserClientOptional bool
}

GenericTool is a type-erased wrapper for GenericTool. This allows referencing the tool without knowing the concrete argument or return type. The Handler function allows calling the tool with known types.

type GetTaskLogsArgs added in v2.28.0

type GetTaskLogsArgs struct {
	TaskID string `json:"task_id"`
}

type GetTaskStatusArgs added in v2.28.0

type GetTaskStatusArgs struct {
	TaskID string `json:"task_id"`
}

type GetTaskStatusResponse added in v2.28.0

type GetTaskStatusResponse struct {
	Status codersdk.TaskStatus      `json:"status"`
	State  *codersdk.TaskStateEntry `json:"state"`
}

type GetTemplateVersionLogsArgs

type GetTemplateVersionLogsArgs struct {
	TemplateVersionID string `json:"template_version_id"`
}

type GetWorkspaceAgentLogsArgs

type GetWorkspaceAgentLogsArgs struct {
	WorkspaceAgentID string `json:"workspace_agent_id"`
}

type GetWorkspaceArgs

type GetWorkspaceArgs struct {
	WorkspaceID string `json:"workspace_id"`
}

type GetWorkspaceBuildLogsArgs

type GetWorkspaceBuildLogsArgs struct {
	WorkspaceBuildID string `json:"workspace_build_id"`
}

type HandlerFunc

type HandlerFunc[Arg, Ret any] func(context.Context, Deps, Arg) (Ret, error)

HandlerFunc is a typed function that handles a tool call.

type ListTasksArgs added in v2.28.0

type ListTasksArgs struct {
	Status codersdk.TaskStatus `json:"status"`
	User   string              `json:"user"`
}

type ListTasksResponse added in v2.28.0

type ListTasksResponse struct {
	Tasks []codersdk.Task `json:"tasks"`
}

type ListTemplateVersionParametersArgs

type ListTemplateVersionParametersArgs struct {
	TemplateVersionID string `json:"template_version_id"`
}

type ListWorkspacesArgs

type ListWorkspacesArgs struct {
	Owner string `json:"owner"`
}

type MinimalTemplate

type MinimalTemplate struct {
	DisplayName     string    `json:"display_name"`
	ID              string    `json:"id"`
	Name            string    `json:"name"`
	Description     string    `json:"description"`
	ActiveVersionID uuid.UUID `json:"active_version_id"`
	ActiveUserCount int       `json:"active_user_count"`
}

type MinimalWorkspace

type MinimalWorkspace struct {
	ID                      string    `json:"id"`
	Name                    string    `json:"name"`
	TemplateID              string    `json:"template_id"`
	TemplateName            string    `json:"template_name"`
	TemplateDisplayName     string    `json:"template_display_name"`
	TemplateIcon            string    `json:"template_icon"`
	TemplateActiveVersionID uuid.UUID `json:"template_active_version_id"`
	Outdated                bool      `json:"outdated"`
}

type NoArgs

type NoArgs struct{}

NoArgs just represents an empty argument struct.

type ObjectID added in v2.26.0

type ObjectID struct {
	Type ObjectType
	ID   string
}

func (ObjectID) String added in v2.26.0

func (o ObjectID) String() string

type ObjectType added in v2.26.0

type ObjectType string
const (
	ObjectTypeTemplate  ObjectType = "template"
	ObjectTypeWorkspace ObjectType = "workspace"
)

type ReportTaskArgs

type ReportTaskArgs struct {
	Link    string `json:"link"`
	State   string `json:"state"`
	Summary string `json:"summary"`
}

type SearchArgs added in v2.26.0

type SearchArgs struct {
	Query string `json:"query"`
}

type SearchQuery added in v2.26.0

type SearchQuery struct {
	Type  SearchQueryType
	Query string
}

type SearchQueryType added in v2.26.0

type SearchQueryType string
const (
	SearchQueryTypeTemplates  SearchQueryType = "templates"
	SearchQueryTypeWorkspaces SearchQueryType = "workspaces"
)

type SearchResult added in v2.26.0

type SearchResult struct {
	Results []SearchResultItem `json:"results"`
}

type SearchResultItem added in v2.26.0

type SearchResultItem struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Text  string `json:"text"`
	URL   string `json:"url"`
}

type SendTaskInputArgs added in v2.28.0

type SendTaskInputArgs struct {
	TaskID string `json:"task_id"`
	Input  string `json:"input"`
}

type Tool

type Tool[Arg, Ret any] struct {
	aisdk.Tool
	Handler HandlerFunc[Arg, Ret]

	// UserClientOptional indicates whether this tool can function without a valid
	// user authentication token. If true, the tool will be available even when
	// running in an unauthenticated mode with just an agent token.
	UserClientOptional bool
}

Tool consists of an aisdk.Tool and a corresponding typed handler function.

func (Tool[Arg, Ret]) Generic

func (t Tool[Arg, Ret]) Generic() GenericTool

Generic returns a type-erased version of a TypedTool where the arguments and return values are converted to/from json.RawMessage. This allows the tool to be referenced without knowing the concrete arguments or return values. The original TypedHandlerFunc is wrapped to handle type conversion.

type UpdateTemplateActiveVersionArgs

type UpdateTemplateActiveVersionArgs struct {
	TemplateID        string `json:"template_id"`
	TemplateVersionID string `json:"template_version_id"`
}

type UploadTarFileArgs

type UploadTarFileArgs struct {
	Files map[string]string `json:"files"`
}

type WorkspaceBashArgs added in v2.25.0

type WorkspaceBashArgs struct {
	Workspace  string `json:"workspace"`
	Command    string `json:"command"`
	TimeoutMs  int    `json:"timeout_ms,omitempty"`
	Background bool   `json:"background,omitempty"`
}

type WorkspaceBashResult added in v2.25.0

type WorkspaceBashResult struct {
	Output   string `json:"output"`
	ExitCode int    `json:"exit_code"`
}

type WorkspaceEditFileArgs added in v2.27.0

type WorkspaceEditFileArgs struct {
	Workspace string                  `json:"workspace"`
	Path      string                  `json:"path"`
	Edits     []workspacesdk.FileEdit `json:"edits"`
}

type WorkspaceEditFilesArgs added in v2.27.0

type WorkspaceEditFilesArgs struct {
	Workspace string                   `json:"workspace"`
	Files     []workspacesdk.FileEdits `json:"files"`
}

type WorkspaceLSArgs added in v2.27.0

type WorkspaceLSArgs struct {
	Workspace string `json:"workspace"`
	Path      string `json:"path"`
}

type WorkspaceLSFile added in v2.27.0

type WorkspaceLSFile struct {
	Path  string `json:"path"`
	IsDir bool   `json:"is_dir"`
}

type WorkspaceLSResponse added in v2.27.0

type WorkspaceLSResponse struct {
	Contents []WorkspaceLSFile `json:"contents"`
}

type WorkspaceListApp added in v2.28.0

type WorkspaceListApp struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

type WorkspaceListAppsArgs added in v2.28.0

type WorkspaceListAppsArgs struct {
	Workspace string `json:"workspace"`
}

type WorkspaceListAppsResponse added in v2.28.0

type WorkspaceListAppsResponse struct {
	Apps []WorkspaceListApp `json:"apps"`
}

type WorkspacePortForwardArgs added in v2.27.0

type WorkspacePortForwardArgs struct {
	Workspace string `json:"workspace"`
	Port      int    `json:"port"`
}

type WorkspacePortForwardResponse added in v2.27.0

type WorkspacePortForwardResponse struct {
	URL string `json:"url"`
}

type WorkspaceReadFileArgs added in v2.27.0

type WorkspaceReadFileArgs struct {
	Workspace string `json:"workspace"`
	Path      string `json:"path"`
	Offset    int64  `json:"offset"`
	Limit     int64  `json:"limit"`
}

type WorkspaceReadFileResponse added in v2.27.0

type WorkspaceReadFileResponse struct {
	// Content is the base64-encoded bytes from the file.
	Content  []byte `json:"content"`
	MimeType string `json:"mimeType"`
}

type WorkspaceWriteFileArgs added in v2.27.0

type WorkspaceWriteFileArgs struct {
	Workspace string `json:"workspace"`
	Path      string `json:"path"`
	Content   []byte `json:"content"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL