@terminai/core 0.26.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +247 -0
- package/README.md +422 -0
- package/dist/docs/CONTRIBUTING.md +8 -8
- package/dist/docs/changelogs/index.md +5 -5
- package/dist/docs/cli/commands.md +43 -17
- package/dist/docs/cli/quick-reference.md +61 -0
- package/dist/docs/cli/telemetry.md +3 -792
- package/dist/docs/extensions/index.md +1 -1
- package/dist/docs/get-started/configuration.md +6 -2
- package/dist/docs/index.md +17 -17
- package/dist/docs/{termai-comparison.md → terminai-comparison.md} +18 -18
- package/dist/docs/{termai-examples.md → terminai-examples.md} +2 -2
- package/dist/docs/{termai-operator-recipes.md → terminai-operator-recipes.md} +1 -1
- package/dist/docs/{termai-process-manager.md → terminai-process-manager.md} +7 -7
- package/dist/docs/{termai-quickstart.md → terminai-quickstart.md} +10 -10
- package/dist/docs/{termai-system.md → terminai-system.md} +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/src/auth/wizardSettings.js +3 -0
- package/dist/src/auth/wizardSettings.js.map +1 -1
- package/dist/src/auth/wizardSettings.test.js +3 -0
- package/dist/src/auth/wizardSettings.test.js.map +1 -1
- package/dist/src/brain/__tests__/advisors.test.js +25 -6
- package/dist/src/brain/__tests__/advisors.test.js.map +1 -1
- package/dist/src/brain/__tests__/cognitiveArchitecture.test.js +24 -0
- package/dist/src/brain/__tests__/cognitiveArchitecture.test.js.map +1 -1
- package/dist/src/brain/__tests__/thinkingOrchestrator.test.js +29 -5
- package/dist/src/brain/__tests__/thinkingOrchestrator.test.js.map +1 -1
- package/dist/src/config/builder.js +4 -1
- package/dist/src/config/builder.js.map +1 -1
- package/dist/src/config/settings/loader.d.ts +1 -0
- package/dist/src/config/settings/loader.js +10 -0
- package/dist/src/config/settings/loader.js.map +1 -1
- package/dist/src/config/settings/schema.d.ts +2 -5
- package/dist/src/config/settings/schema.js +2 -3
- package/dist/src/config/settings/schema.js.map +1 -1
- package/dist/src/core/client.test.js +0 -2
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +1 -1
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/mcp/token-storage/hybrid-token-storage.d.ts +3 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js +34 -13
- package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -1
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js +25 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js.map +1 -1
- package/dist/src/openai_chatgpt/imports.test.js +17 -1
- package/dist/src/openai_chatgpt/imports.test.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +1 -0
- package/dist/src/telemetry/config.js +3 -4
- package/dist/src/telemetry/config.js.map +1 -1
- package/dist/src/telemetry/config.test.js +6 -4
- package/dist/src/telemetry/config.test.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +0 -2
- package/dist/src/telemetry/index.js +0 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.js +0 -36
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +17 -1
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +24 -19
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +17 -135
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/tools/confirmation-policy.test.js +1 -1
- package/dist/src/tools/confirmation-policy.test.js.map +1 -1
- package/dist/src/utils/paths.js +7 -3
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/paths.test.js +31 -2
- package/dist/src/utils/paths.test.js.map +1 -1
- package/dist/src/utils/shell-permissions.test.js +4 -2
- package/dist/src/utils/shell-permissions.test.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +4 -2
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +0 -159
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +0 -1210
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +0 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +0 -20
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +0 -835
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +0 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +0 -140
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +0 -350
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +0 -1
- package/dist/src/telemetry/gcp-exporters.d.ts +0 -36
- package/dist/src/telemetry/gcp-exporters.js +0 -121
- package/dist/src/telemetry/gcp-exporters.js.map +0 -1
- package/dist/src/telemetry/gcp-exporters.test.d.ts +0 -7
- package/dist/src/telemetry/gcp-exporters.test.js +0 -319
- package/dist/src/telemetry/gcp-exporters.test.js.map +0 -1
- package/dist/src/telemetry/integration.test.circular.d.ts +0 -7
- package/dist/src/telemetry/integration.test.circular.js +0 -55
- package/dist/src/telemetry/integration.test.circular.js.map +0 -1
|
@@ -1,835 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* Portions Copyright 2025 TerminaI Authors
|
|
5
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
-
*/
|
|
7
|
-
import 'vitest';
|
|
8
|
-
import { vi, describe, it, expect, afterEach, beforeAll, afterAll, beforeEach, } from 'vitest';
|
|
9
|
-
import { ClearcutLogger, EventNames, TEST_ONLY } from './clearcut-logger.js';
|
|
10
|
-
import { AuthType } from '../../core/contentGenerator.js';
|
|
11
|
-
import { EventMetadataKey } from './event-metadata-key.js';
|
|
12
|
-
import { makeFakeConfig } from '../../test-utils/config.js';
|
|
13
|
-
import { http, HttpResponse } from 'msw';
|
|
14
|
-
import { server } from '../../mocks/msw.js';
|
|
15
|
-
import { UserPromptEvent, makeChatCompressionEvent, ModelRoutingEvent, ToolCallEvent, AgentStartEvent, AgentFinishEvent, WebFetchFallbackAttemptEvent, } from '../types.js';
|
|
16
|
-
import { AgentTerminateMode } from '../../agents/types.js';
|
|
17
|
-
import { GIT_COMMIT_INFO, CLI_VERSION } from '../../generated/git-commit.js';
|
|
18
|
-
import { UserAccountManager } from '../../utils/userAccountManager.js';
|
|
19
|
-
import { InstallationManager } from '../../utils/installationManager.js';
|
|
20
|
-
expect.extend({
|
|
21
|
-
toHaveEventName(received, name) {
|
|
22
|
-
const { isNot } = this;
|
|
23
|
-
const event = JSON.parse(received[0].source_extension_json);
|
|
24
|
-
const pass = event.event_name === name;
|
|
25
|
-
return {
|
|
26
|
-
pass,
|
|
27
|
-
message: () => `event name ${event.event_name} does${isNot ? ' not ' : ''} match ${name}}`,
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
toHaveMetadataValue(received, [key, value]) {
|
|
31
|
-
const event = JSON.parse(received[0].source_extension_json);
|
|
32
|
-
const metadata = event['event_metadata'][0];
|
|
33
|
-
const data = metadata.find((m) => m.gemini_cli_key === key)?.value;
|
|
34
|
-
const pass = data !== undefined && data === value;
|
|
35
|
-
return {
|
|
36
|
-
pass,
|
|
37
|
-
message: () => `event ${received} should have: ${value}. Found: ${data}`,
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
toHaveMetadataKey(received, key) {
|
|
41
|
-
const { isNot } = this;
|
|
42
|
-
const event = JSON.parse(received[0].source_extension_json);
|
|
43
|
-
const metadata = event['event_metadata'][0];
|
|
44
|
-
const pass = metadata.some((m) => m.gemini_cli_key === key);
|
|
45
|
-
return {
|
|
46
|
-
pass,
|
|
47
|
-
message: () => `event ${received} ${isNot ? 'has' : 'does not have'} the metadata key ${key}`,
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
vi.mock('../../utils/userAccountManager.js');
|
|
52
|
-
vi.mock('../../utils/installationManager.js');
|
|
53
|
-
const mockUserAccount = vi.mocked(UserAccountManager.prototype);
|
|
54
|
-
const mockInstallMgr = vi.mocked(InstallationManager.prototype);
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
// Ensure Antigravity detection doesn't interfere with other tests
|
|
57
|
-
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
|
|
58
|
-
});
|
|
59
|
-
// TODO(richieforeman): Consider moving this to test setup globally.
|
|
60
|
-
beforeAll(() => {
|
|
61
|
-
server.listen({});
|
|
62
|
-
});
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
server.resetHandlers();
|
|
65
|
-
});
|
|
66
|
-
afterAll(() => {
|
|
67
|
-
server.close();
|
|
68
|
-
});
|
|
69
|
-
describe.skip('ClearcutLogger', () => {
|
|
70
|
-
const NEXT_WAIT_MS = 1234;
|
|
71
|
-
const CLEARCUT_URL = 'https://play.googleapis.com/log';
|
|
72
|
-
const MOCK_DATE = new Date('2025-01-02T00:00:00.000Z');
|
|
73
|
-
const EXAMPLE_RESPONSE = `["${NEXT_WAIT_MS}",null,[[["ANDROID_BACKUP",0],["BATTERY_STATS",0],["SMART_SETUP",0],["TRON",0]],-3334737594024971225],[]]`;
|
|
74
|
-
// A helper to get the internal events array for testing
|
|
75
|
-
const getEvents = (l) => l['events'].toArray();
|
|
76
|
-
const getEventsSize = (l) => l['events'].size;
|
|
77
|
-
const requeueFailedEvents = (l, events) => l['requeueFailedEvents'](events);
|
|
78
|
-
afterEach(() => {
|
|
79
|
-
vi.unstubAllEnvs();
|
|
80
|
-
});
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
|
|
83
|
-
vi.stubEnv('TERM_PROGRAM', '');
|
|
84
|
-
vi.stubEnv('CURSOR_TRACE_ID', '');
|
|
85
|
-
vi.stubEnv('CODESPACES', '');
|
|
86
|
-
vi.stubEnv('VSCODE_IPC_HOOK_CLI', '');
|
|
87
|
-
vi.stubEnv('EDITOR_IN_CLOUD_SHELL', '');
|
|
88
|
-
vi.stubEnv('CLOUD_SHELL', '');
|
|
89
|
-
vi.stubEnv('TERM_PRODUCT', '');
|
|
90
|
-
vi.stubEnv('MONOSPACE_ENV', '');
|
|
91
|
-
vi.stubEnv('REPLIT_USER', '');
|
|
92
|
-
vi.stubEnv('__COG_BASHRC_SOURCED', '');
|
|
93
|
-
});
|
|
94
|
-
function setup({ config = {
|
|
95
|
-
experiments: {
|
|
96
|
-
experimentIds: [123, 456, 789],
|
|
97
|
-
},
|
|
98
|
-
}, lifetimeGoogleAccounts = 1, cachedGoogleAccount = 'test@google.com', } = {}) {
|
|
99
|
-
server.resetHandlers(http.post(CLEARCUT_URL, () => HttpResponse.text(EXAMPLE_RESPONSE)));
|
|
100
|
-
vi.useFakeTimers();
|
|
101
|
-
vi.setSystemTime(MOCK_DATE);
|
|
102
|
-
const loggerConfig = makeFakeConfig({
|
|
103
|
-
...config,
|
|
104
|
-
});
|
|
105
|
-
ClearcutLogger.clearInstance();
|
|
106
|
-
mockUserAccount.getCachedGoogleAccount.mockReturnValue(cachedGoogleAccount);
|
|
107
|
-
mockUserAccount.getLifetimeGoogleAccounts.mockReturnValue(lifetimeGoogleAccounts);
|
|
108
|
-
mockInstallMgr.getInstallationId = vi
|
|
109
|
-
.fn()
|
|
110
|
-
.mockReturnValue('test-installation-id');
|
|
111
|
-
const logger = ClearcutLogger.getInstance(loggerConfig);
|
|
112
|
-
return { logger, loggerConfig };
|
|
113
|
-
}
|
|
114
|
-
afterEach(() => {
|
|
115
|
-
ClearcutLogger.clearInstance();
|
|
116
|
-
vi.useRealTimers();
|
|
117
|
-
vi.restoreAllMocks();
|
|
118
|
-
});
|
|
119
|
-
describe('getInstance', () => {
|
|
120
|
-
it.each([
|
|
121
|
-
{ usageStatisticsEnabled: false, expectedValue: undefined },
|
|
122
|
-
{
|
|
123
|
-
usageStatisticsEnabled: true,
|
|
124
|
-
expectedValue: expect.any(ClearcutLogger),
|
|
125
|
-
},
|
|
126
|
-
])('returns an instance if usage statistics are enabled', ({ usageStatisticsEnabled, expectedValue }) => {
|
|
127
|
-
ClearcutLogger.clearInstance();
|
|
128
|
-
const { logger } = setup({
|
|
129
|
-
config: {
|
|
130
|
-
usageStatisticsEnabled,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
expect(logger).toEqual(expectedValue);
|
|
134
|
-
});
|
|
135
|
-
it('is a singleton', () => {
|
|
136
|
-
ClearcutLogger.clearInstance();
|
|
137
|
-
const { loggerConfig } = setup();
|
|
138
|
-
const logger1 = ClearcutLogger.getInstance(loggerConfig);
|
|
139
|
-
const logger2 = ClearcutLogger.getInstance(loggerConfig);
|
|
140
|
-
expect(logger1).toBe(logger2);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe('createLogEvent', () => {
|
|
144
|
-
it('logs the total number of google accounts', () => {
|
|
145
|
-
const { logger } = setup({
|
|
146
|
-
lifetimeGoogleAccounts: 9001,
|
|
147
|
-
});
|
|
148
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
149
|
-
expect(event?.event_metadata[0]).toContainEqual({
|
|
150
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GOOGLE_ACCOUNTS_COUNT,
|
|
151
|
-
value: '9001',
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
it('logs default metadata', () => {
|
|
155
|
-
// Define expected values
|
|
156
|
-
const session_id = 'my-session-id';
|
|
157
|
-
const auth_type = AuthType.USE_GEMINI;
|
|
158
|
-
const google_accounts = 123;
|
|
159
|
-
const surface = 'ide-1234';
|
|
160
|
-
const cli_version = CLI_VERSION;
|
|
161
|
-
const git_commit_hash = GIT_COMMIT_INFO;
|
|
162
|
-
const prompt_id = 'my-prompt-123';
|
|
163
|
-
// Setup logger with expected values
|
|
164
|
-
const { logger, loggerConfig } = setup({
|
|
165
|
-
lifetimeGoogleAccounts: google_accounts,
|
|
166
|
-
config: { sessionId: session_id },
|
|
167
|
-
});
|
|
168
|
-
vi.spyOn(loggerConfig, 'getContentGeneratorConfig').mockReturnValue({
|
|
169
|
-
authType: auth_type,
|
|
170
|
-
});
|
|
171
|
-
logger?.logNewPromptEvent(new UserPromptEvent(1, prompt_id)); // prompt_id == session_id before this
|
|
172
|
-
vi.stubEnv('SURFACE', surface);
|
|
173
|
-
// Create log event
|
|
174
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
175
|
-
// Ensure expected values exist
|
|
176
|
-
expect(event?.event_metadata[0]).toEqual(expect.arrayContaining([
|
|
177
|
-
{
|
|
178
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID,
|
|
179
|
-
value: session_id,
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_AUTH_TYPE,
|
|
183
|
-
value: JSON.stringify(auth_type),
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GOOGLE_ACCOUNTS_COUNT,
|
|
187
|
-
value: `${google_accounts}`,
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
|
|
191
|
-
value: surface,
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_VERSION,
|
|
195
|
-
value: cli_version,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GIT_COMMIT_HASH,
|
|
199
|
-
value: git_commit_hash,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
|
203
|
-
value: prompt_id,
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_OS,
|
|
207
|
-
value: process.platform,
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS,
|
|
211
|
-
value: logger?.getConfigJson(),
|
|
212
|
-
},
|
|
213
|
-
]));
|
|
214
|
-
});
|
|
215
|
-
it('logs the current nodejs version', () => {
|
|
216
|
-
const { logger } = setup({});
|
|
217
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
218
|
-
expect(event?.event_metadata[0]).toContainEqual({
|
|
219
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_NODE_VERSION,
|
|
220
|
-
value: process.versions.node,
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
it('logs all user settings', () => {
|
|
224
|
-
const { logger } = setup({
|
|
225
|
-
config: { useSmartEdit: true },
|
|
226
|
-
});
|
|
227
|
-
vi.stubEnv('TERM_PROGRAM', 'vscode');
|
|
228
|
-
vi.stubEnv('SURFACE', 'ide-1234');
|
|
229
|
-
const event = logger?.createLogEvent(EventNames.TOOL_CALL, []);
|
|
230
|
-
expect(event?.event_metadata[0]).toContainEqual({
|
|
231
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS,
|
|
232
|
-
value: logger?.getConfigJson(),
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
it.each([
|
|
236
|
-
{
|
|
237
|
-
name: 'github action',
|
|
238
|
-
env: { GITHUB_SHA: '8675309' },
|
|
239
|
-
expected: 'GitHub',
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
name: 'Cloud Shell via EDITOR_IN_CLOUD_SHELL',
|
|
243
|
-
env: { EDITOR_IN_CLOUD_SHELL: 'true' },
|
|
244
|
-
expected: 'cloudshell',
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: 'Cloud Shell via CLOUD_SHELL',
|
|
248
|
-
env: { CLOUD_SHELL: 'true' },
|
|
249
|
-
expected: 'cloudshell',
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
name: 'VSCode via TERM_PROGRAM',
|
|
253
|
-
env: {
|
|
254
|
-
TERM_PROGRAM: 'vscode',
|
|
255
|
-
GITHUB_SHA: undefined,
|
|
256
|
-
MONOSPACE_ENV: '',
|
|
257
|
-
},
|
|
258
|
-
expected: 'vscode',
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'SURFACE env var',
|
|
262
|
-
env: { SURFACE: 'ide-1234' },
|
|
263
|
-
expected: 'ide-1234',
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: 'SURFACE env var takes precedence',
|
|
267
|
-
env: { TERM_PROGRAM: 'vscode', SURFACE: 'ide-1234' },
|
|
268
|
-
expected: 'ide-1234',
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
name: 'Cursor',
|
|
272
|
-
env: {
|
|
273
|
-
CURSOR_TRACE_ID: 'abc123',
|
|
274
|
-
TERM_PROGRAM: 'vscode',
|
|
275
|
-
GITHUB_SHA: undefined,
|
|
276
|
-
},
|
|
277
|
-
expected: 'cursor',
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
name: 'Firebase Studio',
|
|
281
|
-
env: {
|
|
282
|
-
MONOSPACE_ENV: 'true',
|
|
283
|
-
TERM_PROGRAM: 'vscode',
|
|
284
|
-
GITHUB_SHA: undefined,
|
|
285
|
-
},
|
|
286
|
-
expected: 'firebasestudio',
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: 'Devin',
|
|
290
|
-
env: {
|
|
291
|
-
__COG_BASHRC_SOURCED: 'true',
|
|
292
|
-
TERM_PROGRAM: 'vscode',
|
|
293
|
-
GITHUB_SHA: undefined,
|
|
294
|
-
},
|
|
295
|
-
expected: 'devin',
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
name: 'unidentified',
|
|
299
|
-
env: {
|
|
300
|
-
GITHUB_SHA: undefined,
|
|
301
|
-
TERM_PROGRAM: undefined,
|
|
302
|
-
SURFACE: undefined,
|
|
303
|
-
},
|
|
304
|
-
expected: 'SURFACE_NOT_SET',
|
|
305
|
-
},
|
|
306
|
-
])('logs the current surface as $expected from $name', ({ env, expected }) => {
|
|
307
|
-
const { logger } = setup({});
|
|
308
|
-
for (const [key, value] of Object.entries(env)) {
|
|
309
|
-
vi.stubEnv(key, value);
|
|
310
|
-
}
|
|
311
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
312
|
-
expect(event?.event_metadata[0]).toContainEqual({
|
|
313
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
|
|
314
|
-
value: expected,
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
describe('GH_WORKFLOW_NAME metadata', () => {
|
|
319
|
-
it('includes workflow name when GH_WORKFLOW_NAME is set', () => {
|
|
320
|
-
const { logger } = setup({});
|
|
321
|
-
vi.stubEnv('GH_WORKFLOW_NAME', 'test-workflow');
|
|
322
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
323
|
-
expect(event?.event_metadata[0]).toContainEqual({
|
|
324
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GH_WORKFLOW_NAME,
|
|
325
|
-
value: 'test-workflow',
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
it('does not include workflow name when GH_WORKFLOW_NAME is not set', () => {
|
|
329
|
-
const { logger } = setup({});
|
|
330
|
-
vi.stubEnv('GH_WORKFLOW_NAME', undefined);
|
|
331
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
332
|
-
const hasWorkflowName = event?.event_metadata[0].some((item) => item.gemini_cli_key === EventMetadataKey.GEMINI_CLI_GH_WORKFLOW_NAME);
|
|
333
|
-
expect(hasWorkflowName).toBe(false);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
describe('GITHUB_REPOSITORY metadata', () => {
|
|
337
|
-
it('includes hashed repository when GITHUB_REPOSITORY is set', () => {
|
|
338
|
-
vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli');
|
|
339
|
-
const { logger } = setup({});
|
|
340
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
341
|
-
const repositoryMetadata = event?.event_metadata[0].find((item) => item.gemini_cli_key ===
|
|
342
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH);
|
|
343
|
-
expect(repositoryMetadata).toBeDefined();
|
|
344
|
-
expect(repositoryMetadata?.value).toMatch(/^[a-f0-9]{64}$/);
|
|
345
|
-
expect(repositoryMetadata?.value).not.toBe('google/gemini-cli');
|
|
346
|
-
});
|
|
347
|
-
it('hashes repository name consistently', () => {
|
|
348
|
-
vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli');
|
|
349
|
-
const { logger } = setup({});
|
|
350
|
-
const event1 = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
351
|
-
const event2 = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
352
|
-
const hash1 = event1?.event_metadata[0].find((item) => item.gemini_cli_key ===
|
|
353
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH)?.value;
|
|
354
|
-
const hash2 = event2?.event_metadata[0].find((item) => item.gemini_cli_key ===
|
|
355
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH)?.value;
|
|
356
|
-
expect(hash1).toBeDefined();
|
|
357
|
-
expect(hash2).toBeDefined();
|
|
358
|
-
expect(hash1).toBe(hash2);
|
|
359
|
-
});
|
|
360
|
-
it('produces different hashes for different repositories', () => {
|
|
361
|
-
vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli');
|
|
362
|
-
const { logger: logger1 } = setup({});
|
|
363
|
-
const event1 = logger1?.createLogEvent(EventNames.API_ERROR, []);
|
|
364
|
-
const hash1 = event1?.event_metadata[0].find((item) => item.gemini_cli_key ===
|
|
365
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH)?.value;
|
|
366
|
-
vi.stubEnv('GITHUB_REPOSITORY', 'google/other-repo');
|
|
367
|
-
ClearcutLogger.clearInstance();
|
|
368
|
-
const { logger: logger2 } = setup({});
|
|
369
|
-
const event2 = logger2?.createLogEvent(EventNames.API_ERROR, []);
|
|
370
|
-
const hash2 = event2?.event_metadata[0].find((item) => item.gemini_cli_key ===
|
|
371
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH)?.value;
|
|
372
|
-
expect(hash1).toBeDefined();
|
|
373
|
-
expect(hash2).toBeDefined();
|
|
374
|
-
expect(hash1).not.toBe(hash2);
|
|
375
|
-
});
|
|
376
|
-
it('does not include repository when GITHUB_REPOSITORY is not set', () => {
|
|
377
|
-
vi.stubEnv('GITHUB_REPOSITORY', undefined);
|
|
378
|
-
const { logger } = setup({});
|
|
379
|
-
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
|
|
380
|
-
const hasRepository = event?.event_metadata[0].some((item) => item.gemini_cli_key ===
|
|
381
|
-
EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH);
|
|
382
|
-
expect(hasRepository).toBe(false);
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
describe('logChatCompressionEvent', () => {
|
|
386
|
-
it('logs an event with proper fields', () => {
|
|
387
|
-
const { logger } = setup();
|
|
388
|
-
logger?.logChatCompressionEvent(makeChatCompressionEvent({
|
|
389
|
-
tokens_before: 9001,
|
|
390
|
-
tokens_after: 8000,
|
|
391
|
-
}));
|
|
392
|
-
const events = getEvents(logger);
|
|
393
|
-
expect(events.length).toBe(1);
|
|
394
|
-
expect(events[0]).toHaveEventName(EventNames.CHAT_COMPRESSION);
|
|
395
|
-
expect(events[0]).toHaveMetadataValue([
|
|
396
|
-
EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_BEFORE,
|
|
397
|
-
'9001',
|
|
398
|
-
]);
|
|
399
|
-
expect(events[0]).toHaveMetadataValue([
|
|
400
|
-
EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_AFTER,
|
|
401
|
-
'8000',
|
|
402
|
-
]);
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
describe('logRipgrepFallbackEvent', () => {
|
|
406
|
-
it('logs an event with the proper name', () => {
|
|
407
|
-
const { logger } = setup();
|
|
408
|
-
// Spy on flushToClearcut to prevent it from clearing the queue
|
|
409
|
-
const flushSpy = vi
|
|
410
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
411
|
-
.spyOn(logger, 'flushToClearcut')
|
|
412
|
-
.mockResolvedValue({ nextRequestWaitMs: 0 });
|
|
413
|
-
logger?.logRipgrepFallbackEvent();
|
|
414
|
-
const events = getEvents(logger);
|
|
415
|
-
expect(events.length).toBe(1);
|
|
416
|
-
expect(events[0]).toHaveEventName(EventNames.RIPGREP_FALLBACK);
|
|
417
|
-
expect(flushSpy).toHaveBeenCalledOnce();
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
describe('enqueueLogEvent', () => {
|
|
421
|
-
it('should add events to the queue', () => {
|
|
422
|
-
const { logger } = setup();
|
|
423
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
424
|
-
expect(getEventsSize(logger)).toBe(1);
|
|
425
|
-
});
|
|
426
|
-
it('should evict the oldest event when the queue is full', () => {
|
|
427
|
-
const { logger } = setup();
|
|
428
|
-
for (let i = 0; i < TEST_ONLY.MAX_EVENTS; i++) {
|
|
429
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR, [
|
|
430
|
-
{
|
|
431
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
432
|
-
value: `${i}`,
|
|
433
|
-
},
|
|
434
|
-
]));
|
|
435
|
-
}
|
|
436
|
-
let events = getEvents(logger);
|
|
437
|
-
expect(events.length).toBe(TEST_ONLY.MAX_EVENTS);
|
|
438
|
-
expect(events[0]).toHaveMetadataValue([
|
|
439
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
440
|
-
'0',
|
|
441
|
-
]);
|
|
442
|
-
// This should push out the first event
|
|
443
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR, [
|
|
444
|
-
{
|
|
445
|
-
gemini_cli_key: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
446
|
-
value: `${TEST_ONLY.MAX_EVENTS}`,
|
|
447
|
-
},
|
|
448
|
-
]));
|
|
449
|
-
events = getEvents(logger);
|
|
450
|
-
expect(events.length).toBe(TEST_ONLY.MAX_EVENTS);
|
|
451
|
-
expect(events[0]).toHaveMetadataValue([
|
|
452
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
453
|
-
'1',
|
|
454
|
-
]);
|
|
455
|
-
expect(events.at(TEST_ONLY.MAX_EVENTS - 1)).toHaveMetadataValue([
|
|
456
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
457
|
-
`${TEST_ONLY.MAX_EVENTS}`,
|
|
458
|
-
]);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
describe('flushToClearcut', () => {
|
|
462
|
-
it('allows for usage with a configured proxy agent', async () => {
|
|
463
|
-
const { logger } = setup({
|
|
464
|
-
config: {
|
|
465
|
-
proxy: 'http://mycoolproxy.whatever.com:3128',
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
469
|
-
const response = await logger.flushToClearcut();
|
|
470
|
-
expect(response.nextRequestWaitMs).toBe(NEXT_WAIT_MS);
|
|
471
|
-
});
|
|
472
|
-
it('should clear events on successful flush', async () => {
|
|
473
|
-
const { logger } = setup();
|
|
474
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
475
|
-
const response = await logger.flushToClearcut();
|
|
476
|
-
expect(getEvents(logger)).toEqual([]);
|
|
477
|
-
expect(response.nextRequestWaitMs).toBe(NEXT_WAIT_MS);
|
|
478
|
-
});
|
|
479
|
-
it('should handle a network error and requeue events', async () => {
|
|
480
|
-
const { logger } = setup();
|
|
481
|
-
server.resetHandlers(http.post(CLEARCUT_URL, () => HttpResponse.error()));
|
|
482
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_REQUEST));
|
|
483
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
484
|
-
expect(getEventsSize(logger)).toBe(2);
|
|
485
|
-
const x = logger.flushToClearcut();
|
|
486
|
-
await x;
|
|
487
|
-
expect(getEventsSize(logger)).toBe(2);
|
|
488
|
-
const events = getEvents(logger);
|
|
489
|
-
expect(events.length).toBe(2);
|
|
490
|
-
expect(events[0]).toHaveEventName(EventNames.API_REQUEST);
|
|
491
|
-
});
|
|
492
|
-
it('should handle an HTTP error and requeue events', async () => {
|
|
493
|
-
const { logger } = setup();
|
|
494
|
-
server.resetHandlers(http.post(CLEARCUT_URL, () => new HttpResponse({ 'the system is down': true }, {
|
|
495
|
-
status: 500,
|
|
496
|
-
})));
|
|
497
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_REQUEST));
|
|
498
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
499
|
-
expect(getEvents(logger).length).toBe(2);
|
|
500
|
-
await logger.flushToClearcut();
|
|
501
|
-
const events = getEvents(logger);
|
|
502
|
-
expect(events[0]).toHaveEventName(EventNames.API_REQUEST);
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
describe('requeueFailedEvents logic', () => {
|
|
506
|
-
it('should limit the number of requeued events to max_retry_events', () => {
|
|
507
|
-
const { logger } = setup();
|
|
508
|
-
const eventsToLogCount = TEST_ONLY.MAX_RETRY_EVENTS + 5;
|
|
509
|
-
const eventsToSend = [];
|
|
510
|
-
for (let i = 0; i < eventsToLogCount; i++) {
|
|
511
|
-
eventsToSend.push([
|
|
512
|
-
{
|
|
513
|
-
event_time_ms: Date.now(),
|
|
514
|
-
source_extension_json: JSON.stringify({ event_id: i }),
|
|
515
|
-
},
|
|
516
|
-
]);
|
|
517
|
-
}
|
|
518
|
-
requeueFailedEvents(logger, eventsToSend);
|
|
519
|
-
expect(getEventsSize(logger)).toBe(TEST_ONLY.MAX_RETRY_EVENTS);
|
|
520
|
-
const firstRequeuedEvent = JSON.parse(getEvents(logger)[0][0].source_extension_json);
|
|
521
|
-
// The last `maxRetryEvents` are kept. The oldest of those is at index `eventsToLogCount - maxRetryEvents`.
|
|
522
|
-
expect(firstRequeuedEvent.event_id).toBe(eventsToLogCount - TEST_ONLY.MAX_RETRY_EVENTS);
|
|
523
|
-
});
|
|
524
|
-
it('should not requeue more events than available space in the queue', () => {
|
|
525
|
-
const { logger } = setup();
|
|
526
|
-
const maxEvents = TEST_ONLY.MAX_EVENTS;
|
|
527
|
-
const spaceToLeave = 5;
|
|
528
|
-
const initialEventCount = maxEvents - spaceToLeave;
|
|
529
|
-
for (let i = 0; i < initialEventCount; i++) {
|
|
530
|
-
logger.enqueueLogEvent(logger.createLogEvent(EventNames.API_ERROR));
|
|
531
|
-
}
|
|
532
|
-
expect(getEventsSize(logger)).toBe(initialEventCount);
|
|
533
|
-
const failedEventsCount = 10; // More than spaceToLeave
|
|
534
|
-
const eventsToSend = [];
|
|
535
|
-
for (let i = 0; i < failedEventsCount; i++) {
|
|
536
|
-
eventsToSend.push([
|
|
537
|
-
{
|
|
538
|
-
event_time_ms: Date.now(),
|
|
539
|
-
source_extension_json: JSON.stringify({ event_id: `failed_${i}` }),
|
|
540
|
-
},
|
|
541
|
-
]);
|
|
542
|
-
}
|
|
543
|
-
requeueFailedEvents(logger, eventsToSend);
|
|
544
|
-
// availableSpace is 5. eventsToRequeue is min(10, 5) = 5.
|
|
545
|
-
// Total size should be initialEventCount + 5 = maxEvents.
|
|
546
|
-
expect(getEventsSize(logger)).toBe(maxEvents);
|
|
547
|
-
// The requeued events are the *last* 5 of the failed events.
|
|
548
|
-
// startIndex = max(0, 10 - 5) = 5.
|
|
549
|
-
// Loop unshifts events from index 9 down to 5.
|
|
550
|
-
// The first element in the deque is the one with id 'failed_5'.
|
|
551
|
-
const firstRequeuedEvent = JSON.parse(getEvents(logger)[0][0].source_extension_json);
|
|
552
|
-
expect(firstRequeuedEvent.event_id).toBe('failed_5');
|
|
553
|
-
});
|
|
554
|
-
});
|
|
555
|
-
describe('logModelRoutingEvent', () => {
|
|
556
|
-
it('logs a successful routing event', () => {
|
|
557
|
-
const { logger } = setup();
|
|
558
|
-
const event = new ModelRoutingEvent('gemini-pro', 'default-strategy', 123, 'some reasoning', false, undefined);
|
|
559
|
-
logger?.logModelRoutingEvent(event);
|
|
560
|
-
const events = getEvents(logger);
|
|
561
|
-
expect(events.length).toBe(1);
|
|
562
|
-
expect(events[0]).toHaveEventName(EventNames.MODEL_ROUTING);
|
|
563
|
-
expect(events[0]).toHaveMetadataValue([
|
|
564
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_DECISION,
|
|
565
|
-
'gemini-pro',
|
|
566
|
-
]);
|
|
567
|
-
expect(events[0]).toHaveMetadataValue([
|
|
568
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_DECISION_SOURCE,
|
|
569
|
-
'default-strategy',
|
|
570
|
-
]);
|
|
571
|
-
expect(events[0]).toHaveMetadataValue([
|
|
572
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_LATENCY_MS,
|
|
573
|
-
'123',
|
|
574
|
-
]);
|
|
575
|
-
expect(events[0]).toHaveMetadataValue([
|
|
576
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_FAILURE,
|
|
577
|
-
'false',
|
|
578
|
-
]);
|
|
579
|
-
});
|
|
580
|
-
it('logs a failed routing event with a reason', () => {
|
|
581
|
-
const { logger } = setup();
|
|
582
|
-
const event = new ModelRoutingEvent('gemini-pro', 'router-exception', 234, 'some reasoning', true, 'Something went wrong');
|
|
583
|
-
logger?.logModelRoutingEvent(event);
|
|
584
|
-
const events = getEvents(logger);
|
|
585
|
-
expect(events.length).toBe(1);
|
|
586
|
-
expect(events[0]).toHaveEventName(EventNames.MODEL_ROUTING);
|
|
587
|
-
expect(events[0]).toHaveMetadataValue([
|
|
588
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_DECISION,
|
|
589
|
-
'gemini-pro',
|
|
590
|
-
]);
|
|
591
|
-
expect(events[0]).toHaveMetadataValue([
|
|
592
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_DECISION_SOURCE,
|
|
593
|
-
'router-exception',
|
|
594
|
-
]);
|
|
595
|
-
expect(events[0]).toHaveMetadataValue([
|
|
596
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_LATENCY_MS,
|
|
597
|
-
'234',
|
|
598
|
-
]);
|
|
599
|
-
expect(events[0]).toHaveMetadataValue([
|
|
600
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_FAILURE,
|
|
601
|
-
'true',
|
|
602
|
-
]);
|
|
603
|
-
expect(events[0]).toHaveMetadataValue([
|
|
604
|
-
EventMetadataKey.GEMINI_CLI_ROUTING_FAILURE_REASON,
|
|
605
|
-
'Something went wrong',
|
|
606
|
-
]);
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
describe('logAgentStartEvent', () => {
|
|
610
|
-
it('logs an event with proper fields', () => {
|
|
611
|
-
const { logger } = setup();
|
|
612
|
-
const event = new AgentStartEvent('agent-123', 'TestAgent');
|
|
613
|
-
logger?.logAgentStartEvent(event);
|
|
614
|
-
const events = getEvents(logger);
|
|
615
|
-
expect(events.length).toBe(1);
|
|
616
|
-
expect(events[0]).toHaveEventName(EventNames.AGENT_START);
|
|
617
|
-
expect(events[0]).toHaveMetadataValue([
|
|
618
|
-
EventMetadataKey.GEMINI_CLI_AGENT_ID,
|
|
619
|
-
'agent-123',
|
|
620
|
-
]);
|
|
621
|
-
expect(events[0]).toHaveMetadataValue([
|
|
622
|
-
EventMetadataKey.GEMINI_CLI_AGENT_NAME,
|
|
623
|
-
'TestAgent',
|
|
624
|
-
]);
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
describe('logExperiments', () => {
|
|
628
|
-
it('logs an event with gws_experiment field containing exp ids', () => {
|
|
629
|
-
const { logger } = setup();
|
|
630
|
-
const event = new AgentStartEvent('agent-123', 'TestAgent');
|
|
631
|
-
logger?.logAgentStartEvent(event);
|
|
632
|
-
const events = getEvents(logger);
|
|
633
|
-
expect(events.length).toBe(1);
|
|
634
|
-
expect(events[0]).toHaveEventName(EventNames.AGENT_START);
|
|
635
|
-
expect(events[0]).toHaveMetadataValue([
|
|
636
|
-
EventMetadataKey.GEMINI_CLI_EXPERIMENT_IDS,
|
|
637
|
-
'123,456,789',
|
|
638
|
-
]);
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
describe('logAgentFinishEvent', () => {
|
|
642
|
-
it('logs an event with proper fields (success)', () => {
|
|
643
|
-
const { logger } = setup();
|
|
644
|
-
const event = new AgentFinishEvent('agent-123', 'TestAgent', 1000, 5, AgentTerminateMode.GOAL);
|
|
645
|
-
logger?.logAgentFinishEvent(event);
|
|
646
|
-
const events = getEvents(logger);
|
|
647
|
-
expect(events.length).toBe(1);
|
|
648
|
-
expect(events[0]).toHaveEventName(EventNames.AGENT_FINISH);
|
|
649
|
-
expect(events[0]).toHaveMetadataValue([
|
|
650
|
-
EventMetadataKey.GEMINI_CLI_AGENT_ID,
|
|
651
|
-
'agent-123',
|
|
652
|
-
]);
|
|
653
|
-
expect(events[0]).toHaveMetadataValue([
|
|
654
|
-
EventMetadataKey.GEMINI_CLI_AGENT_NAME,
|
|
655
|
-
'TestAgent',
|
|
656
|
-
]);
|
|
657
|
-
expect(events[0]).toHaveMetadataValue([
|
|
658
|
-
EventMetadataKey.GEMINI_CLI_AGENT_DURATION_MS,
|
|
659
|
-
'1000',
|
|
660
|
-
]);
|
|
661
|
-
expect(events[0]).toHaveMetadataValue([
|
|
662
|
-
EventMetadataKey.GEMINI_CLI_AGENT_TURN_COUNT,
|
|
663
|
-
'5',
|
|
664
|
-
]);
|
|
665
|
-
expect(events[0]).toHaveMetadataValue([
|
|
666
|
-
EventMetadataKey.GEMINI_CLI_AGENT_TERMINATE_REASON,
|
|
667
|
-
'GOAL',
|
|
668
|
-
]);
|
|
669
|
-
});
|
|
670
|
-
it('logs an event with proper fields (error)', () => {
|
|
671
|
-
const { logger } = setup();
|
|
672
|
-
const event = new AgentFinishEvent('agent-123', 'TestAgent', 500, 2, AgentTerminateMode.ERROR);
|
|
673
|
-
logger?.logAgentFinishEvent(event);
|
|
674
|
-
const events = getEvents(logger);
|
|
675
|
-
expect(events.length).toBe(1);
|
|
676
|
-
expect(events[0]).toHaveEventName(EventNames.AGENT_FINISH);
|
|
677
|
-
expect(events[0]).toHaveMetadataValue([
|
|
678
|
-
EventMetadataKey.GEMINI_CLI_AGENT_TERMINATE_REASON,
|
|
679
|
-
'ERROR',
|
|
680
|
-
]);
|
|
681
|
-
});
|
|
682
|
-
});
|
|
683
|
-
describe('logToolCallEvent', () => {
|
|
684
|
-
it('logs an event with all diff metadata', () => {
|
|
685
|
-
const { logger } = setup();
|
|
686
|
-
const completedToolCall = {
|
|
687
|
-
request: { name: 'test', args: {}, prompt_id: 'prompt-123' },
|
|
688
|
-
response: {
|
|
689
|
-
resultDisplay: {
|
|
690
|
-
diffStat: {
|
|
691
|
-
model_added_lines: 1,
|
|
692
|
-
model_removed_lines: 2,
|
|
693
|
-
model_added_chars: 3,
|
|
694
|
-
model_removed_chars: 4,
|
|
695
|
-
user_added_lines: 5,
|
|
696
|
-
user_removed_lines: 6,
|
|
697
|
-
user_added_chars: 7,
|
|
698
|
-
user_removed_chars: 8,
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
status: 'success',
|
|
703
|
-
};
|
|
704
|
-
logger?.logToolCallEvent(new ToolCallEvent(completedToolCall));
|
|
705
|
-
const events = getEvents(logger);
|
|
706
|
-
expect(events.length).toBe(1);
|
|
707
|
-
expect(events[0]).toHaveEventName(EventNames.TOOL_CALL);
|
|
708
|
-
expect(events[0]).toHaveMetadataValue([
|
|
709
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
710
|
-
'1',
|
|
711
|
-
]);
|
|
712
|
-
expect(events[0]).toHaveMetadataValue([
|
|
713
|
-
EventMetadataKey.GEMINI_CLI_AI_REMOVED_LINES,
|
|
714
|
-
'2',
|
|
715
|
-
]);
|
|
716
|
-
expect(events[0]).toHaveMetadataValue([
|
|
717
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_CHARS,
|
|
718
|
-
'3',
|
|
719
|
-
]);
|
|
720
|
-
expect(events[0]).toHaveMetadataValue([
|
|
721
|
-
EventMetadataKey.GEMINI_CLI_AI_REMOVED_CHARS,
|
|
722
|
-
'4',
|
|
723
|
-
]);
|
|
724
|
-
expect(events[0]).toHaveMetadataValue([
|
|
725
|
-
EventMetadataKey.GEMINI_CLI_USER_ADDED_LINES,
|
|
726
|
-
'5',
|
|
727
|
-
]);
|
|
728
|
-
expect(events[0]).toHaveMetadataValue([
|
|
729
|
-
EventMetadataKey.GEMINI_CLI_USER_REMOVED_LINES,
|
|
730
|
-
'6',
|
|
731
|
-
]);
|
|
732
|
-
expect(events[0]).toHaveMetadataValue([
|
|
733
|
-
EventMetadataKey.GEMINI_CLI_USER_ADDED_CHARS,
|
|
734
|
-
'7',
|
|
735
|
-
]);
|
|
736
|
-
expect(events[0]).toHaveMetadataValue([
|
|
737
|
-
EventMetadataKey.GEMINI_CLI_USER_REMOVED_CHARS,
|
|
738
|
-
'8',
|
|
739
|
-
]);
|
|
740
|
-
});
|
|
741
|
-
it('logs an event with partial diff metadata', () => {
|
|
742
|
-
const { logger } = setup();
|
|
743
|
-
const completedToolCall = {
|
|
744
|
-
request: { name: 'test', args: {}, prompt_id: 'prompt-123' },
|
|
745
|
-
response: {
|
|
746
|
-
resultDisplay: {
|
|
747
|
-
diffStat: {
|
|
748
|
-
model_added_lines: 1,
|
|
749
|
-
model_removed_lines: 2,
|
|
750
|
-
model_added_chars: 3,
|
|
751
|
-
model_removed_chars: 4,
|
|
752
|
-
},
|
|
753
|
-
},
|
|
754
|
-
},
|
|
755
|
-
status: 'success',
|
|
756
|
-
};
|
|
757
|
-
logger?.logToolCallEvent(new ToolCallEvent(completedToolCall));
|
|
758
|
-
const events = getEvents(logger);
|
|
759
|
-
expect(events.length).toBe(1);
|
|
760
|
-
expect(events[0]).toHaveEventName(EventNames.TOOL_CALL);
|
|
761
|
-
expect(events[0]).toHaveMetadataValue([
|
|
762
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
|
763
|
-
'1',
|
|
764
|
-
]);
|
|
765
|
-
expect(events[0]).toHaveMetadataValue([
|
|
766
|
-
EventMetadataKey.GEMINI_CLI_AI_REMOVED_LINES,
|
|
767
|
-
'2',
|
|
768
|
-
]);
|
|
769
|
-
expect(events[0]).toHaveMetadataValue([
|
|
770
|
-
EventMetadataKey.GEMINI_CLI_AI_ADDED_CHARS,
|
|
771
|
-
'3',
|
|
772
|
-
]);
|
|
773
|
-
expect(events[0]).toHaveMetadataValue([
|
|
774
|
-
EventMetadataKey.GEMINI_CLI_AI_REMOVED_CHARS,
|
|
775
|
-
'4',
|
|
776
|
-
]);
|
|
777
|
-
expect(events[0]).not.toHaveMetadataKey(EventMetadataKey.GEMINI_CLI_USER_ADDED_LINES);
|
|
778
|
-
expect(events[0]).not.toHaveMetadataKey(EventMetadataKey.GEMINI_CLI_USER_REMOVED_LINES);
|
|
779
|
-
expect(events[0]).not.toHaveMetadataKey(EventMetadataKey.GEMINI_CLI_USER_ADDED_CHARS);
|
|
780
|
-
expect(events[0]).not.toHaveMetadataKey(EventMetadataKey.GEMINI_CLI_USER_REMOVED_CHARS);
|
|
781
|
-
});
|
|
782
|
-
it('does not log diff metadata if diffStat is not present', () => {
|
|
783
|
-
const { logger } = setup();
|
|
784
|
-
const completedToolCall = {
|
|
785
|
-
request: { name: 'test', args: {}, prompt_id: 'prompt-123' },
|
|
786
|
-
response: {
|
|
787
|
-
resultDisplay: {},
|
|
788
|
-
},
|
|
789
|
-
status: 'success',
|
|
790
|
-
};
|
|
791
|
-
logger?.logToolCallEvent(new ToolCallEvent(completedToolCall));
|
|
792
|
-
const events = getEvents(logger);
|
|
793
|
-
expect(events.length).toBe(1);
|
|
794
|
-
expect(events[0]).toHaveEventName(EventNames.TOOL_CALL);
|
|
795
|
-
expect(events[0]).not.toHaveMetadataKey(EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES);
|
|
796
|
-
});
|
|
797
|
-
});
|
|
798
|
-
describe('flushIfNeeded', () => {
|
|
799
|
-
it('should not flush if the interval has not passed', () => {
|
|
800
|
-
const { logger } = setup();
|
|
801
|
-
const flushSpy = vi
|
|
802
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
803
|
-
.spyOn(logger, 'flushToClearcut')
|
|
804
|
-
.mockResolvedValue({ nextRequestWaitMs: 0 });
|
|
805
|
-
logger.flushIfNeeded();
|
|
806
|
-
expect(flushSpy).not.toHaveBeenCalled();
|
|
807
|
-
});
|
|
808
|
-
it('should flush if the interval has passed', async () => {
|
|
809
|
-
const { logger } = setup();
|
|
810
|
-
const flushSpy = vi
|
|
811
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
812
|
-
.spyOn(logger, 'flushToClearcut')
|
|
813
|
-
.mockResolvedValue({ nextRequestWaitMs: 0 });
|
|
814
|
-
// Advance time by more than the flush interval
|
|
815
|
-
await vi.advanceTimersByTimeAsync(1000 * 60 * 2);
|
|
816
|
-
logger.flushIfNeeded();
|
|
817
|
-
expect(flushSpy).toHaveBeenCalled();
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
describe('logWebFetchFallbackAttemptEvent', () => {
|
|
821
|
-
it('logs an event with the proper name and reason', () => {
|
|
822
|
-
const { logger } = setup();
|
|
823
|
-
const event = new WebFetchFallbackAttemptEvent('private_ip');
|
|
824
|
-
logger?.logWebFetchFallbackAttemptEvent(event);
|
|
825
|
-
const events = getEvents(logger);
|
|
826
|
-
expect(events.length).toBe(1);
|
|
827
|
-
expect(events[0]).toHaveEventName(EventNames.WEB_FETCH_FALLBACK_ATTEMPT);
|
|
828
|
-
expect(events[0]).toHaveMetadataValue([
|
|
829
|
-
EventMetadataKey.GEMINI_CLI_WEB_FETCH_FALLBACK_REASON,
|
|
830
|
-
'private_ip',
|
|
831
|
-
]);
|
|
832
|
-
});
|
|
833
|
-
});
|
|
834
|
-
});
|
|
835
|
-
//# sourceMappingURL=clearcut-logger.test.js.map
|