Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .icons/poolside.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions registry/coder-labs/modules/pool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
display_name: Pool CLI
icon: ../../../../.icons/poolside.svg
description: Install and configure Poolside's Pool coding agent in your workspace.
verified: false
tags: [agent, poolside, pool, ai, ai-gateway]
---

# Pool CLI

Install and configure [Pool](https://docs.poolside.ai/cli/pool), Poolside's coding agent, in your workspace.

```tf
module "pool" {
source = "registry.coder.com/coder-labs/pool/coder"
version = "0.1.0"
agent_id = coder_agent.main.id

poolside_api_key = var.poolside_api_key
}
```

The module accepts Pool's EULA noninteractively during installation, installs the CLI in `~/.local/bin` by default, and makes `pool` available to Coder scripts and interactive shells.

## AI Gateway

[AI Gateway](https://coder.com/docs/ai-coder/ai-gateway) is a Premium Coder feature that provides centralized LLM proxy management. Requires Coder >= 2.30.0.

Pool supports OpenAI-compatible endpoints through `POOLSIDE_STANDALONE_BASE_URL`. Set `enable_ai_gateway = true` to configure the Coder AI Gateway endpoint and authenticate Pool with the workspace owner's Coder session token.

```tf
module "pool" {
source = "registry.coder.com/coder-labs/pool/coder"
version = "0.1.0"
agent_id = coder_agent.main.id

enable_ai_gateway = true
model = "gpt-5"
}
```

> [!CAUTION]
> `enable_ai_gateway = true` is mutually exclusive with `poolside_api_key` and `standalone_base_url`. AI Gateway supplies both the endpoint and authentication.

## OpenAI-compatible endpoints

Use `standalone_base_url` to configure another OpenAI-compatible proxy or local inference server. Provide `poolside_api_key` when that endpoint requires authentication; `model` selects the model if the endpoint does not provide a default.

```tf
module "pool" {
source = "registry.coder.com/coder-labs/pool/coder"
version = "0.1.0"
agent_id = coder_agent.main.id

poolside_api_key = var.gateway_api_key
standalone_base_url = "https://gateway.example.com/v1"
model = "my-coding-model"
}
```

For a Poolside deployment, use `poolside_api_url` to set `POOLSIDE_API_URL` instead.

## Existing installations

Set `install_pool = false` when Pool CLI is already present in your workspace image. Configure `pool_binary_path` if the binary is in a directory other than `~/.local/bin`.

## Serialize a downstream `coder_script` after installation

The `scripts` output is an ordered list of `coder exp sync` names created by the module.

```tf
resource "coder_script" "verify_pool" {
agent_id = coder_agent.main.id
display_name = "Verify Pool CLI"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -euo pipefail
trap 'coder exp sync complete verify-pool' EXIT
coder exp sync want verify-pool ${join(" ", module.pool.scripts)}
coder exp sync start verify-pool

pool --version
EOT
}
```

## Troubleshooting

The module's script logs are under `~/.coder-modules/coder-labs/pool/logs/`.

```bash
cat ~/.coder-modules/coder-labs/pool/logs/install.log
cat ~/.coder-modules/coder-labs/pool/logs/pre_install.log
cat ~/.coder-modules/coder-labs/pool/logs/post_install.log
```

## References

- [Pool CLI documentation](https://docs.poolside.ai/cli/pool)
- [Pool CLI installation](https://docs.poolside.ai/cli/install)
- [AI Gateway](https://coder.com/docs/ai-coder/ai-gateway)
159 changes: 159 additions & 0 deletions registry/coder-labs/modules/pool/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
terraform {
required_version = ">= 1.9"

required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
}
}
}

variable "agent_id" {
description = "The ID of a Coder agent."
type = string
}

data "coder_workspace" "me" {}

data "coder_workspace_owner" "me" {}

variable "icon" {
description = "The icon to use for the app."
type = string
default = "/icon/poolside.svg"
}

variable "pre_install_script" {
description = "Custom script to run before installing Pool CLI."
type = string
default = null
}

variable "post_install_script" {
description = "Custom script to run after installing Pool CLI."
type = string
default = null
}

variable "install_pool" {
description = "Whether to install Pool CLI. Set false when Pool is already installed in the workspace image."
type = bool
default = true
}

variable "pool_binary_path" {
description = "Directory containing the Pool CLI binary. The installer uses this directory when install_pool is true."
type = string
default = "$HOME/.local/bin"
}

variable "poolside_api_key" {
description = "Poolside API key passed to Pool CLI via POOLSIDE_API_KEY."
type = string
default = ""
sensitive = true
}

variable "poolside_api_url" {
description = "Optional Poolside deployment API URL passed to Pool CLI via POOLSIDE_API_URL."
type = string
default = ""
}

variable "standalone_base_url" {
description = "Optional OpenAI-compatible API base URL passed to Pool CLI via POOLSIDE_STANDALONE_BASE_URL. Use this for a non-Coder gateway or local inference server."
type = string
default = ""

validation {
condition = !(var.enable_ai_gateway && var.standalone_base_url != "")
error_message = "standalone_base_url cannot be provided when enable_ai_gateway is true."
}
}

variable "model" {
description = "Optional model passed to Pool CLI via POOLSIDE_STANDALONE_MODEL when using an OpenAI-compatible API or Coder AI Gateway."
type = string
default = ""
}

variable "enable_ai_gateway" {
description = "Use Coder AI Gateway through its OpenAI-compatible endpoint. https://coder.com/docs/ai-coder/ai-gateway"
type = bool
default = false

validation {
condition = !(var.enable_ai_gateway && var.poolside_api_key != "")
error_message = "poolside_api_key cannot be provided when enable_ai_gateway is true. AI Gateway automatically authenticates Pool CLI using Coder credentials."
}
}

resource "coder_env" "poolside_api_key" {
count = var.poolside_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_API_KEY"
value = var.poolside_api_key
}

resource "coder_env" "poolside_api_url" {
count = var.poolside_api_url != "" ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_API_URL"
value = var.poolside_api_url
}

resource "coder_env" "standalone_base_url" {
count = var.standalone_base_url != "" ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_STANDALONE_BASE_URL"
value = var.standalone_base_url
}

resource "coder_env" "model" {
count = var.model != "" ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_STANDALONE_MODEL"
value = var.model
}

# Pool CLI uses POOLSIDE_API_KEY for OpenAI-compatible API authentication.
# Coder AI Gateway accepts the workspace owner's session token as a bearer token.
resource "coder_env" "ai_gateway_session_token" {
count = var.enable_ai_gateway ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_API_KEY"
value = data.coder_workspace_owner.me.session_token
}

resource "coder_env" "ai_gateway_base_url" {
count = var.enable_ai_gateway ? 1 : 0
agent_id = var.agent_id
name = "POOLSIDE_STANDALONE_BASE_URL"
value = "${trimsuffix(data.coder_workspace.me.access_url, "/")}/api/v2/ai-gateway/openai/v1"
}

locals {
install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
ARG_INSTALL_POOL = tostring(var.install_pool)
ARG_POOL_BINARY_PATH = var.pool_binary_path
})
}

module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "0.0.1"

agent_id = var.agent_id
module_directory = "$HOME/.coder-modules/coder-labs/pool"
display_name_prefix = "Pool CLI"
icon = var.icon
pre_install_script = var.pre_install_script
install_script = local.install_script
post_install_script = var.post_install_script
}

output "scripts" {
description = "Ordered list of coder exp sync names produced by this module, in run order."
value = module.coder_utils.scripts
}
122 changes: 122 additions & 0 deletions registry/coder-labs/modules/pool/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
run "test_pool_defaults" {
command = plan

variables {
agent_id = "test-agent"
}

assert {
condition = var.install_pool == true
error_message = "install_pool should default to true"
}

assert {
condition = var.pool_binary_path == "$HOME/.local/bin"
error_message = "pool_binary_path should default to $HOME/.local/bin"
}
}

run "test_pool_with_api_key" {
command = plan

variables {
agent_id = "test-agent"
poolside_api_key = "test-key"
}

assert {
condition = coder_env.poolside_api_key[0].value == "test-key"
error_message = "POOLSIDE_API_KEY should use the supplied key"
}
}

run "test_standalone_endpoint" {
command = plan

variables {
agent_id = "test-agent"
standalone_base_url = "https://gateway.example.com/v1"
model = "test-model"
}

assert {
condition = coder_env.standalone_base_url[0].value == "https://gateway.example.com/v1"
error_message = "POOLSIDE_STANDALONE_BASE_URL should use the supplied endpoint"
}

assert {
condition = coder_env.model[0].value == "test-model"
error_message = "POOLSIDE_STANDALONE_MODEL should use the supplied model"
}
}

run "test_ai_gateway_enabled" {
command = plan

variables {
agent_id = "test-agent"
enable_ai_gateway = true
model = "gpt-5"
}

override_data {
target = data.coder_workspace_owner.me
values = {
session_token = "mock-session-token"
}
}

assert {
condition = coder_env.ai_gateway_session_token[0].value == data.coder_workspace_owner.me.session_token
error_message = "AI Gateway should use the workspace owner's session token"
}

assert {
condition = coder_env.ai_gateway_base_url[0].name == "POOLSIDE_STANDALONE_BASE_URL"
error_message = "AI Gateway should configure Pool's OpenAI-compatible endpoint"
}

assert {
condition = length(coder_env.poolside_api_key) == 0
error_message = "A direct Poolside API key should not be set when AI Gateway is enabled"
}
}

run "test_ai_gateway_rejects_api_key" {
command = plan

variables {
agent_id = "test-agent"
enable_ai_gateway = true
poolside_api_key = "test-key"
}

expect_failures = [var.enable_ai_gateway]
}

run "test_ai_gateway_rejects_standalone_endpoint" {
command = plan

variables {
agent_id = "test-agent"
enable_ai_gateway = true
standalone_base_url = "https://gateway.example.com/v1"
}

expect_failures = [var.standalone_base_url]
}

run "test_scripts_output" {
command = plan

variables {
agent_id = "test-agent"
pre_install_script = "echo pre"
post_install_script = "echo post"
}

assert {
condition = length(output.scripts) == 3
error_message = "scripts should include pre-install, install, and post-install scripts"
}
}
Loading
Loading