• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

semperai / amica-personas / 18364971839

09 Oct 2025 03:52AM UTC coverage: 27.166% (-0.5%) from 27.703%
18364971839

push

github

web-flow
Merge pull request #39 from kasumi-1/etcimprov-fixed

Etcimprov fixed

1425 of 1807 branches covered (78.86%)

Branch coverage included in aggregate %.

1982 of 4805 new or added lines in 67 files covered. (41.25%)

40 existing lines in 19 files now uncovered.

7729 of 31889 relevant lines covered (24.24%)

1074.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/api/src/server.ts
1
import * as fs from "node:fs";
×
2
import * as Sentry from "@sentry/node";
×
3
import cors from "cors";
×
4
import express, { type Express, type Request, type Response } from "express";
×
5
import helmet from "helmet";
×
6
import multer from "multer";
×
7
import * as promClient from "prom-client";
×
8
import {
×
9
  apiCallDuration,
10
  apiCallErrors,
11
  creditsRemainingGauge,
12
  creditsUsedCounter,
13
  endpointHealthGauge,
14
  endpointResponseTimeGauge,
15
  outOfCreditsCounter,
16
  streamingEventsCounter,
17
} from "@/metrics";
18
import authorizationCheck from "@/middleware/authorizationCheck";
×
19
import errorHandler from "@/middleware/errorHandler";
×
20
import metricsMiddleware from "@/middleware/metricsMiddleware";
×
21
import rateLimiter from "@/middleware/rateLimiter";
×
22
import requestLogger from "@/middleware/requestLogger";
×
23
import { env } from "@/utils/envConfig";
×
24
import { fetchWithRetry } from "@/utils/fetchWithRetry";
×
25
import { createLogger, logError } from "@/utils/logger";
×
26

27
const logger = createLogger({ component: "server" });
×
28
const app: Express = express();
×
29
const upload = multer({ dest: "uploads/" });
×
30

31
// Set the application to trust the reverse proxy (for ip address)
32
app.set("trust proxy", true);
×
33

34
// Middlewares
35
app.use(express.json());
×
36
app.use(express.urlencoded({ extended: true }));
×
37
app.use(cors());
×
38
app.use(helmet());
×
39
app.use(rateLimiter);
×
40
app.use(metricsMiddleware);
×
41
app.use(requestLogger);
×
42

43
const sendEvent = (res: Response, data: any, endpoint: string) => {
×
44
  try {
×
45
    for (const item of ["id", "object", "model", "system_fingerprint", "x_groq"]) {
×
46
      if (typeof data[item] !== "undefined") {
×
47
        delete data[item];
×
48
      }
×
49
    }
×
50

51
    const json = JSON.stringify(data);
×
52
    res.write(`data: ${json}\n\n`);
×
53
    streamingEventsCounter.inc({ endpoint });
×
54
  } catch (error) {
×
55
    logError(error as Error, { context: "sendEvent" });
×
56
  }
×
57
};
×
58

59
app.get("/", (_req: Request, res: Response) => {
×
60
  return res.send("Hey, Amica!");
×
61
});
×
62

63
// Kubernetes liveness probe - checks if the app is alive
64
app.get("/livez", (_req: Request, res: Response) => {
×
65
  res.status(200).json({ status: "ok" });
×
66
});
×
67

68
// Kubernetes readiness probe - checks if the app is ready to serve traffic
69
app.get("/readyz", async (_req: Request, res: Response) => {
×
70
  try {
×
71
    // Check database connectivity
72
    const { connect } = await import("ts-postgres");
×
73
    const client = await connect();
×
74
    await client.query("SELECT 1");
×
75
    await client.end();
×
76

77
    res.status(200).json({ status: "ready" });
×
78
  } catch (error) {
×
79
    logError(error as Error, { context: "readiness_check" });
×
80
    res.status(503).json({ status: "not ready", error: "Database unavailable" });
×
81
  }
×
82
});
×
83

84
app.get("/health-check", async (_req: Request, res: Response) => {
×
85
  try {
×
86
    const health = {
×
87
      status: "healthy",
×
88
      timestamp: new Date().toISOString(),
×
89
      uptime: process.uptime(),
×
90
      environment: env.NODE_ENV,
×
91
      version: "1.0.14",
×
92
      dependencies: {
×
93
        openai_chat: { status: "unknown", responseTime: 0 },
×
94
        openai_whisper: { status: "unknown", responseTime: 0 },
×
95
        fish_tts: { status: "unknown", responseTime: 0 },
×
96
        database: { status: "unknown", responseTime: 0 },
×
97
      },
×
98
    };
×
99

100
    // Quick dependency checks (with timeouts to avoid blocking)
101
    const checkPromises = [
×
102
      // Check OpenAI Chat
103
      (async () => {
×
104
        const start = Date.now();
×
105
        try {
×
106
          await fetch(env.OPENAI_CHAT_URL, {
×
107
            method: "HEAD",
×
108
            signal: AbortSignal.timeout(2000),
×
109
          });
×
110
          health.dependencies.openai_chat.status = "healthy";
×
111
          health.dependencies.openai_chat.responseTime = Date.now() - start;
×
112
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "openai_chat" }, 1);
×
113
          endpointResponseTimeGauge.set({ dependency: "openai_chat" }, health.dependencies.openai_chat.responseTime);
×
114
        } catch {
×
115
          health.dependencies.openai_chat.status = "unhealthy";
×
116
          health.dependencies.openai_chat.responseTime = Date.now() - start;
×
117
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "openai_chat" }, 0);
×
118
          endpointResponseTimeGauge.set({ dependency: "openai_chat" }, health.dependencies.openai_chat.responseTime);
×
119
        }
×
120
      })(),
×
121

122
      // Check OpenAI Whisper
123
      (async () => {
×
124
        const start = Date.now();
×
125
        try {
×
126
          await fetch(env.OPENAI_WHISPER_URL, {
×
127
            method: "HEAD",
×
128
            signal: AbortSignal.timeout(2000),
×
129
          });
×
130
          health.dependencies.openai_whisper.status = "healthy";
×
131
          health.dependencies.openai_whisper.responseTime = Date.now() - start;
×
132
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "openai_whisper" }, 1);
×
133
          endpointResponseTimeGauge.set(
×
134
            { dependency: "openai_whisper" },
×
135
            health.dependencies.openai_whisper.responseTime,
×
136
          );
×
137
        } catch {
×
138
          health.dependencies.openai_whisper.status = "unhealthy";
×
139
          health.dependencies.openai_whisper.responseTime = Date.now() - start;
×
140
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "openai_whisper" }, 0);
×
141
          endpointResponseTimeGauge.set(
×
142
            { dependency: "openai_whisper" },
×
143
            health.dependencies.openai_whisper.responseTime,
×
144
          );
×
145
        }
×
146
      })(),
×
147

148
      // Check Fish TTS
149
      (async () => {
×
150
        const start = Date.now();
×
151
        try {
×
152
          await fetch(env.FISH_URL, {
×
153
            method: "HEAD",
×
154
            signal: AbortSignal.timeout(2000),
×
155
          });
×
156
          health.dependencies.fish_tts.status = "healthy";
×
157
          health.dependencies.fish_tts.responseTime = Date.now() - start;
×
158
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "fish_tts" }, 1);
×
159
          endpointResponseTimeGauge.set({ dependency: "fish_tts" }, health.dependencies.fish_tts.responseTime);
×
160
        } catch {
×
161
          health.dependencies.fish_tts.status = "unhealthy";
×
162
          health.dependencies.fish_tts.responseTime = Date.now() - start;
×
163
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "fish_tts" }, 0);
×
164
          endpointResponseTimeGauge.set({ dependency: "fish_tts" }, health.dependencies.fish_tts.responseTime);
×
165
        }
×
166
      })(),
×
167

168
      // Check Database
169
      (async () => {
×
170
        const start = Date.now();
×
171
        try {
×
172
          const { connect } = await import("ts-postgres");
×
173
          const client = await connect();
×
174
          await client.query("SELECT 1");
×
175
          await client.end();
×
176
          health.dependencies.database.status = "healthy";
×
177
          health.dependencies.database.responseTime = Date.now() - start;
×
178
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "database" }, 1);
×
179
          endpointResponseTimeGauge.set({ dependency: "database" }, health.dependencies.database.responseTime);
×
180
        } catch {
×
181
          health.dependencies.database.status = "unhealthy";
×
182
          health.dependencies.database.responseTime = Date.now() - start;
×
183
          endpointHealthGauge.set({ endpoint: "/health-check", dependency: "database" }, 0);
×
184
          endpointResponseTimeGauge.set({ dependency: "database" }, health.dependencies.database.responseTime);
×
185
        }
×
186
      })(),
×
187
    ];
×
188

189
    // Wait for all checks with timeout
190
    await Promise.race([Promise.allSettled(checkPromises), new Promise((resolve) => setTimeout(resolve, 3000))]);
×
191

192
    // Determine overall status
193
    const hasUnhealthy = Object.values(health.dependencies).some((dep) => dep.status === "unhealthy");
×
194
    if (hasUnhealthy) {
×
195
      health.status = "degraded";
×
196
      return res.status(503).json(health);
×
197
    }
×
198

199
    return res.json(health);
×
200
  } catch (e) {
×
201
    logError(e as Error, { context: "health_check" });
×
202
    return res.status(500).json({
×
203
      status: "unhealthy",
×
204
      error: "Internal error",
×
205
      timestamp: new Date().toISOString(),
×
206
    });
×
207
  }
×
208
});
×
209

210
app.get("/metrics", async (req: Request, res: Response) => {
×
211
  res.setHeader("Content-Type", promClient.register.contentType);
×
212
  res.end(await promClient.register.metrics());
×
213
});
×
214

215
// Monitoring dashboard endpoint
216
app.get("/monitoring", async (_req: Request, res: Response) => {
×
217
  try {
×
218
    const metrics = await promClient.register.getMetricsAsJSON();
×
219
    const metricsMap = new Map(metrics.map((m) => [m.name, m]));
×
220

221
    const dashboard = {
×
222
      timestamp: new Date().toISOString(),
×
223
      uptime: process.uptime(),
×
224
      version: "1.0.14",
×
225
      environment: env.NODE_ENV,
×
226
      health: {
×
227
        dependencies: {
×
228
          openai_chat:
×
229
            metricsMap.get("endpoint_health_status")?.values.find((v) => v.labels.dependency === "openai_chat")
×
230
              ?.value === 1
×
231
              ? "healthy"
×
232
              : "unhealthy",
×
233
          openai_whisper:
×
234
            metricsMap.get("endpoint_health_status")?.values.find((v) => v.labels.dependency === "openai_whisper")
×
235
              ?.value === 1
×
236
              ? "healthy"
×
237
              : "unhealthy",
×
238
          fish_tts:
×
239
            metricsMap.get("endpoint_health_status")?.values.find((v) => v.labels.dependency === "fish_tts")?.value ===
×
240
            1
×
241
              ? "healthy"
×
242
              : "unhealthy",
×
243
          database:
×
244
            metricsMap.get("endpoint_health_status")?.values.find((v) => v.labels.dependency === "database")?.value ===
×
245
            1
×
246
              ? "healthy"
×
247
              : "unhealthy",
×
248
        },
×
249
        responseTimes: {
×
250
          openai_chat:
×
251
            metricsMap.get("endpoint_response_time_ms")?.values.find((v) => v.labels.dependency === "openai_chat")
×
252
              ?.value || 0,
×
253
          openai_whisper:
×
254
            metricsMap.get("endpoint_response_time_ms")?.values.find((v) => v.labels.dependency === "openai_whisper")
×
255
              ?.value || 0,
×
256
          fish_tts:
×
257
            metricsMap.get("endpoint_response_time_ms")?.values.find((v) => v.labels.dependency === "fish_tts")
×
258
              ?.value || 0,
×
259
          database:
×
260
            metricsMap.get("endpoint_response_time_ms")?.values.find((v) => v.labels.dependency === "database")
×
261
              ?.value || 0,
×
262
        },
×
263
      },
×
264
      api: {
×
265
        totalRequests: metricsMap.get("api_requests_total")?.values.reduce((sum, v) => sum + (v.value || 0), 0) || 0,
×
266
        creditsUsed: metricsMap.get("credits_used_total")?.values.reduce((sum, v) => sum + (v.value || 0), 0) || 0,
×
267
        outOfCreditsEvents:
×
268
          metricsMap.get("out_of_credits_total")?.values.reduce((sum, v) => sum + (v.value || 0), 0) || 0,
×
269
        apiCallErrors: metricsMap.get("api_call_errors_total")?.values.reduce((sum, v) => sum + (v.value || 0), 0) || 0,
×
270
      },
×
271
      endpoints: {
×
272
        "/v1/chat/completions": {
×
273
          requests:
×
274
            metricsMap.get("api_requests_total")?.values.find((v) => v.labels.endpoint === "/v1/chat/completions")
×
275
              ?.value || 0,
×
276
          credits:
×
277
            metricsMap.get("credits_used_total")?.values.find((v) => v.labels.endpoint === "/v1/chat/completions")
×
278
              ?.value || 0,
×
279
          errors:
×
280
            metricsMap.get("api_call_errors_total")?.values.find((v) => v.labels.endpoint === "/v1/chat/completions")
×
281
              ?.value || 0,
×
282
        },
×
283
        "/v1/audio/speech": {
×
284
          requests:
×
285
            metricsMap.get("api_requests_total")?.values.find((v) => v.labels.endpoint === "/v1/audio/speech")?.value ||
×
286
            0,
×
287
          credits:
×
288
            metricsMap.get("credits_used_total")?.values.find((v) => v.labels.endpoint === "/v1/audio/speech")?.value ||
×
289
            0,
×
290
          errors:
×
291
            metricsMap.get("api_call_errors_total")?.values.find((v) => v.labels.endpoint === "/v1/audio/speech")
×
292
              ?.value || 0,
×
293
        },
×
294
        "/v1/audio/transcriptions": {
×
295
          requests:
×
296
            metricsMap.get("api_requests_total")?.values.find((v) => v.labels.endpoint === "/v1/audio/transcriptions")
×
297
              ?.value || 0,
×
298
          credits:
×
299
            metricsMap.get("credits_used_total")?.values.find((v) => v.labels.endpoint === "/v1/audio/transcriptions")
×
300
              ?.value || 0,
×
301
          errors:
×
302
            metricsMap
×
303
              .get("api_call_errors_total")
×
304
              ?.values.find((v) => v.labels.endpoint === "/v1/audio/transcriptions")?.value || 0,
×
305
        },
×
306
      },
×
307
    };
×
308

309
    return res.json(dashboard);
×
310
  } catch (e) {
×
311
    logError(e as Error, { context: "monitoring_dashboard" });
×
312
    return res.status(500).json({ error: "Failed to generate monitoring dashboard" });
×
313
  }
×
314
});
×
315

316
// TODO we want to fallover between list of providers
317
// e.g. if groq fails, disable it for 5 minutes and use fireworks
318
// then re-enable groq upon test success
319
app.post("/v1/chat/completions", [authorizationCheck(env.CREDITS_PER_CHAT)], async (req: Request, res: Response) => {
×
320
  try {
×
321
    const accountInfo = res.locals.accountInfo;
×
322
    const tier = accountInfo.tier || "unknown";
×
323
    const userId = accountInfo.userId || "anonymous";
×
324

325
    logger.info({ accountInfo, requestId: req.id }, "Processing chat completion request");
×
326

327
    // Track credits
328
    creditsRemainingGauge.set({ user_id: userId, tier }, accountInfo.credits);
×
329

330
    if (accountInfo.credits < 0) {
×
331
      outOfCreditsCounter.inc({ tier, endpoint: "/v1/chat/completions" });
×
332
      res.writeHead(200, {
×
333
        "Content-Type": "text/event-stream",
×
334
        "Cache-Control": "no-cache",
×
335
        Connection: "keep-alive",
×
336
      });
×
337

338
      const json = {
×
339
        created: +new Date(),
×
340
        choices: [
×
341
          {
×
342
            index: 0,
×
343
            delta: {
×
344
              content: "[sad] You have run out of credits. Please top up your account to continue chatting.",
×
345
            },
×
346
            logprobs: null,
×
347
            finish_reason: null,
×
348
          },
×
349
        ],
×
350
      };
×
351

352
      sendEvent(res, json, "/v1/chat/completions");
×
353

354
      // Close the connection when the client disconnects
355
      req.on("close", () => {
×
356
        try {
×
357
          res.end();
×
358
        } catch (error) {
×
359
          logError(error as Error, { context: "chat_completion_close", requestId: req.id });
×
360
        }
×
361
      });
×
362

363
      return;
×
364
    }
×
365

366
    const apiUrl = env.OPENAI_CHAT_URL;
×
367
    const model = env.OPENAI_CHAT_MODEL;
×
368
    const headers: Record<string, string> = {
×
369
      "Content-Type": "application/json",
×
370
      Accept: "application/json",
×
371
      "Accept-Encoding": "gzip, deflate, br, zstd",
×
372
      Authorization: `Bearer ${env.OPENAI_CHAT_API_KEY}`,
×
373
    };
×
374

375
    const messages: {
×
376
      role: string;
377
      content: string;
378
    }[] = [];
×
379

380
    try {
×
381
      for (const message of req.body.messages) {
×
382
        // Skip messages with empty content
NEW
383
        if (!message.content || typeof message.content !== "string" || message.content.trim() === "") {
×
NEW
384
          logger.info("Skipping empty message", { role: message.role, requestId: req.id });
×
NEW
385
          continue;
×
NEW
386
        }
×
387
        messages.push({
×
388
          role: message.role,
×
389
          content: message.content,
×
390
        });
×
391
      }
×
392
    } catch (error) {
×
393
      logError(error as Error, { context: "parse_messages", requestId: req.id });
×
394
      return res.status(400).send("Bad Request");
×
395
    }
×
396

NEW
397
    if (messages.length === 0) {
×
NEW
398
      logError(new Error("No valid messages in request"), { context: "validate_messages", requestId: req.id });
×
NEW
399
      return res.status(400).send("Bad Request: No valid messages");
×
NEW
400
    }
×
401

402
    const body = {
×
403
      stream: true,
×
404
      model,
×
405
      max_tokens: 2000,
×
406
      messages,
×
407
    };
×
408

409
    try {
×
NEW
410
      logger.info("Sending chat request", {
×
NEW
411
        component: "server",
×
NEW
412
        model,
×
NEW
413
        messageCount: messages.length,
×
NEW
414
        requestId: req.id,
×
NEW
415
      });
×
416

417
      // Track credits usage
418
      creditsUsedCounter.inc({ tier, endpoint: "/v1/chat/completions", user_id: userId }, env.CREDITS_PER_CHAT);
×
419

420
      const startTime = Date.now();
×
421
      const response = await fetchWithRetry(apiUrl, {
×
422
        method: "POST",
×
423
        headers,
×
424
        body: JSON.stringify(body),
×
425
        signal: AbortSignal.timeout(env.TIMEOUT_CHAT),
×
426
      });
×
427

428
      const reader = response.body?.getReader();
×
429
      if (!reader) {
×
430
        throw new Error("Response body is not readable");
×
431
      }
×
432

433
      res.writeHead(200, {
×
434
        "Content-Type": "text/event-stream",
×
435
        "Cache-Control": "no-cache",
×
436
        Connection: "keep-alive",
×
437
      });
×
438

439
      const decoder = new TextDecoder("utf-8");
×
440

441
      let combined = "";
×
442
      while (true) {
×
443
        const { done, value } = await reader.read();
×
444
        if (done) {
×
445
          res.write("event: close\n");
×
446
          res.end();
×
447
          break;
×
448
        }
×
449

450
        const data = decoder.decode(value);
×
451
        const chunks = data.split("data:").filter((val) => !!val && val.trim() !== "[DONE]");
×
452

453
        for (const chunk of chunks) {
×
454
          // skip comments
455
          if (chunk.length > 0 && chunk[0] === ":") {
×
456
            continue;
×
457
          }
×
458
          combined += chunk;
×
459

460
          try {
×
461
            const json = JSON.parse(combined);
×
462
            combined = "";
×
463
            sendEvent(res, json, "/v1/chat/completions");
×
464
          } catch {
×
465
            // its normal for the last chunk to not be a full json object
466
          }
×
467
        }
×
468
      }
×
469

470
      // Track API call duration
471
      const duration = (Date.now() - startTime) / 1000;
×
472
      apiCallDuration.observe({ service: "openai", endpoint: "/v1/chat/completions", status: "success" }, duration);
×
473
    } catch (error) {
×
474
      apiCallErrors.inc({ service: "openai", endpoint: "/v1/chat/completions", error_type: (error as Error).name });
×
475
      logError(error as Error, { context: "chat_streaming", requestId: req.id });
×
476
      res.end();
×
477
    }
×
478

479
    // Close the connection when the client disconnects
480
    req.on("close", () => {
×
481
      try {
×
482
        res.end();
×
483
      } catch (error) {
×
484
        logError(error as Error, { context: "chat_stream_close", requestId: req.id });
×
485
      }
×
486
    });
×
487
  } catch {
×
488
    return res.status(500).json({ error: "Internal error" });
×
489
  }
×
490
});
×
491

NEW
492
const outOfCreditsAudios: Buffer[] = [];
×
NEW
493
for (let i = 1; i <= 5; i++) {
×
NEW
494
  const path = `./assets/outofcredits${i}.mp3`;
×
NEW
495
  if (fs.existsSync(path)) {
×
NEW
496
    outOfCreditsAudios.push(Buffer.from(fs.readFileSync(path)));
×
NEW
497
  }
×
NEW
498
}
×
499

500
app.post("/v1/audio/speech", [authorizationCheck(env.CREDITS_PER_TTS)], async (req: Request, res: Response) => {
×
501
  try {
×
502
    const accountInfo = res.locals.accountInfo;
×
503
    const tier = accountInfo.tier || "unknown";
×
504
    const userId = accountInfo.userId || "anonymous";
×
505

506
    creditsRemainingGauge.set({ user_id: userId, tier }, accountInfo.credits);
×
507

508
    if (accountInfo.credits < 0) {
×
509
      outOfCreditsCounter.inc({ tier, endpoint: "/v1/audio/speech" });
×
NEW
510
      if (outOfCreditsAudios.length > 0) {
×
NEW
511
        res.setHeader("Content-Type", "audio/mpeg");
×
NEW
512
        res.send(outOfCreditsAudios[Math.floor(Math.random() * outOfCreditsAudios.length)]);
×
NEW
513
      } else {
×
NEW
514
        res.status(402).json({ error: "Out of credits" });
×
NEW
515
      }
×
516
      return;
×
517
    }
×
518

519
    creditsUsedCounter.inc({ tier, endpoint: "/v1/audio/speech", user_id: userId }, env.CREDITS_PER_TTS);
×
520

521
    const apiUrl = env.FISH_URL;
×
522
    const referenceId = env.FISH_MODEL;
×
523

524
    // parse the request body
525
    const text = req.body.input;
×
526
    if (!text) {
×
527
      return res.status(400).json({ error: "No input text provided" });
×
528
    }
×
529

530
    const headers: Record<string, string> = {
×
531
      "Content-Type": "application/json",
×
532
      Authorization: `Bearer ${env.FISH_API_KEY}`,
×
533
    };
×
534

535
    const startTime = Date.now();
×
536
    const response = await fetchWithRetry(apiUrl, {
×
537
      method: "POST",
×
538
      headers,
×
539
      body: JSON.stringify({
×
540
        text,
×
541
        reference_id: referenceId,
×
542
        format: "mp3",
×
543
      }),
×
544
      signal: AbortSignal.timeout(env.TIMEOUT_FISH),
×
545
    });
×
546

547
    // Set headers before streaming
548
    res.setHeader("Content-Type", "audio/mpeg");
×
NEW
549
    if (response.headers.get("Content-Length")) {
×
NEW
550
      res.setHeader("Content-Length", response.headers.get("Content-Length")!);
×
NEW
551
    }
×
552

553
    // Stream the response directly instead of buffering
NEW
554
    const reader = response.body?.getReader();
×
NEW
555
    if (!reader) {
×
NEW
556
      throw new Error("Response body is not readable");
×
NEW
557
    }
×
558

NEW
559
    try {
×
NEW
560
      while (true) {
×
NEW
561
        const { done, value } = await reader.read();
×
NEW
562
        if (done) {
×
NEW
563
          break;
×
NEW
564
        }
×
NEW
565
        res.write(Buffer.from(value));
×
NEW
566
      }
×
NEW
567
      res.end();
×
568

NEW
569
      const duration = (Date.now() - startTime) / 1000;
×
NEW
570
      apiCallDuration.observe({ service: "fish", endpoint: "/v1/audio/speech", status: "success" }, duration);
×
NEW
571
    } catch (error) {
×
NEW
572
      apiCallErrors.inc({ service: "fish", endpoint: "/v1/audio/speech", error_type: (error as Error).name });
×
NEW
573
      logError(error as Error, { context: "tts_streaming", requestId: req.id });
×
NEW
574
      throw error;
×
NEW
575
    }
×
576
  } catch {
×
577
    return res.status(500).json({ error: "Internal error" });
×
578
  }
×
579
});
×
580

581
// TODO fireworks returns 400 for this, only groq works?
582
// figure out how to enable fireworks
583
app.post(
×
584
  "/v1/audio/transcriptions",
×
585
  [upload.single("file"), authorizationCheck(env.CREDITS_PER_STT)],
×
586
  async (req: Request, res: Response) => {
×
587
    try {
×
588
      const accountInfo = res.locals.accountInfo;
×
589
      const tier = accountInfo.tier || "unknown";
×
590
      const userId = accountInfo.userId || "anonymous";
×
591

592
      creditsRemainingGauge.set({ user_id: userId, tier }, accountInfo.credits);
×
593

594
      if (accountInfo.credits < 0) {
×
595
        outOfCreditsCounter.inc({ tier, endpoint: "/v1/audio/transcriptions" });
×
596
        return res.json({
×
597
          text: "You have run out of credits. Please top up your account to continue using this service.",
×
598
        });
×
599
      }
×
600

601
      creditsUsedCounter.inc({ tier, endpoint: "/v1/audio/transcriptions", user_id: userId }, env.CREDITS_PER_STT);
×
602

603
      const apiUrl = env.OPENAI_WHISPER_URL;
×
604
      const model = env.OPENAI_WHISPER_MODEL;
×
605

606
      if (!req.file) {
×
607
        return res.status(400).json({ error: "No file uploaded" });
×
608
      }
×
609

610
      const form = new FormData();
×
611
      const buffer = fs.readFileSync(req.file!.path);
×
612
      const blob = new Blob([buffer], { type: req.file.mimetype });
×
613
      form.append("file", blob, req.file.originalname);
×
614
      form.append("prompt", req.body.prompt || "");
×
615
      form.append("model", model);
×
616
      form.append("temperature", req.body.temperature || "0");
×
617
      form.append("response_format", req.body.response_format || "json");
×
618
      form.append("language", req.body.language || "en");
×
619

620
      const headers: Record<string, string> = {
×
621
        Authorization: `Bearer ${env.OPENAI_WHISPER_API_KEY}`,
×
622
      };
×
623

624
      const startTime = Date.now();
×
625
      const response = await fetchWithRetry(apiUrl, {
×
626
        method: "POST",
×
627
        headers,
×
628
        body: form,
×
629
        signal: AbortSignal.timeout(env.TIMEOUT_WHISPER),
×
630
      });
×
631

632
      const duration = (Date.now() - startTime) / 1000;
×
633
      apiCallDuration.observe({ service: "openai", endpoint: "/v1/audio/transcriptions", status: "success" }, duration);
×
634

635
      const data = await response.json();
×
636
      data.x_groq = undefined; // delete
×
637
      return res.json(data);
×
638
    } catch {
×
639
      return res.status(500).json({ error: "Internal error" });
×
640
    }
×
641
  },
×
642
);
×
643

644
// The error handler must be registered before any other error middleware and after all controllers
645
Sentry.setupExpressErrorHandler(app);
×
646

647
// Error handlers (must be last)
648
app.use(errorHandler());
×
649

650
export { app };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc