From be5a1b9434d88e2d310a4014a2abf0bb034614e9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Jun 2025 09:13:51 +0000 Subject: [PATCH] feat(agent/agentcontainers): add more envs to readconfig for app URL building These additions will allow us to craft URLs for e.g. Cursor. ```json { "customizations": { "coder": { "apps": [ { "slug": "cursor", "displayName": "Cursor Desktop", "url": "cursor://coder.coder-remote/openDevContainer?owner=${localEnv:CODER_WORKSPACE_OWNER_NAME}&workspace=${localEnv:CODER_WORKSPACE_NAME}&agent=${localEnv:CODER_WORKSPACE_PARENT_AGENT_NAME:dev}&url=${localEnv:CODER_URL}&token=$SESSION_TOKEN&devContainerName=${localEnv:CONTAINER_ID}&devContainerFolder=${containerWorkspaceFolder}", "external": true, "icon": "/icon/cursor.svg", "order": 1 } ] } } } ``` --- agent/agent.go | 2 +- agent/agentcontainers/api.go | 6 +++++- agent/agentcontainers/api_test.go | 12 ++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 8d3766dae89ad..018b4e0ac29d0 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1166,7 +1166,7 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, ) if a.devcontainers { a.containerAPI.Init( - agentcontainers.WithManifestInfo(manifest.OwnerName, manifest.WorkspaceName), + agentcontainers.WithManifestInfo(manifest.OwnerName, manifest.WorkspaceName, manifest.AgentName), agentcontainers.WithDevcontainers(manifest.Devcontainers, scripts), agentcontainers.WithSubAgentClient(agentcontainers.NewSubAgentClientFromAPI(a.logger, aAPI)), ) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index fba5babce5003..d2b90216d2436 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -71,6 +71,7 @@ type API struct { ownerName string workspaceName string + parentAgent string mu sync.RWMutex closed bool @@ -188,10 +189,11 @@ func WithSubAgentEnv(env ...string) Option { // WithManifestInfo sets the owner name, and workspace name // for the sub-agent. -func WithManifestInfo(owner, workspace string) Option { +func WithManifestInfo(owner, workspace, parentAgent string) Option { return func(api *API) { api.ownerName = owner api.workspaceName = workspace + api.parentAgent = parentAgent } } @@ -1319,7 +1321,9 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s", subAgentConfig.Name), fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s", api.ownerName), fmt.Sprintf("CODER_WORKSPACE_NAME=%s", api.workspaceName), + fmt.Sprintf("CODER_WORKSPACE_PARENT_AGENT_NAME=%s", api.parentAgent), fmt.Sprintf("CODER_URL=%s", api.subAgentURL), + fmt.Sprintf("CONTAINER_ID=%s", container.ID), }...), ) } diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index f61f4c24bfc2b..1d2099b15a31c 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -1393,7 +1393,7 @@ func TestAPI(t *testing.T) { agentcontainers.WithSubAgentClient(fakeSAC), agentcontainers.WithSubAgentURL("test-subagent-url"), agentcontainers.WithDevcontainerCLI(fakeDCCLI), - agentcontainers.WithManifestInfo("test-user", "test-workspace"), + agentcontainers.WithManifestInfo("test-user", "test-workspace", "test-parent-agent"), ) api.Init() apiClose := func() { @@ -1415,7 +1415,9 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_WORKSPACE_PARENT_AGENT_NAME=test-parent-agent") assert.Contains(t, envs, "CODER_URL=test-subagent-url") + assert.Contains(t, envs, "CONTAINER_ID=test-container-id") return nil }) @@ -1557,7 +1559,9 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_WORKSPACE_PARENT_AGENT_NAME=test-parent-agent") assert.Contains(t, envs, "CODER_URL=test-subagent-url") + assert.NotContains(t, envs, "CONTAINER_ID=test-container-id") return nil }) @@ -2134,7 +2138,7 @@ func TestAPI(t *testing.T) { agentcontainers.WithSubAgentClient(fSAC), agentcontainers.WithSubAgentURL("test-subagent-url"), agentcontainers.WithWatcher(watcher.NewNoop()), - agentcontainers.WithManifestInfo("test-user", "test-workspace"), + agentcontainers.WithManifestInfo("test-user", "test-workspace", "test-parent-agent"), ) api.Init() defer api.Close() @@ -2150,7 +2154,9 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_WORKSPACE_PARENT_AGENT_NAME=test-parent-agent") assert.Contains(t, envs, "CODER_URL=test-subagent-url") + assert.Contains(t, envs, "CONTAINER_ID=test-container-id") // First call should not have feature envs. assert.NotContains(t, envs, "FEATURE_CODE_SERVER_OPTION_PORT=9090") assert.NotContains(t, envs, "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false") @@ -2161,7 +2167,9 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_WORKSPACE_PARENT_AGENT_NAME=test-parent-agent") assert.Contains(t, envs, "CODER_URL=test-subagent-url") + assert.Contains(t, envs, "CONTAINER_ID=test-container-id") // Second call should have feature envs from the first config read. assert.Contains(t, envs, "FEATURE_CODE_SERVER_OPTION_PORT=9090") assert.Contains(t, envs, "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false")