@utilia-os/sdk-js 1.1.0 → 1.2.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
@@ -43,10 +43,22 @@ declare class UtiliaClient {
43
43
  * Ejecuta una funcion con reintentos y backoff exponencial
44
44
  */
45
45
  private executeWithRetry;
46
+ /**
47
+ * Genera un ID simple como fallback cuando crypto.randomUUID no esta disponible
48
+ */
49
+ private generateSimpleId;
46
50
  /**
47
51
  * Helper para esperar un tiempo
48
52
  */
49
53
  private sleep;
54
+ /**
55
+ * Construye una URL completa para conexiones SSE con la API key como query param.
56
+ * Util para EventSource que no soporta headers personalizados.
57
+ *
58
+ * @param path - Ruta relativa de la API (ej: /external/v1/tickets/ai-suggest/123/stream)
59
+ * @returns URL completa con apiKey como parametro de consulta
60
+ */
61
+ buildSseUrl(path: string): string;
50
62
  /**
51
63
  * Realiza una peticion GET
52
64
  */
@@ -672,6 +684,65 @@ interface GetSuggestionsOptions {
672
684
  /** Callback para reportar progreso */
673
685
  onProgress?: (progress: number) => void;
674
686
  }
687
+ /**
688
+ * Evento SSE recibido durante el streaming de un job
689
+ */
690
+ interface SseEvent {
691
+ /** Tipo de evento */
692
+ event: 'progress' | 'completed' | 'failed' | 'heartbeat';
693
+ /** Progreso actual (0-100) */
694
+ progress?: number;
695
+ /** Estado descriptivo */
696
+ status?: string;
697
+ /** Resultado del job (disponible en evento 'completed') */
698
+ result?: AiJobStatus['result'];
699
+ /** Mensaje de error (disponible en evento 'failed') */
700
+ error?: string;
701
+ }
702
+ /**
703
+ * Opciones para el metodo streamSuggestions
704
+ */
705
+ interface StreamSuggestionsOptions {
706
+ /** Callback para reportar progreso (0-100) */
707
+ onProgress?: (progress: number) => void;
708
+ /** Callback para errores no fatales */
709
+ onError?: (error: Error) => void;
710
+ /** Timeout en ms (default: 120000) */
711
+ timeout?: number;
712
+ }
713
+ /**
714
+ * Handle retornado por streamSuggestions para controlar la conexion SSE
715
+ */
716
+ interface StreamSuggestionsHandle {
717
+ /** Promesa que resuelve con el resultado cuando el job completa */
718
+ result: Promise<AiJobStatus['result']>;
719
+ /** Funcion para abortar la conexion SSE */
720
+ abort: () => void;
721
+ }
722
+ /**
723
+ * Estado del job de transcripcion asincrona
724
+ */
725
+ interface TranscriptionJobStatus {
726
+ /** Estado actual del job */
727
+ status: 'pending' | 'processing' | 'completed' | 'failed';
728
+ /** Progreso del job (0-100) */
729
+ progress?: number;
730
+ /** Texto transcrito (disponible cuando status es 'completed') */
731
+ transcription?: string;
732
+ /** Mensaje de error (si status es 'failed') */
733
+ error?: string;
734
+ }
735
+ /**
736
+ * Opciones para el metodo streamTranscription
737
+ */
738
+ interface StreamTranscriptionOptions {
739
+ /** Callback para reportar progreso (0-100) */
740
+ onProgress?: (progress: number) => void;
741
+ /** Callback para errores no fatales */
742
+ onError?: (error: Error) => void;
743
+ /** Timeout en ms (default: 120000) */
744
+ timeout?: number;
745
+ }
675
746
  declare class AiService {
676
747
  private readonly client;
677
748
  private readonly basePath;
@@ -794,6 +865,122 @@ declare class AiService {
794
865
  trackAiAction(data: TrackAiActionInput): Promise<{
795
866
  success: boolean;
796
867
  }>;
868
+ /**
869
+ * Solicita sugerencias de IA y recibe el resultado via SSE (Server-Sent Events).
870
+ * Alternativa al polling que ofrece actualizaciones en tiempo real.
871
+ *
872
+ * Requiere un entorno con EventSource nativo (navegador).
873
+ * Para Node.js se necesita un polyfill como 'eventsource'.
874
+ *
875
+ * @param data - Datos para generar sugerencias
876
+ * @param options - Opciones de streaming
877
+ * @returns Handle con promesa de resultado y funcion de aborto
878
+ *
879
+ * @example
880
+ * ```typescript
881
+ * const handle = await sdk.ai.streamSuggestions(
882
+ * { userId: 'user-123', text: 'No puedo iniciar sesion' },
883
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
884
+ * );
885
+ *
886
+ * // Esperar resultado
887
+ * const result = await handle.result;
888
+ * console.log(result?.suggestions);
889
+ *
890
+ * // O abortar si el usuario cancela
891
+ * handle.abort();
892
+ * ```
893
+ */
894
+ streamSuggestions(data: AiSuggestInput, options?: StreamSuggestionsOptions): Promise<StreamSuggestionsHandle>;
895
+ /**
896
+ * Solicita una transcripcion de audio de forma asincrona.
897
+ * Retorna un jobId para consultar el estado o usar con streamTranscription.
898
+ *
899
+ * @param audioBase64 - Audio codificado en base64
900
+ * @param audioFilename - Nombre del archivo con extension
901
+ * @returns ID del job para consultar estado
902
+ *
903
+ * @example
904
+ * ```typescript
905
+ * const { jobId } = await sdk.ai.requestTranscription(
906
+ * audioBase64,
907
+ * 'recording.webm',
908
+ * );
909
+ * ```
910
+ */
911
+ requestTranscription(audioBase64: string, audioFilename: string): Promise<{
912
+ jobId: string;
913
+ }>;
914
+ /**
915
+ * Obtiene el estado de un job de transcripcion asincrona.
916
+ *
917
+ * @param jobId - ID del job obtenido de requestTranscription
918
+ * @returns Estado actual del job de transcripcion
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * const status = await sdk.ai.getTranscriptionStatus(jobId);
923
+ *
924
+ * if (status.status === 'completed') {
925
+ * console.log(status.transcription);
926
+ * }
927
+ * ```
928
+ */
929
+ getTranscriptionStatus(jobId: string): Promise<TranscriptionJobStatus>;
930
+ /**
931
+ * Solicita una transcripcion de audio y recibe el resultado via SSE.
932
+ * Alternativa al polling para transcripciones asincronas.
933
+ *
934
+ * Requiere un entorno con EventSource nativo (navegador).
935
+ * Para Node.js se necesita un polyfill como 'eventsource'.
936
+ *
937
+ * @param audioBase64 - Audio codificado en base64
938
+ * @param audioFilename - Nombre del archivo con extension
939
+ * @param options - Opciones de streaming
940
+ * @returns Promesa que resuelve con el texto transcrito
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * const { transcription } = await sdk.ai.streamTranscription(
945
+ * audioBase64,
946
+ * 'recording.webm',
947
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
948
+ * );
949
+ * console.log(transcription);
950
+ * ```
951
+ */
952
+ streamTranscription(audioBase64: string, audioFilename: string, options?: StreamTranscriptionOptions): Promise<{
953
+ transcription: string;
954
+ }>;
955
+ /**
956
+ * Reporta un error del cliente al servidor para monitoreo.
957
+ * Este metodo nunca lanza excepciones; los errores de reporte se ignoran silenciosamente.
958
+ *
959
+ * @param params - Datos del error a reportar
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * await sdk.ai.reportError({
964
+ * message: 'No se pudo cargar el widget',
965
+ * code: 'WIDGET_LOAD_ERROR',
966
+ * component: 'widget',
967
+ * url: window.location.href,
968
+ * userAgent: navigator.userAgent,
969
+ * });
970
+ * ```
971
+ */
972
+ reportError(params: {
973
+ message: string;
974
+ stack?: string;
975
+ code?: string;
976
+ component?: string;
977
+ context?: string;
978
+ endpoint?: string;
979
+ method?: string;
980
+ requestId?: string;
981
+ url?: string;
982
+ userAgent?: string;
983
+ }): Promise<void>;
797
984
  /**
798
985
  * Helper para esperar un tiempo
799
986
  */
@@ -828,7 +1015,14 @@ declare class UtiliaSDKError extends Error {
828
1015
  readonly code: ErrorCode;
829
1016
  /** Timestamp de cuando ocurrio el error */
830
1017
  readonly timestamp: Date;
831
- constructor(code: ErrorCode, message: string);
1018
+ /** ID de la peticion que genero el error */
1019
+ readonly requestId?: string;
1020
+ /** Codigo de estado HTTP de la respuesta */
1021
+ readonly statusCode?: number;
1022
+ constructor(code: ErrorCode, message: string, options?: {
1023
+ requestId?: string;
1024
+ statusCode?: number;
1025
+ });
832
1026
  /**
833
1027
  * Serializa el error a un objeto JSON
834
1028
  */
@@ -837,6 +1031,8 @@ declare class UtiliaSDKError extends Error {
837
1031
  code: ErrorCode;
838
1032
  message: string;
839
1033
  timestamp: string;
1034
+ requestId: string | undefined;
1035
+ statusCode: number | undefined;
840
1036
  };
841
1037
  /**
842
1038
  * Verifica si el error es de tipo rate limit
@@ -943,4 +1139,4 @@ declare class UtiliaSDK {
943
1139
  constructor(config: UtiliaSDKConfig);
944
1140
  }
945
1141
 
946
- export { type AddMessageInput, type AiJobStatus, type AiSuggestInput, type AiSuggestions, type AiUserAction, type CreateTicketInput, type CreateTicketUser, type CreatedMessage, type CreatedTicket, ErrorCode, type ExternalUser, type FileQuota, type GetSuggestionsOptions, type IdentifyUserInput, type MessageAuthor, type PaginatedResponse, type PaginationMeta, type RequestConfig, SDK_LIMITS, type TicketAttachment, type TicketCategory, type TicketContext, type TicketDetail, type TicketFilters, type TicketListItem, type TicketMessage, type TicketPriority, type TicketReporter, type TicketStatus, type TrackAiActionInput, type UnreadCount, type UploadFileOptions, type UploadedFile, UtiliaSDK, type UtiliaSDKConfig, UtiliaSDKError };
1142
+ export { type AddMessageInput, type AiJobStatus, type AiSuggestInput, type AiSuggestions, type AiUserAction, type CreateTicketInput, type CreateTicketUser, type CreatedMessage, type CreatedTicket, ErrorCode, type ExternalUser, type FileQuota, type GetSuggestionsOptions, type IdentifyUserInput, type MessageAuthor, type PaginatedResponse, type PaginationMeta, type RequestConfig, SDK_LIMITS, type SseEvent, type StreamSuggestionsHandle, type StreamSuggestionsOptions, type StreamTranscriptionOptions, type TicketAttachment, type TicketCategory, type TicketContext, type TicketDetail, type TicketFilters, type TicketListItem, type TicketMessage, type TicketPriority, type TicketReporter, type TicketStatus, type TrackAiActionInput, type TranscriptionJobStatus, type UnreadCount, type UploadFileOptions, type UploadedFile, UtiliaSDK, type UtiliaSDKConfig, UtiliaSDKError };
package/dist/index.d.ts CHANGED
@@ -43,10 +43,22 @@ declare class UtiliaClient {
43
43
  * Ejecuta una funcion con reintentos y backoff exponencial
44
44
  */
45
45
  private executeWithRetry;
46
+ /**
47
+ * Genera un ID simple como fallback cuando crypto.randomUUID no esta disponible
48
+ */
49
+ private generateSimpleId;
46
50
  /**
47
51
  * Helper para esperar un tiempo
48
52
  */
49
53
  private sleep;
54
+ /**
55
+ * Construye una URL completa para conexiones SSE con la API key como query param.
56
+ * Util para EventSource que no soporta headers personalizados.
57
+ *
58
+ * @param path - Ruta relativa de la API (ej: /external/v1/tickets/ai-suggest/123/stream)
59
+ * @returns URL completa con apiKey como parametro de consulta
60
+ */
61
+ buildSseUrl(path: string): string;
50
62
  /**
51
63
  * Realiza una peticion GET
52
64
  */
@@ -672,6 +684,65 @@ interface GetSuggestionsOptions {
672
684
  /** Callback para reportar progreso */
673
685
  onProgress?: (progress: number) => void;
674
686
  }
687
+ /**
688
+ * Evento SSE recibido durante el streaming de un job
689
+ */
690
+ interface SseEvent {
691
+ /** Tipo de evento */
692
+ event: 'progress' | 'completed' | 'failed' | 'heartbeat';
693
+ /** Progreso actual (0-100) */
694
+ progress?: number;
695
+ /** Estado descriptivo */
696
+ status?: string;
697
+ /** Resultado del job (disponible en evento 'completed') */
698
+ result?: AiJobStatus['result'];
699
+ /** Mensaje de error (disponible en evento 'failed') */
700
+ error?: string;
701
+ }
702
+ /**
703
+ * Opciones para el metodo streamSuggestions
704
+ */
705
+ interface StreamSuggestionsOptions {
706
+ /** Callback para reportar progreso (0-100) */
707
+ onProgress?: (progress: number) => void;
708
+ /** Callback para errores no fatales */
709
+ onError?: (error: Error) => void;
710
+ /** Timeout en ms (default: 120000) */
711
+ timeout?: number;
712
+ }
713
+ /**
714
+ * Handle retornado por streamSuggestions para controlar la conexion SSE
715
+ */
716
+ interface StreamSuggestionsHandle {
717
+ /** Promesa que resuelve con el resultado cuando el job completa */
718
+ result: Promise<AiJobStatus['result']>;
719
+ /** Funcion para abortar la conexion SSE */
720
+ abort: () => void;
721
+ }
722
+ /**
723
+ * Estado del job de transcripcion asincrona
724
+ */
725
+ interface TranscriptionJobStatus {
726
+ /** Estado actual del job */
727
+ status: 'pending' | 'processing' | 'completed' | 'failed';
728
+ /** Progreso del job (0-100) */
729
+ progress?: number;
730
+ /** Texto transcrito (disponible cuando status es 'completed') */
731
+ transcription?: string;
732
+ /** Mensaje de error (si status es 'failed') */
733
+ error?: string;
734
+ }
735
+ /**
736
+ * Opciones para el metodo streamTranscription
737
+ */
738
+ interface StreamTranscriptionOptions {
739
+ /** Callback para reportar progreso (0-100) */
740
+ onProgress?: (progress: number) => void;
741
+ /** Callback para errores no fatales */
742
+ onError?: (error: Error) => void;
743
+ /** Timeout en ms (default: 120000) */
744
+ timeout?: number;
745
+ }
675
746
  declare class AiService {
676
747
  private readonly client;
677
748
  private readonly basePath;
@@ -794,6 +865,122 @@ declare class AiService {
794
865
  trackAiAction(data: TrackAiActionInput): Promise<{
795
866
  success: boolean;
796
867
  }>;
868
+ /**
869
+ * Solicita sugerencias de IA y recibe el resultado via SSE (Server-Sent Events).
870
+ * Alternativa al polling que ofrece actualizaciones en tiempo real.
871
+ *
872
+ * Requiere un entorno con EventSource nativo (navegador).
873
+ * Para Node.js se necesita un polyfill como 'eventsource'.
874
+ *
875
+ * @param data - Datos para generar sugerencias
876
+ * @param options - Opciones de streaming
877
+ * @returns Handle con promesa de resultado y funcion de aborto
878
+ *
879
+ * @example
880
+ * ```typescript
881
+ * const handle = await sdk.ai.streamSuggestions(
882
+ * { userId: 'user-123', text: 'No puedo iniciar sesion' },
883
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
884
+ * );
885
+ *
886
+ * // Esperar resultado
887
+ * const result = await handle.result;
888
+ * console.log(result?.suggestions);
889
+ *
890
+ * // O abortar si el usuario cancela
891
+ * handle.abort();
892
+ * ```
893
+ */
894
+ streamSuggestions(data: AiSuggestInput, options?: StreamSuggestionsOptions): Promise<StreamSuggestionsHandle>;
895
+ /**
896
+ * Solicita una transcripcion de audio de forma asincrona.
897
+ * Retorna un jobId para consultar el estado o usar con streamTranscription.
898
+ *
899
+ * @param audioBase64 - Audio codificado en base64
900
+ * @param audioFilename - Nombre del archivo con extension
901
+ * @returns ID del job para consultar estado
902
+ *
903
+ * @example
904
+ * ```typescript
905
+ * const { jobId } = await sdk.ai.requestTranscription(
906
+ * audioBase64,
907
+ * 'recording.webm',
908
+ * );
909
+ * ```
910
+ */
911
+ requestTranscription(audioBase64: string, audioFilename: string): Promise<{
912
+ jobId: string;
913
+ }>;
914
+ /**
915
+ * Obtiene el estado de un job de transcripcion asincrona.
916
+ *
917
+ * @param jobId - ID del job obtenido de requestTranscription
918
+ * @returns Estado actual del job de transcripcion
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * const status = await sdk.ai.getTranscriptionStatus(jobId);
923
+ *
924
+ * if (status.status === 'completed') {
925
+ * console.log(status.transcription);
926
+ * }
927
+ * ```
928
+ */
929
+ getTranscriptionStatus(jobId: string): Promise<TranscriptionJobStatus>;
930
+ /**
931
+ * Solicita una transcripcion de audio y recibe el resultado via SSE.
932
+ * Alternativa al polling para transcripciones asincronas.
933
+ *
934
+ * Requiere un entorno con EventSource nativo (navegador).
935
+ * Para Node.js se necesita un polyfill como 'eventsource'.
936
+ *
937
+ * @param audioBase64 - Audio codificado en base64
938
+ * @param audioFilename - Nombre del archivo con extension
939
+ * @param options - Opciones de streaming
940
+ * @returns Promesa que resuelve con el texto transcrito
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * const { transcription } = await sdk.ai.streamTranscription(
945
+ * audioBase64,
946
+ * 'recording.webm',
947
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
948
+ * );
949
+ * console.log(transcription);
950
+ * ```
951
+ */
952
+ streamTranscription(audioBase64: string, audioFilename: string, options?: StreamTranscriptionOptions): Promise<{
953
+ transcription: string;
954
+ }>;
955
+ /**
956
+ * Reporta un error del cliente al servidor para monitoreo.
957
+ * Este metodo nunca lanza excepciones; los errores de reporte se ignoran silenciosamente.
958
+ *
959
+ * @param params - Datos del error a reportar
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * await sdk.ai.reportError({
964
+ * message: 'No se pudo cargar el widget',
965
+ * code: 'WIDGET_LOAD_ERROR',
966
+ * component: 'widget',
967
+ * url: window.location.href,
968
+ * userAgent: navigator.userAgent,
969
+ * });
970
+ * ```
971
+ */
972
+ reportError(params: {
973
+ message: string;
974
+ stack?: string;
975
+ code?: string;
976
+ component?: string;
977
+ context?: string;
978
+ endpoint?: string;
979
+ method?: string;
980
+ requestId?: string;
981
+ url?: string;
982
+ userAgent?: string;
983
+ }): Promise<void>;
797
984
  /**
798
985
  * Helper para esperar un tiempo
799
986
  */
@@ -828,7 +1015,14 @@ declare class UtiliaSDKError extends Error {
828
1015
  readonly code: ErrorCode;
829
1016
  /** Timestamp de cuando ocurrio el error */
830
1017
  readonly timestamp: Date;
831
- constructor(code: ErrorCode, message: string);
1018
+ /** ID de la peticion que genero el error */
1019
+ readonly requestId?: string;
1020
+ /** Codigo de estado HTTP de la respuesta */
1021
+ readonly statusCode?: number;
1022
+ constructor(code: ErrorCode, message: string, options?: {
1023
+ requestId?: string;
1024
+ statusCode?: number;
1025
+ });
832
1026
  /**
833
1027
  * Serializa el error a un objeto JSON
834
1028
  */
@@ -837,6 +1031,8 @@ declare class UtiliaSDKError extends Error {
837
1031
  code: ErrorCode;
838
1032
  message: string;
839
1033
  timestamp: string;
1034
+ requestId: string | undefined;
1035
+ statusCode: number | undefined;
840
1036
  };
841
1037
  /**
842
1038
  * Verifica si el error es de tipo rate limit
@@ -943,4 +1139,4 @@ declare class UtiliaSDK {
943
1139
  constructor(config: UtiliaSDKConfig);
944
1140
  }
945
1141
 
946
- export { type AddMessageInput, type AiJobStatus, type AiSuggestInput, type AiSuggestions, type AiUserAction, type CreateTicketInput, type CreateTicketUser, type CreatedMessage, type CreatedTicket, ErrorCode, type ExternalUser, type FileQuota, type GetSuggestionsOptions, type IdentifyUserInput, type MessageAuthor, type PaginatedResponse, type PaginationMeta, type RequestConfig, SDK_LIMITS, type TicketAttachment, type TicketCategory, type TicketContext, type TicketDetail, type TicketFilters, type TicketListItem, type TicketMessage, type TicketPriority, type TicketReporter, type TicketStatus, type TrackAiActionInput, type UnreadCount, type UploadFileOptions, type UploadedFile, UtiliaSDK, type UtiliaSDKConfig, UtiliaSDKError };
1142
+ export { type AddMessageInput, type AiJobStatus, type AiSuggestInput, type AiSuggestions, type AiUserAction, type CreateTicketInput, type CreateTicketUser, type CreatedMessage, type CreatedTicket, ErrorCode, type ExternalUser, type FileQuota, type GetSuggestionsOptions, type IdentifyUserInput, type MessageAuthor, type PaginatedResponse, type PaginationMeta, type RequestConfig, SDK_LIMITS, type SseEvent, type StreamSuggestionsHandle, type StreamSuggestionsOptions, type StreamTranscriptionOptions, type TicketAttachment, type TicketCategory, type TicketContext, type TicketDetail, type TicketFilters, type TicketListItem, type TicketMessage, type TicketPriority, type TicketReporter, type TicketStatus, type TrackAiActionInput, type TranscriptionJobStatus, type UnreadCount, type UploadFileOptions, type UploadedFile, UtiliaSDK, type UtiliaSDKConfig, UtiliaSDKError };
package/dist/index.js CHANGED
@@ -52,11 +52,13 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
52
52
  return ErrorCode2;
53
53
  })(ErrorCode || {});
54
54
  var UtiliaSDKError = class _UtiliaSDKError extends Error {
55
- constructor(code, message) {
55
+ constructor(code, message, options) {
56
56
  super(message);
57
57
  this.name = "UtiliaSDKError";
58
58
  this.code = code;
59
59
  this.timestamp = /* @__PURE__ */ new Date();
60
+ this.requestId = options?.requestId;
61
+ this.statusCode = options?.statusCode;
60
62
  if (Error.captureStackTrace) {
61
63
  Error.captureStackTrace(this, _UtiliaSDKError);
62
64
  }
@@ -69,7 +71,9 @@ var UtiliaSDKError = class _UtiliaSDKError extends Error {
69
71
  name: this.name,
70
72
  code: this.code,
71
73
  message: this.message,
72
- timestamp: this.timestamp.toISOString()
74
+ timestamp: this.timestamp.toISOString(),
75
+ requestId: this.requestId,
76
+ statusCode: this.statusCode
73
77
  };
74
78
  }
75
79
  /**
@@ -109,6 +113,12 @@ var UtiliaClient = class {
109
113
  "X-Api-Key": this.config.apiKey
110
114
  }
111
115
  });
116
+ this.axios.interceptors.request.use((config2) => {
117
+ const requestId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : this.generateSimpleId();
118
+ config2.headers["x-request-id"] = requestId;
119
+ config2._requestId = requestId;
120
+ return config2;
121
+ });
112
122
  this.setupInterceptors();
113
123
  }
114
124
  /**
@@ -134,40 +144,43 @@ var UtiliaClient = class {
134
144
  * Convierte un error de Axios en un UtiliaSDKError
135
145
  */
136
146
  toSDKError(error) {
147
+ const requestId = error.response?.headers?.["x-request-id"] || error.config?._requestId;
148
+ const statusCode = error.response?.status;
149
+ const errorOptions = { requestId, statusCode };
137
150
  if (!error.response) {
138
151
  if (error.code === "ECONNABORTED") {
139
- return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Timeout: la solicitud excedio el tiempo limite");
152
+ return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Timeout: la solicitud excedio el tiempo limite", errorOptions);
140
153
  }
141
- return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Error de conexion: no se pudo conectar con el servidor");
154
+ return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Error de conexion: no se pudo conectar con el servidor", errorOptions);
142
155
  }
143
156
  const status = error.response.status;
144
157
  const data = error.response.data;
145
158
  const message = data?.message || data?.error || error.message;
146
159
  switch (status) {
147
160
  case 401:
148
- return new UtiliaSDKError("UNAUTHORIZED" /* UNAUTHORIZED */, message || "API Key invalida o faltante");
161
+ return new UtiliaSDKError("UNAUTHORIZED" /* UNAUTHORIZED */, message || "API Key invalida o faltante", errorOptions);
149
162
  case 403:
150
163
  if (message?.toLowerCase().includes("rate limit")) {
151
- return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limit excedido. Intenta de nuevo mas tarde.");
164
+ return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limit excedido. Intenta de nuevo mas tarde.", errorOptions);
152
165
  }
153
166
  if (message?.toLowerCase().includes("origen")) {
154
- return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, "Origen no permitido por CORS");
167
+ return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, "Origen no permitido por CORS", errorOptions);
155
168
  }
156
- return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, message || "Acceso denegado");
169
+ return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, message || "Acceso denegado", errorOptions);
157
170
  case 404:
158
- return new UtiliaSDKError("NOT_FOUND" /* NOT_FOUND */, message || "Recurso no encontrado");
171
+ return new UtiliaSDKError("NOT_FOUND" /* NOT_FOUND */, message || "Recurso no encontrado", errorOptions);
159
172
  case 400:
160
173
  case 422:
161
- return new UtiliaSDKError("VALIDATION_ERROR" /* VALIDATION_ERROR */, message || "Error de validacion en los datos enviados");
174
+ return new UtiliaSDKError("VALIDATION_ERROR" /* VALIDATION_ERROR */, message || "Error de validacion en los datos enviados", errorOptions);
162
175
  case 429:
163
- return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, message || "Rate limit excedido. Intenta de nuevo mas tarde.");
176
+ return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, message || "Rate limit excedido. Intenta de nuevo mas tarde.", errorOptions);
164
177
  case 500:
165
178
  case 502:
166
179
  case 503:
167
180
  case 504:
168
- return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || "Error interno del servidor");
181
+ return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || "Error interno del servidor", errorOptions);
169
182
  default:
170
- return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || `Error desconocido (HTTP ${status})`);
183
+ return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || `Error desconocido (HTTP ${status})`, errorOptions);
171
184
  }
172
185
  }
173
186
  /**
@@ -214,12 +227,30 @@ var UtiliaClient = class {
214
227
  }
215
228
  throw lastError;
216
229
  }
230
+ /**
231
+ * Genera un ID simple como fallback cuando crypto.randomUUID no esta disponible
232
+ */
233
+ generateSimpleId() {
234
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
235
+ }
217
236
  /**
218
237
  * Helper para esperar un tiempo
219
238
  */
220
239
  sleep(ms) {
221
240
  return new Promise((resolve) => setTimeout(resolve, ms));
222
241
  }
242
+ /**
243
+ * Construye una URL completa para conexiones SSE con la API key como query param.
244
+ * Util para EventSource que no soporta headers personalizados.
245
+ *
246
+ * @param path - Ruta relativa de la API (ej: /external/v1/tickets/ai-suggest/123/stream)
247
+ * @returns URL completa con apiKey como parametro de consulta
248
+ */
249
+ buildSseUrl(path) {
250
+ const base = this.config.baseURL.replace(/\/$/, "");
251
+ const separator = path.includes("?") ? "&" : "?";
252
+ return `${base}${path}${separator}apiKey=${this.config.apiKey}`;
253
+ }
223
254
  /**
224
255
  * Realiza una peticion GET
225
256
  */
@@ -792,6 +823,237 @@ var AiService = class {
792
823
  async trackAiAction(data) {
793
824
  return this.client.post(`${this.basePath}/ai-track-action`, data);
794
825
  }
826
+ /**
827
+ * Solicita sugerencias de IA y recibe el resultado via SSE (Server-Sent Events).
828
+ * Alternativa al polling que ofrece actualizaciones en tiempo real.
829
+ *
830
+ * Requiere un entorno con EventSource nativo (navegador).
831
+ * Para Node.js se necesita un polyfill como 'eventsource'.
832
+ *
833
+ * @param data - Datos para generar sugerencias
834
+ * @param options - Opciones de streaming
835
+ * @returns Handle con promesa de resultado y funcion de aborto
836
+ *
837
+ * @example
838
+ * ```typescript
839
+ * const handle = await sdk.ai.streamSuggestions(
840
+ * { userId: 'user-123', text: 'No puedo iniciar sesion' },
841
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
842
+ * );
843
+ *
844
+ * // Esperar resultado
845
+ * const result = await handle.result;
846
+ * console.log(result?.suggestions);
847
+ *
848
+ * // O abortar si el usuario cancela
849
+ * handle.abort();
850
+ * ```
851
+ */
852
+ async streamSuggestions(data, options = {}) {
853
+ const { onProgress, onError, timeout = 12e4 } = options;
854
+ if (typeof EventSource === "undefined") {
855
+ throw new Error(
856
+ 'EventSource no esta disponible en este entorno. Para Node.js, instala un polyfill como "eventsource" y asignalo a globalThis.EventSource.'
857
+ );
858
+ }
859
+ const { jobId } = await this.requestSuggestions(data);
860
+ const sseUrl = this.client.buildSseUrl(`${this.basePath}/ai-suggest/${jobId}/stream`);
861
+ let eventSource;
862
+ let timeoutId;
863
+ const result = new Promise((resolve, reject) => {
864
+ eventSource = new EventSource(sseUrl);
865
+ timeoutId = setTimeout(() => {
866
+ eventSource.close();
867
+ reject(new Error("Timeout: la conexion SSE excedio el tiempo limite"));
868
+ }, timeout);
869
+ eventSource.onmessage = (event) => {
870
+ try {
871
+ const data2 = JSON.parse(event.data);
872
+ switch (data2.event) {
873
+ case "progress":
874
+ if (onProgress && data2.progress !== void 0) {
875
+ onProgress(data2.progress);
876
+ }
877
+ break;
878
+ case "completed":
879
+ clearTimeout(timeoutId);
880
+ eventSource.close();
881
+ resolve(data2.result);
882
+ break;
883
+ case "failed":
884
+ clearTimeout(timeoutId);
885
+ eventSource.close();
886
+ reject(new Error(data2.error || "Error al generar sugerencias"));
887
+ break;
888
+ case "heartbeat":
889
+ break;
890
+ }
891
+ } catch (parseError) {
892
+ if (onError) {
893
+ onError(new Error("Error al parsear evento SSE"));
894
+ }
895
+ }
896
+ };
897
+ eventSource.onerror = () => {
898
+ clearTimeout(timeoutId);
899
+ eventSource.close();
900
+ reject(new Error("Error en la conexion SSE"));
901
+ };
902
+ });
903
+ const abort = () => {
904
+ clearTimeout(timeoutId);
905
+ eventSource?.close();
906
+ };
907
+ return { result, abort };
908
+ }
909
+ /**
910
+ * Solicita una transcripcion de audio de forma asincrona.
911
+ * Retorna un jobId para consultar el estado o usar con streamTranscription.
912
+ *
913
+ * @param audioBase64 - Audio codificado en base64
914
+ * @param audioFilename - Nombre del archivo con extension
915
+ * @returns ID del job para consultar estado
916
+ *
917
+ * @example
918
+ * ```typescript
919
+ * const { jobId } = await sdk.ai.requestTranscription(
920
+ * audioBase64,
921
+ * 'recording.webm',
922
+ * );
923
+ * ```
924
+ */
925
+ async requestTranscription(audioBase64, audioFilename) {
926
+ return this.client.post(`${this.basePath}/ai-transcribe-async`, {
927
+ audioBase64,
928
+ audioFilename
929
+ });
930
+ }
931
+ /**
932
+ * Obtiene el estado de un job de transcripcion asincrona.
933
+ *
934
+ * @param jobId - ID del job obtenido de requestTranscription
935
+ * @returns Estado actual del job de transcripcion
936
+ *
937
+ * @example
938
+ * ```typescript
939
+ * const status = await sdk.ai.getTranscriptionStatus(jobId);
940
+ *
941
+ * if (status.status === 'completed') {
942
+ * console.log(status.transcription);
943
+ * }
944
+ * ```
945
+ */
946
+ async getTranscriptionStatus(jobId) {
947
+ return this.client.get(`${this.basePath}/ai-transcribe/${jobId}`);
948
+ }
949
+ /**
950
+ * Solicita una transcripcion de audio y recibe el resultado via SSE.
951
+ * Alternativa al polling para transcripciones asincronas.
952
+ *
953
+ * Requiere un entorno con EventSource nativo (navegador).
954
+ * Para Node.js se necesita un polyfill como 'eventsource'.
955
+ *
956
+ * @param audioBase64 - Audio codificado en base64
957
+ * @param audioFilename - Nombre del archivo con extension
958
+ * @param options - Opciones de streaming
959
+ * @returns Promesa que resuelve con el texto transcrito
960
+ *
961
+ * @example
962
+ * ```typescript
963
+ * const { transcription } = await sdk.ai.streamTranscription(
964
+ * audioBase64,
965
+ * 'recording.webm',
966
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
967
+ * );
968
+ * console.log(transcription);
969
+ * ```
970
+ */
971
+ async streamTranscription(audioBase64, audioFilename, options = {}) {
972
+ const { onProgress, onError, timeout = 12e4 } = options;
973
+ if (typeof EventSource === "undefined") {
974
+ throw new Error(
975
+ 'EventSource no esta disponible en este entorno. Para Node.js, instala un polyfill como "eventsource" y asignalo a globalThis.EventSource.'
976
+ );
977
+ }
978
+ const { jobId } = await this.requestTranscription(audioBase64, audioFilename);
979
+ const sseUrl = this.client.buildSseUrl(`${this.basePath}/ai-transcribe/${jobId}/stream`);
980
+ return new Promise((resolve, reject) => {
981
+ const eventSource = new EventSource(sseUrl);
982
+ const timeoutId = setTimeout(() => {
983
+ eventSource.close();
984
+ reject(new Error("Timeout: la conexion SSE excedio el tiempo limite"));
985
+ }, timeout);
986
+ eventSource.onmessage = (event) => {
987
+ try {
988
+ const data = JSON.parse(event.data);
989
+ switch (data.event) {
990
+ case "progress":
991
+ if (onProgress && data.progress !== void 0) {
992
+ onProgress(data.progress);
993
+ }
994
+ break;
995
+ case "completed":
996
+ clearTimeout(timeoutId);
997
+ eventSource.close();
998
+ resolve({ transcription: data.result?.transcription || "" });
999
+ break;
1000
+ case "failed":
1001
+ clearTimeout(timeoutId);
1002
+ eventSource.close();
1003
+ reject(new Error(data.error || "Error en la transcripcion"));
1004
+ break;
1005
+ case "heartbeat":
1006
+ break;
1007
+ }
1008
+ } catch (parseError) {
1009
+ if (onError) {
1010
+ onError(new Error("Error al parsear evento SSE"));
1011
+ }
1012
+ }
1013
+ };
1014
+ eventSource.onerror = () => {
1015
+ clearTimeout(timeoutId);
1016
+ eventSource.close();
1017
+ reject(new Error("Error en la conexion SSE"));
1018
+ };
1019
+ });
1020
+ }
1021
+ /**
1022
+ * Reporta un error del cliente al servidor para monitoreo.
1023
+ * Este metodo nunca lanza excepciones; los errores de reporte se ignoran silenciosamente.
1024
+ *
1025
+ * @param params - Datos del error a reportar
1026
+ *
1027
+ * @example
1028
+ * ```typescript
1029
+ * await sdk.ai.reportError({
1030
+ * message: 'No se pudo cargar el widget',
1031
+ * code: 'WIDGET_LOAD_ERROR',
1032
+ * component: 'widget',
1033
+ * url: window.location.href,
1034
+ * userAgent: navigator.userAgent,
1035
+ * });
1036
+ * ```
1037
+ */
1038
+ async reportError(params) {
1039
+ try {
1040
+ await this.client.post(`${this.basePath}/client-errors`, {
1041
+ errors: [{
1042
+ message: params.message,
1043
+ stack: params.stack,
1044
+ code: params.code,
1045
+ severity: "low",
1046
+ component: params.component || "sdk",
1047
+ endpoint: params.endpoint,
1048
+ method: params.method,
1049
+ requestId: params.requestId,
1050
+ url: params.url,
1051
+ userAgent: params.userAgent
1052
+ }]
1053
+ });
1054
+ } catch {
1055
+ }
1056
+ }
795
1057
  /**
796
1058
  * Helper para esperar un tiempo
797
1059
  */
package/dist/index.mjs CHANGED
@@ -13,11 +13,13 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
13
13
  return ErrorCode2;
14
14
  })(ErrorCode || {});
15
15
  var UtiliaSDKError = class _UtiliaSDKError extends Error {
16
- constructor(code, message) {
16
+ constructor(code, message, options) {
17
17
  super(message);
18
18
  this.name = "UtiliaSDKError";
19
19
  this.code = code;
20
20
  this.timestamp = /* @__PURE__ */ new Date();
21
+ this.requestId = options?.requestId;
22
+ this.statusCode = options?.statusCode;
21
23
  if (Error.captureStackTrace) {
22
24
  Error.captureStackTrace(this, _UtiliaSDKError);
23
25
  }
@@ -30,7 +32,9 @@ var UtiliaSDKError = class _UtiliaSDKError extends Error {
30
32
  name: this.name,
31
33
  code: this.code,
32
34
  message: this.message,
33
- timestamp: this.timestamp.toISOString()
35
+ timestamp: this.timestamp.toISOString(),
36
+ requestId: this.requestId,
37
+ statusCode: this.statusCode
34
38
  };
35
39
  }
36
40
  /**
@@ -70,6 +74,12 @@ var UtiliaClient = class {
70
74
  "X-Api-Key": this.config.apiKey
71
75
  }
72
76
  });
77
+ this.axios.interceptors.request.use((config2) => {
78
+ const requestId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : this.generateSimpleId();
79
+ config2.headers["x-request-id"] = requestId;
80
+ config2._requestId = requestId;
81
+ return config2;
82
+ });
73
83
  this.setupInterceptors();
74
84
  }
75
85
  /**
@@ -95,40 +105,43 @@ var UtiliaClient = class {
95
105
  * Convierte un error de Axios en un UtiliaSDKError
96
106
  */
97
107
  toSDKError(error) {
108
+ const requestId = error.response?.headers?.["x-request-id"] || error.config?._requestId;
109
+ const statusCode = error.response?.status;
110
+ const errorOptions = { requestId, statusCode };
98
111
  if (!error.response) {
99
112
  if (error.code === "ECONNABORTED") {
100
- return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Timeout: la solicitud excedio el tiempo limite");
113
+ return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Timeout: la solicitud excedio el tiempo limite", errorOptions);
101
114
  }
102
- return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Error de conexion: no se pudo conectar con el servidor");
115
+ return new UtiliaSDKError("NETWORK_ERROR" /* NETWORK_ERROR */, "Error de conexion: no se pudo conectar con el servidor", errorOptions);
103
116
  }
104
117
  const status = error.response.status;
105
118
  const data = error.response.data;
106
119
  const message = data?.message || data?.error || error.message;
107
120
  switch (status) {
108
121
  case 401:
109
- return new UtiliaSDKError("UNAUTHORIZED" /* UNAUTHORIZED */, message || "API Key invalida o faltante");
122
+ return new UtiliaSDKError("UNAUTHORIZED" /* UNAUTHORIZED */, message || "API Key invalida o faltante", errorOptions);
110
123
  case 403:
111
124
  if (message?.toLowerCase().includes("rate limit")) {
112
- return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limit excedido. Intenta de nuevo mas tarde.");
125
+ return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, "Rate limit excedido. Intenta de nuevo mas tarde.", errorOptions);
113
126
  }
114
127
  if (message?.toLowerCase().includes("origen")) {
115
- return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, "Origen no permitido por CORS");
128
+ return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, "Origen no permitido por CORS", errorOptions);
116
129
  }
117
- return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, message || "Acceso denegado");
130
+ return new UtiliaSDKError("FORBIDDEN" /* FORBIDDEN */, message || "Acceso denegado", errorOptions);
118
131
  case 404:
119
- return new UtiliaSDKError("NOT_FOUND" /* NOT_FOUND */, message || "Recurso no encontrado");
132
+ return new UtiliaSDKError("NOT_FOUND" /* NOT_FOUND */, message || "Recurso no encontrado", errorOptions);
120
133
  case 400:
121
134
  case 422:
122
- return new UtiliaSDKError("VALIDATION_ERROR" /* VALIDATION_ERROR */, message || "Error de validacion en los datos enviados");
135
+ return new UtiliaSDKError("VALIDATION_ERROR" /* VALIDATION_ERROR */, message || "Error de validacion en los datos enviados", errorOptions);
123
136
  case 429:
124
- return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, message || "Rate limit excedido. Intenta de nuevo mas tarde.");
137
+ return new UtiliaSDKError("RATE_LIMITED" /* RATE_LIMITED */, message || "Rate limit excedido. Intenta de nuevo mas tarde.", errorOptions);
125
138
  case 500:
126
139
  case 502:
127
140
  case 503:
128
141
  case 504:
129
- return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || "Error interno del servidor");
142
+ return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || "Error interno del servidor", errorOptions);
130
143
  default:
131
- return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || `Error desconocido (HTTP ${status})`);
144
+ return new UtiliaSDKError("UNKNOWN" /* UNKNOWN */, message || `Error desconocido (HTTP ${status})`, errorOptions);
132
145
  }
133
146
  }
134
147
  /**
@@ -175,12 +188,30 @@ var UtiliaClient = class {
175
188
  }
176
189
  throw lastError;
177
190
  }
191
+ /**
192
+ * Genera un ID simple como fallback cuando crypto.randomUUID no esta disponible
193
+ */
194
+ generateSimpleId() {
195
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
196
+ }
178
197
  /**
179
198
  * Helper para esperar un tiempo
180
199
  */
181
200
  sleep(ms) {
182
201
  return new Promise((resolve) => setTimeout(resolve, ms));
183
202
  }
203
+ /**
204
+ * Construye una URL completa para conexiones SSE con la API key como query param.
205
+ * Util para EventSource que no soporta headers personalizados.
206
+ *
207
+ * @param path - Ruta relativa de la API (ej: /external/v1/tickets/ai-suggest/123/stream)
208
+ * @returns URL completa con apiKey como parametro de consulta
209
+ */
210
+ buildSseUrl(path) {
211
+ const base = this.config.baseURL.replace(/\/$/, "");
212
+ const separator = path.includes("?") ? "&" : "?";
213
+ return `${base}${path}${separator}apiKey=${this.config.apiKey}`;
214
+ }
184
215
  /**
185
216
  * Realiza una peticion GET
186
217
  */
@@ -753,6 +784,237 @@ var AiService = class {
753
784
  async trackAiAction(data) {
754
785
  return this.client.post(`${this.basePath}/ai-track-action`, data);
755
786
  }
787
+ /**
788
+ * Solicita sugerencias de IA y recibe el resultado via SSE (Server-Sent Events).
789
+ * Alternativa al polling que ofrece actualizaciones en tiempo real.
790
+ *
791
+ * Requiere un entorno con EventSource nativo (navegador).
792
+ * Para Node.js se necesita un polyfill como 'eventsource'.
793
+ *
794
+ * @param data - Datos para generar sugerencias
795
+ * @param options - Opciones de streaming
796
+ * @returns Handle con promesa de resultado y funcion de aborto
797
+ *
798
+ * @example
799
+ * ```typescript
800
+ * const handle = await sdk.ai.streamSuggestions(
801
+ * { userId: 'user-123', text: 'No puedo iniciar sesion' },
802
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
803
+ * );
804
+ *
805
+ * // Esperar resultado
806
+ * const result = await handle.result;
807
+ * console.log(result?.suggestions);
808
+ *
809
+ * // O abortar si el usuario cancela
810
+ * handle.abort();
811
+ * ```
812
+ */
813
+ async streamSuggestions(data, options = {}) {
814
+ const { onProgress, onError, timeout = 12e4 } = options;
815
+ if (typeof EventSource === "undefined") {
816
+ throw new Error(
817
+ 'EventSource no esta disponible en este entorno. Para Node.js, instala un polyfill como "eventsource" y asignalo a globalThis.EventSource.'
818
+ );
819
+ }
820
+ const { jobId } = await this.requestSuggestions(data);
821
+ const sseUrl = this.client.buildSseUrl(`${this.basePath}/ai-suggest/${jobId}/stream`);
822
+ let eventSource;
823
+ let timeoutId;
824
+ const result = new Promise((resolve, reject) => {
825
+ eventSource = new EventSource(sseUrl);
826
+ timeoutId = setTimeout(() => {
827
+ eventSource.close();
828
+ reject(new Error("Timeout: la conexion SSE excedio el tiempo limite"));
829
+ }, timeout);
830
+ eventSource.onmessage = (event) => {
831
+ try {
832
+ const data2 = JSON.parse(event.data);
833
+ switch (data2.event) {
834
+ case "progress":
835
+ if (onProgress && data2.progress !== void 0) {
836
+ onProgress(data2.progress);
837
+ }
838
+ break;
839
+ case "completed":
840
+ clearTimeout(timeoutId);
841
+ eventSource.close();
842
+ resolve(data2.result);
843
+ break;
844
+ case "failed":
845
+ clearTimeout(timeoutId);
846
+ eventSource.close();
847
+ reject(new Error(data2.error || "Error al generar sugerencias"));
848
+ break;
849
+ case "heartbeat":
850
+ break;
851
+ }
852
+ } catch (parseError) {
853
+ if (onError) {
854
+ onError(new Error("Error al parsear evento SSE"));
855
+ }
856
+ }
857
+ };
858
+ eventSource.onerror = () => {
859
+ clearTimeout(timeoutId);
860
+ eventSource.close();
861
+ reject(new Error("Error en la conexion SSE"));
862
+ };
863
+ });
864
+ const abort = () => {
865
+ clearTimeout(timeoutId);
866
+ eventSource?.close();
867
+ };
868
+ return { result, abort };
869
+ }
870
+ /**
871
+ * Solicita una transcripcion de audio de forma asincrona.
872
+ * Retorna un jobId para consultar el estado o usar con streamTranscription.
873
+ *
874
+ * @param audioBase64 - Audio codificado en base64
875
+ * @param audioFilename - Nombre del archivo con extension
876
+ * @returns ID del job para consultar estado
877
+ *
878
+ * @example
879
+ * ```typescript
880
+ * const { jobId } = await sdk.ai.requestTranscription(
881
+ * audioBase64,
882
+ * 'recording.webm',
883
+ * );
884
+ * ```
885
+ */
886
+ async requestTranscription(audioBase64, audioFilename) {
887
+ return this.client.post(`${this.basePath}/ai-transcribe-async`, {
888
+ audioBase64,
889
+ audioFilename
890
+ });
891
+ }
892
+ /**
893
+ * Obtiene el estado de un job de transcripcion asincrona.
894
+ *
895
+ * @param jobId - ID del job obtenido de requestTranscription
896
+ * @returns Estado actual del job de transcripcion
897
+ *
898
+ * @example
899
+ * ```typescript
900
+ * const status = await sdk.ai.getTranscriptionStatus(jobId);
901
+ *
902
+ * if (status.status === 'completed') {
903
+ * console.log(status.transcription);
904
+ * }
905
+ * ```
906
+ */
907
+ async getTranscriptionStatus(jobId) {
908
+ return this.client.get(`${this.basePath}/ai-transcribe/${jobId}`);
909
+ }
910
+ /**
911
+ * Solicita una transcripcion de audio y recibe el resultado via SSE.
912
+ * Alternativa al polling para transcripciones asincronas.
913
+ *
914
+ * Requiere un entorno con EventSource nativo (navegador).
915
+ * Para Node.js se necesita un polyfill como 'eventsource'.
916
+ *
917
+ * @param audioBase64 - Audio codificado en base64
918
+ * @param audioFilename - Nombre del archivo con extension
919
+ * @param options - Opciones de streaming
920
+ * @returns Promesa que resuelve con el texto transcrito
921
+ *
922
+ * @example
923
+ * ```typescript
924
+ * const { transcription } = await sdk.ai.streamTranscription(
925
+ * audioBase64,
926
+ * 'recording.webm',
927
+ * { onProgress: (p) => console.log(`Progreso: ${p}%`) },
928
+ * );
929
+ * console.log(transcription);
930
+ * ```
931
+ */
932
+ async streamTranscription(audioBase64, audioFilename, options = {}) {
933
+ const { onProgress, onError, timeout = 12e4 } = options;
934
+ if (typeof EventSource === "undefined") {
935
+ throw new Error(
936
+ 'EventSource no esta disponible en este entorno. Para Node.js, instala un polyfill como "eventsource" y asignalo a globalThis.EventSource.'
937
+ );
938
+ }
939
+ const { jobId } = await this.requestTranscription(audioBase64, audioFilename);
940
+ const sseUrl = this.client.buildSseUrl(`${this.basePath}/ai-transcribe/${jobId}/stream`);
941
+ return new Promise((resolve, reject) => {
942
+ const eventSource = new EventSource(sseUrl);
943
+ const timeoutId = setTimeout(() => {
944
+ eventSource.close();
945
+ reject(new Error("Timeout: la conexion SSE excedio el tiempo limite"));
946
+ }, timeout);
947
+ eventSource.onmessage = (event) => {
948
+ try {
949
+ const data = JSON.parse(event.data);
950
+ switch (data.event) {
951
+ case "progress":
952
+ if (onProgress && data.progress !== void 0) {
953
+ onProgress(data.progress);
954
+ }
955
+ break;
956
+ case "completed":
957
+ clearTimeout(timeoutId);
958
+ eventSource.close();
959
+ resolve({ transcription: data.result?.transcription || "" });
960
+ break;
961
+ case "failed":
962
+ clearTimeout(timeoutId);
963
+ eventSource.close();
964
+ reject(new Error(data.error || "Error en la transcripcion"));
965
+ break;
966
+ case "heartbeat":
967
+ break;
968
+ }
969
+ } catch (parseError) {
970
+ if (onError) {
971
+ onError(new Error("Error al parsear evento SSE"));
972
+ }
973
+ }
974
+ };
975
+ eventSource.onerror = () => {
976
+ clearTimeout(timeoutId);
977
+ eventSource.close();
978
+ reject(new Error("Error en la conexion SSE"));
979
+ };
980
+ });
981
+ }
982
+ /**
983
+ * Reporta un error del cliente al servidor para monitoreo.
984
+ * Este metodo nunca lanza excepciones; los errores de reporte se ignoran silenciosamente.
985
+ *
986
+ * @param params - Datos del error a reportar
987
+ *
988
+ * @example
989
+ * ```typescript
990
+ * await sdk.ai.reportError({
991
+ * message: 'No se pudo cargar el widget',
992
+ * code: 'WIDGET_LOAD_ERROR',
993
+ * component: 'widget',
994
+ * url: window.location.href,
995
+ * userAgent: navigator.userAgent,
996
+ * });
997
+ * ```
998
+ */
999
+ async reportError(params) {
1000
+ try {
1001
+ await this.client.post(`${this.basePath}/client-errors`, {
1002
+ errors: [{
1003
+ message: params.message,
1004
+ stack: params.stack,
1005
+ code: params.code,
1006
+ severity: "low",
1007
+ component: params.component || "sdk",
1008
+ endpoint: params.endpoint,
1009
+ method: params.method,
1010
+ requestId: params.requestId,
1011
+ url: params.url,
1012
+ userAgent: params.userAgent
1013
+ }]
1014
+ });
1015
+ } catch {
1016
+ }
1017
+ }
756
1018
  /**
757
1019
  * Helper para esperar un tiempo
758
1020
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilia-os/sdk-js",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "SDK JavaScript/TypeScript para UTILIA OS External Integrations",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",