From b1b9fef5230015a72a35305c102fb271b531f4bc Mon Sep 17 00:00:00 2001 From: Marco Barisione Date: Wed, 8 Apr 2026 16:51:26 +0100 Subject: [PATCH] Explain: track usage in telemetry and keyserver (when possible) --- explain/agents.py | 3 +++ explain/claude_agent.py | 1 + explain/copilot_cli_agent.py | 1 + explain/explain.py | 21 +++++++++++++++++++++ 4 files changed, 26 insertions(+) diff --git a/explain/agents.py b/explain/agents.py index f3335bc..e714cf9 100644 --- a/explain/agents.py +++ b/explain/agents.py @@ -25,6 +25,9 @@ class BaseAgent(ABC): name: ClassVar[str] program_name: ClassVar[str] display_name: ClassVar[str] + # Name reported to the keyserver for usage tracking. Override in subclasses if the + # agent name doesn't match the `SupportedAgent` enum in UDB. + usage_name: ClassVar[str | None] = None def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) diff --git a/explain/claude_agent.py b/explain/claude_agent.py index 5c8fc4e..c58aee1 100644 --- a/explain/claude_agent.py +++ b/explain/claude_agent.py @@ -26,6 +26,7 @@ class ClaudeAgent(BaseAgent): name: ClassVar[str] = "claude" program_name: ClassVar[str] = "claude" display_name: ClassVar[str] = "Claude Code" + usage_name: ClassVar[str] = "claude-code" @classmethod def find_binary(cls) -> Path | None: diff --git a/explain/copilot_cli_agent.py b/explain/copilot_cli_agent.py index 793800d..48bab18 100644 --- a/explain/copilot_cli_agent.py +++ b/explain/copilot_cli_agent.py @@ -28,6 +28,7 @@ class CopilotCLIAgent(BaseAgent): name: ClassVar[str] = "copilot" program_name: ClassVar[str] = "copilot" display_name: ClassVar[str] = "Copilot CLI" + usage_name: ClassVar[str] = "github-copilot-cli" async def _handle_messages(self, stdout: asyncio.StreamReader) -> str: """ diff --git a/explain/explain.py b/explain/explain.py index cd0b112..b4e8c86 100644 --- a/explain/explain.py +++ b/explain/explain.py @@ -81,6 +81,24 @@ def _validate_log_level(level: str) -> LogLevel: agent: BaseAgent | None = None """Agent instance for the current session.""" +_ai_usage_tracked_in_keyserver = False + + +def _track_ai_usage(udb: udb_base.Udb, usage_name: str) -> None: + """ + Track AI Gen 1 usage in telemetry and keyserver. + + Errors are silently ignored so that tracking failures never prevents the command from running. + """ + with contextlib.suppress(Exception): + udb.telemetry_session.data.licensing.used_licensable_features.ai_gen1 = True + + global _ai_usage_tracked_in_keyserver + with contextlib.suppress(Exception): + _ai_usage_tracked_in_keyserver = udb.keyclient_communicator.log_session_meta( + "ai_gen1", usage_name + ) + @contextlib.contextmanager def temporary_gdb_settings(udb: udb_base.Udb) -> Iterator[None]: @@ -971,6 +989,7 @@ def uexperimental__mcp__serve(udb: udb_base.Udb, args: Any) -> None: """ Start an MCP server for this UDB instance. """ + _track_ai_usage(udb, "mcp-serve") # We don't know the agent name when used as an MCP server. gateway = UdbMcpGateway(udb) with temporary_gdb_settings(udb): run_server(gateway, args.port) @@ -1052,6 +1071,8 @@ def explain(udb: udb_base.Udb, args: Any) -> None: if not event_loop: event_loop = asyncio.new_event_loop() + _track_ai_usage(udb, agent.usage_name or agent.name) + # Don't allow debuggee standard streams or user breakpoints, they will confuse the LLM. with temporary_gdb_settings(udb): explanation = event_loop.run_until_complete(explain_query(agent, gateway, why))