@elsium-ai/gateway 0.6.0 → 0.8.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 CHANGED
@@ -919,6 +919,14 @@ interface ProviderEntry {
919
919
  ### `ProviderMeshConfig`
920
920
 
921
921
  ```ts
922
+ interface MeshAuditLogger {
923
+ log(
924
+ type: string,
925
+ data: Record<string, unknown>,
926
+ options?: { actor?: string; traceId?: string },
927
+ ): void
928
+ }
929
+
922
930
  interface ProviderMeshConfig {
923
931
  providers: ProviderEntry[]
924
932
  strategy: RoutingStrategy
@@ -928,6 +936,7 @@ interface ProviderMeshConfig {
928
936
  complexityThreshold?: number
929
937
  }
930
938
  circuitBreaker?: CircuitBreakerConfig | boolean
939
+ audit?: MeshAuditLogger
931
940
  }
932
941
  ```
933
942
 
@@ -937,6 +946,7 @@ interface ProviderMeshConfig {
937
946
  | `strategy` | `RoutingStrategy` | Routing strategy to use. |
938
947
  | `costOptimizer` | `CostOptimizerConfig` | Configuration for the `cost-optimized` strategy. |
939
948
  | `circuitBreaker` | `CircuitBreakerConfig \| boolean` | Enable circuit breakers per provider. Pass `true` for defaults or an object to configure thresholds. |
949
+ | `audit` | `MeshAuditLogger` | Optional audit logger. When provided, the mesh logs `provider_failover` and `circuit_breaker_state_change` events. Compatible with `AuditTrail` from `@elsium-ai/observe`. |
940
950
 
941
951
  ### `ProviderMesh`
942
952
 
@@ -1042,6 +1052,41 @@ const response = await mesh.complete({
1042
1052
  })
1043
1053
  ```
1044
1054
 
1055
+ #### Failover Audit Trail
1056
+
1057
+ Pass an audit trail to the mesh to get tamper-evident records of every provider failover and circuit breaker state change:
1058
+
1059
+ ```ts
1060
+ import { createProviderMesh } from '@elsium-ai/gateway'
1061
+ import { createAuditTrail } from '@elsium-ai/observe'
1062
+
1063
+ const audit = createAuditTrail({
1064
+ hashChain: true,
1065
+ batch: { size: 500, intervalMs: 100 },
1066
+ })
1067
+
1068
+ const mesh = createProviderMesh({
1069
+ providers: [
1070
+ { name: 'anthropic', config: { apiKey: process.env.ANTHROPIC_API_KEY! }, model: 'claude-sonnet-4-6' },
1071
+ { name: 'openai', config: { apiKey: process.env.OPENAI_API_KEY! }, model: 'gpt-4o' },
1072
+ ],
1073
+ strategy: 'fallback',
1074
+ circuitBreaker: { failureThreshold: 5, resetTimeoutMs: 30_000 },
1075
+ audit,
1076
+ })
1077
+
1078
+ // If Anthropic fails and OpenAI succeeds, the audit trail records:
1079
+ // { type: 'provider_failover', data: { fromProvider: 'anthropic', toProvider: 'openai', strategy: 'fallback', reason: '...' } }
1080
+ //
1081
+ // If the circuit breaker trips:
1082
+ // { type: 'circuit_breaker_state_change', data: { provider: 'anthropic', fromState: 'closed', toState: 'open' } }
1083
+
1084
+ const failovers = await audit.query({ type: 'provider_failover' })
1085
+ const breakerEvents = await audit.query({ type: ['circuit_breaker_state_change'] })
1086
+ ```
1087
+
1088
+ The `MeshAuditLogger` interface is intentionally minimal — any object with a `log(type, data, options?)` method works, so you can use `AuditTrail` from `@elsium-ai/observe` or your own logger.
1089
+
1045
1090
  ---
1046
1091
 
1047
1092
  ## Part of ElsiumAI
@@ -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,gBAAgB,EAChB,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;AAW7C,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,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACrC,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;AA0BD,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,WAAW,GAC9C,IAAI,CAGN;AAwJD,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAkItD"}
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,gBAAgB,EAChB,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;AAY7C,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,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACrC,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;AA4BD,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,WAAW,GAC9C,IAAI,CAGN;AAwJD,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAkItD"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export { registerProvider, getProviderFactory, listProviders, registerProviderMe
5
5
  export { createAnthropicProvider } from './providers/anthropic';
6
6
  export { createOpenAIProvider } from './providers/openai';
7
7
  export { createGoogleProvider } from './providers/google';
8
+ export { createOpenAICompatibleProvider } from './providers/openai-compatible';
9
+ export type { OpenAICompatibleConfig } from './providers/openai-compatible';
8
10
  export { composeMiddleware, composeStreamMiddleware, loggingMiddleware, costTrackingMiddleware, xrayMiddleware, } from './middleware';
9
11
  export type { XRayStore } from './middleware';
10
12
  export { securityMiddleware, detectPromptInjection, detectJailbreak, redactSecrets, checkBlockedPatterns, classifyContent, } from './security';
@@ -19,5 +21,5 @@ export { calculateCost, registerPricing, estimateCost } from './pricing';
19
21
  export { createBatch } from './batch';
20
22
  export type { BatchConfig, BatchResult, BatchResultItem } from './batch';
21
23
  export { createProviderMesh } from './router';
22
- export type { ProviderMeshConfig, ProviderEntry, RoutingStrategy, ProviderMesh } from './router';
24
+ export type { ProviderMeshConfig, ProviderEntry, RoutingStrategy, ProviderMesh, MeshAuditLogger, } from './router';
23
25
  //# sourceMappingURL=index.d.ts.map
@@ -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,uBAAuB,EACvB,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,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxE,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"}
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;AACzD,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAA;AAC9E,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAG3E,OAAO,EACN,iBAAiB,EACjB,uBAAuB,EACvB,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,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxE,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,EACX,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,eAAe,GACf,MAAM,UAAU,CAAA"}
package/dist/index.js CHANGED
@@ -1991,11 +1991,44 @@ async function processOpenAISSEStream(body, emit) {
1991
1991
  }
1992
1992
  }
1993
1993
 
1994
+ // src/providers/openai-compatible.ts
1995
+ function createOpenAICompatibleProvider(config) {
1996
+ const providerName = config.name ?? "openai-compatible";
1997
+ const model = config.defaultModel ?? "default";
1998
+ const inner = createOpenAIProvider({
1999
+ apiKey: config.apiKey,
2000
+ baseUrl: config.baseUrl,
2001
+ timeout: config.timeout,
2002
+ maxRetries: config.maxRetries
2003
+ });
2004
+ const metadata = {
2005
+ baseUrl: `${config.baseUrl}/v1/chat/completions`,
2006
+ capabilities: config.capabilities ?? ["tools", "streaming", "system"],
2007
+ authStyle: "bearer"
2008
+ };
2009
+ return {
2010
+ name: providerName,
2011
+ defaultModel: model,
2012
+ metadata,
2013
+ async complete(request) {
2014
+ const response = await inner.complete(request);
2015
+ return { ...response, provider: providerName };
2016
+ },
2017
+ stream(request) {
2018
+ return inner.stream(request);
2019
+ },
2020
+ async listModels() {
2021
+ return inner.listModels();
2022
+ }
2023
+ };
2024
+ }
2025
+
1994
2026
  // src/gateway.ts
1995
2027
  var PROVIDER_FACTORIES = {
1996
2028
  anthropic: createAnthropicProvider,
1997
2029
  openai: createOpenAIProvider,
1998
- google: createGoogleProvider
2030
+ google: createGoogleProvider,
2031
+ "openai-compatible": (cfg) => createOpenAICompatibleProvider({ ...cfg, baseUrl: cfg.baseUrl ?? "" })
1999
2032
  };
2000
2033
  registerProviderMetadata("anthropic", {
2001
2034
  baseUrl: "https://api.anthropic.com/v1/messages",
@@ -2989,6 +3022,7 @@ function createProviderMesh(config) {
2989
3022
  const sortedProviders = [...config.providers];
2990
3023
  const gateways = new Map;
2991
3024
  const circuitBreakers = new Map;
3025
+ const audit = config.audit;
2992
3026
  for (const entry of sortedProviders) {
2993
3027
  const gw = gateway({
2994
3028
  provider: entry.name,
@@ -2999,7 +3033,19 @@ function createProviderMesh(config) {
2999
3033
  gateways.set(entry.name, gw);
3000
3034
  if (config.circuitBreaker) {
3001
3035
  const cbConfig = typeof config.circuitBreaker === "boolean" ? {} : config.circuitBreaker;
3002
- circuitBreakers.set(entry.name, createCircuitBreaker(cbConfig));
3036
+ const providerName = entry.name;
3037
+ const wrappedConfig = {
3038
+ ...cbConfig,
3039
+ onStateChange(from, to) {
3040
+ cbConfig.onStateChange?.(from, to);
3041
+ audit?.log("circuit_breaker_state_change", {
3042
+ provider: providerName,
3043
+ fromState: from,
3044
+ toState: to
3045
+ });
3046
+ }
3047
+ };
3048
+ circuitBreakers.set(entry.name, createCircuitBreaker(wrappedConfig));
3003
3049
  }
3004
3050
  }
3005
3051
  function callWithCircuitBreaker(providerName, fn) {
@@ -3021,23 +3067,41 @@ function createProviderMesh(config) {
3021
3067
  }
3022
3068
  return gw;
3023
3069
  }
3024
- async function fallbackComplete(request) {
3070
+ function attemptProvider(entry, request) {
3071
+ const gw = getGateway(entry.name);
3072
+ return callWithCircuitBreaker(entry.name, () => gw.complete({ ...request, model: request.model ?? entry.model }));
3073
+ }
3074
+ function logFailover(fromProvider, toProvider, reason) {
3075
+ audit?.log("provider_failover", {
3076
+ fromProvider,
3077
+ toProvider,
3078
+ strategy: config.strategy,
3079
+ reason
3080
+ });
3081
+ }
3082
+ function toError2(err2) {
3083
+ return err2 instanceof Error ? err2 : new Error(String(err2));
3084
+ }
3085
+ async function tryProvidersWithAudit(providers, request, errorMessage) {
3025
3086
  let lastError = null;
3026
- for (const entry of sortedProviders) {
3087
+ let failedProvider = null;
3088
+ for (const entry of providers) {
3027
3089
  if (!isProviderAvailable(entry.name))
3028
3090
  continue;
3029
3091
  try {
3030
- const gw = getGateway(entry.name);
3031
- return await callWithCircuitBreaker(entry.name, () => gw.complete({ ...request, model: request.model ?? entry.model }));
3092
+ const response = await attemptProvider(entry, request);
3093
+ if (failedProvider)
3094
+ logFailover(failedProvider, entry.name, lastError?.message);
3095
+ return response;
3032
3096
  } catch (err2) {
3033
- lastError = err2 instanceof Error ? err2 : new Error(String(err2));
3097
+ failedProvider = entry.name;
3098
+ lastError = toError2(err2);
3034
3099
  }
3035
3100
  }
3036
- throw lastError ?? new ElsiumError({
3037
- code: "PROVIDER_ERROR",
3038
- message: "All providers failed",
3039
- retryable: false
3040
- });
3101
+ throw lastError ?? new ElsiumError({ code: "PROVIDER_ERROR", message: errorMessage, retryable: false });
3102
+ }
3103
+ async function fallbackComplete(request) {
3104
+ return tryProvidersWithAudit(sortedProviders, request, "All providers failed");
3041
3105
  }
3042
3106
  async function costOptimizedComplete(request) {
3043
3107
  const optimizer = config.costOptimizer;
@@ -3050,7 +3114,13 @@ function createProviderMesh(config) {
3050
3114
  const gw = getGateway(target.provider);
3051
3115
  try {
3052
3116
  return await gw.complete({ ...request, model: target.model });
3053
- } catch {
3117
+ } catch (err2) {
3118
+ audit?.log("provider_failover", {
3119
+ fromProvider: target.provider,
3120
+ toProvider: "fallback-chain",
3121
+ strategy: "cost-optimized",
3122
+ reason: err2 instanceof Error ? err2.message : String(err2)
3123
+ });
3054
3124
  return fallbackComplete(request);
3055
3125
  }
3056
3126
  }
@@ -3094,31 +3164,13 @@ function createProviderMesh(config) {
3094
3164
  return capabilities.every((c) => providerCaps.includes(c));
3095
3165
  });
3096
3166
  }
3097
- async function tryProviders(providers, request) {
3098
- let lastError = null;
3099
- for (const entry of providers) {
3100
- if (!isProviderAvailable(entry.name))
3101
- continue;
3102
- try {
3103
- const gw = getGateway(entry.name);
3104
- return await callWithCircuitBreaker(entry.name, () => gw.complete({ ...request, model: request.model ?? entry.model }));
3105
- } catch (err2) {
3106
- lastError = err2 instanceof Error ? err2 : new Error(String(err2));
3107
- }
3108
- }
3109
- throw lastError ?? new ElsiumError({
3110
- code: "PROVIDER_ERROR",
3111
- message: "No capable provider succeeded",
3112
- retryable: false
3113
- });
3114
- }
3115
3167
  async function capabilityAwareComplete(request) {
3116
3168
  const capabilities = detectRequiredCapabilities(request);
3117
3169
  const capable = filterCapableProviders(capabilities);
3118
3170
  if (capable.length === 0) {
3119
3171
  return fallbackComplete(request);
3120
3172
  }
3121
- return tryProviders(capable, request);
3173
+ return tryProvidersWithAudit(capable, request, "No capable provider succeeded");
3122
3174
  }
3123
3175
  function defaultCapabilities(provider) {
3124
3176
  const meta = getProviderMetadata(provider);
@@ -3135,6 +3187,149 @@ function createProviderMesh(config) {
3135
3187
  return ["streaming"];
3136
3188
  }
3137
3189
  }
3190
+ function errorStream(message) {
3191
+ return new ElsiumStream(async function* () {
3192
+ yield {
3193
+ type: "error",
3194
+ error: new ElsiumError({
3195
+ code: "PROVIDER_ERROR",
3196
+ message,
3197
+ retryable: false
3198
+ })
3199
+ };
3200
+ }());
3201
+ }
3202
+ function logStreamFailover(provider, error) {
3203
+ audit?.log("provider_failover", {
3204
+ fromProvider: provider,
3205
+ toProvider: "next",
3206
+ strategy: config.strategy,
3207
+ reason: error?.message
3208
+ });
3209
+ }
3210
+ async function tryStreamProvider(entry, request, emit) {
3211
+ const gw = getGateway(entry.name);
3212
+ const providerStream = await callWithCircuitBreaker(entry.name, async () => gw.stream({ ...request, model: request.model ?? entry.model }));
3213
+ let hasEmittedContent = false;
3214
+ for await (const event of providerStream) {
3215
+ if (event.type === "error") {
3216
+ const err2 = event.error instanceof Error ? event.error : new Error(String(event.error));
3217
+ if (hasEmittedContent) {
3218
+ emit(event);
3219
+ return { success: true };
3220
+ }
3221
+ return { success: false, error: err2 };
3222
+ }
3223
+ hasEmittedContent = true;
3224
+ emit(event);
3225
+ }
3226
+ return { success: true };
3227
+ }
3228
+ async function runStreamFallbackLoop(available, request, emit) {
3229
+ let lastError = null;
3230
+ let failedProvider = null;
3231
+ for (const entry of available) {
3232
+ try {
3233
+ const result = await tryStreamProvider(entry, request, emit);
3234
+ if (result.success) {
3235
+ if (failedProvider)
3236
+ logFailover(failedProvider, entry.name, lastError?.message);
3237
+ return;
3238
+ }
3239
+ lastError = result.error ?? null;
3240
+ failedProvider = entry.name;
3241
+ logStreamFailover(entry.name, result.error);
3242
+ } catch (err2) {
3243
+ failedProvider = entry.name;
3244
+ lastError = toError2(err2);
3245
+ logStreamFailover(entry.name, lastError);
3246
+ }
3247
+ }
3248
+ emit({
3249
+ type: "error",
3250
+ error: lastError ?? new ElsiumError({
3251
+ code: "PROVIDER_ERROR",
3252
+ message: "All providers failed during streaming",
3253
+ retryable: false
3254
+ })
3255
+ });
3256
+ }
3257
+ function streamWithFallback(providers, request) {
3258
+ const available = providers.filter((e) => isProviderAvailable(e.name));
3259
+ if (available.length === 0) {
3260
+ return errorStream("All providers unavailable");
3261
+ }
3262
+ return createStream(async (emit) => {
3263
+ await runStreamFallbackLoop(available, request, emit);
3264
+ });
3265
+ }
3266
+ function streamCostOptimized(request) {
3267
+ const optimizer = config.costOptimizer;
3268
+ if (!optimizer) {
3269
+ return streamWithFallback(sortedProviders, request);
3270
+ }
3271
+ const complexity = estimateComplexity(request);
3272
+ const threshold = optimizer.complexityThreshold ?? 0.5;
3273
+ const target = complexity < threshold ? optimizer.simpleModel : optimizer.complexModel;
3274
+ return createStream(async (emit) => {
3275
+ try {
3276
+ const gw = getGateway(target.provider);
3277
+ const providerStream = gw.stream({ ...request, model: target.model });
3278
+ for await (const event of providerStream) {
3279
+ emit(event);
3280
+ }
3281
+ } catch {
3282
+ const fallbackStream = streamWithFallback(sortedProviders, request);
3283
+ for await (const event of fallbackStream) {
3284
+ emit(event);
3285
+ }
3286
+ }
3287
+ });
3288
+ }
3289
+ function streamLatencyOptimized(request) {
3290
+ const available = sortedProviders.filter((e) => isProviderAvailable(e.name));
3291
+ if (available.length === 0) {
3292
+ return errorStream("All providers unavailable");
3293
+ }
3294
+ return createStream(async (emit) => {
3295
+ const controller = new AbortController;
3296
+ const racePromises = available.map(async (entry) => {
3297
+ const gw = getGateway(entry.name);
3298
+ return callWithCircuitBreaker(entry.name, async () => ({
3299
+ entry,
3300
+ stream: gw.stream({
3301
+ ...request,
3302
+ model: request.model ?? entry.model,
3303
+ signal: controller.signal
3304
+ })
3305
+ }));
3306
+ });
3307
+ try {
3308
+ const winner = await Promise.any(racePromises);
3309
+ controller.abort();
3310
+ for await (const event of winner.stream) {
3311
+ emit(event);
3312
+ }
3313
+ } catch {
3314
+ emit({
3315
+ type: "error",
3316
+ error: new ElsiumError({
3317
+ code: "PROVIDER_ERROR",
3318
+ message: "All providers failed during streaming",
3319
+ retryable: false
3320
+ })
3321
+ });
3322
+ }
3323
+ });
3324
+ }
3325
+ function streamCapabilityAware(request) {
3326
+ const capabilities = detectRequiredCapabilities(request);
3327
+ const capable = filterCapableProviders(capabilities);
3328
+ if (capable.length === 0) {
3329
+ return streamWithFallback(sortedProviders, request);
3330
+ }
3331
+ return streamWithFallback(capable, request);
3332
+ }
3138
3333
  return {
3139
3334
  providers: sortedProviders.map((p) => p.name),
3140
3335
  strategy: config.strategy,
@@ -3153,25 +3348,18 @@ function createProviderMesh(config) {
3153
3348
  }
3154
3349
  },
3155
3350
  stream(request) {
3156
- const available = sortedProviders.find((e) => isProviderAvailable(e.name));
3157
- const entry = available ?? sortedProviders[0];
3158
- const gw = getGateway(entry.name);
3159
- let resolvedStream = null;
3160
- callWithCircuitBreaker(entry.name, () => {
3161
- resolvedStream = gw.stream({ ...request, model: request.model ?? entry.model });
3162
- return Promise.resolve(resolvedStream);
3163
- }).catch(() => {});
3164
- if (resolvedStream === null) {
3165
- const err2 = new ElsiumError({
3166
- code: "PROVIDER_ERROR",
3167
- message: "Circuit breaker is open",
3168
- retryable: true
3169
- });
3170
- return new ElsiumStream(async function* () {
3171
- yield { type: "error", error: err2 };
3172
- }());
3351
+ switch (config.strategy) {
3352
+ case "fallback":
3353
+ return streamWithFallback(sortedProviders, request);
3354
+ case "cost-optimized":
3355
+ return streamCostOptimized(request);
3356
+ case "latency-optimized":
3357
+ return streamLatencyOptimized(request);
3358
+ case "capability-aware":
3359
+ return streamCapabilityAware(request);
3360
+ default:
3361
+ return streamWithFallback(sortedProviders, request);
3173
3362
  }
3174
- return resolvedStream;
3175
3363
  }
3176
3364
  };
3177
3365
  }
@@ -3194,6 +3382,7 @@ export {
3194
3382
  detectJailbreak,
3195
3383
  createProviderMesh,
3196
3384
  createOpenAIProvider,
3385
+ createOpenAICompatibleProvider,
3197
3386
  createInMemoryCache,
3198
3387
  createGoogleProvider,
3199
3388
  createBulkhead,
@@ -0,0 +1,10 @@
1
+ import type { ProviderConfig } from '@elsium-ai/core';
2
+ import type { LLMProvider } from '../provider';
3
+ export interface OpenAICompatibleConfig extends ProviderConfig {
4
+ baseUrl: string;
5
+ name?: string;
6
+ defaultModel?: string;
7
+ capabilities?: string[];
8
+ }
9
+ export declare function createOpenAICompatibleProvider(config: OpenAICompatibleConfig): LLMProvider;
10
+ //# sourceMappingURL=openai-compatible.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible.d.ts","sourceRoot":"","sources":["../../src/providers/openai-compatible.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAA;AAGhE,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,sBAAsB,GAAG,WAAW,CAmC1F"}
package/dist/router.d.ts CHANGED
@@ -21,11 +21,18 @@ export interface CostOptimizerConfig {
21
21
  };
22
22
  complexityThreshold?: number;
23
23
  }
24
+ export interface MeshAuditLogger {
25
+ log(type: string, data: Record<string, unknown>, options?: {
26
+ actor?: string;
27
+ traceId?: string;
28
+ }): void;
29
+ }
24
30
  export interface ProviderMeshConfig {
25
31
  providers: ProviderEntry[];
26
32
  strategy: RoutingStrategy;
27
33
  costOptimizer?: CostOptimizerConfig;
28
34
  circuitBreaker?: CircuitBreakerConfig | boolean;
35
+ audit?: MeshAuditLogger;
29
36
  }
30
37
  export interface ProviderMesh {
31
38
  complete(request: CompletionRequest): Promise<LLMResponse>;
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAEN,KAAK,oBAAoB,EAEzB,YAAY,EAEZ,MAAM,iBAAiB,CAAA;AAKxB,MAAM,MAAM,eAAe,GACxB,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,kBAAkB,CAAA;AAErB,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,WAAW,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,YAAY,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,QAAQ,EAAE,eAAe,CAAA;IACzB,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,cAAc,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAA;CAC/C;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1D,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CAAA;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;CAClC;AAoDD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAgQ3E"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAEN,KAAK,oBAAoB,EAEzB,YAAY,EAGZ,MAAM,iBAAiB,CAAA;AAKxB,MAAM,MAAM,eAAe,GACxB,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,kBAAkB,CAAA;AAErB,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,WAAW,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,YAAY,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC/B,GAAG,CACF,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,IAAI,CAAA;CACP;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,QAAQ,EAAE,eAAe,CAAA;IACzB,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,cAAc,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAA;IAC/C,KAAK,CAAC,EAAE,eAAe,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1D,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CAAA;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;CAClC;AAoDD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAob3E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elsium-ai/gateway",
3
- "version": "0.6.0",
3
+ "version": "0.8.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.6.0",
29
+ "@elsium-ai/core": "^0.8.0",
30
30
  "zod": "^3.24.0"
31
31
  },
32
32
  "devDependencies": {