@elsium-ai/gateway 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/batch.d.ts +24 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/gateway.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +515 -95
- package/dist/output-guardrails.d.ts +23 -0
- package/dist/output-guardrails.d.ts.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CompletionRequest, LLMResponse } from '@elsium-ai/core';
|
|
2
|
+
import type { Gateway } from './gateway';
|
|
3
|
+
export interface BatchConfig {
|
|
4
|
+
concurrency?: number;
|
|
5
|
+
retryPerItem?: number;
|
|
6
|
+
onProgress?: (completed: number, total: number) => void;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
export interface BatchResultItem {
|
|
10
|
+
index: number;
|
|
11
|
+
success: boolean;
|
|
12
|
+
response?: LLMResponse;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface BatchResult {
|
|
16
|
+
results: BatchResultItem[];
|
|
17
|
+
totalSucceeded: number;
|
|
18
|
+
totalFailed: number;
|
|
19
|
+
totalDurationMs: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function createBatch(gateway: Gateway, config?: BatchConfig): {
|
|
22
|
+
execute(requests: CompletionRequest[]): Promise<BatchResult>;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAErE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAIxC,MAAM,WAAW,WAAW;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,MAAM,CAAC,EAAE,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,WAAW,CAC1B,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,WAAW,GAClB;IAAE,OAAO,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAAE,CAiGlE"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LLMResponse, Middleware, MiddlewareContext } from '@elsium-ai/core';
|
|
2
|
+
export interface CacheAdapter {
|
|
3
|
+
get(key: string): Promise<LLMResponse | null>;
|
|
4
|
+
set(key: string, value: LLMResponse, ttlMs: number): Promise<void>;
|
|
5
|
+
delete(key: string): Promise<void>;
|
|
6
|
+
clear(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface CacheStats {
|
|
9
|
+
hits: number;
|
|
10
|
+
misses: number;
|
|
11
|
+
size: number;
|
|
12
|
+
hitRate: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CacheMiddlewareConfig {
|
|
15
|
+
adapter?: CacheAdapter;
|
|
16
|
+
ttlMs?: number;
|
|
17
|
+
maxSize?: number;
|
|
18
|
+
keyFn?: (ctx: MiddlewareContext) => string;
|
|
19
|
+
shouldCache?: (ctx: MiddlewareContext, response: LLMResponse) => boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function createInMemoryCache(maxSize?: number): CacheAdapter;
|
|
22
|
+
export declare function cacheMiddleware(config?: CacheMiddlewareConfig): Middleware & {
|
|
23
|
+
readonly adapter: CacheAdapter;
|
|
24
|
+
stats(): CacheStats;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAKjF,MAAM,WAAW,YAAY;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,qBAAqB;IACrC,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,MAAM,CAAA;IAC1C,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,KAAK,OAAO,CAAA;CACxE;AAOD,wBAAgB,mBAAmB,CAAC,OAAO,SAAO,GAAG,YAAY,CAuChE;AAmBD,wBAAgB,eAAe,CAC9B,MAAM,CAAC,EAAE,qBAAqB,GAC5B,UAAU,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAAC,KAAK,IAAI,UAAU,CAAA;CAAE,CA8CtE"}
|
package/dist/gateway.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,iBAAiB,EACjB,WAAW,EACX,UAAU,EAEV,cAAc,EAEd,QAAQ,EACR,MAAM,iBAAiB,CAAA;AACxB,OAAO,
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,iBAAiB,EACjB,WAAW,EACX,UAAU,EAEV,cAAc,EAEd,QAAQ,EACR,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEN,KAAK,YAAY,EAIjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAI5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAM7C,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1D,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CAAA;IAChD,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,GAAG;QAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC;QAC3E,IAAI,EAAE,CAAC,CAAA;QACP,QAAQ,EAAE,WAAW,CAAA;KACrB,CAAC,CAAA;IACF,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,IAAI,QAAQ,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;CACvC;AAQD,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,WAAW,GAC9C,IAAI,CAEN;AAiID,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CA0ItD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,13 @@ export { securityMiddleware, detectPromptInjection, detectJailbreak, redactSecre
|
|
|
11
11
|
export type { SecurityMiddlewareConfig, SecurityViolation, SecurityResult, DataClassification, ClassificationResult, } from './security';
|
|
12
12
|
export { createBulkhead, bulkheadMiddleware } from './bulkhead';
|
|
13
13
|
export type { BulkheadConfig, Bulkhead } from './bulkhead';
|
|
14
|
+
export { cacheMiddleware, createInMemoryCache } from './cache';
|
|
15
|
+
export type { CacheAdapter, CacheStats, CacheMiddlewareConfig } from './cache';
|
|
16
|
+
export { outputGuardrailMiddleware } from './output-guardrails';
|
|
17
|
+
export type { OutputGuardrailConfig, OutputGuardrailRule, OutputViolation, } from './output-guardrails';
|
|
14
18
|
export { calculateCost, registerPricing } from './pricing';
|
|
19
|
+
export { createBatch } from './batch';
|
|
20
|
+
export type { BatchConfig, BatchResult, BatchResultItem } from './batch';
|
|
15
21
|
export { createProviderMesh } from './router';
|
|
16
22
|
export type { ProviderMeshConfig, ProviderEntry, RoutingStrategy, ProviderMesh } from './router';
|
|
17
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGvD,YAAY,EACX,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,SAAS,GACT,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,mBAAmB,GACnB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAGzD,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,GACd,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAG7C,OAAO,EACN,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EACX,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACpB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/D,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG1D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGvD,YAAY,EACX,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,SAAS,GACT,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,mBAAmB,GACnB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAGzD,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,GACd,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAG7C,OAAO,EACN,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EACX,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACpB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/D,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG1D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAC9D,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAG9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EACX,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,GACf,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -394,6 +394,94 @@ function createLogger(options = {}) {
|
|
|
394
394
|
}
|
|
395
395
|
};
|
|
396
396
|
}
|
|
397
|
+
// ../core/src/schema.ts
|
|
398
|
+
var log = createLogger();
|
|
399
|
+
function zodDefKind(def) {
|
|
400
|
+
return typeof def.type === "string" ? def.type : def.typeName;
|
|
401
|
+
}
|
|
402
|
+
function zodObjectToJsonSchema(schema, convert) {
|
|
403
|
+
const shape = typeof schema.shape === "function" ? schema.shape() : schema.shape;
|
|
404
|
+
const properties = {};
|
|
405
|
+
const required = [];
|
|
406
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
407
|
+
const fieldSchema = value;
|
|
408
|
+
properties[key] = convert(fieldSchema);
|
|
409
|
+
const fieldDef = fieldSchema._def;
|
|
410
|
+
const fieldKind = zodDefKind(fieldDef);
|
|
411
|
+
if (fieldKind !== "optional" && fieldKind !== "ZodOptional" && fieldKind !== "default" && fieldKind !== "ZodDefault") {
|
|
412
|
+
required.push(key);
|
|
413
|
+
}
|
|
414
|
+
if (fieldDef.description) {
|
|
415
|
+
properties[key].description = fieldDef.description;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { type: "object", properties, required };
|
|
419
|
+
}
|
|
420
|
+
function zodToJsonSchema(schema) {
|
|
421
|
+
if (!("_def" in schema))
|
|
422
|
+
return { type: "object" };
|
|
423
|
+
const def = schema._def;
|
|
424
|
+
const kind = zodDefKind(def);
|
|
425
|
+
switch (kind) {
|
|
426
|
+
case "object":
|
|
427
|
+
case "ZodObject":
|
|
428
|
+
return zodObjectToJsonSchema(def, zodToJsonSchema);
|
|
429
|
+
case "string":
|
|
430
|
+
case "ZodString":
|
|
431
|
+
return { type: "string" };
|
|
432
|
+
case "number":
|
|
433
|
+
case "ZodNumber":
|
|
434
|
+
return { type: "number" };
|
|
435
|
+
case "boolean":
|
|
436
|
+
case "ZodBoolean":
|
|
437
|
+
return { type: "boolean" };
|
|
438
|
+
case "array":
|
|
439
|
+
case "ZodArray":
|
|
440
|
+
return {
|
|
441
|
+
type: "array",
|
|
442
|
+
items: zodToJsonSchema(def.element ?? def.type)
|
|
443
|
+
};
|
|
444
|
+
case "enum":
|
|
445
|
+
case "ZodEnum": {
|
|
446
|
+
const values = def.values ?? (def.entries ? Object.values(def.entries) : []);
|
|
447
|
+
return { type: "string", enum: values };
|
|
448
|
+
}
|
|
449
|
+
case "optional":
|
|
450
|
+
case "ZodOptional":
|
|
451
|
+
return zodToJsonSchema(def.innerType);
|
|
452
|
+
case "default":
|
|
453
|
+
case "ZodDefault":
|
|
454
|
+
return zodToJsonSchema(def.innerType);
|
|
455
|
+
case "nullable":
|
|
456
|
+
case "ZodNullable": {
|
|
457
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
458
|
+
return { ...inner, nullable: true };
|
|
459
|
+
}
|
|
460
|
+
case "ZodLiteral":
|
|
461
|
+
return { type: typeof def.value, const: def.value };
|
|
462
|
+
case "ZodUnion": {
|
|
463
|
+
const options = def.options.map(zodToJsonSchema);
|
|
464
|
+
return { anyOf: options };
|
|
465
|
+
}
|
|
466
|
+
case "ZodRecord":
|
|
467
|
+
return {
|
|
468
|
+
type: "object",
|
|
469
|
+
additionalProperties: def.valueType ? zodToJsonSchema(def.valueType) : { type: "string" }
|
|
470
|
+
};
|
|
471
|
+
case "ZodTuple": {
|
|
472
|
+
const items = (def.items ?? []).map(zodToJsonSchema);
|
|
473
|
+
return { type: "array", prefixItems: items, minItems: items.length, maxItems: items.length };
|
|
474
|
+
}
|
|
475
|
+
case "ZodDate":
|
|
476
|
+
return { type: "string", format: "date-time" };
|
|
477
|
+
default:
|
|
478
|
+
log.warn(`zodToJsonSchema: unsupported type ${kind}, defaulting to string`);
|
|
479
|
+
return { type: "string" };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// ../core/src/registry.ts
|
|
483
|
+
var log2 = createLogger();
|
|
484
|
+
var BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
397
485
|
// ../core/src/circuit-breaker.ts
|
|
398
486
|
function defaultShouldCount(error) {
|
|
399
487
|
if (error && typeof error === "object" && "retryable" in error) {
|
|
@@ -560,16 +648,16 @@ function composeMiddleware(middlewares) {
|
|
|
560
648
|
};
|
|
561
649
|
}
|
|
562
650
|
function loggingMiddleware(logger) {
|
|
563
|
-
const
|
|
651
|
+
const log3 = logger ?? createLogger({ level: "info" });
|
|
564
652
|
return async (ctx, next) => {
|
|
565
|
-
|
|
653
|
+
log3.info("LLM request", {
|
|
566
654
|
provider: ctx.provider,
|
|
567
655
|
model: ctx.model,
|
|
568
656
|
traceId: ctx.traceId,
|
|
569
657
|
messageCount: ctx.request.messages.length
|
|
570
658
|
});
|
|
571
659
|
const response = await next(ctx);
|
|
572
|
-
|
|
660
|
+
log3.info("LLM response", {
|
|
573
661
|
provider: ctx.provider,
|
|
574
662
|
model: ctx.model,
|
|
575
663
|
traceId: ctx.traceId,
|
|
@@ -705,7 +793,7 @@ function xrayMiddleware(options = {}) {
|
|
|
705
793
|
}
|
|
706
794
|
|
|
707
795
|
// src/pricing.ts
|
|
708
|
-
var
|
|
796
|
+
var log3 = createLogger();
|
|
709
797
|
var PRICING = {
|
|
710
798
|
"claude-opus-4-6": { inputPerMillion: 15, outputPerMillion: 75 },
|
|
711
799
|
"claude-sonnet-4-6": { inputPerMillion: 3, outputPerMillion: 15 },
|
|
@@ -743,7 +831,7 @@ function resolveModelName(model) {
|
|
|
743
831
|
function calculateCost(model, usage) {
|
|
744
832
|
const pricing = PRICING[resolveModelName(model)];
|
|
745
833
|
if (!pricing) {
|
|
746
|
-
|
|
834
|
+
log3.warn(`Unknown model "${model}" — cost will be reported as $0. Register pricing with registerPricing().`);
|
|
747
835
|
return {
|
|
748
836
|
inputCost: 0,
|
|
749
837
|
outputCost: 0,
|
|
@@ -837,15 +925,33 @@ function createAnthropicProvider(config) {
|
|
|
837
925
|
if (part.type === "text")
|
|
838
926
|
return { type: "text", text: part.text };
|
|
839
927
|
if (part.type === "image" && part.source?.type === "base64") {
|
|
928
|
+
const src = part.source;
|
|
840
929
|
return {
|
|
841
930
|
type: "image",
|
|
842
931
|
source: {
|
|
843
932
|
type: "base64",
|
|
844
|
-
media_type:
|
|
845
|
-
data:
|
|
933
|
+
media_type: src.mediaType,
|
|
934
|
+
data: src.data
|
|
846
935
|
}
|
|
847
936
|
};
|
|
848
937
|
}
|
|
938
|
+
if (part.type === "document" && part.source) {
|
|
939
|
+
if (part.source.type === "base64") {
|
|
940
|
+
const src = part.source;
|
|
941
|
+
return {
|
|
942
|
+
type: "document",
|
|
943
|
+
source: {
|
|
944
|
+
type: "base64",
|
|
945
|
+
media_type: src.mediaType,
|
|
946
|
+
data: src.data
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
return { type: "text", text: "[document: url source not supported by Anthropic]" };
|
|
951
|
+
}
|
|
952
|
+
if (part.type === "audio") {
|
|
953
|
+
return { type: "text", text: "[audio content not supported by this provider]" };
|
|
954
|
+
}
|
|
849
955
|
return { type: "text", text: "[unsupported content]" };
|
|
850
956
|
}
|
|
851
957
|
function formatMultipartContent(msg, role) {
|
|
@@ -954,6 +1060,16 @@ function createAnthropicProvider(config) {
|
|
|
954
1060
|
const tools = formatTools(req.tools);
|
|
955
1061
|
if (tools)
|
|
956
1062
|
body.tools = tools;
|
|
1063
|
+
if (req.schema) {
|
|
1064
|
+
const jsonSchema = zodToJsonSchema(req.schema);
|
|
1065
|
+
const structuredTool = {
|
|
1066
|
+
name: "_structured_output",
|
|
1067
|
+
description: "Return structured output matching the required schema",
|
|
1068
|
+
input_schema: jsonSchema
|
|
1069
|
+
};
|
|
1070
|
+
body.tools = [...tools ?? [], structuredTool];
|
|
1071
|
+
body.tool_choice = { type: "tool", name: "_structured_output" };
|
|
1072
|
+
}
|
|
957
1073
|
const startTime = performance.now();
|
|
958
1074
|
const raw = await retry(async () => {
|
|
959
1075
|
const controller = new AbortController;
|
|
@@ -1174,6 +1290,18 @@ function createGoogleProvider(config) {
|
|
|
1174
1290
|
} else {
|
|
1175
1291
|
parts.push({ fileData: { mimeType: "image/jpeg", fileUri: img.source.url } });
|
|
1176
1292
|
}
|
|
1293
|
+
} else if (p.type === "audio" || p.type === "document") {
|
|
1294
|
+
const media = p;
|
|
1295
|
+
if (media.source.type === "base64") {
|
|
1296
|
+
parts.push({
|
|
1297
|
+
inlineData: { mimeType: media.source.mediaType, data: media.source.data }
|
|
1298
|
+
});
|
|
1299
|
+
} else {
|
|
1300
|
+
const urlSource = media.source;
|
|
1301
|
+
parts.push({
|
|
1302
|
+
fileData: { mimeType: "application/octet-stream", fileUri: urlSource.url }
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1177
1305
|
}
|
|
1178
1306
|
}
|
|
1179
1307
|
return { role, parts };
|
|
@@ -1272,6 +1400,10 @@ function createGoogleProvider(config) {
|
|
|
1272
1400
|
config2.topP = req.topP;
|
|
1273
1401
|
if (req.stopSequences?.length)
|
|
1274
1402
|
config2.stopSequences = req.stopSequences;
|
|
1403
|
+
if (req.schema) {
|
|
1404
|
+
config2.responseMimeType = "application/json";
|
|
1405
|
+
config2.responseSchema = zodToJsonSchema(req.schema);
|
|
1406
|
+
}
|
|
1275
1407
|
return config2;
|
|
1276
1408
|
}
|
|
1277
1409
|
function buildRequestBody(req) {
|
|
@@ -1551,6 +1683,25 @@ function createOpenAIProvider(config) {
|
|
|
1551
1683
|
} else {
|
|
1552
1684
|
parts.push({ type: "image_url", image_url: { url: part.source.url } });
|
|
1553
1685
|
}
|
|
1686
|
+
} else if (part.type === "audio") {
|
|
1687
|
+
if (part.source.type === "base64") {
|
|
1688
|
+
const format = part.source.mediaType.split("/")[1] ?? "wav";
|
|
1689
|
+
parts.push({
|
|
1690
|
+
type: "input_audio",
|
|
1691
|
+
input_audio: { data: part.source.data, format }
|
|
1692
|
+
});
|
|
1693
|
+
} else {
|
|
1694
|
+
parts.push({ type: "text", text: "[audio: url source requires file upload]" });
|
|
1695
|
+
}
|
|
1696
|
+
} else if (part.type === "document") {
|
|
1697
|
+
if (part.source.type === "base64") {
|
|
1698
|
+
parts.push({
|
|
1699
|
+
type: "text",
|
|
1700
|
+
text: `[document: ${part.source.mediaType} content attached as base64]`
|
|
1701
|
+
});
|
|
1702
|
+
} else {
|
|
1703
|
+
parts.push({ type: "text", text: `[document: ${part.source.url}]` });
|
|
1704
|
+
}
|
|
1554
1705
|
}
|
|
1555
1706
|
}
|
|
1556
1707
|
return parts;
|
|
@@ -1647,6 +1798,17 @@ function createOpenAIProvider(config) {
|
|
|
1647
1798
|
const tools = formatTools(req.tools);
|
|
1648
1799
|
if (tools)
|
|
1649
1800
|
body.tools = tools;
|
|
1801
|
+
if (req.schema) {
|
|
1802
|
+
const jsonSchema = zodToJsonSchema(req.schema);
|
|
1803
|
+
body.response_format = {
|
|
1804
|
+
type: "json_schema",
|
|
1805
|
+
json_schema: {
|
|
1806
|
+
name: "structured_output",
|
|
1807
|
+
strict: true,
|
|
1808
|
+
schema: jsonSchema
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1650
1812
|
const startTime = performance.now();
|
|
1651
1813
|
const raw = await retry(async () => {
|
|
1652
1814
|
const controller = new AbortController;
|
|
@@ -1960,107 +2122,46 @@ function gateway(config) {
|
|
|
1960
2122
|
},
|
|
1961
2123
|
async generate(request) {
|
|
1962
2124
|
const { schema, ...rest } = request;
|
|
1963
|
-
const jsonSchema =
|
|
1964
|
-
const systemPrompt = [
|
|
1965
|
-
rest.system ?? "",
|
|
1966
|
-
"You MUST respond with valid JSON matching this schema:",
|
|
1967
|
-
JSON.stringify(jsonSchema, null, 2),
|
|
1968
|
-
"Respond ONLY with the JSON object, no markdown or explanation."
|
|
1969
|
-
].filter(Boolean).join(`
|
|
1970
|
-
|
|
1971
|
-
`);
|
|
2125
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
1972
2126
|
const response = await executeWithMiddleware({
|
|
1973
2127
|
...rest,
|
|
1974
|
-
|
|
2128
|
+
schema,
|
|
2129
|
+
system: [
|
|
2130
|
+
rest.system ?? "",
|
|
2131
|
+
"You MUST respond with valid JSON matching this schema:",
|
|
2132
|
+
JSON.stringify(jsonSchema, null, 2),
|
|
2133
|
+
"Respond ONLY with the JSON object, no markdown or explanation."
|
|
2134
|
+
].filter(Boolean).join(`
|
|
2135
|
+
|
|
2136
|
+
`)
|
|
1975
2137
|
});
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
}
|
|
2138
|
+
let parsed;
|
|
2139
|
+
if (response.stopReason === "tool_use" && response.message.toolCalls?.length) {
|
|
2140
|
+
const structuredCall = response.message.toolCalls.find((tc) => tc.name === "_structured_output");
|
|
2141
|
+
if (structuredCall) {
|
|
2142
|
+
parsed = structuredCall.arguments;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (parsed === undefined) {
|
|
2146
|
+
const text = typeof response.message.content === "string" ? response.message.content : "";
|
|
2147
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
2148
|
+
if (!jsonMatch) {
|
|
2149
|
+
throw ElsiumError.validation("LLM response did not contain valid JSON", {
|
|
2150
|
+
response: text
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
1982
2154
|
}
|
|
1983
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
1984
2155
|
const result = schema.safeParse(parsed);
|
|
1985
2156
|
if (!result.success) {
|
|
1986
2157
|
throw ElsiumError.validation("LLM response did not match schema", {
|
|
1987
|
-
errors: result.error.issues
|
|
1988
|
-
response: text
|
|
2158
|
+
errors: result.error.issues
|
|
1989
2159
|
});
|
|
1990
2160
|
}
|
|
1991
2161
|
return { data: result.data, response };
|
|
1992
2162
|
}
|
|
1993
2163
|
};
|
|
1994
2164
|
}
|
|
1995
|
-
function schemaToJsonSchema(schema) {
|
|
1996
|
-
try {
|
|
1997
|
-
if ("_def" in schema) {
|
|
1998
|
-
const def = schema._def;
|
|
1999
|
-
const result = convertZodDef(def);
|
|
2000
|
-
if (result)
|
|
2001
|
-
return result;
|
|
2002
|
-
}
|
|
2003
|
-
} catch {}
|
|
2004
|
-
return { type: "string" };
|
|
2005
|
-
}
|
|
2006
|
-
function zodDefKind(def) {
|
|
2007
|
-
return typeof def.type === "string" ? def.type : def.typeName;
|
|
2008
|
-
}
|
|
2009
|
-
function convertZodDef(def) {
|
|
2010
|
-
const kind = zodDefKind(def);
|
|
2011
|
-
switch (kind) {
|
|
2012
|
-
case "object":
|
|
2013
|
-
case "ZodObject":
|
|
2014
|
-
return convertZodObject(def);
|
|
2015
|
-
case "string":
|
|
2016
|
-
case "ZodString":
|
|
2017
|
-
return { type: "string" };
|
|
2018
|
-
case "number":
|
|
2019
|
-
case "ZodNumber":
|
|
2020
|
-
return { type: "number" };
|
|
2021
|
-
case "boolean":
|
|
2022
|
-
case "ZodBoolean":
|
|
2023
|
-
return { type: "boolean" };
|
|
2024
|
-
case "array":
|
|
2025
|
-
case "ZodArray":
|
|
2026
|
-
return convertZodArray(def);
|
|
2027
|
-
case "enum":
|
|
2028
|
-
case "ZodEnum": {
|
|
2029
|
-
const values = def.values ?? (def.entries ? Object.values(def.entries) : []);
|
|
2030
|
-
return { type: "string", enum: values };
|
|
2031
|
-
}
|
|
2032
|
-
case "optional":
|
|
2033
|
-
case "ZodOptional":
|
|
2034
|
-
return convertZodOptional(def);
|
|
2035
|
-
default:
|
|
2036
|
-
return null;
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
function convertZodObject(def) {
|
|
2040
|
-
if (!def.shape)
|
|
2041
|
-
return null;
|
|
2042
|
-
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
2043
|
-
const properties = {};
|
|
2044
|
-
const required = [];
|
|
2045
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
2046
|
-
properties[key] = schemaToJsonSchema(value);
|
|
2047
|
-
const valDef = value._def;
|
|
2048
|
-
const valKind = zodDefKind(valDef);
|
|
2049
|
-
if (valKind !== "optional" && valKind !== "ZodOptional") {
|
|
2050
|
-
required.push(key);
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
return { type: "object", properties, required };
|
|
2054
|
-
}
|
|
2055
|
-
function convertZodArray(def) {
|
|
2056
|
-
return {
|
|
2057
|
-
type: "array",
|
|
2058
|
-
items: schemaToJsonSchema(def.element ?? def.type)
|
|
2059
|
-
};
|
|
2060
|
-
}
|
|
2061
|
-
function convertZodOptional(def) {
|
|
2062
|
-
return schemaToJsonSchema(def.innerType ?? def.innerType);
|
|
2063
|
-
}
|
|
2064
2165
|
// src/security.ts
|
|
2065
2166
|
var INJECTION_PATTERNS = [
|
|
2066
2167
|
{
|
|
@@ -2423,6 +2524,321 @@ function bulkheadMiddleware(config) {
|
|
|
2423
2524
|
return bulkhead.execute(() => next(ctx));
|
|
2424
2525
|
};
|
|
2425
2526
|
}
|
|
2527
|
+
// src/cache.ts
|
|
2528
|
+
import { createHash } from "node:crypto";
|
|
2529
|
+
var log4 = createLogger();
|
|
2530
|
+
function createInMemoryCache(maxSize = 1000) {
|
|
2531
|
+
const cache = new Map;
|
|
2532
|
+
function evict() {
|
|
2533
|
+
if (cache.size <= maxSize)
|
|
2534
|
+
return;
|
|
2535
|
+
const firstKey = cache.keys().next().value;
|
|
2536
|
+
if (firstKey !== undefined)
|
|
2537
|
+
cache.delete(firstKey);
|
|
2538
|
+
}
|
|
2539
|
+
return {
|
|
2540
|
+
async get(key) {
|
|
2541
|
+
const entry = cache.get(key);
|
|
2542
|
+
if (!entry)
|
|
2543
|
+
return null;
|
|
2544
|
+
if (Date.now() > entry.expiresAt) {
|
|
2545
|
+
cache.delete(key);
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
cache.delete(key);
|
|
2549
|
+
cache.set(key, entry);
|
|
2550
|
+
return entry.value;
|
|
2551
|
+
},
|
|
2552
|
+
async set(key, value, ttlMs) {
|
|
2553
|
+
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
2554
|
+
evict();
|
|
2555
|
+
},
|
|
2556
|
+
async delete(key) {
|
|
2557
|
+
cache.delete(key);
|
|
2558
|
+
},
|
|
2559
|
+
async clear() {
|
|
2560
|
+
cache.clear();
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function defaultCacheKey(ctx) {
|
|
2565
|
+
const data = JSON.stringify({
|
|
2566
|
+
provider: ctx.provider,
|
|
2567
|
+
model: ctx.model,
|
|
2568
|
+
messages: ctx.request.messages,
|
|
2569
|
+
system: ctx.request.system,
|
|
2570
|
+
temperature: ctx.request.temperature
|
|
2571
|
+
});
|
|
2572
|
+
return createHash("sha256").update(data).digest("hex");
|
|
2573
|
+
}
|
|
2574
|
+
function defaultShouldCache(_ctx, response) {
|
|
2575
|
+
const temp = _ctx.request.temperature;
|
|
2576
|
+
if (temp !== undefined && temp !== 0)
|
|
2577
|
+
return false;
|
|
2578
|
+
return response.stopReason === "end_turn";
|
|
2579
|
+
}
|
|
2580
|
+
function cacheMiddleware(config) {
|
|
2581
|
+
const ttlMs = config?.ttlMs ?? 3600000;
|
|
2582
|
+
const adapter = config?.adapter ?? createInMemoryCache(config?.maxSize ?? 1000);
|
|
2583
|
+
const keyFn = config?.keyFn ?? defaultCacheKey;
|
|
2584
|
+
const shouldCache = config?.shouldCache ?? defaultShouldCache;
|
|
2585
|
+
let hits = 0;
|
|
2586
|
+
let misses = 0;
|
|
2587
|
+
const middleware = async (ctx, next) => {
|
|
2588
|
+
if (ctx.request.stream) {
|
|
2589
|
+
return next(ctx);
|
|
2590
|
+
}
|
|
2591
|
+
const key = keyFn(ctx);
|
|
2592
|
+
const cached = await adapter.get(key);
|
|
2593
|
+
if (cached) {
|
|
2594
|
+
hits++;
|
|
2595
|
+
log4.debug("Cache hit", { key: key.slice(0, 8), provider: ctx.provider });
|
|
2596
|
+
return cached;
|
|
2597
|
+
}
|
|
2598
|
+
misses++;
|
|
2599
|
+
const response = await next(ctx);
|
|
2600
|
+
if (shouldCache(ctx, response)) {
|
|
2601
|
+
await adapter.set(key, response, ttlMs);
|
|
2602
|
+
}
|
|
2603
|
+
return response;
|
|
2604
|
+
};
|
|
2605
|
+
return Object.assign(middleware, {
|
|
2606
|
+
adapter,
|
|
2607
|
+
stats() {
|
|
2608
|
+
const total = hits + misses;
|
|
2609
|
+
return {
|
|
2610
|
+
hits,
|
|
2611
|
+
misses,
|
|
2612
|
+
size: 0,
|
|
2613
|
+
hitRate: total > 0 ? hits / total : 0
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
// src/output-guardrails.ts
|
|
2619
|
+
var log5 = createLogger();
|
|
2620
|
+
var PII_PATTERNS2 = [
|
|
2621
|
+
{
|
|
2622
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
2623
|
+
label: "email",
|
|
2624
|
+
replacement: "[REDACTED_EMAIL]"
|
|
2625
|
+
},
|
|
2626
|
+
{
|
|
2627
|
+
pattern: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
|
|
2628
|
+
label: "phone",
|
|
2629
|
+
replacement: "[REDACTED_PHONE]"
|
|
2630
|
+
},
|
|
2631
|
+
{
|
|
2632
|
+
pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
2633
|
+
label: "ssn",
|
|
2634
|
+
replacement: "[REDACTED_SSN]"
|
|
2635
|
+
},
|
|
2636
|
+
{
|
|
2637
|
+
pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
|
|
2638
|
+
label: "credit_card",
|
|
2639
|
+
replacement: "[REDACTED_CC]"
|
|
2640
|
+
}
|
|
2641
|
+
];
|
|
2642
|
+
var SECRET_PATTERNS2 = [
|
|
2643
|
+
{
|
|
2644
|
+
pattern: /\bsk-[A-Za-z0-9]{20,}\b/g,
|
|
2645
|
+
label: "api_key",
|
|
2646
|
+
replacement: "[REDACTED_API_KEY]"
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
pattern: /\bpk-[A-Za-z0-9]{20,}\b/g,
|
|
2650
|
+
label: "api_key",
|
|
2651
|
+
replacement: "[REDACTED_API_KEY]"
|
|
2652
|
+
},
|
|
2653
|
+
{
|
|
2654
|
+
pattern: /\bAKIA[A-Z0-9]{16}\b/g,
|
|
2655
|
+
label: "aws_key",
|
|
2656
|
+
replacement: "[REDACTED_AWS_KEY]"
|
|
2657
|
+
}
|
|
2658
|
+
];
|
|
2659
|
+
function detectPII(text) {
|
|
2660
|
+
const violations = [];
|
|
2661
|
+
const normalized = text.normalize("NFKC");
|
|
2662
|
+
for (const { pattern, label } of [...PII_PATTERNS2, ...SECRET_PATTERNS2]) {
|
|
2663
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
2664
|
+
if (regex.test(normalized)) {
|
|
2665
|
+
violations.push({
|
|
2666
|
+
type: "pii",
|
|
2667
|
+
detail: `Detected ${label} in output`,
|
|
2668
|
+
pattern: label
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return violations;
|
|
2673
|
+
}
|
|
2674
|
+
function redactPII(text) {
|
|
2675
|
+
let result = text;
|
|
2676
|
+
for (const { pattern, replacement } of [...PII_PATTERNS2, ...SECRET_PATTERNS2]) {
|
|
2677
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
2678
|
+
result = result.replace(regex, replacement);
|
|
2679
|
+
}
|
|
2680
|
+
return result;
|
|
2681
|
+
}
|
|
2682
|
+
function checkContentPolicy(text, policy) {
|
|
2683
|
+
const violations = [];
|
|
2684
|
+
if (policy.maxResponseLength && text.length > policy.maxResponseLength) {
|
|
2685
|
+
violations.push({
|
|
2686
|
+
type: "content_policy",
|
|
2687
|
+
detail: `Response length ${text.length} exceeds max ${policy.maxResponseLength}`
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
if (policy.blockedPatterns) {
|
|
2691
|
+
for (const pattern of policy.blockedPatterns) {
|
|
2692
|
+
if (pattern.test(text)) {
|
|
2693
|
+
violations.push({
|
|
2694
|
+
type: "content_policy",
|
|
2695
|
+
detail: `Response matches blocked pattern: ${pattern.source}`,
|
|
2696
|
+
pattern: pattern.source
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
return violations;
|
|
2702
|
+
}
|
|
2703
|
+
function checkCustomRules(text, rules) {
|
|
2704
|
+
const violations = [];
|
|
2705
|
+
for (const rule of rules) {
|
|
2706
|
+
if (rule.pattern.test(text)) {
|
|
2707
|
+
violations.push({
|
|
2708
|
+
type: "custom_rule",
|
|
2709
|
+
detail: rule.message ?? `Output matched custom rule: ${rule.name}`,
|
|
2710
|
+
pattern: rule.pattern.source
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return violations;
|
|
2715
|
+
}
|
|
2716
|
+
function outputGuardrailMiddleware(config) {
|
|
2717
|
+
const mode = config.onViolation ?? "block";
|
|
2718
|
+
return async (ctx, next) => {
|
|
2719
|
+
const response = await next(ctx);
|
|
2720
|
+
const text = extractText(response.message.content);
|
|
2721
|
+
const violations = [];
|
|
2722
|
+
if (config.piiDetection) {
|
|
2723
|
+
violations.push(...detectPII(text));
|
|
2724
|
+
}
|
|
2725
|
+
if (config.contentPolicy) {
|
|
2726
|
+
violations.push(...checkContentPolicy(text, config.contentPolicy));
|
|
2727
|
+
}
|
|
2728
|
+
if (config.customRules?.length) {
|
|
2729
|
+
violations.push(...checkCustomRules(text, config.customRules));
|
|
2730
|
+
}
|
|
2731
|
+
if (violations.length === 0)
|
|
2732
|
+
return response;
|
|
2733
|
+
for (const v of violations) {
|
|
2734
|
+
config.onViolationCallback?.(v);
|
|
2735
|
+
}
|
|
2736
|
+
switch (mode) {
|
|
2737
|
+
case "block":
|
|
2738
|
+
throw ElsiumError.validation(`Output guardrail violation: ${violations.map((v) => v.detail).join("; ")}`, { violations });
|
|
2739
|
+
case "redact": {
|
|
2740
|
+
let redacted = text;
|
|
2741
|
+
if (config.piiDetection) {
|
|
2742
|
+
redacted = redactPII(redacted);
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
...response,
|
|
2746
|
+
message: { ...response.message, content: redacted }
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
case "warn":
|
|
2750
|
+
log5.warn("Output guardrail violations detected", { violations });
|
|
2751
|
+
return response;
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
// src/batch.ts
|
|
2756
|
+
var log6 = createLogger();
|
|
2757
|
+
function createBatch(gateway2, config) {
|
|
2758
|
+
const concurrency = config?.concurrency ?? 5;
|
|
2759
|
+
const retryPerItem = config?.retryPerItem ?? 0;
|
|
2760
|
+
return {
|
|
2761
|
+
async execute(requests) {
|
|
2762
|
+
const startTime = performance.now();
|
|
2763
|
+
const results = new Array(requests.length);
|
|
2764
|
+
let completed = 0;
|
|
2765
|
+
let totalSucceeded = 0;
|
|
2766
|
+
let totalFailed = 0;
|
|
2767
|
+
let running = 0;
|
|
2768
|
+
let nextIndex = 0;
|
|
2769
|
+
const signal = config?.signal;
|
|
2770
|
+
async function processItem(index) {
|
|
2771
|
+
if (signal?.aborted) {
|
|
2772
|
+
results[index] = {
|
|
2773
|
+
index,
|
|
2774
|
+
success: false,
|
|
2775
|
+
error: "Batch cancelled"
|
|
2776
|
+
};
|
|
2777
|
+
totalFailed++;
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
let lastError;
|
|
2781
|
+
for (let attempt = 0;attempt <= retryPerItem; attempt++) {
|
|
2782
|
+
try {
|
|
2783
|
+
const response = await gateway2.complete(requests[index]);
|
|
2784
|
+
results[index] = { index, success: true, response };
|
|
2785
|
+
totalSucceeded++;
|
|
2786
|
+
return;
|
|
2787
|
+
} catch (err2) {
|
|
2788
|
+
lastError = err2 instanceof Error ? err2.message : String(err2);
|
|
2789
|
+
if (attempt < retryPerItem && err2 instanceof ElsiumError && err2.retryable) {
|
|
2790
|
+
continue;
|
|
2791
|
+
}
|
|
2792
|
+
break;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
results[index] = { index, success: false, error: lastError };
|
|
2796
|
+
totalFailed++;
|
|
2797
|
+
}
|
|
2798
|
+
return new Promise((resolve) => {
|
|
2799
|
+
function scheduleNext() {
|
|
2800
|
+
while (running < concurrency && nextIndex < requests.length) {
|
|
2801
|
+
if (signal?.aborted) {
|
|
2802
|
+
for (let i = nextIndex;i < requests.length; i++) {
|
|
2803
|
+
results[i] = { index: i, success: false, error: "Batch cancelled" };
|
|
2804
|
+
totalFailed++;
|
|
2805
|
+
}
|
|
2806
|
+
nextIndex = requests.length;
|
|
2807
|
+
break;
|
|
2808
|
+
}
|
|
2809
|
+
const idx = nextIndex++;
|
|
2810
|
+
running++;
|
|
2811
|
+
processItem(idx).then(() => {
|
|
2812
|
+
running--;
|
|
2813
|
+
completed++;
|
|
2814
|
+
config?.onProgress?.(completed, requests.length);
|
|
2815
|
+
if (completed === requests.length) {
|
|
2816
|
+
resolve({
|
|
2817
|
+
results,
|
|
2818
|
+
totalSucceeded,
|
|
2819
|
+
totalFailed,
|
|
2820
|
+
totalDurationMs: Math.round(performance.now() - startTime)
|
|
2821
|
+
});
|
|
2822
|
+
} else {
|
|
2823
|
+
scheduleNext();
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
if (requests.length === 0) {
|
|
2829
|
+
resolve({
|
|
2830
|
+
results: [],
|
|
2831
|
+
totalSucceeded: 0,
|
|
2832
|
+
totalFailed: 0,
|
|
2833
|
+
totalDurationMs: 0
|
|
2834
|
+
});
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
scheduleNext();
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2426
2842
|
// src/router.ts
|
|
2427
2843
|
var REASONING_KEYWORDS = /\b(prove|explain why|analyze|compare|contrast|evaluate|critique|debate|reason|deduce|infer|justify|argue|synthesize|hypothesize|derive)\b/i;
|
|
2428
2844
|
var CODE_KEYWORDS = /\b(implement|refactor|debug|optimize|architect|design pattern|algorithm|data structure|write code|code review|fix the bug|type system)\b/i;
|
|
@@ -2678,6 +3094,7 @@ export {
|
|
|
2678
3094
|
registerProvider,
|
|
2679
3095
|
registerPricing,
|
|
2680
3096
|
redactSecrets,
|
|
3097
|
+
outputGuardrailMiddleware,
|
|
2681
3098
|
loggingMiddleware,
|
|
2682
3099
|
listProviders,
|
|
2683
3100
|
getProviderMetadata,
|
|
@@ -2687,13 +3104,16 @@ export {
|
|
|
2687
3104
|
detectJailbreak,
|
|
2688
3105
|
createProviderMesh,
|
|
2689
3106
|
createOpenAIProvider,
|
|
3107
|
+
createInMemoryCache,
|
|
2690
3108
|
createGoogleProvider,
|
|
2691
3109
|
createBulkhead,
|
|
3110
|
+
createBatch,
|
|
2692
3111
|
createAnthropicProvider,
|
|
2693
3112
|
costTrackingMiddleware,
|
|
2694
3113
|
composeMiddleware,
|
|
2695
3114
|
classifyContent,
|
|
2696
3115
|
checkBlockedPatterns,
|
|
2697
3116
|
calculateCost,
|
|
3117
|
+
cacheMiddleware,
|
|
2698
3118
|
bulkheadMiddleware
|
|
2699
3119
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Middleware } from '@elsium-ai/core';
|
|
2
|
+
export interface OutputViolation {
|
|
3
|
+
type: 'pii' | 'content_policy' | 'custom_rule';
|
|
4
|
+
detail: string;
|
|
5
|
+
pattern?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OutputGuardrailRule {
|
|
8
|
+
name: string;
|
|
9
|
+
pattern: RegExp;
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OutputGuardrailConfig {
|
|
13
|
+
piiDetection?: boolean;
|
|
14
|
+
contentPolicy?: {
|
|
15
|
+
blockedPatterns?: RegExp[];
|
|
16
|
+
maxResponseLength?: number;
|
|
17
|
+
};
|
|
18
|
+
customRules?: OutputGuardrailRule[];
|
|
19
|
+
onViolation?: 'block' | 'redact' | 'warn';
|
|
20
|
+
onViolationCallback?: (violation: OutputViolation) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function outputGuardrailMiddleware(config: OutputGuardrailConfig): Middleware;
|
|
23
|
+
//# sourceMappingURL=output-guardrails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-guardrails.d.ts","sourceRoot":"","sources":["../src/output-guardrails.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAKjD,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,KAAK,GAAG,gBAAgB,GAAG,aAAa,CAAA;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,qBAAqB;IACrC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE;QACf,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;QAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IACzC,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAA;CAC1D;AA+GD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,qBAAqB,GAAG,UAAU,CAiDnF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,EAUnB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA2C9C,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAoV3E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,EAUnB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAqC9C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAiRxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,cAAc,EAUnB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAqD9C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAqUxE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elsium-ai/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Multi-provider LLM gateway for ElsiumAI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Utrera <ebutrera9103@gmail.com>",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dev": "bun --watch src/index.ts"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@elsium-ai/core": "^0.
|
|
29
|
+
"@elsium-ai/core": "^0.3.0",
|
|
30
30
|
"zod": "^3.24.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|