Skip to content

Inject document content when using @-mentions#74

Open
conbrodie wants to merge 19 commits into
getomnico:masterfrom
conbrodie:feature/mentioned-document-context
Open

Inject document content when using @-mentions#74
conbrodie wants to merge 19 commits into
getomnico:masterfrom
conbrodie:feature/mentioned-document-context

Conversation

@conbrodie

@conbrodie conbrodie commented Mar 22, 2026

Copy link
Copy Markdown

Summary

Adds document @-mentions to chat. Users can search indexed documents from the composer, attach one or more references, and send them alongside text and uploads. Mentioned document content is fetched and injected into the LLM request when the message is processed.

What changed

Mention composer and rendering

  • Adds document typeahead to the chat composer with keyboard and mouse selection.
  • Requires at least 3 normalized Unicode characters before searching.
  • Uses case-insensitive, punctuation-normalized prefix matching without fuzzy typo matching.
  • Displays source-aware document icons with a generic fallback for historical metadata.
  • Renders mention chips in sent messages.
  • Preserves authorized mention/upload blocks when retrying or editing messages, including mention-only messages.

Search and typeahead

  • Maintains an in-memory document-title index refreshed hourly.
  • Refreshes the title index with keyset pagination.
  • Limits the ranked candidate set to 100 before ACL filtering.
  • Filters unsupported content types and soft-deleted sources.

Persistence and LLM context

  • Stores compact mention references as custom document blocks in the existing chat_messages.message JSON; no document content is duplicated in chat persistence.
  • Expands mention references per chat turn by fetching the current document content.
  • Supports text and binary/PDF fallback paths through the document handler.
  • Converts mention document blocks for OpenAI, OpenAI-compatible, Gemini, Anthropic, and Bedrock provider payloads.
  • Leaves compaction behavior unchanged; persisted references can be expanded again on later turns.

Permissions

Mention typeahead, expansion, and explicit document reads use aligned access rules:

  • Public documents
  • Direct user grants
  • Domain grants
  • Resolved group grants
  • Soft-deleted source exclusion

Group/identity lookup failures fail closed. Permission bypass remains limited to contexts that explicitly set skip_permission_check.

Validation

  • Focused AI integration/unit tests: 29 passed
  • Searcher typeahead unit tests: 16 passed
  • cargo check -p omni-searcher -p shared: passed
  • Searcher integration test target compilation: passed
  • Changed frontend files pass Prettier

The full repository Svelte check still reports pre-existing upstream diagnostics unrelated to this PR.

Manual testing

  • Mention text-backed documents
  • Mention PDF/binary documents
  • Send mention-only messages
  • Retry and edit messages containing mentions/uploads
  • Verify public, direct-user, domain, and group access paths
  • Verify unauthorized and deleted-source documents are excluded

@prvnsmpth

Copy link
Copy Markdown
Contributor

Awesome, thanks for the PR! Will review and leave my comments.

Comment thread services/ai/tools/document_handler.py Outdated
],
)

async def fetch_document_for_context(self, document_id: str, context: ToolContext) -> MentionedDocumentContext | None:

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.

So this func is basically the same as execute above, except for how we handle binary files. Maybe a cleaner approach would be to add a skip_binary_files: bool argument to execute and invoke that with this bool set to True instead? That way we probably won't even need the MentionedDocumentContext dataclass anymore, I think.

@prvnsmpth prvnsmpth 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.

Overall looks great! The only major detail we should reconsider is the schema change, the rest LGTM. Thanks again!

Comment thread services/ai/routers/chat.py Outdated
# Inject mentioned document content into the last user message
last_msg = chat_messages[-1]
if last_msg.mentioned_document_ids:
doc_handler = DocumentToolHandler(

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.

I like that we're using the document tool handler here, good choice

Comment thread services/ai/routers/chat.py Outdated
if mentioned_doc_context:
context_blocks = []
for doc in mentioned_doc_context:
context_blocks.append(f'<document doc_id="{doc.doc_id}" doc_title="{doc.title}">\n{doc.content}\n</document>')

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.

Probably won't make any difference, but we don't use <document> tags when injecting the results of the read_document tool, so maybe we should do the same here?

And we have yet another way to inject documents into context when using the search_documents tool. We don't need to consolidate it in this PR, but maybe we can add a TODO here to deal with it later.

@@ -0,0 +1 @@
ALTER TABLE chat_messages ADD COLUMN mentioned_document_ids JSONB NOT NULL DEFAULT '[]'; No newline at end of file

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.

I trhink we can achieve this without making a schema change... I have a somewhat weird idea that we can consider:

  • when the user mentions a doc, we inject a synthetic read_document tool call into the chat conversation on behalf of the LLM (role = assistant)
  • when the chat is executed, the main chat loop in chat.py will pick this up and handle the tool call automatically, no extra handling required.

No new column required, the mentioned doc IDs will be stored in the chat message content as implicit tool calls.

There's a couple issues with this though: (1) we'll have to generate our own tool call IDs and the LLM provider APIs might not play nicely with fake tool calls (2) The chat creation API (in web/src/routes/api/chat) will need to gain some knowledge of the read_document tool, which is exclusively an ai service concern.

But if we can work around this somehow, I think we'll end up with a much cleaner impl. WDYT?

@prvnsmpth prvnsmpth added this to the UI/UX polish milestone Apr 26, 2026
@prvnsmpth prvnsmpth added the area:web Frontend (SvelteKit) label Apr 26, 2026
@prvnsmpth
prvnsmpth force-pushed the feature/mentioned-document-context branch 2 times, most recently from 17774fc to a4755d2 Compare July 17, 2026 06:20
prvnsmpth added 19 commits July 24, 2026 18:23
- add document mention chips and persist structured mention references
- expand mentions through read_document with permission and sandbox handling
- enforce chat and upload ownership and add integration coverage
- restrict mention candidates to supported document content types
- enforce current user, domain, group, and source permissions per request
- add coverage for content types, ACL changes, and result batching
- retain expanded document text across non-Anthropic providers
- stage scanned PDFs when indexed text extraction failed
- prevent models from searching again for resolved mentions
- include source and content types in typeahead results
- reuse the existing document icon resolver for mention suggestions
- retain the generic file icon as a fallback
@prvnsmpth
prvnsmpth force-pushed the feature/mentioned-document-context branch from 9bbd341 to f4ac4d4 Compare July 24, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:web Frontend (SvelteKit)

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants