@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/README.md +98 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +715 -273
- package/dist/routes.d.ts.map +1 -1
- package/dist/sse.d.ts +6 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/tenant.d.ts +18 -0
- package/dist/tenant.d.ts.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -10
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
|
|
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
|
-
|
|
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:
|
|
755
|
-
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
|
|
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
|
|
870
|
-
|
|
871
|
-
|
|
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
|
|
889
|
-
|
|
890
|
-
const
|
|
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
|
-
|
|
906
|
-
|
|
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
|
-
}
|
|
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
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
|
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
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
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: ${
|
|
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
|
|
2073
|
+
const ctx2 = buildMiddlewareContext(req, provider.name, defaultModel, request.metadata ?? {});
|
|
1848
2074
|
return createStream(async (emit) => {
|
|
1849
|
-
await composedMiddleware(
|
|
2075
|
+
await composedMiddleware(ctx2, async (c) => {
|
|
1850
2076
|
const result = await accumulateStreamEvents(provider.stream(c.request), emit);
|
|
1851
|
-
const latencyMs = Math.round(performance.now() -
|
|
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:
|
|
2087
|
+
traceId: ctx2.traceId
|
|
1862
2088
|
};
|
|
1863
2089
|
});
|
|
1864
2090
|
});
|
|
1865
2091
|
}
|
|
1866
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2178
|
-
// ../../node_modules/.bun/@hono+node-server@1.19.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4456
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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: () =>
|
|
4582
|
-
onDrainComplete: () =>
|
|
4920
|
+
onDrainStart: () => log10.info("Draining connections..."),
|
|
4921
|
+
onDrainComplete: () => log10.info("Drain complete")
|
|
4583
4922
|
});
|
|
4584
4923
|
}
|
|
4585
|
-
|
|
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
|
|
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
|
-
|
|
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,
|