Skip to content

Commit a290c00

Browse files
committed
feat: add telemetry to TUI add paths via withAddTelemetry
1 parent 29a9614 commit a290c00

8 files changed

Lines changed: 157 additions & 57 deletions

File tree

src/cli/telemetry/cli-command-run.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getErrorMessage } from '../errors';
2+
import type { AddResult } from '../primitives/types.js';
23
import { TelemetryClientAccessor } from './client-accessor.js';
34
import type { Command, CommandAttrs } from './schemas/command-run.js';
45

@@ -35,3 +36,32 @@ export async function cliCommandRun<C extends Command>(
3536
process.exit(1);
3637
}
3738
}
39+
40+
/**
41+
* Wrap a primitive .add() call with telemetry — used by TUI paths.
42+
* CLI paths use {@link cliCommandRun} instead.
43+
*/
44+
export async function withAddTelemetry<C extends Command, T extends Record<string, unknown>>(
45+
command: C,
46+
attrs: CommandAttrs<C>,
47+
fn: () => Promise<AddResult<T>>
48+
): Promise<AddResult<T>> {
49+
let client;
50+
try {
51+
client = await TelemetryClientAccessor.get();
52+
} catch {
53+
return fn();
54+
}
55+
56+
let result: AddResult<T> | undefined;
57+
try {
58+
await client.withCommandRun(command, async () => {
59+
result = await fn();
60+
if (!result.success) throw new Error(result.error);
61+
return attrs;
62+
});
63+
} catch {
64+
if (!result) return fn();
65+
}
66+
return result!;
67+
}

src/cli/tui/hooks/useCreateEvaluator.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { EvaluatorConfig } from '../../../schema';
22
import { evaluatorPrimitive } from '../../primitives/registry';
3+
import { withAddTelemetry } from '../../telemetry/cli-command-run.js';
4+
import { Level, standardize } from '../../telemetry/schemas/common-shapes.js';
35
import { useCallback, useEffect, useState } from 'react';
46

57
interface CreateEvaluatorConfig {
@@ -16,11 +18,19 @@ export function useCreateEvaluator() {
1618
const create = useCallback(async (config: CreateEvaluatorConfig) => {
1719
setStatus({ state: 'loading' });
1820
try {
19-
const addResult = await evaluatorPrimitive.add({
20-
name: config.name,
21-
level: config.level as 'SESSION' | 'TRACE' | 'TOOL_CALL',
22-
config: config.config,
23-
});
21+
const addResult = await withAddTelemetry(
22+
'add.evaluator',
23+
{
24+
evaluator_type: config.config.codeBased ? 'code-based' : 'llm-as-a-judge',
25+
level: standardize(Level, config.level),
26+
},
27+
() =>
28+
evaluatorPrimitive.add({
29+
name: config.name,
30+
level: config.level as 'SESSION' | 'TRACE' | 'TOOL_CALL',
31+
config: config.config,
32+
})
33+
);
2434
if (!addResult.success) {
2535
throw new Error(addResult.error ?? 'Failed to create evaluator');
2636
}

src/cli/tui/hooks/useCreateMcp.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
gatewayTargetPrimitive,
55
policyEnginePrimitive,
66
} from '../../primitives/registry';
7+
import { withAddTelemetry } from '../../telemetry/cli-command-run.js';
8+
import { AuthorizerType, PolicyEngineMode, standardize } from '../../telemetry/schemas/common-shapes.js';
79
import type { AddGatewayConfig } from '../screens/mcp/types';
810
import { useCallback, useEffect, useState } from 'react';
911

@@ -23,22 +25,33 @@ export function useCreateGateway() {
2325
const createGateway = useCallback(async (config: AddGatewayConfig) => {
2426
setStatus({ state: 'loading' });
2527
try {
26-
const addResult = await gatewayPrimitive.add({
27-
name: config.name,
28-
description: config.description,
29-
authorizerType: config.authorizerType,
30-
discoveryUrl: config.jwtConfig?.discoveryUrl,
31-
allowedAudience: config.jwtConfig?.allowedAudience?.join(','),
32-
allowedClients: config.jwtConfig?.allowedClients?.join(','),
33-
allowedScopes: config.jwtConfig?.allowedScopes?.join(','),
34-
customClaims: config.jwtConfig?.customClaims,
35-
clientId: config.jwtConfig?.clientId,
36-
clientSecret: config.jwtConfig?.clientSecret,
37-
enableSemanticSearch: config.enableSemanticSearch,
38-
exceptionLevel: config.exceptionLevel,
39-
policyEngine: config.policyEngineConfiguration?.policyEngineName,
40-
policyEngineMode: config.policyEngineConfiguration?.mode,
41-
});
28+
const addResult = await withAddTelemetry(
29+
'add.gateway',
30+
{
31+
authorizer_type: standardize(AuthorizerType, config.authorizerType ?? 'NONE'),
32+
has_policy_engine: !!config.policyEngineConfiguration?.policyEngineName,
33+
policy_engine_mode: standardize(PolicyEngineMode, config.policyEngineConfiguration?.mode ?? 'log_only'),
34+
semantic_search: config.enableSemanticSearch !== false,
35+
runtime_count: 0,
36+
},
37+
() =>
38+
gatewayPrimitive.add({
39+
name: config.name,
40+
description: config.description,
41+
authorizerType: config.authorizerType,
42+
discoveryUrl: config.jwtConfig?.discoveryUrl,
43+
allowedAudience: config.jwtConfig?.allowedAudience?.join(','),
44+
allowedClients: config.jwtConfig?.allowedClients?.join(','),
45+
allowedScopes: config.jwtConfig?.allowedScopes?.join(','),
46+
customClaims: config.jwtConfig?.customClaims,
47+
clientId: config.jwtConfig?.clientId,
48+
clientSecret: config.jwtConfig?.clientSecret,
49+
enableSemanticSearch: config.enableSemanticSearch,
50+
exceptionLevel: config.exceptionLevel,
51+
policyEngine: config.policyEngineConfiguration?.policyEngineName,
52+
policyEngineMode: config.policyEngineConfiguration?.mode,
53+
})
54+
);
4255
if (!addResult.success) {
4356
throw new Error(addResult.error ?? 'Failed to create gateway');
4457
}

src/cli/tui/hooks/useCreateMemory.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ConfigIO } from '../../../lib';
22
import type { Memory } from '../../../schema';
33
import { getAvailableAgents } from '../../operations/attach';
44
import { memoryPrimitive } from '../../primitives/registry';
5+
import { withAddTelemetry } from '../../telemetry/cli-command-run.js';
56
import { useCallback, useEffect, useState } from 'react';
67

78
interface CreateMemoryConfig {
@@ -24,13 +25,25 @@ export function useCreateMemory() {
2425
setStatus({ state: 'loading' });
2526
try {
2627
const strategiesStr = config.strategies.map(s => s.type).join(',');
27-
const addResult = await memoryPrimitive.add({
28-
name: config.name,
29-
expiry: config.eventExpiryDuration,
30-
strategies: strategiesStr || undefined,
31-
dataStreamArn: config.streaming?.dataStreamArn,
32-
contentLevel: config.streaming?.contentLevel,
33-
});
28+
const strategyList = strategiesStr ? strategiesStr.split(',').map(s => s.trim().toUpperCase()) : [];
29+
const addResult = await withAddTelemetry(
30+
'add.memory',
31+
{
32+
strategy_count: strategyList.length,
33+
strategy_semantic: strategyList.includes('SEMANTIC'),
34+
strategy_summarization: strategyList.includes('SUMMARIZATION'),
35+
strategy_user_preference: strategyList.includes('USER_PREFERENCE'),
36+
strategy_episodic: strategyList.includes('EPISODIC'),
37+
},
38+
() =>
39+
memoryPrimitive.add({
40+
name: config.name,
41+
expiry: config.eventExpiryDuration,
42+
strategies: strategiesStr || undefined,
43+
dataStreamArn: config.streaming?.dataStreamArn,
44+
contentLevel: config.streaming?.contentLevel,
45+
})
46+
);
3447
if (!addResult.success) {
3548
throw new Error(addResult.error ?? 'Failed to create memory');
3649
}

src/cli/tui/hooks/useCreateOnlineEval.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { onlineEvalConfigPrimitive } from '../../primitives/registry';
2+
import { withAddTelemetry } from '../../telemetry/cli-command-run.js';
23
import { useCallback, useEffect, useState } from 'react';
34

45
interface CreateOnlineEvalConfig {
@@ -17,13 +18,21 @@ export function useCreateOnlineEval() {
1718
const create = useCallback(async (config: CreateOnlineEvalConfig) => {
1819
setStatus({ state: 'loading' });
1920
try {
20-
const addResult = await onlineEvalConfigPrimitive.add({
21-
name: config.name,
22-
agent: config.agent,
23-
evaluators: config.evaluators,
24-
samplingRate: config.samplingRate,
25-
enableOnCreate: config.enableOnCreate,
26-
});
21+
const addResult = await withAddTelemetry(
22+
'add.online-eval',
23+
{
24+
evaluator_count: config.evaluators.length,
25+
enable_on_create: config.enableOnCreate ?? false,
26+
},
27+
() =>
28+
onlineEvalConfigPrimitive.add({
29+
name: config.name,
30+
agent: config.agent,
31+
evaluators: config.evaluators,
32+
samplingRate: config.samplingRate,
33+
enableOnCreate: config.enableOnCreate,
34+
})
35+
);
2736
if (!addResult.success) {
2837
throw new Error(addResult.error ?? 'Failed to create online eval config');
2938
}

src/cli/tui/screens/identity/useCreateIdentity.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ConfigIO } from '../../../../lib';
22
import type { Credential } from '../../../../schema';
33
import type { AddCredentialOptions } from '../../../primitives/CredentialPrimitive';
44
import { credentialPrimitive } from '../../../primitives/registry';
5+
import { withAddTelemetry } from '../../../telemetry/cli-command-run.js';
56
import { useCallback, useEffect, useState } from 'react';
67

78
interface CreateStatus<T> {
@@ -16,7 +17,13 @@ export function useCreateIdentity() {
1617
const create = useCallback(async (config: AddCredentialOptions) => {
1718
setStatus({ state: 'loading' });
1819
try {
19-
const result = await credentialPrimitive.add(config);
20+
const result = await withAddTelemetry(
21+
'add.credential',
22+
{
23+
credential_type: config.authorizerType === 'OAuthCredentialProvider' ? 'oauth' : 'api-key',
24+
},
25+
() => credentialPrimitive.add(config)
26+
);
2027
if (!result.success) {
2128
throw new Error(result.error ?? 'Failed to create credential');
2229
}

src/cli/tui/screens/policy/AddPolicyFlow.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { policyEnginePrimitive, policyPrimitive } from '../../../primitives/registry';
2+
import { withAddTelemetry } from '../../../telemetry/cli-command-run.js';
3+
import { ValidationMode, standardize } from '../../../telemetry/schemas/common-shapes.js';
24
import {
35
ErrorPrompt,
46
Panel,
@@ -128,7 +130,14 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD
128130
}, []);
129131

130132
const commitEngine = useCallback(async (engineName: string, gateways?: string[], mode?: 'LOG_ONLY' | 'ENFORCE') => {
131-
const result = await policyEnginePrimitive.add({ name: engineName });
133+
const result = await withAddTelemetry(
134+
'add.policy-engine',
135+
{
136+
attach_gateway_count: 0,
137+
attach_mode: 'log_only',
138+
},
139+
() => policyEnginePrimitive.add({ name: engineName })
140+
);
132141
if (!result.success) {
133142
setFlow({ name: 'error', message: result.error });
134143
return;
@@ -155,13 +164,21 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD
155164
);
156165

157166
const handlePolicyComplete = useCallback(async (config: AddPolicyConfig) => {
158-
const result = await policyPrimitive.add({
159-
name: config.name,
160-
engine: config.engine,
161-
statement: config.statement,
162-
source: config.sourceFile || undefined,
163-
validationMode: config.validationMode,
164-
});
167+
const result = await withAddTelemetry(
168+
'add.policy',
169+
{
170+
source_type: config.sourceFile ? 'file' : 'statement',
171+
validation_mode: standardize(ValidationMode, config.validationMode ?? 'FAIL_ON_ANY_FINDINGS'),
172+
},
173+
() =>
174+
policyPrimitive.add({
175+
name: config.name,
176+
engine: config.engine,
177+
statement: config.statement,
178+
source: config.sourceFile || undefined,
179+
validationMode: config.validationMode,
180+
})
181+
);
165182

166183
if (result.success) {
167184
setPolicyNames(prev => [...prev, config.name]);

src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ConfigIO } from '../../../../lib';
22
import { runtimeEndpointPrimitive } from '../../../primitives/registry';
3+
import { withAddTelemetry } from '../../../telemetry/cli-command-run.js';
34
import { ErrorPrompt } from '../../components';
45
import { AddSuccessScreen } from '../add/AddSuccessScreen';
56
import { AddRuntimeEndpointScreen } from './AddRuntimeEndpointScreen';
@@ -78,24 +79,24 @@ export function AddRuntimeEndpointFlow({
7879
}, [isInteractive, flow.name, onExit]);
7980

8081
const handleCreateComplete = useCallback((config: RuntimeEndpointWizardConfig) => {
81-
void runtimeEndpointPrimitive
82-
.add({
82+
void withAddTelemetry('add.runtime-endpoint', {}, () =>
83+
runtimeEndpointPrimitive.add({
8384
runtime: config.runtimeName,
8485
endpoint: config.endpointName,
8586
version: config.version,
8687
description: config.description,
8788
})
88-
.then(result => {
89-
if (result.success) {
90-
setFlow({
91-
name: 'create-success',
92-
endpointName: config.endpointName,
93-
runtimeName: config.runtimeName,
94-
});
95-
return;
96-
}
97-
setFlow({ name: 'error', message: result.error ?? 'Unknown error' });
98-
});
89+
).then(result => {
90+
if (result.success) {
91+
setFlow({
92+
name: 'create-success',
93+
endpointName: config.endpointName,
94+
runtimeName: config.runtimeName,
95+
});
96+
return;
97+
}
98+
setFlow({ name: 'error', message: result.error ?? 'Unknown error' });
99+
});
99100
}, []);
100101

101102
if (flow.name === 'loading') {

0 commit comments

Comments
 (0)