Skip to content

Commit 949e85c

Browse files
authored
feat(core): differentiate User-Agent for a2a-server and ACP clients (#22059)
1 parent f090736 commit 949e85c

12 files changed

Lines changed: 277 additions & 4 deletions

File tree

docs/cli/telemetry.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Environment variables can override these settings.
4545
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | Include prompts in telemetry logs | `true`/`false` | `true` |
4646
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | Use external OTLP collector (advanced) | `true`/`false` | `false` |
4747
| `useCliAuth` | `GEMINI_TELEMETRY_USE_CLI_AUTH` | Use CLI credentials for telemetry (GCP target only) | `true`/`false` | `false` |
48+
| - | `GEMINI_CLI_SURFACE` | Optional custom label for traffic reporting | string | - |
4849

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

220+
## Client identification
221+
222+
Gemini CLI includes identifiers in its `User-Agent` header to help you
223+
differentiate and report on API traffic from different environments (for
224+
example, identifying calls from Gemini Code Assist versus a standard terminal).
225+
226+
### Automatic identification
227+
228+
Most integrated environments are identified automatically without additional
229+
configuration. The identifier is included as a prefix to the `User-Agent` and as
230+
a "surface" tag in the parenthetical metadata.
231+
232+
| Environment | User-Agent Prefix | Surface Tag |
233+
| :---------------------------------- | :--------------------------- | :---------- |
234+
| **Gemini Code Assist (Agent Mode)** | `GeminiCLI-a2a-server` | `vscode` |
235+
| **Zed (via ACP)** | `GeminiCLI-acp-zed` | `zed` |
236+
| **XCode (via ACP)** | `GeminiCLI-acp-xcode` | `xcode` |
237+
| **IntelliJ IDEA (via ACP)** | `GeminiCLI-acp-intellijidea` | `jetbrains` |
238+
| **Standard Terminal** | `GeminiCLI` | `terminal` |
239+
240+
**Example User-Agent:**
241+
`GeminiCLI-a2a-server/0.34.0/gemini-pro (linux; x64; vscode)`
242+
243+
### Custom identification
244+
245+
You can provide a custom identifier for your own scripts or automation by
246+
setting the `GEMINI_CLI_SURFACE` environment variable. This is useful for
247+
tracking specific internal tools or distribution channels in your GCP logs.
248+
249+
**macOS/Linux**
250+
251+
```bash
252+
export GEMINI_CLI_SURFACE="my-custom-tool"
253+
```
254+
255+
**Windows (PowerShell)**
256+
257+
```powershell
258+
$env:GEMINI_CLI_SURFACE="my-custom-tool"
259+
```
260+
261+
When set, the value appears at the end of the `User-Agent` parenthetical:
262+
`GeminiCLI/0.34.0/gemini-pro (linux; x64; my-custom-tool)`
263+
219264
## Logs, metrics, and traces
220265

221266
This section describes the structure of logs, metrics, and traces generated by

docs/reference/configuration.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,13 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
13841384
- Useful for shared compute environments or keeping CLI state isolated.
13851385
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
13861386
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
1387+
- **`GEMINI_CLI_SURFACE`**:
1388+
- Specifies a custom label to include in the `User-Agent` header for API
1389+
traffic reporting.
1390+
- This is useful for tracking specific internal tools or distribution
1391+
channels.
1392+
- Example: `export GEMINI_CLI_SURFACE="my-custom-tool"` (Windows PowerShell:
1393+
`$env:GEMINI_CLI_SURFACE="my-custom-tool"`)
13871394
- **`GOOGLE_API_KEY`**:
13881395
- Your Google Cloud API key.
13891396
- Required for using Vertex AI in express mode.

packages/a2a-server/src/config/config.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ describe('loadConfig', () => {
9191
expect(fetchAdminControlsOnce).not.toHaveBeenCalled();
9292
});
9393

94+
it('should pass clientName as a2a-server to Config', async () => {
95+
await loadConfig(mockSettings, mockExtensionLoader, taskId);
96+
expect(Config).toHaveBeenCalledWith(
97+
expect.objectContaining({
98+
clientName: 'a2a-server',
99+
}),
100+
);
101+
});
102+
94103
describe('when admin controls experiment is enabled', () => {
95104
beforeEach(() => {
96105
// We need to cast to any here to modify the mock implementation

packages/a2a-server/src/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export async function loadConfig(
6262

6363
const configParams: ConfigParameters = {
6464
sessionId: taskId,
65+
clientName: 'a2a-server',
6566
model: PREVIEW_GEMINI_MODEL,
6667
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
6768
sandbox: undefined, // Sandbox might not be relevant for a server-side agent

packages/cli/src/config/config.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3616,3 +3616,54 @@ describe('loadCliConfig mcpEnabled', () => {
36163616
});
36173617
});
36183618
});
3619+
3620+
describe('loadCliConfig acpMode and clientName', () => {
3621+
beforeEach(() => {
3622+
vi.resetAllMocks();
3623+
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
3624+
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
3625+
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
3626+
});
3627+
3628+
afterEach(() => {
3629+
vi.unstubAllEnvs();
3630+
});
3631+
3632+
it('should set acpMode to true and detect clientName when --acp flag is used', async () => {
3633+
process.argv = ['node', 'script.js', '--acp'];
3634+
vi.stubEnv('TERM_PROGRAM', 'vscode');
3635+
const argv = await parseArguments(createTestMergedSettings());
3636+
const config = await loadCliConfig(
3637+
createTestMergedSettings(),
3638+
'test-session',
3639+
argv,
3640+
);
3641+
expect(config.getAcpMode()).toBe(true);
3642+
expect(config.getClientName()).toBe('acp-vscode');
3643+
});
3644+
3645+
it('should set acpMode to true but leave clientName undefined for generic terminals', async () => {
3646+
process.argv = ['node', 'script.js', '--acp'];
3647+
vi.stubEnv('TERM_PROGRAM', 'iTerm.app'); // Generic terminal
3648+
const argv = await parseArguments(createTestMergedSettings());
3649+
const config = await loadCliConfig(
3650+
createTestMergedSettings(),
3651+
'test-session',
3652+
argv,
3653+
);
3654+
expect(config.getAcpMode()).toBe(true);
3655+
expect(config.getClientName()).toBeUndefined();
3656+
});
3657+
3658+
it('should set acpMode to false and clientName to undefined by default', async () => {
3659+
process.argv = ['node', 'script.js'];
3660+
const argv = await parseArguments(createTestMergedSettings());
3661+
const config = await loadCliConfig(
3662+
createTestMergedSettings(),
3663+
'test-session',
3664+
argv,
3665+
);
3666+
expect(config.getAcpMode()).toBe(false);
3667+
expect(config.getClientName()).toBeUndefined();
3668+
});
3669+
});

packages/cli/src/config/config.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
type HookDefinition,
4141
type HookEventName,
4242
type OutputFormat,
43+
detectIdeFromEnv,
4344
} from '@google/gemini-cli-core';
4445
import {
4546
type Settings,
@@ -710,8 +711,21 @@ export async function loadCliConfig(
710711
}
711712
}
712713

714+
const isAcpMode = !!argv.acp || !!argv.experimentalAcp;
715+
let clientName: string | undefined = undefined;
716+
if (isAcpMode) {
717+
const ide = detectIdeFromEnv();
718+
if (
719+
ide &&
720+
(ide.name !== 'vscode' || process.env['TERM_PROGRAM'] === 'vscode')
721+
) {
722+
clientName = `acp-${ide.name}`;
723+
}
724+
}
725+
713726
return new Config({
714-
acpMode: !!argv.acp || !!argv.experimentalAcp,
727+
acpMode: isAcpMode,
728+
clientName,
715729
sessionId,
716730
clientVersion: await getVersion(),
717731
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,

packages/core/src/config/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ export interface PolicyUpdateConfirmationRequest {
502502

503503
export interface ConfigParameters {
504504
sessionId: string;
505+
clientName?: string;
505506
clientVersion?: string;
506507
embeddingModel?: string;
507508
sandbox?: SandboxConfig;
@@ -646,6 +647,7 @@ export class Config implements McpContext, AgentLoopContext {
646647
private readonly acknowledgedAgentsService: AcknowledgedAgentsService;
647648
private skillManager!: SkillManager;
648649
private _sessionId: string;
650+
private readonly clientName: string | undefined;
649651
private clientVersion: string;
650652
private fileSystemService: FileSystemService;
651653
private trackerService?: TrackerService;
@@ -843,6 +845,7 @@ export class Config implements McpContext, AgentLoopContext {
843845

844846
constructor(params: ConfigParameters) {
845847
this._sessionId = params.sessionId;
848+
this.clientName = params.clientName;
846849
this.clientVersion = params.clientVersion ?? 'unknown';
847850
this.approvedPlanPath = undefined;
848851
this.embeddingModel =
@@ -1408,6 +1411,10 @@ export class Config implements McpContext, AgentLoopContext {
14081411
return this.promptId;
14091412
}
14101413

1414+
getClientName(): string | undefined {
1415+
return this.clientName;
1416+
}
1417+
14111418
setSessionId(sessionId: string): void {
14121419
this._sessionId = sessionId;
14131420
}

0 commit comments

Comments
 (0)