Skip to content

Commit 0c8031a

Browse files
committed
feat(seer): Poll Seer Explorer by UUID
Adopt the run's UUID (sentry_run_id) for Seer Explorer polling instead of the numeric run id, mirroring the search agent. The run id widens to number | string and new runs take sentry_run_id ?? run_id. Existing numeric-keyed runs keep working unchanged (the endpoint resolves both forms). The persisted run id and explorerRunId deep links accept UUIDs too.
1 parent ea7423d commit 0c8031a

12 files changed

Lines changed: 63 additions & 39 deletions

File tree

static/app/utils/analytics/seerAnalyticsEvents.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {Organization} from 'sentry/types/organization';
2+
import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
23

34
export type SeerAnalyticsEventsParameters = {
45
'ai_query.applied': {
@@ -136,7 +137,7 @@ export type SeerAnalyticsEventsParameters = {
136137
conversations_url: string | undefined;
137138
explorer_url: string | undefined;
138139
langfuse_url: string | undefined;
139-
run_id: number | undefined;
140+
run_id: SeerExplorerRunId | undefined;
140141
type: 'positive' | 'negative';
141142
};
142143
'seer.explorer.global_panel.opened': {
@@ -158,7 +159,7 @@ export type SeerAnalyticsEventsParameters = {
158159
};
159160
'seer.explorer.session_link_copied': Record<string, unknown>;
160161
'seer.explorer.timed_out': {
161-
run_id: number | null;
162+
run_id: SeerExplorerRunId | null;
162163
};
163164
};
164165

static/app/views/seerExplorer/components/chat/assistant.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
1111
import {useOrganization} from 'sentry/utils/useOrganization';
1212
import {useSessionStorage} from 'sentry/utils/useSessionStorage';
1313
import {getConversationsUrlForExternalUse} from 'sentry/views/explore/conversations/utils/urlParams';
14-
import type {Block} from 'sentry/views/seerExplorer/types';
14+
import type {Block, SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
1515
import {getExplorerUrl, getLangfuseUrl} from 'sentry/views/seerExplorer/utils';
1616

1717
import type {AssistantBlockProps} from './shared';
@@ -53,7 +53,11 @@ export function AssistantBlock({
5353
);
5454
}
5555

56-
function useBlockFeedback(block: Block, blockIndex: number, runId: number | undefined) {
56+
function useBlockFeedback(
57+
block: Block,
58+
blockIndex: number,
59+
runId: SeerExplorerRunId | undefined
60+
) {
5761
const organization = useOrganization();
5862
const [feedbackSubmitted, setFeedbackSubmitted] = useSessionStorage(
5963
`seer-explorer-feedback:run-${runId ?? 'null'}:block-${block.id}`,

static/app/views/seerExplorer/components/chat/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {motion} from 'framer-motion';
33
import {Container} from '@sentry/scraps/layout';
44

55
import {unreachable} from 'sentry/utils/unreachable';
6-
import type {Block} from 'sentry/views/seerExplorer/types';
6+
import type {Block, SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
77

88
import {AssistantBlock} from './assistant';
99
import {ToolUseBlock} from './toolUse';
@@ -18,7 +18,7 @@ interface BlockProps {
1818
onClick?: () => void;
1919
readOnly?: boolean;
2020
ref?: React.Ref<HTMLDivElement>;
21-
runId?: number;
21+
runId?: SeerExplorerRunId;
2222
showThinking?: boolean;
2323
}
2424

static/app/views/seerExplorer/components/chat/shared.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {Link} from '@sentry/scraps/link';
88
import {Markdown, type MarkdownProps} from '@sentry/scraps/markdown';
99
import {Heading} from '@sentry/scraps/text';
1010

11-
import type {Block} from 'sentry/views/seerExplorer/types';
11+
import type {Block, SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
1212

1313
interface BlockVariantProps {
1414
block: Block;
@@ -20,7 +20,7 @@ export interface AssistantBlockProps extends BlockVariantProps {
2020
blockIndex: number;
2121
interactionPending?: boolean;
2222
readOnly?: boolean;
23-
runId?: number;
23+
runId?: SeerExplorerRunId;
2424
}
2525

2626
export interface ToolUseBlockProps extends BlockVariantProps {

static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
77
import {useOrganization} from 'sentry/utils/useOrganization';
88
import {ExplorerDrawerContent} from 'sentry/views/seerExplorer/components/drawer/explorerDrawerContent';
99
import {useSeerExplorerChatDispatch} from 'sentry/views/seerExplorer/seerExplorerChatStateContext';
10+
import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
1011
import {isSeerExplorerEnabled, usePageReferrer} from 'sentry/views/seerExplorer/utils';
1112

1213
export type OpenSeerExplorerDrawerOptions = {
@@ -19,7 +20,7 @@ export type OpenSeerExplorerDrawerOptions = {
1920
* Optional run ID to open. If provided, opens an existing session.
2021
* Cannot be used together with `startNewRun`.
2122
*/
22-
runId?: number;
23+
runId?: SeerExplorerRunId;
2324
/**
2425
* If true, switches to a new session before opening.
2526
* Cannot be used together with `runId`.

static/app/views/seerExplorer/components/emptyState.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {Flex} from '@sentry/scraps/layout';
77
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
88
import {IconSeer} from 'sentry/icons';
99
import {t, tct} from 'sentry/locale';
10+
import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
1011

1112
const SUGGESTED_QUESTIONS = [
1213
t('Which of my open issues are getting worse, not better?'),
@@ -19,7 +20,7 @@ interface EmptyStateProps {
1920
isError?: boolean;
2021
isLoading?: boolean;
2122
onSuggestionClick?: (question: string) => void;
22-
runId?: number | null;
23+
runId?: SeerExplorerRunId | null;
2324
}
2425

2526
export function EmptyState({

static/app/views/seerExplorer/hooks/useSeerExplorer.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
Block,
2121
RepoPRState,
2222
SeerExplorerResponse,
23+
SeerExplorerRunId,
2324
} from 'sentry/views/seerExplorer/types';
2425
import {
2526
isSeerExplorerEnabled,
@@ -30,6 +31,7 @@ import {
3031
type SeerExplorerChatResponse = {
3132
message: Block;
3233
run_id: number;
34+
sentry_run_id?: string | null;
3335
};
3436

3537
type SeerExplorerUpdateResponse = {
@@ -170,7 +172,7 @@ export const useSeerExplorer = () => {
170172
overrideCtxEngEnable: boolean;
171173
pageName: string;
172174
query: string;
173-
runId: number | null;
175+
runId: SeerExplorerRunId | null;
174176
screenshot: string | undefined;
175177
}
176178
>({
@@ -209,8 +211,11 @@ export const useSeerExplorer = () => {
209211
},
210212
onSuccess: (response, params) => {
211213
if (params.runId === null) {
212-
// set run ID if this is a new session
213-
dispatch({type: 'set run id', payload: response.run_id});
214+
// Prefer the UUID; fall back to the numeric run_id for legacy runs.
215+
dispatch({
216+
type: 'set run id',
217+
payload: response.sentry_run_id ?? response.run_id,
218+
});
214219
} else {
215220
// invalidate the query so fresh data is fetched
216221
queryClient.invalidateQueries({
@@ -243,7 +248,7 @@ export const useSeerExplorer = () => {
243248
{
244249
inputId: string;
245250
orgSlug: string;
246-
runId: number | null;
251+
runId: SeerExplorerRunId | null;
247252
responseData?: Record<string, any>;
248253
}
249254
>({
@@ -309,7 +314,7 @@ export const useSeerExplorer = () => {
309314
const {mutate: createPRMutate} = useMutation<
310315
SeerExplorerUpdateResponse,
311316
RequestError,
312-
{orgSlug: string; runId: number | null; repoName?: string}
317+
{orgSlug: string; runId: SeerExplorerRunId | null; repoName?: string}
313318
>({
314319
mutationFn: async params => {
315320
setHasSentInterrupt(false);
@@ -367,7 +372,7 @@ export const useSeerExplorer = () => {
367372
RequestError,
368373
{
369374
orgSlug: string;
370-
runId: number | null;
375+
runId: SeerExplorerRunId | null;
371376
}
372377
>({
373378
mutationFn: async params => {
@@ -405,7 +410,7 @@ export const useSeerExplorer = () => {
405410

406411
/** Switches to a different run and fetches its latest state. */
407412
const switchToRun = useCallback(
408-
(newRunId: number | null, {onSuccess}: {onSuccess?: () => void} = {}) => {
413+
(newRunId: SeerExplorerRunId | null, {onSuccess}: {onSuccess?: () => void} = {}) => {
409414
if (newRunId === runId) {
410415
return;
411416
}
@@ -436,7 +441,11 @@ export const useSeerExplorer = () => {
436441
);
437442

438443
const sendMessage = useCallback(
439-
(query: string, explicitInsertIndex?: number, explicitRunId?: number | null) => {
444+
(
445+
query: string,
446+
explicitInsertIndex?: number,
447+
explicitRunId?: SeerExplorerRunId | null
448+
) => {
440449
if (!orgSlug) {
441450
return;
442451
}
@@ -643,7 +652,6 @@ export const useSeerExplorer = () => {
643652
];
644653

645654
const baseSession = rawSessionData ?? {
646-
run_id: runId ?? undefined,
647655
blocks: [],
648656
status: 'processing' as const,
649657
updated_at: new Date().toISOString(),

static/app/views/seerExplorer/hooks/useSeerExplorerPolling.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import {useApiQuery} from 'sentry/utils/queryClient';
55
import {useOrganization} from 'sentry/utils/useOrganization';
66
import {useTimeout} from 'sentry/utils/useTimeout';
77
import type {PollingState} from 'sentry/views/seerExplorer/seerExplorerChatStateContext';
8-
import type {SeerExplorerResponse} from 'sentry/views/seerExplorer/types';
8+
import type {
9+
SeerExplorerResponse,
10+
SeerExplorerRunId,
11+
} from 'sentry/views/seerExplorer/types';
912
import type {Block} from 'sentry/views/seerExplorer/types';
1013
import {
1114
isSeerExplorerEnabled,
@@ -36,7 +39,7 @@ const getTimestampAge = (updatedAt: string | undefined): number | null => {
3639
};
3740

3841
const getPollingState = (
39-
runId: number | null,
42+
runId: SeerExplorerRunId | null,
4043
sessionData: SeerExplorerResponse['session'] | undefined,
4144
isError: boolean,
4245
errorStatusCode: number | undefined,
@@ -70,7 +73,7 @@ const getPollingState = (
7073
* Called exclusively by `SeerExplorerChatStateProvider`, which dispatches the
7174
* derived polling state into context for all consumers.
7275
*/
73-
export const useSeerExplorerPolling = ({runId}: {runId: number | null}) => {
76+
export const useSeerExplorerPolling = ({runId}: {runId: SeerExplorerRunId | null}) => {
7477
const organization = useOrganization({allowNull: true});
7578
const orgSlug = organization?.slug;
7679
const errorPollCountRef = useRef(0);

static/app/views/seerExplorer/seerExplorerChatStateContext.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010

1111
import {sessionStorageWrapper} from 'sentry/utils/sessionStorage';
1212
import {useSeerExplorerPolling} from 'sentry/views/seerExplorer/hooks/useSeerExplorerPolling';
13+
import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types';
1314

1415
export type PollingState =
1516
| 'polling'
@@ -22,24 +23,24 @@ type ChatState = {
2223
};
2324

2425
type SeerExplorerChatState = {
25-
chatStates: Record<number, ChatState>;
26-
runId: number | null;
26+
chatStates: Record<SeerExplorerRunId, ChatState>;
27+
runId: SeerExplorerRunId | null;
2728
};
2829

2930
type ChatStateAction =
30-
| {payload: {polling: PollingState; runId: number}; type: 'set polling'}
31-
| {payload: number | null; type: 'set run id'};
31+
| {payload: {polling: PollingState; runId: SeerExplorerRunId}; type: 'set polling'}
32+
| {payload: SeerExplorerRunId | null; type: 'set run id'};
3233

3334
const RUN_ID_STORAGE_KEY = 'seer-explorer-run-id';
3435

35-
function readRunIdFromStorage(): number | null {
36+
function readRunIdFromStorage(): SeerExplorerRunId | null {
3637
const raw = sessionStorageWrapper.getItem(RUN_ID_STORAGE_KEY);
3738
if (raw === null || raw === 'undefined') {
3839
return null;
3940
}
4041
try {
4142
const parsed: unknown = JSON.parse(raw);
42-
return typeof parsed === 'number' ? parsed : null;
43+
return typeof parsed === 'number' || typeof parsed === 'string' ? parsed : null;
4344
} catch {
4445
return null;
4546
}
@@ -121,7 +122,7 @@ function SeerExplorerChatStatePolling({
121122
}: {
122123
children: ReactNode;
123124
dispatch: Dispatch<ChatStateAction>;
124-
runId: number | null;
125+
runId: SeerExplorerRunId | null;
125126
}) {
126127
const {pollingState} = useSeerExplorerPolling({runId});
127128

static/app/views/seerExplorer/types.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ export type PendingUserInput = {
142142
input_type: 'file_change_approval' | 'ask_user_question';
143143
};
144144

145+
export type SeerExplorerRunId = number | string;
146+
145147
export type SeerExplorerResponse = {
146148
session: {
147149
blocks: Block[];
@@ -152,4 +154,5 @@ export type SeerExplorerResponse = {
152154
repo_pr_states?: Record<string, RepoPRState>;
153155
run_id?: number;
154156
} | null;
157+
sentry_run_id?: string | null;
155158
};

0 commit comments

Comments
 (0)