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
45 changes: 45 additions & 0 deletions docs/cli/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Environment variables can override these settings.
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | Include prompts in telemetry logs | `true`/`false` | `true` |
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | Use external OTLP collector (advanced) | `true`/`false` | `false` |
| `useCliAuth` | `GEMINI_TELEMETRY_USE_CLI_AUTH` | Use CLI credentials for telemetry (GCP target only) | `true`/`false` | `false` |
| - | `GEMINI_CLI_SURFACE` | Optional custom label for traffic reporting | string | - |

**Note on boolean environment variables:** For boolean settings like `enabled`,
setting the environment variable to `true` or `1` enables the feature.
Expand Down Expand Up @@ -216,6 +217,50 @@ recommend using file-based output for local development.
For advanced local telemetry setups (such as Jaeger or Genkit), see the
[Local development guide](../local-development.md#viewing-traces).

## Client identification

Gemini CLI includes identifiers in its `User-Agent` header to help you
differentiate and report on API traffic from different environments (for
example, identifying calls from Gemini Code Assist versus a standard terminal).

### Automatic identification

Most integrated environments are identified automatically without additional
configuration. The identifier is included as a prefix to the `User-Agent` and as
a "surface" tag in the parenthetical metadata.

| Environment | User-Agent Prefix | Surface Tag |
| :---------------------------------- | :--------------------------- | :---------- |
| **Gemini Code Assist (Agent Mode)** | `GeminiCLI-a2a-server` | `vscode` |
| **Zed (via ACP)** | `GeminiCLI-acp-zed` | `zed` |
| **XCode (via ACP)** | `GeminiCLI-acp-xcode` | `xcode` |
| **IntelliJ IDEA (via ACP)** | `GeminiCLI-acp-intellijidea` | `jetbrains` |
| **Standard Terminal** | `GeminiCLI` | `terminal` |

**Example User-Agent:**
`GeminiCLI-a2a-server/0.34.0/gemini-pro (linux; x64; vscode)`

### Custom identification

You can provide a custom identifier for your own scripts or automation by
setting the `GEMINI_CLI_SURFACE` environment variable. This is useful for
tracking specific internal tools or distribution channels in your GCP logs.

**macOS/Linux**

```bash
export GEMINI_CLI_SURFACE="my-custom-tool"
```

**Windows (PowerShell)**

```powershell
$env:GEMINI_CLI_SURFACE="my-custom-tool"
```

When set, the value appears at the end of the `User-Agent` parenthetical:
`GeminiCLI/0.34.0/gemini-pro (linux; x64; my-custom-tool)`

## Logs, metrics, and traces

This section describes the structure of logs, metrics, and traces generated by
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,13 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- Useful for shared compute environments or keeping CLI state isolated.
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
- **`GEMINI_CLI_SURFACE`**:
- Specifies a custom label to include in the `User-Agent` header for API
traffic reporting.
- This is useful for tracking specific internal tools or distribution
channels.
- Example: `export GEMINI_CLI_SURFACE="my-custom-tool"` (Windows PowerShell:
`$env:GEMINI_CLI_SURFACE="my-custom-tool"`)
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
Expand Down
9 changes: 9 additions & 0 deletions packages/a2a-server/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ describe('loadConfig', () => {
expect(fetchAdminControlsOnce).not.toHaveBeenCalled();
});

it('should pass clientName as a2a-server to Config', async () => {
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(Config).toHaveBeenCalledWith(
expect.objectContaining({
clientName: 'a2a-server',
}),
);
});

describe('when admin controls experiment is enabled', () => {
beforeEach(() => {
// We need to cast to any here to modify the mock implementation
Expand Down
1 change: 1 addition & 0 deletions packages/a2a-server/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export async function loadConfig(

const configParams: ConfigParameters = {
sessionId: taskId,
clientName: 'a2a-server',
model: PREVIEW_GEMINI_MODEL,
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
sandbox: undefined, // Sandbox might not be relevant for a server-side agent
Expand Down
51 changes: 51 additions & 0 deletions packages/cli/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3616,3 +3616,54 @@ describe('loadCliConfig mcpEnabled', () => {
});
});
});

describe('loadCliConfig acpMode and clientName', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('should set acpMode to true and detect clientName when --acp flag is used', async () => {
process.argv = ['node', 'script.js', '--acp'];
vi.stubEnv('TERM_PROGRAM', 'vscode');
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(true);
expect(config.getClientName()).toBe('acp-vscode');
});

it('should set acpMode to true but leave clientName undefined for generic terminals', async () => {
process.argv = ['node', 'script.js', '--acp'];
vi.stubEnv('TERM_PROGRAM', 'iTerm.app'); // Generic terminal
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(true);
expect(config.getClientName()).toBeUndefined();
});

it('should set acpMode to false and clientName to undefined by default', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const config = await loadCliConfig(
createTestMergedSettings(),
'test-session',
argv,
);
expect(config.getAcpMode()).toBe(false);
expect(config.getClientName()).toBeUndefined();
});
});
16 changes: 15 additions & 1 deletion packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
type HookDefinition,
type HookEventName,
type OutputFormat,
detectIdeFromEnv,
} from '@google/gemini-cli-core';
import {
type Settings,
Expand Down Expand Up @@ -710,8 +711,21 @@ export async function loadCliConfig(
}
}

const isAcpMode = !!argv.acp || !!argv.experimentalAcp;
let clientName: string | undefined = undefined;
if (isAcpMode) {
const ide = detectIdeFromEnv();
if (
ide &&
(ide.name !== 'vscode' || process.env['TERM_PROGRAM'] === 'vscode')
) {
clientName = `acp-${ide.name}`;
}
}

return new Config({
acpMode: !!argv.acp || !!argv.experimentalAcp,
acpMode: isAcpMode,
clientName,
sessionId,
clientVersion: await getVersion(),
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ export interface PolicyUpdateConfirmationRequest {

export interface ConfigParameters {
sessionId: string;
clientName?: string;
clientVersion?: string;
embeddingModel?: string;
sandbox?: SandboxConfig;
Expand Down Expand Up @@ -646,6 +647,7 @@ export class Config implements McpContext, AgentLoopContext {
private readonly acknowledgedAgentsService: AcknowledgedAgentsService;
private skillManager!: SkillManager;
private _sessionId: string;
private readonly clientName: string | undefined;
private clientVersion: string;
private fileSystemService: FileSystemService;
private trackerService?: TrackerService;
Expand Down Expand Up @@ -843,6 +845,7 @@ export class Config implements McpContext, AgentLoopContext {

constructor(params: ConfigParameters) {
this._sessionId = params.sessionId;
this.clientName = params.clientName;
this.clientVersion = params.clientVersion ?? 'unknown';
this.approvedPlanPath = undefined;
this.embeddingModel =
Expand Down Expand Up @@ -1408,6 +1411,10 @@ export class Config implements McpContext, AgentLoopContext {
return this.promptId;
}

getClientName(): string | undefined {
return this.clientName;
}

setSessionId(sessionId: string): void {
this._sessionId = sessionId;
}
Expand Down
Loading
Loading