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.
- package/.env.example +169 -0
- package/.eslintrc.cjs +23 -0
- package/.github/workflows/ci.yml +51 -0
- package/.github/workflows/keep-alive.yml +22 -0
- package/.github/workflows/publish.yml +34 -0
- package/ARCHITECTURE.md +594 -0
- package/Dockerfile +16 -0
- package/LICENSE +28 -0
- package/README.md +261 -0
- package/dist/alerts/discord.d.ts +19 -0
- package/dist/alerts/discord.d.ts.map +1 -0
- package/dist/alerts/discord.js +70 -0
- package/dist/alerts/discord.js.map +1 -0
- package/dist/cache/redisCache.d.ts +45 -0
- package/dist/cache/redisCache.d.ts.map +1 -0
- package/dist/cache/redisCache.js +171 -0
- package/dist/cache/redisCache.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +251 -0
- package/dist/config.js.map +1 -0
- package/dist/fallback/templateFallback.d.ts +7 -0
- package/dist/fallback/templateFallback.d.ts.map +1 -0
- package/dist/fallback/templateFallback.js +29 -0
- package/dist/fallback/templateFallback.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +198 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +10 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +44 -0
- package/dist/logging.js.map +1 -0
- package/dist/metrics/metrics.d.ts +22 -0
- package/dist/metrics/metrics.d.ts.map +1 -0
- package/dist/metrics/metrics.js +78 -0
- package/dist/metrics/metrics.js.map +1 -0
- package/dist/providers/factory.d.ts +11 -0
- package/dist/providers/factory.d.ts.map +1 -0
- package/dist/providers/factory.js +52 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/hfSpace.adapter.d.ts +21 -0
- package/dist/providers/hfSpace.adapter.d.ts.map +1 -0
- package/dist/providers/hfSpace.adapter.js +110 -0
- package/dist/providers/hfSpace.adapter.js.map +1 -0
- package/dist/providers/httpTemplate.adapter.d.ts +42 -0
- package/dist/providers/httpTemplate.adapter.d.ts.map +1 -0
- package/dist/providers/httpTemplate.adapter.js +98 -0
- package/dist/providers/httpTemplate.adapter.js.map +1 -0
- package/dist/providers/promptBuilder.d.ts +34 -0
- package/dist/providers/promptBuilder.d.ts.map +1 -0
- package/dist/providers/promptBuilder.js +315 -0
- package/dist/providers/promptBuilder.js.map +1 -0
- package/dist/providers/provider.interface.d.ts +45 -0
- package/dist/providers/provider.interface.d.ts.map +1 -0
- package/dist/providers/provider.interface.js +47 -0
- package/dist/providers/provider.interface.js.map +1 -0
- package/dist/providers/specs.d.ts +18 -0
- package/dist/providers/specs.d.ts.map +1 -0
- package/dist/providers/specs.js +326 -0
- package/dist/providers/specs.js.map +1 -0
- package/dist/providers/unified.adapter.d.ts +37 -0
- package/dist/providers/unified.adapter.d.ts.map +1 -0
- package/dist/providers/unified.adapter.js +141 -0
- package/dist/providers/unified.adapter.js.map +1 -0
- package/dist/queue/producer.d.ts +30 -0
- package/dist/queue/producer.d.ts.map +1 -0
- package/dist/queue/producer.js +87 -0
- package/dist/queue/producer.js.map +1 -0
- package/dist/queue/worker.d.ts +9 -0
- package/dist/queue/worker.d.ts.map +1 -0
- package/dist/queue/worker.js +137 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/server/app.d.ts +4 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +394 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/start.d.ts +16 -0
- package/dist/server/start.d.ts.map +1 -0
- package/dist/server/start.js +45 -0
- package/dist/server/start.js.map +1 -0
- package/dist/stepper/orchestrator.d.ts +22 -0
- package/dist/stepper/orchestrator.d.ts.map +1 -0
- package/dist/stepper/orchestrator.js +333 -0
- package/dist/stepper/orchestrator.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/redaction.d.ts +9 -0
- package/dist/utils/redaction.d.ts.map +1 -0
- package/dist/utils/redaction.js +41 -0
- package/dist/utils/redaction.js.map +1 -0
- package/dist/utils/safeRequest.d.ts +38 -0
- package/dist/utils/safeRequest.d.ts.map +1 -0
- package/dist/utils/safeRequest.js +104 -0
- package/dist/utils/safeRequest.js.map +1 -0
- package/dist/validation/report.schema.d.ts +48 -0
- package/dist/validation/report.schema.d.ts.map +1 -0
- package/dist/validation/report.schema.js +72 -0
- package/dist/validation/report.schema.js.map +1 -0
- package/dist/webhooks/delivery.d.ts +31 -0
- package/dist/webhooks/delivery.d.ts.map +1 -0
- package/dist/webhooks/delivery.js +102 -0
- package/dist/webhooks/delivery.js.map +1 -0
- package/docs/assets/architecture.png +0 -0
- package/package.json +75 -0
- package/render.yaml +25 -0
- package/src/alerts/README.md +25 -0
- package/src/alerts/discord.ts +86 -0
- package/src/cache/How redis caching works in package stepper.md +971 -0
- package/src/cache/README.md +51 -0
- package/src/cache/redisCache.ts +194 -0
- package/src/ci/deploy.sh +36 -0
- package/src/cli.ts +9 -0
- package/src/config.ts +265 -0
- package/src/fallback/templateFallback.ts +32 -0
- package/src/index.ts +246 -0
- package/src/logging.ts +46 -0
- package/src/metrics/README.md +24 -0
- package/src/metrics/metrics.ts +84 -0
- package/src/providers/How the providers interact.md +121 -0
- package/src/providers/README.md +121 -0
- package/src/providers/factory.ts +57 -0
- package/src/providers/hfSpace.adapter.ts +119 -0
- package/src/providers/httpTemplate.adapter.ts +138 -0
- package/src/providers/promptBuilder.ts +330 -0
- package/src/providers/provider.interface.ts +73 -0
- package/src/providers/specs.ts +366 -0
- package/src/providers/unified.adapter.ts +172 -0
- package/src/queue/How queue works in package stepper.md +149 -0
- package/src/queue/README.md +41 -0
- package/src/queue/producer.ts +108 -0
- package/src/queue/worker.ts +170 -0
- package/src/server/app.ts +451 -0
- package/src/server/start.ts +68 -0
- package/src/stepper/Dockerfile +48 -0
- package/src/stepper/How orchestrator works in package stepper.md +746 -0
- package/src/stepper/README.md +43 -0
- package/src/stepper/orchestrator.ts +437 -0
- package/src/types.ts +238 -0
- package/src/utils/redaction.ts +50 -0
- package/src/utils/safeRequest.ts +140 -0
- package/src/validation/README.md +25 -0
- package/src/validation/report.schema.ts +96 -0
- package/src/webhooks/delivery.ts +162 -0
- package/tests/integration/full-flow.test.ts +192 -0
- package/tests/unit/alerts/discord.test.ts +119 -0
- package/tests/unit/cache.test.ts +87 -0
- package/tests/unit/orchestrator-fallback.test.ts +92 -0
- package/tests/unit/orchestrator.test.ts +105 -0
- package/tests/unit/providers/factory.test.ts +161 -0
- package/tests/unit/providers/unified.adapter.test.ts +206 -0
- package/tests/unit/utils/redaction.test.ts +140 -0
- package/tests/unit/utils/safeRequest.test.ts +164 -0
- package/tsconfig.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Stepper: Production-Grade AI Inference Orchestrator
|
|
2
|
+
|
|
3
|
+
[](https://github.com/samuel-adedigba/ai-inference-stepper/actions)
|
|
4
|
+
[](#license)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
**Stepper** is a resilient, multi-provider AI inference engine designed for high-load production environments. It handles provider fallbacks, intelligent caching, job queuing, and circuit breaking out of the box.
|
|
8
|
+
|
|
9
|
+
- Back to root: [../../README.md](../../README.md)
|
|
10
|
+
- CommitDiary packages: [../api/README.md](../api/README.md) • [../web-dashboard/README.md](../web-dashboard/README.md) • [../extension/README.md](../extension/README.md) • [../core/README.md](../core/README.md)
|
|
11
|
+
|
|
12
|
+
## ✅ Standalone Setup (Local)
|
|
13
|
+
|
|
14
|
+
Stepper is open source and can run independently or inside this monorepo.
|
|
15
|
+
|
|
16
|
+
### Prerequisites
|
|
17
|
+
|
|
18
|
+
- Node.js 18+
|
|
19
|
+
- pnpm
|
|
20
|
+
- Redis (required)
|
|
21
|
+
|
|
22
|
+
### Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd packages/stepper
|
|
26
|
+
pnpm install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Configure
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cp .env.example .env
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Add at least one provider key and Redis config in .env.
|
|
36
|
+
|
|
37
|
+
### Run
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
docker run -d -p 6379:6379 redis:alpine
|
|
41
|
+
pnpm dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### ✅ Why This Setup Works
|
|
45
|
+
|
|
46
|
+
- Redis backs cache and queue state for resilient processing
|
|
47
|
+
- Provider adapters allow fallback across multiple AI vendors
|
|
48
|
+
- Callbacks let CommitDiary save reports and notify users reliably
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🏗️ Architecture First
|
|
53
|
+
|
|
54
|
+
Understanding how Stepper handles your requests is key to using its full power.
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
### The Core Flow
|
|
59
|
+
|
|
60
|
+
1. **Request Capture**: Received via HTTP or internal Library Call.
|
|
61
|
+
2. **Smart Caching**: Checks Redis. Supports **Stale-While-Revalidate** (returns stale data while refreshing in background).
|
|
62
|
+
3. **Job Queueing**: If not cached, the request is enqueued via **BullMQ** to prevent overloading providers.
|
|
63
|
+
4. **Resilient Orchestration**:
|
|
64
|
+
- **Priority Fallback**: Tries Gemini → Cohere → HF Space in sequence.
|
|
65
|
+
- **Circuit Breakers**: Stops calling failing providers to allow them to recover.
|
|
66
|
+
- **Rate Limiting**: Per-provider bottlenecking to respect API quotas.
|
|
67
|
+
5. **Finalize**: Result is cached, and `onSuccess` callbacks are triggered.
|
|
68
|
+
|
|
69
|
+
> [!TIP]
|
|
70
|
+
> For a deep dive into the system design, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🔗 CommitDiary Integration Flow
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
flowchart LR
|
|
78
|
+
A[API Server] --> B[Stepper enqueueReport]
|
|
79
|
+
B --> C[Queue + Providers]
|
|
80
|
+
C --> D[Callback to API]
|
|
81
|
+
D --> E[Report saved + webhooks]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### How CommitDiary Uses Stepper
|
|
85
|
+
|
|
86
|
+
- API calls Stepper to generate commit reports
|
|
87
|
+
- Stepper returns a jobId or cached result
|
|
88
|
+
- Stepper posts back to API callbacks for delivery and persistence
|
|
89
|
+
|
|
90
|
+
See API docs for endpoints and callbacks: [../api/README.md](../api/README.md)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🧩 Component Deep Dives
|
|
95
|
+
|
|
96
|
+
Stepper is modular. Explore each subsystem's technical documentation:
|
|
97
|
+
|
|
98
|
+
| Component | Purpose | Technical Details |
|
|
99
|
+
| :---------------- | :------------------------------- | :------------------------------------------ |
|
|
100
|
+
| **⚡ Cache** | Intelligent Redis strategies | [Cache Guide](./src/cache/README.md) |
|
|
101
|
+
| **🤖 Providers** | Adapter logic for different LLMs | [Provider Specs](./src/providers/README.md) |
|
|
102
|
+
| **📥 Queue** | Background processing & retries | [Queue System](./src/queue/README.md) |
|
|
103
|
+
| **📊 Metrics** | Prometheus & Observability | [Metrics Docs](./src/metrics/README.md) |
|
|
104
|
+
| **🛡️ Alerts** | Discord & error notifications | [Alerts System](./src/alerts/README.md) |
|
|
105
|
+
| **✅ Validation** | Zod-based strict output parsing | [Validation](./src/validation/README.md) |
|
|
106
|
+
|
|
107
|
+
### 🌟 Provider-Specific Optimizations
|
|
108
|
+
|
|
109
|
+
#### Google Gemini (Gemini 3 Models)
|
|
110
|
+
|
|
111
|
+
Stepper includes specialized optimizations for Google's Gemini 3 models based on [official Google prompting strategies](https://ai.google.dev/gemini-api/docs/prompting-strategies).
|
|
112
|
+
|
|
113
|
+
**Why Gemini is Different:**
|
|
114
|
+
- **XML-Structured Prompts**: Uses `<role>`, `<instructions>`, `<context>`, `<task>` tags for better model understanding
|
|
115
|
+
- **Query Parameter Authentication**: API key passed in URL (`?key=YOUR_KEY`) instead of headers
|
|
116
|
+
- **Locked Temperature**: Must use `temperature: 1.0` (Google requirement for optimal Gemini 3 performance)
|
|
117
|
+
- **Increased Token Limit**: 4096 tokens for detailed analysis
|
|
118
|
+
|
|
119
|
+
**Conditional Implementation:**
|
|
120
|
+
```typescript
|
|
121
|
+
if (provider === 'gemini') {
|
|
122
|
+
// Use XML-structured prompt
|
|
123
|
+
prompt = buildGeminiPrompt(input);
|
|
124
|
+
// Append API key to URL
|
|
125
|
+
endpoint = `${endpoint}?key=${apiKey}`;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This pattern allows each provider to have unique optimizations while maintaining clean code separation. See [Provider Documentation](./src/providers/README.md#-provider-specific-implementations) for details.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## ⚡ Quick Start (3 Minutes)
|
|
134
|
+
|
|
135
|
+
### 1. Install Dependencies
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pnpm install
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2. Configure Environment
|
|
142
|
+
|
|
143
|
+
Copy the example and add your API keys:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
cp .env.example .env
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 3. Spin Up Redis & Stepper
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Start Redis (Required)
|
|
153
|
+
docker run -d -p 6379:6379 redis:alpine
|
|
154
|
+
|
|
155
|
+
# Start in Dev Mode
|
|
156
|
+
pnpm dev
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🧭 Monorepo Notes
|
|
162
|
+
|
|
163
|
+
- API expects Stepper at STEPPER_URL (default http://localhost:3005)
|
|
164
|
+
- If running inside the monorepo, keep API and Stepper dev servers up
|
|
165
|
+
- See root setup guide: [../../README.md](../../README.md)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🛠️ Usage Modes
|
|
170
|
+
|
|
171
|
+
### Mode A: As a Library (Direct Integration)
|
|
172
|
+
|
|
173
|
+
Best for monorepos or when you want to avoid network overhead.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { enqueueReport, registerCallbacks, initStepper } from "ai-inference-stepper";
|
|
177
|
+
|
|
178
|
+
// Optional: programmatic config overrides (no env file required)
|
|
179
|
+
initStepper({
|
|
180
|
+
config: {
|
|
181
|
+
redis: { url: "redis://localhost:6379" },
|
|
182
|
+
},
|
|
183
|
+
providers: [
|
|
184
|
+
{ name: "gemini", enabled: true, apiKey: process.env.GEMINI_API_KEY, baseUrl: "https://generativelanguage.googleapis.com/v1", modelName: "gemini-pro", concurrency: 2, rateLimitRPM: 5 }
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// 1. Setup notification logic
|
|
189
|
+
registerCallbacks({
|
|
190
|
+
onSuccess: (id, provider, data) => console.log(`✅ Success via ${provider}`),
|
|
191
|
+
onFailure: (id, errors) => console.error("❌ Failed:", errors),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 2. Trigger a request (returns immediately if queued or cached)
|
|
195
|
+
const result = await enqueueReport({
|
|
196
|
+
commitSha: "abc123",
|
|
197
|
+
message: "Fix bug",
|
|
198
|
+
// ...other input
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Mode B: As an HTTP Service
|
|
203
|
+
|
|
204
|
+
Best for microservices or remote deployments (Render/Railway).
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Send a report generation request
|
|
208
|
+
curl -X POST http://localhost:3001/v1/reports \
|
|
209
|
+
-H "Content-Type: application/json" \
|
|
210
|
+
-d '{ "message": "Refactor API", "files": ["src/app.ts"] }'
|
|
211
|
+
|
|
212
|
+
#### CLI (npm)
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# One-off run
|
|
216
|
+
npx ai-inference-stepper
|
|
217
|
+
|
|
218
|
+
# Or install and run
|
|
219
|
+
npm i -g ai-inference-stepper
|
|
220
|
+
ai-inference-stepper
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Environment Setup (Service Mode)
|
|
224
|
+
|
|
225
|
+
Stepper reads config from environment variables. Use .env for local runs:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cp .env.example .env
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
If you install Stepper as a library, you can either:
|
|
232
|
+
- Provide env variables in your host app process (recommended for deployments), or
|
|
233
|
+
- Call `initStepper({ config, providers })` programmatically to override defaults.
|
|
234
|
+
|
|
235
|
+
# Response gives you a JobID to poll
|
|
236
|
+
# { "status": "queued", "jobId": "...", "statusUrl": "..." }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 🤝 Contributing & Community
|
|
242
|
+
|
|
243
|
+
We love contributors! Whether it's a bug report or a new provider adapter:
|
|
244
|
+
|
|
245
|
+
- **Issues**: Found a bug? [Raise an issue](https://github.com/samuel-adedigba/ai-inference-stepper/issues).
|
|
246
|
+
- **Pull Requests**: Have a fix? [Open a PR](https://github.com/samuel-adedigba/ai-inference-stepper/pulls).
|
|
247
|
+
|
|
248
|
+
If contributing inside the CommitDiary monorepo, start at [../../README.md](../../README.md) for the full workflow.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 📜 License
|
|
253
|
+
|
|
254
|
+
**Custom Attribution License**
|
|
255
|
+
|
|
256
|
+
You are free to use, modify, and distribute this software for personal or commercial projects, provided that:
|
|
257
|
+
|
|
258
|
+
1. **Credit is given**: You must attribute the original work to **Samuel Adedigba (@samuel-adedigba)**.
|
|
259
|
+
2. **Pull Requests**: Contributions and improvements are encouraged back to this core repository.
|
|
260
|
+
|
|
261
|
+
_For full details, see the [LICENSE](./LICENSE) file (MIT-based with attribution)._
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DiscordAlert {
|
|
2
|
+
title: string;
|
|
3
|
+
message: string;
|
|
4
|
+
severity: 'info' | 'warning' | 'critical';
|
|
5
|
+
metadata?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Send alert to Discord webhook
|
|
9
|
+
*/
|
|
10
|
+
export declare function sendDiscordAlert(alert: DiscordAlert): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Send provider failure alert
|
|
13
|
+
*/
|
|
14
|
+
export declare function alertProviderFailure(provider: string, errorCount: number, error?: unknown): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Send circuit breaker alert
|
|
17
|
+
*/
|
|
18
|
+
export declare function alertCircuitOpen(provider: string, lastError?: unknown): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=discord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.d.ts","sourceRoot":"","sources":["../../src/alerts/discord.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCzE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/G;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3F"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// `packages/stepper/src/alerts/discord.ts
|
|
2
|
+
import { logger } from '../logging.js';
|
|
3
|
+
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL;
|
|
4
|
+
/**
|
|
5
|
+
* Send alert to Discord webhook
|
|
6
|
+
*/
|
|
7
|
+
export async function sendDiscordAlert(alert) {
|
|
8
|
+
if (!DISCORD_WEBHOOK_URL) {
|
|
9
|
+
logger.debug('Discord webhook URL not configured, skipping alert');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const emoji = alert.severity === 'critical' ? '🚨' : alert.severity === 'warning' ? '⚠️' : 'ℹ️';
|
|
13
|
+
const content = `${emoji} **${alert.title}**\n${alert.message}`;
|
|
14
|
+
const embed = alert.metadata
|
|
15
|
+
? {
|
|
16
|
+
embeds: [
|
|
17
|
+
{
|
|
18
|
+
title: alert.title,
|
|
19
|
+
description: alert.message,
|
|
20
|
+
color: alert.severity === 'critical' ? 0xff0000 : alert.severity === 'warning' ? 0xffa500 : 0x00ff00,
|
|
21
|
+
fields: Object.entries(alert.metadata).map(([key, value]) => ({
|
|
22
|
+
name: key,
|
|
23
|
+
value: String(value),
|
|
24
|
+
inline: true,
|
|
25
|
+
})),
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
: { content };
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(DISCORD_WEBHOOK_URL, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json' },
|
|
35
|
+
body: JSON.stringify(embed),
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
logger.warn({ status: response.status }, 'Discord webhook request failed');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.error({ error }, 'Failed to send Discord alert');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Send provider failure alert
|
|
47
|
+
*/
|
|
48
|
+
export async function alertProviderFailure(provider, errorCount, error) {
|
|
49
|
+
const errorDetail = error instanceof Error ? error.message : String(error || 'Unknown error');
|
|
50
|
+
await sendDiscordAlert({
|
|
51
|
+
title: 'AI Provider Failure',
|
|
52
|
+
message: `Provider **${provider}** has failed ${errorCount} times\n\n**Error Details:**\n\`${errorDetail}\``,
|
|
53
|
+
severity: errorCount >= 5 ? 'critical' : 'warning',
|
|
54
|
+
metadata: { provider, errorCount, error: errorDetail, timestamp: new Date().toISOString() },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Send circuit breaker alert
|
|
59
|
+
*/
|
|
60
|
+
export async function alertCircuitOpen(provider, lastError) {
|
|
61
|
+
const errorDetail = lastError ? (lastError instanceof Error ? lastError.message : String(lastError)) : undefined;
|
|
62
|
+
const message = `Circuit breaker for provider **${provider}** is now OPEN${errorDetail ? `\n\n**Last Error:**\n\`${errorDetail}\`` : ''}`;
|
|
63
|
+
await sendDiscordAlert({
|
|
64
|
+
title: 'Circuit Breaker Opened',
|
|
65
|
+
message,
|
|
66
|
+
severity: 'critical',
|
|
67
|
+
metadata: { provider, lastError: errorDetail, timestamp: new Date().toISOString() },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../src/alerts/discord.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
import { CacheEntry, ReportOutput, ProviderAttemptMeta } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Get or create Redis client
|
|
5
|
+
*/
|
|
6
|
+
export declare function getRedisClient(): Redis;
|
|
7
|
+
/**
|
|
8
|
+
* Build cache key for a report
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildCacheKey(userId: string, commitSha: string, templateHash?: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Get cache entry: Looks up a report in the cache using its key. Like asking: "Do we already have a copy of this report?"
|
|
13
|
+
*/
|
|
14
|
+
export declare function getReportCache(key: string): Promise<CacheEntry | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Set dehydrated cache entry (job enqueued)
|
|
17
|
+
*/
|
|
18
|
+
export declare function setDehydrated(key: string, jobId: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Set hydrated cache entry (report generated)
|
|
21
|
+
* Stores a completed report in the cache. This is the "meal is ready!" moment.
|
|
22
|
+
*/
|
|
23
|
+
export declare function setHydrated(key: string, result: ReportOutput, providersAttempted: ProviderAttemptMeta[], fallback?: boolean, ttl?: number): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Mark cache entry as failed: Records that report generation failed completely. All AI providers were tried and none worked.
|
|
26
|
+
*/
|
|
27
|
+
export declare function markFailed(key: string, errorMessage: string, providersAttempted: ProviderAttemptMeta[]): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if hydrated entry is fresh
|
|
30
|
+
*/
|
|
31
|
+
export declare function isHydratedFresh(entry: CacheEntry): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Check if entry is stale but usable for stale-while-revalidate: Checks if a report is old but still usable while a new one is being generated in the background.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isStaleButUsable(entry: CacheEntry): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Delete cache entry: Removes the record from Redis immediately.
|
|
38
|
+
* Call this once the backend has successfully saved the report to its database.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deleteCacheEntry(key: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Close Redis connection (for graceful shutdown)
|
|
43
|
+
*/
|
|
44
|
+
export declare function closeRedis(): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=redisCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redisCache.d.ts","sourceRoot":"","sources":["../../src/cache/redisCache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,SAAS,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAO5E;;GAEG;AACH,wBAAgB,cAAc,IAAI,KAAK,CAwBtC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAE,MAAkB,GAAG,MAAM,CAEzG;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAa5E;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB7E;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC7B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,EACpB,kBAAkB,EAAE,mBAAmB,EAAE,EACzC,QAAQ,GAAE,OAAe,EACzB,GAAG,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5H;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAQ1D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAK3D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASjE;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhD"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// packages/stepper/src/cache/redisCache.ts
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { logger } from '../logging.js';
|
|
5
|
+
import { sendDiscordAlert } from '../alerts/discord.js';
|
|
6
|
+
let redisClient = null;
|
|
7
|
+
/**
|
|
8
|
+
* Get or create Redis client
|
|
9
|
+
*/
|
|
10
|
+
export function getRedisClient() {
|
|
11
|
+
if (!redisClient) {
|
|
12
|
+
redisClient = new Redis(config.redis.url, {
|
|
13
|
+
maxRetriesPerRequest: null, // Required by BullMQ for blocking operations
|
|
14
|
+
enableReadyCheck: true,
|
|
15
|
+
lazyConnect: false,
|
|
16
|
+
});
|
|
17
|
+
redisClient.on('error', (err) => {
|
|
18
|
+
logger.error({ err }, 'Redis client error');
|
|
19
|
+
void sendDiscordAlert({
|
|
20
|
+
title: 'Redis Connection Error',
|
|
21
|
+
message: `Redis client encountered an error: ${err.message}`,
|
|
22
|
+
severity: 'critical',
|
|
23
|
+
metadata: { error: err.message, timestamp: new Date().toISOString() }
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
redisClient.on('connect', () => {
|
|
27
|
+
logger.info('Redis client connected');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return redisClient;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build cache key for a report
|
|
34
|
+
*/
|
|
35
|
+
export function buildCacheKey(userId, commitSha, templateHash = 'default') {
|
|
36
|
+
return `${config.redis.keyPrefix}report:${userId}:${commitSha}:${templateHash}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get cache entry: Looks up a report in the cache using its key. Like asking: "Do we already have a copy of this report?"
|
|
40
|
+
*/
|
|
41
|
+
export async function getReportCache(key) {
|
|
42
|
+
const redis = getRedisClient();
|
|
43
|
+
try {
|
|
44
|
+
const data = await redis.get(key);
|
|
45
|
+
if (!data)
|
|
46
|
+
return null;
|
|
47
|
+
const entry = JSON.parse(data);
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error({ error, key }, 'Failed to get cache entry');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set dehydrated cache entry (job enqueued)
|
|
57
|
+
*/
|
|
58
|
+
export async function setDehydrated(key, jobId) {
|
|
59
|
+
const redis = getRedisClient();
|
|
60
|
+
const entry = {
|
|
61
|
+
status: 'dehydrated', //Mark it as "in progress"
|
|
62
|
+
jobId,
|
|
63
|
+
timestamps: {
|
|
64
|
+
created: new Date().toISOString(),
|
|
65
|
+
updated: new Date().toISOString(),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
await redis.setex(key, config.cache.ttlSeconds, JSON.stringify(entry)); //Store in Redis with expiration time Default is 604,800 seconds = 7 days
|
|
70
|
+
logger.debug({ key, jobId }, 'Created dehydrated cache entry');
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.error({ error, key }, 'Failed to set dehydrated cache');
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Set hydrated cache entry (report generated)
|
|
79
|
+
* Stores a completed report in the cache. This is the "meal is ready!" moment.
|
|
80
|
+
*/
|
|
81
|
+
export async function setHydrated(key, result, providersAttempted, fallback = false, ttl // How long to keep it 604800 (7 days in seconds)
|
|
82
|
+
) {
|
|
83
|
+
const redis = getRedisClient();
|
|
84
|
+
const entry = {
|
|
85
|
+
status: 'hydrated', // Report is complete
|
|
86
|
+
result,
|
|
87
|
+
providersAttempted,
|
|
88
|
+
fallback,
|
|
89
|
+
timestamps: {
|
|
90
|
+
created: new Date().toISOString(),
|
|
91
|
+
updated: new Date().toISOString(),
|
|
92
|
+
},
|
|
93
|
+
ttl: ttl || config.cache.ttlSeconds,
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
await redis.setex(key, ttl || config.cache.ttlSeconds, JSON.stringify(entry));
|
|
97
|
+
logger.debug({ key, fallback }, 'Stored hydrated cache entry');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.error({ error, key }, 'Failed to set hydrated cache');
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mark cache entry as failed: Records that report generation failed completely. All AI providers were tried and none worked.
|
|
106
|
+
*/
|
|
107
|
+
export async function markFailed(key, errorMessage, providersAttempted) {
|
|
108
|
+
const redis = getRedisClient();
|
|
109
|
+
const entry = {
|
|
110
|
+
status: 'failed',
|
|
111
|
+
error: errorMessage,
|
|
112
|
+
providersAttempted,
|
|
113
|
+
timestamps: {
|
|
114
|
+
created: new Date().toISOString(),
|
|
115
|
+
updated: new Date().toISOString(),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
await redis.setex(key, 3600, JSON.stringify(entry)); // Keep failed for 1 hour
|
|
120
|
+
logger.debug({ key }, 'Marked cache entry as failed');
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
logger.error({ error, key }, 'Failed to mark cache as failed');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if hydrated entry is fresh
|
|
128
|
+
*/
|
|
129
|
+
export function isHydratedFresh(entry) {
|
|
130
|
+
if (entry.status !== 'hydrated')
|
|
131
|
+
return false; //Is this a complete report?
|
|
132
|
+
const updatedAt = new Date(entry.timestamps.updated).getTime();
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const ageSeconds = (now - updatedAt) / 1000; //How old is this report?
|
|
135
|
+
return ageSeconds < config.cache.staleThresholdSeconds; //Is it younger than 24 hours?
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if entry is stale but usable for stale-while-revalidate: Checks if a report is old but still usable while a new one is being generated in the background.
|
|
139
|
+
*/
|
|
140
|
+
export function isStaleButUsable(entry) {
|
|
141
|
+
if (entry.status !== 'hydrated')
|
|
142
|
+
return false;
|
|
143
|
+
if (!config.cache.enableStaleWhileRevalidate)
|
|
144
|
+
return false;
|
|
145
|
+
return !isHydratedFresh(entry);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Delete cache entry: Removes the record from Redis immediately.
|
|
149
|
+
* Call this once the backend has successfully saved the report to its database.
|
|
150
|
+
*/
|
|
151
|
+
export async function deleteCacheEntry(key) {
|
|
152
|
+
const redis = getRedisClient();
|
|
153
|
+
try {
|
|
154
|
+
await redis.del(key);
|
|
155
|
+
logger.debug({ key }, 'Deleted cache entry after successful delivery');
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
logger.error({ error, key }, 'Failed to delete cache entry');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Close Redis connection (for graceful shutdown)
|
|
163
|
+
*/
|
|
164
|
+
export async function closeRedis() {
|
|
165
|
+
if (redisClient) {
|
|
166
|
+
await redisClient.quit();
|
|
167
|
+
redisClient = null;
|
|
168
|
+
logger.info('Redis client disconnected');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=redisCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redisCache.js","sourceRoot":"","sources":["../../src/cache/redisCache.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startServer } from './server/start.js';
|
|
3
|
+
import { logger } from './logging.js';
|
|
4
|
+
startServer().catch((error) => {
|
|
5
|
+
logger.error({ error }, 'Failed to start Stepper server');
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC5B,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,gCAAgC,CAAC,CAAC;IAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StepperConfig } from './types.js';
|
|
2
|
+
export declare function loadConfig(): StepperConfig;
|
|
3
|
+
export declare function createConfig(overrides?: Partial<StepperConfig>): StepperConfig;
|
|
4
|
+
export declare let config: StepperConfig;
|
|
5
|
+
export declare function applyConfigOverrides(overrides?: Partial<StepperConfig>): StepperConfig;
|
|
6
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAyD3D,wBAAgB,UAAU,IAAI,aAAa,CAoI1C;AA2DD,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAM9E;AAED,eAAO,IAAI,MAAM,eAAe,CAAC;AAEjC,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAMtF"}
|