@elsium-ai/app 0.2.3 → 0.4.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,94 @@ 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"]);
392
480
  // ../core/src/shutdown.ts
393
481
  function createShutdownManager(config) {
394
482
  const drainTimeoutMs = config?.drainTimeoutMs ?? 30000;
@@ -487,6 +575,12 @@ function createShutdownManager(config) {
487
575
  // ../gateway/src/provider.ts
488
576
  var providerRegistry = new Map;
489
577
  var metadataRegistry = new Map;
578
+ function getProviderFactory(name) {
579
+ return providerRegistry.get(name);
580
+ }
581
+ function listProviders() {
582
+ return Array.from(providerRegistry.keys());
583
+ }
490
584
  function registerProviderMetadata(name, metadata) {
491
585
  metadataRegistry.set(name, metadata);
492
586
  }
@@ -512,6 +606,17 @@ function composeMiddleware(middlewares) {
512
606
  return dispatch(0);
513
607
  };
514
608
  }
609
+ function composeStreamMiddleware(middlewares) {
610
+ return (ctx, source, finalNext) => {
611
+ function dispatch(i, currentCtx, currentSource) {
612
+ if (i >= middlewares.length) {
613
+ return finalNext(currentCtx, currentSource);
614
+ }
615
+ return middlewares[i](currentCtx, currentSource, (c, s) => dispatch(i + 1, c, s));
616
+ }
617
+ return dispatch(0, ctx, source);
618
+ };
619
+ }
515
620
  var SENSITIVE_HEADERS = ["x-api-key", "authorization", "api-key"];
516
621
  function redactHeaders(headers) {
517
622
  const redacted = {};
@@ -615,7 +720,7 @@ function xrayMiddleware(options = {}) {
615
720
  }
616
721
 
617
722
  // ../gateway/src/pricing.ts
618
- var log = createLogger();
723
+ var log3 = createLogger();
619
724
  var PRICING = {
620
725
  "claude-opus-4-6": { inputPerMillion: 15, outputPerMillion: 75 },
621
726
  "claude-sonnet-4-6": { inputPerMillion: 3, outputPerMillion: 15 },
@@ -653,7 +758,7 @@ function resolveModelName(model) {
653
758
  function calculateCost(model, usage) {
654
759
  const pricing = PRICING[resolveModelName(model)];
655
760
  if (!pricing) {
656
- log.warn(`Unknown model "${model}" — cost will be reported as $0. Register pricing with registerPricing().`);
761
+ log3.warn(`Unknown model "${model}" — cost will be reported as $0. Register pricing with registerPricing().`);
657
762
  return {
658
763
  inputCost: 0,
659
764
  outputCost: 0,
@@ -747,15 +852,33 @@ function createAnthropicProvider(config) {
747
852
  if (part.type === "text")
748
853
  return { type: "text", text: part.text };
749
854
  if (part.type === "image" && part.source?.type === "base64") {
855
+ const src = part.source;
750
856
  return {
751
857
  type: "image",
752
858
  source: {
753
859
  type: "base64",
754
- media_type: part.source.mediaType,
755
- data: part.source.data
860
+ media_type: src.mediaType,
861
+ data: src.data
756
862
  }
757
863
  };
758
864
  }
865
+ if (part.type === "document" && part.source) {
866
+ if (part.source.type === "base64") {
867
+ const src = part.source;
868
+ return {
869
+ type: "document",
870
+ source: {
871
+ type: "base64",
872
+ media_type: src.mediaType,
873
+ data: src.data
874
+ }
875
+ };
876
+ }
877
+ return { type: "text", text: "[document: url source not supported by Anthropic]" };
878
+ }
879
+ if (part.type === "audio") {
880
+ return { type: "text", text: "[audio content not supported by this provider]" };
881
+ }
759
882
  return { type: "text", text: "[unsupported content]" };
760
883
  }
761
884
  function formatMultipartContent(msg, role) {
@@ -798,6 +921,52 @@ function createAnthropicProvider(config) {
798
921
  input_schema: t.inputSchema
799
922
  }));
800
923
  }
924
+ function buildOptionalParams(req) {
925
+ const params = {};
926
+ if (req.temperature !== undefined)
927
+ params.temperature = req.temperature;
928
+ if (req.topP !== undefined)
929
+ params.top_p = req.topP;
930
+ if (req.stopSequences?.length)
931
+ params.stop_sequences = req.stopSequences;
932
+ return params;
933
+ }
934
+ function applyStructuredOutput(body, req, tools) {
935
+ if (!req.schema)
936
+ return;
937
+ const jsonSchema = zodToJsonSchema(req.schema);
938
+ const structuredTool = {
939
+ name: "_structured_output",
940
+ description: "Return structured output matching the required schema",
941
+ input_schema: jsonSchema
942
+ };
943
+ body.tools = [...tools ?? [], structuredTool];
944
+ body.tool_choice = { type: "tool", name: "_structured_output" };
945
+ }
946
+ function buildRequestBody(req) {
947
+ const { system, messages } = formatMessages(req.messages);
948
+ const model = req.model ?? "claude-sonnet-4-6";
949
+ const body = {
950
+ model,
951
+ messages,
952
+ max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS,
953
+ ...system || req.system ? { system: req.system ?? system } : {},
954
+ ...buildOptionalParams(req),
955
+ ...buildSeedMetadata(req)
956
+ };
957
+ const tools = formatTools(req.tools);
958
+ if (tools)
959
+ body.tools = tools;
960
+ applyStructuredOutput(body, req, tools);
961
+ return body;
962
+ }
963
+ function executeWithTimeout(fn, reqSignal) {
964
+ const controller = new AbortController;
965
+ const timer = setTimeout(() => controller.abort(), timeout);
966
+ const signals = [controller.signal, reqSignal].filter(Boolean);
967
+ const mergedSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
968
+ return fn(mergedSignal).finally(() => clearTimeout(timer));
969
+ }
801
970
  function extractContentBlocks(content) {
802
971
  const toolCalls = [];
803
972
  const textParts = [];
@@ -849,34 +1018,12 @@ function createAnthropicProvider(config) {
849
1018
  authStyle: "x-api-key"
850
1019
  },
851
1020
  async complete(req) {
852
- const { system, messages } = formatMessages(req.messages);
853
- const model = req.model ?? "claude-sonnet-4-6";
854
- const body = {
855
- model,
856
- messages,
857
- max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS,
858
- ...system || req.system ? { system: req.system ?? system } : {},
859
- ...req.temperature !== undefined ? { temperature: req.temperature } : {},
860
- ...req.topP !== undefined ? { top_p: req.topP } : {},
861
- ...req.stopSequences?.length ? { stop_sequences: req.stopSequences } : {},
862
- ...buildSeedMetadata(req)
863
- };
864
- const tools = formatTools(req.tools);
865
- if (tools)
866
- body.tools = tools;
1021
+ const body = buildRequestBody(req);
867
1022
  const startTime = performance.now();
868
- const raw = await retry(async () => {
869
- const controller = new AbortController;
870
- const timer = setTimeout(() => controller.abort(), timeout);
871
- try {
872
- const signals = [controller.signal, req.signal].filter(Boolean);
873
- const mergedSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
874
- const resp = await request("/messages", body, mergedSignal);
875
- return await resp.json();
876
- } finally {
877
- clearTimeout(timer);
878
- }
879
- }, {
1023
+ const raw = await retry(() => executeWithTimeout(async (signal) => {
1024
+ const resp = await request("/messages", body, signal);
1025
+ return await resp.json();
1026
+ }, req.signal), {
880
1027
  maxRetries,
881
1028
  baseDelayMs: 1000,
882
1029
  shouldRetry: (e) => e instanceof ElsiumError && e.retryable
@@ -885,29 +1032,12 @@ function createAnthropicProvider(config) {
885
1032
  return parseResponse(raw, latencyMs);
886
1033
  },
887
1034
  stream(req) {
888
- const { system, messages } = formatMessages(req.messages);
889
- const model = req.model ?? "claude-sonnet-4-6";
890
- const body = {
891
- model,
892
- messages,
893
- max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS,
894
- stream: true,
895
- ...system || req.system ? { system: req.system ?? system } : {},
896
- ...req.temperature !== undefined ? { temperature: req.temperature } : {},
897
- ...req.topP !== undefined ? { top_p: req.topP } : {},
898
- ...req.stopSequences?.length ? { stop_sequences: req.stopSequences } : {},
899
- ...buildSeedMetadata(req)
900
- };
901
- const tools = formatTools(req.tools);
902
- if (tools)
903
- body.tools = tools;
1035
+ const body = buildRequestBody(req);
1036
+ body.stream = true;
1037
+ const model = body.model ?? "claude-sonnet-4-6";
904
1038
  return createStream(async (emit) => {
905
- const controller = new AbortController;
906
- const timer = setTimeout(() => controller.abort(), timeout);
907
- try {
908
- const signals = [controller.signal, req.signal].filter(Boolean);
909
- const mergedSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
910
- const resp = await request("/messages", body, mergedSignal);
1039
+ await executeWithTimeout(async (signal) => {
1040
+ const resp = await request("/messages", body, signal);
911
1041
  if (!resp.body)
912
1042
  throw new ElsiumError({
913
1043
  code: "STREAM_ERROR",
@@ -916,9 +1046,7 @@ function createAnthropicProvider(config) {
916
1046
  retryable: false
917
1047
  });
918
1048
  await processAnthropicSSEStream(resp.body, model, emit);
919
- } finally {
920
- clearTimeout(timer);
921
- }
1049
+ }, req.signal);
922
1050
  });
923
1051
  },
924
1052
  async listModels() {
@@ -1072,19 +1200,38 @@ function createGoogleProvider(config) {
1072
1200
  }
1073
1201
  return { role, parts };
1074
1202
  }
1203
+ function convertGeminiImagePart(p) {
1204
+ if (p.source.type === "base64") {
1205
+ return { inlineData: { mimeType: p.source.mediaType, data: p.source.data } };
1206
+ }
1207
+ return { fileData: { mimeType: "image/jpeg", fileUri: p.source.url } };
1208
+ }
1209
+ function convertGeminiMediaPart(p) {
1210
+ if (p.source.type === "base64") {
1211
+ return { inlineData: { mimeType: p.source.mediaType, data: p.source.data } };
1212
+ }
1213
+ const urlSource = p.source;
1214
+ return { fileData: { mimeType: "application/octet-stream", fileUri: urlSource.url } };
1215
+ }
1216
+ function convertGeminiContentPart(p) {
1217
+ if (p.type === "text") {
1218
+ return { text: p.text };
1219
+ }
1220
+ if (p.type === "image") {
1221
+ return convertGeminiImagePart(p);
1222
+ }
1223
+ if (p.type === "audio" || p.type === "document") {
1224
+ return convertGeminiMediaPart(p);
1225
+ }
1226
+ return null;
1227
+ }
1075
1228
  function formatGeminiMultipartContent(msg, role) {
1229
+ const content = msg.content;
1076
1230
  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
- }
1231
+ for (const p of content) {
1232
+ const converted = convertGeminiContentPart(p);
1233
+ if (converted)
1234
+ parts.push(converted);
1088
1235
  }
1089
1236
  return { role, parts };
1090
1237
  }
@@ -1182,6 +1329,10 @@ function createGoogleProvider(config) {
1182
1329
  config2.topP = req.topP;
1183
1330
  if (req.stopSequences?.length)
1184
1331
  config2.stopSequences = req.stopSequences;
1332
+ if (req.schema) {
1333
+ config2.responseMimeType = "application/json";
1334
+ config2.responseSchema = zodToJsonSchema(req.schema);
1335
+ }
1185
1336
  return config2;
1186
1337
  }
1187
1338
  function buildRequestBody(req) {
@@ -1447,21 +1598,48 @@ function createOpenAIProvider(config) {
1447
1598
  }
1448
1599
  return openaiMsg;
1449
1600
  }
1601
+ function convertImagePart(part) {
1602
+ if (part.source.type === "base64") {
1603
+ const url = `data:${part.source.mediaType};base64,${part.source.data}`;
1604
+ return { type: "image_url", image_url: { url } };
1605
+ }
1606
+ return { type: "image_url", image_url: { url: part.source.url } };
1607
+ }
1608
+ function convertAudioPart(part) {
1609
+ if (part.source.type === "base64") {
1610
+ const format = part.source.mediaType.split("/")[1] ?? "wav";
1611
+ return { type: "input_audio", input_audio: { data: part.source.data, format } };
1612
+ }
1613
+ return { type: "text", text: "[audio: url source requires file upload]" };
1614
+ }
1615
+ function convertDocumentPart(part) {
1616
+ if (part.source.type === "base64") {
1617
+ return {
1618
+ type: "text",
1619
+ text: `[document: ${part.source.mediaType} content attached as base64]`
1620
+ };
1621
+ }
1622
+ return { type: "text", text: `[document: ${part.source.url}]` };
1623
+ }
1624
+ function convertContentPart(part) {
1625
+ if (part.type === "text")
1626
+ return { type: "text", text: part.text };
1627
+ if (part.type === "image")
1628
+ return convertImagePart(part);
1629
+ if (part.type === "audio")
1630
+ return convertAudioPart(part);
1631
+ if (part.type === "document")
1632
+ return convertDocumentPart(part);
1633
+ return null;
1634
+ }
1450
1635
  function formatUserContent(msg) {
1451
1636
  if (typeof msg.content === "string")
1452
1637
  return msg.content;
1453
1638
  const parts = [];
1454
1639
  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
- }
1640
+ const converted = convertContentPart(part);
1641
+ if (converted)
1642
+ parts.push(converted);
1465
1643
  }
1466
1644
  return parts;
1467
1645
  }
@@ -1496,6 +1674,49 @@ function createOpenAIProvider(config) {
1496
1674
  }
1497
1675
  }));
1498
1676
  }
1677
+ function buildOptionalParams(req) {
1678
+ const params = {};
1679
+ if (req.temperature !== undefined)
1680
+ params.temperature = req.temperature;
1681
+ if (req.seed !== undefined)
1682
+ params.seed = req.seed;
1683
+ if (req.topP !== undefined)
1684
+ params.top_p = req.topP;
1685
+ if (req.stopSequences?.length)
1686
+ params.stop = req.stopSequences;
1687
+ return params;
1688
+ }
1689
+ function applyResponseFormat(body, req) {
1690
+ if (!req.schema)
1691
+ return;
1692
+ const jsonSchema = zodToJsonSchema(req.schema);
1693
+ body.response_format = {
1694
+ type: "json_schema",
1695
+ json_schema: {
1696
+ name: "structured_output",
1697
+ strict: true,
1698
+ schema: jsonSchema
1699
+ }
1700
+ };
1701
+ }
1702
+ function buildRequestBody(req) {
1703
+ const messages = formatMessages(req.messages);
1704
+ const model = req.model ?? "gpt-4o";
1705
+ if (req.system) {
1706
+ messages.unshift({ role: "system", content: req.system });
1707
+ }
1708
+ const body = {
1709
+ model,
1710
+ messages,
1711
+ max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS2,
1712
+ ...buildOptionalParams(req)
1713
+ };
1714
+ const tools = formatTools(req.tools);
1715
+ if (tools)
1716
+ body.tools = tools;
1717
+ applyResponseFormat(body, req);
1718
+ return body;
1719
+ }
1499
1720
  function parseResponse(raw, latencyMs) {
1500
1721
  const traceId = generateTraceId();
1501
1722
  const choice = raw.choices[0];
@@ -1540,23 +1761,7 @@ function createOpenAIProvider(config) {
1540
1761
  authStyle: "bearer"
1541
1762
  },
1542
1763
  async complete(req) {
1543
- const messages = formatMessages(req.messages);
1544
- const model = req.model ?? "gpt-4o";
1545
- if (req.system) {
1546
- messages.unshift({ role: "system", content: req.system });
1547
- }
1548
- const body = {
1549
- model,
1550
- messages,
1551
- max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS2,
1552
- ...req.temperature !== undefined ? { temperature: req.temperature } : {},
1553
- ...req.seed !== undefined ? { seed: req.seed } : {},
1554
- ...req.topP !== undefined ? { top_p: req.topP } : {},
1555
- ...req.stopSequences?.length ? { stop: req.stopSequences } : {}
1556
- };
1557
- const tools = formatTools(req.tools);
1558
- if (tools)
1559
- body.tools = tools;
1764
+ const body = buildRequestBody(req);
1560
1765
  const startTime = performance.now();
1561
1766
  const raw = await retry(async () => {
1562
1767
  const controller = new AbortController;
@@ -1578,25 +1783,10 @@ function createOpenAIProvider(config) {
1578
1783
  return parseResponse(raw, latencyMs);
1579
1784
  },
1580
1785
  stream(req) {
1581
- const messages = formatMessages(req.messages);
1582
- const model = req.model ?? "gpt-4o";
1583
- if (req.system) {
1584
- messages.unshift({ role: "system", content: req.system });
1585
- }
1586
- const body = {
1587
- model,
1588
- messages,
1589
- max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS2,
1590
- stream: true,
1591
- stream_options: { include_usage: true },
1592
- ...req.temperature !== undefined ? { temperature: req.temperature } : {},
1593
- ...req.seed !== undefined ? { seed: req.seed } : {},
1594
- ...req.topP !== undefined ? { top_p: req.topP } : {},
1595
- ...req.stopSequences?.length ? { stop: req.stopSequences } : {}
1596
- };
1597
- const tools = formatTools(req.tools);
1598
- if (tools)
1599
- body.tools = tools;
1786
+ const body = buildRequestBody(req);
1787
+ body.stream = true;
1788
+ body.stream_options = { include_usage: true };
1789
+ const model = body.model ?? "gpt-4o";
1600
1790
  return createStream(async (emit) => {
1601
1791
  const controller = new AbortController;
1602
1792
  const timer = setTimeout(() => controller.abort(), timeout);
@@ -1716,12 +1906,29 @@ var PROVIDER_FACTORIES = {
1716
1906
  openai: createOpenAIProvider,
1717
1907
  google: createGoogleProvider
1718
1908
  };
1909
+ registerProviderMetadata("anthropic", {
1910
+ baseUrl: "https://api.anthropic.com/v1/messages",
1911
+ capabilities: ["tools", "vision", "streaming", "system"],
1912
+ authStyle: "x-api-key"
1913
+ });
1914
+ registerProviderMetadata("openai", {
1915
+ baseUrl: "https://api.openai.com/v1/chat/completions",
1916
+ capabilities: ["tools", "vision", "streaming", "system", "json_mode"],
1917
+ authStyle: "bearer"
1918
+ });
1919
+ registerProviderMetadata("google", {
1920
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
1921
+ capabilities: ["tools", "vision", "streaming", "system"],
1922
+ authStyle: "bearer"
1923
+ });
1719
1924
  function validateGatewayConfig(config) {
1720
- const factory = PROVIDER_FACTORIES[config.provider];
1925
+ const factory = PROVIDER_FACTORIES[config.provider] ?? getProviderFactory(config.provider);
1721
1926
  if (!factory) {
1927
+ const available = [...Object.keys(PROVIDER_FACTORIES), ...listProviders()];
1928
+ const unique = [...new Set(available)];
1722
1929
  throw new ElsiumError({
1723
1930
  code: "CONFIG_ERROR",
1724
- message: `Unknown provider: ${config.provider}. Available: ${Object.keys(PROVIDER_FACTORIES).join(", ")}`,
1931
+ message: `Unknown provider: ${config.provider}. Available: ${unique.join(", ")}`,
1725
1932
  retryable: false
1726
1933
  });
1727
1934
  }
@@ -1799,6 +2006,24 @@ async function accumulateStreamEvents(stream, emit) {
1799
2006
  }
1800
2007
  return { textContent, usage, stopReason, id };
1801
2008
  }
2009
+ function extractFromToolCalls(response) {
2010
+ if (response.stopReason !== "tool_use" || !response.message.toolCalls?.length) {
2011
+ return;
2012
+ }
2013
+ const structuredCall = response.message.toolCalls.find((tc) => tc.name === "_structured_output");
2014
+ return structuredCall?.arguments;
2015
+ }
2016
+ function extractJsonFromText(response) {
2017
+ let text = typeof response.message.content === "string" ? response.message.content : "";
2018
+ text = text.replace(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/gm, "$1").trim();
2019
+ const jsonMatch = text.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
2020
+ if (!jsonMatch) {
2021
+ throw ElsiumError.validation("LLM response did not contain valid JSON", {
2022
+ response: text
2023
+ });
2024
+ }
2025
+ return JSON.parse(jsonMatch[0]);
2026
+ }
1802
2027
  function gateway(config) {
1803
2028
  const factory = validateGatewayConfig(config);
1804
2029
  const provider = factory({
@@ -1820,6 +2045,7 @@ function gateway(config) {
1820
2045
  allMiddleware.push(xm);
1821
2046
  }
1822
2047
  const composedMiddleware = allMiddleware.length ? composeMiddleware(allMiddleware) : null;
2048
+ const composedStreamMiddleware = config.streamMiddleware?.length ? composeStreamMiddleware(config.streamMiddleware) : null;
1823
2049
  async function executeWithMiddleware(request) {
1824
2050
  const req = { ...request, model: request.model ?? defaultModel };
1825
2051
  if (!composedMiddleware) {
@@ -1844,11 +2070,11 @@ function gateway(config) {
1844
2070
  validateRequestLimits(request, maxMessages, maxInputTokens);
1845
2071
  const req = { ...request, model: request.model ?? defaultModel };
1846
2072
  if (composedMiddleware) {
1847
- const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
2073
+ const ctx2 = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
1848
2074
  return createStream(async (emit) => {
1849
- await composedMiddleware(ctx, async (c) => {
2075
+ await composedMiddleware(ctx2, async (c) => {
1850
2076
  const result = await accumulateStreamEvents(provider.stream(c.request), emit);
1851
- const latencyMs = Math.round(performance.now() - ctx.startTime);
2077
+ const latencyMs = Math.round(performance.now() - ctx2.startTime);
1852
2078
  return {
1853
2079
  id: result.id,
1854
2080
  message: { role: "assistant", content: result.textContent },
@@ -1858,116 +2084,54 @@ function gateway(config) {
1858
2084
  provider: provider.name,
1859
2085
  stopReason: result.stopReason,
1860
2086
  latencyMs,
1861
- traceId: ctx.traceId
2087
+ traceId: ctx2.traceId
1862
2088
  };
1863
2089
  });
1864
2090
  });
1865
2091
  }
1866
- return provider.stream(req);
2092
+ const rawStream = provider.stream(req);
2093
+ if (!composedStreamMiddleware)
2094
+ return rawStream;
2095
+ const ctx = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
2096
+ return createStream(async (emit) => {
2097
+ const processed = composedStreamMiddleware(ctx, rawStream, (_c, s) => s);
2098
+ for await (const event of processed) {
2099
+ emit(event);
2100
+ }
2101
+ });
1867
2102
  },
1868
2103
  async generate(request) {
1869
2104
  const { schema, ...rest } = request;
1870
- const jsonSchema = schemaToJsonSchema(schema);
1871
- const systemPrompt = [
1872
- rest.system ?? "",
1873
- "You MUST respond with valid JSON matching this schema:",
1874
- JSON.stringify(jsonSchema, null, 2),
1875
- "Respond ONLY with the JSON object, no markdown or explanation."
1876
- ].filter(Boolean).join(`
1877
-
1878
- `);
2105
+ const jsonSchema = zodToJsonSchema(schema);
1879
2106
  const response = await executeWithMiddleware({
1880
2107
  ...rest,
1881
- system: systemPrompt
2108
+ schema,
2109
+ system: [
2110
+ rest.system ?? "",
2111
+ "You MUST respond with valid JSON matching this schema:",
2112
+ JSON.stringify(jsonSchema, null, 2),
2113
+ "Respond ONLY with the JSON object, no markdown or explanation."
2114
+ ].filter(Boolean).join(`
2115
+
2116
+ `)
1882
2117
  });
1883
- const text = typeof response.message.content === "string" ? response.message.content : "";
1884
- const jsonMatch = text.match(/\{[\s\S]*\}/);
1885
- if (!jsonMatch) {
1886
- throw ElsiumError.validation("LLM response did not contain valid JSON", {
1887
- response: text
1888
- });
1889
- }
1890
- const parsed = JSON.parse(jsonMatch[0]);
2118
+ const parsed = extractFromToolCalls(response) ?? extractJsonFromText(response);
1891
2119
  const result = schema.safeParse(parsed);
1892
2120
  if (!result.success) {
1893
2121
  throw ElsiumError.validation("LLM response did not match schema", {
1894
- errors: result.error.issues,
1895
- response: text
2122
+ errors: result.error.issues
1896
2123
  });
1897
2124
  }
1898
2125
  return { data: result.data, response };
1899
2126
  }
1900
2127
  };
1901
2128
  }
1902
- function schemaToJsonSchema(schema) {
1903
- try {
1904
- if ("_def" in schema) {
1905
- const def = schema._def;
1906
- const result = convertZodDef(def);
1907
- if (result)
1908
- return result;
1909
- }
1910
- } catch {}
1911
- return { type: "string" };
1912
- }
1913
- function zodDefKind(def) {
1914
- return typeof def.type === "string" ? def.type : def.typeName;
1915
- }
1916
- function convertZodDef(def) {
1917
- const kind = zodDefKind(def);
1918
- switch (kind) {
1919
- case "object":
1920
- case "ZodObject":
1921
- return convertZodObject(def);
1922
- case "string":
1923
- case "ZodString":
1924
- return { type: "string" };
1925
- case "number":
1926
- case "ZodNumber":
1927
- return { type: "number" };
1928
- case "boolean":
1929
- case "ZodBoolean":
1930
- return { type: "boolean" };
1931
- case "array":
1932
- case "ZodArray":
1933
- return convertZodArray(def);
1934
- case "enum":
1935
- case "ZodEnum": {
1936
- const values = def.values ?? (def.entries ? Object.values(def.entries) : []);
1937
- return { type: "string", enum: values };
1938
- }
1939
- case "optional":
1940
- case "ZodOptional":
1941
- return convertZodOptional(def);
1942
- default:
1943
- return null;
1944
- }
1945
- }
1946
- function convertZodObject(def) {
1947
- if (!def.shape)
1948
- return null;
1949
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1950
- const properties = {};
1951
- const required = [];
1952
- for (const [key, value] of Object.entries(shape)) {
1953
- properties[key] = schemaToJsonSchema(value);
1954
- const valDef = value._def;
1955
- const valKind = zodDefKind(valDef);
1956
- if (valKind !== "optional" && valKind !== "ZodOptional") {
1957
- required.push(key);
1958
- }
1959
- }
1960
- return { type: "object", properties, required };
1961
- }
1962
- function convertZodArray(def) {
1963
- return {
1964
- type: "array",
1965
- items: schemaToJsonSchema(def.element ?? def.type)
1966
- };
1967
- }
1968
- function convertZodOptional(def) {
1969
- return schemaToJsonSchema(def.innerType ?? def.innerType);
1970
- }
2129
+ // ../gateway/src/cache.ts
2130
+ var log4 = createLogger();
2131
+ // ../gateway/src/output-guardrails.ts
2132
+ var log5 = createLogger();
2133
+ // ../gateway/src/batch.ts
2134
+ var log6 = createLogger();
1971
2135
  // ../observe/src/span.ts
1972
2136
  function createSpan(name, options = {}) {
1973
2137
  const id = generateId("spn");
@@ -2038,7 +2202,7 @@ function createSpan(name, options = {}) {
2038
2202
  }
2039
2203
  // ../observe/src/tracer.ts
2040
2204
  import { writeFileSync } from "node:fs";
2041
- var log2 = createLogger();
2205
+ var log7 = createLogger();
2042
2206
  function observe(config = {}) {
2043
2207
  const {
2044
2208
  output = ["console"],
@@ -2061,7 +2225,7 @@ function observe(config = {}) {
2061
2225
  try {
2062
2226
  writeFileSync(filename, JSON.stringify(spansToExport, null, 2));
2063
2227
  } catch (err2) {
2064
- log2.error("Failed to write trace file", {
2228
+ log7.error("Failed to write trace file", {
2065
2229
  error: err2 instanceof Error ? err2.message : String(err2)
2066
2230
  });
2067
2231
  }
@@ -2136,7 +2300,7 @@ function observe(config = {}) {
2136
2300
  function consoleHandler(span) {
2137
2301
  const duration = span.durationMs !== undefined ? `${span.durationMs}ms` : "running";
2138
2302
  const status = span.status === "error" ? "[ERROR]" : span.status === "ok" ? "[OK]" : "[...]";
2139
- log2.info("span", {
2303
+ log7.info("span", {
2140
2304
  trace: span.traceId,
2141
2305
  span: span.name,
2142
2306
  kind: span.kind,
@@ -2173,9 +2337,11 @@ function createNoopSpan(name, kind) {
2173
2337
  }
2174
2338
  };
2175
2339
  }
2340
+ // ../observe/src/experiment.ts
2341
+ var log8 = createLogger();
2176
2342
  // ../observe/src/otel.ts
2177
- var log3 = createLogger();
2178
- // ../../node_modules/.bun/@hono+node-server@1.19.9/node_modules/@hono/node-server/dist/index.mjs
2343
+ var log9 = createLogger();
2344
+ // ../../node_modules/.bun/@hono+node-server@1.19.10/node_modules/@hono/node-server/dist/index.mjs
2179
2345
  import { createServer as createServerHTTP } from "http";
2180
2346
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
2181
2347
  import { Http2ServerRequest } from "http2";
@@ -2709,7 +2875,7 @@ var serve = (options, listeningListener) => {
2709
2875
  return server;
2710
2876
  };
2711
2877
 
2712
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/compose.js
2878
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/compose.js
2713
2879
  var compose = (middleware, onError, onNotFound) => {
2714
2880
  return (context, next) => {
2715
2881
  let index = -1;
@@ -2753,10 +2919,10 @@ var compose = (middleware, onError, onNotFound) => {
2753
2919
  };
2754
2920
  };
2755
2921
 
2756
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/request/constants.js
2922
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/request/constants.js
2757
2923
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
2758
2924
 
2759
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/utils/body.js
2925
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/body.js
2760
2926
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
2761
2927
  const { all = false, dot = false } = options;
2762
2928
  const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
@@ -2824,7 +2990,7 @@ var handleParsingNestedValues = (form, key, value) => {
2824
2990
  });
2825
2991
  };
2826
2992
 
2827
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/utils/url.js
2993
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/url.js
2828
2994
  var splitPath = (path) => {
2829
2995
  const paths = path.split("/");
2830
2996
  if (paths[0] === "") {
@@ -3024,7 +3190,7 @@ var getQueryParams = (url, key) => {
3024
3190
  };
3025
3191
  var decodeURIComponent_ = decodeURIComponent;
3026
3192
 
3027
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/request.js
3193
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/request.js
3028
3194
  var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
3029
3195
  var HonoRequest = class {
3030
3196
  raw;
@@ -3135,7 +3301,7 @@ var HonoRequest = class {
3135
3301
  }
3136
3302
  };
3137
3303
 
3138
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/utils/html.js
3304
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/html.js
3139
3305
  var HtmlEscapedCallbackPhase = {
3140
3306
  Stringify: 1,
3141
3307
  BeforeStream: 2,
@@ -3173,7 +3339,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
3173
3339
  }
3174
3340
  };
3175
3341
 
3176
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/context.js
3342
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/context.js
3177
3343
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
3178
3344
  var setDefaultContentType = (contentType, headers) => {
3179
3345
  return {
@@ -3340,7 +3506,7 @@ var Context = class {
3340
3506
  };
3341
3507
  };
3342
3508
 
3343
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router.js
3509
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router.js
3344
3510
  var METHOD_NAME_ALL = "ALL";
3345
3511
  var METHOD_NAME_ALL_LOWERCASE = "all";
3346
3512
  var METHODS = ["get", "post", "put", "delete", "options", "patch"];
@@ -3348,10 +3514,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
3348
3514
  var UnsupportedPathError = class extends Error {
3349
3515
  };
3350
3516
 
3351
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/utils/constants.js
3517
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/constants.js
3352
3518
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
3353
3519
 
3354
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/hono-base.js
3520
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/hono-base.js
3355
3521
  var notFoundHandler = (c) => {
3356
3522
  return c.text("404 Not Found", 404);
3357
3523
  };
@@ -3570,7 +3736,7 @@ var Hono = class _Hono {
3570
3736
  };
3571
3737
  };
3572
3738
 
3573
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/reg-exp-router/matcher.js
3739
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/reg-exp-router/matcher.js
3574
3740
  var emptyParam = [];
3575
3741
  function match(method, path) {
3576
3742
  const matchers = this.buildAllMatchers();
@@ -3591,7 +3757,7 @@ function match(method, path) {
3591
3757
  return match2(method, path);
3592
3758
  }
3593
3759
 
3594
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/reg-exp-router/node.js
3760
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/reg-exp-router/node.js
3595
3761
  var LABEL_REG_EXP_STR = "[^/]+";
3596
3762
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
3597
3763
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -3695,7 +3861,7 @@ var Node = class _Node {
3695
3861
  }
3696
3862
  };
3697
3863
 
3698
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/reg-exp-router/trie.js
3864
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/reg-exp-router/trie.js
3699
3865
  var Trie = class {
3700
3866
  #context = { varIndex: 0 };
3701
3867
  #root = new Node;
@@ -3751,7 +3917,7 @@ var Trie = class {
3751
3917
  }
3752
3918
  };
3753
3919
 
3754
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/reg-exp-router/router.js
3920
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/reg-exp-router/router.js
3755
3921
  var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
3756
3922
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
3757
3923
  function buildWildcardRegExp(path) {
@@ -3916,7 +4082,7 @@ var RegExpRouter = class {
3916
4082
  }
3917
4083
  };
3918
4084
 
3919
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
4085
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
3920
4086
  var PreparedRegExpRouter = class {
3921
4087
  name = "PreparedRegExpRouter";
3922
4088
  #matchers;
@@ -3988,7 +4154,7 @@ var PreparedRegExpRouter = class {
3988
4154
  match = match;
3989
4155
  };
3990
4156
 
3991
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/smart-router/router.js
4157
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/smart-router/router.js
3992
4158
  var SmartRouter = class {
3993
4159
  name = "SmartRouter";
3994
4160
  #routers = [];
@@ -4043,7 +4209,7 @@ var SmartRouter = class {
4043
4209
  }
4044
4210
  };
4045
4211
 
4046
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/trie-router/node.js
4212
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/trie-router/node.js
4047
4213
  var emptyParams = /* @__PURE__ */ Object.create(null);
4048
4214
  var hasChildren = (children) => {
4049
4215
  for (const _ in children) {
@@ -4212,7 +4378,7 @@ var Node2 = class _Node2 {
4212
4378
  }
4213
4379
  };
4214
4380
 
4215
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/router/trie-router/router.js
4381
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/router/trie-router/router.js
4216
4382
  var TrieRouter = class {
4217
4383
  name = "TrieRouter";
4218
4384
  #node;
@@ -4234,7 +4400,7 @@ var TrieRouter = class {
4234
4400
  }
4235
4401
  };
4236
4402
 
4237
- // ../../node_modules/.bun/hono@4.12.3/node_modules/hono/dist/hono.js
4403
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/hono.js
4238
4404
  var Hono2 = class extends Hono {
4239
4405
  constructor(options = {}) {
4240
4406
  super(options);
@@ -4334,12 +4500,12 @@ function requestIdMiddleware() {
4334
4500
  };
4335
4501
  }
4336
4502
  function requestLoggerMiddleware(logger) {
4337
- const log4 = logger ?? createLogger();
4503
+ const log10 = logger ?? createLogger();
4338
4504
  return async (c, next) => {
4339
4505
  const start = Date.now();
4340
4506
  await next();
4341
4507
  const duration = Date.now() - start;
4342
- log4.info(`${c.req.method} ${c.req.path}`, {
4508
+ log10.info(`${c.req.method} ${c.req.path}`, {
4343
4509
  method: c.req.method,
4344
4510
  path: c.req.path,
4345
4511
  status: c.res.status,
@@ -4349,6 +4515,158 @@ function requestLoggerMiddleware(logger) {
4349
4515
  };
4350
4516
  }
4351
4517
 
4518
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/stream.js
4519
+ var StreamingApi = class {
4520
+ writer;
4521
+ encoder;
4522
+ writable;
4523
+ abortSubscribers = [];
4524
+ responseReadable;
4525
+ aborted = false;
4526
+ closed = false;
4527
+ constructor(writable, _readable) {
4528
+ this.writable = writable;
4529
+ this.writer = writable.getWriter();
4530
+ this.encoder = new TextEncoder;
4531
+ const reader = _readable.getReader();
4532
+ this.abortSubscribers.push(async () => {
4533
+ await reader.cancel();
4534
+ });
4535
+ this.responseReadable = new ReadableStream({
4536
+ async pull(controller) {
4537
+ const { done, value } = await reader.read();
4538
+ done ? controller.close() : controller.enqueue(value);
4539
+ },
4540
+ cancel: () => {
4541
+ this.abort();
4542
+ }
4543
+ });
4544
+ }
4545
+ async write(input) {
4546
+ try {
4547
+ if (typeof input === "string") {
4548
+ input = this.encoder.encode(input);
4549
+ }
4550
+ await this.writer.write(input);
4551
+ } catch {}
4552
+ return this;
4553
+ }
4554
+ async writeln(input) {
4555
+ await this.write(input + `
4556
+ `);
4557
+ return this;
4558
+ }
4559
+ sleep(ms) {
4560
+ return new Promise((res) => setTimeout(res, ms));
4561
+ }
4562
+ async close() {
4563
+ try {
4564
+ await this.writer.close();
4565
+ } catch {}
4566
+ this.closed = true;
4567
+ }
4568
+ async pipe(body) {
4569
+ this.writer.releaseLock();
4570
+ await body.pipeTo(this.writable, { preventClose: true });
4571
+ this.writer = this.writable.getWriter();
4572
+ }
4573
+ onAbort(listener) {
4574
+ this.abortSubscribers.push(listener);
4575
+ }
4576
+ abort() {
4577
+ if (!this.aborted) {
4578
+ this.aborted = true;
4579
+ this.abortSubscribers.forEach((subscriber) => subscriber());
4580
+ }
4581
+ }
4582
+ };
4583
+
4584
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/helper/streaming/utils.js
4585
+ var isOldBunVersion = () => {
4586
+ const version = typeof Bun !== "undefined" ? Bun.version : undefined;
4587
+ if (version === undefined) {
4588
+ return false;
4589
+ }
4590
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
4591
+ isOldBunVersion = () => result;
4592
+ return result;
4593
+ };
4594
+
4595
+ // ../../node_modules/.bun/hono@4.12.5/node_modules/hono/dist/helper/streaming/stream.js
4596
+ var contextStash = /* @__PURE__ */ new WeakMap;
4597
+ var stream = (c, cb, onError) => {
4598
+ const { readable, writable } = new TransformStream;
4599
+ const stream2 = new StreamingApi(writable, readable);
4600
+ if (isOldBunVersion()) {
4601
+ c.req.raw.signal.addEventListener("abort", () => {
4602
+ if (!stream2.closed) {
4603
+ stream2.abort();
4604
+ }
4605
+ });
4606
+ }
4607
+ contextStash.set(stream2.responseReadable, c);
4608
+ (async () => {
4609
+ try {
4610
+ await cb(stream2);
4611
+ } catch (e) {
4612
+ if (e === undefined) {} else if (e instanceof Error && onError) {
4613
+ await onError(e, stream2);
4614
+ } else {
4615
+ console.error(e);
4616
+ }
4617
+ } finally {
4618
+ stream2.close();
4619
+ }
4620
+ })();
4621
+ return c.newResponse(stream2.responseReadable);
4622
+ };
4623
+
4624
+ // src/sse.ts
4625
+ function sseHeaders() {
4626
+ return {
4627
+ "Content-Type": "text/event-stream",
4628
+ "Cache-Control": "no-cache",
4629
+ Connection: "keep-alive",
4630
+ "X-Accel-Buffering": "no"
4631
+ };
4632
+ }
4633
+ function formatSSE(event, data) {
4634
+ const json = JSON.stringify(data);
4635
+ if (event === "message") {
4636
+ return `data: ${json}
4637
+
4638
+ `;
4639
+ }
4640
+ return `event: ${event}
4641
+ data: ${json}
4642
+
4643
+ `;
4644
+ }
4645
+ function streamResponse(c, source) {
4646
+ const headers = sseHeaders();
4647
+ for (const [key, value] of Object.entries(headers)) {
4648
+ c.header(key, value);
4649
+ }
4650
+ return stream(c, async (s) => {
4651
+ try {
4652
+ for await (const event of source) {
4653
+ const sseData = formatSSE("message", event);
4654
+ await s.write(sseData);
4655
+ }
4656
+ } catch (err2) {
4657
+ const errorEvent = {
4658
+ type: "error",
4659
+ error: err2 instanceof Error ? err2 : new Error(String(err2))
4660
+ };
4661
+ const sseData = formatSSE("error", {
4662
+ type: "error",
4663
+ message: errorEvent.error.message
4664
+ });
4665
+ await s.write(sseData);
4666
+ }
4667
+ });
4668
+ }
4669
+
4352
4670
  // src/routes.ts
4353
4671
  function parseJsonBody(raw2) {
4354
4672
  try {
@@ -4369,6 +4687,31 @@ function resolveAgent(name, agents, defaultAgent) {
4369
4687
  return { agent };
4370
4688
  return { error: name ? `Agent "${name}" not found` : "No default agent configured" };
4371
4689
  }
4690
+ var MAX_BODY_SIZE = 1048576;
4691
+ function parseRequestBody(c, rawText) {
4692
+ if (rawText.length > MAX_BODY_SIZE) {
4693
+ return { ok: false, response: c.json({ error: "Request body too large (max 1MB)" }, 413) };
4694
+ }
4695
+ const parsed = parseJsonBody(rawText);
4696
+ if (!parsed.ok) {
4697
+ return { ok: false, response: c.json({ error: "Invalid JSON in request body" }, 400) };
4698
+ }
4699
+ return { ok: true, data: parsed.data };
4700
+ }
4701
+ function buildChatResponse(result, model) {
4702
+ const content = typeof result.message.content === "string" ? result.message.content : "";
4703
+ return {
4704
+ message: content,
4705
+ usage: {
4706
+ inputTokens: result.usage.totalInputTokens,
4707
+ outputTokens: result.usage.totalOutputTokens,
4708
+ totalTokens: result.usage.totalTokens,
4709
+ cost: result.usage.totalCost
4710
+ },
4711
+ model: model ?? "default",
4712
+ traceId: result.traceId
4713
+ };
4714
+ }
4372
4715
  function createRoutes(deps) {
4373
4716
  const app = new Hono2;
4374
4717
  let totalRequests = 0;
@@ -4404,15 +4747,10 @@ function createRoutes(deps) {
4404
4747
  });
4405
4748
  app.post("/chat", async (c) => {
4406
4749
  totalRequests++;
4407
- const MAX_BODY_SIZE = 1048576;
4408
4750
  const rawText = await c.req.text();
4409
- if (rawText.length > MAX_BODY_SIZE) {
4410
- return c.json({ error: "Request body too large (max 1MB)" }, 413);
4411
- }
4412
- const parsed = parseJsonBody(rawText);
4413
- if (!parsed.ok) {
4414
- return c.json({ error: "Invalid JSON in request body" }, 400);
4415
- }
4751
+ const parsed = parseRequestBody(c, rawText);
4752
+ if (!parsed.ok)
4753
+ return parsed.response;
4416
4754
  const body = parsed.data;
4417
4755
  if (!body.message) {
4418
4756
  return c.json({ error: "message is required" }, 400);
@@ -4421,6 +4759,14 @@ function createRoutes(deps) {
4421
4759
  if ("error" in resolved) {
4422
4760
  return c.json({ error: resolved.error }, 404);
4423
4761
  }
4762
+ if (body.stream) {
4763
+ const stream2 = deps.gateway.stream({
4764
+ messages: [{ role: "user", content: body.message }],
4765
+ system: resolved.agent.config.system,
4766
+ model: resolved.agent.config.model
4767
+ });
4768
+ return streamResponse(c, stream2);
4769
+ }
4424
4770
  let result;
4425
4771
  try {
4426
4772
  result = await resolved.agent.run(body.message);
@@ -4428,37 +4774,20 @@ function createRoutes(deps) {
4428
4774
  return elsiumErrorResponse(c, err2, "Agent execution failed");
4429
4775
  }
4430
4776
  deps.tracer?.trackLLMCall({
4431
- model: "unknown",
4777
+ model: resolved.agent.config.model ?? "unknown",
4432
4778
  inputTokens: result.usage.totalInputTokens,
4433
4779
  outputTokens: result.usage.totalOutputTokens,
4434
4780
  cost: result.usage.totalCost,
4435
4781
  latencyMs: 0
4436
4782
  });
4437
- const content = typeof result.message.content === "string" ? result.message.content : "";
4438
- const response = {
4439
- message: content,
4440
- usage: {
4441
- inputTokens: result.usage.totalInputTokens,
4442
- outputTokens: result.usage.totalOutputTokens,
4443
- totalTokens: result.usage.totalTokens,
4444
- cost: result.usage.totalCost
4445
- },
4446
- model: resolved.agent.config.model ?? "default",
4447
- traceId: result.traceId
4448
- };
4449
- return c.json(response);
4783
+ return c.json(buildChatResponse(result, resolved.agent.config.model));
4450
4784
  });
4451
4785
  app.post("/complete", async (c) => {
4452
4786
  totalRequests++;
4453
- const MAX_BODY_SIZE = 1048576;
4454
4787
  const rawText = await c.req.text();
4455
- if (rawText.length > MAX_BODY_SIZE) {
4456
- return c.json({ error: "Request body too large (max 1MB)" }, 413);
4457
- }
4458
- const parsed = parseJsonBody(rawText);
4459
- if (!parsed.ok) {
4460
- return c.json({ error: "Invalid JSON in request body" }, 400);
4461
- }
4788
+ const parsed = parseRequestBody(c, rawText);
4789
+ if (!parsed.ok)
4790
+ return parsed.response;
4462
4791
  const body = parsed.data;
4463
4792
  if (!body.messages?.length) {
4464
4793
  return c.json({ error: "messages array is required" }, 400);
@@ -4467,6 +4796,16 @@ function createRoutes(deps) {
4467
4796
  role: m.role,
4468
4797
  content: m.content
4469
4798
  }));
4799
+ if (body.stream) {
4800
+ const stream2 = deps.gateway.stream({
4801
+ messages,
4802
+ model: body.model,
4803
+ system: body.system,
4804
+ maxTokens: body.maxTokens,
4805
+ temperature: body.temperature
4806
+ });
4807
+ return streamResponse(c, stream2);
4808
+ }
4470
4809
  let response;
4471
4810
  try {
4472
4811
  response = await deps.gateway.complete({
@@ -4507,13 +4846,13 @@ function createRoutes(deps) {
4507
4846
  }
4508
4847
 
4509
4848
  // src/app.ts
4510
- var log4 = createLogger();
4849
+ var log10 = createLogger();
4511
4850
  function createApp(config) {
4512
4851
  const app = new Hono2;
4513
4852
  app.onError((err2, c) => {
4514
4853
  const statusCode = err2 instanceof ElsiumError ? err2.statusCode ?? 500 : 500;
4515
4854
  const code = err2 instanceof ElsiumError ? err2.code : "UNKNOWN";
4516
- log4.error("Unhandled error", { error: err2.message, code, path: c.req.path });
4855
+ log10.error("Unhandled error", { error: err2.message, code, path: c.req.path });
4517
4856
  return c.json({ error: err2.message, code }, statusCode);
4518
4857
  });
4519
4858
  app.notFound((c) => {
@@ -4534,7 +4873,7 @@ function createApp(config) {
4534
4873
  });
4535
4874
  const serverConfig = config.server ?? {};
4536
4875
  app.use("*", requestIdMiddleware());
4537
- app.use("*", requestLoggerMiddleware(log4));
4876
+ app.use("*", requestLoggerMiddleware(log10));
4538
4877
  if (serverConfig.cors) {
4539
4878
  app.use("*", corsMiddleware(serverConfig.cors));
4540
4879
  }
@@ -4578,11 +4917,11 @@ function createApp(config) {
4578
4917
  const drainTimeoutMs = typeof serverConfig.gracefulShutdown === "object" ? serverConfig.gracefulShutdown.drainTimeoutMs : undefined;
4579
4918
  shutdownManager = createShutdownManager({
4580
4919
  drainTimeoutMs,
4581
- onDrainStart: () => log4.info("Draining connections..."),
4582
- onDrainComplete: () => log4.info("Drain complete")
4920
+ onDrainStart: () => log10.info("Draining connections..."),
4921
+ onDrainComplete: () => log10.info("Drain complete")
4583
4922
  });
4584
4923
  }
4585
- log4.info("ElsiumAI server started", {
4924
+ log10.info("ElsiumAI server started", {
4586
4925
  url: `http://${hostname}:${listenPort}`,
4587
4926
  routes: ["POST /chat", "POST /complete", "GET /health", "GET /metrics", "GET /agents"]
4588
4927
  });
@@ -4599,7 +4938,7 @@ function createApp(config) {
4599
4938
  };
4600
4939
  }
4601
4940
  // src/rbac.ts
4602
- var log5 = createLogger();
4941
+ var log11 = createLogger();
4603
4942
  var BUILT_IN_ROLES = [
4604
4943
  {
4605
4944
  name: "admin",
@@ -4637,7 +4976,7 @@ function matchPermission(granted, required) {
4637
4976
  }
4638
4977
  function createRBAC(config) {
4639
4978
  if (config.trustRoleHeader === true) {
4640
- 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.");
4979
+ 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.");
4641
4980
  }
4642
4981
  const roleMap = new Map;
4643
4982
  for (const role of BUILT_IN_ROLES) {
@@ -4693,10 +5032,113 @@ function createRBAC(config) {
4693
5032
  }
4694
5033
  };
4695
5034
  }
5035
+ // src/tenant.ts
5036
+ var log12 = createLogger();
5037
+ var tenantUsage = new Map;
5038
+ function tenantMiddleware(config) {
5039
+ const { extractTenant, onUnknownTenant = "reject", defaultTenant } = config;
5040
+ return async (c, next) => {
5041
+ const tenant = extractTenant(c);
5042
+ if (!tenant) {
5043
+ if (onUnknownTenant === "default" && defaultTenant) {
5044
+ c.set("tenant", defaultTenant);
5045
+ log12.debug("Using default tenant", { tenantId: defaultTenant.tenantId });
5046
+ } else {
5047
+ return c.json({ error: "Tenant identification required" }, 401);
5048
+ }
5049
+ } else {
5050
+ c.set("tenant", tenant);
5051
+ log12.debug("Tenant identified", { tenantId: tenant.tenantId });
5052
+ }
5053
+ await next();
5054
+ };
5055
+ }
5056
+ function tenantRateLimitMiddleware() {
5057
+ const windows = new Map;
5058
+ return async (c, next) => {
5059
+ const tenant = c.get("tenant");
5060
+ if (!tenant?.limits?.maxRequestsPerMinute) {
5061
+ await next();
5062
+ return;
5063
+ }
5064
+ const limit = tenant.limits.maxRequestsPerMinute;
5065
+ const now = Date.now();
5066
+ const windowMs = 60000;
5067
+ const key = tenant.tenantId;
5068
+ let entry = windows.get(key);
5069
+ if (!entry || now - entry.windowStart > windowMs) {
5070
+ entry = { count: 0, windowStart: now };
5071
+ windows.set(key, entry);
5072
+ }
5073
+ entry.count++;
5074
+ if (entry.count > limit) {
5075
+ return c.json({
5076
+ error: "Rate limit exceeded",
5077
+ retryAfterMs: windowMs - (now - entry.windowStart)
5078
+ }, 429);
5079
+ }
5080
+ await next();
5081
+ };
5082
+ }
5083
+ function getOrCreateUsage(tenantId) {
5084
+ let usage = tenantUsage.get(tenantId);
5085
+ if (!usage) {
5086
+ const now = Date.now();
5087
+ usage = {
5088
+ minute: { tokens: 0, cost: 0, windowStart: now },
5089
+ day: { tokens: 0, cost: 0, windowStart: now }
5090
+ };
5091
+ tenantUsage.set(tenantId, usage);
5092
+ }
5093
+ return usage;
5094
+ }
5095
+ function resetWindowIfExpired(window, durationMs) {
5096
+ const now = Date.now();
5097
+ if (now - window.windowStart > durationMs) {
5098
+ window.tokens = 0;
5099
+ window.cost = 0;
5100
+ window.windowStart = now;
5101
+ }
5102
+ }
5103
+ function tenantBudgetMiddleware() {
5104
+ return async (c, next) => {
5105
+ const tenant = c.get("tenant");
5106
+ if (!tenant?.limits) {
5107
+ await next();
5108
+ return;
5109
+ }
5110
+ const usage = getOrCreateUsage(tenant.tenantId);
5111
+ resetWindowIfExpired(usage.minute, 60000);
5112
+ resetWindowIfExpired(usage.day, 86400000);
5113
+ if (tenant.limits.maxTokensPerMinute && usage.minute.tokens >= tenant.limits.maxTokensPerMinute) {
5114
+ return c.json({ error: "Token rate limit exceeded", retryAfterMs: 60000 }, 429);
5115
+ }
5116
+ if (tenant.limits.maxCostPerDay && usage.day.cost >= tenant.limits.maxCostPerDay) {
5117
+ return c.json({ error: "Daily cost limit exceeded" }, 429);
5118
+ }
5119
+ await next();
5120
+ const tokenCount = Number(c.res.headers.get("x-token-count")) || 0;
5121
+ const cost = Number(c.res.headers.get("x-cost")) || 0;
5122
+ if (tokenCount > 0) {
5123
+ usage.minute.tokens += tokenCount;
5124
+ usage.day.tokens += tokenCount;
5125
+ }
5126
+ if (cost > 0) {
5127
+ usage.minute.cost += cost;
5128
+ usage.day.cost += cost;
5129
+ }
5130
+ };
5131
+ }
4696
5132
  export {
5133
+ tenantRateLimitMiddleware,
5134
+ tenantMiddleware,
5135
+ tenantBudgetMiddleware,
5136
+ streamResponse,
5137
+ sseHeaders,
4697
5138
  requestLoggerMiddleware,
4698
5139
  requestIdMiddleware,
4699
5140
  rateLimitMiddleware,
5141
+ formatSSE,
4700
5142
  createRoutes,
4701
5143
  createRBAC,
4702
5144
  createApp,