@herdctl/chat 0.0.1 → 0.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/dm-filter.test.d.ts +5 -0
  3. package/dist/__tests__/dm-filter.test.d.ts.map +1 -0
  4. package/dist/__tests__/dm-filter.test.js +136 -0
  5. package/dist/__tests__/dm-filter.test.js.map +1 -0
  6. package/dist/__tests__/error-handler.test.d.ts +5 -0
  7. package/dist/__tests__/error-handler.test.d.ts.map +1 -0
  8. package/dist/__tests__/error-handler.test.js +235 -0
  9. package/dist/__tests__/error-handler.test.js.map +1 -0
  10. package/dist/__tests__/errors.test.d.ts +5 -0
  11. package/dist/__tests__/errors.test.d.ts.map +1 -0
  12. package/dist/__tests__/errors.test.js +140 -0
  13. package/dist/__tests__/errors.test.js.map +1 -0
  14. package/dist/__tests__/index.test.d.ts +2 -0
  15. package/dist/__tests__/index.test.d.ts.map +1 -0
  16. package/dist/__tests__/index.test.js +25 -0
  17. package/dist/__tests__/index.test.js.map +1 -0
  18. package/dist/__tests__/message-extraction.test.d.ts +5 -0
  19. package/dist/__tests__/message-extraction.test.d.ts.map +1 -0
  20. package/dist/__tests__/message-extraction.test.js +157 -0
  21. package/dist/__tests__/message-extraction.test.js.map +1 -0
  22. package/dist/__tests__/message-splitting.test.d.ts +5 -0
  23. package/dist/__tests__/message-splitting.test.d.ts.map +1 -0
  24. package/dist/__tests__/message-splitting.test.js +153 -0
  25. package/dist/__tests__/message-splitting.test.js.map +1 -0
  26. package/dist/__tests__/session-manager.test.d.ts +2 -0
  27. package/dist/__tests__/session-manager.test.d.ts.map +1 -0
  28. package/dist/__tests__/session-manager.test.js +779 -0
  29. package/dist/__tests__/session-manager.test.js.map +1 -0
  30. package/dist/__tests__/status-formatting.test.d.ts +5 -0
  31. package/dist/__tests__/status-formatting.test.d.ts.map +1 -0
  32. package/dist/__tests__/status-formatting.test.js +160 -0
  33. package/dist/__tests__/status-formatting.test.js.map +1 -0
  34. package/dist/__tests__/streaming-responder.test.d.ts +5 -0
  35. package/dist/__tests__/streaming-responder.test.d.ts.map +1 -0
  36. package/dist/__tests__/streaming-responder.test.js +154 -0
  37. package/dist/__tests__/streaming-responder.test.js.map +1 -0
  38. package/dist/dm-filter.d.ts +121 -0
  39. package/dist/dm-filter.d.ts.map +1 -0
  40. package/dist/dm-filter.js +162 -0
  41. package/dist/dm-filter.js.map +1 -0
  42. package/dist/error-handler.d.ts +217 -0
  43. package/dist/error-handler.d.ts.map +1 -0
  44. package/dist/error-handler.js +313 -0
  45. package/dist/error-handler.js.map +1 -0
  46. package/dist/errors.d.ts +118 -0
  47. package/dist/errors.d.ts.map +1 -0
  48. package/dist/errors.js +157 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/index.d.ts +22 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +69 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/message-extraction.d.ts +81 -0
  55. package/dist/message-extraction.d.ts.map +1 -0
  56. package/dist/message-extraction.js +90 -0
  57. package/dist/message-extraction.js.map +1 -0
  58. package/dist/message-splitting.d.ts +133 -0
  59. package/dist/message-splitting.d.ts.map +1 -0
  60. package/dist/message-splitting.js +188 -0
  61. package/dist/message-splitting.js.map +1 -0
  62. package/dist/session-manager/errors.d.ts +59 -0
  63. package/dist/session-manager/errors.d.ts.map +1 -0
  64. package/dist/session-manager/errors.js +71 -0
  65. package/dist/session-manager/errors.js.map +1 -0
  66. package/dist/session-manager/index.d.ts +10 -0
  67. package/dist/session-manager/index.d.ts.map +1 -0
  68. package/dist/session-manager/index.js +14 -0
  69. package/dist/session-manager/index.js.map +1 -0
  70. package/dist/session-manager/session-manager.d.ts +123 -0
  71. package/dist/session-manager/session-manager.d.ts.map +1 -0
  72. package/dist/session-manager/session-manager.js +394 -0
  73. package/dist/session-manager/session-manager.js.map +1 -0
  74. package/dist/session-manager/types.d.ts +205 -0
  75. package/dist/session-manager/types.d.ts.map +1 -0
  76. package/dist/session-manager/types.js +67 -0
  77. package/dist/session-manager/types.js.map +1 -0
  78. package/dist/status-formatting.d.ts +147 -0
  79. package/dist/status-formatting.d.ts.map +1 -0
  80. package/dist/status-formatting.js +234 -0
  81. package/dist/status-formatting.js.map +1 -0
  82. package/dist/streaming-responder.d.ts +130 -0
  83. package/dist/streaming-responder.d.ts.map +1 -0
  84. package/dist/streaming-responder.js +178 -0
  85. package/dist/streaming-responder.js.map +1 -0
  86. package/dist/types.d.ts +184 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +8 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +39 -4
@@ -0,0 +1,162 @@
1
+ /**
2
+ * DM (Direct Message) filtering utilities
3
+ *
4
+ * Provides utilities for:
5
+ * - Checking if DMs should be processed
6
+ * - Filtering users via allowlist/blocklist
7
+ * - Determining DM mode configuration
8
+ *
9
+ * These utilities are platform-agnostic and work for any chat platform
10
+ * that supports direct messages with user filtering.
11
+ */
12
+ // =============================================================================
13
+ // DM Filtering Functions
14
+ // =============================================================================
15
+ /**
16
+ * Check if DMs are enabled based on configuration
17
+ *
18
+ * If no DM config is provided, DMs are enabled by default.
19
+ *
20
+ * @param dmConfig - DM configuration from agent's chat config
21
+ * @returns true if DMs are enabled, false otherwise
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * if (isDMEnabled(agentConfig.chat.discord?.dm)) {
26
+ * // Process DM
27
+ * }
28
+ * ```
29
+ */
30
+ export function isDMEnabled(dmConfig) {
31
+ // If no DM config provided, DMs are enabled by default
32
+ if (!dmConfig) {
33
+ return true;
34
+ }
35
+ // If enabled is not specified, default to true
36
+ return dmConfig.enabled !== false;
37
+ }
38
+ /**
39
+ * Get the mode for DM processing
40
+ *
41
+ * DMs default to "auto" mode (no mention required).
42
+ *
43
+ * @param dmConfig - DM configuration from agent's chat config
44
+ * @returns The mode for DM processing
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const mode = getDMMode(agentConfig.chat.discord?.dm);
49
+ * if (mode === 'auto') {
50
+ * // Process all DM messages
51
+ * }
52
+ * ```
53
+ */
54
+ export function getDMMode(dmConfig) {
55
+ // DMs default to auto mode (no mention required)
56
+ if (!dmConfig) {
57
+ return "auto";
58
+ }
59
+ return dmConfig.mode ?? "auto";
60
+ }
61
+ /**
62
+ * Check if a user is allowed to send DMs to the bot
63
+ *
64
+ * Filtering rules:
65
+ * 1. If DMs are disabled, no users are allowed
66
+ * 2. If a blocklist is defined and user is on it, they are blocked
67
+ * 3. If an allowlist is defined, only users on it are allowed
68
+ * 4. If neither list is defined, all users are allowed
69
+ *
70
+ * Note: Blocklist takes precedence over allowlist.
71
+ *
72
+ * @param userId - User ID to check
73
+ * @param dmConfig - DM configuration from agent's chat config
74
+ * @returns Filter result with allowed status and reason
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const result = checkDMUserFilter("123456789", dmConfig);
79
+ * if (!result.allowed) {
80
+ * console.log(`User blocked: ${result.reason}`);
81
+ * return; // Don't process message
82
+ * }
83
+ * ```
84
+ */
85
+ export function checkDMUserFilter(userId, dmConfig) {
86
+ // If DMs are disabled, no users are allowed
87
+ if (!isDMEnabled(dmConfig)) {
88
+ return {
89
+ allowed: false,
90
+ reason: "dm_disabled",
91
+ };
92
+ }
93
+ // If no config, all users are allowed
94
+ if (!dmConfig) {
95
+ return {
96
+ allowed: true,
97
+ reason: "allowed",
98
+ };
99
+ }
100
+ const { allowlist, blocklist } = dmConfig;
101
+ // Check blocklist first (takes precedence)
102
+ if (blocklist && blocklist.length > 0) {
103
+ if (blocklist.includes(userId)) {
104
+ return {
105
+ allowed: false,
106
+ reason: "in_blocklist",
107
+ };
108
+ }
109
+ }
110
+ // Check allowlist (if defined, only users on it are allowed)
111
+ if (allowlist && allowlist.length > 0) {
112
+ if (!allowlist.includes(userId)) {
113
+ return {
114
+ allowed: false,
115
+ reason: "not_in_allowlist",
116
+ };
117
+ }
118
+ }
119
+ // User is allowed
120
+ return {
121
+ allowed: true,
122
+ reason: "allowed",
123
+ };
124
+ }
125
+ /**
126
+ * Check if a message should be processed in the given mode
127
+ *
128
+ * In auto mode:
129
+ * - All non-bot messages are processed
130
+ * - No mention is required
131
+ *
132
+ * In mention mode:
133
+ * - Only messages that mention the bot are processed
134
+ *
135
+ * Bot messages are never processed in either mode.
136
+ *
137
+ * @param isBot - Whether the message author is a bot
138
+ * @param mode - The channel/DM mode
139
+ * @param wasMentioned - Whether the bot was mentioned in the message
140
+ * @returns true if the message should be processed
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const mode = getDMMode(dmConfig);
145
+ * if (shouldProcessInMode(message.author.bot, mode, wasMentioned)) {
146
+ * // Process message
147
+ * }
148
+ * ```
149
+ */
150
+ export function shouldProcessInMode(isBot, mode, wasMentioned) {
151
+ // Never process bot messages
152
+ if (isBot) {
153
+ return false;
154
+ }
155
+ // In auto mode, process all non-bot messages
156
+ if (mode === "auto") {
157
+ return true;
158
+ }
159
+ // In mention mode, only process if mentioned
160
+ return wasMentioned;
161
+ }
162
+ //# sourceMappingURL=dm-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-filter.js","sourceRoot":"","sources":["../src/dm-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiCH,gFAAgF;AAChF,yBAAyB;AACzB,gFAAgF;AAEhF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,QAA4B;IACtD,uDAAuD;IACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IACD,+CAA+C;IAC/C,OAAO,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,QAA4B;IACpD,iDAAiD;IACjD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,IAAI,MAAM,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,QAA4B;IAE5B,4CAA4C;IAC5C,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,aAAa;SACtB,CAAC;IACJ,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC;IAE1C,2CAA2C;IAC3C,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,cAAc;aACvB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,6DAA6D;IAC7D,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,kBAAkB;aAC3B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,kBAAkB;IAClB,OAAO;QACL,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,SAAS;KAClB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,IAAwB,EACxB,YAAqB;IAErB,6BAA6B;IAC7B,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACf,CAAC;IAED,6CAA6C;IAC7C,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6CAA6C;IAC7C,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Error handling utilities for chat connectors
3
+ *
4
+ * Provides:
5
+ * - Error classification for handling decisions
6
+ * - User-friendly error messages
7
+ * - Retry logic for transient failures
8
+ * - Safe execution wrappers
9
+ *
10
+ * Platform-specific error classification stays in platform packages,
11
+ * but the common infrastructure is here.
12
+ */
13
+ import type { ChatConnectorLogger } from "./types.js";
14
+ /**
15
+ * Categories of errors for handling purposes
16
+ *
17
+ * Used to determine appropriate responses and whether to retry.
18
+ */
19
+ export declare enum ErrorCategory {
20
+ /** Transient errors that may succeed on retry (network issues, timeouts) */
21
+ TRANSIENT = "transient",
22
+ /** Permanent errors that won't succeed on retry (invalid config) */
23
+ PERMANENT = "permanent",
24
+ /** Rate limit errors that need backoff before retry */
25
+ RATE_LIMIT = "rate_limit",
26
+ /** Configuration or setup errors */
27
+ CONFIGURATION = "configuration",
28
+ /** Authentication or authorization errors */
29
+ AUTH = "auth",
30
+ /** Network connectivity errors */
31
+ NETWORK = "network",
32
+ /** API-specific errors */
33
+ API = "api",
34
+ /** Internal/unexpected errors */
35
+ INTERNAL = "internal",
36
+ /** Unknown/unexpected errors */
37
+ UNKNOWN = "unknown"
38
+ }
39
+ /**
40
+ * Classified error with category and handling information
41
+ */
42
+ export interface ClassifiedError {
43
+ /** Original error */
44
+ error: Error;
45
+ /** Error category for handling decisions */
46
+ category: ErrorCategory;
47
+ /** User-friendly message to display */
48
+ userMessage: string;
49
+ /** Whether this error should be retried */
50
+ shouldRetry: boolean;
51
+ /** Suggested retry delay in milliseconds (if shouldRetry is true) */
52
+ retryDelayMs?: number;
53
+ }
54
+ /**
55
+ * Standard user-friendly error messages
56
+ *
57
+ * These are safe to show to end users in chat platforms.
58
+ */
59
+ export declare const USER_ERROR_MESSAGES: {
60
+ /** Generic processing error */
61
+ readonly PROCESSING_ERROR: "Sorry, I encountered an error processing your request. Please try again.";
62
+ /** Connection/network error */
63
+ readonly CONNECTION_ERROR: "I'm having trouble connecting right now. Please try again in a moment.";
64
+ /** Rate limit error */
65
+ readonly RATE_LIMITED: "I'm receiving too many requests right now. Please wait a moment and try again.";
66
+ /** Command execution error */
67
+ readonly COMMAND_ERROR: "Sorry, I couldn't complete that command. Please try again.";
68
+ /** Session error */
69
+ readonly SESSION_ERROR: "I'm having trouble with your conversation session. Please try again.";
70
+ /** Timeout error */
71
+ readonly TIMEOUT_ERROR: "The request took too long to complete. Please try again.";
72
+ /** Permission error */
73
+ readonly PERMISSION_ERROR: "I don't have permission to do that in this channel.";
74
+ /** Authentication error */
75
+ readonly AUTH_ERROR: "I'm having trouble authenticating. Please contact an administrator.";
76
+ /** API error */
77
+ readonly API_ERROR: "Something went wrong with the chat API. Please try again.";
78
+ /** Internal error */
79
+ readonly INTERNAL_ERROR: "An internal error occurred. Please try again or start a new session.";
80
+ /** Unknown error */
81
+ readonly UNKNOWN_ERROR: "An unexpected error occurred. Please try again.";
82
+ };
83
+ export type UserErrorMessageKey = keyof typeof USER_ERROR_MESSAGES;
84
+ /**
85
+ * Options for retry operations
86
+ */
87
+ export interface RetryOptions {
88
+ /** Maximum number of retry attempts (default: 3) */
89
+ maxAttempts?: number;
90
+ /** Base delay between retries in ms (default: 1000) */
91
+ baseDelayMs?: number;
92
+ /** Maximum delay between retries in ms (default: 30000) */
93
+ maxDelayMs?: number;
94
+ /** Exponential backoff multiplier (default: 2) */
95
+ backoffMultiplier?: number;
96
+ /** Logger for retry attempts */
97
+ logger?: ChatConnectorLogger;
98
+ /** Operation name for logging */
99
+ operationName?: string;
100
+ /** Predicate to determine if error should be retried */
101
+ shouldRetry?: (error: unknown, attempt: number) => boolean;
102
+ }
103
+ /**
104
+ * Result of a retry operation
105
+ */
106
+ export interface RetryResult<T> {
107
+ /** Whether the operation succeeded */
108
+ success: boolean;
109
+ /** The result value (if success is true) */
110
+ value?: T;
111
+ /** The last error encountered (if success is false) */
112
+ error?: Error;
113
+ /** Number of attempts made */
114
+ attempts: number;
115
+ }
116
+ /**
117
+ * Execute an async operation with retry logic
118
+ *
119
+ * Uses exponential backoff for transient failures.
120
+ * Only retries errors when the shouldRetry predicate returns true.
121
+ *
122
+ * @param operation - Async function to execute
123
+ * @param options - Retry configuration
124
+ * @returns Result with success status, value, or error
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const result = await withRetry(
129
+ * async () => {
130
+ * const response = await fetch(url);
131
+ * if (!response.ok) throw new Error('Request failed');
132
+ * return response.json();
133
+ * },
134
+ * {
135
+ * maxAttempts: 3,
136
+ * operationName: 'fetchData',
137
+ * logger: myLogger,
138
+ * shouldRetry: (error) => isTransientError(error),
139
+ * }
140
+ * );
141
+ *
142
+ * if (result.success) {
143
+ * console.log('Data:', result.value);
144
+ * } else {
145
+ * console.error('Failed after', result.attempts, 'attempts:', result.error);
146
+ * }
147
+ * ```
148
+ */
149
+ export declare function withRetry<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<RetryResult<T>>;
150
+ /**
151
+ * Check if an error is a transient error (may succeed on retry)
152
+ *
153
+ * @param error - Error to check
154
+ * @returns true if the error appears to be transient
155
+ */
156
+ export declare function isTransientError(error: unknown): boolean;
157
+ /**
158
+ * Check if an error is a rate limit error
159
+ *
160
+ * @param error - Error to check
161
+ * @returns true if the error indicates rate limiting
162
+ */
163
+ export declare function isRateLimitError(error: unknown): boolean;
164
+ /**
165
+ * Check if an error is an authentication error
166
+ *
167
+ * @param error - Error to check
168
+ * @returns true if the error indicates an auth problem
169
+ */
170
+ export declare function isAuthError(error: unknown): boolean;
171
+ /**
172
+ * Execute an async function and handle errors safely
173
+ *
174
+ * Returns the result or undefined if an error occurs.
175
+ * Always logs errors for debugging.
176
+ *
177
+ * @param operation - Async function to execute
178
+ * @param logger - Logger for error logging
179
+ * @param context - Description of the operation for logging
180
+ * @returns Result or undefined on error
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const result = await safeExecute(
185
+ * () => fetchUserData(userId),
186
+ * logger,
187
+ * 'fetching user data'
188
+ * );
189
+ * if (result) {
190
+ * // Use result
191
+ * }
192
+ * ```
193
+ */
194
+ export declare function safeExecute<T>(operation: () => Promise<T>, logger: ChatConnectorLogger, context: string): Promise<T | undefined>;
195
+ /**
196
+ * Execute an async function and reply with error message on failure
197
+ *
198
+ * On error, sends a user-friendly error message to the reply function.
199
+ *
200
+ * @param operation - Async function to execute
201
+ * @param reply - Function to send error reply
202
+ * @param logger - Logger for error logging
203
+ * @param context - Description of the operation for logging
204
+ * @param userMessage - Custom error message for user (optional)
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * await safeExecuteWithReply(
209
+ * () => processMessage(message),
210
+ * (msg) => channel.send(msg),
211
+ * logger,
212
+ * 'processing message'
213
+ * );
214
+ * ```
215
+ */
216
+ export declare function safeExecuteWithReply(operation: () => Promise<void>, reply: (content: string) => Promise<void>, logger: ChatConnectorLogger, context: string, userMessage?: string): Promise<void>;
217
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Error handling utilities for chat connectors
3
+ *
4
+ * Provides:
5
+ * - Error classification for handling decisions
6
+ * - User-friendly error messages
7
+ * - Retry logic for transient failures
8
+ * - Safe execution wrappers
9
+ *
10
+ * Platform-specific error classification stays in platform packages,
11
+ * but the common infrastructure is here.
12
+ */
13
+ // =============================================================================
14
+ // Error Categories
15
+ // =============================================================================
16
+ /**
17
+ * Categories of errors for handling purposes
18
+ *
19
+ * Used to determine appropriate responses and whether to retry.
20
+ */
21
+ export var ErrorCategory;
22
+ (function (ErrorCategory) {
23
+ /** Transient errors that may succeed on retry (network issues, timeouts) */
24
+ ErrorCategory["TRANSIENT"] = "transient";
25
+ /** Permanent errors that won't succeed on retry (invalid config) */
26
+ ErrorCategory["PERMANENT"] = "permanent";
27
+ /** Rate limit errors that need backoff before retry */
28
+ ErrorCategory["RATE_LIMIT"] = "rate_limit";
29
+ /** Configuration or setup errors */
30
+ ErrorCategory["CONFIGURATION"] = "configuration";
31
+ /** Authentication or authorization errors */
32
+ ErrorCategory["AUTH"] = "auth";
33
+ /** Network connectivity errors */
34
+ ErrorCategory["NETWORK"] = "network";
35
+ /** API-specific errors */
36
+ ErrorCategory["API"] = "api";
37
+ /** Internal/unexpected errors */
38
+ ErrorCategory["INTERNAL"] = "internal";
39
+ /** Unknown/unexpected errors */
40
+ ErrorCategory["UNKNOWN"] = "unknown";
41
+ })(ErrorCategory || (ErrorCategory = {}));
42
+ // =============================================================================
43
+ // User Error Messages
44
+ // =============================================================================
45
+ /**
46
+ * Standard user-friendly error messages
47
+ *
48
+ * These are safe to show to end users in chat platforms.
49
+ */
50
+ export const USER_ERROR_MESSAGES = {
51
+ /** Generic processing error */
52
+ PROCESSING_ERROR: "Sorry, I encountered an error processing your request. Please try again.",
53
+ /** Connection/network error */
54
+ CONNECTION_ERROR: "I'm having trouble connecting right now. Please try again in a moment.",
55
+ /** Rate limit error */
56
+ RATE_LIMITED: "I'm receiving too many requests right now. Please wait a moment and try again.",
57
+ /** Command execution error */
58
+ COMMAND_ERROR: "Sorry, I couldn't complete that command. Please try again.",
59
+ /** Session error */
60
+ SESSION_ERROR: "I'm having trouble with your conversation session. Please try again.",
61
+ /** Timeout error */
62
+ TIMEOUT_ERROR: "The request took too long to complete. Please try again.",
63
+ /** Permission error */
64
+ PERMISSION_ERROR: "I don't have permission to do that in this channel.",
65
+ /** Authentication error */
66
+ AUTH_ERROR: "I'm having trouble authenticating. Please contact an administrator.",
67
+ /** API error */
68
+ API_ERROR: "Something went wrong with the chat API. Please try again.",
69
+ /** Internal error */
70
+ INTERNAL_ERROR: "An internal error occurred. Please try again or start a new session.",
71
+ /** Unknown error */
72
+ UNKNOWN_ERROR: "An unexpected error occurred. Please try again.",
73
+ };
74
+ /**
75
+ * Default retry options
76
+ */
77
+ const DEFAULT_RETRY_OPTIONS = {
78
+ maxAttempts: 3,
79
+ baseDelayMs: 1000,
80
+ maxDelayMs: 30000,
81
+ backoffMultiplier: 2,
82
+ operationName: "operation",
83
+ };
84
+ /**
85
+ * Execute an async operation with retry logic
86
+ *
87
+ * Uses exponential backoff for transient failures.
88
+ * Only retries errors when the shouldRetry predicate returns true.
89
+ *
90
+ * @param operation - Async function to execute
91
+ * @param options - Retry configuration
92
+ * @returns Result with success status, value, or error
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const result = await withRetry(
97
+ * async () => {
98
+ * const response = await fetch(url);
99
+ * if (!response.ok) throw new Error('Request failed');
100
+ * return response.json();
101
+ * },
102
+ * {
103
+ * maxAttempts: 3,
104
+ * operationName: 'fetchData',
105
+ * logger: myLogger,
106
+ * shouldRetry: (error) => isTransientError(error),
107
+ * }
108
+ * );
109
+ *
110
+ * if (result.success) {
111
+ * console.log('Data:', result.value);
112
+ * } else {
113
+ * console.error('Failed after', result.attempts, 'attempts:', result.error);
114
+ * }
115
+ * ```
116
+ */
117
+ export async function withRetry(operation, options = {}) {
118
+ const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
119
+ const { maxAttempts, baseDelayMs, maxDelayMs, backoffMultiplier, operationName, logger, } = opts;
120
+ // Default shouldRetry: retry transient errors
121
+ const shouldRetryFn = options.shouldRetry ?? ((error) => isTransientError(error));
122
+ let lastError;
123
+ let attempt = 0;
124
+ while (attempt < maxAttempts) {
125
+ attempt++;
126
+ try {
127
+ const value = await operation();
128
+ return { success: true, value, attempts: attempt };
129
+ }
130
+ catch (error) {
131
+ lastError = error instanceof Error ? error : new Error(String(error));
132
+ // Check if we should retry
133
+ const shouldRetryError = shouldRetryFn(error, attempt);
134
+ if (!shouldRetryError || attempt >= maxAttempts) {
135
+ // Don't retry: either permanent error or out of attempts
136
+ logger?.debug(`${operationName} failed permanently after ${attempt} attempt(s)`, {
137
+ error: lastError.message,
138
+ });
139
+ break;
140
+ }
141
+ // Calculate delay with exponential backoff
142
+ const delay = Math.min(baseDelayMs * Math.pow(backoffMultiplier, attempt - 1), maxDelayMs);
143
+ logger?.info(`${operationName} failed, retrying in ${delay}ms...`, {
144
+ attempt,
145
+ maxAttempts,
146
+ error: lastError.message,
147
+ });
148
+ // Wait before retrying
149
+ await sleep(delay);
150
+ }
151
+ }
152
+ return {
153
+ success: false,
154
+ error: lastError,
155
+ attempts: attempt,
156
+ };
157
+ }
158
+ // =============================================================================
159
+ // Error Classification Helpers
160
+ // =============================================================================
161
+ /**
162
+ * Check if an error is a transient error (may succeed on retry)
163
+ *
164
+ * @param error - Error to check
165
+ * @returns true if the error appears to be transient
166
+ */
167
+ export function isTransientError(error) {
168
+ if (!(error instanceof Error)) {
169
+ return false;
170
+ }
171
+ const message = error.message.toLowerCase();
172
+ const name = error.name.toLowerCase();
173
+ // Network errors
174
+ const networkPatterns = [
175
+ "econnreset",
176
+ "econnrefused",
177
+ "etimedout",
178
+ "enotfound",
179
+ "eai_again",
180
+ "enetunreach",
181
+ "ehostunreach",
182
+ "socket hang up",
183
+ "network",
184
+ "connect econnrefused",
185
+ "getaddrinfo",
186
+ "fetch failed",
187
+ ];
188
+ if (networkPatterns.some((pattern) => message.includes(pattern.toLowerCase()) ||
189
+ name.includes(pattern.toLowerCase()))) {
190
+ return true;
191
+ }
192
+ // Timeout errors
193
+ const timeoutPatterns = ["timeout", "timed out", "etimedout", "aborterror"];
194
+ if (timeoutPatterns.some((pattern) => message.includes(pattern.toLowerCase()) ||
195
+ name.includes(pattern.toLowerCase()))) {
196
+ return true;
197
+ }
198
+ return false;
199
+ }
200
+ /**
201
+ * Check if an error is a rate limit error
202
+ *
203
+ * @param error - Error to check
204
+ * @returns true if the error indicates rate limiting
205
+ */
206
+ export function isRateLimitError(error) {
207
+ if (!(error instanceof Error)) {
208
+ return false;
209
+ }
210
+ const message = error.message.toLowerCase();
211
+ return message.includes("rate_limit") || message.includes("ratelimited");
212
+ }
213
+ /**
214
+ * Check if an error is an authentication error
215
+ *
216
+ * @param error - Error to check
217
+ * @returns true if the error indicates an auth problem
218
+ */
219
+ export function isAuthError(error) {
220
+ if (!(error instanceof Error)) {
221
+ return false;
222
+ }
223
+ const message = error.message.toLowerCase();
224
+ return (message.includes("invalid_auth") ||
225
+ message.includes("token_revoked") ||
226
+ message.includes("token_expired") ||
227
+ message.includes("not_authed") ||
228
+ message.includes("unauthorized") ||
229
+ message.includes("forbidden"));
230
+ }
231
+ // =============================================================================
232
+ // Safe Execution Helpers
233
+ // =============================================================================
234
+ /**
235
+ * Execute an async function and handle errors safely
236
+ *
237
+ * Returns the result or undefined if an error occurs.
238
+ * Always logs errors for debugging.
239
+ *
240
+ * @param operation - Async function to execute
241
+ * @param logger - Logger for error logging
242
+ * @param context - Description of the operation for logging
243
+ * @returns Result or undefined on error
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * const result = await safeExecute(
248
+ * () => fetchUserData(userId),
249
+ * logger,
250
+ * 'fetching user data'
251
+ * );
252
+ * if (result) {
253
+ * // Use result
254
+ * }
255
+ * ```
256
+ */
257
+ export async function safeExecute(operation, logger, context) {
258
+ try {
259
+ return await operation();
260
+ }
261
+ catch (error) {
262
+ const errorMessage = error instanceof Error ? error.message : String(error);
263
+ logger.error(`Error in ${context}: ${errorMessage}`);
264
+ return undefined;
265
+ }
266
+ }
267
+ /**
268
+ * Execute an async function and reply with error message on failure
269
+ *
270
+ * On error, sends a user-friendly error message to the reply function.
271
+ *
272
+ * @param operation - Async function to execute
273
+ * @param reply - Function to send error reply
274
+ * @param logger - Logger for error logging
275
+ * @param context - Description of the operation for logging
276
+ * @param userMessage - Custom error message for user (optional)
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * await safeExecuteWithReply(
281
+ * () => processMessage(message),
282
+ * (msg) => channel.send(msg),
283
+ * logger,
284
+ * 'processing message'
285
+ * );
286
+ * ```
287
+ */
288
+ export async function safeExecuteWithReply(operation, reply, logger, context, userMessage) {
289
+ try {
290
+ await operation();
291
+ }
292
+ catch (error) {
293
+ const err = error instanceof Error ? error : new Error(String(error));
294
+ logger.error(`Error in ${context}: ${err.message}`);
295
+ try {
296
+ const message = userMessage ?? USER_ERROR_MESSAGES.PROCESSING_ERROR;
297
+ await reply(message);
298
+ }
299
+ catch (replyError) {
300
+ logger.error(`Failed to send error reply: ${replyError.message}`);
301
+ }
302
+ }
303
+ }
304
+ // =============================================================================
305
+ // Utility Functions
306
+ // =============================================================================
307
+ /**
308
+ * Sleep for a specified duration
309
+ */
310
+ function sleep(ms) {
311
+ return new Promise((resolve) => setTimeout(resolve, ms));
312
+ }
313
+ //# sourceMappingURL=error-handler.js.map