ai-inference-stepper 1.0.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 (160) hide show
  1. package/.env.example +169 -0
  2. package/.eslintrc.cjs +23 -0
  3. package/.github/workflows/ci.yml +51 -0
  4. package/.github/workflows/keep-alive.yml +22 -0
  5. package/.github/workflows/publish.yml +34 -0
  6. package/ARCHITECTURE.md +594 -0
  7. package/Dockerfile +16 -0
  8. package/LICENSE +28 -0
  9. package/README.md +261 -0
  10. package/dist/alerts/discord.d.ts +19 -0
  11. package/dist/alerts/discord.d.ts.map +1 -0
  12. package/dist/alerts/discord.js +70 -0
  13. package/dist/alerts/discord.js.map +1 -0
  14. package/dist/cache/redisCache.d.ts +45 -0
  15. package/dist/cache/redisCache.d.ts.map +1 -0
  16. package/dist/cache/redisCache.js +171 -0
  17. package/dist/cache/redisCache.js.map +1 -0
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +8 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/config.d.ts +6 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +251 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/fallback/templateFallback.d.ts +7 -0
  27. package/dist/fallback/templateFallback.d.ts.map +1 -0
  28. package/dist/fallback/templateFallback.js +29 -0
  29. package/dist/fallback/templateFallback.js.map +1 -0
  30. package/dist/index.d.ts +121 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +198 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/logging.d.ts +10 -0
  35. package/dist/logging.d.ts.map +1 -0
  36. package/dist/logging.js +44 -0
  37. package/dist/logging.js.map +1 -0
  38. package/dist/metrics/metrics.d.ts +22 -0
  39. package/dist/metrics/metrics.d.ts.map +1 -0
  40. package/dist/metrics/metrics.js +78 -0
  41. package/dist/metrics/metrics.js.map +1 -0
  42. package/dist/providers/factory.d.ts +11 -0
  43. package/dist/providers/factory.d.ts.map +1 -0
  44. package/dist/providers/factory.js +52 -0
  45. package/dist/providers/factory.js.map +1 -0
  46. package/dist/providers/hfSpace.adapter.d.ts +21 -0
  47. package/dist/providers/hfSpace.adapter.d.ts.map +1 -0
  48. package/dist/providers/hfSpace.adapter.js +110 -0
  49. package/dist/providers/hfSpace.adapter.js.map +1 -0
  50. package/dist/providers/httpTemplate.adapter.d.ts +42 -0
  51. package/dist/providers/httpTemplate.adapter.d.ts.map +1 -0
  52. package/dist/providers/httpTemplate.adapter.js +98 -0
  53. package/dist/providers/httpTemplate.adapter.js.map +1 -0
  54. package/dist/providers/promptBuilder.d.ts +34 -0
  55. package/dist/providers/promptBuilder.d.ts.map +1 -0
  56. package/dist/providers/promptBuilder.js +315 -0
  57. package/dist/providers/promptBuilder.js.map +1 -0
  58. package/dist/providers/provider.interface.d.ts +45 -0
  59. package/dist/providers/provider.interface.d.ts.map +1 -0
  60. package/dist/providers/provider.interface.js +47 -0
  61. package/dist/providers/provider.interface.js.map +1 -0
  62. package/dist/providers/specs.d.ts +18 -0
  63. package/dist/providers/specs.d.ts.map +1 -0
  64. package/dist/providers/specs.js +326 -0
  65. package/dist/providers/specs.js.map +1 -0
  66. package/dist/providers/unified.adapter.d.ts +37 -0
  67. package/dist/providers/unified.adapter.d.ts.map +1 -0
  68. package/dist/providers/unified.adapter.js +141 -0
  69. package/dist/providers/unified.adapter.js.map +1 -0
  70. package/dist/queue/producer.d.ts +30 -0
  71. package/dist/queue/producer.d.ts.map +1 -0
  72. package/dist/queue/producer.js +87 -0
  73. package/dist/queue/producer.js.map +1 -0
  74. package/dist/queue/worker.d.ts +9 -0
  75. package/dist/queue/worker.d.ts.map +1 -0
  76. package/dist/queue/worker.js +137 -0
  77. package/dist/queue/worker.js.map +1 -0
  78. package/dist/server/app.d.ts +4 -0
  79. package/dist/server/app.d.ts.map +1 -0
  80. package/dist/server/app.js +394 -0
  81. package/dist/server/app.js.map +1 -0
  82. package/dist/server/start.d.ts +16 -0
  83. package/dist/server/start.d.ts.map +1 -0
  84. package/dist/server/start.js +45 -0
  85. package/dist/server/start.js.map +1 -0
  86. package/dist/stepper/orchestrator.d.ts +22 -0
  87. package/dist/stepper/orchestrator.d.ts.map +1 -0
  88. package/dist/stepper/orchestrator.js +333 -0
  89. package/dist/stepper/orchestrator.js.map +1 -0
  90. package/dist/types.d.ts +216 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +14 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/utils/redaction.d.ts +9 -0
  95. package/dist/utils/redaction.d.ts.map +1 -0
  96. package/dist/utils/redaction.js +41 -0
  97. package/dist/utils/redaction.js.map +1 -0
  98. package/dist/utils/safeRequest.d.ts +38 -0
  99. package/dist/utils/safeRequest.d.ts.map +1 -0
  100. package/dist/utils/safeRequest.js +104 -0
  101. package/dist/utils/safeRequest.js.map +1 -0
  102. package/dist/validation/report.schema.d.ts +48 -0
  103. package/dist/validation/report.schema.d.ts.map +1 -0
  104. package/dist/validation/report.schema.js +72 -0
  105. package/dist/validation/report.schema.js.map +1 -0
  106. package/dist/webhooks/delivery.d.ts +31 -0
  107. package/dist/webhooks/delivery.d.ts.map +1 -0
  108. package/dist/webhooks/delivery.js +102 -0
  109. package/dist/webhooks/delivery.js.map +1 -0
  110. package/docs/assets/architecture.png +0 -0
  111. package/package.json +75 -0
  112. package/render.yaml +25 -0
  113. package/src/alerts/README.md +25 -0
  114. package/src/alerts/discord.ts +86 -0
  115. package/src/cache/How redis caching works in package stepper.md +971 -0
  116. package/src/cache/README.md +51 -0
  117. package/src/cache/redisCache.ts +194 -0
  118. package/src/ci/deploy.sh +36 -0
  119. package/src/cli.ts +9 -0
  120. package/src/config.ts +265 -0
  121. package/src/fallback/templateFallback.ts +32 -0
  122. package/src/index.ts +246 -0
  123. package/src/logging.ts +46 -0
  124. package/src/metrics/README.md +24 -0
  125. package/src/metrics/metrics.ts +84 -0
  126. package/src/providers/How the providers interact.md +121 -0
  127. package/src/providers/README.md +121 -0
  128. package/src/providers/factory.ts +57 -0
  129. package/src/providers/hfSpace.adapter.ts +119 -0
  130. package/src/providers/httpTemplate.adapter.ts +138 -0
  131. package/src/providers/promptBuilder.ts +330 -0
  132. package/src/providers/provider.interface.ts +73 -0
  133. package/src/providers/specs.ts +366 -0
  134. package/src/providers/unified.adapter.ts +172 -0
  135. package/src/queue/How queue works in package stepper.md +149 -0
  136. package/src/queue/README.md +41 -0
  137. package/src/queue/producer.ts +108 -0
  138. package/src/queue/worker.ts +170 -0
  139. package/src/server/app.ts +451 -0
  140. package/src/server/start.ts +68 -0
  141. package/src/stepper/Dockerfile +48 -0
  142. package/src/stepper/How orchestrator works in package stepper.md +746 -0
  143. package/src/stepper/README.md +43 -0
  144. package/src/stepper/orchestrator.ts +437 -0
  145. package/src/types.ts +238 -0
  146. package/src/utils/redaction.ts +50 -0
  147. package/src/utils/safeRequest.ts +140 -0
  148. package/src/validation/README.md +25 -0
  149. package/src/validation/report.schema.ts +96 -0
  150. package/src/webhooks/delivery.ts +162 -0
  151. package/tests/integration/full-flow.test.ts +192 -0
  152. package/tests/unit/alerts/discord.test.ts +119 -0
  153. package/tests/unit/cache.test.ts +87 -0
  154. package/tests/unit/orchestrator-fallback.test.ts +92 -0
  155. package/tests/unit/orchestrator.test.ts +105 -0
  156. package/tests/unit/providers/factory.test.ts +161 -0
  157. package/tests/unit/providers/unified.adapter.test.ts +206 -0
  158. package/tests/unit/utils/redaction.test.ts +140 -0
  159. package/tests/unit/utils/safeRequest.test.ts +164 -0
  160. package/tsconfig.json +26 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Redact sensitive information from text before sending to AI providers
3
+ */
4
+ export function redactSecrets(text) {
5
+ if (!text)
6
+ return text;
7
+ let redacted = text;
8
+ // AWS access keys
9
+ redacted = redacted.replace(/(A?KIA|AKIA)[A-Z0-9]{16}/g, '[REDACTED_AWS_KEY]');
10
+ // Generic API keys and tokens (common patterns)
11
+ redacted = redacted.replace(/[a-zA-Z0-9_-]{32,}/g, (match) => {
12
+ // Don't redact commit SHAs (typically 40 chars) or very long base64
13
+ if (match.length === 40 || match.length > 200)
14
+ return match;
15
+ return '[REDACTED_TOKEN]';
16
+ });
17
+ // Password/secret assignments in env-style
18
+ redacted = redacted.replace(/(password|passwd|secret|api_key|apikey|token|auth)(\s*[:=]\s*)(\S+)/gi, '$1$2[REDACTED]');
19
+ // Email addresses
20
+ redacted = redacted.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]');
21
+ // Base64 encoded secrets (long base64 strings)
22
+ redacted = redacted.replace(/[A-Za-z0-9+/]{100,}={0,2}/g, '[REDACTED_BASE64]');
23
+ return redacted;
24
+ }
25
+ /**
26
+ * Redact sensitive fields from an object
27
+ */
28
+ export function redactObject(obj) {
29
+ const result = { ...obj };
30
+ const sensitiveKeys = ['apiKey', 'api_key', 'token', 'password', 'secret', 'authorization'];
31
+ for (const key of Object.keys(result)) {
32
+ if (sensitiveKeys.includes(key.toLowerCase())) {
33
+ result[key] = '[REDACTED]';
34
+ }
35
+ else if (typeof result[key] === 'string') {
36
+ result[key] = redactSecrets(result[key]);
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/utils/redaction.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,IAAI,QAAQ,GAAG,IAAI,CAAC;IAEpB,kBAAkB;IAClB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,2BAA2B,EAAE,oBAAoB,CAAC,CAAC;IAE/E,gDAAgD;IAChD,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3D,oEAAoE;QACpE,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;YAAE,OAAO,KAAK,CAAC;QAC5D,OAAO,kBAAkB,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,2CAA2C;IAC3C,QAAQ,GAAG,QAAQ,CAAC,OAAO,CACzB,uEAAuE,EACvE,gBAAgB,CACjB,CAAC;IAEF,kBAAkB;IAClB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,iDAAiD,EAAE,kBAAkB,CAAC,CAAC;IAEnG,+CAA+C;IAC/C,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,4BAA4B,EAAE,mBAAmB,CAAC,CAAC;IAE/E,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAoC,GAAM;IACpE,MAAM,MAAM,GAA4B,EAAE,GAAG,GAAG,EAAE,CAAC;IACnD,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IAE5F,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;QAC7B,CAAC;aAAM,IAAI,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAW,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,OAAO,MAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { AxiosRequestConfig } from 'axios';
2
+ export interface SafeRequestOptions extends AxiosRequestConfig {
3
+ timeout?: number;
4
+ retryAfterHeader?: boolean;
5
+ }
6
+ export interface SafeRequestResult<T = unknown> {
7
+ data: T;
8
+ status: number;
9
+ headers: Record<string, string>;
10
+ retryAfter?: number;
11
+ }
12
+ export declare class RequestError extends Error {
13
+ status?: number | undefined;
14
+ code?: string | undefined;
15
+ retryAfter?: number | undefined;
16
+ constructor(message: string, status?: number | undefined, code?: string | undefined, retryAfter?: number | undefined);
17
+ }
18
+ /**
19
+ * Make an HTTP request with timeout and retry-after header parsing
20
+ */
21
+ export declare function safeRequest<T = unknown>(url: string, options?: SafeRequestOptions): Promise<SafeRequestResult<T>>;
22
+ /**
23
+ * Parse error and extract retry-after information
24
+ */
25
+ export declare function parseRetryAfter(error: unknown): number | undefined;
26
+ /**
27
+ * Check if error is retryable
28
+ */
29
+ export declare function isRetryableError(error: unknown): boolean;
30
+ /**
31
+ * Check if error is auth-related
32
+ */
33
+ export declare function isAuthError(error: unknown): boolean;
34
+ /**
35
+ * Check if error is rate limit
36
+ */
37
+ export declare function isRateLimitError(error: unknown): boolean;
38
+ //# sourceMappingURL=safeRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeRequest.d.ts","sourceRoot":"","sources":["../../src/utils/safeRequest.ts"],"names":[],"mappings":"AAAA,OAAc,EAAc,kBAAkB,EAAiB,MAAM,OAAO,CAAC;AAG7E,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC5C,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAa,SAAQ,KAAK;IAG5B,MAAM,CAAC,EAAE,MAAM;IACf,IAAI,CAAC,EAAE,MAAM;IACb,UAAU,CAAC,EAAE,MAAM;gBAH1B,OAAO,EAAE,MAAM,EACR,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,UAAU,CAAC,EAAE,MAAM,YAAA;CAK7B;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAsE/B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAKlE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKxD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGnD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGxD"}
@@ -0,0 +1,104 @@
1
+ import axios from 'axios';
2
+ import { logger } from '../logging.js';
3
+ export class RequestError extends Error {
4
+ status;
5
+ code;
6
+ retryAfter;
7
+ constructor(message, status, code, retryAfter) {
8
+ super(message);
9
+ this.status = status;
10
+ this.code = code;
11
+ this.retryAfter = retryAfter;
12
+ this.name = 'RequestError';
13
+ }
14
+ }
15
+ /**
16
+ * Make an HTTP request with timeout and retry-after header parsing
17
+ */
18
+ export async function safeRequest(url, options = {}) {
19
+ const timeout = options.timeout || 15000;
20
+ try {
21
+ const response = await axios({
22
+ ...options,
23
+ url,
24
+ timeout,
25
+ validateStatus: (status) => status < 600, // Don't throw on any status
26
+ });
27
+ // Parse Retry-After header if present
28
+ let retryAfter;
29
+ const retryAfterHeader = response.headers['retry-after'];
30
+ if (retryAfterHeader) {
31
+ const parsed = parseInt(retryAfterHeader, 10);
32
+ retryAfter = isNaN(parsed) ? undefined : parsed;
33
+ }
34
+ // Throw on error statuses
35
+ if (response.status >= 400) {
36
+ throw new RequestError(`HTTP ${response.status}: ${response.statusText}`, response.status, `HTTP_${response.status}`, retryAfter);
37
+ }
38
+ return {
39
+ data: response.data,
40
+ status: response.status,
41
+ headers: response.headers,
42
+ retryAfter,
43
+ };
44
+ }
45
+ catch (error) {
46
+ if (error instanceof RequestError) {
47
+ throw error;
48
+ }
49
+ if (axios.isAxiosError(error)) {
50
+ const axiosError = error;
51
+ // Timeout
52
+ if (axiosError.code === 'ECONNABORTED' || axiosError.code === 'ETIMEDOUT') {
53
+ throw new RequestError('Request timeout', 408, 'TIMEOUT');
54
+ }
55
+ // Network errors
56
+ if (axiosError.code === 'ENOTFOUND' || axiosError.code === 'ECONNREFUSED') {
57
+ throw new RequestError('Network error', 503, 'NETWORK_ERROR');
58
+ }
59
+ // Parse response error
60
+ const status = axiosError.response?.status;
61
+ const retryAfter = axiosError.response?.headers['retry-after']
62
+ ? parseInt(axiosError.response.headers['retry-after'], 10)
63
+ : undefined;
64
+ throw new RequestError(axiosError.message, status, status ? `HTTP_${status}` : 'UNKNOWN', retryAfter);
65
+ }
66
+ logger.error({ error }, 'Unexpected request error');
67
+ throw new RequestError('Unexpected error', undefined, 'UNKNOWN');
68
+ }
69
+ }
70
+ /**
71
+ * Parse error and extract retry-after information
72
+ */
73
+ export function parseRetryAfter(error) {
74
+ if (error instanceof RequestError && error.retryAfter) {
75
+ return error.retryAfter;
76
+ }
77
+ return undefined;
78
+ }
79
+ /**
80
+ * Check if error is retryable
81
+ */
82
+ export function isRetryableError(error) {
83
+ if (!(error instanceof RequestError))
84
+ return false;
85
+ const retryableCodes = [408, 429, 500, 502, 503, 504];
86
+ return error.status ? retryableCodes.includes(error.status) : false;
87
+ }
88
+ /**
89
+ * Check if error is auth-related
90
+ */
91
+ export function isAuthError(error) {
92
+ if (!(error instanceof RequestError))
93
+ return false;
94
+ return error.status === 401 || error.status === 403;
95
+ }
96
+ /**
97
+ * Check if error is rate limit
98
+ */
99
+ export function isRateLimitError(error) {
100
+ if (!(error instanceof RequestError))
101
+ return false;
102
+ return error.status === 429;
103
+ }
104
+ //# sourceMappingURL=safeRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeRequest.js","sourceRoot":"","sources":["../../src/utils/safeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAwD,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAcvC,MAAM,OAAO,YAAa,SAAQ,KAAK;IAG5B;IACA;IACA;IAJT,YACE,OAAe,EACR,MAAe,EACf,IAAa,EACb,UAAmB;QAE1B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,WAAM,GAAN,MAAM,CAAS;QACf,SAAI,GAAJ,IAAI,CAAS;QACb,eAAU,GAAV,UAAU,CAAS;QAG1B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAW,EACX,UAA8B,EAAE;IAEhC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;IAEzC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAqB,MAAM,KAAK,CAAC;YAC7C,GAAG,OAAO;YACV,GAAG;YACH,OAAO;YACP,cAAc,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE,4BAA4B;SACvE,CAAC,CAAC;QAEH,sCAAsC;QACtC,IAAI,UAA8B,CAAC;QACnC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACzD,IAAI,gBAAgB,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;YAC9C,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QAClD,CAAC;QAED,0BAA0B;QAC1B,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAC3B,MAAM,IAAI,YAAY,CACpB,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,EACjD,QAAQ,CAAC,MAAM,EACf,QAAQ,QAAQ,CAAC,MAAM,EAAE,EACzB,UAAU,CACX,CAAC;QACJ,CAAC;QAED,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,QAAQ,CAAC,OAAiC;YACnD,UAAU;SACX,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,IAAI,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,KAAmB,CAAC;YAEvC,UAAU;YACV,IAAI,UAAU,CAAC,IAAI,KAAK,cAAc,IAAI,UAAU,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC1E,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;YAC5D,CAAC;YAED,iBAAiB;YACjB,IAAI,UAAU,CAAC,IAAI,KAAK,WAAW,IAAI,UAAU,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAC1E,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;YAChE,CAAC;YAED,uBAAuB;YACvB,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;YAC3C,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC;gBAC5D,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;gBAC1D,CAAC,CAAC,SAAS,CAAC;YAEd,MAAM,IAAI,YAAY,CACpB,UAAU,CAAC,OAAO,EAClB,MAAM,EACN,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,EACrC,UAAU,CACX,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,0BAA0B,CAAC,CAAC;QACpD,MAAM,IAAI,YAAY,CAAC,kBAAkB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACtD,OAAO,KAAK,CAAC,UAAU,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,CAAC,KAAK,YAAY,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnD,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,CAAC,KAAK,YAAY,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,CAAC,KAAK,YAAY,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { ReportOutput } from '../types.js';
3
+ /**
4
+ * Comprehensive Zod schema for validating AI-generated report output
5
+ * with detailed error messages
6
+ */
7
+ export declare const ReportOutputSchema: z.ZodObject<{
8
+ title: z.ZodEffects<z.ZodString, string, string>;
9
+ summary: z.ZodString;
10
+ changes: z.ZodArray<z.ZodString, "many">;
11
+ rationale: z.ZodString;
12
+ impact_and_tests: z.ZodString;
13
+ next_steps: z.ZodArray<z.ZodString, "many">;
14
+ tags: z.ZodEffects<z.ZodString, string, string>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ title: string;
17
+ summary: string;
18
+ changes: string[];
19
+ rationale: string;
20
+ impact_and_tests: string;
21
+ next_steps: string[];
22
+ tags: string;
23
+ }, {
24
+ title: string;
25
+ summary: string;
26
+ changes: string[];
27
+ rationale: string;
28
+ impact_and_tests: string;
29
+ next_steps: string[];
30
+ tags: string;
31
+ }>;
32
+ /**
33
+ * Validate report output against schema
34
+ */
35
+ export declare function validateReportOutput(data: unknown): {
36
+ valid: boolean;
37
+ result?: ReportOutput;
38
+ error?: string;
39
+ };
40
+ /**
41
+ * Safely parse JSON and validate
42
+ */
43
+ export declare function parseAndValidateReport(jsonString: string): {
44
+ valid: boolean;
45
+ result?: ReportOutput;
46
+ error?: string;
47
+ };
48
+ //# sourceMappingURL=report.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.schema.d.ts","sourceRoot":"","sources":["../../src/validation/report.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;EAkC7B,CAAC;AAEH;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAaA;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG;IAC1D,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAuBA"}
@@ -0,0 +1,72 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Comprehensive Zod schema for validating AI-generated report output
4
+ * with detailed error messages
5
+ */
6
+ export const ReportOutputSchema = z.object({
7
+ title: z.string()
8
+ .min(10, 'Title must be at least 10 characters')
9
+ .max(120, 'Title must not exceed 120 characters')
10
+ .refine((val) => val.trim().length > 0, 'Title cannot be empty or whitespace only'),
11
+ summary: z.string()
12
+ .min(50, 'Summary must be at least 50 characters')
13
+ .max(2000, 'Summary must not exceed 2000 characters'),
14
+ changes: z.array(z.string().min(5, 'Each change must be at least 5 characters'))
15
+ .min(1, 'At least one change must be listed')
16
+ .max(50, 'Maximum 50 changes allowed'),
17
+ rationale: z.string()
18
+ .min(20, 'Rationale must be at least 20 characters')
19
+ .max(2000, 'Rationale must not exceed 2000 characters'),
20
+ impact_and_tests: z.string()
21
+ .min(20, 'Impact and tests section must be at least 20 characters')
22
+ .max(2000, 'Impact and tests section must not exceed 2000 characters'),
23
+ next_steps: z.array(z.string().min(5, 'Each next step must be at least 5 characters'))
24
+ .max(20, 'Maximum 20 next steps allowed'),
25
+ tags: z.string()
26
+ .max(200, 'Tags must not exceed 200 characters')
27
+ .refine((val) => val.split(',').every(tag => tag.trim().length > 0), 'Tags must be comma-separated with no empty values'),
28
+ });
29
+ /**
30
+ * Validate report output against schema
31
+ */
32
+ export function validateReportOutput(data) {
33
+ try {
34
+ const validated = ReportOutputSchema.parse(data);
35
+ return { valid: true, result: validated };
36
+ }
37
+ catch (error) {
38
+ if (error instanceof z.ZodError) {
39
+ return {
40
+ valid: false,
41
+ error: error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; '),
42
+ };
43
+ }
44
+ return { valid: false, error: 'Unknown validation error' };
45
+ }
46
+ }
47
+ /**
48
+ * Safely parse JSON and validate
49
+ */
50
+ export function parseAndValidateReport(jsonString) {
51
+ try {
52
+ // Clean up common AI model output issues
53
+ let cleaned = jsonString.trim();
54
+ // Remove markdown code blocks if present
55
+ cleaned = cleaned.replace(/^```json\s*/i, '').replace(/```\s*$/, '');
56
+ cleaned = cleaned.replace(/^```\s*/i, '').replace(/```\s*$/, '');
57
+ // Remove any leading/trailing text that isn't JSON
58
+ const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
59
+ if (jsonMatch) {
60
+ cleaned = jsonMatch[0];
61
+ }
62
+ const parsed = JSON.parse(cleaned);
63
+ return validateReportOutput(parsed);
64
+ }
65
+ catch (error) {
66
+ if (error instanceof SyntaxError) {
67
+ return { valid: false, error: `Invalid JSON: ${error.message}` };
68
+ }
69
+ return { valid: false, error: 'Failed to parse JSON' };
70
+ }
71
+ }
72
+ //# sourceMappingURL=report.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.schema.js","sourceRoot":"","sources":["../../src/validation/report.schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export interface WebhookPayload {
2
+ jobId: string;
3
+ status: 'completed' | 'failed';
4
+ result?: unknown;
5
+ error?: string;
6
+ timestamp: number;
7
+ }
8
+ export interface WebhookConfig {
9
+ url: string;
10
+ secret: string;
11
+ maxRetries?: number;
12
+ retryDelayMs?: number;
13
+ }
14
+ /**
15
+ * Send webhook notification with bearer token + HMAC signature
16
+ * Implements retry logic for failed deliveries
17
+ */
18
+ export declare function sendWebhook(config: WebhookConfig, payload: WebhookPayload, attempt?: number): Promise<{
19
+ success: boolean;
20
+ statusCode?: number;
21
+ error?: string;
22
+ }>;
23
+ /**
24
+ * Send success webhook notification
25
+ */
26
+ export declare function notifyWebhookSuccess(webhookUrl: string, webhookSecret: string, jobId: string, result: unknown): Promise<void>;
27
+ /**
28
+ * Send failure webhook notification
29
+ */
30
+ export declare function notifyWebhookFailure(webhookUrl: string, webhookSecret: string, jobId: string, error: string): Promise<void>;
31
+ //# sourceMappingURL=delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../../src/webhooks/delivery.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAYD;;;GAGG;AACH,wBAAsB,WAAW,CAC7B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,MAAU,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiEpE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACtC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACtC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -0,0 +1,102 @@
1
+ // packages/stepper/src/webhooks/delivery.ts
2
+ import crypto from 'crypto';
3
+ import { logger, createChildLogger } from '../logging.js';
4
+ /**
5
+ * Generate HMAC-SHA256 signature for webhook payload
6
+ */
7
+ function generateSignature(payload, secret) {
8
+ return crypto
9
+ .createHmac('sha256', secret)
10
+ .update(payload)
11
+ .digest('hex');
12
+ }
13
+ /**
14
+ * Send webhook notification with bearer token + HMAC signature
15
+ * Implements retry logic for failed deliveries
16
+ */
17
+ export async function sendWebhook(config, payload, attempt = 1) {
18
+ const log = createChildLogger({ jobId: payload.jobId, webhookAttempt: attempt });
19
+ const maxRetries = config.maxRetries || 3;
20
+ const retryDelayMs = config.retryDelayMs || 5000;
21
+ try {
22
+ const payloadString = JSON.stringify(payload);
23
+ const signature = generateSignature(payloadString, config.secret);
24
+ log.info({ url: config.url, attempt, maxRetries }, 'Sending webhook');
25
+ const response = await fetch(config.url, {
26
+ method: 'POST',
27
+ headers: {
28
+ 'Content-Type': 'application/json',
29
+ 'Authorization': `Bearer ${config.secret}`,
30
+ 'X-Webhook-Signature': signature,
31
+ 'X-Webhook-Timestamp': payload.timestamp.toString(),
32
+ 'User-Agent': 'Stepper/1.0'
33
+ },
34
+ body: payloadString,
35
+ signal: AbortSignal.timeout(10000) // 10s timeout
36
+ });
37
+ if (response.ok) {
38
+ log.info({ statusCode: response.status }, 'Webhook delivered successfully');
39
+ return { success: true, statusCode: response.status };
40
+ }
41
+ // Non-OK response
42
+ const errorBody = await response.text().catch(() => 'Unable to read response');
43
+ log.warn({ statusCode: response.status, errorBody }, 'Webhook delivery failed with non-OK status');
44
+ // Retry on 5xx errors or specific 4xx errors
45
+ const shouldRetry = response.status >= 500 || response.status === 408 || response.status === 429;
46
+ if (shouldRetry && attempt < maxRetries) {
47
+ log.info({ nextAttempt: attempt + 1, delayMs: retryDelayMs }, 'Retrying webhook delivery');
48
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs * attempt)); // Exponential backoff
49
+ return sendWebhook(config, payload, attempt + 1);
50
+ }
51
+ return {
52
+ success: false,
53
+ statusCode: response.status,
54
+ error: `HTTP ${response.status}: ${errorBody.substring(0, 200)}`
55
+ };
56
+ }
57
+ catch (error) {
58
+ const errorMessage = error instanceof Error ? error.message : String(error);
59
+ log.error({ error: errorMessage, attempt }, 'Webhook delivery error');
60
+ // Retry on network errors
61
+ if (attempt < maxRetries) {
62
+ log.info({ nextAttempt: attempt + 1, delayMs: retryDelayMs }, 'Retrying webhook after error');
63
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs * attempt));
64
+ return sendWebhook(config, payload, attempt + 1);
65
+ }
66
+ return {
67
+ success: false,
68
+ error: `Network error after ${maxRetries} attempts: ${errorMessage}`
69
+ };
70
+ }
71
+ }
72
+ /**
73
+ * Send success webhook notification
74
+ */
75
+ export async function notifyWebhookSuccess(webhookUrl, webhookSecret, jobId, result) {
76
+ const payload = {
77
+ jobId,
78
+ status: 'completed',
79
+ result,
80
+ timestamp: Date.now()
81
+ };
82
+ const webhookResult = await sendWebhook({ url: webhookUrl, secret: webhookSecret }, payload);
83
+ if (!webhookResult.success) {
84
+ logger.warn({ jobId, error: webhookResult.error }, 'Webhook delivery failed after all retries - job completed but notification not delivered');
85
+ }
86
+ }
87
+ /**
88
+ * Send failure webhook notification
89
+ */
90
+ export async function notifyWebhookFailure(webhookUrl, webhookSecret, jobId, error) {
91
+ const payload = {
92
+ jobId,
93
+ status: 'failed',
94
+ error,
95
+ timestamp: Date.now()
96
+ };
97
+ const webhookResult = await sendWebhook({ url: webhookUrl, secret: webhookSecret }, payload);
98
+ if (!webhookResult.success) {
99
+ logger.warn({ jobId, error: webhookResult.error }, 'Failure webhook delivery failed - job failed and notification not delivered');
100
+ }
101
+ }
102
+ //# sourceMappingURL=delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../../src/webhooks/delivery.ts"],"names":[],"mappings":"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"}
Binary file
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "ai-inference-stepper",
3
+ "version": "1.0.0",
4
+ "description": "Production-grade AI inference stepper with multi-provider fallback",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./server": {
14
+ "import": "./dist/server/start.js",
15
+ "types": "./dist/server/start.d.ts"
16
+ },
17
+ "./app": {
18
+ "import": "./dist/server/app.js",
19
+ "types": "./dist/server/app.d.ts"
20
+ }
21
+ },
22
+ "bin": {
23
+ "stepper": "./dist/cli.js"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "dev": "tsx watch src/server/start.ts",
31
+ "start": "node dist/server/start.js",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "lint": "eslint src --ext .ts",
35
+ "lint:fix": "eslint src --ext .ts --fix",
36
+ "docker:build": "docker build -t commitdiary-stepper .",
37
+ "docker:run": "docker run -p 3001:3001 --env-file .env commitdiary-stepper",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "axios": "^1.6.5",
42
+ "bottleneck": "^2.19.5",
43
+ "bullmq": "^5.1.8",
44
+ "cors": "^2.8.6",
45
+ "dotenv": "^17.2.3",
46
+ "express": "^4.18.2",
47
+ "express-rate-limit": "^8.2.1",
48
+ "helmet": "^8.1.0",
49
+ "ioredis": "^5.3.2",
50
+ "opossum": "^8.1.3",
51
+ "pino": "^8.17.2",
52
+ "pino-pretty": "^10.3.1",
53
+ "prom-client": "^15.1.0",
54
+ "uuid": "^9.0.1",
55
+ "zod": "^3.22.4"
56
+ },
57
+ "devDependencies": {
58
+ "@types/cors": "^2.8.19",
59
+ "@types/express": "^4.17.21",
60
+ "@types/node": "^20.11.5",
61
+ "@types/opossum": "^8.1.9",
62
+ "@types/uuid": "^9.0.8",
63
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
64
+ "@typescript-eslint/parser": "^6.19.0",
65
+ "eslint": "^8.56.0",
66
+ "ioredis-mock": "^8.13.1",
67
+ "nock": "^13.5.6",
68
+ "tsx": "^4.7.0",
69
+ "typescript": "^5.3.3",
70
+ "vitest": "^1.6.1"
71
+ },
72
+ "engines": {
73
+ "node": ">=18.0.0"
74
+ }
75
+ }
package/render.yaml ADDED
@@ -0,0 +1,25 @@
1
+ services:
2
+ - type: web
3
+ name: commitdiary-stepper
4
+ env: node
5
+ rootDir: packages/stepper
6
+ plan: free
7
+ region: frankfurt
8
+ buildCommand: pnpm install && pnpm build
9
+ startCommand: pnpm start
10
+ envVars:
11
+ - key: NODE_ENV
12
+ value: production
13
+ - key: PORT
14
+ value: 3001
15
+ - key: REDIS_URL
16
+ sync: false
17
+ - key: API_KEY_ENABLED
18
+ value: "true"
19
+ - key: STEPPER_API_KEY
20
+ generateValue: true
21
+ - key: CORS_ENABLED
22
+ value: "true"
23
+ - key: RATE_LIMIT_ENABLED
24
+ value: "true"
25
+ healthCheckPath: /health
@@ -0,0 +1,25 @@
1
+ # 🔔 Alerts & Notifications
2
+
3
+ This module handles communicating critical system events to external channels, primarily **Discord**.
4
+
5
+ ## 🎯 Purpose
6
+
7
+ - **Observability**: Know when the system is struggling in real-time.
8
+ - **Incident Response**: Get notified immediately if all AI providers fail or if a circuit breaker opens.
9
+
10
+ ## 🚀 Features
11
+
12
+ ### Discord Integration
13
+
14
+ Sends rich, formatted "embed" messages to a Discord webhook.
15
+
16
+ **What gets alerted?**
17
+
18
+ - **Circuit Breaker Open**: When a provider enters a failed state.
19
+ - **Circuit Breaker Closed**: When a provider recovers.
20
+ - **Job Failures**: When all providers fail and the system falls back to a template.
21
+ - **System Errors**: Unexpected crashes or critical configuration issues.
22
+
23
+ ## 🛠️ Configuration
24
+
25
+ Set the `DISCORD_WEBHOOK_URL` in your `.env` to enable these alerts. If not set, the system will gracefully skip alerting and only log to the console.