portal-aiatende-api 0.2.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 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,14 +294,32 @@ 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
 
306
+ /** Uma mensagem simplificada do histórico da conversa, enviada pro Portal reescrever a
307
+ * pergunta de acompanhamento como consulta autônoma antes de gerar o embedding. */
308
+ interface ConversationHistoryMessage {
309
+ role: 'user' | 'assistant' | 'system' | 'tool';
310
+ content: string;
311
+ }
291
312
  interface GetInternalContextParams {
292
313
  /** Id do AiConfig do cliente — o Portal usa a `apiKey` configurada nele pra gerar o embedding da pergunta. */
293
314
  aiConfigId: string;
294
315
  /** Pergunta em texto puro do usuário. O embedding é gerado internamente pelo Portal (RagService.getEmbedding), não pelo chamador. */
295
316
  question: string;
296
- conversationHistory?: unknown;
317
+ /** Últimas mensagens da conversa (mais antiga → mais recente), sem incluir `question`. Quando
318
+ * presente e não vazio, o Portal reescreve `question` como uma consulta autônoma (LLM rewrite)
319
+ * antes de buscar na base de conhecimento — melhora o recall em perguntas de acompanhamento
320
+ * (ex.: "e o azul?"). Omitir ou passar array vazio preserva o comportamento anterior (busca
321
+ * direto pela `question`, sem custo extra). */
322
+ conversationHistory?: ConversationHistoryMessage[];
297
323
  }
298
324
  interface InternalContextChunk {
299
325
  chunkId?: string;
@@ -310,11 +336,191 @@ declare class KnowledgeResource extends BaseResource {
310
336
  getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult>;
311
337
  }
312
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
+
313
518
  declare class PortalClient {
314
519
  config: PortalClientConfig;
315
520
  httpClient: AxiosInstance;
316
521
  clients: ClientsResource;
317
522
  knowledge: KnowledgeResource;
523
+ messaging: MessagingResource;
318
524
  constructor(config: PortalClientConfig);
319
525
  }
320
526
 
@@ -324,4 +530,4 @@ declare class PortalApiError extends Error {
324
530
  constructor(message: string, info?: Partial<PortalApiError>);
325
531
  }
326
532
 
327
- 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 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,14 +294,32 @@ 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
 
306
+ /** Uma mensagem simplificada do histórico da conversa, enviada pro Portal reescrever a
307
+ * pergunta de acompanhamento como consulta autônoma antes de gerar o embedding. */
308
+ interface ConversationHistoryMessage {
309
+ role: 'user' | 'assistant' | 'system' | 'tool';
310
+ content: string;
311
+ }
291
312
  interface GetInternalContextParams {
292
313
  /** Id do AiConfig do cliente — o Portal usa a `apiKey` configurada nele pra gerar o embedding da pergunta. */
293
314
  aiConfigId: string;
294
315
  /** Pergunta em texto puro do usuário. O embedding é gerado internamente pelo Portal (RagService.getEmbedding), não pelo chamador. */
295
316
  question: string;
296
- conversationHistory?: unknown;
317
+ /** Últimas mensagens da conversa (mais antiga → mais recente), sem incluir `question`. Quando
318
+ * presente e não vazio, o Portal reescreve `question` como uma consulta autônoma (LLM rewrite)
319
+ * antes de buscar na base de conhecimento — melhora o recall em perguntas de acompanhamento
320
+ * (ex.: "e o azul?"). Omitir ou passar array vazio preserva o comportamento anterior (busca
321
+ * direto pela `question`, sem custo extra). */
322
+ conversationHistory?: ConversationHistoryMessage[];
297
323
  }
298
324
  interface InternalContextChunk {
299
325
  chunkId?: string;
@@ -310,11 +336,191 @@ declare class KnowledgeResource extends BaseResource {
310
336
  getInternalContext(params: GetInternalContextParams): Promise<InternalContextResult>;
311
337
  }
312
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
+
313
518
  declare class PortalClient {
314
519
  config: PortalClientConfig;
315
520
  httpClient: AxiosInstance;
316
521
  clients: ClientsResource;
317
522
  knowledge: KnowledgeResource;
523
+ messaging: MessagingResource;
318
524
  constructor(config: PortalClientConfig);
319
525
  }
320
526
 
@@ -324,4 +530,4 @@ declare class PortalApiError extends Error {
324
530
  constructor(message: string, info?: Partial<PortalApiError>);
325
531
  }
326
532
 
327
- 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 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,9 +79,32 @@ var ClientsResource = class extends BaseResource {
64
79
  });
65
80
  return response.data.data[0] ?? null;
66
81
  }
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;
95
+ }
96
+ }
67
97
  };
68
98
 
69
99
  // src/resources/knowledge/index.ts
100
+ var import_zod = require("zod");
101
+ var internalContextChunkSchema = import_zod.z.object({
102
+ chunkId: import_zod.z.string().optional(),
103
+ content: import_zod.z.string(),
104
+ similarity: import_zod.z.number(),
105
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
106
+ });
107
+ var internalContextResultSchema = import_zod.z.array(internalContextChunkSchema);
70
108
  var KnowledgeResource = class extends BaseResource {
71
109
  /** 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. */
72
110
  async getInternalContext(params) {
@@ -75,21 +113,72 @@ var KnowledgeResource = class extends BaseResource {
75
113
  question: params.question,
76
114
  conversationHistory: params.conversationHistory
77
115
  });
78
- return response.data;
116
+ const parsed = internalContextResultSchema.safeParse(response.data);
117
+ if (!parsed.success) {
118
+ throw new PortalApiError("Resposta inv\xE1lida de /api/knowledge/internal/context", {
119
+ raw: parsed.error.flatten()
120
+ });
121
+ }
122
+ return parsed.data;
79
123
  }
80
124
  };
81
125
 
82
- // src/errors.ts
83
- var PortalApiError = class extends Error {
84
- status;
85
- raw;
86
- constructor(message, info) {
87
- super(message);
88
- this.name = "PortalApiError";
89
- if (info) {
90
- this.status = info.status;
91
- this.raw = info.raw;
92
- }
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;
93
182
  }
94
183
  };
95
184
 
@@ -100,6 +189,7 @@ var PortalClient = class {
100
189
  httpClient;
101
190
  clients;
102
191
  knowledge;
192
+ messaging;
103
193
  constructor(config) {
104
194
  this.config = config;
105
195
  this.httpClient = import_axios.default.create({
@@ -125,12 +215,14 @@ var PortalClient = class {
125
215
  );
126
216
  this.clients = new ClientsResource(this.httpClient);
127
217
  this.knowledge = new KnowledgeResource(this.httpClient);
218
+ this.messaging = new MessagingResource(this.httpClient);
128
219
  }
129
220
  };
130
221
  // Annotate the CommonJS export names for ESM import in node:
131
222
  0 && (module.exports = {
132
223
  ClientsResource,
133
224
  KnowledgeResource,
225
+ MessagingResource,
134
226
  PortalApiError,
135
227
  PortalClient
136
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 { BaseResource } from '../../base';\nimport { GetInternalContextParams, InternalContextResult } from './types';\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<InternalContextResult>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n return response.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;;;AChBO,IAAM,oBAAN,cAAgC,aAAa;AAAA;AAAA,EAElD,MAAM,mBAAmB,QAAkE;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,KAA4B,mCAAmC;AAAA,MAChG,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,qBAAqB,OAAO;AAAA,IAC9B,CAAC;AACD,WAAO,SAAS;AAAA,EAClB;AACF;;;ACbO,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;;;AJPA,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,9 +39,32 @@ var ClientsResource = class extends BaseResource {
25
39
  });
26
40
  return response.data.data[0] ?? null;
27
41
  }
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;
55
+ }
56
+ }
28
57
  };
29
58
 
30
59
  // src/resources/knowledge/index.ts
60
+ import { z } from "zod";
61
+ var internalContextChunkSchema = z.object({
62
+ chunkId: z.string().optional(),
63
+ content: z.string(),
64
+ similarity: z.number(),
65
+ metadata: z.record(z.string(), z.unknown()).optional()
66
+ });
67
+ var internalContextResultSchema = z.array(internalContextChunkSchema);
31
68
  var KnowledgeResource = class extends BaseResource {
32
69
  /** 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. */
33
70
  async getInternalContext(params) {
@@ -36,21 +73,72 @@ var KnowledgeResource = class extends BaseResource {
36
73
  question: params.question,
37
74
  conversationHistory: params.conversationHistory
38
75
  });
39
- return response.data;
76
+ const parsed = internalContextResultSchema.safeParse(response.data);
77
+ if (!parsed.success) {
78
+ throw new PortalApiError("Resposta inv\xE1lida de /api/knowledge/internal/context", {
79
+ raw: parsed.error.flatten()
80
+ });
81
+ }
82
+ return parsed.data;
40
83
  }
41
84
  };
42
85
 
43
- // src/errors.ts
44
- var PortalApiError = class extends Error {
45
- status;
46
- raw;
47
- constructor(message, info) {
48
- super(message);
49
- this.name = "PortalApiError";
50
- if (info) {
51
- this.status = info.status;
52
- this.raw = info.raw;
53
- }
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;
54
142
  }
55
143
  };
56
144
 
@@ -61,6 +149,7 @@ var PortalClient = class {
61
149
  httpClient;
62
150
  clients;
63
151
  knowledge;
152
+ messaging;
64
153
  constructor(config) {
65
154
  this.config = config;
66
155
  this.httpClient = axios.create({
@@ -86,11 +175,13 @@ var PortalClient = class {
86
175
  );
87
176
  this.clients = new ClientsResource(this.httpClient);
88
177
  this.knowledge = new KnowledgeResource(this.httpClient);
178
+ this.messaging = new MessagingResource(this.httpClient);
89
179
  }
90
180
  };
91
181
  export {
92
182
  ClientsResource,
93
183
  KnowledgeResource,
184
+ MessagingResource,
94
185
  PortalApiError,
95
186
  PortalClient
96
187
  };
@@ -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 { BaseResource } from '../../base';\nimport { GetInternalContextParams, InternalContextResult } from './types';\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<InternalContextResult>('/api/knowledge/internal/context', {\n aiConfigId: params.aiConfigId,\n question: params.question,\n conversationHistory: params.conversationHistory\n });\n return response.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;;;AChBO,IAAM,oBAAN,cAAgC,aAAa;AAAA;AAAA,EAElD,MAAM,mBAAmB,QAAkE;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,KAA4B,mCAAmC;AAAA,MAChG,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,qBAAqB,OAAO;AAAA,IAC9B,CAAC;AACD,WAAO,SAAS;AAAA,EAClB;AACF;;;ACbO,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;;;AJPA,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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portal-aiatende-api",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Cliente TypeScript tipado para a API interna do Portal AI Atende.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -23,7 +23,8 @@
23
23
  "license": "ISC",
24
24
  "type": "commonjs",
25
25
  "dependencies": {
26
- "axios": "^1.15.2"
26
+ "axios": "^1.15.2",
27
+ "zod": "^4.3.6"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/node": "^22.10.0",