@adatechnology/meta-whatsapp-module 0.2.0-rc.16 → 0.2.0-rc.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
2
  import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
3
  import * as _adatechnology_meta_whatsapp_contracts from '@adatechnology/meta-whatsapp-contracts';
4
- import { SessionState, SessionMode, ConversationSummary, CacheInterface, FlowGraphData, FlowGraphSummary, LiveFlowPosition, WhatsAppSettings, MessageDirection, MessageSender, MessageStatus, RealtimeNotifierInterface, ChannelAdapterInterface, ObjectStorageInterface, FlowActionKind, FlowActionHandler, ConversationSession, MetaWhatsAppHooks, SubjectResolverInterface, CatalogPort } from '@adatechnology/meta-whatsapp-contracts';
4
+ import { TranscriptionMode, SessionState, SessionMode, ConversationSummary, MessageDirection, MessageSender, MessageStatus, CacheInterface, FlowGraphData, FlowGraphSummary, LiveFlowPosition, WhatsAppSettings, RealtimeNotifierInterface, ChannelAdapterInterface, ObjectStorageInterface, FlowActionKind, FlowActionHandler, ConversationSession, MetaWhatsAppHooks, SubjectResolverInterface, CatalogPort } from '@adatechnology/meta-whatsapp-contracts';
5
+ export { PREVIEW_MEDIA_ID_PREFIX, TranscriptionMode, resolvePreviewUploadId, toPreviewMediaId } from '@adatechnology/meta-whatsapp-contracts';
5
6
  import { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider';
6
7
 
7
8
  type MetaWhatsAppDatabase = PgDatabase<PgQueryResultHKT, any, any>;
@@ -10,6 +11,72 @@ type DrizzleMigrateFunction = (db: never, config: {
10
11
  migrationsTable?: string;
11
12
  }) => Promise<void>;
12
13
 
14
+ /**
15
+ * Vocabulário de transcrição de áudio do módulo.
16
+ *
17
+ * Fica em arquivo próprio porque o schema e os dois use-cases (ingestão automática e sob demanda)
18
+ * precisam dos mesmos tipos, e pendurá-los em qualquer um dos três faria os outros dois importarem
19
+ * de dentro de uma camada que não é a deles.
20
+ */
21
+
22
+ declare const TRANSCRIPTION_STATUS: {
23
+ /** Falhou de forma retriável (cota, rede, 5xx) — vai sair quando alguém tentar de novo. */
24
+ readonly PENDING: "pending";
25
+ /** Processado. Texto vazio aqui é áudio em silêncio, e NÃO deve ser reprocessado. */
26
+ readonly DONE: "done";
27
+ /** Falha definitiva do engine (credencial, áudio corrompido, arquivo grande demais). */
28
+ readonly FAILED: "failed";
29
+ /** Nenhum engine da cadeia aceita o formato. Retentar não conserta codec. */
30
+ readonly UNSUPPORTED: "unsupported";
31
+ };
32
+ type TranscriptionStatus = (typeof TRANSCRIPTION_STATUS)[keyof typeof TRANSCRIPTION_STATUS];
33
+ /**
34
+ * Quando transcrever.
35
+ *
36
+ * `auto` transcreve durante a ingestão da mídia, onde o buffer do áudio JÁ está em memória — não
37
+ * custa um segundo download do storage. `onDemand` só transcreve quando o atendente pede, o que
38
+ * troca latência na interface por não gastar cota com áudio que ninguém vai ler.
39
+ *
40
+ * O tipo vem do contrato (o painel escolhe, a API transporta, o módulo obedece) e `satisfies`
41
+ * garante em tempo de compilação que estes valores continuam sendo exatamente os de lá — sem isso,
42
+ * um modo novo no contrato passaria despercebido aqui.
43
+ */
44
+ declare const TRANSCRIPTION_MODE: {
45
+ readonly AUTO: "auto";
46
+ readonly ON_DEMAND: "onDemand";
47
+ };
48
+
49
+ /**
50
+ * O contrato mínimo de `@adatechnology/audio-transcription-provider`, declarado aqui em vez de
51
+ * importado — mesma decisão do `MessageModerator` em `LogMessage.use-case.ts`.
52
+ *
53
+ * Assim o módulo não ganha dependência de pacote por um recurso opcional, e transcrição não fica
54
+ * amarrada a WhatsApp: o que atravessa esta fronteira é um buffer de áudio e um mime.
55
+ */
56
+ type AudioTranscriber = {
57
+ readonly name: string;
58
+ transcribe: (input: {
59
+ buffer: Buffer;
60
+ mimeType: string;
61
+ languageHint?: string;
62
+ }) => Promise<{
63
+ text: string;
64
+ language?: string;
65
+ durationSeconds?: number;
66
+ engine: string;
67
+ }>;
68
+ };
69
+ /**
70
+ * Recorte do erro do provider que o módulo precisa ler para escolher entre `'pending'` e
71
+ * `'failed'`. Estrutural, não `instanceof`: o provider é opcional e pode nem estar instalado, e um
72
+ * `instanceof` contra classe ausente não compila.
73
+ */
74
+ declare function isRetriableTranscriptionError(error: unknown): boolean;
75
+ declare function isUnsupportedTranscriptionError(error: unknown): boolean;
76
+ declare function transcriptionRetryAfterSeconds(error: unknown): number | undefined;
77
+ /** Mime de áudio? Só áudio é transcrito — vídeo, imagem e documento passam sem tocar no engine. */
78
+ declare function isAudioMimeType(mimeType: string | undefined | null): boolean;
79
+
13
80
  declare const metaWhatsAppSchema: drizzle_orm_pg_core.PgSchema<"meta_whatsapp">;
14
81
  declare const sessions: drizzle_orm_pg_core.PgTableWithColumns<{
15
82
  name: "sessions";
@@ -560,6 +627,81 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
560
627
  }, {}, {
561
628
  $type: string[];
562
629
  }>;
630
+ transcriptionStatus: drizzle_orm_pg_core.PgColumn<{
631
+ name: "transcription_status";
632
+ tableName: "messages";
633
+ dataType: "string";
634
+ columnType: "PgVarchar";
635
+ data: TranscriptionStatus;
636
+ driverParam: string;
637
+ notNull: false;
638
+ hasDefault: false;
639
+ isPrimaryKey: false;
640
+ isAutoincrement: false;
641
+ hasRuntimeDefault: false;
642
+ enumValues: [string, ...string[]];
643
+ baseColumn: never;
644
+ identity: undefined;
645
+ generated: undefined;
646
+ }, {}, {
647
+ length: 16;
648
+ $type: TranscriptionStatus;
649
+ }>;
650
+ transcriptionText: drizzle_orm_pg_core.PgColumn<{
651
+ name: "transcription_text";
652
+ tableName: "messages";
653
+ dataType: "string";
654
+ columnType: "PgText";
655
+ data: string;
656
+ driverParam: string;
657
+ notNull: false;
658
+ hasDefault: false;
659
+ isPrimaryKey: false;
660
+ isAutoincrement: false;
661
+ hasRuntimeDefault: false;
662
+ enumValues: [string, ...string[]];
663
+ baseColumn: never;
664
+ identity: undefined;
665
+ generated: undefined;
666
+ }, {}, {}>;
667
+ transcriptionLanguage: drizzle_orm_pg_core.PgColumn<{
668
+ name: "transcription_language";
669
+ tableName: "messages";
670
+ dataType: "string";
671
+ columnType: "PgVarchar";
672
+ data: string;
673
+ driverParam: string;
674
+ notNull: false;
675
+ hasDefault: false;
676
+ isPrimaryKey: false;
677
+ isAutoincrement: false;
678
+ hasRuntimeDefault: false;
679
+ enumValues: [string, ...string[]];
680
+ baseColumn: never;
681
+ identity: undefined;
682
+ generated: undefined;
683
+ }, {}, {
684
+ length: 32;
685
+ }>;
686
+ transcriptionEngine: drizzle_orm_pg_core.PgColumn<{
687
+ name: "transcription_engine";
688
+ tableName: "messages";
689
+ dataType: "string";
690
+ columnType: "PgVarchar";
691
+ data: string;
692
+ driverParam: string;
693
+ notNull: false;
694
+ hasDefault: false;
695
+ isPrimaryKey: false;
696
+ isAutoincrement: false;
697
+ hasRuntimeDefault: false;
698
+ enumValues: [string, ...string[]];
699
+ baseColumn: never;
700
+ identity: undefined;
701
+ generated: undefined;
702
+ }, {}, {
703
+ length: 32;
704
+ }>;
563
705
  createdAt: drizzle_orm_pg_core.PgColumn<{
564
706
  name: "created_at";
565
707
  tableName: "messages";
@@ -1363,6 +1505,43 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
1363
1505
  identity: undefined;
1364
1506
  generated: undefined;
1365
1507
  }, {}, {}>;
1508
+ transcriptionEnabled: drizzle_orm_pg_core.PgColumn<{
1509
+ name: "transcription_enabled";
1510
+ tableName: "settings";
1511
+ dataType: "boolean";
1512
+ columnType: "PgBoolean";
1513
+ data: boolean;
1514
+ driverParam: boolean;
1515
+ notNull: false;
1516
+ hasDefault: false;
1517
+ isPrimaryKey: false;
1518
+ isAutoincrement: false;
1519
+ hasRuntimeDefault: false;
1520
+ enumValues: undefined;
1521
+ baseColumn: never;
1522
+ identity: undefined;
1523
+ generated: undefined;
1524
+ }, {}, {}>;
1525
+ transcriptionMode: drizzle_orm_pg_core.PgColumn<{
1526
+ name: "transcription_mode";
1527
+ tableName: "settings";
1528
+ dataType: "string";
1529
+ columnType: "PgVarchar";
1530
+ data: TranscriptionMode;
1531
+ driverParam: string;
1532
+ notNull: false;
1533
+ hasDefault: false;
1534
+ isPrimaryKey: false;
1535
+ isAutoincrement: false;
1536
+ hasRuntimeDefault: false;
1537
+ enumValues: [string, ...string[]];
1538
+ baseColumn: never;
1539
+ identity: undefined;
1540
+ generated: undefined;
1541
+ }, {}, {
1542
+ length: 16;
1543
+ $type: TranscriptionMode;
1544
+ }>;
1366
1545
  createdAt: drizzle_orm_pg_core.PgColumn<{
1367
1546
  name: "created_at";
1368
1547
  tableName: "settings";
@@ -1479,11 +1658,83 @@ declare class SessionRepository {
1479
1658
  readAt: Date | null;
1480
1659
  moderationFlagged: boolean | null;
1481
1660
  moderationTerms: string[] | null;
1661
+ transcriptionStatus: TranscriptionStatus | null;
1662
+ transcriptionText: string | null;
1663
+ transcriptionLanguage: string | null;
1664
+ transcriptionEngine: string | null;
1482
1665
  createdAt: Date;
1483
1666
  }[];
1484
1667
  } | null>;
1485
1668
  }
1486
1669
 
1670
+ interface InsertMessageParams {
1671
+ companyId: string;
1672
+ sessionId: string;
1673
+ whatsappNumber: string;
1674
+ direction: MessageDirection;
1675
+ sender: MessageSender;
1676
+ agentUserId?: string | null;
1677
+ type: string;
1678
+ content?: string | null;
1679
+ payload?: Record<string, unknown> | null;
1680
+ waMessageId?: string | null;
1681
+ status?: MessageStatus | null;
1682
+ /** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */
1683
+ moderationFlagged?: boolean | null;
1684
+ moderationTerms?: string[] | null;
1685
+ }
1686
+ interface ListMessagesParams$1 {
1687
+ companyId: string;
1688
+ sessionId: string;
1689
+ limit?: number;
1690
+ before?: string;
1691
+ }
1692
+ interface SaveTranscriptionByWaMessageIdParams extends Omit<SaveTranscriptionParams, 'messageId'> {
1693
+ /**
1694
+ * Id da mensagem na Meta. É o único que quem processa o webhook conhece — o id do módulo só
1695
+ * existe depois da gravação, e obrigar o host a descobri-lo faria cada um escrever a própria
1696
+ * consulta por `wa_message_id`.
1697
+ */
1698
+ waMessageId: string;
1699
+ }
1700
+ interface SaveTranscriptionParams {
1701
+ companyId: string;
1702
+ messageId: string;
1703
+ status: TranscriptionStatus;
1704
+ /** Ausente em `pending`/`failed`/`unsupported`; vazio em `done` é silêncio já processado. */
1705
+ text?: string | null;
1706
+ language?: string | null;
1707
+ engine?: string | null;
1708
+ }
1709
+ declare class MessageRepository {
1710
+ private readonly db;
1711
+ constructor(db: MetaWhatsAppDatabase);
1712
+ insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
1713
+ updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
1714
+ /**
1715
+ * Grava a transcrição endereçando pelo id da Meta, para quem só tem esse.
1716
+ *
1717
+ * Serve ao caso em que a transcrição acontece no próprio webhook — o grafo precisa do texto para
1718
+ * responder ao cliente, e jogar fora o que ele já pagou para transcrever significaria transcrever
1719
+ * o mesmo áudio uma segunda vez só para o painel ver.
1720
+ *
1721
+ * Devolve `undefined` quando não achou a mensagem: entrega duplicada e mensagem apagada são
1722
+ * corridas normais, não erro.
1723
+ */
1724
+ saveTranscriptionByWaMessageId(params: SaveTranscriptionByWaMessageIdParams): Promise<MessageRow | undefined>;
1725
+ findById(companyId: string, messageId: string): Promise<MessageRow | undefined>;
1726
+ /**
1727
+ * Grava o resultado da transcrição. Devolve `undefined` quando a mensagem não existe (apagada
1728
+ * entre o enfileiramento e a execução do job) — não é erro, é corrida normal.
1729
+ *
1730
+ * `text`/`language`/`engine` só são tocados quando informados: uma retentativa que volta a falhar
1731
+ * atualiza o status sem apagar a transcrição parcial de uma tentativa anterior que tenha vindo de
1732
+ * outro engine da cadeia.
1733
+ */
1734
+ saveTranscription(params: SaveTranscriptionParams): Promise<MessageRow | undefined>;
1735
+ listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
1736
+ }
1737
+
1487
1738
  declare const DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
1488
1739
  /**
1489
1740
  * Cache de leitura dos grafos de fluxo, com invalidação na publicação.
@@ -1538,36 +1789,6 @@ declare class SettingsRepository {
1538
1789
  resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
1539
1790
  }
1540
1791
 
1541
- interface InsertMessageParams {
1542
- companyId: string;
1543
- sessionId: string;
1544
- whatsappNumber: string;
1545
- direction: MessageDirection;
1546
- sender: MessageSender;
1547
- agentUserId?: string | null;
1548
- type: string;
1549
- content?: string | null;
1550
- payload?: Record<string, unknown> | null;
1551
- waMessageId?: string | null;
1552
- status?: MessageStatus | null;
1553
- /** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */
1554
- moderationFlagged?: boolean | null;
1555
- moderationTerms?: string[] | null;
1556
- }
1557
- interface ListMessagesParams$1 {
1558
- companyId: string;
1559
- sessionId: string;
1560
- limit?: number;
1561
- before?: string;
1562
- }
1563
- declare class MessageRepository {
1564
- private readonly db;
1565
- constructor(db: MetaWhatsAppDatabase);
1566
- insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
1567
- updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
1568
- listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
1569
- }
1570
-
1571
1792
  type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
1572
1793
  startState: SessionState;
1573
1794
  };
@@ -2133,6 +2354,39 @@ declare class ReceiveWebhookUseCase {
2133
2354
  private handleStatus;
2134
2355
  }
2135
2356
 
2357
+ /**
2358
+ * Política efetiva de transcrição de uma empresa.
2359
+ *
2360
+ * A separação que este arquivo existe para manter: **ambiente decide se é POSSÍVEL, settings decide
2361
+ * se é para FAZER.** A capacidade (engine, chave, storage que sabe reler) é injetada pelo host e é
2362
+ * por deploy — chave de API não vai para tabela de configuração de tenant. Já "transcrever ou não" e
2363
+ * "automático ou sob demanda" são decisão de operação de cada empresa, e pedir deploy para mudar
2364
+ * isso é o que transforma um interruptor em ticket.
2365
+ */
2366
+ type TranscriptionPolicy = {
2367
+ readonly isEnabled: boolean;
2368
+ readonly mode: TranscriptionMode;
2369
+ };
2370
+ type TranscriptionPolicyDefaults = {
2371
+ /** O que vale quando o painel não decidiu. Tipicamente vem do ambiente do host. */
2372
+ readonly isEnabled: boolean;
2373
+ readonly mode: TranscriptionMode;
2374
+ };
2375
+ type ResolveTranscriptionPolicyDependencies = {
2376
+ readonly settingsRepository: SettingsRepository;
2377
+ readonly defaults: TranscriptionPolicyDefaults;
2378
+ };
2379
+ /**
2380
+ * Resolve a política por empresa, com o padrão do host como base.
2381
+ *
2382
+ * Uma consulta a `settings` por áudio transcrito. Não é cacheado de propósito: é uma leitura por
2383
+ * chave primária, acontece uma vez por nota de voz (não por mensagem), e cachear introduziria a
2384
+ * pergunta "por quanto tempo o operador continua vendo o interruptor antigo depois de mexer nele" —
2385
+ * custo real, para economizar um índice único.
2386
+ */
2387
+ declare function createTranscriptionPolicyResolver(dependencies: ResolveTranscriptionPolicyDependencies): (companyId: string) => Promise<TranscriptionPolicy>;
2388
+ type TranscriptionPolicyResolver = ReturnType<typeof createTranscriptionPolicyResolver>;
2389
+
2136
2390
  type IngestInboundMediaParams = {
2137
2391
  companyId: string;
2138
2392
  messageId: string;
@@ -2143,14 +2397,52 @@ type IngestInboundMediaParams = {
2143
2397
  type IngestInboundMediaResult = {
2144
2398
  uploadId: string;
2145
2399
  alreadyIngested: boolean;
2400
+ /**
2401
+ * Só presente quando a transcrição automática rodou nesta execução. Ausente é o normal: mídia que
2402
+ * não é áudio, transcrição desligada, modo sob demanda, ou mídia já ingerida antes.
2403
+ */
2404
+ transcription?: {
2405
+ status: TranscriptionStatus;
2406
+ };
2407
+ };
2408
+ /**
2409
+ * Transcrição durante a ingestão — o modo `auto`.
2410
+ *
2411
+ * Entra aqui, e não em use-case separado, por um motivo só: neste ponto o buffer do áudio ACABOU de
2412
+ * ser baixado e está em memória. Transcrever fora daqui custaria um segundo download do storage por
2413
+ * áudio, e o `TranscribeAudioUseCase` existe justamente para esse caso (sob demanda e retomada).
2414
+ */
2415
+ type IngestTranscriptionOptions = {
2416
+ transcriber: AudioTranscriber;
2417
+ messageRepository: MessageRepository;
2418
+ /**
2419
+ * Política POR EMPRESA, resolvida a cada áudio. Não é um `mode` fixo porque o interruptor mora nas
2420
+ * configurações da empresa: um valor capturado na construção do use-case congelaria a escolha até
2421
+ * o próximo deploy, e o worker é um processo longo — o operador mexeria no painel e nada mudaria.
2422
+ */
2423
+ resolvePolicy: TranscriptionPolicyResolver;
2424
+ languageHint?: string;
2425
+ hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
2146
2426
  };
2147
2427
  declare class IngestInboundMediaUseCase {
2148
2428
  private readonly db;
2149
2429
  private readonly channel;
2150
2430
  private readonly objectStorage;
2151
2431
  private readonly documentRepository?;
2152
- constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined);
2432
+ private readonly transcription?;
2433
+ constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined, transcription?: IngestTranscriptionOptions | undefined);
2153
2434
  execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult>;
2435
+ /**
2436
+ * Transcreve o áudio recém-baixado, quando o modo é `auto`.
2437
+ *
2438
+ * **Nunca propaga erro.** Neste ponto o binário já está no storage e já entrou na biblioteca da
2439
+ * conversa: deixar uma falha de transcrição subir marcaria a ingestão inteira como falha, e o
2440
+ * retry do host baixaria de novo da Meta um arquivo que está salvo — gastando banda para reproduzir
2441
+ * um efeito que já aconteceu. O status fica gravado na mensagem e o `onTranscriptionDeferred`
2442
+ * avisa quem sabe reenfileirar.
2443
+ */
2444
+ private transcribeIfAuto;
2445
+ private recordTranscriptionFailure;
2154
2446
  }
2155
2447
  declare function extractMediaDescriptor(message: MessageRow): {
2156
2448
  sourceMediaId: string;
@@ -2158,6 +2450,105 @@ declare function extractMediaDescriptor(message: MessageRow): {
2158
2450
  filename?: string;
2159
2451
  } | undefined;
2160
2452
 
2453
+ /**
2454
+ * Storage com leitura garantida. `getObject` é opcional no contrato, então quem monta este use-case
2455
+ * precisa provar que o método existe — sem os bytes não há o que transcrever, e um use-case que
2456
+ * sempre falha é pior do que a ausência ser visível no tipo.
2457
+ */
2458
+ type ReadableObjectStorage = ObjectStorageInterface & {
2459
+ getObject: NonNullable<ObjectStorageInterface['getObject']>;
2460
+ };
2461
+ type TranscribeAudioParams = {
2462
+ companyId: string;
2463
+ messageId: string;
2464
+ /**
2465
+ * Refaz mesmo com transcrição já salva. Serve ao "transcrever de novo" depois de trocar de engine
2466
+ * — sem isto, um resultado ruim do engine antigo ficaria congelado para sempre.
2467
+ */
2468
+ force?: boolean;
2469
+ };
2470
+ type TranscribeAudioResult = {
2471
+ status: TranscriptionStatus;
2472
+ text: string | null;
2473
+ language: string | null;
2474
+ engine: string | null;
2475
+ /** `true` quando devolveu o que já estava salvo, sem gastar cota. */
2476
+ alreadyTranscribed: boolean;
2477
+ };
2478
+ type TranscribeAudioDependencies = {
2479
+ messageRepository: MessageRepository;
2480
+ objectStorage: ReadableObjectStorage;
2481
+ transcriber: AudioTranscriber;
2482
+ /**
2483
+ * Política por empresa. Ausente, a transcrição sob demanda não consulta configuração nenhuma e
2484
+ * atende sempre — que é o comportamento de quem controla o liga/desliga só por ambiente.
2485
+ */
2486
+ resolvePolicy?: TranscriptionPolicyResolver;
2487
+ /** ISO 639-1 do produto. Informar corta a detecção do Whisper e evita pt-BR curto virar espanhol. */
2488
+ languageHint?: string;
2489
+ hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
2490
+ };
2491
+ /**
2492
+ * Transcreve o áudio de UMA mensagem já persistida e ingerida.
2493
+ *
2494
+ * Serve aos dois modos: é o que o painel chama no botão "transcrever" (`onDemand`) e é o que o host
2495
+ * chama ao retomar um `pending` reenfileirado. O modo `auto` não passa por aqui — ele transcreve
2496
+ * dentro da ingestão, onde o buffer já está em memória e não custa um segundo download.
2497
+ *
2498
+ * Idempotente por `transcription_status`: chamar de novo num `'done'` devolve o que está salvo em
2499
+ * vez de gastar cota transcrevendo o mesmo áudio.
2500
+ */
2501
+ declare class TranscribeAudioUseCase {
2502
+ private readonly dependencies;
2503
+ constructor(dependencies: TranscribeAudioDependencies);
2504
+ execute(params: TranscribeAudioParams): Promise<TranscribeAudioResult>;
2505
+ private transcribeBuffer;
2506
+ /**
2507
+ * Carimba o motivo antes de propagar. O status é o que impede os dois desperdícios simétricos:
2508
+ * reprocessar para sempre um codec impossível, e desistir de um áudio que só esbarrou na cota.
2509
+ */
2510
+ private persistFailure;
2511
+ }
2512
+ declare function resolveFailureStatus(error: unknown): TranscriptionStatus;
2513
+
2514
+ /**
2515
+ * Guarda um arquivo gravado no simulador e devolve o id que o webhook vai referenciar.
2516
+ *
2517
+ * Existe como use-case para o host só precisar da ROTA: receber o corpo, chamar isto, devolver o
2518
+ * `mediaId`. A parte que erra — onde gravar, com que chave, como marcar o id para o adaptador
2519
+ * reconhecer depois — fica aqui, num lugar só, e não em cada produto.
2520
+ *
2521
+ * O SDK para exatamente na porta do HTTP: registrar endpoint é do host, e um pacote que abrisse rota
2522
+ * no servidor de quem o instala decidiria caminho, autenticação e versionamento no lugar dele.
2523
+ */
2524
+ type StorePreviewMediaParams = {
2525
+ companyId: string;
2526
+ /** Bytes do arquivo. Quem converte de base64 é a rota — o use-case não conhece transporte. */
2527
+ buffer: Buffer;
2528
+ mimeType: string;
2529
+ filename?: string;
2530
+ };
2531
+ type StorePreviewMediaResult = {
2532
+ /** Já com o prefixo: é isto que o simulador manda no webhook. */
2533
+ mediaId: string;
2534
+ uploadId: string;
2535
+ };
2536
+ declare class StorePreviewMediaUseCase {
2537
+ private readonly objectStorage;
2538
+ /**
2539
+ * Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
2540
+ * um teste precisa de chave previsível.
2541
+ */
2542
+ private readonly generateKeySuffix;
2543
+ constructor(objectStorage: ObjectStorageInterface,
2544
+ /**
2545
+ * Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
2546
+ * um teste precisa de chave previsível.
2547
+ */
2548
+ generateKeySuffix: () => string);
2549
+ execute(params: StorePreviewMediaParams): Promise<StorePreviewMediaResult>;
2550
+ }
2551
+
2161
2552
  interface MetaWhatsAppModuleConfig {
2162
2553
  phoneNumberId: string;
2163
2554
  accessToken: string;
@@ -2172,6 +2563,41 @@ interface MetaWhatsAppModuleFeatures {
2172
2563
  flowGraphCache?: boolean | {
2173
2564
  ttlSeconds?: number;
2174
2565
  };
2566
+ /**
2567
+ * Aceita mídia do simulador de conversa — o que faz o microfone aparecer no preview do cliente.
2568
+ *
2569
+ * **Desligado por omissão, e a decisão é consciente.** Ligado, o canal passa a aceitar id que não
2570
+ * veio da Meta: `preview-upload:<chave>` faz o servidor ler aquele objeto do storage. Em ambiente
2571
+ * de simulação isso é o recurso; em produção é leitura arbitrária do bucket por webhook forjado.
2572
+ *
2573
+ * Exige `providers.objectStorage` com `getObject` — sem os bytes não há o que devolver, e o flag é
2574
+ * ignorado em vez de produzir um canal que falha na primeira nota de voz.
2575
+ */
2576
+ previewMedia?: boolean;
2577
+ }
2578
+ /**
2579
+ * Transcrição de áudio. Ausente = desligada, e as colunas ficam nulas ("não avaliado").
2580
+ *
2581
+ * `mode` é a decisão de produto que este objeto existe para carregar: `auto` transcreve toda nota de
2582
+ * voz recebida, na ingestão, onde o buffer já está em memória; `onDemand` só transcreve quando o
2583
+ * atendente pede, gastando cota apenas com áudio que alguém vai ler de fato.
2584
+ */
2585
+ interface MetaWhatsAppTranscriptionConfig {
2586
+ transcriber: AudioTranscriber;
2587
+ /**
2588
+ * PADRÃO, não decisão final: vale para as empresas que não mexeram no interruptor do painel. As
2589
+ * configurações por empresa (`settings.transcriptionMode`) têm precedência.
2590
+ *
2591
+ * Padrão `onDemand` — o modo que não gasta cota sem alguém pedir.
2592
+ */
2593
+ mode?: TranscriptionMode;
2594
+ /**
2595
+ * PADRÃO de ligado/desligado para empresas sem escolha registrada. `true` — injetar o transcritor
2596
+ * já é a declaração de que o host quer o recurso; quem controla por empresa usa o painel.
2597
+ */
2598
+ isEnabledByDefault?: boolean;
2599
+ /** ISO 639-1 do produto (ex.: `'pt'`). Corta a detecção de idioma do engine. */
2600
+ languageHint?: string;
2175
2601
  }
2176
2602
  interface MetaWhatsAppModuleProviders {
2177
2603
  objectStorage?: ObjectStorageInterface;
@@ -2180,6 +2606,11 @@ interface MetaWhatsAppModuleProviders {
2180
2606
  subjectResolver?: SubjectResolverInterface;
2181
2607
  catalog?: CatalogPort;
2182
2608
  moderator?: MessageModerator;
2609
+ /**
2610
+ * Converte nota de voz em texto. Exige `objectStorage` com `getObject` para o modo sob demanda —
2611
+ * transcrever um áudio já salvo significa ler os bytes de volta.
2612
+ */
2613
+ transcription?: MetaWhatsAppTranscriptionConfig;
2183
2614
  }
2184
2615
  interface CreateMetaWhatsAppModuleParams {
2185
2616
  db: MetaWhatsAppDatabase;
@@ -2205,10 +2636,26 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
2205
2636
  delete: DeleteConversationUseCase;
2206
2637
  purgeExpiredDocuments: PurgeExpiredDocumentsUseCase;
2207
2638
  export: ExportConversationUseCase;
2639
+ transcribeAudio: TranscribeAudioUseCase | undefined;
2208
2640
  repository: SessionRepository;
2641
+ messageRepository: MessageRepository;
2209
2642
  documentRepository: DocumentRepository;
2210
2643
  };
2644
+ /**
2645
+ * `undefined` = o host não injetou transcritor, e nenhuma configuração de empresa muda isso: a
2646
+ * capacidade não existe. Presente, `resolvePolicy` responde o que vale para uma empresa —
2647
+ * é o que a rota de configurações usa para dizer ao painel se desenha o interruptor.
2648
+ */
2649
+ transcription: {
2650
+ defaultMode: TranscriptionMode;
2651
+ resolvePolicy: (companyId: string) => Promise<TranscriptionPolicy>;
2652
+ } | undefined;
2211
2653
  settings: SettingsRepository;
2654
+ /**
2655
+ * `undefined` quando o recurso não está ligado (ou falta storage legível). O host consulta a
2656
+ * ausência para não registrar a rota de upload — e o preview, sem a rota, esconde o microfone.
2657
+ */
2658
+ previewMedia: StorePreviewMediaUseCase | undefined;
2212
2659
  webhook: {
2213
2660
  receive: ReceiveWebhookUseCase;
2214
2661
  verifyChallenge: (query: {
@@ -2233,9 +2680,36 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
2233
2680
  };
2234
2681
  type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>;
2235
2682
 
2683
+ /**
2684
+ * Leitura de mídia do simulador de conversa.
2685
+ *
2686
+ * **Atrás de flag de propósito, e não ligado por omissão.** Aceitar id que não veio da Meta é
2687
+ * exatamente o que um webhook forjado exploraria: bastaria mandar `preview-upload:<chave>` para
2688
+ * fazer o servidor ler um objeto arbitrário do storage e devolvê-lo. Num ambiente de simulação isso
2689
+ * é o recurso; em produção é leitura arbitrária. Quem liga assume, e a decisão fica visível no
2690
+ * lugar onde o módulo é montado.
2691
+ */
2692
+ type PreviewMediaSupport = {
2693
+ readonly isEnabled: boolean;
2694
+ readonly objectStorage: ObjectStorageInterface & {
2695
+ getObject: NonNullable<ObjectStorageInterface['getObject']>;
2696
+ };
2697
+ /** Mime a devolver, já que o storage guarda bytes e não o tipo. Padrão `audio/ogg`. */
2698
+ readonly defaultMimeType?: string;
2699
+ };
2236
2700
  declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
2237
2701
  private readonly messages;
2238
- constructor(messages: WhatsAppMessageProvider);
2702
+ /**
2703
+ * Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
2704
+ * que atualizar o pacote não abre nada em quem não pediu.
2705
+ */
2706
+ private readonly previewMedia?;
2707
+ constructor(messages: WhatsAppMessageProvider,
2708
+ /**
2709
+ * Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
2710
+ * que atualizar o pacote não abre nada em quem não pediu.
2711
+ */
2712
+ previewMedia?: PreviewMediaSupport | undefined);
2239
2713
  private translateErrors;
2240
2714
  sendText(to: string, body: string): Promise<{
2241
2715
  externalMessageId: string | null;
@@ -2268,6 +2742,12 @@ declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
2268
2742
  }): Promise<{
2269
2743
  externalMessageId: string | null;
2270
2744
  }>;
2745
+ /**
2746
+ * Busca o binário da mídia — da Meta, ou do storage quando o id é do simulador.
2747
+ *
2748
+ * O desvio acontece ANTES de qualquer chamada de rede: id do simulador não existe na Meta, e
2749
+ * tentar buscá-lo lá renderia um 404 confuso em vez do áudio que o operador acabou de gravar.
2750
+ */
2271
2751
  fetchMediaAsBase64(mediaId: string): Promise<{
2272
2752
  data: string;
2273
2753
  mimeType: string;
@@ -2349,4 +2829,4 @@ type CreateSendMediaActionParams = {
2349
2829
  */
2350
2830
  declare function createSendMediaAction(params: CreateSendMediaActionParams): FlowActionHandler;
2351
2831
 
2352
- export { type AttachFlowMediaParams, type CompanyDocumentView, type CompanyDocumentsPage, type ConversationDocumentView, type ConversationDocumentsPage, type CreateFlowGraphParams, CreateFlowGraphUseCase, type CreateMetaWhatsAppModuleParams, type CreateSendMediaActionParams, DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS, type DeleteConversationParams, type DeleteConversationResult, DeleteConversationUseCase, DeleteFlowGraphUseCase, DocumentRepository, type DocumentRow, type DrizzleMigrateFunction, type ExportConversationParams, type ExportConversationResult, ExportConversationUseCase, FlowGraphCache, FlowGraphRepository, type FlowGraphRow, FlowInterpreter, type FlowMediaLocation, FlowMediaRepository, type FlowMediaRow, type FlowMediaTranscriptLogger, type FlowRunResult, type FlowStepInput, type FlowStepResult, GetFlowGraphUseCase, GetLiveFlowPositionsUseCase, type IngestInboundMediaParams, type IngestInboundMediaResult, IngestInboundMediaUseCase, type InsertMessageParams, InvalidFlowGraphError, type LinkDocumentParams, type ListCompanyDocumentsParams, ListCompanyDocumentsUseCase, type ListConversationDocumentsParams, ListConversationDocumentsUseCase, type ListConversationsFilters, type ListConversationsParams, ListConversationsUseCase, type ListDocumentsParams, type ListDocumentsResult, ListFlowGraphsUseCase, type ListMessagesParams, ListMessagesUseCase, type LogMessageParams, LogMessageUseCase, META_WHATSAPP_MIGRATIONS_TABLE, type MessageModerator, MessageRepository, type MessageRow, type MetaWhatsAppDatabase, type MetaWhatsAppModule, type MetaWhatsAppModuleConfig, type MetaWhatsAppModuleFeatures, type MetaWhatsAppModuleProviders, type NewDocumentRow, type NewFlowGraphRow, type NewFlowMediaRow, type NewMessageRow, type NewSessionRow, type NewSettingsRow, type NonceStoreInterface, OptimisticLockError, type PurgeExpiredDocumentsParams, type PurgeExpiredDocumentsResult, PurgeExpiredDocumentsUseCase, type RealtimeRelay, type ReceiveWebhookParams, type ReceiveWebhookResult, ReceiveWebhookUseCase, type ReleaseConversationParams, ReleaseConversationUseCase, type ListMessagesParams$1 as RepositoryListMessagesParams, type RunMetaWhatsAppMigrationsParams, type SaveFlowGraphParams, SaveFlowGraphUseCase, type SendMediaParams, SendMessageUseCase, type SendTemplateParams, type SendTextParams, SessionRepository, type SessionRow, SettingsRepository, type SettingsRow, SseHub, type SseListener, type TakeoverConversationParams, TakeoverConversationUseCase, type TicketStoreInterface, type UpdateFlowMediaParams, WEBHOOK_NONCE_TTL_SECONDS, WhatsAppChannelAdapter, claimWebhookDelivery, createMetaWhatsAppModule, createSendMediaAction, documents, extractMediaDescriptor, flowGraphs, flowMedia, issueSseTicket, messages, metaWhatsAppMigrationsFolder, metaWhatsAppSchema, redeemSseTicket, runMetaWhatsAppMigrations, sessions, settings, verifyWebhookChallenge, verifyWebhookSignature };
2832
+ export { type AttachFlowMediaParams, type AudioTranscriber, type CompanyDocumentView, type CompanyDocumentsPage, type ConversationDocumentView, type ConversationDocumentsPage, type CreateFlowGraphParams, CreateFlowGraphUseCase, type CreateMetaWhatsAppModuleParams, type CreateSendMediaActionParams, DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS, type DeleteConversationParams, type DeleteConversationResult, DeleteConversationUseCase, DeleteFlowGraphUseCase, DocumentRepository, type DocumentRow, type DrizzleMigrateFunction, type ExportConversationParams, type ExportConversationResult, ExportConversationUseCase, FlowGraphCache, FlowGraphRepository, type FlowGraphRow, FlowInterpreter, type FlowMediaLocation, FlowMediaRepository, type FlowMediaRow, type FlowMediaTranscriptLogger, type FlowRunResult, type FlowStepInput, type FlowStepResult, GetFlowGraphUseCase, GetLiveFlowPositionsUseCase, type IngestInboundMediaParams, type IngestInboundMediaResult, IngestInboundMediaUseCase, type IngestTranscriptionOptions, type InsertMessageParams, InvalidFlowGraphError, type LinkDocumentParams, type ListCompanyDocumentsParams, ListCompanyDocumentsUseCase, type ListConversationDocumentsParams, ListConversationDocumentsUseCase, type ListConversationsFilters, type ListConversationsParams, ListConversationsUseCase, type ListDocumentsParams, type ListDocumentsResult, ListFlowGraphsUseCase, type ListMessagesParams, ListMessagesUseCase, type LogMessageParams, LogMessageUseCase, META_WHATSAPP_MIGRATIONS_TABLE, type MessageModerator, MessageRepository, type MessageRow, type MetaWhatsAppDatabase, type MetaWhatsAppModule, type MetaWhatsAppModuleConfig, type MetaWhatsAppModuleFeatures, type MetaWhatsAppModuleProviders, type MetaWhatsAppTranscriptionConfig, type NewDocumentRow, type NewFlowGraphRow, type NewFlowMediaRow, type NewMessageRow, type NewSessionRow, type NewSettingsRow, type NonceStoreInterface, OptimisticLockError, type PreviewMediaSupport, type PurgeExpiredDocumentsParams, type PurgeExpiredDocumentsResult, PurgeExpiredDocumentsUseCase, type RealtimeRelay, type ReceiveWebhookParams, type ReceiveWebhookResult, ReceiveWebhookUseCase, type ReleaseConversationParams, ReleaseConversationUseCase, type ListMessagesParams$1 as RepositoryListMessagesParams, type ResolveTranscriptionPolicyDependencies, type RunMetaWhatsAppMigrationsParams, type SaveFlowGraphParams, SaveFlowGraphUseCase, type SaveTranscriptionByWaMessageIdParams, type SaveTranscriptionParams, type SendMediaParams, SendMessageUseCase, type SendTemplateParams, type SendTextParams, SessionRepository, type SessionRow, SettingsRepository, type SettingsRow, SseHub, type SseListener, type StorePreviewMediaParams, type StorePreviewMediaResult, StorePreviewMediaUseCase, TRANSCRIPTION_MODE, TRANSCRIPTION_STATUS, type TakeoverConversationParams, TakeoverConversationUseCase, type TicketStoreInterface, type TranscribeAudioDependencies, type TranscribeAudioParams, type TranscribeAudioResult, TranscribeAudioUseCase, type TranscriptionPolicy, type TranscriptionPolicyDefaults, type TranscriptionPolicyResolver, type TranscriptionStatus, type UpdateFlowMediaParams, WEBHOOK_NONCE_TTL_SECONDS, WhatsAppChannelAdapter, claimWebhookDelivery, createMetaWhatsAppModule, createSendMediaAction, createTranscriptionPolicyResolver, documents, extractMediaDescriptor, flowGraphs, flowMedia, isAudioMimeType, isRetriableTranscriptionError, isUnsupportedTranscriptionError, issueSseTicket, messages, metaWhatsAppMigrationsFolder, metaWhatsAppSchema, redeemSseTicket, resolveFailureStatus, runMetaWhatsAppMigrations, sessions, settings, transcriptionRetryAfterSeconds, verifyWebhookChallenge, verifyWebhookSignature };