Display object, field and view links as chips in the AI chat (#23573)

<img width="3840" height="1876" alt="CleanShot 2026-07-30 at 15 37
46@2x"
src="https://github.com/user-attachments/assets/9fe178b9-c2fa-4b05-9c9d-0cdc80270b67"
/>



https://github.com/user-attachments/assets/34c4e486-c462-4300-ae98-da99f614f069



The AI chat already renders record chips from a `[[record:...]]` marker
the model writes in its prose, but naming an object, field or view
produced plain text. This adds three sibling markers so those render as
chips too, as in the [Figma
design](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=104416-116261).

- `[[object:<nameSingular>:<label>[[/object]]` links to the record index
page. It is name-keyed rather than id-keyed so an object the assistant
only *proposes* to create still renders as a chip, just without a link.
- `[[field:<id>:<label>[[/field]]` links to the field's settings page,
gated on the `DATA_MODEL` permission.
- `[[view:<id>:<label>[[/view]]` links to the object index page for that
view.

Field and view ids must come from a tool, so an unresolvable one falls
back to plain text rather than a chip that goes nowhere.

The record-only parser becomes one scan over all four kinds. Alternative
order is load-bearing: `[[view:<uuid>:` is shaped exactly like the
legacy prefix-less record marker, so metadata kinds are tried first and
only records keep the legacy `]]` terminator.

Server side is prompt-only. The metadata and view tools return bare
objects rather than `ToolOutput`, so there is nowhere to hang a
structured reference array without wrapping every factory, and the names
and ids the markers need are already in those results verbatim.

Also fixes a pre-existing issue in `LazyMarkdownRenderer`: its
`components` map was rebuilt on every render, and react-markdown uses
each entry as the JSX element type, so every node remounted on every
streamed chunk. Harmless before, expensive once the model is told to
chip every metadata name it writes.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23573?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-30 17:08:21 +02:00
committed by GitHub
parent a9d996ff7e
commit 5848c9bd30
43 changed files with 1456 additions and 436 deletions
@@ -85,5 +85,24 @@ Record References - IMPORTANT:
- The recordId MUST be a real UUID (like "abc12345-1234-5678-abcd-123456789012")
- DO NOT create record references before calling the tool
- DO NOT use placeholder IDs like "rec-snowflake" or "rec-person-1"
- If a tool hasn't been called yet, don't reference records that don't exist`,
- If a tool hasn't been called yet, don't reference records that don't exist
Metadata References:
Whenever you name an object, a field, or a view in your prose, write it as a metadata reference instead of plain text. Each one becomes a chip the user can click.
- Object: [[object:objectNameSingular:displayName[[/object]]
- Example: [[object:company:Companies[[/object]]
- Use the \`nameSingular\` from \`get_object_metadata\` or \`create_object_metadata\` (NOT the label, NOT the plural, NOT the id)
- This is the only reference you may write for something that does not exist yet: when you propose creating an object, reference it with the \`nameSingular\` you intend to use and it renders as a chip without a link
- Field: [[field:fieldMetadataId:displayName[[/field]]
- Example: [[field:abc12345-1234-5678-abcd-123456789012:Annual Recurring Revenue[[/field]]
- Use the \`id\` returned by \`get_field_metadata\`, \`create_field_metadata\`, or the \`fields\` array of \`get_object_metadata\`
- View: [[view:viewId:displayName[[/view]]
- Example: [[view:abc12345-1234-5678-abcd-123456789012:All Companies[[/view]]
- Use the \`id\` returned by \`get_views\`, \`create_view\`, or \`upsert_complete_view\`
- The displayName is what the user reads, so use the human-readable label ("Annual Recurring Revenue"), not the technical name
- Field and view ids MUST be real UUIDs copied from a tool response - never invent one, and never reference a field or view before the tool that returns it has run
- Always close a reference with its own tag: \`[[/object]]\`, \`[[/field]]\`, \`[[/view]]\`. A mismatched closing tag drops the chip
- Use metadata references only in paragraphs, lists, or markdown tables (\`| ... |\`); never in headings, code, links, or raw HTML`,
};