SCAL-336112: Support chat history for the Spotter MCP server - #650
SCAL-336112: Support chat history for the Spotter MCP server#650mouryabalabhadra wants to merge 2 commits into
Conversation
Replay stored Spotter conversations in auto-rendered MCP frames
Problem
An MCP answer frame carries its session parameters (sessionId, genNo, acSessionId, acGenNo) in the URL hash. Those parameters are tied to a specific answer generation and expire with the answer object (~8 hours), so a host app that persists an answer URL to show a past chat later ends up with a dead frame.
Change
A host app can now persist just the conversation identifier and an ordinal answer position instead of the expiring session parameters. Two new SDK-facing URL markers drive this:
Param.TsmcpConversationId (tsmcpConversationId) — the analytical session/conversation id
Param.TsmcpAnswerIndex (tsmcpAnswerIndex) — zero-based position of the answer within the conversation, counting only non-thinking answer response items in message order; defaults to 0
When startAutoMCPFrameRenderer sees a frame carrying tsmcpConversationId, it resolves live session parameters before rendering:
GET /api/rest/2.0/ai/agent/conversations/{id}/messages → collects answer ids, skipping is_thinking items.
GET .../answers/{answerId}/details → yields session_identifier, generation_number and ac_state, which are written into the hash query string (route preserved, defaulting to /embed/conv-assist-answer when the source URL has none).
Both markers are stripped from the URL handed to the ThoughtSpot application — they address the SDK, not the app.
Stale-data notice
A replayed answer is re-run against current data, so its numbers may differ from what the user originally saw. Re-resolved frames get a native title tooltip plus a data-ts-stale-answer="true" attribute so host apps can style or replace it. Two new config options on AutoMCPFrameRendererViewConfig:
suppressStaleAnswerNotice?: boolean — render without any notice
staleAnswerNoticeText?: string — override the copy for translation or product voice
Backward compatibility
Frames arriving straight from the MCP server carry their own session parameters and no conversation id, so they take the existing path unchanged — no API calls, no notice. If resolution fails at any step (non-OK response, missing answer at the requested index, missing session state, thrown error), the renderer logs a warning and falls back to the source URL as-is; it never invents parameters and never shows a notice claiming a successful refresh.
Additive only: two new enum members, two new optional config fields. No existing enum value, type or default changed.
Tests
New stored conversation replay suite in auto-frame-renderer.spec.ts covers: session params resolved into the rendered src, correct answer picked at an index with a thinking item interleaved, default route applied, replay markers stripped, notice applied / suppressed / overridden, fallback on API failure, and no API calls for a frame without a conversation id.
There was a problem hiding this comment.
Code Review
This pull request introduces support for replaying stored conversations in the AutoFrameRenderer by resolving session parameters from the conversation and answer APIs and adding a stale-data notice. The review feedback focuses on enhancing code robustness, specifically by safely handling relative URLs in the URL constructor, preventing potential runtime TypeErrors during API response destructuring, avoiding literal 'null' or 'undefined' string parameter values, and using parseInt for robust index parsing.
commit: |
| conversationId: string, | ||
| answerIndex: number, | ||
| ): Promise<AnswerSessionParams | null> { | ||
| const base = `${this.thoughtSpotHost}/api/rest/2.0/ai/agent/conversations/${encodeURIComponent(conversationId)}`; |
There was a problem hiding this comment.
Should we be using the private APIs here? So that when we deprecate/remove the public APIs this continues to work?
There was a problem hiding this comment.
yeah. will do that.
Problem
A host app replaying a stored conversation cannot persist an answer's embed URL. The
sessionId/genNo/acSessionId/acGenNoparameters point at an answer objectthat expires after roughly 8 hours, so a reopened chat renders dead frames.
Change
<iframe src="…/v2/?tsmcp=true&tsmcpConversationId=&tsmcpAnswerIndex=">startAutoMCPFrameRenderercan now rebuild those parameters at render time. A host appstores only the conversation identifier and the answer's position, and marks the
placeholder iframe with two new params:
The renderer then resolves the answer through two public APIs:
GET /api/rest/2.0/ai/agent/conversations/{id}/messages— the non-thinkingansweritems, in message order, give the answer ids.
GET …/answers/{answer_id}/details— turns the id attsmcpAnswerIndexinto a livesession_identifier,generation_numberandac_state.Those are written into the route's hash, the replay markers are stripped before the URL
reaches the application, and the frame is tagged with a tooltip:
"This data may have changed since the last time you had a chat." — since the chart is
re-run against current data. Suppress or reword it with
suppressStaleAnswerNotice/staleAnswerNoticeText.Calls go through
tokenizedFetch, so cookieless auth is handled. If resolution failsfor any reason, the renderer falls back to whatever the source URL carried and logs a
warning — the frame degrades rather than disappearing, and is not marked as refreshed.
Backwards compatibility
Fully additive. The replay path runs only when
tsmcpConversationIdis present. A framestraight from the MCP server carries its own session params and no conversation id, so
it takes the existing code path, makes no API calls, and is left unmarked. Covered by a
regression test.
API added
Param.TsmcpConversationId,Param.TsmcpAnswerIndexAutoMCPFrameRendererViewConfig.suppressStaleAnswerNotice(defaultfalse)AutoMCPFrameRendererViewConfig.staleAnswerNoticeTextTesting
auto-frame-renderer.spec.ts: 62 tests pass — 53 pre-existing (no regressions) plus 9new covering resolution, index selection skipping thinking items, the default route,
marker stripping, the notice and both of its config flags, resolution failure, and the
untouched non-replay path.
tsc --noEmitandeslintclean.Not verified against a live expired conversation — the test cluster's REST layer was
returning 502 while this was written, so the two API calls are covered by mocks only.