feat: enrich AI stream with replyToMessageId, sourceGroupName, organizationId + tenant/source filters

- Extract quotedPlatformMsgId from Baileys contextInfo.stanzaId in normalizer
- Thread quotedPlatformMsgId through IngestJobData → ingest processor → Message record
- Widen ingest processor group/tenant selects to include name and organizationId
- Add replyToMessageId, sourceGroupName, organizationId to StreamMessagePayload and XADD
- Update StreamRecord, parseFields, and streamMessages in AI stream service with new fields
- Add optional ?tenant= and ?source= SSE filters; cursor advances for filtered entries
  so Last-Event-ID resume remains accurate across reconnects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:07:42 +05:30
parent 5a0f7aa58f
commit e598073dc7
7 changed files with 39 additions and 15 deletions
@@ -18,6 +18,9 @@ interface StreamRecord {
senderJid: string;
senderName: string | null;
sourceGroupId: string;
sourceGroupName: string | null;
organizationId: string | null;
replyToMessageId: string | null;
tags: string[];
effectiveAction: string | null;
traceId: string | null;
@@ -46,7 +49,7 @@ export class AiStreamService {
* '0' → replay the entire retained stream (~50k messages) then follow live
* '<id>' → resume strictly after that entry (used on reconnect)
*/
streamMessages(afterId: string): Observable<MessageEvent> {
streamMessages(afterId: string, filters?: { tenantId?: string; sourceGroupId?: string }): Observable<MessageEvent> {
return new Observable<MessageEvent>((subscriber) => {
const redis = new Redis(this.redisUrl, { maxRetriesPerRequest: null });
let active = true;
@@ -70,8 +73,11 @@ export class AiStreamService {
for (const [, entries] of res) {
for (const [id, fields] of entries) {
const record = parseFields(fields);
cursor = id;
subscriber.next({ id, data: parseFields(fields) } as MessageEvent);
if (filters?.tenantId && record.tenantId !== filters.tenantId) continue;
if (filters?.sourceGroupId && record.sourceGroupId !== filters.sourceGroupId) continue;
subscriber.next({ id, data: record } as MessageEvent);
}
}
} catch (err) {
@@ -142,6 +148,9 @@ function parseFields(fields: string[]): StreamRecord {
senderJid: obj.senderJid ?? '',
senderName: obj.senderName || null,
sourceGroupId: obj.sourceGroupId ?? '',
sourceGroupName: obj.sourceGroupName || null,
organizationId: obj.organizationId || null,
replyToMessageId: obj.replyToMessageId || null,
tags,
effectiveAction: obj.effectiveAction || null,
traceId: obj.traceId || null,