From 1730861b609220bc691cf33e4c0efee833ad0bc0 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Wed, 19 Aug 2026 11:20:23 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Detect=20when=20user=20is=20using?= =?UTF-8?q?=20agents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shortcake-Parent: main --- pyproject.toml | 1 + src/fastapi_cloud_cli/utils/api.py | 12 +++++++++++- tests/test_api_client.py | 26 ++++++++++++++++++++++++++ uv.lock | 11 +++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4e2572e9..c4b35205 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "sentry-sdk >= 2.20.0", "fastar >= 0.10.0", "detect-installer>=0.1.0", + "agent-detector>=1.1.0", ] [project.optional-dependencies] diff --git a/src/fastapi_cloud_cli/utils/api.py b/src/fastapi_cloud_cli/utils/api.py index ec021eff..439a96cc 100644 --- a/src/fastapi_cloud_cli/utils/api.py +++ b/src/fastapi_cloud_cli/utils/api.py @@ -14,6 +14,7 @@ import httpx import typer +from agent_detector import detect_agent from pydantic import BaseModel, Field, TypeAdapter, ValidationError from rich_toolkit.progress import Progress from typing_extensions import ParamSpec @@ -304,6 +305,15 @@ def get_http_error_hint(code: ErrorCode, *, auth_mode: AuthMode = "user") -> str return None +def _get_user_agent() -> str: + user_agent = f"fastapi-cloud-cli/{__version__}" + + if detection := detect_agent(minimum_confidence="high"): + user_agent = f"{user_agent} AI-Agent/{detection.agent}" + + return user_agent + + class APIClient(httpx.Client): auth_mode: AuthMode @@ -319,7 +329,7 @@ def __init__(self, use_deploy_token: bool = False) -> None: token = identity.user_token self.auth_mode = "user" - headers = {"User-Agent": f"fastapi-cloud-cli/{__version__}"} + headers = {"User-Agent": _get_user_agent()} if token: headers["Authorization"] = f"Bearer {token}" diff --git a/tests/test_api_client.py b/tests/test_api_client.py index bb3cf675..fa8a8271 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -4,8 +4,10 @@ import httpx import pytest import respx +from agent_detector import DetectionResult from httpx import Response +from fastapi_cloud_cli import __version__ from fastapi_cloud_cli.utils.api import ( STREAM_LOGS_MAX_RETRIES, APIClient, @@ -30,6 +32,30 @@ def deployment_id() -> str: return "test-deployment-123" +def test_api_client_reports_high_confidence_agent() -> None: + detection = DetectionResult("codex", "high", "environment", "CODEX_THREAD_ID") + + with ( + patch( + "fastapi_cloud_cli.utils.api.detect_agent", return_value=detection + ) as mock_detect_agent, + APIClient() as client, + ): + assert client.headers["User-Agent"] == ( + f"fastapi-cloud-cli/{__version__} AI-Agent/codex" + ) + + mock_detect_agent.assert_called_once_with(minimum_confidence="high") + + +def test_api_client_omits_unattributed_agent() -> None: + with ( + patch("fastapi_cloud_cli.utils.api.detect_agent", return_value=None), + APIClient() as client, + ): + assert client.headers["User-Agent"] == f"fastapi-cloud-cli/{__version__}" + + @pytest.fixture def logs_route(respx_mock: respx.MockRouter, deployment_id: str) -> respx.Route: return respx_mock.get(f"/deployments/{deployment_id}/build-logs") diff --git a/uv.lock b/uv.lock index 14a62420..67830857 100644 --- a/uv.lock +++ b/uv.lock @@ -8,6 +8,15 @@ resolution-markers = [ "python_full_version < '3.13'", ] +[[package]] +name = "agent-detector" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/51/2d6a4580f8a1ad26eca1f3849207e08d65a0df318188fe1a5b56cd868a1e/agent_detector-1.1.0.tar.gz", hash = "sha256:3428dc45713f93a115bd0b05c9dbbfdc72af70246064c07bd837a9a95cf016bc", size = 53720, upload-time = "2026-08-19T10:24:41.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/26/43b02c1cf72b12597dd7a3b49023918bded80edd733e00222f5a3ce42d85/agent_detector-1.1.0-py3-none-any.whl", hash = "sha256:c605af97afb894737dd798721b117d9bbc7aa491e02a676cd5916c79dc39a46d", size = 7966, upload-time = "2026-08-19T10:24:40.276Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -276,6 +285,7 @@ wheels = [ name = "fastapi-cloud-cli" source = { editable = "." } dependencies = [ + { name = "agent-detector" }, { name = "detect-installer" }, { name = "fastar" }, { name = "httpx" }, @@ -310,6 +320,7 @@ github-actions = [ [package.metadata] requires-dist = [ + { name = "agent-detector", specifier = ">=1.1.0" }, { name = "detect-installer", specifier = ">=0.1.0" }, { name = "fastar", specifier = ">=0.10.0" }, { name = "httpx", specifier = ">=0.27.0" },