portal-aiatende-api 0.3.0 → 0.4.0
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.mts +196 -1
- package/dist/index.d.ts +196 -1
- package/dist/index.js +91 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +90 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -94,6 +94,14 @@ interface AiConfig {
|
|
|
94
94
|
untaggedLeadAiState?: 'inactive' | 'active' | 'active_for_new_leads';
|
|
95
95
|
/** Estado da IA após handoff. 'inactive' = trava permanente e global (nunca reativa sozinha). 'untag' = remove a tag, elegível pro bootstrap na próxima mensagem. */
|
|
96
96
|
handoffAiState?: 'inactive' | 'untag';
|
|
97
|
+
/** Canal WhatsApp direto (aiatendeWa) — domínio separado das tags Kommo acima, usa Conversation.aiCareStatus/aiResumeMode. Se true (default), conversas novas nesse canal já nascem com aiCareStatus ACTIVE; se false, nascem INACTIVE e exigem ativação manual pelo Portal. */
|
|
98
|
+
aiatendeWaDefaultActive?: boolean;
|
|
99
|
+
/** Canal aiatendeWa: responder em áudio (TTS) quando o lead mandar uma mensagem de áudio. Default true. */
|
|
100
|
+
audioReplyOnInboundAudio?: boolean;
|
|
101
|
+
/** Canal aiatendeWa: responder em áudio em vez de texto quando a resposta gerada passar desse tamanho (caracteres). `undefined`/`null`/`0` desliga esse gatilho específico. Default 300. */
|
|
102
|
+
audioReplyCharThreshold?: number | null;
|
|
103
|
+
/** Canal aiatendeWa: id bruto da voz de TTS escolhida — seu significado depende de qual provider `model` indica no momento do envio (OpenAI e Gemini têm listas de vozes totalmente diferentes). */
|
|
104
|
+
ttsVoice?: string | null;
|
|
97
105
|
/** Ativa a Reflection Agent (segunda passada de revisão/auditoria da resposta) pra este cliente. Ausente/undefined = usa o fallback global (env USE_REFLECTION_AGENT) no atende360-assistant — ver Orchestrator.ts. */
|
|
98
106
|
reflectionAgentEnabled?: boolean;
|
|
99
107
|
/** Limiar mínimo de similaridade (cosine, 0-1) pra um trecho da base de conhecimento entrar no contexto do RAG. `undefined`/`null` = o Portal usa o fallback global 0.5 até o admin calibrar via RAG eval. Aplicado internamente pelo Portal em `POST /api/knowledge/internal/context` — não precisa ser enviado pelo assistant-api. */
|
|
@@ -286,6 +294,13 @@ declare class ClientsResource extends BaseResource {
|
|
|
286
294
|
getByKommoSubdomain(subdomain: string): Promise<Client | null>;
|
|
287
295
|
/** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */
|
|
288
296
|
getByWazzupChannelId(channelId: string): Promise<Client | null>;
|
|
297
|
+
/**
|
|
298
|
+
* Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e
|
|
299
|
+
* `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o
|
|
300
|
+
* tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de
|
|
301
|
+
* um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.
|
|
302
|
+
*/
|
|
303
|
+
getById(clientId: string): Promise<Client | null>;
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
/** Uma mensagem simplificada do histórico da conversa, enviada pro Portal reescrever a
|
|
@@ -321,11 +336,191 @@ declare class KnowledgeResource extends BaseResource {
|
|
|
321
336
|
getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult>;
|
|
322
337
|
}
|
|
323
338
|
|
|
339
|
+
type AiCareStatus = 'ACTIVE' | 'INACTIVE' | 'PAUSED';
|
|
340
|
+
type AiResumeMode = 'immediate' | 'next_message';
|
|
341
|
+
interface MessagingContact {
|
|
342
|
+
id: string;
|
|
343
|
+
waId: string;
|
|
344
|
+
profileName: string | null;
|
|
345
|
+
displayName: string | null;
|
|
346
|
+
displayNameSetByName: string | null;
|
|
347
|
+
displayNameSetAt: Date | string | null;
|
|
348
|
+
}
|
|
349
|
+
interface MessagingPhoneNumber {
|
|
350
|
+
id: string;
|
|
351
|
+
phoneNumberId: string;
|
|
352
|
+
displayNumber: string;
|
|
353
|
+
metaConnection: {
|
|
354
|
+
channel: string;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
interface MessagingConversation {
|
|
358
|
+
id: string;
|
|
359
|
+
contactId: string;
|
|
360
|
+
phoneNumberId: string;
|
|
361
|
+
lastMessageAt: Date | string | null;
|
|
362
|
+
contact: MessagingContact;
|
|
363
|
+
phoneNumber: MessagingPhoneNumber;
|
|
364
|
+
_count: {
|
|
365
|
+
messages: number;
|
|
366
|
+
};
|
|
367
|
+
assignedToId: string | null;
|
|
368
|
+
assignedTo: {
|
|
369
|
+
id: string;
|
|
370
|
+
name: string;
|
|
371
|
+
email: string;
|
|
372
|
+
} | null;
|
|
373
|
+
lastMessage: Record<string, unknown> | null;
|
|
374
|
+
lastInboundAt: Date | string | null;
|
|
375
|
+
lastOutboundAt: Date | string | null;
|
|
376
|
+
temperature: 'HOT' | 'WARM' | 'COLD' | null;
|
|
377
|
+
aiCareStatus: AiCareStatus;
|
|
378
|
+
aiCareStatusSetAt: Date | string | null;
|
|
379
|
+
aiCareStatusSetByName: string | null;
|
|
380
|
+
/** Só presente na resposta do GET de detalhe (não na listagem). */
|
|
381
|
+
aiResumeMode?: AiResumeMode | null;
|
|
382
|
+
[key: string]: unknown;
|
|
383
|
+
}
|
|
384
|
+
interface MessagingConversationsListResponse {
|
|
385
|
+
data: MessagingConversation[];
|
|
386
|
+
meta: {
|
|
387
|
+
total: number;
|
|
388
|
+
page: number;
|
|
389
|
+
limit: number;
|
|
390
|
+
totalPages: number;
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
interface MessagingMessage {
|
|
394
|
+
id: string;
|
|
395
|
+
externalId: string;
|
|
396
|
+
conversationId: string;
|
|
397
|
+
direction: 'inbound' | 'outbound';
|
|
398
|
+
type: string;
|
|
399
|
+
status: string;
|
|
400
|
+
content: Record<string, unknown>;
|
|
401
|
+
waMessageId: string | null;
|
|
402
|
+
media: unknown[];
|
|
403
|
+
sentByType: 'platform_user' | 'api_key' | 'external_agent' | 'whatsapp_app' | null;
|
|
404
|
+
sentById: string | null;
|
|
405
|
+
sentByName: string | null;
|
|
406
|
+
error: string | null;
|
|
407
|
+
errorCode: string | null;
|
|
408
|
+
errorDetails: unknown;
|
|
409
|
+
createdAt: Date | string;
|
|
410
|
+
updatedAt: Date | string;
|
|
411
|
+
}
|
|
412
|
+
interface MessagingMessagesListResponse {
|
|
413
|
+
data: MessagingMessage[];
|
|
414
|
+
meta: {
|
|
415
|
+
total: number;
|
|
416
|
+
page: number;
|
|
417
|
+
limit: number;
|
|
418
|
+
totalPages: number;
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
interface ListConversationsFilters {
|
|
422
|
+
page?: number;
|
|
423
|
+
limit?: number;
|
|
424
|
+
assignedToId?: string | 'unassigned';
|
|
425
|
+
phoneNumberId?: string;
|
|
426
|
+
contactWaId?: string;
|
|
427
|
+
contactName?: string;
|
|
428
|
+
createdFrom?: string;
|
|
429
|
+
createdTo?: string;
|
|
430
|
+
lastMessageFrom?: string;
|
|
431
|
+
lastMessageTo?: string;
|
|
432
|
+
sortBy?: 'default' | 'createdAt' | 'lastMessageAt' | 'contactName';
|
|
433
|
+
sortDirection?: 'asc' | 'desc';
|
|
434
|
+
}
|
|
435
|
+
interface PatchConversationPayload {
|
|
436
|
+
assignedToId?: string | null;
|
|
437
|
+
contactDisplayName?: string | null;
|
|
438
|
+
aiCareStatus?: AiCareStatus;
|
|
439
|
+
/** Só tem efeito quando `aiCareStatus` é 'ACTIVE' — em qualquer outro status o Portal força `null`. */
|
|
440
|
+
aiResumeMode?: AiResumeMode | null;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* `actor` só é respeitado pelo Portal quando o chamador é um papel de sistema/staff (ex.: o
|
|
444
|
+
* atende360-assistant autenticado via APP_API_KEY) — um usuário comum (OWNER/MEMBER) sempre
|
|
445
|
+
* manda como si mesmo, a API ignora um `actor` customizado nesse caso.
|
|
446
|
+
*/
|
|
447
|
+
interface SendMessagePayload {
|
|
448
|
+
phoneNumberId: string;
|
|
449
|
+
to: string;
|
|
450
|
+
type: string;
|
|
451
|
+
content: Record<string, unknown> & {
|
|
452
|
+
id?: string;
|
|
453
|
+
link?: string;
|
|
454
|
+
_media?: {
|
|
455
|
+
storagePath?: string | null;
|
|
456
|
+
mimeType?: string;
|
|
457
|
+
fileSize?: number;
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
replyToWaMessageId?: string;
|
|
461
|
+
actor?: {
|
|
462
|
+
externalUserId: string;
|
|
463
|
+
externalUserName?: string;
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
interface SendMessageResponse {
|
|
467
|
+
messageId: string;
|
|
468
|
+
conversationId: string;
|
|
469
|
+
status: string;
|
|
470
|
+
correlationId: string;
|
|
471
|
+
}
|
|
472
|
+
interface UploadMediaPayload {
|
|
473
|
+
/** Bytes do arquivo (buffer do áudio/imagem/etc. gerado ou recebido). */
|
|
474
|
+
file: Buffer;
|
|
475
|
+
filename: string;
|
|
476
|
+
mimeType: string;
|
|
477
|
+
phoneNumberId: string;
|
|
478
|
+
/**
|
|
479
|
+
* true faz a plataforma transcodificar pra OGG/Opus mono e renderizar como balão de
|
|
480
|
+
* mensagem de voz no WhatsApp (em vez de anexo de áudio genérico) — usar sempre que for
|
|
481
|
+
* uma resposta de TTS da IA.
|
|
482
|
+
*/
|
|
483
|
+
isVoiceNote?: boolean;
|
|
484
|
+
}
|
|
485
|
+
interface UploadMediaResponse {
|
|
486
|
+
id: string | null;
|
|
487
|
+
storagePath: string | null;
|
|
488
|
+
filename: string;
|
|
489
|
+
mimeType: string;
|
|
490
|
+
fileSize: number;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Canal WhatsApp direto (aiatendeWa) — proxy pras rotas `/api/clients/{clientId}/messaging/...`
|
|
495
|
+
* do Portal, que por sua vez falam com a plataforma de mensageria própria
|
|
496
|
+
* (plataforma_whatsapp_coexistence). O atende360-assistant nunca fala direto com essa
|
|
497
|
+
* plataforma — sempre por aqui, autenticado via a mesma `apiKey`/APP_API_KEY já usada pro
|
|
498
|
+
* resto do SDK.
|
|
499
|
+
*/
|
|
500
|
+
declare class MessagingResource extends BaseResource {
|
|
501
|
+
listConversations(clientId: string, filters?: ListConversationsFilters): Promise<MessagingConversationsListResponse>;
|
|
502
|
+
getConversation(clientId: string, conversationId: string): Promise<MessagingConversation>;
|
|
503
|
+
patchConversation(clientId: string, conversationId: string, payload: PatchConversationPayload): Promise<MessagingConversation>;
|
|
504
|
+
getMessages(clientId: string, conversationId: string, params?: {
|
|
505
|
+
page?: number;
|
|
506
|
+
limit?: number;
|
|
507
|
+
}): Promise<MessagingMessagesListResponse>;
|
|
508
|
+
sendMessage(clientId: string, conversationId: string, payload: SendMessagePayload): Promise<SendMessageResponse>;
|
|
509
|
+
/**
|
|
510
|
+
* Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no
|
|
511
|
+
* `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`
|
|
512
|
+
* precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o
|
|
513
|
+
* registro de mídia local pra conexões Cloud API).
|
|
514
|
+
*/
|
|
515
|
+
uploadMedia(clientId: string, payload: UploadMediaPayload): Promise<UploadMediaResponse>;
|
|
516
|
+
}
|
|
517
|
+
|
|
324
518
|
declare class PortalClient {
|
|
325
519
|
config: PortalClientConfig;
|
|
326
520
|
httpClient: AxiosInstance;
|
|
327
521
|
clients: ClientsResource;
|
|
328
522
|
knowledge: KnowledgeResource;
|
|
523
|
+
messaging: MessagingResource;
|
|
329
524
|
constructor(config: PortalClientConfig);
|
|
330
525
|
}
|
|
331
526
|
|
|
@@ -335,4 +530,4 @@ declare class PortalApiError extends Error {
|
|
|
335
530
|
constructor(message: string, info?: Partial<PortalApiError>);
|
|
336
531
|
}
|
|
337
532
|
|
|
338
|
-
export { type AiActivationSchedule, type AiConfig, type AiExperiment, type AiExperimentConfig, type AiExperimentCriteria, type AiExperimentVariant, type AiPrompt, type BusinessHours, type CalendarConfig, type Client, type ClientsListResponse, ClientsResource, type ConversationHistoryMessage, type DateException, type GetInternalContextParams, type InternalContextChunk, type InternalContextResult, KnowledgeResource, type KommoAccount, type KommoConfig, type LossReasonMapping, type NudgeSettings, type PipelineMapping, PortalApiError, PortalClient, type PortalClientConfig, type StageMapping, type TimeRange, type Wazzup, type WazzupChannel };
|
|
533
|
+
export { type AiActivationSchedule, type AiCareStatus, type AiConfig, type AiExperiment, type AiExperimentConfig, type AiExperimentCriteria, type AiExperimentVariant, type AiPrompt, type AiResumeMode, type BusinessHours, type CalendarConfig, type Client, type ClientsListResponse, ClientsResource, type ConversationHistoryMessage, type DateException, type GetInternalContextParams, type InternalContextChunk, type InternalContextResult, KnowledgeResource, type KommoAccount, type KommoConfig, type ListConversationsFilters, type LossReasonMapping, type MessagingContact, type MessagingConversation, type MessagingConversationsListResponse, type MessagingMessage, type MessagingMessagesListResponse, type MessagingPhoneNumber, MessagingResource, type NudgeSettings, type PatchConversationPayload, type PipelineMapping, PortalApiError, PortalClient, type PortalClientConfig, type SendMessagePayload, type SendMessageResponse, type StageMapping, type TimeRange, type UploadMediaPayload, type UploadMediaResponse, type Wazzup, type WazzupChannel };
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,14 @@ interface AiConfig {
|
|
|
94
94
|
untaggedLeadAiState?: 'inactive' | 'active' | 'active_for_new_leads';
|
|
95
95
|
/** Estado da IA após handoff. 'inactive' = trava permanente e global (nunca reativa sozinha). 'untag' = remove a tag, elegível pro bootstrap na próxima mensagem. */
|
|
96
96
|
handoffAiState?: 'inactive' | 'untag';
|
|
97
|
+
/** Canal WhatsApp direto (aiatendeWa) — domínio separado das tags Kommo acima, usa Conversation.aiCareStatus/aiResumeMode. Se true (default), conversas novas nesse canal já nascem com aiCareStatus ACTIVE; se false, nascem INACTIVE e exigem ativação manual pelo Portal. */
|
|
98
|
+
aiatendeWaDefaultActive?: boolean;
|
|
99
|
+
/** Canal aiatendeWa: responder em áudio (TTS) quando o lead mandar uma mensagem de áudio. Default true. */
|
|
100
|
+
audioReplyOnInboundAudio?: boolean;
|
|
101
|
+
/** Canal aiatendeWa: responder em áudio em vez de texto quando a resposta gerada passar desse tamanho (caracteres). `undefined`/`null`/`0` desliga esse gatilho específico. Default 300. */
|
|
102
|
+
audioReplyCharThreshold?: number | null;
|
|
103
|
+
/** Canal aiatendeWa: id bruto da voz de TTS escolhida — seu significado depende de qual provider `model` indica no momento do envio (OpenAI e Gemini têm listas de vozes totalmente diferentes). */
|
|
104
|
+
ttsVoice?: string | null;
|
|
97
105
|
/** Ativa a Reflection Agent (segunda passada de revisão/auditoria da resposta) pra este cliente. Ausente/undefined = usa o fallback global (env USE_REFLECTION_AGENT) no atende360-assistant — ver Orchestrator.ts. */
|
|
98
106
|
reflectionAgentEnabled?: boolean;
|
|
99
107
|
/** Limiar mínimo de similaridade (cosine, 0-1) pra um trecho da base de conhecimento entrar no contexto do RAG. `undefined`/`null` = o Portal usa o fallback global 0.5 até o admin calibrar via RAG eval. Aplicado internamente pelo Portal em `POST /api/knowledge/internal/context` — não precisa ser enviado pelo assistant-api. */
|
|
@@ -286,6 +294,13 @@ declare class ClientsResource extends BaseResource {
|
|
|
286
294
|
getByKommoSubdomain(subdomain: string): Promise<Client | null>;
|
|
287
295
|
/** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */
|
|
288
296
|
getByWazzupChannelId(channelId: string): Promise<Client | null>;
|
|
297
|
+
/**
|
|
298
|
+
* Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e
|
|
299
|
+
* `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o
|
|
300
|
+
* tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de
|
|
301
|
+
* um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.
|
|
302
|
+
*/
|
|
303
|
+
getById(clientId: string): Promise<Client | null>;
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
/** Uma mensagem simplificada do histórico da conversa, enviada pro Portal reescrever a
|
|
@@ -321,11 +336,191 @@ declare class KnowledgeResource extends BaseResource {
|
|
|
321
336
|
getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult>;
|
|
322
337
|
}
|
|
323
338
|
|
|
339
|
+
type AiCareStatus = 'ACTIVE' | 'INACTIVE' | 'PAUSED';
|
|
340
|
+
type AiResumeMode = 'immediate' | 'next_message';
|
|
341
|
+
interface MessagingContact {
|
|
342
|
+
id: string;
|
|
343
|
+
waId: string;
|
|
344
|
+
profileName: string | null;
|
|
345
|
+
displayName: string | null;
|
|
346
|
+
displayNameSetByName: string | null;
|
|
347
|
+
displayNameSetAt: Date | string | null;
|
|
348
|
+
}
|
|
349
|
+
interface MessagingPhoneNumber {
|
|
350
|
+
id: string;
|
|
351
|
+
phoneNumberId: string;
|
|
352
|
+
displayNumber: string;
|
|
353
|
+
metaConnection: {
|
|
354
|
+
channel: string;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
interface MessagingConversation {
|
|
358
|
+
id: string;
|
|
359
|
+
contactId: string;
|
|
360
|
+
phoneNumberId: string;
|
|
361
|
+
lastMessageAt: Date | string | null;
|
|
362
|
+
contact: MessagingContact;
|
|
363
|
+
phoneNumber: MessagingPhoneNumber;
|
|
364
|
+
_count: {
|
|
365
|
+
messages: number;
|
|
366
|
+
};
|
|
367
|
+
assignedToId: string | null;
|
|
368
|
+
assignedTo: {
|
|
369
|
+
id: string;
|
|
370
|
+
name: string;
|
|
371
|
+
email: string;
|
|
372
|
+
} | null;
|
|
373
|
+
lastMessage: Record<string, unknown> | null;
|
|
374
|
+
lastInboundAt: Date | string | null;
|
|
375
|
+
lastOutboundAt: Date | string | null;
|
|
376
|
+
temperature: 'HOT' | 'WARM' | 'COLD' | null;
|
|
377
|
+
aiCareStatus: AiCareStatus;
|
|
378
|
+
aiCareStatusSetAt: Date | string | null;
|
|
379
|
+
aiCareStatusSetByName: string | null;
|
|
380
|
+
/** Só presente na resposta do GET de detalhe (não na listagem). */
|
|
381
|
+
aiResumeMode?: AiResumeMode | null;
|
|
382
|
+
[key: string]: unknown;
|
|
383
|
+
}
|
|
384
|
+
interface MessagingConversationsListResponse {
|
|
385
|
+
data: MessagingConversation[];
|
|
386
|
+
meta: {
|
|
387
|
+
total: number;
|
|
388
|
+
page: number;
|
|
389
|
+
limit: number;
|
|
390
|
+
totalPages: number;
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
interface MessagingMessage {
|
|
394
|
+
id: string;
|
|
395
|
+
externalId: string;
|
|
396
|
+
conversationId: string;
|
|
397
|
+
direction: 'inbound' | 'outbound';
|
|
398
|
+
type: string;
|
|
399
|
+
status: string;
|
|
400
|
+
content: Record<string, unknown>;
|
|
401
|
+
waMessageId: string | null;
|
|
402
|
+
media: unknown[];
|
|
403
|
+
sentByType: 'platform_user' | 'api_key' | 'external_agent' | 'whatsapp_app' | null;
|
|
404
|
+
sentById: string | null;
|
|
405
|
+
sentByName: string | null;
|
|
406
|
+
error: string | null;
|
|
407
|
+
errorCode: string | null;
|
|
408
|
+
errorDetails: unknown;
|
|
409
|
+
createdAt: Date | string;
|
|
410
|
+
updatedAt: Date | string;
|
|
411
|
+
}
|
|
412
|
+
interface MessagingMessagesListResponse {
|
|
413
|
+
data: MessagingMessage[];
|
|
414
|
+
meta: {
|
|
415
|
+
total: number;
|
|
416
|
+
page: number;
|
|
417
|
+
limit: number;
|
|
418
|
+
totalPages: number;
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
interface ListConversationsFilters {
|
|
422
|
+
page?: number;
|
|
423
|
+
limit?: number;
|
|
424
|
+
assignedToId?: string | 'unassigned';
|
|
425
|
+
phoneNumberId?: string;
|
|
426
|
+
contactWaId?: string;
|
|
427
|
+
contactName?: string;
|
|
428
|
+
createdFrom?: string;
|
|
429
|
+
createdTo?: string;
|
|
430
|
+
lastMessageFrom?: string;
|
|
431
|
+
lastMessageTo?: string;
|
|
432
|
+
sortBy?: 'default' | 'createdAt' | 'lastMessageAt' | 'contactName';
|
|
433
|
+
sortDirection?: 'asc' | 'desc';
|
|
434
|
+
}
|
|
435
|
+
interface PatchConversationPayload {
|
|
436
|
+
assignedToId?: string | null;
|
|
437
|
+
contactDisplayName?: string | null;
|
|
438
|
+
aiCareStatus?: AiCareStatus;
|
|
439
|
+
/** Só tem efeito quando `aiCareStatus` é 'ACTIVE' — em qualquer outro status o Portal força `null`. */
|
|
440
|
+
aiResumeMode?: AiResumeMode | null;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* `actor` só é respeitado pelo Portal quando o chamador é um papel de sistema/staff (ex.: o
|
|
444
|
+
* atende360-assistant autenticado via APP_API_KEY) — um usuário comum (OWNER/MEMBER) sempre
|
|
445
|
+
* manda como si mesmo, a API ignora um `actor` customizado nesse caso.
|
|
446
|
+
*/
|
|
447
|
+
interface SendMessagePayload {
|
|
448
|
+
phoneNumberId: string;
|
|
449
|
+
to: string;
|
|
450
|
+
type: string;
|
|
451
|
+
content: Record<string, unknown> & {
|
|
452
|
+
id?: string;
|
|
453
|
+
link?: string;
|
|
454
|
+
_media?: {
|
|
455
|
+
storagePath?: string | null;
|
|
456
|
+
mimeType?: string;
|
|
457
|
+
fileSize?: number;
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
replyToWaMessageId?: string;
|
|
461
|
+
actor?: {
|
|
462
|
+
externalUserId: string;
|
|
463
|
+
externalUserName?: string;
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
interface SendMessageResponse {
|
|
467
|
+
messageId: string;
|
|
468
|
+
conversationId: string;
|
|
469
|
+
status: string;
|
|
470
|
+
correlationId: string;
|
|
471
|
+
}
|
|
472
|
+
interface UploadMediaPayload {
|
|
473
|
+
/** Bytes do arquivo (buffer do áudio/imagem/etc. gerado ou recebido). */
|
|
474
|
+
file: Buffer;
|
|
475
|
+
filename: string;
|
|
476
|
+
mimeType: string;
|
|
477
|
+
phoneNumberId: string;
|
|
478
|
+
/**
|
|
479
|
+
* true faz a plataforma transcodificar pra OGG/Opus mono e renderizar como balão de
|
|
480
|
+
* mensagem de voz no WhatsApp (em vez de anexo de áudio genérico) — usar sempre que for
|
|
481
|
+
* uma resposta de TTS da IA.
|
|
482
|
+
*/
|
|
483
|
+
isVoiceNote?: boolean;
|
|
484
|
+
}
|
|
485
|
+
interface UploadMediaResponse {
|
|
486
|
+
id: string | null;
|
|
487
|
+
storagePath: string | null;
|
|
488
|
+
filename: string;
|
|
489
|
+
mimeType: string;
|
|
490
|
+
fileSize: number;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Canal WhatsApp direto (aiatendeWa) — proxy pras rotas `/api/clients/{clientId}/messaging/...`
|
|
495
|
+
* do Portal, que por sua vez falam com a plataforma de mensageria própria
|
|
496
|
+
* (plataforma_whatsapp_coexistence). O atende360-assistant nunca fala direto com essa
|
|
497
|
+
* plataforma — sempre por aqui, autenticado via a mesma `apiKey`/APP_API_KEY já usada pro
|
|
498
|
+
* resto do SDK.
|
|
499
|
+
*/
|
|
500
|
+
declare class MessagingResource extends BaseResource {
|
|
501
|
+
listConversations(clientId: string, filters?: ListConversationsFilters): Promise<MessagingConversationsListResponse>;
|
|
502
|
+
getConversation(clientId: string, conversationId: string): Promise<MessagingConversation>;
|
|
503
|
+
patchConversation(clientId: string, conversationId: string, payload: PatchConversationPayload): Promise<MessagingConversation>;
|
|
504
|
+
getMessages(clientId: string, conversationId: string, params?: {
|
|
505
|
+
page?: number;
|
|
506
|
+
limit?: number;
|
|
507
|
+
}): Promise<MessagingMessagesListResponse>;
|
|
508
|
+
sendMessage(clientId: string, conversationId: string, payload: SendMessagePayload): Promise<SendMessageResponse>;
|
|
509
|
+
/**
|
|
510
|
+
* Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no
|
|
511
|
+
* `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`
|
|
512
|
+
* precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o
|
|
513
|
+
* registro de mídia local pra conexões Cloud API).
|
|
514
|
+
*/
|
|
515
|
+
uploadMedia(clientId: string, payload: UploadMediaPayload): Promise<UploadMediaResponse>;
|
|
516
|
+
}
|
|
517
|
+
|
|
324
518
|
declare class PortalClient {
|
|
325
519
|
config: PortalClientConfig;
|
|
326
520
|
httpClient: AxiosInstance;
|
|
327
521
|
clients: ClientsResource;
|
|
328
522
|
knowledge: KnowledgeResource;
|
|
523
|
+
messaging: MessagingResource;
|
|
329
524
|
constructor(config: PortalClientConfig);
|
|
330
525
|
}
|
|
331
526
|
|
|
@@ -335,4 +530,4 @@ declare class PortalApiError extends Error {
|
|
|
335
530
|
constructor(message: string, info?: Partial<PortalApiError>);
|
|
336
531
|
}
|
|
337
532
|
|
|
338
|
-
export { type AiActivationSchedule, type AiConfig, type AiExperiment, type AiExperimentConfig, type AiExperimentCriteria, type AiExperimentVariant, type AiPrompt, type BusinessHours, type CalendarConfig, type Client, type ClientsListResponse, ClientsResource, type ConversationHistoryMessage, type DateException, type GetInternalContextParams, type InternalContextChunk, type InternalContextResult, KnowledgeResource, type KommoAccount, type KommoConfig, type LossReasonMapping, type NudgeSettings, type PipelineMapping, PortalApiError, PortalClient, type PortalClientConfig, type StageMapping, type TimeRange, type Wazzup, type WazzupChannel };
|
|
533
|
+
export { type AiActivationSchedule, type AiCareStatus, type AiConfig, type AiExperiment, type AiExperimentConfig, type AiExperimentCriteria, type AiExperimentVariant, type AiPrompt, type AiResumeMode, type BusinessHours, type CalendarConfig, type Client, type ClientsListResponse, ClientsResource, type ConversationHistoryMessage, type DateException, type GetInternalContextParams, type InternalContextChunk, type InternalContextResult, KnowledgeResource, type KommoAccount, type KommoConfig, type ListConversationsFilters, type LossReasonMapping, type MessagingContact, type MessagingConversation, type MessagingConversationsListResponse, type MessagingMessage, type MessagingMessagesListResponse, type MessagingPhoneNumber, MessagingResource, type NudgeSettings, type PatchConversationPayload, type PipelineMapping, PortalApiError, PortalClient, type PortalClientConfig, type SendMessagePayload, type SendMessageResponse, type StageMapping, type TimeRange, type UploadMediaPayload, type UploadMediaResponse, type Wazzup, type WazzupChannel };
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ClientsResource: () => ClientsResource,
|
|
34
34
|
KnowledgeResource: () => KnowledgeResource,
|
|
35
|
+
MessagingResource: () => MessagingResource,
|
|
35
36
|
PortalApiError: () => PortalApiError,
|
|
36
37
|
PortalClient: () => PortalClient
|
|
37
38
|
});
|
|
@@ -48,6 +49,20 @@ var BaseResource = class {
|
|
|
48
49
|
}
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
// src/errors.ts
|
|
53
|
+
var PortalApiError = class extends Error {
|
|
54
|
+
status;
|
|
55
|
+
raw;
|
|
56
|
+
constructor(message, info) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "PortalApiError";
|
|
59
|
+
if (info) {
|
|
60
|
+
this.status = info.status;
|
|
61
|
+
this.raw = info.raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
51
66
|
// src/resources/clients/index.ts
|
|
52
67
|
var ClientsResource = class extends BaseResource {
|
|
53
68
|
/** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */
|
|
@@ -64,26 +79,25 @@ var ClientsResource = class extends BaseResource {
|
|
|
64
79
|
});
|
|
65
80
|
return response.data.data[0] ?? null;
|
|
66
81
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.status = info.status;
|
|
81
|
-
this.raw = info.raw;
|
|
82
|
+
/**
|
|
83
|
+
* Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e
|
|
84
|
+
* `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o
|
|
85
|
+
* tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de
|
|
86
|
+
* um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.
|
|
87
|
+
*/
|
|
88
|
+
async getById(clientId) {
|
|
89
|
+
try {
|
|
90
|
+
const response = await this.client.get(`/api/clients/${clientId}`);
|
|
91
|
+
return response.data;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof PortalApiError && error.status === 404) return null;
|
|
94
|
+
throw error;
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
};
|
|
85
98
|
|
|
86
99
|
// src/resources/knowledge/index.ts
|
|
100
|
+
var import_zod = require("zod");
|
|
87
101
|
var internalContextChunkSchema = import_zod.z.object({
|
|
88
102
|
chunkId: import_zod.z.string().optional(),
|
|
89
103
|
content: import_zod.z.string(),
|
|
@@ -109,6 +123,65 @@ var KnowledgeResource = class extends BaseResource {
|
|
|
109
123
|
}
|
|
110
124
|
};
|
|
111
125
|
|
|
126
|
+
// src/resources/messaging/index.ts
|
|
127
|
+
var MessagingResource = class extends BaseResource {
|
|
128
|
+
async listConversations(clientId, filters) {
|
|
129
|
+
const response = await this.client.get(
|
|
130
|
+
`/api/clients/${clientId}/messaging/conversations`,
|
|
131
|
+
{ params: filters }
|
|
132
|
+
);
|
|
133
|
+
return response.data;
|
|
134
|
+
}
|
|
135
|
+
async getConversation(clientId, conversationId) {
|
|
136
|
+
const response = await this.client.get(
|
|
137
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}`
|
|
138
|
+
);
|
|
139
|
+
return response.data;
|
|
140
|
+
}
|
|
141
|
+
async patchConversation(clientId, conversationId, payload) {
|
|
142
|
+
const response = await this.client.patch(
|
|
143
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}`,
|
|
144
|
+
payload
|
|
145
|
+
);
|
|
146
|
+
return response.data;
|
|
147
|
+
}
|
|
148
|
+
async getMessages(clientId, conversationId, params) {
|
|
149
|
+
const response = await this.client.get(
|
|
150
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,
|
|
151
|
+
{ params }
|
|
152
|
+
);
|
|
153
|
+
return response.data;
|
|
154
|
+
}
|
|
155
|
+
async sendMessage(clientId, conversationId, payload) {
|
|
156
|
+
const response = await this.client.post(
|
|
157
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,
|
|
158
|
+
payload
|
|
159
|
+
);
|
|
160
|
+
return response.data;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no
|
|
164
|
+
* `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`
|
|
165
|
+
* precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o
|
|
166
|
+
* registro de mídia local pra conexões Cloud API).
|
|
167
|
+
*/
|
|
168
|
+
async uploadMedia(clientId, payload) {
|
|
169
|
+
const form = new FormData();
|
|
170
|
+
form.append("phoneNumberId", payload.phoneNumberId);
|
|
171
|
+
if (payload.isVoiceNote) form.append("isVoiceNote", "true");
|
|
172
|
+
form.append(
|
|
173
|
+
"file",
|
|
174
|
+
new Blob([new Uint8Array(payload.file)], { type: payload.mimeType }),
|
|
175
|
+
payload.filename
|
|
176
|
+
);
|
|
177
|
+
const response = await this.client.post(
|
|
178
|
+
`/api/clients/${clientId}/messaging/media`,
|
|
179
|
+
form
|
|
180
|
+
);
|
|
181
|
+
return response.data;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
112
185
|
// src/client.ts
|
|
113
186
|
var DEFAULT_BASE_URL = "https://portal.aiatende.com.br";
|
|
114
187
|
var PortalClient = class {
|
|
@@ -116,6 +189,7 @@ var PortalClient = class {
|
|
|
116
189
|
httpClient;
|
|
117
190
|
clients;
|
|
118
191
|
knowledge;
|
|
192
|
+
messaging;
|
|
119
193
|
constructor(config) {
|
|
120
194
|
this.config = config;
|
|
121
195
|
this.httpClient = import_axios.default.create({
|
|
@@ -141,12 +215,14 @@ var PortalClient = class {
|
|
|
141
215
|
);
|
|
142
216
|
this.clients = new ClientsResource(this.httpClient);
|
|
143
217
|
this.knowledge = new KnowledgeResource(this.httpClient);
|
|
218
|
+
this.messaging = new MessagingResource(this.httpClient);
|
|
144
219
|
}
|
|
145
220
|
};
|
|
146
221
|
// Annotate the CommonJS export names for ESM import in node:
|
|
147
222
|
0 && (module.exports = {
|
|
148
223
|
ClientsResource,
|
|
149
224
|
KnowledgeResource,
|
|
225
|
+
MessagingResource,
|
|
150
226
|
PortalApiError,
|
|
151
227
|
PortalClient
|
|
152
228
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/base.ts","../src/resources/clients/index.ts","../src/resources/knowledge/index.ts","../src/errors.ts"],"sourcesContent":["export * from './client';\nexport * from './types';\nexport * from './errors';\nexport * from './resources/clients';\nexport * from './resources/clients/types';\nexport * from './resources/knowledge';\nexport * from './resources/knowledge/types';\n","import axios, { AxiosInstance } from 'axios';\nimport { PortalClientConfig } from './types';\nimport { ClientsResource } from './resources/clients';\nimport { KnowledgeResource } from './resources/knowledge';\nimport { PortalApiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://portal.aiatende.com.br';\n\nexport class PortalClient {\n public config: PortalClientConfig;\n public httpClient: AxiosInstance;\n\n public clients: ClientsResource;\n public knowledge: KnowledgeResource;\n\n constructor(config: PortalClientConfig) {\n this.config = config;\n\n this.httpClient = axios.create({\n baseURL: config.baseUrl || DEFAULT_BASE_URL,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n 'x-internal-token': config.apiKey\n }\n });\n\n this.httpClient.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const errData = error.response.data;\n throw new PortalApiError(errData?.error || 'Portal API Error', {\n status: error.response.status,\n raw: errData\n });\n }\n throw error;\n }\n );\n\n this.clients = new ClientsResource(this.httpClient);\n this.knowledge = new KnowledgeResource(this.httpClient);\n }\n}\n","import { AxiosInstance } from 'axios';\n\nexport abstract class BaseResource {\n protected client: AxiosInstance;\n\n constructor(client: AxiosInstance) {\n this.client = client;\n }\n}\n","import { BaseResource } from '../../base';\nimport { Client, ClientsListResponse } from './types';\n\nexport class ClientsResource extends BaseResource {\n /** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */\n async getByKommoSubdomain(subdomain: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { kommo_subdomain: subdomain, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */\n async getByWazzupChannelId(channelId: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { channelId, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n}\n","import { z } from 'zod';\nimport { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { GetInternalContextParams, InternalContextResult } from './types';\n\nconst internalContextChunkSchema = z.object({\n chunkId: z.string().optional(),\n content: z.string(),\n similarity: z.number(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst internalContextResultSchema = z.array(internalContextChunkSchema);\n\nexport class KnowledgeResource extends BaseResource {\n /** Recupera trechos relevantes da base RAG do cliente pra uma pergunta em texto puro (`POST /api/knowledge/internal/context`). O Portal gera o embedding internamente usando a `apiKey` do AiConfig. */\n async getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult> {\n const response = await this.client.post<unknown>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n\n const parsed = internalContextResultSchema.safeParse(response.data);\n if (!parsed.success) {\n throw new PortalApiError('Resposta inválida de /api/knowledge/internal/context', {\n raw: parsed.error.flatten(),\n });\n }\n return parsed.data;\n }\n}\n","export class PortalApiError extends Error {\n status?: number;\n raw?: unknown;\n\n constructor(message: string, info?: Partial<PortalApiError>) {\n super(message);\n this.name = 'PortalApiError';\n\n if (info) {\n this.status = info.status;\n this.raw = info.raw;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAqC;;;ACE9B,IAAe,eAAf,MAA4B;AAAA,EACvB;AAAA,EAEV,YAAY,QAAuB;AACjC,SAAK,SAAS;AAAA,EAChB;AACF;;;ACLO,IAAM,kBAAN,cAA8B,aAAa;AAAA;AAAA,EAEhD,MAAM,oBAAoB,WAA2C;AACnE,UAAM,WAAW,MAAM,KAAK,OAAO,IAAyB,gBAAgB;AAAA,MAC1E,QAAQ,EAAE,iBAAiB,WAAW,gBAAgB,KAAK;AAAA,IAC7D,CAAC;AACD,WAAO,SAAS,KAAK,KAAK,CAAC,KAAK;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,qBAAqB,WAA2C;AACpE,UAAM,WAAW,MAAM,KAAK,OAAO,IAAyB,gBAAgB;AAAA,MAC1E,QAAQ,EAAE,WAAW,gBAAgB,KAAK;AAAA,IAC5C,CAAC;AACD,WAAO,SAAS,KAAK,KAAK,CAAC,KAAK;AAAA,EAClC;AACF;;;ACnBA,iBAAkB;;;ACAX,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,MAAgC;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AAEZ,QAAI,MAAM;AACR,WAAK,SAAS,KAAK;AACnB,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EACF;AACF;;;ADRA,IAAM,6BAA6B,aAAE,OAAO;AAAA,EAC1C,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,SAAS,aAAE,OAAO;AAAA,EAClB,YAAY,aAAE,OAAO;AAAA,EACrB,UAAU,aAAE,OAAO,aAAE,OAAO,GAAG,aAAE,QAAQ,CAAC,EAAE,SAAS;AACvD,CAAC;AAED,IAAM,8BAA8B,aAAE,MAAM,0BAA0B;AAE/D,IAAM,oBAAN,cAAgC,aAAa;AAAA;AAAA,EAElD,MAAM,mBAAmB,QAAkE;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,KAAc,mCAAmC;AAAA,MAClF,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,qBAAqB,OAAO;AAAA,IAC9B,CAAC;AAED,UAAM,SAAS,4BAA4B,UAAU,SAAS,IAAI;AAClE,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,eAAe,2DAAwD;AAAA,QAC/E,KAAK,OAAO,MAAM,QAAQ;AAAA,MAC5B,CAAC;AAAA,IACH;AACA,WAAO,OAAO;AAAA,EAChB;AACF;;;AHzBA,IAAM,mBAAmB;AAElB,IAAM,eAAN,MAAmB;AAAA,EACjB;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EAEP,YAAY,QAA4B;AACtC,SAAK,SAAS;AAEd,SAAK,aAAa,aAAAA,QAAM,OAAO;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa,OAAO;AAAA,QACpB,oBAAoB,OAAO;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,SAAK,WAAW,aAAa,SAAS;AAAA,MACpC,CAAC,aAAa;AAAA,MACd,CAAC,UAAU;AACT,YAAI,MAAM,UAAU;AAClB,gBAAM,UAAU,MAAM,SAAS;AAC/B,gBAAM,IAAI,eAAe,SAAS,SAAS,oBAAoB;AAAA,YAC7D,QAAQ,MAAM,SAAS;AAAA,YACvB,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAEA,SAAK,UAAU,IAAI,gBAAgB,KAAK,UAAU;AAClD,SAAK,YAAY,IAAI,kBAAkB,KAAK,UAAU;AAAA,EACxD;AACF;","names":["axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/base.ts","../src/errors.ts","../src/resources/clients/index.ts","../src/resources/knowledge/index.ts","../src/resources/messaging/index.ts"],"sourcesContent":["export * from './client';\nexport * from './types';\nexport * from './errors';\nexport * from './resources/clients';\nexport * from './resources/clients/types';\nexport * from './resources/knowledge';\nexport * from './resources/knowledge/types';\nexport * from './resources/messaging';\nexport * from './resources/messaging/types';\n","import axios, { AxiosInstance } from 'axios';\nimport { PortalClientConfig } from './types';\nimport { ClientsResource } from './resources/clients';\nimport { KnowledgeResource } from './resources/knowledge';\nimport { MessagingResource } from './resources/messaging';\nimport { PortalApiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://portal.aiatende.com.br';\n\nexport class PortalClient {\n public config: PortalClientConfig;\n public httpClient: AxiosInstance;\n\n public clients: ClientsResource;\n public knowledge: KnowledgeResource;\n public messaging: MessagingResource;\n\n constructor(config: PortalClientConfig) {\n this.config = config;\n\n this.httpClient = axios.create({\n baseURL: config.baseUrl || DEFAULT_BASE_URL,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n 'x-internal-token': config.apiKey\n }\n });\n\n this.httpClient.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const errData = error.response.data;\n throw new PortalApiError(errData?.error || 'Portal API Error', {\n status: error.response.status,\n raw: errData\n });\n }\n throw error;\n }\n );\n\n this.clients = new ClientsResource(this.httpClient);\n this.knowledge = new KnowledgeResource(this.httpClient);\n this.messaging = new MessagingResource(this.httpClient);\n }\n}\n","import { AxiosInstance } from 'axios';\n\nexport abstract class BaseResource {\n protected client: AxiosInstance;\n\n constructor(client: AxiosInstance) {\n this.client = client;\n }\n}\n","export class PortalApiError extends Error {\n status?: number;\n raw?: unknown;\n\n constructor(message: string, info?: Partial<PortalApiError>) {\n super(message);\n this.name = 'PortalApiError';\n\n if (info) {\n this.status = info.status;\n this.raw = info.raw;\n }\n }\n}\n","import { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { Client, ClientsListResponse } from './types';\n\nexport class ClientsResource extends BaseResource {\n /** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */\n async getByKommoSubdomain(subdomain: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { kommo_subdomain: subdomain, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */\n async getByWazzupChannelId(channelId: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { channelId, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /**\n * Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e\n * `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o\n * tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de\n * um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.\n */\n async getById(clientId: string): Promise<Client | null> {\n try {\n const response = await this.client.get<Client>(`/api/clients/${clientId}`);\n return response.data;\n } catch (error) {\n if (error instanceof PortalApiError && error.status === 404) return null;\n throw error;\n }\n }\n}\n","import { z } from 'zod';\nimport { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { GetInternalContextParams, InternalContextResult } from './types';\n\nconst internalContextChunkSchema = z.object({\n chunkId: z.string().optional(),\n content: z.string(),\n similarity: z.number(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst internalContextResultSchema = z.array(internalContextChunkSchema);\n\nexport class KnowledgeResource extends BaseResource {\n /** Recupera trechos relevantes da base RAG do cliente pra uma pergunta em texto puro (`POST /api/knowledge/internal/context`). O Portal gera o embedding internamente usando a `apiKey` do AiConfig. */\n async getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult> {\n const response = await this.client.post<unknown>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n\n const parsed = internalContextResultSchema.safeParse(response.data);\n if (!parsed.success) {\n throw new PortalApiError('Resposta inválida de /api/knowledge/internal/context', {\n raw: parsed.error.flatten(),\n });\n }\n return parsed.data;\n }\n}\n","import { BaseResource } from '../../base';\nimport {\n ListConversationsFilters,\n MessagingConversation,\n MessagingConversationsListResponse,\n MessagingMessagesListResponse,\n PatchConversationPayload,\n SendMessagePayload,\n SendMessageResponse,\n UploadMediaPayload,\n UploadMediaResponse,\n} from './types';\n\n/**\n * Canal WhatsApp direto (aiatendeWa) — proxy pras rotas `/api/clients/{clientId}/messaging/...`\n * do Portal, que por sua vez falam com a plataforma de mensageria própria\n * (plataforma_whatsapp_coexistence). O atende360-assistant nunca fala direto com essa\n * plataforma — sempre por aqui, autenticado via a mesma `apiKey`/APP_API_KEY já usada pro\n * resto do SDK.\n */\nexport class MessagingResource extends BaseResource {\n async listConversations(\n clientId: string,\n filters?: ListConversationsFilters,\n ): Promise<MessagingConversationsListResponse> {\n const response = await this.client.get<MessagingConversationsListResponse>(\n `/api/clients/${clientId}/messaging/conversations`,\n { params: filters },\n );\n return response.data;\n }\n\n async getConversation(clientId: string, conversationId: string): Promise<MessagingConversation> {\n const response = await this.client.get<MessagingConversation>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}`,\n );\n return response.data;\n }\n\n async patchConversation(\n clientId: string,\n conversationId: string,\n payload: PatchConversationPayload,\n ): Promise<MessagingConversation> {\n const response = await this.client.patch<MessagingConversation>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}`,\n payload,\n );\n return response.data;\n }\n\n async getMessages(\n clientId: string,\n conversationId: string,\n params?: { page?: number; limit?: number },\n ): Promise<MessagingMessagesListResponse> {\n const response = await this.client.get<MessagingMessagesListResponse>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,\n { params },\n );\n return response.data;\n }\n\n async sendMessage(\n clientId: string,\n conversationId: string,\n payload: SendMessagePayload,\n ): Promise<SendMessageResponse> {\n const response = await this.client.post<SendMessageResponse>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,\n payload,\n );\n return response.data;\n }\n\n /**\n * Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no\n * `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`\n * precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o\n * registro de mídia local pra conexões Cloud API).\n */\n async uploadMedia(clientId: string, payload: UploadMediaPayload): Promise<UploadMediaResponse> {\n const form = new FormData();\n // Ordem importa: phoneNumberId/isVoiceNote precisam vir ANTES de file no multipart — o\n // parser do Portal (e o da plataforma por trás dele) lê em stream.\n form.append('phoneNumberId', payload.phoneNumberId);\n if (payload.isVoiceNote) form.append('isVoiceNote', 'true');\n form.append(\n 'file',\n new Blob([new Uint8Array(payload.file)], { type: payload.mimeType }),\n payload.filename,\n );\n\n const response = await this.client.post<UploadMediaResponse>(\n `/api/clients/${clientId}/messaging/media`,\n form,\n );\n return response.data;\n }\n}\n"],"mappings":"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","names":["axios"]}
|
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,20 @@ var BaseResource = class {
|
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/errors.ts
|
|
13
|
+
var PortalApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
raw;
|
|
16
|
+
constructor(message, info) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "PortalApiError";
|
|
19
|
+
if (info) {
|
|
20
|
+
this.status = info.status;
|
|
21
|
+
this.raw = info.raw;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
12
26
|
// src/resources/clients/index.ts
|
|
13
27
|
var ClientsResource = class extends BaseResource {
|
|
14
28
|
/** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */
|
|
@@ -25,26 +39,25 @@ var ClientsResource = class extends BaseResource {
|
|
|
25
39
|
});
|
|
26
40
|
return response.data.data[0] ?? null;
|
|
27
41
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.status = info.status;
|
|
42
|
-
this.raw = info.raw;
|
|
42
|
+
/**
|
|
43
|
+
* Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e
|
|
44
|
+
* `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o
|
|
45
|
+
* tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de
|
|
46
|
+
* um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.
|
|
47
|
+
*/
|
|
48
|
+
async getById(clientId) {
|
|
49
|
+
try {
|
|
50
|
+
const response = await this.client.get(`/api/clients/${clientId}`);
|
|
51
|
+
return response.data;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof PortalApiError && error.status === 404) return null;
|
|
54
|
+
throw error;
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
// src/resources/knowledge/index.ts
|
|
60
|
+
import { z } from "zod";
|
|
48
61
|
var internalContextChunkSchema = z.object({
|
|
49
62
|
chunkId: z.string().optional(),
|
|
50
63
|
content: z.string(),
|
|
@@ -70,6 +83,65 @@ var KnowledgeResource = class extends BaseResource {
|
|
|
70
83
|
}
|
|
71
84
|
};
|
|
72
85
|
|
|
86
|
+
// src/resources/messaging/index.ts
|
|
87
|
+
var MessagingResource = class extends BaseResource {
|
|
88
|
+
async listConversations(clientId, filters) {
|
|
89
|
+
const response = await this.client.get(
|
|
90
|
+
`/api/clients/${clientId}/messaging/conversations`,
|
|
91
|
+
{ params: filters }
|
|
92
|
+
);
|
|
93
|
+
return response.data;
|
|
94
|
+
}
|
|
95
|
+
async getConversation(clientId, conversationId) {
|
|
96
|
+
const response = await this.client.get(
|
|
97
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}`
|
|
98
|
+
);
|
|
99
|
+
return response.data;
|
|
100
|
+
}
|
|
101
|
+
async patchConversation(clientId, conversationId, payload) {
|
|
102
|
+
const response = await this.client.patch(
|
|
103
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}`,
|
|
104
|
+
payload
|
|
105
|
+
);
|
|
106
|
+
return response.data;
|
|
107
|
+
}
|
|
108
|
+
async getMessages(clientId, conversationId, params) {
|
|
109
|
+
const response = await this.client.get(
|
|
110
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,
|
|
111
|
+
{ params }
|
|
112
|
+
);
|
|
113
|
+
return response.data;
|
|
114
|
+
}
|
|
115
|
+
async sendMessage(clientId, conversationId, payload) {
|
|
116
|
+
const response = await this.client.post(
|
|
117
|
+
`/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,
|
|
118
|
+
payload
|
|
119
|
+
);
|
|
120
|
+
return response.data;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no
|
|
124
|
+
* `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`
|
|
125
|
+
* precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o
|
|
126
|
+
* registro de mídia local pra conexões Cloud API).
|
|
127
|
+
*/
|
|
128
|
+
async uploadMedia(clientId, payload) {
|
|
129
|
+
const form = new FormData();
|
|
130
|
+
form.append("phoneNumberId", payload.phoneNumberId);
|
|
131
|
+
if (payload.isVoiceNote) form.append("isVoiceNote", "true");
|
|
132
|
+
form.append(
|
|
133
|
+
"file",
|
|
134
|
+
new Blob([new Uint8Array(payload.file)], { type: payload.mimeType }),
|
|
135
|
+
payload.filename
|
|
136
|
+
);
|
|
137
|
+
const response = await this.client.post(
|
|
138
|
+
`/api/clients/${clientId}/messaging/media`,
|
|
139
|
+
form
|
|
140
|
+
);
|
|
141
|
+
return response.data;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
73
145
|
// src/client.ts
|
|
74
146
|
var DEFAULT_BASE_URL = "https://portal.aiatende.com.br";
|
|
75
147
|
var PortalClient = class {
|
|
@@ -77,6 +149,7 @@ var PortalClient = class {
|
|
|
77
149
|
httpClient;
|
|
78
150
|
clients;
|
|
79
151
|
knowledge;
|
|
152
|
+
messaging;
|
|
80
153
|
constructor(config) {
|
|
81
154
|
this.config = config;
|
|
82
155
|
this.httpClient = axios.create({
|
|
@@ -102,11 +175,13 @@ var PortalClient = class {
|
|
|
102
175
|
);
|
|
103
176
|
this.clients = new ClientsResource(this.httpClient);
|
|
104
177
|
this.knowledge = new KnowledgeResource(this.httpClient);
|
|
178
|
+
this.messaging = new MessagingResource(this.httpClient);
|
|
105
179
|
}
|
|
106
180
|
};
|
|
107
181
|
export {
|
|
108
182
|
ClientsResource,
|
|
109
183
|
KnowledgeResource,
|
|
184
|
+
MessagingResource,
|
|
110
185
|
PortalApiError,
|
|
111
186
|
PortalClient
|
|
112
187
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/base.ts","../src/resources/clients/index.ts","../src/resources/knowledge/index.ts","../src/errors.ts"],"sourcesContent":["import axios, { AxiosInstance } from 'axios';\nimport { PortalClientConfig } from './types';\nimport { ClientsResource } from './resources/clients';\nimport { KnowledgeResource } from './resources/knowledge';\nimport { PortalApiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://portal.aiatende.com.br';\n\nexport class PortalClient {\n public config: PortalClientConfig;\n public httpClient: AxiosInstance;\n\n public clients: ClientsResource;\n public knowledge: KnowledgeResource;\n\n constructor(config: PortalClientConfig) {\n this.config = config;\n\n this.httpClient = axios.create({\n baseURL: config.baseUrl || DEFAULT_BASE_URL,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n 'x-internal-token': config.apiKey\n }\n });\n\n this.httpClient.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const errData = error.response.data;\n throw new PortalApiError(errData?.error || 'Portal API Error', {\n status: error.response.status,\n raw: errData\n });\n }\n throw error;\n }\n );\n\n this.clients = new ClientsResource(this.httpClient);\n this.knowledge = new KnowledgeResource(this.httpClient);\n }\n}\n","import { AxiosInstance } from 'axios';\n\nexport abstract class BaseResource {\n protected client: AxiosInstance;\n\n constructor(client: AxiosInstance) {\n this.client = client;\n }\n}\n","import { BaseResource } from '../../base';\nimport { Client, ClientsListResponse } from './types';\n\nexport class ClientsResource extends BaseResource {\n /** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */\n async getByKommoSubdomain(subdomain: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { kommo_subdomain: subdomain, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */\n async getByWazzupChannelId(channelId: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { channelId, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n}\n","import { z } from 'zod';\nimport { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { GetInternalContextParams, InternalContextResult } from './types';\n\nconst internalContextChunkSchema = z.object({\n chunkId: z.string().optional(),\n content: z.string(),\n similarity: z.number(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst internalContextResultSchema = z.array(internalContextChunkSchema);\n\nexport class KnowledgeResource extends BaseResource {\n /** Recupera trechos relevantes da base RAG do cliente pra uma pergunta em texto puro (`POST /api/knowledge/internal/context`). O Portal gera o embedding internamente usando a `apiKey` do AiConfig. */\n async getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult> {\n const response = await this.client.post<unknown>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n\n const parsed = internalContextResultSchema.safeParse(response.data);\n if (!parsed.success) {\n throw new PortalApiError('Resposta inválida de /api/knowledge/internal/context', {\n raw: parsed.error.flatten(),\n });\n }\n return parsed.data;\n }\n}\n","export class PortalApiError extends Error {\n status?: number;\n raw?: unknown;\n\n constructor(message: string, info?: Partial<PortalApiError>) {\n super(message);\n this.name = 'PortalApiError';\n\n if (info) {\n this.status = info.status;\n this.raw = info.raw;\n }\n }\n}\n"],"mappings":";AAAA,OAAO,WAA8B;;;ACE9B,IAAe,eAAf,MAA4B;AAAA,EACvB;AAAA,EAEV,YAAY,QAAuB;AACjC,SAAK,SAAS;AAAA,EAChB;AACF;;;ACLO,IAAM,kBAAN,cAA8B,aAAa;AAAA;AAAA,EAEhD,MAAM,oBAAoB,WAA2C;AACnE,UAAM,WAAW,MAAM,KAAK,OAAO,IAAyB,gBAAgB;AAAA,MAC1E,QAAQ,EAAE,iBAAiB,WAAW,gBAAgB,KAAK;AAAA,IAC7D,CAAC;AACD,WAAO,SAAS,KAAK,KAAK,CAAC,KAAK;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,qBAAqB,WAA2C;AACpE,UAAM,WAAW,MAAM,KAAK,OAAO,IAAyB,gBAAgB;AAAA,MAC1E,QAAQ,EAAE,WAAW,gBAAgB,KAAK;AAAA,IAC5C,CAAC;AACD,WAAO,SAAS,KAAK,KAAK,CAAC,KAAK;AAAA,EAClC;AACF;;;ACnBA,SAAS,SAAS;;;ACAX,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,MAAgC;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AAEZ,QAAI,MAAM;AACR,WAAK,SAAS,KAAK;AACnB,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EACF;AACF;;;ADRA,IAAM,6BAA6B,EAAE,OAAO;AAAA,EAC1C,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,SAAS,EAAE,OAAO;AAAA,EAClB,YAAY,EAAE,OAAO;AAAA,EACrB,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AACvD,CAAC;AAED,IAAM,8BAA8B,EAAE,MAAM,0BAA0B;AAE/D,IAAM,oBAAN,cAAgC,aAAa;AAAA;AAAA,EAElD,MAAM,mBAAmB,QAAkE;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,KAAc,mCAAmC;AAAA,MAClF,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,qBAAqB,OAAO;AAAA,IAC9B,CAAC;AAED,UAAM,SAAS,4BAA4B,UAAU,SAAS,IAAI;AAClE,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,eAAe,2DAAwD;AAAA,QAC/E,KAAK,OAAO,MAAM,QAAQ;AAAA,MAC5B,CAAC;AAAA,IACH;AACA,WAAO,OAAO;AAAA,EAChB;AACF;;;AHzBA,IAAM,mBAAmB;AAElB,IAAM,eAAN,MAAmB;AAAA,EACjB;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EAEP,YAAY,QAA4B;AACtC,SAAK,SAAS;AAEd,SAAK,aAAa,MAAM,OAAO;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa,OAAO;AAAA,QACpB,oBAAoB,OAAO;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,SAAK,WAAW,aAAa,SAAS;AAAA,MACpC,CAAC,aAAa;AAAA,MACd,CAAC,UAAU;AACT,YAAI,MAAM,UAAU;AAClB,gBAAM,UAAU,MAAM,SAAS;AAC/B,gBAAM,IAAI,eAAe,SAAS,SAAS,oBAAoB;AAAA,YAC7D,QAAQ,MAAM,SAAS;AAAA,YACvB,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAEA,SAAK,UAAU,IAAI,gBAAgB,KAAK,UAAU;AAClD,SAAK,YAAY,IAAI,kBAAkB,KAAK,UAAU;AAAA,EACxD;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/base.ts","../src/errors.ts","../src/resources/clients/index.ts","../src/resources/knowledge/index.ts","../src/resources/messaging/index.ts"],"sourcesContent":["import axios, { AxiosInstance } from 'axios';\nimport { PortalClientConfig } from './types';\nimport { ClientsResource } from './resources/clients';\nimport { KnowledgeResource } from './resources/knowledge';\nimport { MessagingResource } from './resources/messaging';\nimport { PortalApiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://portal.aiatende.com.br';\n\nexport class PortalClient {\n public config: PortalClientConfig;\n public httpClient: AxiosInstance;\n\n public clients: ClientsResource;\n public knowledge: KnowledgeResource;\n public messaging: MessagingResource;\n\n constructor(config: PortalClientConfig) {\n this.config = config;\n\n this.httpClient = axios.create({\n baseURL: config.baseUrl || DEFAULT_BASE_URL,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': config.apiKey,\n 'x-internal-token': config.apiKey\n }\n });\n\n this.httpClient.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response) {\n const errData = error.response.data;\n throw new PortalApiError(errData?.error || 'Portal API Error', {\n status: error.response.status,\n raw: errData\n });\n }\n throw error;\n }\n );\n\n this.clients = new ClientsResource(this.httpClient);\n this.knowledge = new KnowledgeResource(this.httpClient);\n this.messaging = new MessagingResource(this.httpClient);\n }\n}\n","import { AxiosInstance } from 'axios';\n\nexport abstract class BaseResource {\n protected client: AxiosInstance;\n\n constructor(client: AxiosInstance) {\n this.client = client;\n }\n}\n","export class PortalApiError extends Error {\n status?: number;\n raw?: unknown;\n\n constructor(message: string, info?: Partial<PortalApiError>) {\n super(message);\n this.name = 'PortalApiError';\n\n if (info) {\n this.status = info.status;\n this.raw = info.raw;\n }\n }\n}\n","import { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { Client, ClientsListResponse } from './types';\n\nexport class ClientsResource extends BaseResource {\n /** Busca o Client dono de um subdomínio Kommo (`GET /api/clients?kommo_subdomain=...`). */\n async getByKommoSubdomain(subdomain: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { kommo_subdomain: subdomain, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /** Busca o Client dono de um canal Wazzup (`GET /api/clients?channelId=...`). */\n async getByWazzupChannelId(channelId: string): Promise<Client | null> {\n const response = await this.client.get<ClientsListResponse>('/api/clients', {\n params: { channelId, includeConfigs: true }\n });\n return response.data.data[0] ?? null;\n }\n\n /**\n * Busca o Client pelo seu id direto (`GET /api/clients/{id}`) — inclui `aiConfig` e\n * `messagingConfig` (sem a API key criptografada). Usado pelo canal aiatendeWa, que resolve o\n * tenant a partir do `clientId` já incluído no payload do relay de webhook, sem precisar de\n * um identificador de canal indireto como Kommo subdomain ou Wazzup channelId.\n */\n async getById(clientId: string): Promise<Client | null> {\n try {\n const response = await this.client.get<Client>(`/api/clients/${clientId}`);\n return response.data;\n } catch (error) {\n if (error instanceof PortalApiError && error.status === 404) return null;\n throw error;\n }\n }\n}\n","import { z } from 'zod';\nimport { BaseResource } from '../../base';\nimport { PortalApiError } from '../../errors';\nimport { GetInternalContextParams, InternalContextResult } from './types';\n\nconst internalContextChunkSchema = z.object({\n chunkId: z.string().optional(),\n content: z.string(),\n similarity: z.number(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst internalContextResultSchema = z.array(internalContextChunkSchema);\n\nexport class KnowledgeResource extends BaseResource {\n /** Recupera trechos relevantes da base RAG do cliente pra uma pergunta em texto puro (`POST /api/knowledge/internal/context`). O Portal gera o embedding internamente usando a `apiKey` do AiConfig. */\n async getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult> {\n const response = await this.client.post<unknown>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n\n const parsed = internalContextResultSchema.safeParse(response.data);\n if (!parsed.success) {\n throw new PortalApiError('Resposta inválida de /api/knowledge/internal/context', {\n raw: parsed.error.flatten(),\n });\n }\n return parsed.data;\n }\n}\n","import { BaseResource } from '../../base';\nimport {\n ListConversationsFilters,\n MessagingConversation,\n MessagingConversationsListResponse,\n MessagingMessagesListResponse,\n PatchConversationPayload,\n SendMessagePayload,\n SendMessageResponse,\n UploadMediaPayload,\n UploadMediaResponse,\n} from './types';\n\n/**\n * Canal WhatsApp direto (aiatendeWa) — proxy pras rotas `/api/clients/{clientId}/messaging/...`\n * do Portal, que por sua vez falam com a plataforma de mensageria própria\n * (plataforma_whatsapp_coexistence). O atende360-assistant nunca fala direto com essa\n * plataforma — sempre por aqui, autenticado via a mesma `apiKey`/APP_API_KEY já usada pro\n * resto do SDK.\n */\nexport class MessagingResource extends BaseResource {\n async listConversations(\n clientId: string,\n filters?: ListConversationsFilters,\n ): Promise<MessagingConversationsListResponse> {\n const response = await this.client.get<MessagingConversationsListResponse>(\n `/api/clients/${clientId}/messaging/conversations`,\n { params: filters },\n );\n return response.data;\n }\n\n async getConversation(clientId: string, conversationId: string): Promise<MessagingConversation> {\n const response = await this.client.get<MessagingConversation>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}`,\n );\n return response.data;\n }\n\n async patchConversation(\n clientId: string,\n conversationId: string,\n payload: PatchConversationPayload,\n ): Promise<MessagingConversation> {\n const response = await this.client.patch<MessagingConversation>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}`,\n payload,\n );\n return response.data;\n }\n\n async getMessages(\n clientId: string,\n conversationId: string,\n params?: { page?: number; limit?: number },\n ): Promise<MessagingMessagesListResponse> {\n const response = await this.client.get<MessagingMessagesListResponse>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,\n { params },\n );\n return response.data;\n }\n\n async sendMessage(\n clientId: string,\n conversationId: string,\n payload: SendMessagePayload,\n ): Promise<SendMessageResponse> {\n const response = await this.client.post<SendMessageResponse>(\n `/api/clients/${clientId}/messaging/conversations/${conversationId}/messages`,\n payload,\n );\n return response.data;\n }\n\n /**\n * Upload de mídia (ex.: áudio gerado por TTS) — pré-requisito pra mandar `type: 'audio'` no\n * `sendMessage` (o `content.id` retornado aqui vira `content.id` do envio; `storagePath`\n * precisa ser ecoado em `content._media.storagePath` no send, senão a plataforma não cria o\n * registro de mídia local pra conexões Cloud API).\n */\n async uploadMedia(clientId: string, payload: UploadMediaPayload): Promise<UploadMediaResponse> {\n const form = new FormData();\n // Ordem importa: phoneNumberId/isVoiceNote precisam vir ANTES de file no multipart — o\n // parser do Portal (e o da plataforma por trás dele) lê em stream.\n form.append('phoneNumberId', payload.phoneNumberId);\n if (payload.isVoiceNote) form.append('isVoiceNote', 'true');\n form.append(\n 'file',\n new Blob([new Uint8Array(payload.file)], { type: payload.mimeType }),\n payload.filename,\n );\n\n const response = await this.client.post<UploadMediaResponse>(\n `/api/clients/${clientId}/messaging/media`,\n form,\n );\n return response.data;\n }\n}\n"],"mappings":"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","names":[]}
|