@elsium-ai/app 0.2.2 → 0.2.3

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.d.ts CHANGED
@@ -8,7 +8,7 @@ export interface ElsiumApp {
8
8
  readonly tracer: Tracer;
9
9
  listen(port?: number): {
10
10
  port: number;
11
- stop: () => void;
11
+ stop: () => Promise<void>;
12
12
  };
13
13
  }
14
14
  export declare function createApp(config: AppConfig): ElsiumApp;
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,oBAAoB,CAAA;AAIzD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CACzD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CA8FtD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,oBAAoB,CAAA;AAIzD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAS3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAA;CAClE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAgItD"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createApp } from './app';
2
2
  export type { ElsiumApp } from './app';
3
3
  export type { AppConfig, ServerConfig, CorsConfig, AuthConfig, RateLimitConfig, ChatRequest, ChatResponse, CompleteRequest, HealthResponse, MetricsResponse, } from './types';
4
- export { corsMiddleware, authMiddleware, rateLimitMiddleware } from './middleware';
4
+ export { corsMiddleware, authMiddleware, rateLimitMiddleware, requestIdMiddleware, requestLoggerMiddleware, } from './middleware';
5
5
  export { createRoutes } from './routes';
6
6
  export type { RoutesDeps } from './routes';
7
7
  export { createRBAC } from './rbac';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,YAAY,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,eAAe,EACf,WAAW,EACX,YAAY,EACZ,eAAe,EACf,cAAc,EACd,eAAe,GACf,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGlF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1C,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,YAAY,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,eAAe,EACf,WAAW,EACX,YAAY,EACZ,eAAe,EACf,cAAc,EACd,eAAe,GACf,MAAM,SAAS,CAAA;AAGhB,OAAO,EACN,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GACvB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1C,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
package/dist/index.js CHANGED
@@ -389,6 +389,101 @@ function createLogger(options = {}) {
389
389
  }
390
390
  };
391
391
  }
392
+ // ../core/src/shutdown.ts
393
+ function createShutdownManager(config) {
394
+ const drainTimeoutMs = config?.drainTimeoutMs ?? 30000;
395
+ const signals = config?.signals ?? ["SIGTERM", "SIGINT"];
396
+ if (drainTimeoutMs < 0 || !Number.isFinite(drainTimeoutMs)) {
397
+ throw new ElsiumError({
398
+ code: "CONFIG_ERROR",
399
+ message: "drainTimeoutMs must be >= 0 and finite",
400
+ retryable: false
401
+ });
402
+ }
403
+ let shuttingDown = false;
404
+ let inFlightCount = 0;
405
+ let drainResolve = null;
406
+ let shutdownPromise = null;
407
+ const signalHandlers = [];
408
+ function checkDrained() {
409
+ if (inFlightCount === 0 && drainResolve) {
410
+ drainResolve();
411
+ drainResolve = null;
412
+ }
413
+ }
414
+ async function shutdown() {
415
+ if (shutdownPromise)
416
+ return shutdownPromise;
417
+ shuttingDown = true;
418
+ shutdownPromise = (async () => {
419
+ config?.onDrainStart?.();
420
+ if (inFlightCount === 0) {
421
+ config?.onDrainComplete?.();
422
+ return;
423
+ }
424
+ const drainPromise = new Promise((resolve) => {
425
+ drainResolve = resolve;
426
+ });
427
+ let drainTimer;
428
+ const timeoutPromise = new Promise((resolve) => {
429
+ drainTimer = setTimeout(() => resolve("timeout"), drainTimeoutMs);
430
+ });
431
+ const result = await Promise.race([
432
+ drainPromise.then(() => "drained"),
433
+ timeoutPromise
434
+ ]);
435
+ if (drainTimer !== undefined)
436
+ clearTimeout(drainTimer);
437
+ if (result === "timeout") {
438
+ config?.onForceShutdown?.();
439
+ } else {
440
+ config?.onDrainComplete?.();
441
+ }
442
+ })();
443
+ return shutdownPromise;
444
+ }
445
+ const manager = {
446
+ get inFlight() {
447
+ return inFlightCount;
448
+ },
449
+ get isShuttingDown() {
450
+ return shuttingDown;
451
+ },
452
+ async trackOperation(fn) {
453
+ if (shuttingDown) {
454
+ throw new ElsiumError({
455
+ code: "VALIDATION_ERROR",
456
+ message: "Server is shutting down, not accepting new operations",
457
+ retryable: true
458
+ });
459
+ }
460
+ inFlightCount++;
461
+ try {
462
+ return await fn();
463
+ } finally {
464
+ inFlightCount--;
465
+ checkDrained();
466
+ }
467
+ },
468
+ shutdown,
469
+ dispose() {
470
+ for (const { signal, handler } of signalHandlers) {
471
+ process.removeListener(signal, handler);
472
+ }
473
+ signalHandlers.length = 0;
474
+ }
475
+ };
476
+ if (typeof process !== "undefined" && process.on) {
477
+ for (const signal of signals) {
478
+ const handler = () => {
479
+ manager.shutdown();
480
+ };
481
+ signalHandlers.push({ signal, handler });
482
+ process.on(signal, handler);
483
+ }
484
+ }
485
+ return manager;
486
+ }
392
487
  // ../gateway/src/provider.ts
393
488
  var providerRegistry = new Map;
394
489
  var metadataRegistry = new Map;
@@ -978,7 +1073,19 @@ function createGoogleProvider(config) {
978
1073
  return { role, parts };
979
1074
  }
980
1075
  function formatGeminiMultipartContent(msg, role) {
981
- const parts = msg.content.filter((p) => p.type === "text").map((p) => ({ text: p.text }));
1076
+ const parts = [];
1077
+ for (const p of msg.content) {
1078
+ if (p.type === "text") {
1079
+ parts.push({ text: p.text });
1080
+ } else if (p.type === "image") {
1081
+ const img = p;
1082
+ if (img.source.type === "base64") {
1083
+ parts.push({ inlineData: { mimeType: img.source.mediaType, data: img.source.data } });
1084
+ } else {
1085
+ parts.push({ fileData: { mimeType: "image/jpeg", fileUri: img.source.url } });
1086
+ }
1087
+ }
1088
+ }
982
1089
  return { role, parts };
983
1090
  }
984
1091
  function formatMessages(messages) {
@@ -1153,7 +1260,8 @@ async function handleGoogleErrorResponse(response) {
1153
1260
  throw ElsiumError.authError("google");
1154
1261
  }
1155
1262
  if (response.status === 429) {
1156
- throw ElsiumError.rateLimit("google");
1263
+ const retryAfter = response.headers.get("retry-after");
1264
+ throw ElsiumError.rateLimit("google", retryAfter ? Number.parseInt(retryAfter) * 1000 : undefined);
1157
1265
  }
1158
1266
  throw ElsiumError.providerError(`Google API error ${response.status}: ${errorBody}`, {
1159
1267
  provider: "google",
@@ -1339,6 +1447,24 @@ function createOpenAIProvider(config) {
1339
1447
  }
1340
1448
  return openaiMsg;
1341
1449
  }
1450
+ function formatUserContent(msg) {
1451
+ if (typeof msg.content === "string")
1452
+ return msg.content;
1453
+ const parts = [];
1454
+ for (const part of msg.content) {
1455
+ if (part.type === "text") {
1456
+ parts.push({ type: "text", text: part.text });
1457
+ } else if (part.type === "image") {
1458
+ if (part.source.type === "base64") {
1459
+ const url = `data:${part.source.mediaType};base64,${part.source.data}`;
1460
+ parts.push({ type: "image_url", image_url: { url } });
1461
+ } else {
1462
+ parts.push({ type: "image_url", image_url: { url: part.source.url } });
1463
+ }
1464
+ }
1465
+ }
1466
+ return parts;
1467
+ }
1342
1468
  function formatMessages(messages) {
1343
1469
  const formatted = [];
1344
1470
  for (const msg of messages) {
@@ -1354,7 +1480,7 @@ function createOpenAIProvider(config) {
1354
1480
  formatted.push(formatAssistantMessage(msg));
1355
1481
  continue;
1356
1482
  }
1357
- formatted.push({ role: "user", content: extractTextContent(msg) });
1483
+ formatted.push({ role: "user", content: formatUserContent(msg) });
1358
1484
  }
1359
1485
  return formatted;
1360
1486
  }
@@ -1590,7 +1716,7 @@ var PROVIDER_FACTORIES = {
1590
1716
  openai: createOpenAIProvider,
1591
1717
  google: createGoogleProvider
1592
1718
  };
1593
- function gateway(config) {
1719
+ function validateGatewayConfig(config) {
1594
1720
  const factory = PROVIDER_FACTORIES[config.provider];
1595
1721
  if (!factory) {
1596
1722
  throw new ElsiumError({
@@ -1599,21 +1725,92 @@ function gateway(config) {
1599
1725
  retryable: false
1600
1726
  });
1601
1727
  }
1728
+ if (typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
1729
+ throw new ElsiumError({
1730
+ code: "CONFIG_ERROR",
1731
+ message: "apiKey must be a non-empty string",
1732
+ retryable: false
1733
+ });
1734
+ }
1735
+ if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
1736
+ throw new ElsiumError({
1737
+ code: "CONFIG_ERROR",
1738
+ message: "timeout must be a positive finite number",
1739
+ retryable: false
1740
+ });
1741
+ }
1742
+ if (config.maxRetries !== undefined && (!Number.isFinite(config.maxRetries) || !Number.isInteger(config.maxRetries) || config.maxRetries < 0)) {
1743
+ throw new ElsiumError({
1744
+ code: "CONFIG_ERROR",
1745
+ message: "maxRetries must be a non-negative finite integer",
1746
+ retryable: false
1747
+ });
1748
+ }
1749
+ return factory;
1750
+ }
1751
+ function autoRegisterProvider(provider) {
1752
+ if (!provider.metadata)
1753
+ return;
1754
+ registerProviderMetadata(provider.name, provider.metadata);
1755
+ if (!provider.metadata.pricing)
1756
+ return;
1757
+ for (const [model, pricing] of Object.entries(provider.metadata.pricing)) {
1758
+ registerPricing(model, pricing);
1759
+ }
1760
+ }
1761
+ function validateRequestLimits(request, maxMessages, maxInputTokens) {
1762
+ if (request.messages.length > maxMessages) {
1763
+ throw ElsiumError.validation(`Message count ${request.messages.length} exceeds limit of ${maxMessages}`);
1764
+ }
1765
+ let estimatedTokens = 0;
1766
+ for (const msg of request.messages) {
1767
+ const text = typeof msg.content === "string" ? msg.content : msg.content.map((p) => p.type === "text" ? p.text : "").join("");
1768
+ estimatedTokens += Math.ceil(text.length / 4);
1769
+ }
1770
+ if (estimatedTokens > maxInputTokens) {
1771
+ throw ElsiumError.validation(`Estimated input tokens (~${estimatedTokens}) exceeds limit of ${maxInputTokens}`);
1772
+ }
1773
+ }
1774
+ function buildMiddlewareContext(req, providerName, defaultModel, metadata) {
1775
+ return {
1776
+ request: req,
1777
+ provider: providerName,
1778
+ model: req.model ?? defaultModel,
1779
+ traceId: generateTraceId(),
1780
+ startTime: performance.now(),
1781
+ metadata
1782
+ };
1783
+ }
1784
+ async function accumulateStreamEvents(stream, emit) {
1785
+ let textContent = "";
1786
+ let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1787
+ let stopReason = "end_turn";
1788
+ let id = "";
1789
+ for await (const event of stream) {
1790
+ emit(event);
1791
+ if (event.type === "text_delta") {
1792
+ textContent += event.text;
1793
+ } else if (event.type === "message_end") {
1794
+ usage = event.usage;
1795
+ stopReason = event.stopReason;
1796
+ } else if (event.type === "message_start") {
1797
+ id = event.id;
1798
+ }
1799
+ }
1800
+ return { textContent, usage, stopReason, id };
1801
+ }
1802
+ function gateway(config) {
1803
+ const factory = validateGatewayConfig(config);
1602
1804
  const provider = factory({
1603
1805
  apiKey: config.apiKey,
1604
1806
  baseUrl: config.baseUrl,
1605
1807
  timeout: config.timeout,
1606
1808
  maxRetries: config.maxRetries
1607
1809
  });
1608
- if (provider.metadata) {
1609
- registerProviderMetadata(provider.name, provider.metadata);
1610
- if (provider.metadata.pricing) {
1611
- for (const [model, pricing] of Object.entries(provider.metadata.pricing)) {
1612
- registerPricing(model, pricing);
1613
- }
1614
- }
1615
- }
1810
+ autoRegisterProvider(provider);
1616
1811
  const defaultModel = config.model ?? provider.defaultModel;
1812
+ const maxMessages = config.maxMessages ?? 1000;
1813
+ const maxInputTokens = config.maxInputTokens ?? 1e6;
1617
1814
  let xrayStore = null;
1618
1815
  const allMiddleware = [...config.middleware ?? []];
1619
1816
  if (config.xray) {
@@ -1628,14 +1825,7 @@ function gateway(config) {
1628
1825
  if (!composedMiddleware) {
1629
1826
  return provider.complete(req);
1630
1827
  }
1631
- const ctx = {
1632
- request: req,
1633
- provider: provider.name,
1634
- model: req.model ?? defaultModel,
1635
- traceId: generateTraceId(),
1636
- startTime: performance.now(),
1637
- metadata: request.metadata ?? {}
1638
- };
1828
+ const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
1639
1829
  return composedMiddleware(ctx, async (c) => provider.complete(c.request));
1640
1830
  }
1641
1831
  return {
@@ -1647,34 +1837,27 @@ function gateway(config) {
1647
1837
  return xrayStore?.callHistory(limit) ?? [];
1648
1838
  },
1649
1839
  async complete(request) {
1840
+ validateRequestLimits(request, maxMessages, maxInputTokens);
1650
1841
  return executeWithMiddleware(request);
1651
1842
  },
1652
1843
  stream(request) {
1844
+ validateRequestLimits(request, maxMessages, maxInputTokens);
1653
1845
  const req = { ...request, model: request.model ?? defaultModel };
1654
1846
  if (composedMiddleware) {
1655
- const ctx = {
1656
- request: req,
1657
- provider: provider.name,
1658
- model: req.model ?? defaultModel,
1659
- traceId: generateTraceId(),
1660
- startTime: performance.now(),
1661
- metadata: request.metadata ?? {}
1662
- };
1847
+ const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
1663
1848
  return createStream(async (emit) => {
1664
1849
  await composedMiddleware(ctx, async (c) => {
1665
- const stream = provider.stream(c.request);
1666
- for await (const event of stream) {
1667
- emit(event);
1668
- }
1850
+ const result = await accumulateStreamEvents(provider.stream(c.request), emit);
1851
+ const latencyMs = Math.round(performance.now() - ctx.startTime);
1669
1852
  return {
1670
- id: "",
1671
- message: { role: "assistant", content: "" },
1672
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
1673
- cost: { inputCost: 0, outputCost: 0, totalCost: 0, currency: "USD" },
1853
+ id: result.id,
1854
+ message: { role: "assistant", content: result.textContent },
1855
+ usage: result.usage,
1856
+ cost: calculateCost(c.model, result.usage),
1674
1857
  model: c.model,
1675
1858
  provider: provider.name,
1676
- stopReason: "end_turn",
1677
- latencyMs: 0,
1859
+ stopReason: result.stopReason,
1860
+ latencyMs,
1678
1861
  traceId: ctx.traceId
1679
1862
  };
1680
1863
  });
@@ -1854,6 +2037,7 @@ function createSpan(name, options = {}) {
1854
2037
  return span;
1855
2038
  }
1856
2039
  // ../observe/src/tracer.ts
2040
+ import { writeFileSync } from "node:fs";
1857
2041
  var log2 = createLogger();
1858
2042
  function observe(config = {}) {
1859
2043
  const {
@@ -1869,7 +2053,21 @@ function observe(config = {}) {
1869
2053
  for (const out of output) {
1870
2054
  if (out === "console") {
1871
2055
  handlers.push(consoleHandler);
1872
- } else if (out === "json-file") {} else {
2056
+ } else if (out === "json-file") {
2057
+ exporters.push({
2058
+ name: "json-file",
2059
+ export(spansToExport) {
2060
+ const filename = `.elsium/traces-${Date.now()}.json`;
2061
+ try {
2062
+ writeFileSync(filename, JSON.stringify(spansToExport, null, 2));
2063
+ } catch (err2) {
2064
+ log2.error("Failed to write trace file", {
2065
+ error: err2 instanceof Error ? err2.message : String(err2)
2066
+ });
2067
+ }
2068
+ }
2069
+ });
2070
+ } else {
1873
2071
  exporters.push(out);
1874
2072
  }
1875
2073
  }
@@ -4049,7 +4247,7 @@ var Hono2 = class extends Hono {
4049
4247
  // src/middleware.ts
4050
4248
  import { timingSafeEqual } from "node:crypto";
4051
4249
  function corsMiddleware(config = true) {
4052
- const opts = typeof config === "boolean" ? { origin: [], methods: ["GET", "POST", "OPTIONS"] } : config;
4250
+ const opts = typeof config === "boolean" ? { origin: "*", methods: ["GET", "POST", "OPTIONS"] } : config;
4053
4251
  return async (c, next) => {
4054
4252
  const requestOrigin = c.req.header("Origin") ?? "";
4055
4253
  let allowedOrigin;
@@ -4126,8 +4324,51 @@ function rateLimitMiddleware(config) {
4126
4324
  await next();
4127
4325
  };
4128
4326
  }
4327
+ function requestIdMiddleware() {
4328
+ return async (c, next) => {
4329
+ const raw2 = c.req.header("X-Request-ID");
4330
+ const id = raw2 && /^[\w\-.:]{1,128}$/.test(raw2) ? raw2 : generateId("req");
4331
+ c.set("requestId", id);
4332
+ await next();
4333
+ c.res.headers.set("X-Request-ID", id);
4334
+ };
4335
+ }
4336
+ function requestLoggerMiddleware(logger) {
4337
+ const log4 = logger ?? createLogger();
4338
+ return async (c, next) => {
4339
+ const start = Date.now();
4340
+ await next();
4341
+ const duration = Date.now() - start;
4342
+ log4.info(`${c.req.method} ${c.req.path}`, {
4343
+ method: c.req.method,
4344
+ path: c.req.path,
4345
+ status: c.res.status,
4346
+ durationMs: duration,
4347
+ requestId: c.get("requestId")
4348
+ });
4349
+ };
4350
+ }
4129
4351
 
4130
4352
  // src/routes.ts
4353
+ function parseJsonBody(raw2) {
4354
+ try {
4355
+ return { ok: true, data: JSON.parse(raw2) };
4356
+ } catch {
4357
+ return { ok: false };
4358
+ }
4359
+ }
4360
+ function elsiumErrorResponse(c, err2, fallbackMessage) {
4361
+ if (err2 instanceof ElsiumError) {
4362
+ return c.json({ error: err2.message, code: err2.code }, err2.statusCode ?? 500);
4363
+ }
4364
+ return c.json({ error: fallbackMessage }, 500);
4365
+ }
4366
+ function resolveAgent(name, agents, defaultAgent) {
4367
+ const agent = name ? agents.get(name) : defaultAgent;
4368
+ if (agent)
4369
+ return { agent };
4370
+ return { error: name ? `Agent "${name}" not found` : "No default agent configured" };
4371
+ }
4131
4372
  function createRoutes(deps) {
4132
4373
  const app = new Hono2;
4133
4374
  let totalRequests = 0;
@@ -4168,17 +4409,24 @@ function createRoutes(deps) {
4168
4409
  if (rawText.length > MAX_BODY_SIZE) {
4169
4410
  return c.json({ error: "Request body too large (max 1MB)" }, 413);
4170
4411
  }
4171
- const body = JSON.parse(rawText);
4412
+ const parsed = parseJsonBody(rawText);
4413
+ if (!parsed.ok) {
4414
+ return c.json({ error: "Invalid JSON in request body" }, 400);
4415
+ }
4416
+ const body = parsed.data;
4172
4417
  if (!body.message) {
4173
4418
  return c.json({ error: "message is required" }, 400);
4174
4419
  }
4175
- const agent = body.agent ? deps.agents.get(body.agent) : deps.defaultAgent;
4176
- if (!agent) {
4177
- return c.json({
4178
- error: body.agent ? `Agent "${body.agent}" not found` : "No default agent configured"
4179
- }, 404);
4420
+ const resolved = resolveAgent(body.agent, deps.agents, deps.defaultAgent);
4421
+ if ("error" in resolved) {
4422
+ return c.json({ error: resolved.error }, 404);
4423
+ }
4424
+ let result;
4425
+ try {
4426
+ result = await resolved.agent.run(body.message);
4427
+ } catch (err2) {
4428
+ return elsiumErrorResponse(c, err2, "Agent execution failed");
4180
4429
  }
4181
- const result = await agent.run(body.message);
4182
4430
  deps.tracer?.trackLLMCall({
4183
4431
  model: "unknown",
4184
4432
  inputTokens: result.usage.totalInputTokens,
@@ -4195,7 +4443,7 @@ function createRoutes(deps) {
4195
4443
  totalTokens: result.usage.totalTokens,
4196
4444
  cost: result.usage.totalCost
4197
4445
  },
4198
- model: agent.config.model ?? "default",
4446
+ model: resolved.agent.config.model ?? "default",
4199
4447
  traceId: result.traceId
4200
4448
  };
4201
4449
  return c.json(response);
@@ -4207,7 +4455,11 @@ function createRoutes(deps) {
4207
4455
  if (rawText.length > MAX_BODY_SIZE) {
4208
4456
  return c.json({ error: "Request body too large (max 1MB)" }, 413);
4209
4457
  }
4210
- const body = JSON.parse(rawText);
4458
+ const parsed = parseJsonBody(rawText);
4459
+ if (!parsed.ok) {
4460
+ return c.json({ error: "Invalid JSON in request body" }, 400);
4461
+ }
4462
+ const body = parsed.data;
4211
4463
  if (!body.messages?.length) {
4212
4464
  return c.json({ error: "messages array is required" }, 400);
4213
4465
  }
@@ -4215,13 +4467,18 @@ function createRoutes(deps) {
4215
4467
  role: m.role,
4216
4468
  content: m.content
4217
4469
  }));
4218
- const response = await deps.gateway.complete({
4219
- messages,
4220
- model: body.model,
4221
- system: body.system,
4222
- maxTokens: body.maxTokens,
4223
- temperature: body.temperature
4224
- });
4470
+ let response;
4471
+ try {
4472
+ response = await deps.gateway.complete({
4473
+ messages,
4474
+ model: body.model,
4475
+ system: body.system,
4476
+ maxTokens: body.maxTokens,
4477
+ temperature: body.temperature
4478
+ });
4479
+ } catch (err2) {
4480
+ return elsiumErrorResponse(c, err2, "Completion failed");
4481
+ }
4225
4482
  deps.tracer?.trackLLMCall({
4226
4483
  model: response.model,
4227
4484
  inputTokens: response.usage.inputTokens,
@@ -4253,6 +4510,15 @@ function createRoutes(deps) {
4253
4510
  var log4 = createLogger();
4254
4511
  function createApp(config) {
4255
4512
  const app = new Hono2;
4513
+ app.onError((err2, c) => {
4514
+ const statusCode = err2 instanceof ElsiumError ? err2.statusCode ?? 500 : 500;
4515
+ const code = err2 instanceof ElsiumError ? err2.code : "UNKNOWN";
4516
+ log4.error("Unhandled error", { error: err2.message, code, path: c.req.path });
4517
+ return c.json({ error: err2.message, code }, statusCode);
4518
+ });
4519
+ app.notFound((c) => {
4520
+ return c.json({ error: "Not found" }, 404);
4521
+ });
4256
4522
  const providerNames = Object.keys(config.gateway.providers);
4257
4523
  const primaryProvider = providerNames[0];
4258
4524
  const primaryConfig = config.gateway.providers[primaryProvider];
@@ -4267,6 +4533,8 @@ function createApp(config) {
4267
4533
  costTracking: config.observe?.costTracking ?? true
4268
4534
  });
4269
4535
  const serverConfig = config.server ?? {};
4536
+ app.use("*", requestIdMiddleware());
4537
+ app.use("*", requestLoggerMiddleware(log4));
4270
4538
  if (serverConfig.cors) {
4271
4539
  app.use("*", corsMiddleware(serverConfig.cors));
4272
4540
  }
@@ -4289,7 +4557,7 @@ function createApp(config) {
4289
4557
  defaultAgent,
4290
4558
  tracer,
4291
4559
  startTime: Date.now(),
4292
- version: "0.1.0",
4560
+ version: config.version ?? "0.2.2",
4293
4561
  providers: providerNames
4294
4562
  });
4295
4563
  app.route("/", routes);
@@ -4305,13 +4573,25 @@ function createApp(config) {
4305
4573
  port: listenPort,
4306
4574
  hostname
4307
4575
  });
4576
+ let shutdownManager;
4577
+ if (serverConfig.gracefulShutdown) {
4578
+ const drainTimeoutMs = typeof serverConfig.gracefulShutdown === "object" ? serverConfig.gracefulShutdown.drainTimeoutMs : undefined;
4579
+ shutdownManager = createShutdownManager({
4580
+ drainTimeoutMs,
4581
+ onDrainStart: () => log4.info("Draining connections..."),
4582
+ onDrainComplete: () => log4.info("Drain complete")
4583
+ });
4584
+ }
4308
4585
  log4.info("ElsiumAI server started", {
4309
4586
  url: `http://${hostname}:${listenPort}`,
4310
4587
  routes: ["POST /chat", "POST /complete", "GET /health", "GET /metrics", "GET /agents"]
4311
4588
  });
4312
4589
  return {
4313
4590
  port: listenPort,
4314
- stop: () => {
4591
+ stop: async () => {
4592
+ if (shutdownManager) {
4593
+ await shutdownManager.shutdown();
4594
+ }
4315
4595
  server.close();
4316
4596
  }
4317
4597
  };
@@ -4414,6 +4694,8 @@ function createRBAC(config) {
4414
4694
  };
4415
4695
  }
4416
4696
  export {
4697
+ requestLoggerMiddleware,
4698
+ requestIdMiddleware,
4417
4699
  rateLimitMiddleware,
4418
4700
  createRoutes,
4419
4701
  createRBAC,
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '@elsium-ai/core';
1
2
  import type { Context, Next } from 'hono';
2
3
  import type { AuthConfig, CorsConfig, RateLimitConfig } from './types';
3
4
  export declare function corsMiddleware(config?: CorsConfig | boolean): (c: Context, next: Next) => Promise<(Response & import("hono").TypedResponse<null, 200, "body">) | undefined>;
@@ -8,4 +9,6 @@ export declare function rateLimitMiddleware(config: RateLimitConfig): (c: Contex
8
9
  error: string;
9
10
  retryAfterMs: number;
10
11
  }, 429, "json">) | undefined>;
12
+ export declare function requestIdMiddleware(): (c: Context, next: Next) => Promise<void>;
13
+ export declare function requestLoggerMiddleware(logger?: Logger): (c: Context, next: Next) => Promise<void>;
11
14
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAItE,wBAAgB,cAAc,CAAC,MAAM,GAAE,UAAU,GAAG,OAAc,IAInD,GAAG,OAAO,EAAE,MAAM,IAAI,uFAiCpC;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,IAClC,GAAG,OAAO,EAAE,MAAM,IAAI;;kBAwBpC;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,IAG5C,GAAG,OAAO,EAAE,MAAM,IAAI;;;8BAsCpC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAA4B,MAAM,iBAAiB,CAAA;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAItE,wBAAgB,cAAc,CAAC,MAAM,GAAE,UAAU,GAAG,OAAc,IAInD,GAAG,OAAO,EAAE,MAAM,IAAI,uFAiCpC;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,IAClC,GAAG,OAAO,EAAE,MAAM,IAAI;;kBAwBpC;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,IAG5C,GAAG,OAAO,EAAE,MAAM,IAAI;;;8BAsCpC;AAID,wBAAgB,mBAAmB,KACpB,GAAG,OAAO,EAAE,MAAM,IAAI,mBASpC;AAID,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,IAGxC,GAAG,OAAO,EAAE,MAAM,IAAI,mBAcpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAS3B,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CA+JnD"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAkC3B,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAyKnD"}
package/dist/types.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface AppConfig {
16
16
  export?: string;
17
17
  };
18
18
  server?: ServerConfig;
19
+ version?: string;
19
20
  }
20
21
  export interface ServerConfig {
21
22
  port?: number;
@@ -23,6 +24,9 @@ export interface ServerConfig {
23
24
  cors?: boolean | CorsConfig;
24
25
  auth?: AuthConfig;
25
26
  rateLimit?: RateLimitConfig;
27
+ gracefulShutdown?: boolean | {
28
+ drainTimeoutMs?: number;
29
+ };
26
30
  }
27
31
  export interface CorsConfig {
28
32
  origin?: string | string[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE;QACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC/D,YAAY,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAChB,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,MAAM,CAAC,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,SAAS,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACnB;AAID,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACN,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;KACZ,CAAA;IACD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,IAAI,GAAG,UAAU,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC3E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE;QACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC/D,YAAY,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAChB,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxD;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACnB;AAID,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACN,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;KACZ,CAAA;IACD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,IAAI,GAAG,UAAU,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC3E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elsium-ai/app",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "App bootstrap, HTTP server, and API routes for ElsiumAI",
5
5
  "license": "MIT",
6
6
  "author": "Eric Utrera <ebutrera9103@gmail.com>",
@@ -26,13 +26,13 @@
26
26
  "dev": "bun --watch src/index.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@elsium-ai/core": "^0.2.2",
30
- "@elsium-ai/gateway": "^0.2.2",
31
- "@elsium-ai/agents": "^0.2.2",
32
- "@elsium-ai/tools": "^0.2.2",
33
- "@elsium-ai/observe": "^0.2.2",
34
- "@elsium-ai/rag": "^0.2.2",
35
- "@elsium-ai/workflows": "^0.2.2",
29
+ "@elsium-ai/core": "^0.2.3",
30
+ "@elsium-ai/gateway": "^0.2.3",
31
+ "@elsium-ai/agents": "^0.2.3",
32
+ "@elsium-ai/tools": "^0.2.3",
33
+ "@elsium-ai/observe": "^0.2.3",
34
+ "@elsium-ai/rag": "^0.2.3",
35
+ "@elsium-ai/workflows": "^0.2.3",
36
36
  "@hono/node-server": "^1.13.0",
37
37
  "hono": "^4.7.0",
38
38
  "zod": "^3.24.0"