@opengeni/api-router 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  validateToolRefs,
15
15
  withDefaultEnabledCapabilityMcpTools,
16
16
  workflowIdForSession
17
- } from "./chunk-I2EDWHVF.js";
17
+ } from "./chunk-2JL5OXRE.js";
18
18
  export {
19
19
  allowedCorsOrigin,
20
20
  createApp,
@@ -6,6 +6,7 @@ import {
6
6
  } from "@opengeni/config";
7
7
  import { ClientConfig } from "@opengeni/contracts";
8
8
  import { createDocumentServices, indexDocumentNow } from "@opengeni/documents";
9
+ import { dbSql } from "@opengeni/db";
9
10
  import { createObservability } from "@opengeni/observability";
10
11
  import { createObjectStorage } from "@opengeni/storage";
11
12
  import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
@@ -1367,7 +1368,7 @@ function isAuthExempt(c, settings) {
1367
1368
  if (installExactPaths.has(path) || isInstallRedirectPath(path)) {
1368
1369
  return true;
1369
1370
  }
1370
- if (settings.authAllowHealth && path === "/healthz") {
1371
+ if (settings.authAllowHealth && (path === "/healthz" || path === "/readyz")) {
1371
1372
  return true;
1372
1373
  }
1373
1374
  if (settings.authAllowMetrics && path === "/metrics") {
@@ -3394,6 +3395,7 @@ async function handleCheckoutSessionCompleted(deps, event) {
3394
3395
  stripeCreditAmountUsd: credit.amountUsd
3395
3396
  }
3396
3397
  });
3398
+ recordCreditMicrosMetric(deps, "topup", credit.amountMicros);
3397
3399
  }
3398
3400
  async function mirrorPaymentIntentCustomer(deps, event) {
3399
3401
  const intent = event.data.object;
@@ -3426,18 +3428,23 @@ async function applyRefundDebit(deps, stripe, refund) {
3426
3428
  if (!accountId) {
3427
3429
  return;
3428
3430
  }
3431
+ const idempotencyKey = `stripe:refund:${refund.id}`;
3432
+ if (await hasCreditLedgerEntry(deps.db, accountId, idempotencyKey)) {
3433
+ return;
3434
+ }
3429
3435
  await applyCreditLedgerEntry(deps.db, {
3430
3436
  accountId,
3431
3437
  type: "credit_refund",
3432
3438
  amountMicros: -centsToMicros(refund.amount),
3433
3439
  sourceType: "stripe_refund",
3434
3440
  sourceId: refund.id,
3435
- idempotencyKey: `stripe:refund:${refund.id}`,
3441
+ idempotencyKey,
3436
3442
  metadata: {
3437
3443
  stripeRefundId: refund.id,
3438
3444
  stripePaymentIntentId: paymentIntentId(refund.payment_intent)
3439
3445
  }
3440
3446
  });
3447
+ recordCreditMicrosMetric(deps, "refund", centsToMicros(refund.amount));
3441
3448
  }
3442
3449
  async function holdDisputedCredits(deps, stripe, event) {
3443
3450
  const dispute = event.data.object;
@@ -3492,6 +3499,17 @@ async function mirrorCustomer(deps, event, customer) {
3492
3499
  email: typeof customer.email === "string" ? customer.email : null
3493
3500
  });
3494
3501
  }
3502
+ function recordCreditMicrosMetric(deps, kind, amountMicros) {
3503
+ if (amountMicros <= 0) {
3504
+ return;
3505
+ }
3506
+ deps.observability?.incrementCounter({
3507
+ name: "opengeni_credit_micros_total",
3508
+ help: "Total credit micros recorded by kind.",
3509
+ labels: { kind },
3510
+ amount: amountMicros
3511
+ });
3512
+ }
3495
3513
  async function metadataForRefund(stripe, refund) {
3496
3514
  if (Object.keys(refund.metadata ?? {}).length > 0) {
3497
3515
  return refund.metadata;
@@ -6194,13 +6212,19 @@ function createApp(deps) {
6194
6212
  service: deps.settings.serviceName,
6195
6213
  environment: deps.settings.environment,
6196
6214
  deploymentRevision: deps.settings.deploymentRevision,
6215
+ ...deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {},
6197
6216
  ok: true
6198
6217
  }));
6199
- app.get("/metrics", (c) => c.text(observability.prometheusMetrics(), 200, {
6218
+ app.get("/readyz", async (c) => {
6219
+ const result = await runReadinessChecks(readinessChecks(deps), 2e3);
6220
+ return c.json(result, result.ok ? 200 : 503);
6221
+ });
6222
+ app.get("/metrics", async (c) => c.text(await observability.prometheusMetrics(), 200, {
6200
6223
  "content-type": "text/plain; version=0.0.4; charset=utf-8"
6201
6224
  }));
6202
6225
  app.get("/v1/config/client", (c) => c.json(ClientConfig.parse({
6203
6226
  deploymentRevision: deps.settings.deploymentRevision,
6227
+ ...deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {},
6204
6228
  defaultModel: deps.settings.openaiModel,
6205
6229
  allowedModels: configuredAllowedModels(deps.settings),
6206
6230
  // Provider-grouped model list for the picker. configuredModels() carries the
@@ -6283,8 +6307,56 @@ function httpStatusForError(error) {
6283
6307
  }
6284
6308
  return 500;
6285
6309
  }
6310
+ function readinessChecks(deps) {
6311
+ return {
6312
+ db: deps.readinessChecks?.db ?? (async () => {
6313
+ await deps.db.execute(dbSql`select 1`);
6314
+ }),
6315
+ nats: deps.readinessChecks?.nats ?? (() => {
6316
+ if (deps.bus.isConnected && !deps.bus.isConnected()) {
6317
+ throw new Error("NATS is not connected");
6318
+ }
6319
+ }),
6320
+ temporal: deps.readinessChecks?.temporal ?? deps.workflowClient.check ?? (() => {
6321
+ throw new Error("Temporal readiness check unavailable");
6322
+ })
6323
+ };
6324
+ }
6325
+ async function runReadinessChecks(checks, timeoutMs) {
6326
+ const entries = await Promise.all(
6327
+ Object.entries(checks).map(async ([name, check]) => {
6328
+ try {
6329
+ await withTimeout(Promise.resolve().then(check), timeoutMs);
6330
+ return [name, { ok: true }];
6331
+ } catch (error) {
6332
+ return [name, { ok: false, error: error instanceof Error ? error.message : String(error) }];
6333
+ }
6334
+ })
6335
+ );
6336
+ const result = Object.fromEntries(entries);
6337
+ return {
6338
+ ok: Object.values(result).every((check) => check.ok),
6339
+ checks: result
6340
+ };
6341
+ }
6342
+ async function withTimeout(promise, timeoutMs) {
6343
+ let timer;
6344
+ try {
6345
+ return await Promise.race([
6346
+ promise,
6347
+ new Promise((_, reject) => {
6348
+ timer = setTimeout(() => reject(new Error(`readiness check timed out after ${timeoutMs}ms`)), timeoutMs);
6349
+ })
6350
+ ]);
6351
+ } finally {
6352
+ if (timer) {
6353
+ clearTimeout(timer);
6354
+ }
6355
+ }
6356
+ }
6286
6357
  var routeLabelPatterns = [
6287
6358
  { pattern: /^\/healthz$/, label: "/healthz" },
6359
+ { pattern: /^\/readyz$/, label: "/readyz" },
6288
6360
  { pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/start$/, label: "/v1/workspaces/:workspaceId/codex/connect/start" },
6289
6361
  { pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/poll$/, label: "/v1/workspaces/:workspaceId/codex/connect/poll" },
6290
6362
  { pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/, label: "/v1/workspaces/:workspaceId/codex/status" },
@@ -6384,4 +6456,4 @@ export {
6384
6456
  withDefaultEnabledCapabilityMcpTools,
6385
6457
  workflowIdForSession3 as workflowIdForSession
6386
6458
  };
6387
- //# sourceMappingURL=chunk-I2EDWHVF.js.map
6459
+ //# sourceMappingURL=chunk-2JL5OXRE.js.map