Extend AI-agent detection in the User-Agent header (Roo Code, Duo Agent Platform, IDE-terminal context)

Summary

Expand DetectCodingAgent (internal/api/coding_agent.go) to recognise three additional sources so GitLab can attribute API traffic to a wider slice of the AI-coding-agent ecosystem.

Background

glab already appends a Coding-Agent/<name> token to the User-Agent header when it detects one of: Claude Code, Codex, Cursor, OpenCode, or a value passed via the universal AI_AGENT escape hatch. This shipped in commit 4508ae6b (first released in v1.98.0). A follow-up branch adds Gemini CLI in commit 47a925e1.

The ecosystem has grown since the original list. This issue tracks adding the next batch of detections without changing the existing UA format or wire protocol.

Proposed detections

Signal Env condition Returned name
Roo Code CLI ROO_CLI_RUNTIME == "1" roo-code
GitLab Duo Agent Platform AGENT_PLATFORM_GITLAB_VERSION != "" duo-agent-platform
Cursor terminal (fallback) TERM_PROGRAM == "cursor" cursor-terminal
Windsurf terminal (fallback) TERM_PROGRAM == "windsurf" (case-insensitive) windsurf-terminal
Zed terminal (fallback) TERM_PROGRAM == "zed" zed-terminal

Naming convention: the -terminal suffix

Explicit "an agent invoked glab" signals (like CLAUDECODE=1 or CURSOR_AGENT=1) stay as plain names. TERM_PROGRAM-based signals get a -terminal suffix because they can fire for human-typed commands inside those IDE terminals too — analytics can then split true agent invocations from "ran in an IDE terminal" context.

Precedence

DetectCodingAgent returns the first match. The order is:

  1. AI_AGENT (escape hatch, validated)
  2. Explicit agent env vars, alphabetical (existing behaviour preserved) — strong signals win
  3. TERM_PROGRAM-based fallback (new)

So CURSOR_AGENT=1 with TERM_PROGRAM=cursor still returns cursor, not cursor-terminal.

Files to change

  • internal/api/coding_agent.go — add the new branches in DetectCodingAgent
  • internal/api/coding_agent_test.go — table-driven cases for each new detection, the strong-signal-beats-terminal precedence, and add the three new env vars (ROO_CLI_RUNTIME, AGENT_PLATFORM_GITLAB_VERSION, TERM_PROGRAM) to the allAgentVars reset list

No other changes needed — BuildInfo.UserAgent() already plumbs whatever DetectCodingAgent() returns into the Coding-Agent/<name> token.

Acceptance criteria

  • go test ./internal/api/ -run TestDetectCodingAgent passes, including new cases
  • go test ./internal/api/... passes (no regression in other tests that read TERM_PROGRAM)
  • Manual: with each env var set in turn, GLAB_DEBUG_HTTP=1 glab auth status 2>&1 | grep -i user-agent shows the expected Coding-Agent/<name> token
  • Precedence sanity: CURSOR_AGENT=1 + TERM_PROGRAM=cursorCoding-Agent/cursor (not cursor-terminal)

Non-goals

  • Capturing agent version — keeping the existing name-only format. If useful later, can be done as a follow-up.
  • Switching to a separate HTTP header — staying with the existing Coding-Agent/<name> UA token.
  • Snowplow / product analytics instrumentation — out of scope here; only the UA header is changed.

Coordination

  • A separate branch add_gemini_cli_detection (commit 47a925e1) adds Gemini CLI (GEMINI_CLI=1gemini). This issue does not duplicate that work; whichever MR merges first wins, and the other rebases. The conflict is adjacent-line and trivial.
  • For the duo-agent-platform detection, AGENT_PLATFORM_GITLAB_VERSION is documented as Duo Agent Platform-specific in the execution-variables docs. Happy to swap it for a more explicit flag if the Duo team prefers something like a dedicated DUO_AGENT=1.

Help wanted: other agents we should detect

Anyone aware of a reliable, documented self-identification env var for any of these? Please drop a comment.

  • Aider
  • Cline
  • Goose
  • Jules
  • Continue CLI
  • Sourcegraph Cody / Amp

The bar is "the agent sets a deterministic env var when it shells out to subprocesses". Falling back to TERM_PROGRAM for IDE-hosted agents is also an option.