diff --git a/spx-gui/src/apps/account/.env b/spx-gui/src/apps/account/.env index 207f5346df..b88fe54092 100644 --- a/spx-gui/src/apps/account/.env +++ b/spx-gui/src/apps/account/.env @@ -30,6 +30,7 @@ VITE_AVATAR_BASE_URL="" VITE_SENTRY_DSN="" VITE_SENTRY_TRACES_SAMPLE_RATE="0.8" VITE_SENTRY_LSP_SAMPLE_RATE="0.1" +VITE_SENTRY_AI_SAMPLE_RATE="1" # Default language for app account, e.g. `en`, `zh`. VITE_DEFAULT_LANG="en" diff --git a/spx-gui/src/apps/account/env.ts b/spx-gui/src/apps/account/env.ts index 8f9c347f60..20cbf668dd 100644 --- a/spx-gui/src/apps/account/env.ts +++ b/spx-gui/src/apps/account/env.ts @@ -1,8 +1,10 @@ export const defaultLang = (import.meta.env.VITE_DEFAULT_LANG as string) || 'en' const sentryTracesSampleRate = parseFloat(import.meta.env.VITE_SENTRY_TRACES_SAMPLE_RATE as string) const sentryLSPSampleRate = parseFloat(import.meta.env.VITE_SENTRY_LSP_SAMPLE_RATE as string) +const sentryAISampleRate = parseFloat(import.meta.env.VITE_SENTRY_AI_SAMPLE_RATE as string) export const sentry = { dsn: (import.meta.env.VITE_SENTRY_DSN as string) || '', tracesSampleRate: Number.isNaN(sentryTracesSampleRate) ? 0.1 : sentryTracesSampleRate, - lspSampleRate: Number.isNaN(sentryLSPSampleRate) ? 0.1 : sentryLSPSampleRate + lspSampleRate: Number.isNaN(sentryLSPSampleRate) ? 0.1 : sentryLSPSampleRate, + aiSampleRate: Number.isNaN(sentryAISampleRate) ? 1 : sentryAISampleRate } diff --git a/spx-gui/src/apps/xbuilder/.env b/spx-gui/src/apps/xbuilder/.env index a1bd316221..ced0406be6 100644 --- a/spx-gui/src/apps/xbuilder/.env +++ b/spx-gui/src/apps/xbuilder/.env @@ -64,6 +64,10 @@ VITE_SENTRY_TRACES_SAMPLE_RATE="0.8" # 0.0 means no LSP events are sent, 1.0 means all LSP events are sent. VITE_SENTRY_LSP_SAMPLE_RATE="0.1" +# Sampling rate for independent AI traces (ai.think and later ai.* roots). +# Kept separate from pageload so Think is not all-or-none with the page. +VITE_SENTRY_AI_SAMPLE_RATE="1" + # If tutorials entry should be shown in the Navbar. VITE_SHOW_TUTORIALS_ENTRY="false" diff --git a/spx-gui/src/apps/xbuilder/env.ts b/spx-gui/src/apps/xbuilder/env.ts index adc1e3a52a..e51b38a84d 100644 --- a/spx-gui/src/apps/xbuilder/env.ts +++ b/spx-gui/src/apps/xbuilder/env.ts @@ -12,8 +12,10 @@ export const defaultFontPreferences = (import.meta.env.VITE_DEFAULT_FONT_PREFERE export const accountOAuthClientId = import.meta.env.VITE_ACCOUNT_OAUTH_CLIENT_ID as string const sentryTracesSampleRate = parseFloat(import.meta.env.VITE_SENTRY_TRACES_SAMPLE_RATE as string) const sentryLSPSampleRate = parseFloat(import.meta.env.VITE_SENTRY_LSP_SAMPLE_RATE as string) +const sentryAISampleRate = parseFloat(import.meta.env.VITE_SENTRY_AI_SAMPLE_RATE as string) export const sentry = { dsn: (import.meta.env.VITE_SENTRY_DSN as string) || '', tracesSampleRate: Number.isNaN(sentryTracesSampleRate) ? 0.1 : sentryTracesSampleRate, - lspSampleRate: Number.isNaN(sentryLSPSampleRate) ? 0.1 : sentryLSPSampleRate + lspSampleRate: Number.isNaN(sentryLSPSampleRate) ? 0.1 : sentryLSPSampleRate, + aiSampleRate: Number.isNaN(sentryAISampleRate) ? 1 : sentryAISampleRate } diff --git a/spx-gui/src/components/project/runner/ProjectRunner.vue b/spx-gui/src/components/project/runner/ProjectRunner.vue index d3c5497742..f5b679cf22 100644 --- a/spx-gui/src/components/project/runner/ProjectRunner.vue +++ b/spx-gui/src/components/project/runner/ProjectRunner.vue @@ -52,10 +52,80 @@ type RunnerFiles = { [path: string]: RunnerFile } +/** Parent-page Sentry hooks injected into the runner iframe. WASM calls these during Think / Archive. */ +type AISentryBridge = { + startThink: (info: { thinkId: string; startTime: number; gameSessionId: string }) => { + sentryTrace: string + baggage: string + } + endThink: (info: { + thinkId: string + endTime: number + outcome?: string + category?: string + reason?: string + turnCount?: number + attemptCount?: number + exception?: { + message: string + userMsg?: string + commandName?: string + commandArgs?: string + turnIndex?: number + attempt?: number + category?: string + reason?: string + gameSessionId?: string + } + }) => void + startAttempt: (info: { thinkId: string; startTime: number; turnIndex: number; attempt: number }) => void + endAttempt: (info: { + thinkId: string + endTime: number + ok: boolean + backendCategory?: string + backendReason?: string + requestId?: string + }) => void + startCommand: (info: { thinkId: string; startTime: number; turnIndex: number; commandName: string }) => void + endCommand: (info: { thinkId: string; endTime: number; ok: boolean; errorMessage: string }) => void + startArchive: (info: { archiveId: string; startTime: number; gameSessionId: string }) => { + sentryTrace: string + baggage: string + } + endArchive: (info: { + archiveId: string + endTime: number + ok?: boolean + attemptCount?: number + category?: string + reason?: string + exception?: { + message: string + error?: string + attemptCount?: number + category?: string + reason?: string + gameSessionId?: string + } + }) => void + startArchiveAttempt: (info: { archiveId: string; startTime: number; attempt: number }) => void + endArchiveAttempt: (info: { + archiveId: string + endTime: number + ok: boolean + backendCategory?: string + backendReason?: string + requestId?: string + }) => void +} + interface RunnerIframeWindow extends Window { xbuilder_set_ai_interaction_api_endpoint: (endpoint: string) => void xbuilder_set_ai_interaction_api_token_provider: (provider: () => Promise) => void xbuilder_set_ai_description: (description: string) => void + xbuilder_set_game_session_id: (id: string) => void + xbuilder_set_sentry_bridge: (bridge: AISentryBridge) => void /** Set the current logged-in username, injected into the spx runtime before running. */ xbuilder_set_username: (username: string) => void /** Init the engine. Can be called early; project-agnostic. */ @@ -145,6 +215,7 @@ type State =