Skip to content

feat(get-data): Spotter answer data (default on, with expired-answer reload) and iframe suppression flag - #225

Closed
mouryabalabhadra wants to merge 4 commits into
mainfrom
spotter-answer-data-iframe-suppression
Closed

mouryabalabhadra wants to merge 4 commits into
mainfrom
spotter-answer-data-iframe-suppression

Conversation

@mouryabalabhadra

@mouryabalabhadra mouryabalabhadra commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

get_data can now read the rows behind a Spotter answer, including temporary and expired ones. This is on by default. A separate opt-in URL flag strips iframes. Design doc: "Spotter answer data in getData + iframe suppression".

Spotter answer data (on by default; enable-spotter-answer-data=false turns it off)

  • get_data accepts object_type: "SPOTTER_ANSWER", with object_id set to the answer_id from get_session_updates.
  • Rows come from exportAnswerReport (CSV, session + generation number), parsed into the existing get_data result shape:
    • Empty cells become null.
    • A cell becomes a number only when it converts back to exactly the same text, so leading-zero IDs and integers above 2^53 stay as strings.
    • total_row_count is the full row count.
  • The export runs as the calling user, so permissions and RLS apply. The default and max_rows handling match the existing get_data (25 rows).
  • The cluster's data-extract watermark is skipped. That is a "Data extract produced by …" preamble before the header, and it is only recognised by that text, so single-column answers with empty cells keep all their rows.
  • The get_data description keeps the iframe-first default: Claude shows Spotter's visualization, and fetches rows only when the user asks to chart, reshape, combine or build a dashboard.

Expired answers

  • An answer's session ids expire after about 8 hours. answer_id now also carries conversation_id and message_id, which last as long as the conversation does. Existing readers that only use session_id and gen_no, such as the dashboard tools, are unaffected.
  • When the export fails, get_data loads the answer again with POST /conversation/v2/{conversation_id}/message/{message_id}/load/public, then exports with the fresh ids. This is the same call the Visual Embed SDK makes to replay a stored answer. It re-runs the query, so it can be slow.
  • There is no reload on 401/403, or for an answer_id issued before this change.
  • If the conversation itself is gone, the error asks Claude to put the question to Spotter again.
  • A 403 gets a permission hint instead of the expiry hint.

suppress-iframes=true (opt-in and undocumented; also forces answer data on)

  • iframe_url is removed from get_session_updates results and from its output schema. The schema text no longer mentions iframes, embeds or visualizations.
  • Embed links and <iframe> tags are stripped from update text, including thinking (and from content in raw mode).
  • frame_url is removed from V1 getAnswer.
  • The get_data description tells Claude to build visuals from the returned rows.

With suppress-iframes unset, iframes behave exactly as before. With enable-spotter-answer-data=false, get_data is unchanged from before this PR.

Why

Customers can front the server with its own proxy and Claude client. Today Claude can show a Spotter answer but can't read its rows, and Customer has no way to turn iframes off. Answer data is on by default for the doc's default-user story: "turn that into a Claude chart I can tweak".

Implementation notes

  • Flags are parsed in both props hooks in src/index.ts. Answer data is treated as on unless the prop is explicitly false, so other entry points also default to on.
  • Tool-text changes are in applyUrlFlagToolOverrides (tool-definitions.ts). Stripping happens when get_session_updates reads the stored updates (stripEmbeddedVisualizations).
  • answer_id is built in buildAnswerId (streaming-utils.ts). The reload is loadSpotter3Answer in spotter-3-client.ts, recorded as the load_agent_conversation_answer upstream operation.
  • No new API version was added.

Testing

  • Unit tests cover:
    • the CSV parser (watermark, single-column answers with empty cells, number handling);
    • embed stripping;
    • the tool list in each mode, and the default/off-switch URL parsing;
    • the SPOTTER_ANSWER call paths: success, off switch, bad answer_id, export failure, 403;
    • expired-answer reload: success, no reload on 403, old-style answer_id, conversation gone;
    • loadSpotter3Answer, and getAnswer.
  • Full suite: 1865 passed.
  • Ran locally with wrangler dev against champagne-master-aws staging:
    • Default: SPOTTER_ANSWER is offered, iframes are kept, and real rows come back.
    • enable-spotter-answer-data=false: unchanged from before, and SPOTTER_ANSWER is rejected.
    • suppress-iframes=true: no tsmcp, embed or iframe text in any update or tool definition, and real rows come back.
    • Simulated expired answer: a dead session_id with real conversation ids was reloaded through load/public and returned the same rows, in both default and suppress modes.

Known gaps / follow-ups

  • Reload latency: a reload re-runs the query, which can take 45s or more on a busy cluster, so a client with a short timeout may give up.
  • Default-on behaviour: every client now sees SPOTTER_ANSWER. We rely on the tool description to keep models iframe-first, which needs a check in Claude before release.
  • Memory: the CSV export loads the whole answer before trimming to max_rows.
  • Permission gap: with suppression on, a user without the data-download privilege gets neither a visual nor data, because get_data is hidden for them.
  • Skill file: line 12 of skills/analyzing-data-with-thoughtspot/SKILL.md ("Don't generate your own charts") conflicts with the suppressed flow.

… flags

Let Claude read the rows behind an unsaved Analytics Agent answer, and let
callers such as TriNet's proxy strip embedded visualizations from tool
output. Both are behind undocumented URL flags and off by default.

- enable-spotter-answer-data=true: get_data accepts object_type
  SPOTTER_ANSWER with the answer_id from get_session_updates. Rows come
  from the answer's CSV export (session + generation number) and are parsed
  into the get_data shape, skipping the data-extract watermark preamble.
  The description keeps the iframe-first default.
- suppress-iframes=true: implies the above, drops iframe_url from session
  updates and its schema, strips embed links and iframe tags from update
  text (thinking included), drops frame_url from getAnswer, and steers
  Claude to build visuals from get_data.
@mouryabalabhadra
mouryabalabhadra requested review from a team and mouryab as code owners October 29, 2026 06:01

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for two new URL flags: enable-spotter-answer-data and suppress-iframes. These flags enable fetching the underlying data rows of unsaved Analytics Agent (Spotter) answers via CSV export, and stripping iframe HTML or embedded visualization links from the agent's responses. The changes include updating tool definitions, schemas, and handlers, implementing a custom CSV parser to handle the exports (including watermark removal), and adding comprehensive test coverage. There are no review comments to address, and I have no feedback to provide.

@mouryabalabhadra

Copy link
Copy Markdown
Collaborator Author

@copilot review PR

Copilot AI commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

@copilot review PR

Finding — Medium: src/thoughtspot/get-data/spotter-answer-data.ts:59 converts every numeric-looking CSV cell to Number, which silently changes integers outside JavaScript’s safe range (for example, 9007199254740993 becomes 9007199254740992). This can return incorrect answer data. Preserve values as strings when numeric conversion is not exact.

…frame-suppression

# Conflicts:
#	src/servers/mcp-server.ts
#	src/servers/tool-definitions.ts
#	src/services/clients/get-data/spotter-answer-data.ts
#	test/streaming-utils.spec.ts
- Strip the watermark preamble only when it reads "Data extract…", so a
  single-column answer with an empty cell no longer loses rows
- Coerce a cell to a number only when it round-trips exactly, keeping
  leading-zero IDs, >2^53 integers and formatted text as strings
- Give a permission hint instead of the expiry hint on 403
- Drop the unused title parameter
…ault

- answer_id now also carries conversation_id and message_id. These outlive
  the answer's session ids (about 8 hours), and existing readers ignore them
- When the CSV export fails, get_data loads the answer again through
  /conversation/v2/{conv}/message/{id}/load/public (the call the Visual
  Embed SDK uses to replay answers) and exports with the fresh ids.
  Not on 401/403, or for answer_ids without the conversation ids
- get_data on SPOTTER_ANSWER is on by default; enable-spotter-answer-data=false
  turns it off. suppress-iframes stays opt-in and forces it on
@mouryabalabhadra mouryabalabhadra changed the title feat(get-data): Spotter answer data and iframe suppression behind URL flags feat(get-data): Spotter answer data (default on, with expired-answer reload) and iframe suppression flag Nov 2, 2026
@mouryab mouryab closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants