Remove the remaining no-op tracing and telemetry-only helpers

The build no longer ships telemetry egress, so the next cleanup pass deletes the remaining tracing compatibility layer and the helper modules whose only job was to shape telemetry payloads. This removes the dead session/beta/perfetto tracing files, drops telemetry-only file-operation and plugin-fetch helpers, and rewires the affected callers to keep only their real product behavior.

Constraint: Preserve existing user-visible behavior and feature-gated product logic while removing inert tracing/reporting scaffolding
Constraint: Leave GrowthBook in place for now because it functions as the repo's local feature-flag adapter, not a live reporting path
Rejected: Delete growthbook.ts in the same pass | Its call surface is wide and now tied to local product behavior rather than telemetry export
Rejected: Leave no-op tracing and helper modules in place | They continued to create audit noise and implied behavior that no longer existed
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Remaining analytics-named code should be treated as either local compatibility calls or feature-gate infrastructure unless a concrete egress path is reintroduced
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:
2026-04-09 14:26:11 +08:00
parent 5af8acb2bb
commit 9ba783f10b
22 changed files with 11 additions and 1072 deletions

View File

@@ -1,71 +0,0 @@
import { createHash } from 'crypto'
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from 'src/services/analytics/index.js'
import { logEvent } from 'src/services/analytics/index.js'
/**
* Creates a truncated SHA256 hash (16 chars) for file paths
* Used for privacy-preserving analytics on file operations
*/
function hashFilePath(
filePath: string,
): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {
return createHash('sha256')
.update(filePath)
.digest('hex')
.slice(0, 16) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
}
/**
* Creates a full SHA256 hash (64 chars) for file contents
* Used for deduplication and change detection analytics
*/
function hashFileContent(
content: string,
): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {
return createHash('sha256')
.update(content)
.digest('hex') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
}
// Maximum content size to hash (100KB)
// Prevents memory exhaustion when hashing large files (e.g., base64-encoded images)
const MAX_CONTENT_HASH_SIZE = 100 * 1024
/**
* Logs file operation analytics to Statsig
*/
export function logFileOperation(params: {
operation: 'read' | 'write' | 'edit'
tool: 'FileReadTool' | 'FileWriteTool' | 'FileEditTool'
filePath: string
content?: string
type?: 'create' | 'update'
}): void {
const metadata: Record<
string,
| AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
| number
| boolean
> = {
operation:
params.operation as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
tool: params.tool as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
filePathHash: hashFilePath(params.filePath),
}
// Only hash content if it's provided and below size limit
// This prevents memory exhaustion from hashing large files (e.g., base64-encoded images)
if (
params.content !== undefined &&
params.content.length <= MAX_CONTENT_HASH_SIZE
) {
metadata.contentHash = hashFileContent(params.content)
}
if (params.type !== undefined) {
metadata.type =
params.type as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
}
logEvent('tengu_file_operation', metadata)
}

View File

@@ -56,11 +56,6 @@ import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
} from 'src/services/analytics/index.js'
import { ALLOWED_OFFICIAL_MARKETPLACE_NAMES } from './plugins/schemas.js'
import {
startHookSpan,
endHookSpan,
isBetaTracingEnabled,
} from './telemetry/sessionTracing.js'
import {
hookJSONOutputSchema,
promptRequestSchema,
@@ -2065,19 +2060,6 @@ async function* executeHooks({
return
}
// Collect hook definitions for beta tracing telemetry
const hookDefinitionsJson = isBetaTracingEnabled()
? jsonStringify(getHookDefinitionsForTelemetry(matchingHooks))
: '[]'
// Start hook span for beta tracing
const hookSpan = startHookSpan(
hookEvent,
hookName,
matchingHooks.length,
hookDefinitionsJson,
)
// Yield progress messages for each hook before execution
for (const { hook } of matchingHooks) {
yield {
@@ -2930,13 +2912,6 @@ async function* executeHooks({
totalDurationMs,
})
// End hook span for beta tracing
endHookSpan(hookSpan, {
numSuccess: outcomes.success,
numBlocking: outcomes.blocking,
numNonBlockingError: outcomes.non_blocking_error,
numCancelled: outcomes.cancelled,
})
}
export type HookOutsideReplResult = {
@@ -4969,22 +4944,3 @@ export async function executeWorktreeRemoveHook(
return true
}
function getHookDefinitionsForTelemetry(
matchedHooks: MatchedHook[],
): Array<{ type: string; command?: string; prompt?: string; name?: string }> {
return matchedHooks.map(({ hook }) => {
if (hook.type === 'command') {
return { type: 'command', command: hook.command }
} else if (hook.type === 'prompt') {
return { type: 'prompt', prompt: hook.prompt }
} else if (hook.type === 'http') {
return { type: 'http', command: hook.url }
} else if (hook.type === 'function') {
return { type: 'function', name: 'function' }
} else if (hook.type === 'callback') {
return { type: 'callback', name: 'callback' }
}
return { type: 'unknown' }
})
}

View File

@@ -1,135 +0,0 @@
/**
* Telemetry for plugin/marketplace fetches that hit the network.
*
* Added for inc-5046 (GitHub complained about claude-plugins-official load).
* Before this, fetch operations only had logForDebugging — no way to measure
* actual network volume. This surfaces what's hitting GitHub vs GCS vs
* user-hosted so we can see the GCS migration take effect and catch future
* hot-path regressions before GitHub emails us again.
*
* Volume: these fire at startup (install-counts 24h-TTL)
* and on explicit user action (install/update). NOT per-interaction. Similar
* envelope to tengu_binary_download_*.
*/
import {
logEvent,
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as SafeString,
} from '../../services/analytics/index.js'
import { OFFICIAL_MARKETPLACE_NAME } from './officialMarketplace.js'
export type PluginFetchSource =
| 'install_counts'
| 'marketplace_clone'
| 'marketplace_pull'
| 'marketplace_url'
| 'plugin_clone'
| 'mcpb'
export type PluginFetchOutcome = 'success' | 'failure' | 'cache_hit'
// Allowlist of public hosts we report by name. Anything else (enterprise
// git, self-hosted, internal) is bucketed as 'other' — we don't want
// internal hostnames (git.mycorp.internal) landing in telemetry. Bounded
// cardinality also keeps the dashboard host-breakdown tractable.
const KNOWN_PUBLIC_HOSTS = new Set([
'github.com',
'raw.githubusercontent.com',
'objects.githubusercontent.com',
'gist.githubusercontent.com',
'gitlab.com',
'bitbucket.org',
'codeberg.org',
'dev.azure.com',
'ssh.dev.azure.com',
'storage.googleapis.com', // GCS — where Dickson's migration points
])
/**
* Extract hostname from a URL or git spec and bucket to the allowlist.
* Handles `https://host/...`, `git@host:path`, `ssh://host/...`.
* Returns a known public host, 'other' (parseable but not allowlisted —
* don't leak private hostnames), or 'unknown' (unparseable / local path).
*/
function extractHost(urlOrSpec: string): string {
let host: string
const scpMatch = /^[^@/]+@([^:/]+):/.exec(urlOrSpec)
if (scpMatch) {
host = scpMatch[1]!
} else {
try {
host = new URL(urlOrSpec).hostname
} catch {
return 'unknown'
}
}
const normalized = host.toLowerCase()
return KNOWN_PUBLIC_HOSTS.has(normalized) ? normalized : 'other'
}
/**
* True if the URL/spec points at anthropics/claude-plugins-official — the
* repo GitHub complained about. Lets the dashboard separate "our problem"
* traffic from user-configured marketplaces.
*/
function isOfficialRepo(urlOrSpec: string): boolean {
return urlOrSpec.includes(`anthropics/${OFFICIAL_MARKETPLACE_NAME}`)
}
export function logPluginFetch(
source: PluginFetchSource,
urlOrSpec: string | undefined,
outcome: PluginFetchOutcome,
durationMs: number,
errorKind?: string,
): void {
// String values are bounded enums / hostname-only — no code, no paths,
// no raw error messages. Same privacy envelope as tengu_web_fetch_host.
logEvent('tengu_plugin_remote_fetch', {
source: source as SafeString,
host: (urlOrSpec ? extractHost(urlOrSpec) : 'unknown') as SafeString,
is_official: urlOrSpec ? isOfficialRepo(urlOrSpec) : false,
outcome: outcome as SafeString,
duration_ms: Math.round(durationMs),
...(errorKind && { error_kind: errorKind as SafeString }),
})
}
/**
* Classify an error into a stable bucket for the error_kind field. Keeps
* cardinality bounded — raw error messages would explode dashboard grouping.
*
* Handles both axios Error objects (Node.js error codes like ENOTFOUND) and
* git stderr strings (human phrases like "Could not resolve host"). DNS
* checked BEFORE timeout because gitClone's error enhancement at
* marketplaceManager.ts:~950 rewrites DNS failures to include the word
* "timeout" — ordering the other way would misclassify git DNS as timeout.
*/
export function classifyFetchError(error: unknown): string {
const msg = String((error as { message?: unknown })?.message ?? error)
if (
/ENOTFOUND|ECONNREFUSED|EAI_AGAIN|Could not resolve host|Connection refused/i.test(
msg,
)
) {
return 'dns_or_refused'
}
if (/ETIMEDOUT|timed out|timeout/i.test(msg)) return 'timeout'
if (
/ECONNRESET|socket hang up|Connection reset by peer|remote end hung up/i.test(
msg,
)
) {
return 'conn_reset'
}
if (/403|401|authentication|permission denied/i.test(msg)) return 'auth'
if (/404|not found|repository not found/i.test(msg)) return 'not_found'
if (/certificate|SSL|TLS|unable to get local issuer/i.test(msg)) return 'tls'
// Schema validation throws "Invalid response format" (install_counts) —
// distinguish from true unknowns so the dashboard can
// see "server sent garbage" separately.
if (/Invalid response format|Invalid marketplace schema/i.test(msg)) {
return 'invalid_schema'
}
return 'other'
}

View File

@@ -17,7 +17,6 @@ import { errorMessage, getErrnoCode } from '../errors.js'
import { getFsImplementation } from '../fsOperations.js'
import { logError } from '../log.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { getPluginsDirectory } from './pluginDirectories.js'
const INSTALL_COUNTS_CACHE_VERSION = 1
@@ -196,21 +195,8 @@ async function fetchInstallCountsFromGitHub(): Promise<
throw new Error('Invalid response format from install counts API')
}
logPluginFetch(
'install_counts',
INSTALL_COUNTS_URL,
'success',
performance.now() - started,
)
return response.data.plugins
} catch (error) {
logPluginFetch(
'install_counts',
INSTALL_COUNTS_URL,
'failure',
performance.now() - started,
classifyFetchError(error),
)
throw error
}
}
@@ -227,7 +213,6 @@ export async function getInstallCounts(): Promise<Map<string, number> | null> {
const cache = await loadInstallCountsCache()
if (cache) {
logForDebugging('Using cached install counts')
logPluginFetch('install_counts', INSTALL_COUNTS_URL, 'cache_hit', 0)
const map = new Map<string, number>()
for (const entry of cache.counts) {
map.set(entry.plugin, entry.unique_installs)

View File

@@ -53,7 +53,6 @@ import {
getAddDirExtraMarketplaces,
} from './addDirPluginSettings.js'
import { markPluginVersionOrphaned } from './cacheUtils.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { removeAllPluginsForMarketplace } from './installedPluginsManager.js'
import {
extractHostFromSource,
@@ -1110,13 +1109,7 @@ async function cacheMarketplaceFromGit(
disableCredentialHelper: options?.disableCredentialHelper,
sparsePaths,
})
logPluginFetch(
'marketplace_pull',
gitUrl,
pullResult.code === 0 ? 'success' : 'failure',
performance.now() - pullStarted,
pullResult.code === 0 ? undefined : classifyFetchError(pullResult.stderr),
)
void pullStarted
if (pullResult.code === 0) return
logForDebugging(`git pull failed, will re-clone: ${pullResult.stderr}`, {
level: 'warn',
@@ -1156,13 +1149,7 @@ async function cacheMarketplaceFromGit(
)
const cloneStarted = performance.now()
const result = await gitClone(gitUrl, cachePath, ref, sparsePaths)
logPluginFetch(
'marketplace_clone',
gitUrl,
result.code === 0 ? 'success' : 'failure',
performance.now() - cloneStarted,
result.code === 0 ? undefined : classifyFetchError(result.stderr),
)
void cloneStarted
if (result.code !== 0) {
// Clean up any partial directory created by the failed clone so the next
// attempt starts fresh. Best-effort: if this fails, the stale dir will be
@@ -1284,13 +1271,6 @@ async function cacheMarketplaceFromUrl(
headers,
})
} catch (error) {
logPluginFetch(
'marketplace_url',
url,
'failure',
performance.now() - fetchStarted,
classifyFetchError(error),
)
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
throw new Error(
@@ -1317,25 +1297,13 @@ async function cacheMarketplaceFromUrl(
// Validate the response is a valid marketplace
const result = PluginMarketplaceSchema().safeParse(response.data)
if (!result.success) {
logPluginFetch(
'marketplace_url',
url,
'failure',
performance.now() - fetchStarted,
'invalid_schema',
)
throw new ConfigParseError(
`Invalid marketplace schema from URL: ${result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
redactedUrl,
response.data,
)
}
logPluginFetch(
'marketplace_url',
url,
'success',
performance.now() - fetchStarted,
)
void fetchStarted
safeCallProgress(onProgress, 'Saving marketplace to cache')
// Ensure cache directory exists

View File

@@ -20,7 +20,6 @@ import {
} from '../settings/settings.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { getSystemDirectories } from '../systemDirectories.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
/**
* User configuration values for MCPB
*/
@@ -490,7 +489,6 @@ async function downloadMcpb(
}
const started = performance.now()
let fetchTelemetryFired = false
try {
const response = await axios.get(url, {
timeout: 120000, // 2 minute timeout
@@ -507,11 +505,6 @@ async function downloadMcpb(
})
const data = new Uint8Array(response.data)
// Fire telemetry before writeFile — the event measures the network
// fetch, not disk I/O. A writeFile EACCES would otherwise match
// classifyFetchError's /permission denied/ → misreport as auth.
logPluginFetch('mcpb', url, 'success', performance.now() - started)
fetchTelemetryFired = true
// Save to disk (binary data)
await writeFile(destPath, Buffer.from(data))
@@ -523,15 +516,7 @@ async function downloadMcpb(
return data
} catch (error) {
if (!fetchTelemetryFired) {
logPluginFetch(
'mcpb',
url,
'failure',
performance.now() - started,
classifyFetchError(error),
)
}
void started
const errorMsg = errorMessage(error)
const fullError = new Error(
`Failed to download MCPB file from ${url}: ${errorMsg}`,

View File

@@ -85,7 +85,6 @@ import { SettingsSchema } from '../settings/types.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { getAddDirEnabledPlugins } from './addDirPluginSettings.js'
import { verifyAndDemote } from './dependencyResolver.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { checkGitAvailable } from './gitAvailability.js'
import { getInMemoryInstalledPlugins } from './installedPluginsManager.js'
import { getManagedPluginNames } from './managedPlugins.js'
@@ -563,13 +562,6 @@ export async function gitClone(
const cloneResult = await execFileNoThrow(gitExe(), args)
if (cloneResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(cloneResult.stderr),
)
throw new Error(`Failed to clone repository: ${cloneResult.stderr}`)
}
@@ -595,13 +587,6 @@ export async function gitClone(
)
if (unshallowResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(unshallowResult.stderr),
)
throw new Error(
`Failed to fetch commit ${sha}: ${unshallowResult.stderr}`,
)
@@ -616,27 +601,12 @@ export async function gitClone(
)
if (checkoutResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(checkoutResult.stderr),
)
throw new Error(
`Failed to checkout commit ${sha}: ${checkoutResult.stderr}`,
)
}
}
// Fire success only after ALL network ops (clone + optional SHA fetch)
// complete — same telemetry-scope discipline as mcpb and marketplace_url.
logPluginFetch(
'plugin_clone',
gitUrl,
'success',
performance.now() - cloneStarted,
)
void cloneStarted
}
/**

View File

@@ -9,7 +9,6 @@ import type {
import { logEvent } from '../../services/analytics/index.js'
import type { PermissionMode } from '../../types/permissions.js'
import { createUserMessage } from '../messages.js'
import { startInteractionSpan } from '../telemetry/sessionTracing.js'
import {
matchesKeepGoingKeyword,
matchesNegativeKeyword,
@@ -34,7 +33,6 @@ export function processTextPrompt(
typeof input === 'string'
? input
: input.find(block => block.type === 'text')?.text || ''
startInteractionSpan(userPromptText)
const isNegative = matchesNegativeKeyword(userPromptText)
const isKeepGoing = matchesKeepGoingKeyword(userPromptText)

View File

@@ -96,7 +96,6 @@ import {
readMailbox,
writeToMailbox,
} from '../teammateMailbox.js'
import { unregisterAgent as unregisterPerfettoAgent } from '../telemetry/perfettoTracing.js'
import { createContentReplacementState } from '../toolResultStorage.js'
import { TEAM_LEAD_NAME } from './constants.js'
import {
@@ -1460,7 +1459,6 @@ export async function runInProcessTeammate(
})
}
unregisterPerfettoAgent(identity.agentId)
return { success: true, messages: allMessages }
} catch (error) {
const errorMessage =
@@ -1524,7 +1522,6 @@ export async function runInProcessTeammate(
},
)
unregisterPerfettoAgent(identity.agentId)
return {
success: false,
error: errorMessage,

View File

@@ -35,11 +35,6 @@ import {
STOPPED_DISPLAY_MS,
} from '../task/framework.js'
import { createTeammateContext } from '../teammateContext.js'
import {
isPerfettoTracingEnabled,
registerAgent as registerPerfettoAgent,
unregisterAgent as unregisterPerfettoAgent,
} from '../telemetry/perfettoTracing.js'
import { removeMemberByAgentId } from './teamHelpers.js'
type SetAppStateFn = (updater: (prev: AppState) => AppState) => void
@@ -146,11 +141,6 @@ export async function spawnInProcessTeammate(
abortController,
})
// Register agent in Perfetto trace for hierarchy visualization
if (isPerfettoTracingEnabled()) {
registerPerfettoAgent(agentId, name, parentSessionId)
}
// Create task state
const description = `${name}: ${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}`
@@ -319,10 +309,5 @@ export function killInProcessTeammate(
)
}
// Release perfetto agent registry entry
if (agentId) {
unregisterPerfettoAgent(agentId)
}
return killed
}

View File

@@ -1,86 +0,0 @@
/**
* Detailed beta tracing egress is disabled in this build.
*
* The exported helpers remain for compile-time compatibility, but do not
* retain tracing state or emit tracing attributes.
*/
type AttributeValue = string | number | boolean
export interface SpanAttributeWriter {
setAttribute?(_key: string, _value: AttributeValue): void
setAttributes?(_attributes: Record<string, AttributeValue>): void
}
export interface LLMRequestNewContext {
systemPrompt?: string
querySource?: string
tools?: string
}
const MAX_CONTENT_SIZE = 60 * 1024
export function clearBetaTracingState(): void {
return
}
export function isBetaTracingEnabled(): boolean {
return false
}
export function truncateContent(
content: string,
maxSize: number = MAX_CONTENT_SIZE,
): { content: string; truncated: boolean } {
if (content.length <= maxSize) {
return { content, truncated: false }
}
return {
content:
content.slice(0, maxSize) +
'\n\n[TRUNCATED - Content exceeds 60KB limit]',
truncated: true,
}
}
export function addBetaInteractionAttributes(
_span: SpanAttributeWriter,
_userPrompt: string,
): void {
return
}
export function addBetaLLMRequestAttributes(
_span: SpanAttributeWriter,
_newContext?: LLMRequestNewContext,
_messagesForAPI?: unknown[],
): void {
return
}
export function addBetaLLMResponseAttributes(
_attributes: Record<string, AttributeValue>,
_metadata?: {
modelOutput?: string
thinkingOutput?: string
},
): void {
return
}
export function addBetaToolInputAttributes(
_span: SpanAttributeWriter,
_toolName: string,
_toolInput: string,
): void {
return
}
export function addBetaToolResultAttributes(
_attributes: Record<string, AttributeValue>,
_toolName: string | number | boolean,
_toolResult: string,
): void {
return
}

View File

@@ -1,157 +0,0 @@
/**
* Perfetto tracing is disabled in this build.
*
* The original implementation wrote detailed local trace files containing
* request, tool, and interaction metadata. This compatibility layer keeps the
* API surface intact while ensuring no trace files are created.
*/
export type TraceEventPhase =
| 'B'
| 'E'
| 'X'
| 'i'
| 'C'
| 'b'
| 'n'
| 'e'
| 'M'
export type TraceEvent = {
name: string
cat: string
ph: TraceEventPhase
ts: number
pid: number
tid: number
dur?: number
args?: Record<string, unknown>
id?: string
scope?: string
}
export function initializePerfettoTracing(): void {
return
}
export function isPerfettoTracingEnabled(): boolean {
return false
}
export function registerAgent(
_agentId: string,
_agentName: string,
_parentAgentId?: string,
): void {
return
}
export function unregisterAgent(_agentId: string): void {
return
}
export function startLLMRequestPerfettoSpan(_args: {
model: string
promptTokens?: number
messageId?: string
isSpeculative?: boolean
querySource?: string
}): string {
return ''
}
export function endLLMRequestPerfettoSpan(
_spanId: string,
_metadata: {
ttftMs?: number
ttltMs?: number
promptTokens?: number
outputTokens?: number
cacheReadTokens?: number
cacheCreationTokens?: number
messageId?: string
success?: boolean
error?: string
requestSetupMs?: number
attemptStartTimes?: number[]
},
): void {
return
}
export function startToolPerfettoSpan(
_toolName: string,
_args?: Record<string, unknown>,
): string {
return ''
}
export function endToolPerfettoSpan(
_spanId: string,
_metadata?: {
success?: boolean
error?: string
resultTokens?: number
},
): void {
return
}
export function startUserInputPerfettoSpan(_context?: string): string {
return ''
}
export function endUserInputPerfettoSpan(
_spanId: string,
_metadata?: {
decision?: string
source?: string
},
): void {
return
}
export function emitPerfettoInstant(
_name: string,
_category: string,
_args?: Record<string, unknown>,
): void {
return
}
export function emitPerfettoCounter(
_name: string,
_values: Record<string, number>,
): void {
return
}
export function startInteractionPerfettoSpan(_userPrompt?: string): string {
return ''
}
export function endInteractionPerfettoSpan(_spanId: string): void {
return
}
export function getPerfettoEvents(): TraceEvent[] {
return []
}
export function resetPerfettoTracer(): void {
return
}
export async function triggerPeriodicWriteForTesting(): Promise<void> {
return
}
export function evictStaleSpansForTesting(): void {
return
}
export const MAX_EVENTS_FOR_TESTING = 0
export function evictOldestEventsForTesting(): void {
return
}

View File

@@ -1,172 +0,0 @@
/**
* OpenTelemetry session tracing is disabled in this build.
*
* This module preserves the tracing API surface for callers, but all exported
* operations are local no-ops and never collect or forward tracing data.
*/
export { isBetaTracingEnabled, type LLMRequestNewContext } from './betaSessionTracing.js'
export interface Span {
end(): void
setAttribute(
_key: string,
_value: string | number | boolean,
): void
setAttributes(
_attributes: Record<string, string | number | boolean>,
): void
addEvent(
_eventName: string,
_attributes?: Record<string, string | number | boolean>,
): void
recordException(_error: Error): void
}
class NoopSpan implements Span {
end(): void {}
setAttribute(
_key: string,
_value: string | number | boolean,
): void {}
setAttributes(
_attributes: Record<string, string | number | boolean>,
): void {}
addEvent(
_eventName: string,
_attributes?: Record<string, string | number | boolean>,
): void {}
recordException(_error: Error): void {}
}
const NOOP_SPAN: Span = new NoopSpan()
type LLMRequestMetadata = {
inputTokens?: number
outputTokens?: number
cacheReadTokens?: number
cacheCreationTokens?: number
success?: boolean
statusCode?: number
error?: string
attempt?: number
modelResponse?: string
modelOutput?: string
thinkingOutput?: string
hasToolCall?: boolean
ttftMs?: number
requestSetupMs?: number
attemptStartTimes?: number[]
}
type HookSpanMetadata = {
numSuccess?: number
numBlocking?: number
numNonBlockingError?: number
numCancelled?: number
}
export function isEnhancedTelemetryEnabled(): boolean {
return false
}
export function startInteractionSpan(_userPrompt: string): Span {
return NOOP_SPAN
}
export function endInteractionSpan(): void {
return
}
export function startLLMRequestSpan(
_model: string,
_newContext?: import('./betaSessionTracing.js').LLMRequestNewContext,
_messagesForAPI?: unknown[],
_fastMode?: boolean,
): Span {
return NOOP_SPAN
}
export function endLLMRequestSpan(
_span?: Span,
_metadata?: LLMRequestMetadata,
): void {
return
}
export function startToolSpan(
_toolName: string,
_toolAttributes?: Record<string, string | number | boolean>,
_toolInput?: string,
): Span {
return NOOP_SPAN
}
export function startToolBlockedOnUserSpan(): Span {
return NOOP_SPAN
}
export function endToolBlockedOnUserSpan(
_decision?: string,
_source?: string,
): void {
return
}
export function startToolExecutionSpan(): Span {
return NOOP_SPAN
}
export function endToolExecutionSpan(metadata?: {
success?: boolean
error?: string
}): void {
void metadata
return
}
export function endToolSpan(
_toolResult?: string,
_resultTokens?: number,
): void {
return
}
export function addToolContentEvent(
_eventName: string,
_attributes: Record<string, string | number | boolean>,
): void {
return
}
export function getCurrentSpan(): Span | null {
return null
}
export async function executeInSpan<T>(
_spanName: string,
fn: (span: Span) => Promise<T>,
_attributes?: Record<string, string | number | boolean>,
): Promise<T> {
return fn(NOOP_SPAN)
}
export function startHookSpan(
_hookEvent: string,
_hookName: string,
_numHooks: number,
_hookDefinitions: string,
): Span {
return NOOP_SPAN
}
export function endHookSpan(
_span: Span,
_metadata?: HookSpanMetadata,
): void {
return
}