Checked the current Genie REST API reference for this rather than guessing, a few concrete answers.
Query attachment result data: GET /api/2.0/genie/spaces/{space_id}/conversations/{conversation_id}/messages/{message_id}/attachments/{attachment_id}/query-result (SDK: getMessageAttachmentQueryResult). Returns a statement_response with manifest/schema plus result.data_array (and external_links/total_row_count/truncated for larger results). For bigger result sets there's also a generateDownloadFullQueryResult endpoint. So: iterate message.attachments, and for each attachment where the type is a query attachment, call this to get the actual rows, you don't need to re-run the SQL yourself.
Visualizations: these are very likely your missing piece. Message creation (create_message_and_wait / start_conversation_and_wait) takes an enable_visualization boolean, and it defaults to not generating them. If you're not passing enable_visualization: true, the API will legitimately return a shorter response than the UI, because the UI always requests them. With it set, messages can include a visualization-type attachment carrying a title and a query_attachment_id pointing back to the query attachment it's built from. One caveat from what's documented: that attachment gives you the title and the source query reference, not a ready-to-render chart spec or image, so your app still has to decide how to plot the underlying query-result data. It's parity on "a visualization exists and what it's based on," not a drop-in rendered chart artifact.
On the narrative length gap specifically: a text attachment's content field is described as the actual AI-generated message, not something the UI further synthesizes client-side, so if you're still short after adding enable_visualization, worth checking whether the UI conversation you're comparing against is actually running in Agent mode. That's a separate, still-Beta API surface (POST /api/2.0/genie/agents/{agent_id}/responses) with a different response model entirely, reasoning/function_call/function_call_output/message items instead of conversation attachments, and it's what produces the longer deep-research-style narratives with 5+ chained queries. If your UI comparison was an Agent mode conversation, the Conversation API you're calling isn't the same code path at all.
For the "don't want to reconstruct Genie's logic, just present it faithfully" part of your ask: rather than hand-rolling the attachments-to-UI mapping, Databricks' own AppKit has a GenieChat React component (@databricks/appkit-ui/react) built exactly for this, wire a Genie plugin on the server, bind GenieChat to your space via an alias, and it handles the streaming/history/reconnection plumbing for you. I couldn't confirm from the docs alone whether it has full visualization parity with the standalone Genie UI, so worth a quick spike, but it's the officially maintained path rather than reimplementing attachment rendering from scratch.