Strip dead OTel event noise from telemetry compatibility paths
The open build no longer exports OpenTelemetry events, but several user-prompt, tool, hook, API, and survey paths were still constructing and calling a no-op logOTelEvent helper. This removes those dead calls, drops the now-unused helper module, and deletes an unreferenced GrowthBook experiment event artifact so the remaining compatibility layer is less distracting during future audits. Constraint: Keep the local logEvent and tracing compatibility surfaces untouched where they still structure control flow Constraint: Avoid touching unrelated bridge and session changes already present in the worktree Rejected: Remove sessionTracing compatibility entirely | Call surface is still broad and intertwined with non-telemetry control flow Rejected: Leave no-op OTel event calls in place | They add audit noise without preserving behavior Confidence: high Scope-risk: narrow Reversibility: clean Directive: Continue treating remaining telemetry-named helpers as removable only when their call sites are proven behavior-free Tested: bun test src/services/analytics/index.test.ts src/components/FeedbackSurvey/submitTranscriptShare.test.ts Tested: bun run ./scripts/build.ts Not-tested: bun x tsc --noEmit (repository has pre-existing unrelated type errors)
This commit is contained in:
@@ -9,7 +9,6 @@ import { isEnvTruthy } from '../../utils/envUtils.js';
|
|||||||
import { getLastAssistantMessage } from '../../utils/messages.js';
|
import { getLastAssistantMessage } from '../../utils/messages.js';
|
||||||
import { getMainLoopModel } from '../../utils/model/model.js';
|
import { getMainLoopModel } from '../../utils/model/model.js';
|
||||||
import { getInitialSettings } from '../../utils/settings/settings.js';
|
import { getInitialSettings } from '../../utils/settings/settings.js';
|
||||||
import { logOTelEvent } from '../../utils/telemetry/events.js';
|
|
||||||
import { submitTranscriptShare, type TranscriptShareTrigger } from './submitTranscriptShare.js';
|
import { submitTranscriptShare, type TranscriptShareTrigger } from './submitTranscriptShare.js';
|
||||||
import type { TranscriptShareResponse } from './TranscriptSharePrompt.js';
|
import type { TranscriptShareResponse } from './TranscriptSharePrompt.js';
|
||||||
import { useSurveyState } from './useSurveyState.js';
|
import { useSurveyState } from './useSurveyState.js';
|
||||||
@@ -99,11 +98,6 @@ export function useFeedbackSurvey(messages: Message[], isLoading: boolean, submi
|
|||||||
last_assistant_message_id: lastAssistantMessageIdRef.current as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
last_assistant_message_id: lastAssistantMessageIdRef.current as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'appeared',
|
|
||||||
appearance_id: appearanceId,
|
|
||||||
survey_type: surveyType
|
|
||||||
});
|
|
||||||
}, [updateLastShownTime, surveyType]);
|
}, [updateLastShownTime, surveyType]);
|
||||||
const onSelect = useCallback((appearanceId_0: string, selected: FeedbackSurveyResponse) => {
|
const onSelect = useCallback((appearanceId_0: string, selected: FeedbackSurveyResponse) => {
|
||||||
updateLastShownTime(Date.now(), submitCountRef.current);
|
updateLastShownTime(Date.now(), submitCountRef.current);
|
||||||
@@ -114,12 +108,6 @@ export function useFeedbackSurvey(messages: Message[], isLoading: boolean, submi
|
|||||||
last_assistant_message_id: lastAssistantMessageIdRef.current as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
last_assistant_message_id: lastAssistantMessageIdRef.current as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'responded',
|
|
||||||
appearance_id: appearanceId_0,
|
|
||||||
response: selected,
|
|
||||||
survey_type: surveyType
|
|
||||||
});
|
|
||||||
}, [updateLastShownTime, surveyType]);
|
}, [updateLastShownTime, surveyType]);
|
||||||
const shouldShowTranscriptPrompt = useCallback((selected_0: FeedbackSurveyResponse) => {
|
const shouldShowTranscriptPrompt = useCallback((selected_0: FeedbackSurveyResponse) => {
|
||||||
// Only bad and good ratings trigger the transcript ask
|
// Only bad and good ratings trigger the transcript ask
|
||||||
@@ -150,11 +138,6 @@ export function useFeedbackSurvey(messages: Message[], isLoading: boolean, submi
|
|||||||
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
survey_type: surveyType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
trigger: trigger as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
trigger: trigger as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'transcript_prompt_appeared',
|
|
||||||
appearance_id: appearanceId_1,
|
|
||||||
survey_type: surveyType
|
|
||||||
});
|
|
||||||
}, [surveyType]);
|
}, [surveyType]);
|
||||||
const onTranscriptSelect = useCallback(async (appearanceId_2: string, selected_1: TranscriptShareResponse, surveyResponse_0: FeedbackSurveyResponse | null): Promise<boolean> => {
|
const onTranscriptSelect = useCallback(async (appearanceId_2: string, selected_1: TranscriptShareResponse, surveyResponse_0: FeedbackSurveyResponse | null): Promise<boolean> => {
|
||||||
const trigger_0: TranscriptShareTrigger = surveyResponse_0 === 'good' ? 'good_feedback_survey' : 'bad_feedback_survey';
|
const trigger_0: TranscriptShareTrigger = surveyResponse_0 === 'good' ? 'good_feedback_survey' : 'bad_feedback_survey';
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
|||||||
import { isEnvTruthy } from '../../utils/envUtils.js';
|
import { isEnvTruthy } from '../../utils/envUtils.js';
|
||||||
import { isAutoManagedMemoryFile } from '../../utils/memoryFileDetection.js';
|
import { isAutoManagedMemoryFile } from '../../utils/memoryFileDetection.js';
|
||||||
import { extractTextContent, getLastAssistantMessage } from '../../utils/messages.js';
|
import { extractTextContent, getLastAssistantMessage } from '../../utils/messages.js';
|
||||||
import { logOTelEvent } from '../../utils/telemetry/events.js';
|
|
||||||
import { submitTranscriptShare } from './submitTranscriptShare.js';
|
import { submitTranscriptShare } from './submitTranscriptShare.js';
|
||||||
import type { TranscriptShareResponse } from './TranscriptSharePrompt.js';
|
import type { TranscriptShareResponse } from './TranscriptSharePrompt.js';
|
||||||
import { useSurveyState } from './useSurveyState.js';
|
import { useSurveyState } from './useSurveyState.js';
|
||||||
@@ -67,11 +66,6 @@ export function useMemorySurvey(messages: Message[], isLoading: boolean, hasActi
|
|||||||
event_type: 'appeared' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
event_type: 'appeared' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
appearance_id: appearanceId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
appearance_id: appearanceId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'appeared',
|
|
||||||
appearance_id: appearanceId,
|
|
||||||
survey_type: 'memory'
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
const onSelect = useCallback((appearanceId_0: string, selected: FeedbackSurveyResponse) => {
|
const onSelect = useCallback((appearanceId_0: string, selected: FeedbackSurveyResponse) => {
|
||||||
logEvent(MEMORY_SURVEY_EVENT, {
|
logEvent(MEMORY_SURVEY_EVENT, {
|
||||||
@@ -79,12 +73,6 @@ export function useMemorySurvey(messages: Message[], isLoading: boolean, hasActi
|
|||||||
appearance_id: appearanceId_0 as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
appearance_id: appearanceId_0 as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
response: selected as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
response: selected as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'responded',
|
|
||||||
appearance_id: appearanceId_0,
|
|
||||||
response: selected,
|
|
||||||
survey_type: 'memory'
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
const shouldShowTranscriptPrompt = useCallback((selected_0: FeedbackSurveyResponse) => {
|
const shouldShowTranscriptPrompt = useCallback((selected_0: FeedbackSurveyResponse) => {
|
||||||
if ("external" !== 'ant') {
|
if ("external" !== 'ant') {
|
||||||
@@ -107,11 +95,6 @@ export function useMemorySurvey(messages: Message[], isLoading: boolean, hasActi
|
|||||||
appearance_id: appearanceId_1 as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
appearance_id: appearanceId_1 as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
trigger: TRANSCRIPT_SHARE_TRIGGER as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
trigger: TRANSCRIPT_SHARE_TRIGGER as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||||
});
|
});
|
||||||
void logOTelEvent('feedback_survey', {
|
|
||||||
event_type: 'transcript_prompt_appeared',
|
|
||||||
appearance_id: appearanceId_1,
|
|
||||||
survey_type: 'memory'
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
const onTranscriptSelect = useCallback(async (appearanceId_2: string, selected_1: TranscriptShareResponse): Promise<boolean> => {
|
const onTranscriptSelect = useCallback(async (appearanceId_2: string, selected_1: TranscriptShareResponse): Promise<boolean> => {
|
||||||
logEvent(MEMORY_SURVEY_EVENT, {
|
logEvent(MEMORY_SURVEY_EVENT, {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
// Centralized analytics/telemetry logging for tool permission decisions.
|
// Centralized analytics/telemetry logging for tool permission decisions.
|
||||||
// All permission approve/reject events flow through logPermissionDecision(),
|
// All permission approve/reject events flow through logPermissionDecision(),
|
||||||
// which fans out to Statsig analytics, OTel telemetry, and code-edit metrics.
|
// which fans out to analytics compatibility calls and code-edit metrics.
|
||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle'
|
||||||
import {
|
import {
|
||||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
@@ -11,7 +11,6 @@ import { getCodeEditToolDecisionCounter } from '../../bootstrap/state.js'
|
|||||||
import type { Tool as ToolType, ToolUseContext } from '../../Tool.js'
|
import type { Tool as ToolType, ToolUseContext } from '../../Tool.js'
|
||||||
import { getLanguageName } from '../../utils/cliHighlight.js'
|
import { getLanguageName } from '../../utils/cliHighlight.js'
|
||||||
import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'
|
import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'
|
||||||
import { logOTelEvent } from '../../utils/telemetry/events.js'
|
|
||||||
import type {
|
import type {
|
||||||
PermissionApprovalSource,
|
PermissionApprovalSource,
|
||||||
PermissionRejectionSource,
|
PermissionRejectionSource,
|
||||||
@@ -227,11 +226,6 @@ function logPermissionDecision(
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
void logOTelEvent('tool_decision', {
|
|
||||||
decision,
|
|
||||||
source: sourceString,
|
|
||||||
tool_name: sanitizeToolNameForAnalytics(tool.name),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isCodeEditingTool, buildCodeEditToolAttributes, logPermissionDecision }
|
export { isCodeEditingTool, buildCodeEditToolAttributes, logPermissionDecision }
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { logError } from 'src/utils/log.js'
|
|||||||
import { getAPIProviderForStatsig } from 'src/utils/model/providers.js'
|
import { getAPIProviderForStatsig } from 'src/utils/model/providers.js'
|
||||||
import type { PermissionMode } from 'src/utils/permissions/PermissionMode.js'
|
import type { PermissionMode } from 'src/utils/permissions/PermissionMode.js'
|
||||||
import { jsonStringify } from 'src/utils/slowOperations.js'
|
import { jsonStringify } from 'src/utils/slowOperations.js'
|
||||||
import { logOTelEvent } from 'src/utils/telemetry/events.js'
|
|
||||||
import {
|
import {
|
||||||
endLLMRequestSpan,
|
endLLMRequestSpan,
|
||||||
isBetaTracingEnabled,
|
isBetaTracingEnabled,
|
||||||
@@ -364,16 +363,6 @@ export function logAPIError({
|
|||||||
...getAnthropicEnvMetadata(),
|
...getAnthropicEnvMetadata(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log API error event for OTLP
|
|
||||||
void logOTelEvent('api_error', {
|
|
||||||
model: model,
|
|
||||||
error: errStr,
|
|
||||||
status_code: String(status),
|
|
||||||
duration_ms: String(durationMs),
|
|
||||||
attempt: String(attempt),
|
|
||||||
speed: fastMode ? 'fast' : 'normal',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Pass the span to correctly match responses to requests when beta tracing is enabled
|
// Pass the span to correctly match responses to requests when beta tracing is enabled
|
||||||
endLLMRequestSpan(llmSpan, {
|
endLLMRequestSpan(llmSpan, {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -714,18 +703,6 @@ export function logAPISuccessAndDuration({
|
|||||||
previousRequestId,
|
previousRequestId,
|
||||||
betas,
|
betas,
|
||||||
})
|
})
|
||||||
// Log API request event for OTLP
|
|
||||||
void logOTelEvent('api_request', {
|
|
||||||
model,
|
|
||||||
input_tokens: String(usage.input_tokens),
|
|
||||||
output_tokens: String(usage.output_tokens),
|
|
||||||
cache_read_tokens: String(usage.cache_read_input_tokens),
|
|
||||||
cache_creation_tokens: String(usage.cache_creation_input_tokens),
|
|
||||||
cost_usd: String(costUSD),
|
|
||||||
duration_ms: String(durationMs),
|
|
||||||
speed: fastMode ? 'fast' : 'normal',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Extract model output, thinking output, and tool call flag when beta tracing is enabled
|
// Extract model output, thinking output, and tool call flag when beta tracing is enabled
|
||||||
let modelOutput: string | undefined
|
let modelOutput: string | undefined
|
||||||
let thinkingOutput: string | undefined
|
let thinkingOutput: string | undefined
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ import {
|
|||||||
} from '../../utils/sessionActivity.js'
|
} from '../../utils/sessionActivity.js'
|
||||||
import { jsonStringify } from '../../utils/slowOperations.js'
|
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||||
import { Stream } from '../../utils/stream.js'
|
import { Stream } from '../../utils/stream.js'
|
||||||
import { logOTelEvent } from '../../utils/telemetry/events.js'
|
|
||||||
import {
|
import {
|
||||||
addToolContentEvent,
|
addToolContentEvent,
|
||||||
endToolBlockedOnUserSpan,
|
endToolBlockedOnUserSpan,
|
||||||
@@ -945,28 +944,18 @@ async function checkPermissionsAndCallTool(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit tool_decision OTel event and code-edit counter if the interactive
|
// Increment the code-edit counter here when the interactive permission path
|
||||||
// permission path didn't already log it (headless mode bypasses permission
|
// did not already log a decision (headless mode bypasses permission logging).
|
||||||
// logging, so we need to emit both the generic event and the code-edit
|
|
||||||
// counter here)
|
|
||||||
if (
|
if (
|
||||||
permissionDecision.behavior !== 'ask' &&
|
permissionDecision.behavior !== 'ask' &&
|
||||||
!toolUseContext.toolDecisions?.has(toolUseID)
|
!toolUseContext.toolDecisions?.has(toolUseID)
|
||||||
) {
|
) {
|
||||||
const decision =
|
|
||||||
permissionDecision.behavior === 'allow' ? 'accept' : 'reject'
|
|
||||||
const source = decisionReasonToOTelSource(
|
|
||||||
permissionDecision.decisionReason,
|
|
||||||
permissionDecision.behavior,
|
|
||||||
)
|
|
||||||
void logOTelEvent('tool_decision', {
|
|
||||||
decision,
|
|
||||||
source,
|
|
||||||
tool_name: sanitizeToolNameForAnalytics(tool.name),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Increment code-edit tool decision counter for headless mode
|
// Increment code-edit tool decision counter for headless mode
|
||||||
if (isCodeEditingTool(tool.name)) {
|
if (isCodeEditingTool(tool.name)) {
|
||||||
|
const source = decisionReasonToOTelSource(
|
||||||
|
permissionDecision.decisionReason,
|
||||||
|
permissionDecision.behavior,
|
||||||
|
)
|
||||||
void buildCodeEditToolAttributes(
|
void buildCodeEditToolAttributes(
|
||||||
tool,
|
tool,
|
||||||
processedInput,
|
processedInput,
|
||||||
@@ -1373,27 +1362,10 @@ async function checkPermissionsAndCallTool(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log tool result event for OTLP with tool parameters and decision context
|
|
||||||
const mcpServerScope = isMcpTool(tool)
|
const mcpServerScope = isMcpTool(tool)
|
||||||
? getMcpServerScopeFromToolName(tool.name)
|
? getMcpServerScopeFromToolName(tool.name)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
void logOTelEvent('tool_result', {
|
|
||||||
tool_name: sanitizeToolNameForAnalytics(tool.name),
|
|
||||||
success: 'true',
|
|
||||||
duration_ms: String(durationMs),
|
|
||||||
...(Object.keys(toolParameters).length > 0 && {
|
|
||||||
tool_parameters: jsonStringify(toolParameters),
|
|
||||||
}),
|
|
||||||
...(telemetryToolInput && { tool_input: telemetryToolInput }),
|
|
||||||
tool_result_size_bytes: String(toolResultSizeBytes),
|
|
||||||
...(decisionInfo && {
|
|
||||||
decision_source: decisionInfo.source,
|
|
||||||
decision_type: decisionInfo.decision,
|
|
||||||
}),
|
|
||||||
...(mcpServerScope && { mcp_server_scope: mcpServerScope }),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run PostToolUse hooks
|
// Run PostToolUse hooks
|
||||||
let toolOutput = result.data
|
let toolOutput = result.data
|
||||||
const hookResults = []
|
const hookResults = []
|
||||||
@@ -1666,27 +1638,9 @@ async function checkPermissionsAndCallTool(
|
|||||||
mcpServerBaseUrl,
|
mcpServerBaseUrl,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
// Log tool result error event for OTLP with tool parameters and decision context
|
|
||||||
const mcpServerScope = isMcpTool(tool)
|
const mcpServerScope = isMcpTool(tool)
|
||||||
? getMcpServerScopeFromToolName(tool.name)
|
? getMcpServerScopeFromToolName(tool.name)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
void logOTelEvent('tool_result', {
|
|
||||||
tool_name: sanitizeToolNameForAnalytics(tool.name),
|
|
||||||
use_id: toolUseID,
|
|
||||||
success: 'false',
|
|
||||||
duration_ms: String(durationMs),
|
|
||||||
error: errorMessage(error),
|
|
||||||
...(Object.keys(toolParameters).length > 0 && {
|
|
||||||
tool_parameters: jsonStringify(toolParameters),
|
|
||||||
}),
|
|
||||||
...(telemetryToolInput && { tool_input: telemetryToolInput }),
|
|
||||||
...(decisionInfo && {
|
|
||||||
decision_source: decisionInfo.source,
|
|
||||||
decision_type: decisionInfo.decision,
|
|
||||||
}),
|
|
||||||
...(mcpServerScope && { mcp_server_scope: mcpServerScope }),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const content = formatError(error)
|
const content = formatError(error)
|
||||||
|
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-ts_proto v2.6.1
|
|
||||||
// protoc unknown
|
|
||||||
// source: events_mono/growthbook/v1/growthbook_experiment_event.proto
|
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
import { Timestamp } from '../../../google/protobuf/timestamp.js'
|
|
||||||
import { PublicApiAuth } from '../../common/v1/auth.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GrowthBook experiment assignment event
|
|
||||||
* This event tracks when a user is exposed to an experiment variant
|
|
||||||
* See: https://docs.growthbook.io/guide/bigquery
|
|
||||||
*/
|
|
||||||
export interface GrowthbookExperimentEvent {
|
|
||||||
/** Unique event identifier (for deduplication) */
|
|
||||||
event_id?: string | undefined
|
|
||||||
/** When user was exposed to experiment (maps to GrowthBook's timestamp column) */
|
|
||||||
timestamp?: Date | undefined
|
|
||||||
/** Experiment tracking key (maps to GrowthBook's experiment_id column) */
|
|
||||||
experiment_id?: string | undefined
|
|
||||||
/** Variation index: 0=control, 1+=variants (maps to GrowthBook's variation_id column) */
|
|
||||||
variation_id?: number | undefined
|
|
||||||
/** Environment where assignment occurred */
|
|
||||||
environment?: string | undefined
|
|
||||||
/** User attributes at time of assignment */
|
|
||||||
user_attributes?: string | undefined
|
|
||||||
/** Experiment metadata */
|
|
||||||
experiment_metadata?: string | undefined
|
|
||||||
/** Device identifier for the client */
|
|
||||||
device_id?: string | undefined
|
|
||||||
/** Authentication context automatically injected by the API */
|
|
||||||
auth?: PublicApiAuth | undefined
|
|
||||||
/** Session identifier for tracking user sessions */
|
|
||||||
session_id?: string | undefined
|
|
||||||
/** Anonymous identifier for unauthenticated users */
|
|
||||||
anonymous_id?: string | undefined
|
|
||||||
/** Event metadata variables (automatically populated by internal-tools-common event_logging library) */
|
|
||||||
event_metadata_vars?: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBaseGrowthbookExperimentEvent(): GrowthbookExperimentEvent {
|
|
||||||
return {
|
|
||||||
event_id: '',
|
|
||||||
timestamp: undefined,
|
|
||||||
experiment_id: '',
|
|
||||||
variation_id: 0,
|
|
||||||
environment: '',
|
|
||||||
user_attributes: '',
|
|
||||||
experiment_metadata: '',
|
|
||||||
device_id: '',
|
|
||||||
auth: undefined,
|
|
||||||
session_id: '',
|
|
||||||
anonymous_id: '',
|
|
||||||
event_metadata_vars: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GrowthbookExperimentEvent: MessageFns<GrowthbookExperimentEvent> =
|
|
||||||
{
|
|
||||||
fromJSON(object: any): GrowthbookExperimentEvent {
|
|
||||||
return {
|
|
||||||
event_id: isSet(object.event_id)
|
|
||||||
? globalThis.String(object.event_id)
|
|
||||||
: '',
|
|
||||||
timestamp: isSet(object.timestamp)
|
|
||||||
? fromJsonTimestamp(object.timestamp)
|
|
||||||
: undefined,
|
|
||||||
experiment_id: isSet(object.experiment_id)
|
|
||||||
? globalThis.String(object.experiment_id)
|
|
||||||
: '',
|
|
||||||
variation_id: isSet(object.variation_id)
|
|
||||||
? globalThis.Number(object.variation_id)
|
|
||||||
: 0,
|
|
||||||
environment: isSet(object.environment)
|
|
||||||
? globalThis.String(object.environment)
|
|
||||||
: '',
|
|
||||||
user_attributes: isSet(object.user_attributes)
|
|
||||||
? globalThis.String(object.user_attributes)
|
|
||||||
: '',
|
|
||||||
experiment_metadata: isSet(object.experiment_metadata)
|
|
||||||
? globalThis.String(object.experiment_metadata)
|
|
||||||
: '',
|
|
||||||
device_id: isSet(object.device_id)
|
|
||||||
? globalThis.String(object.device_id)
|
|
||||||
: '',
|
|
||||||
auth: isSet(object.auth)
|
|
||||||
? PublicApiAuth.fromJSON(object.auth)
|
|
||||||
: undefined,
|
|
||||||
session_id: isSet(object.session_id)
|
|
||||||
? globalThis.String(object.session_id)
|
|
||||||
: '',
|
|
||||||
anonymous_id: isSet(object.anonymous_id)
|
|
||||||
? globalThis.String(object.anonymous_id)
|
|
||||||
: '',
|
|
||||||
event_metadata_vars: isSet(object.event_metadata_vars)
|
|
||||||
? globalThis.String(object.event_metadata_vars)
|
|
||||||
: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toJSON(message: GrowthbookExperimentEvent): unknown {
|
|
||||||
const obj: any = {}
|
|
||||||
if (message.event_id !== undefined) {
|
|
||||||
obj.event_id = message.event_id
|
|
||||||
}
|
|
||||||
if (message.timestamp !== undefined) {
|
|
||||||
obj.timestamp = message.timestamp.toISOString()
|
|
||||||
}
|
|
||||||
if (message.experiment_id !== undefined) {
|
|
||||||
obj.experiment_id = message.experiment_id
|
|
||||||
}
|
|
||||||
if (message.variation_id !== undefined) {
|
|
||||||
obj.variation_id = Math.round(message.variation_id)
|
|
||||||
}
|
|
||||||
if (message.environment !== undefined) {
|
|
||||||
obj.environment = message.environment
|
|
||||||
}
|
|
||||||
if (message.user_attributes !== undefined) {
|
|
||||||
obj.user_attributes = message.user_attributes
|
|
||||||
}
|
|
||||||
if (message.experiment_metadata !== undefined) {
|
|
||||||
obj.experiment_metadata = message.experiment_metadata
|
|
||||||
}
|
|
||||||
if (message.device_id !== undefined) {
|
|
||||||
obj.device_id = message.device_id
|
|
||||||
}
|
|
||||||
if (message.auth !== undefined) {
|
|
||||||
obj.auth = PublicApiAuth.toJSON(message.auth)
|
|
||||||
}
|
|
||||||
if (message.session_id !== undefined) {
|
|
||||||
obj.session_id = message.session_id
|
|
||||||
}
|
|
||||||
if (message.anonymous_id !== undefined) {
|
|
||||||
obj.anonymous_id = message.anonymous_id
|
|
||||||
}
|
|
||||||
if (message.event_metadata_vars !== undefined) {
|
|
||||||
obj.event_metadata_vars = message.event_metadata_vars
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
},
|
|
||||||
|
|
||||||
create<I extends Exact<DeepPartial<GrowthbookExperimentEvent>, I>>(
|
|
||||||
base?: I,
|
|
||||||
): GrowthbookExperimentEvent {
|
|
||||||
return GrowthbookExperimentEvent.fromPartial(base ?? ({} as any))
|
|
||||||
},
|
|
||||||
fromPartial<I extends Exact<DeepPartial<GrowthbookExperimentEvent>, I>>(
|
|
||||||
object: I,
|
|
||||||
): GrowthbookExperimentEvent {
|
|
||||||
const message = createBaseGrowthbookExperimentEvent()
|
|
||||||
message.event_id = object.event_id ?? ''
|
|
||||||
message.timestamp = object.timestamp ?? undefined
|
|
||||||
message.experiment_id = object.experiment_id ?? ''
|
|
||||||
message.variation_id = object.variation_id ?? 0
|
|
||||||
message.environment = object.environment ?? ''
|
|
||||||
message.user_attributes = object.user_attributes ?? ''
|
|
||||||
message.experiment_metadata = object.experiment_metadata ?? ''
|
|
||||||
message.device_id = object.device_id ?? ''
|
|
||||||
message.auth =
|
|
||||||
object.auth !== undefined && object.auth !== null
|
|
||||||
? PublicApiAuth.fromPartial(object.auth)
|
|
||||||
: undefined
|
|
||||||
message.session_id = object.session_id ?? ''
|
|
||||||
message.anonymous_id = object.anonymous_id ?? ''
|
|
||||||
message.event_metadata_vars = object.event_metadata_vars ?? ''
|
|
||||||
return message
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builtin =
|
|
||||||
| Date
|
|
||||||
| Function
|
|
||||||
| Uint8Array
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
type DeepPartial<T> = T extends Builtin
|
|
||||||
? T
|
|
||||||
: T extends globalThis.Array<infer U>
|
|
||||||
? globalThis.Array<DeepPartial<U>>
|
|
||||||
: T extends ReadonlyArray<infer U>
|
|
||||||
? ReadonlyArray<DeepPartial<U>>
|
|
||||||
: T extends {}
|
|
||||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
||||||
: Partial<T>
|
|
||||||
|
|
||||||
type KeysOfUnion<T> = T extends T ? keyof T : never
|
|
||||||
type Exact<P, I extends P> = P extends Builtin
|
|
||||||
? P
|
|
||||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
|
||||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromTimestamp(t: Timestamp): Date {
|
|
||||||
let millis = (t.seconds || 0) * 1_000
|
|
||||||
millis += (t.nanos || 0) / 1_000_000
|
|
||||||
return new globalThis.Date(millis)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromJsonTimestamp(o: any): Date {
|
|
||||||
if (o instanceof globalThis.Date) {
|
|
||||||
return o
|
|
||||||
} else if (typeof o === 'string') {
|
|
||||||
return new globalThis.Date(o)
|
|
||||||
} else {
|
|
||||||
return fromTimestamp(Timestamp.fromJSON(o))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSet(value: any): boolean {
|
|
||||||
return value !== null && value !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MessageFns<T> {
|
|
||||||
fromJSON(object: any): T
|
|
||||||
toJSON(message: T): unknown
|
|
||||||
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
|
|
||||||
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
|
|
||||||
}
|
|
||||||
@@ -55,7 +55,6 @@ import {
|
|||||||
logEvent,
|
logEvent,
|
||||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
} from 'src/services/analytics/index.js'
|
} from 'src/services/analytics/index.js'
|
||||||
import { logOTelEvent } from './telemetry/events.js'
|
|
||||||
import { ALLOWED_OFFICIAL_MARKETPLACE_NAMES } from './plugins/schemas.js'
|
import { ALLOWED_OFFICIAL_MARKETPLACE_NAMES } from './plugins/schemas.js'
|
||||||
import {
|
import {
|
||||||
startHookSpan,
|
startHookSpan,
|
||||||
@@ -2071,18 +2070,6 @@ async function* executeHooks({
|
|||||||
? jsonStringify(getHookDefinitionsForTelemetry(matchingHooks))
|
? jsonStringify(getHookDefinitionsForTelemetry(matchingHooks))
|
||||||
: '[]'
|
: '[]'
|
||||||
|
|
||||||
// Log hook execution start to OTEL (only for beta tracing)
|
|
||||||
if (isBetaTracingEnabled()) {
|
|
||||||
void logOTelEvent('hook_execution_start', {
|
|
||||||
hook_event: hookEvent,
|
|
||||||
hook_name: hookName,
|
|
||||||
num_hooks: String(matchingHooks.length),
|
|
||||||
managed_only: String(shouldAllowManagedHooksOnly()),
|
|
||||||
hook_definitions: hookDefinitionsJson,
|
|
||||||
hook_source: shouldAllowManagedHooksOnly() ? 'policySettings' : 'merged',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start hook span for beta tracing
|
// Start hook span for beta tracing
|
||||||
const hookSpan = startHookSpan(
|
const hookSpan = startHookSpan(
|
||||||
hookEvent,
|
hookEvent,
|
||||||
@@ -2943,25 +2930,6 @@ async function* executeHooks({
|
|||||||
totalDurationMs,
|
totalDurationMs,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log hook execution completion to OTEL (only for beta tracing)
|
|
||||||
if (isBetaTracingEnabled()) {
|
|
||||||
const hookDefinitionsComplete =
|
|
||||||
getHookDefinitionsForTelemetry(matchingHooks)
|
|
||||||
|
|
||||||
void logOTelEvent('hook_execution_complete', {
|
|
||||||
hook_event: hookEvent,
|
|
||||||
hook_name: hookName,
|
|
||||||
num_hooks: String(matchingHooks.length),
|
|
||||||
num_success: String(outcomes.success),
|
|
||||||
num_blocking: String(outcomes.blocking),
|
|
||||||
num_non_blocking_error: String(outcomes.non_blocking_error),
|
|
||||||
num_cancelled: String(outcomes.cancelled),
|
|
||||||
managed_only: String(shouldAllowManagedHooksOnly()),
|
|
||||||
hook_definitions: jsonStringify(hookDefinitionsComplete),
|
|
||||||
hook_source: shouldAllowManagedHooksOnly() ? 'policySettings' : 'merged',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// End hook span for beta tracing
|
// End hook span for beta tracing
|
||||||
endHookSpan(hookSpan, {
|
endHookSpan(hookSpan, {
|
||||||
numSuccess: outcomes.success,
|
numSuccess: outcomes.success,
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import { isRestrictedToPluginOnly, isSourceAdminTrusted } from '../settings/plug
|
|||||||
import { parseSlashCommand } from '../slashCommandParsing.js';
|
import { parseSlashCommand } from '../slashCommandParsing.js';
|
||||||
import { sleep } from '../sleep.js';
|
import { sleep } from '../sleep.js';
|
||||||
import { recordSkillUsage } from '../suggestions/skillUsageTracking.js';
|
import { recordSkillUsage } from '../suggestions/skillUsageTracking.js';
|
||||||
import { logOTelEvent, redactIfDisabled } from '../telemetry/events.js';
|
|
||||||
import { buildPluginCommandTelemetryFields } from '../telemetry/pluginTelemetry.js';
|
import { buildPluginCommandTelemetryFields } from '../telemetry/pluginTelemetry.js';
|
||||||
import { getAssistantMessageContentLength } from '../tokens.js';
|
import { getAssistantMessageContentLength } from '../tokens.js';
|
||||||
import { createAgentId } from '../uuid.js';
|
import { createAgentId } from '../uuid.js';
|
||||||
@@ -362,12 +361,6 @@ export async function processSlashCommand(inputString: string, precedingInputBlo
|
|||||||
const promptId = randomUUID();
|
const promptId = randomUUID();
|
||||||
setPromptId(promptId);
|
setPromptId(promptId);
|
||||||
logEvent('tengu_input_prompt', {});
|
logEvent('tengu_input_prompt', {});
|
||||||
// Log user prompt event for OTLP
|
|
||||||
void logOTelEvent('user_prompt', {
|
|
||||||
prompt_length: String(inputString.length),
|
|
||||||
prompt: redactIfDisabled(inputString),
|
|
||||||
'prompt.id': promptId
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
messages: [createUserMessage({
|
messages: [createUserMessage({
|
||||||
content: prepareUserContent({
|
content: prepareUserContent({
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type {
|
|||||||
import { logEvent } from '../../services/analytics/index.js'
|
import { logEvent } from '../../services/analytics/index.js'
|
||||||
import type { PermissionMode } from '../../types/permissions.js'
|
import type { PermissionMode } from '../../types/permissions.js'
|
||||||
import { createUserMessage } from '../messages.js'
|
import { createUserMessage } from '../messages.js'
|
||||||
import { logOTelEvent, redactIfDisabled } from '../telemetry/events.js'
|
|
||||||
import { startInteractionSpan } from '../telemetry/sessionTracing.js'
|
import { startInteractionSpan } from '../telemetry/sessionTracing.js'
|
||||||
import {
|
import {
|
||||||
matchesKeepGoingKeyword,
|
matchesKeepGoingKeyword,
|
||||||
@@ -37,25 +36,6 @@ export function processTextPrompt(
|
|||||||
: input.find(block => block.type === 'text')?.text || ''
|
: input.find(block => block.type === 'text')?.text || ''
|
||||||
startInteractionSpan(userPromptText)
|
startInteractionSpan(userPromptText)
|
||||||
|
|
||||||
// Emit user_prompt OTEL event for both string (CLI) and array (SDK/VS Code)
|
|
||||||
// input shapes. Previously gated on `typeof input === 'string'`, so VS Code
|
|
||||||
// sessions never emitted user_prompt (anthropics/claude-code#33301).
|
|
||||||
// For array input, use the LAST text block: createUserContent pushes the
|
|
||||||
// user's message last (after any <ide_selection>/attachment context blocks),
|
|
||||||
// so .findLast gets the actual prompt. userPromptText (first block) is kept
|
|
||||||
// unchanged for startInteractionSpan to preserve existing span attributes.
|
|
||||||
const otelPromptText =
|
|
||||||
typeof input === 'string'
|
|
||||||
? input
|
|
||||||
: input.findLast(block => block.type === 'text')?.text || ''
|
|
||||||
if (otelPromptText) {
|
|
||||||
void logOTelEvent('user_prompt', {
|
|
||||||
prompt_length: String(otelPromptText.length),
|
|
||||||
prompt: redactIfDisabled(otelPromptText),
|
|
||||||
'prompt.id': promptId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNegative = matchesNegativeKeyword(userPromptText)
|
const isNegative = matchesNegativeKeyword(userPromptText)
|
||||||
const isKeepGoing = matchesKeepGoingKeyword(userPromptText)
|
const isKeepGoing = matchesKeepGoingKeyword(userPromptText)
|
||||||
logEvent('tengu_input_prompt', {
|
logEvent('tengu_input_prompt', {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* OpenTelemetry event egress is disabled in this build.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function redactIfDisabled(_content: string): string {
|
|
||||||
return '<REDACTED>'
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function logOTelEvent(
|
|
||||||
_eventName: string,
|
|
||||||
_metadata: { [key: string]: string | undefined } = {},
|
|
||||||
): Promise<void> {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user