Skip to content
Merged
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
3 changes: 3 additions & 0 deletions explain/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions explain/claude_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions explain/copilot_cli_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down
21 changes: 21 additions & 0 deletions explain/explain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
Loading