@elsium-ai/app 0.2.2 → 0.3.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/dist/index.js CHANGED
@@ -389,6 +389,189 @@ function createLogger(options = {}) {
389
389
  }
390
390
  };
391
391
  }
392
+ // ../core/src/schema.ts
393
+ var log = createLogger();
394
+ function zodDefKind(def) {
395
+ return typeof def.type === "string" ? def.type : def.typeName;
396
+ }
397
+ function zodObjectToJsonSchema(schema, convert) {
398
+ const shape = typeof schema.shape === "function" ? schema.shape() : schema.shape;
399
+ const properties = {};
400
+ const required = [];
401
+ for (const [key, value] of Object.entries(shape)) {
402
+ const fieldSchema = value;
403
+ properties[key] = convert(fieldSchema);
404
+ const fieldDef = fieldSchema._def;
405
+ const fieldKind = zodDefKind(fieldDef);
406
+ if (fieldKind !== "optional" && fieldKind !== "ZodOptional" && fieldKind !== "default" && fieldKind !== "ZodDefault") {
407
+ required.push(key);
408
+ }
409
+ if (fieldDef.description) {
410
+ properties[key].description = fieldDef.description;
411
+ }
412
+ }
413
+ return { type: "object", properties, required };
414
+ }
415
+ function zodToJsonSchema(schema) {
416
+ if (!("_def" in schema))
417
+ return { type: "object" };
418
+ const def = schema._def;
419
+ const kind = zodDefKind(def);
420
+ switch (kind) {
421
+ case "object":
422
+ case "ZodObject":
423
+ return zodObjectToJsonSchema(def, zodToJsonSchema);
424
+ case "string":
425
+ case "ZodString":
426
+ return { type: "string" };
427
+ case "number":
428
+ case "ZodNumber":
429
+ return { type: "number" };
430
+ case "boolean":
431
+ case "ZodBoolean":
432
+ return { type: "boolean" };
433
+ case "array":
434
+ case "ZodArray":
435
+ return {
436
+ type: "array",
437
+ items: zodToJsonSchema(def.element ?? def.type)
438
+ };
439
+ case "enum":
440
+ case "ZodEnum": {
441
+ const values = def.values ?? (def.entries ? Object.values(def.entries) : []);
442
+ return { type: "string", enum: values };
443
+ }
444
+ case "optional":
445
+ case "ZodOptional":
446
+ return zodToJsonSchema(def.innerType);
447
+ case "default":
448
+ case "ZodDefault":
449
+ return zodToJsonSchema(def.innerType);
450
+ case "nullable":
451
+ case "ZodNullable": {
452
+ const inner = zodToJsonSchema(def.innerType);
453
+ return { ...inner, nullable: true };
454
+ }
455
+ case "ZodLiteral":
456
+ return { type: typeof def.value, const: def.value };
457
+ case "ZodUnion": {
458
+ const options = def.options.map(zodToJsonSchema);
459
+ return { anyOf: options };
460
+ }
461
+ case "ZodRecord":
462
+ return {
463
+ type: "object",
464
+ additionalProperties: def.valueType ? zodToJsonSchema(def.valueType) : { type: "string" }
465
+ };
466
+ case "ZodTuple": {
467
+ const items = (def.items ?? []).map(zodToJsonSchema);
468
+ return { type: "array", prefixItems: items, minItems: items.length, maxItems: items.length };
469
+ }
470
+ case "ZodDate":
471
+ return { type: "string", format: "date-time" };
472
+ default:
473
+ log.warn(`zodToJsonSchema: unsupported type ${kind}, defaulting to string`);
474
+ return { type: "string" };
475
+ }
476
+ }
477
+ // ../core/src/registry.ts
478
+ var log2 = createLogger();
479
+ var BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
480
+ // ../core/src/shutdown.ts
481
+ function createShutdownManager(config) {
482
+ const drainTimeoutMs = config?.drainTimeoutMs ?? 30000;
483
+ const signals = config?.signals ?? ["SIGTERM", "SIGINT"];
484
+ if (drainTimeoutMs < 0 || !Number.isFinite(drainTimeoutMs)) {
485
+ throw new ElsiumError({
486
+ code: "CONFIG_ERROR",
487
+ message: "drainTimeoutMs must be >= 0 and finite",
488
+ retryable: false
489
+ });
490
+ }
491
+ let shuttingDown = false;
492
+ let inFlightCount = 0;
493
+ let drainResolve = null;
494
+ let shutdownPromise = null;
495
+ const signalHandlers = [];
496
+ function checkDrained() {
497
+ if (inFlightCount === 0 && drainResolve) {
498
+ drainResolve();
499
+ drainResolve = null;
500
+ }
501
+ }
502
+ async function shutdown() {
503
+ if (shutdownPromise)
504
+ return shutdownPromise;
505
+ shuttingDown = true;
506
+ shutdownPromise = (async () => {
507
+ config?.onDrainStart?.();
508
+ if (inFlightCount === 0) {
509
+ config?.onDrainComplete?.();
510
+ return;
511
+ }
512
+ const drainPromise = new Promise((resolve) => {
513
+ drainResolve = resolve;
514
+ });
515
+ let drainTimer;
516
+ const timeoutPromise = new Promise((resolve) => {
517
+ drainTimer = setTimeout(() => resolve("timeout"), drainTimeoutMs);
518
+ });
519
+ const result = await Promise.race([
520
+ drainPromise.then(() => "drained"),
521
+ timeoutPromise
522
+ ]);
523
+ if (drainTimer !== undefined)
524
+ clearTimeout(drainTimer);
525
+ if (result === "timeout") {
526
+ config?.onForceShutdown?.();
527
+ } else {
528
+ config?.onDrainComplete?.();
529
+ }
530
+ })();
531
+ return shutdownPromise;
532
+ }
533
+ const manager = {
534
+ get inFlight() {
535
+ return inFlightCount;
536
+ },
537
+ get isShuttingDown() {
538
+ return shuttingDown;
539
+ },
540
+ async trackOperation(fn) {
541
+ if (shuttingDown) {
542
+ throw new ElsiumError({
543
+ code: "VALIDATION_ERROR",
544
+ message: "Server is shutting down, not accepting new operations",
545
+ retryable: true
546
+ });
547
+ }
548
+ inFlightCount++;
549
+ try {
550
+ return await fn();
551
+ } finally {
552
+ inFlightCount--;
553
+ checkDrained();
554
+ }
555
+ },
556
+ shutdown,
557
+ dispose() {
558
+ for (const { signal, handler } of signalHandlers) {
559
+ process.removeListener(signal, handler);
560
+ }
561
+ signalHandlers.length = 0;
562
+ }
563
+ };
564
+ if (typeof process !== "undefined" && process.on) {
565
+ for (const signal of signals) {
566
+ const handler = () => {
567
+ manager.shutdown();
568
+ };
569
+ signalHandlers.push({ signal, handler });
570
+ process.on(signal, handler);
571
+ }
572
+ }
573
+ return manager;
574
+ }
392
575
  // ../gateway/src/provider.ts
393
576
  var providerRegistry = new Map;
394
577
  var metadataRegistry = new Map;
@@ -520,7 +703,7 @@ function xrayMiddleware(options = {}) {
520
703
  }
521
704
 
522
705
  // ../gateway/src/pricing.ts
523
- var log = createLogger();
706
+ var log3 = createLogger();
524
707
  var PRICING = {
525
708
  "claude-opus-4-6": { inputPerMillion: 15, outputPerMillion: 75 },
526
709
  "claude-sonnet-4-6": { inputPerMillion: 3, outputPerMillion: 15 },
@@ -558,7 +741,7 @@ function resolveModelName(model) {
558
741
  function calculateCost(model, usage) {
559
742
  const pricing = PRICING[resolveModelName(model)];
560
743
  if (!pricing) {
561
- log.warn(`Unknown model "${model}" — cost will be reported as $0. Register pricing with registerPricing().`);
744
+ log3.warn(`Unknown model "${model}" — cost will be reported as $0. Register pricing with registerPricing().`);
562
745
  return {
563
746
  inputCost: 0,
564
747
  outputCost: 0,
@@ -652,15 +835,33 @@ function createAnthropicProvider(config) {
652
835
  if (part.type === "text")
653
836
  return { type: "text", text: part.text };
654
837
  if (part.type === "image" && part.source?.type === "base64") {
838
+ const src = part.source;
655
839
  return {
656
840
  type: "image",
657
841
  source: {
658
842
  type: "base64",
659
- media_type: part.source.mediaType,
660
- data: part.source.data
843
+ media_type: src.mediaType,
844
+ data: src.data
661
845
  }
662
846
  };
663
847
  }
848
+ if (part.type === "document" && part.source) {
849
+ if (part.source.type === "base64") {
850
+ const src = part.source;
851
+ return {
852
+ type: "document",
853
+ source: {
854
+ type: "base64",
855
+ media_type: src.mediaType,
856
+ data: src.data
857
+ }
858
+ };
859
+ }
860
+ return { type: "text", text: "[document: url source not supported by Anthropic]" };
861
+ }
862
+ if (part.type === "audio") {
863
+ return { type: "text", text: "[audio content not supported by this provider]" };
864
+ }
664
865
  return { type: "text", text: "[unsupported content]" };
665
866
  }
666
867
  function formatMultipartContent(msg, role) {
@@ -769,6 +970,16 @@ function createAnthropicProvider(config) {
769
970
  const tools = formatTools(req.tools);
770
971
  if (tools)
771
972
  body.tools = tools;
973
+ if (req.schema) {
974
+ const jsonSchema = zodToJsonSchema(req.schema);
975
+ const structuredTool = {
976
+ name: "_structured_output",
977
+ description: "Return structured output matching the required schema",
978
+ input_schema: jsonSchema
979
+ };
980
+ body.tools = [...tools ?? [], structuredTool];
981
+ body.tool_choice = { type: "tool", name: "_structured_output" };
982
+ }
772
983
  const startTime = performance.now();
773
984
  const raw = await retry(async () => {
774
985
  const controller = new AbortController;
@@ -978,7 +1189,31 @@ function createGoogleProvider(config) {
978
1189
  return { role, parts };
979
1190
  }
980
1191
  function formatGeminiMultipartContent(msg, role) {
981
- const parts = msg.content.filter((p) => p.type === "text").map((p) => ({ text: p.text }));
1192
+ const parts = [];
1193
+ for (const p of msg.content) {
1194
+ if (p.type === "text") {
1195
+ parts.push({ text: p.text });
1196
+ } else if (p.type === "image") {
1197
+ const img = p;
1198
+ if (img.source.type === "base64") {
1199
+ parts.push({ inlineData: { mimeType: img.source.mediaType, data: img.source.data } });
1200
+ } else {
1201
+ parts.push({ fileData: { mimeType: "image/jpeg", fileUri: img.source.url } });
1202
+ }
1203
+ } else if (p.type === "audio" || p.type === "document") {
1204
+ const media = p;
1205
+ if (media.source.type === "base64") {
1206
+ parts.push({
1207
+ inlineData: { mimeType: media.source.mediaType, data: media.source.data }
1208
+ });
1209
+ } else {
1210
+ const urlSource = media.source;
1211
+ parts.push({
1212
+ fileData: { mimeType: "application/octet-stream", fileUri: urlSource.url }
1213
+ });
1214
+ }
1215
+ }
1216
+ }
982
1217
  return { role, parts };
983
1218
  }
984
1219
  function formatMessages(messages) {
@@ -1075,6 +1310,10 @@ function createGoogleProvider(config) {
1075
1310
  config2.topP = req.topP;
1076
1311
  if (req.stopSequences?.length)
1077
1312
  config2.stopSequences = req.stopSequences;
1313
+ if (req.schema) {
1314
+ config2.responseMimeType = "application/json";
1315
+ config2.responseSchema = zodToJsonSchema(req.schema);
1316
+ }
1078
1317
  return config2;
1079
1318
  }
1080
1319
  function buildRequestBody(req) {
@@ -1153,7 +1392,8 @@ async function handleGoogleErrorResponse(response) {
1153
1392
  throw ElsiumError.authError("google");
1154
1393
  }
1155
1394
  if (response.status === 429) {
1156
- throw ElsiumError.rateLimit("google");
1395
+ const retryAfter = response.headers.get("retry-after");
1396
+ throw ElsiumError.rateLimit("google", retryAfter ? Number.parseInt(retryAfter) * 1000 : undefined);
1157
1397
  }
1158
1398
  throw ElsiumError.providerError(`Google API error ${response.status}: ${errorBody}`, {
1159
1399
  provider: "google",
@@ -1339,6 +1579,43 @@ function createOpenAIProvider(config) {
1339
1579
  }
1340
1580
  return openaiMsg;
1341
1581
  }
1582
+ function formatUserContent(msg) {
1583
+ if (typeof msg.content === "string")
1584
+ return msg.content;
1585
+ const parts = [];
1586
+ for (const part of msg.content) {
1587
+ if (part.type === "text") {
1588
+ parts.push({ type: "text", text: part.text });
1589
+ } else if (part.type === "image") {
1590
+ if (part.source.type === "base64") {
1591
+ const url = `data:${part.source.mediaType};base64,${part.source.data}`;
1592
+ parts.push({ type: "image_url", image_url: { url } });
1593
+ } else {
1594
+ parts.push({ type: "image_url", image_url: { url: part.source.url } });
1595
+ }
1596
+ } else if (part.type === "audio") {
1597
+ if (part.source.type === "base64") {
1598
+ const format = part.source.mediaType.split("/")[1] ?? "wav";
1599
+ parts.push({
1600
+ type: "input_audio",
1601
+ input_audio: { data: part.source.data, format }
1602
+ });
1603
+ } else {
1604
+ parts.push({ type: "text", text: "[audio: url source requires file upload]" });
1605
+ }
1606
+ } else if (part.type === "document") {
1607
+ if (part.source.type === "base64") {
1608
+ parts.push({
1609
+ type: "text",
1610
+ text: `[document: ${part.source.mediaType} content attached as base64]`
1611
+ });
1612
+ } else {
1613
+ parts.push({ type: "text", text: `[document: ${part.source.url}]` });
1614
+ }
1615
+ }
1616
+ }
1617
+ return parts;
1618
+ }
1342
1619
  function formatMessages(messages) {
1343
1620
  const formatted = [];
1344
1621
  for (const msg of messages) {
@@ -1354,7 +1631,7 @@ function createOpenAIProvider(config) {
1354
1631
  formatted.push(formatAssistantMessage(msg));
1355
1632
  continue;
1356
1633
  }
1357
- formatted.push({ role: "user", content: extractTextContent(msg) });
1634
+ formatted.push({ role: "user", content: formatUserContent(msg) });
1358
1635
  }
1359
1636
  return formatted;
1360
1637
  }
@@ -1431,6 +1708,17 @@ function createOpenAIProvider(config) {
1431
1708
  const tools = formatTools(req.tools);
1432
1709
  if (tools)
1433
1710
  body.tools = tools;
1711
+ if (req.schema) {
1712
+ const jsonSchema = zodToJsonSchema(req.schema);
1713
+ body.response_format = {
1714
+ type: "json_schema",
1715
+ json_schema: {
1716
+ name: "structured_output",
1717
+ strict: true,
1718
+ schema: jsonSchema
1719
+ }
1720
+ };
1721
+ }
1434
1722
  const startTime = performance.now();
1435
1723
  const raw = await retry(async () => {
1436
1724
  const controller = new AbortController;
@@ -1590,7 +1878,7 @@ var PROVIDER_FACTORIES = {
1590
1878
  openai: createOpenAIProvider,
1591
1879
  google: createGoogleProvider
1592
1880
  };
1593
- function gateway(config) {
1881
+ function validateGatewayConfig(config) {
1594
1882
  const factory = PROVIDER_FACTORIES[config.provider];
1595
1883
  if (!factory) {
1596
1884
  throw new ElsiumError({
@@ -1599,21 +1887,92 @@ function gateway(config) {
1599
1887
  retryable: false
1600
1888
  });
1601
1889
  }
1890
+ if (typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
1891
+ throw new ElsiumError({
1892
+ code: "CONFIG_ERROR",
1893
+ message: "apiKey must be a non-empty string",
1894
+ retryable: false
1895
+ });
1896
+ }
1897
+ if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
1898
+ throw new ElsiumError({
1899
+ code: "CONFIG_ERROR",
1900
+ message: "timeout must be a positive finite number",
1901
+ retryable: false
1902
+ });
1903
+ }
1904
+ if (config.maxRetries !== undefined && (!Number.isFinite(config.maxRetries) || !Number.isInteger(config.maxRetries) || config.maxRetries < 0)) {
1905
+ throw new ElsiumError({
1906
+ code: "CONFIG_ERROR",
1907
+ message: "maxRetries must be a non-negative finite integer",
1908
+ retryable: false
1909
+ });
1910
+ }
1911
+ return factory;
1912
+ }
1913
+ function autoRegisterProvider(provider) {
1914
+ if (!provider.metadata)
1915
+ return;
1916
+ registerProviderMetadata(provider.name, provider.metadata);
1917
+ if (!provider.metadata.pricing)
1918
+ return;
1919
+ for (const [model, pricing] of Object.entries(provider.metadata.pricing)) {
1920
+ registerPricing(model, pricing);
1921
+ }
1922
+ }
1923
+ function validateRequestLimits(request, maxMessages, maxInputTokens) {
1924
+ if (request.messages.length > maxMessages) {
1925
+ throw ElsiumError.validation(`Message count ${request.messages.length} exceeds limit of ${maxMessages}`);
1926
+ }
1927
+ let estimatedTokens = 0;
1928
+ for (const msg of request.messages) {
1929
+ const text = typeof msg.content === "string" ? msg.content : msg.content.map((p) => p.type === "text" ? p.text : "").join("");
1930
+ estimatedTokens += Math.ceil(text.length / 4);
1931
+ }
1932
+ if (estimatedTokens > maxInputTokens) {
1933
+ throw ElsiumError.validation(`Estimated input tokens (~${estimatedTokens}) exceeds limit of ${maxInputTokens}`);
1934
+ }
1935
+ }
1936
+ function buildMiddlewareContext(req, providerName, defaultModel, metadata) {
1937
+ return {
1938
+ request: req,
1939
+ provider: providerName,
1940
+ model: req.model ?? defaultModel,
1941
+ traceId: generateTraceId(),
1942
+ startTime: performance.now(),
1943
+ metadata
1944
+ };
1945
+ }
1946
+ async function accumulateStreamEvents(stream, emit) {
1947
+ let textContent = "";
1948
+ let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1949
+ let stopReason = "end_turn";
1950
+ let id = "";
1951
+ for await (const event of stream) {
1952
+ emit(event);
1953
+ if (event.type === "text_delta") {
1954
+ textContent += event.text;
1955
+ } else if (event.type === "message_end") {
1956
+ usage = event.usage;
1957
+ stopReason = event.stopReason;
1958
+ } else if (event.type === "message_start") {
1959
+ id = event.id;
1960
+ }
1961
+ }
1962
+ return { textContent, usage, stopReason, id };
1963
+ }
1964
+ function gateway(config) {
1965
+ const factory = validateGatewayConfig(config);
1602
1966
  const provider = factory({
1603
1967
  apiKey: config.apiKey,
1604
1968
  baseUrl: config.baseUrl,
1605
1969
  timeout: config.timeout,
1606
1970
  maxRetries: config.maxRetries
1607
1971
  });
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
- }
1972
+ autoRegisterProvider(provider);
1616
1973
  const defaultModel = config.model ?? provider.defaultModel;
1974
+ const maxMessages = config.maxMessages ?? 1000;
1975
+ const maxInputTokens = config.maxInputTokens ?? 1e6;
1617
1976
  let xrayStore = null;
1618
1977
  const allMiddleware = [...config.middleware ?? []];
1619
1978
  if (config.xray) {
@@ -1628,14 +1987,7 @@ function gateway(config) {
1628
1987
  if (!composedMiddleware) {
1629
1988
  return provider.complete(req);
1630
1989
  }
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
- };
1990
+ const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
1639
1991
  return composedMiddleware(ctx, async (c) => provider.complete(c.request));
1640
1992
  }
1641
1993
  return {
@@ -1647,34 +1999,27 @@ function gateway(config) {
1647
1999
  return xrayStore?.callHistory(limit) ?? [];
1648
2000
  },
1649
2001
  async complete(request) {
2002
+ validateRequestLimits(request, maxMessages, maxInputTokens);
1650
2003
  return executeWithMiddleware(request);
1651
2004
  },
1652
2005
  stream(request) {
2006
+ validateRequestLimits(request, maxMessages, maxInputTokens);
1653
2007
  const req = { ...request, model: request.model ?? defaultModel };
1654
2008
  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
- };
2009
+ const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
1663
2010
  return createStream(async (emit) => {
1664
2011
  await composedMiddleware(ctx, async (c) => {
1665
- const stream = provider.stream(c.request);
1666
- for await (const event of stream) {
1667
- emit(event);
1668
- }
2012
+ const result = await accumulateStreamEvents(provider.stream(c.request), emit);
2013
+ const latencyMs = Math.round(performance.now() - ctx.startTime);
1669
2014
  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" },
2015
+ id: result.id,
2016
+ message: { role: "assistant", content: result.textContent },
2017
+ usage: result.usage,
2018
+ cost: calculateCost(c.model, result.usage),
1674
2019
  model: c.model,
1675
2020
  provider: provider.name,
1676
- stopReason: "end_turn",
1677
- latencyMs: 0,
2021
+ stopReason: result.stopReason,
2022
+ latencyMs,
1678
2023
  traceId: ctx.traceId
1679
2024
  };
1680
2025
  });
@@ -1684,107 +2029,52 @@ function gateway(config) {
1684
2029
  },
1685
2030
  async generate(request) {
1686
2031
  const { schema, ...rest } = request;
1687
- const jsonSchema = schemaToJsonSchema(schema);
1688
- const systemPrompt = [
1689
- rest.system ?? "",
1690
- "You MUST respond with valid JSON matching this schema:",
1691
- JSON.stringify(jsonSchema, null, 2),
1692
- "Respond ONLY with the JSON object, no markdown or explanation."
1693
- ].filter(Boolean).join(`
1694
-
1695
- `);
2032
+ const jsonSchema = zodToJsonSchema(schema);
1696
2033
  const response = await executeWithMiddleware({
1697
2034
  ...rest,
1698
- system: systemPrompt
2035
+ schema,
2036
+ system: [
2037
+ rest.system ?? "",
2038
+ "You MUST respond with valid JSON matching this schema:",
2039
+ JSON.stringify(jsonSchema, null, 2),
2040
+ "Respond ONLY with the JSON object, no markdown or explanation."
2041
+ ].filter(Boolean).join(`
2042
+
2043
+ `)
1699
2044
  });
1700
- const text = typeof response.message.content === "string" ? response.message.content : "";
1701
- const jsonMatch = text.match(/\{[\s\S]*\}/);
1702
- if (!jsonMatch) {
1703
- throw ElsiumError.validation("LLM response did not contain valid JSON", {
1704
- response: text
1705
- });
2045
+ let parsed;
2046
+ if (response.stopReason === "tool_use" && response.message.toolCalls?.length) {
2047
+ const structuredCall = response.message.toolCalls.find((tc) => tc.name === "_structured_output");
2048
+ if (structuredCall) {
2049
+ parsed = structuredCall.arguments;
2050
+ }
2051
+ }
2052
+ if (parsed === undefined) {
2053
+ const text = typeof response.message.content === "string" ? response.message.content : "";
2054
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
2055
+ if (!jsonMatch) {
2056
+ throw ElsiumError.validation("LLM response did not contain valid JSON", {
2057
+ response: text
2058
+ });
2059
+ }
2060
+ parsed = JSON.parse(jsonMatch[0]);
1706
2061
  }
1707
- const parsed = JSON.parse(jsonMatch[0]);
1708
2062
  const result = schema.safeParse(parsed);
1709
2063
  if (!result.success) {
1710
2064
  throw ElsiumError.validation("LLM response did not match schema", {
1711
- errors: result.error.issues,
1712
- response: text
2065
+ errors: result.error.issues
1713
2066
  });
1714
2067
  }
1715
2068
  return { data: result.data, response };
1716
2069
  }
1717
2070
  };
1718
2071
  }
1719
- function schemaToJsonSchema(schema) {
1720
- try {
1721
- if ("_def" in schema) {
1722
- const def = schema._def;
1723
- const result = convertZodDef(def);
1724
- if (result)
1725
- return result;
1726
- }
1727
- } catch {}
1728
- return { type: "string" };
1729
- }
1730
- function zodDefKind(def) {
1731
- return typeof def.type === "string" ? def.type : def.typeName;
1732
- }
1733
- function convertZodDef(def) {
1734
- const kind = zodDefKind(def);
1735
- switch (kind) {
1736
- case "object":
1737
- case "ZodObject":
1738
- return convertZodObject(def);
1739
- case "string":
1740
- case "ZodString":
1741
- return { type: "string" };
1742
- case "number":
1743
- case "ZodNumber":
1744
- return { type: "number" };
1745
- case "boolean":
1746
- case "ZodBoolean":
1747
- return { type: "boolean" };
1748
- case "array":
1749
- case "ZodArray":
1750
- return convertZodArray(def);
1751
- case "enum":
1752
- case "ZodEnum": {
1753
- const values = def.values ?? (def.entries ? Object.values(def.entries) : []);
1754
- return { type: "string", enum: values };
1755
- }
1756
- case "optional":
1757
- case "ZodOptional":
1758
- return convertZodOptional(def);
1759
- default:
1760
- return null;
1761
- }
1762
- }
1763
- function convertZodObject(def) {
1764
- if (!def.shape)
1765
- return null;
1766
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1767
- const properties = {};
1768
- const required = [];
1769
- for (const [key, value] of Object.entries(shape)) {
1770
- properties[key] = schemaToJsonSchema(value);
1771
- const valDef = value._def;
1772
- const valKind = zodDefKind(valDef);
1773
- if (valKind !== "optional" && valKind !== "ZodOptional") {
1774
- required.push(key);
1775
- }
1776
- }
1777
- return { type: "object", properties, required };
1778
- }
1779
- function convertZodArray(def) {
1780
- return {
1781
- type: "array",
1782
- items: schemaToJsonSchema(def.element ?? def.type)
1783
- };
1784
- }
1785
- function convertZodOptional(def) {
1786
- return schemaToJsonSchema(def.innerType ?? def.innerType);
1787
- }
2072
+ // ../gateway/src/cache.ts
2073
+ var log4 = createLogger();
2074
+ // ../gateway/src/output-guardrails.ts
2075
+ var log5 = createLogger();
2076
+ // ../gateway/src/batch.ts
2077
+ var log6 = createLogger();
1788
2078
  // ../observe/src/span.ts
1789
2079
  function createSpan(name, options = {}) {
1790
2080
  const id = generateId("spn");
@@ -1854,7 +2144,8 @@ function createSpan(name, options = {}) {
1854
2144
  return span;
1855
2145
  }
1856
2146
  // ../observe/src/tracer.ts
1857
- var log2 = createLogger();
2147
+ import { writeFileSync } from "node:fs";
2148
+ var log7 = createLogger();
1858
2149
  function observe(config = {}) {
1859
2150
  const {
1860
2151
  output = ["console"],
@@ -1869,7 +2160,21 @@ function observe(config = {}) {
1869
2160
  for (const out of output) {
1870
2161
  if (out === "console") {
1871
2162
  handlers.push(consoleHandler);
1872
- } else if (out === "json-file") {} else {
2163
+ } else if (out === "json-file") {
2164
+ exporters.push({
2165
+ name: "json-file",
2166
+ export(spansToExport) {
2167
+ const filename = `.elsium/traces-${Date.now()}.json`;
2168
+ try {
2169
+ writeFileSync(filename, JSON.stringify(spansToExport, null, 2));
2170
+ } catch (err2) {
2171
+ log7.error("Failed to write trace file", {
2172
+ error: err2 instanceof Error ? err2.message : String(err2)
2173
+ });
2174
+ }
2175
+ }
2176
+ });
2177
+ } else {
1873
2178
  exporters.push(out);
1874
2179
  }
1875
2180
  }
@@ -1938,7 +2243,7 @@ function observe(config = {}) {
1938
2243
  function consoleHandler(span) {
1939
2244
  const duration = span.durationMs !== undefined ? `${span.durationMs}ms` : "running";
1940
2245
  const status = span.status === "error" ? "[ERROR]" : span.status === "ok" ? "[OK]" : "[...]";
1941
- log2.info("span", {
2246
+ log7.info("span", {
1942
2247
  trace: span.traceId,
1943
2248
  span: span.name,
1944
2249
  kind: span.kind,
@@ -1975,8 +2280,10 @@ function createNoopSpan(name, kind) {
1975
2280
  }
1976
2281
  };
1977
2282
  }
2283
+ // ../observe/src/experiment.ts
2284
+ var log8 = createLogger();
1978
2285
  // ../observe/src/otel.ts
1979
- var log3 = createLogger();
2286
+ var log9 = createLogger();
1980
2287
  // ../../node_modules/.bun/@hono+node-server@1.19.9/node_modules/@hono/node-server/dist/index.mjs
1981
2288
  import { createServer as createServerHTTP } from "http";
1982
2289
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
@@ -4049,7 +4356,7 @@ var Hono2 = class extends Hono {
4049
4356
  // src/middleware.ts
4050
4357
  import { timingSafeEqual } from "node:crypto";
4051
4358
  function corsMiddleware(config = true) {
4052
- const opts = typeof config === "boolean" ? { origin: [], methods: ["GET", "POST", "OPTIONS"] } : config;
4359
+ const opts = typeof config === "boolean" ? { origin: "*", methods: ["GET", "POST", "OPTIONS"] } : config;
4053
4360
  return async (c, next) => {
4054
4361
  const requestOrigin = c.req.header("Origin") ?? "";
4055
4362
  let allowedOrigin;
@@ -4126,8 +4433,203 @@ function rateLimitMiddleware(config) {
4126
4433
  await next();
4127
4434
  };
4128
4435
  }
4436
+ function requestIdMiddleware() {
4437
+ return async (c, next) => {
4438
+ const raw2 = c.req.header("X-Request-ID");
4439
+ const id = raw2 && /^[\w\-.:]{1,128}$/.test(raw2) ? raw2 : generateId("req");
4440
+ c.set("requestId", id);
4441
+ await next();
4442
+ c.res.headers.set("X-Request-ID", id);
4443
+ };
4444
+ }
4445
+ function requestLoggerMiddleware(logger) {
4446
+ const log10 = logger ?? createLogger();
4447
+ return async (c, next) => {
4448
+ const start = Date.now();
4449
+ await next();
4450
+ const duration = Date.now() - start;
4451
+ log10.info(`${c.req.method} ${c.req.path}`, {
4452
+ method: c.req.method,
4453
+ path: c.req.path,
4454
+ status: c.res.status,
4455
+ durationMs: duration,
4456
+ requestId: c.get("requestId")
4457
+ });
4458
+ };
4459
+ }
4460
+
4461
+ // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/utils/stream.js
4462
+ var StreamingApi = class {
4463
+ writer;
4464
+ encoder;
4465
+ writable;
4466
+ abortSubscribers = [];
4467
+ responseReadable;
4468
+ aborted = false;
4469
+ closed = false;
4470
+ constructor(writable, _readable) {
4471
+ this.writable = writable;
4472
+ this.writer = writable.getWriter();
4473
+ this.encoder = new TextEncoder;
4474
+ const reader = _readable.getReader();
4475
+ this.abortSubscribers.push(async () => {
4476
+ await reader.cancel();
4477
+ });
4478
+ this.responseReadable = new ReadableStream({
4479
+ async pull(controller) {
4480
+ const { done, value } = await reader.read();
4481
+ done ? controller.close() : controller.enqueue(value);
4482
+ },
4483
+ cancel: () => {
4484
+ this.abort();
4485
+ }
4486
+ });
4487
+ }
4488
+ async write(input) {
4489
+ try {
4490
+ if (typeof input === "string") {
4491
+ input = this.encoder.encode(input);
4492
+ }
4493
+ await this.writer.write(input);
4494
+ } catch {}
4495
+ return this;
4496
+ }
4497
+ async writeln(input) {
4498
+ await this.write(input + `
4499
+ `);
4500
+ return this;
4501
+ }
4502
+ sleep(ms) {
4503
+ return new Promise((res) => setTimeout(res, ms));
4504
+ }
4505
+ async close() {
4506
+ try {
4507
+ await this.writer.close();
4508
+ } catch {}
4509
+ this.closed = true;
4510
+ }
4511
+ async pipe(body) {
4512
+ this.writer.releaseLock();
4513
+ await body.pipeTo(this.writable, { preventClose: true });
4514
+ this.writer = this.writable.getWriter();
4515
+ }
4516
+ onAbort(listener) {
4517
+ this.abortSubscribers.push(listener);
4518
+ }
4519
+ abort() {
4520
+ if (!this.aborted) {
4521
+ this.aborted = true;
4522
+ this.abortSubscribers.forEach((subscriber) => subscriber());
4523
+ }
4524
+ }
4525
+ };
4526
+
4527
+ // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/helper/streaming/utils.js
4528
+ var isOldBunVersion = () => {
4529
+ const version = typeof Bun !== "undefined" ? Bun.version : undefined;
4530
+ if (version === undefined) {
4531
+ return false;
4532
+ }
4533
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
4534
+ isOldBunVersion = () => result;
4535
+ return result;
4536
+ };
4537
+
4538
+ // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/helper/streaming/stream.js
4539
+ var contextStash = /* @__PURE__ */ new WeakMap;
4540
+ var stream = (c, cb, onError) => {
4541
+ const { readable, writable } = new TransformStream;
4542
+ const stream2 = new StreamingApi(writable, readable);
4543
+ if (isOldBunVersion()) {
4544
+ c.req.raw.signal.addEventListener("abort", () => {
4545
+ if (!stream2.closed) {
4546
+ stream2.abort();
4547
+ }
4548
+ });
4549
+ }
4550
+ contextStash.set(stream2.responseReadable, c);
4551
+ (async () => {
4552
+ try {
4553
+ await cb(stream2);
4554
+ } catch (e) {
4555
+ if (e === undefined) {} else if (e instanceof Error && onError) {
4556
+ await onError(e, stream2);
4557
+ } else {
4558
+ console.error(e);
4559
+ }
4560
+ } finally {
4561
+ stream2.close();
4562
+ }
4563
+ })();
4564
+ return c.newResponse(stream2.responseReadable);
4565
+ };
4566
+
4567
+ // src/sse.ts
4568
+ function sseHeaders() {
4569
+ return {
4570
+ "Content-Type": "text/event-stream",
4571
+ "Cache-Control": "no-cache",
4572
+ Connection: "keep-alive",
4573
+ "X-Accel-Buffering": "no"
4574
+ };
4575
+ }
4576
+ function formatSSE(event, data) {
4577
+ const json = JSON.stringify(data);
4578
+ if (event === "message") {
4579
+ return `data: ${json}
4580
+
4581
+ `;
4582
+ }
4583
+ return `event: ${event}
4584
+ data: ${json}
4585
+
4586
+ `;
4587
+ }
4588
+ function streamResponse(c, source) {
4589
+ const headers = sseHeaders();
4590
+ for (const [key, value] of Object.entries(headers)) {
4591
+ c.header(key, value);
4592
+ }
4593
+ return stream(c, async (s) => {
4594
+ try {
4595
+ for await (const event of source) {
4596
+ const sseData = formatSSE("message", event);
4597
+ await s.write(sseData);
4598
+ }
4599
+ } catch (err2) {
4600
+ const errorEvent = {
4601
+ type: "error",
4602
+ error: err2 instanceof Error ? err2 : new Error(String(err2))
4603
+ };
4604
+ const sseData = formatSSE("error", {
4605
+ type: "error",
4606
+ message: errorEvent.error.message
4607
+ });
4608
+ await s.write(sseData);
4609
+ }
4610
+ });
4611
+ }
4129
4612
 
4130
4613
  // src/routes.ts
4614
+ function parseJsonBody(raw2) {
4615
+ try {
4616
+ return { ok: true, data: JSON.parse(raw2) };
4617
+ } catch {
4618
+ return { ok: false };
4619
+ }
4620
+ }
4621
+ function elsiumErrorResponse(c, err2, fallbackMessage) {
4622
+ if (err2 instanceof ElsiumError) {
4623
+ return c.json({ error: err2.message, code: err2.code }, err2.statusCode ?? 500);
4624
+ }
4625
+ return c.json({ error: fallbackMessage }, 500);
4626
+ }
4627
+ function resolveAgent(name, agents, defaultAgent) {
4628
+ const agent = name ? agents.get(name) : defaultAgent;
4629
+ if (agent)
4630
+ return { agent };
4631
+ return { error: name ? `Agent "${name}" not found` : "No default agent configured" };
4632
+ }
4131
4633
  function createRoutes(deps) {
4132
4634
  const app = new Hono2;
4133
4635
  let totalRequests = 0;
@@ -4168,17 +4670,32 @@ function createRoutes(deps) {
4168
4670
  if (rawText.length > MAX_BODY_SIZE) {
4169
4671
  return c.json({ error: "Request body too large (max 1MB)" }, 413);
4170
4672
  }
4171
- const body = JSON.parse(rawText);
4673
+ const parsed = parseJsonBody(rawText);
4674
+ if (!parsed.ok) {
4675
+ return c.json({ error: "Invalid JSON in request body" }, 400);
4676
+ }
4677
+ const body = parsed.data;
4172
4678
  if (!body.message) {
4173
4679
  return c.json({ error: "message is required" }, 400);
4174
4680
  }
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);
4681
+ const resolved = resolveAgent(body.agent, deps.agents, deps.defaultAgent);
4682
+ if ("error" in resolved) {
4683
+ return c.json({ error: resolved.error }, 404);
4684
+ }
4685
+ if (body.stream) {
4686
+ const stream2 = deps.gateway.stream({
4687
+ messages: [{ role: "user", content: body.message }],
4688
+ system: resolved.agent.config.system,
4689
+ model: resolved.agent.config.model
4690
+ });
4691
+ return streamResponse(c, stream2);
4692
+ }
4693
+ let result;
4694
+ try {
4695
+ result = await resolved.agent.run(body.message);
4696
+ } catch (err2) {
4697
+ return elsiumErrorResponse(c, err2, "Agent execution failed");
4180
4698
  }
4181
- const result = await agent.run(body.message);
4182
4699
  deps.tracer?.trackLLMCall({
4183
4700
  model: "unknown",
4184
4701
  inputTokens: result.usage.totalInputTokens,
@@ -4195,7 +4712,7 @@ function createRoutes(deps) {
4195
4712
  totalTokens: result.usage.totalTokens,
4196
4713
  cost: result.usage.totalCost
4197
4714
  },
4198
- model: agent.config.model ?? "default",
4715
+ model: resolved.agent.config.model ?? "default",
4199
4716
  traceId: result.traceId
4200
4717
  };
4201
4718
  return c.json(response);
@@ -4207,7 +4724,11 @@ function createRoutes(deps) {
4207
4724
  if (rawText.length > MAX_BODY_SIZE) {
4208
4725
  return c.json({ error: "Request body too large (max 1MB)" }, 413);
4209
4726
  }
4210
- const body = JSON.parse(rawText);
4727
+ const parsed = parseJsonBody(rawText);
4728
+ if (!parsed.ok) {
4729
+ return c.json({ error: "Invalid JSON in request body" }, 400);
4730
+ }
4731
+ const body = parsed.data;
4211
4732
  if (!body.messages?.length) {
4212
4733
  return c.json({ error: "messages array is required" }, 400);
4213
4734
  }
@@ -4215,13 +4736,28 @@ function createRoutes(deps) {
4215
4736
  role: m.role,
4216
4737
  content: m.content
4217
4738
  }));
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
- });
4739
+ if (body.stream) {
4740
+ const stream2 = deps.gateway.stream({
4741
+ messages,
4742
+ model: body.model,
4743
+ system: body.system,
4744
+ maxTokens: body.maxTokens,
4745
+ temperature: body.temperature
4746
+ });
4747
+ return streamResponse(c, stream2);
4748
+ }
4749
+ let response;
4750
+ try {
4751
+ response = await deps.gateway.complete({
4752
+ messages,
4753
+ model: body.model,
4754
+ system: body.system,
4755
+ maxTokens: body.maxTokens,
4756
+ temperature: body.temperature
4757
+ });
4758
+ } catch (err2) {
4759
+ return elsiumErrorResponse(c, err2, "Completion failed");
4760
+ }
4225
4761
  deps.tracer?.trackLLMCall({
4226
4762
  model: response.model,
4227
4763
  inputTokens: response.usage.inputTokens,
@@ -4250,9 +4786,18 @@ function createRoutes(deps) {
4250
4786
  }
4251
4787
 
4252
4788
  // src/app.ts
4253
- var log4 = createLogger();
4789
+ var log10 = createLogger();
4254
4790
  function createApp(config) {
4255
4791
  const app = new Hono2;
4792
+ app.onError((err2, c) => {
4793
+ const statusCode = err2 instanceof ElsiumError ? err2.statusCode ?? 500 : 500;
4794
+ const code = err2 instanceof ElsiumError ? err2.code : "UNKNOWN";
4795
+ log10.error("Unhandled error", { error: err2.message, code, path: c.req.path });
4796
+ return c.json({ error: err2.message, code }, statusCode);
4797
+ });
4798
+ app.notFound((c) => {
4799
+ return c.json({ error: "Not found" }, 404);
4800
+ });
4256
4801
  const providerNames = Object.keys(config.gateway.providers);
4257
4802
  const primaryProvider = providerNames[0];
4258
4803
  const primaryConfig = config.gateway.providers[primaryProvider];
@@ -4267,6 +4812,8 @@ function createApp(config) {
4267
4812
  costTracking: config.observe?.costTracking ?? true
4268
4813
  });
4269
4814
  const serverConfig = config.server ?? {};
4815
+ app.use("*", requestIdMiddleware());
4816
+ app.use("*", requestLoggerMiddleware(log10));
4270
4817
  if (serverConfig.cors) {
4271
4818
  app.use("*", corsMiddleware(serverConfig.cors));
4272
4819
  }
@@ -4289,7 +4836,7 @@ function createApp(config) {
4289
4836
  defaultAgent,
4290
4837
  tracer,
4291
4838
  startTime: Date.now(),
4292
- version: "0.1.0",
4839
+ version: config.version ?? "0.2.2",
4293
4840
  providers: providerNames
4294
4841
  });
4295
4842
  app.route("/", routes);
@@ -4305,13 +4852,25 @@ function createApp(config) {
4305
4852
  port: listenPort,
4306
4853
  hostname
4307
4854
  });
4308
- log4.info("ElsiumAI server started", {
4855
+ let shutdownManager;
4856
+ if (serverConfig.gracefulShutdown) {
4857
+ const drainTimeoutMs = typeof serverConfig.gracefulShutdown === "object" ? serverConfig.gracefulShutdown.drainTimeoutMs : undefined;
4858
+ shutdownManager = createShutdownManager({
4859
+ drainTimeoutMs,
4860
+ onDrainStart: () => log10.info("Draining connections..."),
4861
+ onDrainComplete: () => log10.info("Drain complete")
4862
+ });
4863
+ }
4864
+ log10.info("ElsiumAI server started", {
4309
4865
  url: `http://${hostname}:${listenPort}`,
4310
4866
  routes: ["POST /chat", "POST /complete", "GET /health", "GET /metrics", "GET /agents"]
4311
4867
  });
4312
4868
  return {
4313
4869
  port: listenPort,
4314
- stop: () => {
4870
+ stop: async () => {
4871
+ if (shutdownManager) {
4872
+ await shutdownManager.shutdown();
4873
+ }
4315
4874
  server.close();
4316
4875
  }
4317
4876
  };
@@ -4319,7 +4878,7 @@ function createApp(config) {
4319
4878
  };
4320
4879
  }
4321
4880
  // src/rbac.ts
4322
- var log5 = createLogger();
4881
+ var log11 = createLogger();
4323
4882
  var BUILT_IN_ROLES = [
4324
4883
  {
4325
4884
  name: "admin",
@@ -4357,7 +4916,7 @@ function matchPermission(granted, required) {
4357
4916
  }
4358
4917
  function createRBAC(config) {
4359
4918
  if (config.trustRoleHeader === true) {
4360
- log5.warn("RBAC: trustRoleHeader is enabled — any client can self-assign roles via the X-Role header. Only use this in development or behind a trusted reverse proxy.");
4919
+ log11.warn("RBAC: trustRoleHeader is enabled — any client can self-assign roles via the X-Role header. Only use this in development or behind a trusted reverse proxy.");
4361
4920
  }
4362
4921
  const roleMap = new Map;
4363
4922
  for (const role of BUILT_IN_ROLES) {
@@ -4413,8 +4972,62 @@ function createRBAC(config) {
4413
4972
  }
4414
4973
  };
4415
4974
  }
4975
+ // src/tenant.ts
4976
+ var log12 = createLogger();
4977
+ function tenantMiddleware(config) {
4978
+ const { extractTenant, onUnknownTenant = "reject", defaultTenant } = config;
4979
+ return async (c, next) => {
4980
+ const tenant = extractTenant(c);
4981
+ if (!tenant) {
4982
+ if (onUnknownTenant === "default" && defaultTenant) {
4983
+ c.set("tenant", defaultTenant);
4984
+ log12.debug("Using default tenant", { tenantId: defaultTenant.tenantId });
4985
+ } else {
4986
+ return c.json({ error: "Tenant identification required" }, 401);
4987
+ }
4988
+ } else {
4989
+ c.set("tenant", tenant);
4990
+ log12.debug("Tenant identified", { tenantId: tenant.tenantId });
4991
+ }
4992
+ await next();
4993
+ };
4994
+ }
4995
+ function tenantRateLimitMiddleware() {
4996
+ const windows = new Map;
4997
+ return async (c, next) => {
4998
+ const tenant = c.get("tenant");
4999
+ if (!tenant?.limits?.maxRequestsPerMinute) {
5000
+ await next();
5001
+ return;
5002
+ }
5003
+ const limit = tenant.limits.maxRequestsPerMinute;
5004
+ const now = Date.now();
5005
+ const windowMs = 60000;
5006
+ const key = tenant.tenantId;
5007
+ let entry = windows.get(key);
5008
+ if (!entry || now - entry.windowStart > windowMs) {
5009
+ entry = { count: 0, windowStart: now };
5010
+ windows.set(key, entry);
5011
+ }
5012
+ entry.count++;
5013
+ if (entry.count > limit) {
5014
+ return c.json({
5015
+ error: "Rate limit exceeded",
5016
+ retryAfterMs: windowMs - (now - entry.windowStart)
5017
+ }, 429);
5018
+ }
5019
+ await next();
5020
+ };
5021
+ }
4416
5022
  export {
5023
+ tenantRateLimitMiddleware,
5024
+ tenantMiddleware,
5025
+ streamResponse,
5026
+ sseHeaders,
5027
+ requestLoggerMiddleware,
5028
+ requestIdMiddleware,
4417
5029
  rateLimitMiddleware,
5030
+ formatSSE,
4418
5031
  createRoutes,
4419
5032
  createRBAC,
4420
5033
  createApp,