nexus-ai-pro 0.2.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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1249 -0
  3. package/assets/nexus-ai-pro-presentation.jpg +0 -0
  4. package/dist/agent/loop.d.ts +12 -0
  5. package/dist/agent/loop.js +89 -0
  6. package/dist/agent/tool.d.ts +16 -0
  7. package/dist/agent/tool.js +43 -0
  8. package/dist/cache/adapters.d.ts +42 -0
  9. package/dist/cache/adapters.js +76 -0
  10. package/dist/cache/memory-cache.d.ts +20 -0
  11. package/dist/cache/memory-cache.js +63 -0
  12. package/dist/cache/semantic-cache.d.ts +19 -0
  13. package/dist/cache/semantic-cache.js +71 -0
  14. package/dist/connectors/web.d.ts +8 -0
  15. package/dist/connectors/web.js +60 -0
  16. package/dist/core/nexus.d.ts +71 -0
  17. package/dist/core/nexus.js +477 -0
  18. package/dist/core/response-format.d.ts +8 -0
  19. package/dist/core/response-format.js +121 -0
  20. package/dist/core/secure-stream.d.ts +3 -0
  21. package/dist/core/secure-stream.js +41 -0
  22. package/dist/core/streaming.d.ts +4 -0
  23. package/dist/core/streaming.js +30 -0
  24. package/dist/embeddings/providers.d.ts +20 -0
  25. package/dist/embeddings/providers.js +60 -0
  26. package/dist/evals/metrics.d.ts +75 -0
  27. package/dist/evals/metrics.js +157 -0
  28. package/dist/evals/runner.d.ts +35 -0
  29. package/dist/evals/runner.js +47 -0
  30. package/dist/hallucination/consistency.d.ts +14 -0
  31. package/dist/hallucination/consistency.js +58 -0
  32. package/dist/hallucination/factual.d.ts +14 -0
  33. package/dist/hallucination/factual.js +35 -0
  34. package/dist/hallucination/knowledge-graph.d.ts +27 -0
  35. package/dist/hallucination/knowledge-graph.js +70 -0
  36. package/dist/hallucination/rag.d.ts +21 -0
  37. package/dist/hallucination/rag.js +46 -0
  38. package/dist/hallucination/retrieval.d.ts +24 -0
  39. package/dist/hallucination/retrieval.js +71 -0
  40. package/dist/hallucination/verification.d.ts +36 -0
  41. package/dist/hallucination/verification.js +110 -0
  42. package/dist/index.d.ts +62 -0
  43. package/dist/index.js +53 -0
  44. package/dist/jobs/batch.d.ts +11 -0
  45. package/dist/jobs/batch.js +32 -0
  46. package/dist/jobs/durable-adapters.d.ts +48 -0
  47. package/dist/jobs/durable-adapters.js +50 -0
  48. package/dist/jobs/queue.d.ts +24 -0
  49. package/dist/jobs/queue.js +57 -0
  50. package/dist/models/registry.d.ts +15 -0
  51. package/dist/models/registry.js +43 -0
  52. package/dist/next/route-handler.d.ts +6 -0
  53. package/dist/next/route-handler.js +35 -0
  54. package/dist/ops/audit-logger.d.ts +6 -0
  55. package/dist/ops/audit-logger.js +19 -0
  56. package/dist/ops/health.d.ts +29 -0
  57. package/dist/ops/health.js +69 -0
  58. package/dist/ops/metrics.d.ts +53 -0
  59. package/dist/ops/metrics.js +122 -0
  60. package/dist/ops/otel-tracing.d.ts +20 -0
  61. package/dist/ops/otel-tracing.js +50 -0
  62. package/dist/ops/rate-limiter.d.ts +11 -0
  63. package/dist/ops/rate-limiter.js +33 -0
  64. package/dist/optimizer/budget.d.ts +19 -0
  65. package/dist/optimizer/budget.js +88 -0
  66. package/dist/optimizer/cost.d.ts +13 -0
  67. package/dist/optimizer/cost.js +31 -0
  68. package/dist/optimizer/densifier.d.ts +10 -0
  69. package/dist/optimizer/densifier.js +77 -0
  70. package/dist/optimizer/index.d.ts +12 -0
  71. package/dist/optimizer/index.js +59 -0
  72. package/dist/pipeline/pipeline.d.ts +14 -0
  73. package/dist/pipeline/pipeline.js +106 -0
  74. package/dist/pipeline/types.d.ts +46 -0
  75. package/dist/pipeline/types.js +1 -0
  76. package/dist/providers/anthropic.d.ts +15 -0
  77. package/dist/providers/anthropic.js +183 -0
  78. package/dist/providers/base.d.ts +19 -0
  79. package/dist/providers/base.js +75 -0
  80. package/dist/providers/cohere.d.ts +16 -0
  81. package/dist/providers/cohere.js +104 -0
  82. package/dist/providers/google.d.ts +22 -0
  83. package/dist/providers/google.js +226 -0
  84. package/dist/providers/groq.d.ts +15 -0
  85. package/dist/providers/groq.js +50 -0
  86. package/dist/providers/mistral.d.ts +15 -0
  87. package/dist/providers/mistral.js +50 -0
  88. package/dist/providers/ollama.d.ts +15 -0
  89. package/dist/providers/ollama.js +108 -0
  90. package/dist/providers/openai.d.ts +21 -0
  91. package/dist/providers/openai.js +304 -0
  92. package/dist/providers/openrouter.d.ts +14 -0
  93. package/dist/providers/openrouter.js +19 -0
  94. package/dist/rag/file-ingestion.d.ts +20 -0
  95. package/dist/rag/file-ingestion.js +55 -0
  96. package/dist/rag/ingestion.d.ts +21 -0
  97. package/dist/rag/ingestion.js +47 -0
  98. package/dist/router/auto-router.d.ts +17 -0
  99. package/dist/router/auto-router.js +228 -0
  100. package/dist/router/failover.d.ts +21 -0
  101. package/dist/router/failover.js +126 -0
  102. package/dist/router/index.d.ts +12 -0
  103. package/dist/router/index.js +31 -0
  104. package/dist/router/rules-router.d.ts +7 -0
  105. package/dist/router/rules-router.js +68 -0
  106. package/dist/router/types.d.ts +19 -0
  107. package/dist/router/types.js +1 -0
  108. package/dist/security/index.d.ts +31 -0
  109. package/dist/security/index.js +173 -0
  110. package/dist/security/injection-calibration.d.ts +17 -0
  111. package/dist/security/injection-calibration.js +48 -0
  112. package/dist/security/injection-detector.d.ts +8 -0
  113. package/dist/security/injection-detector.js +66 -0
  114. package/dist/security/input-guard.d.ts +7 -0
  115. package/dist/security/input-guard.js +109 -0
  116. package/dist/security/output-guard.d.ts +6 -0
  117. package/dist/security/output-guard.js +144 -0
  118. package/dist/security/pii-detector.d.ts +8 -0
  119. package/dist/security/pii-detector.js +66 -0
  120. package/dist/security/policies.d.ts +4 -0
  121. package/dist/security/policies.js +61 -0
  122. package/dist/security/prompt-hardening.d.ts +6 -0
  123. package/dist/security/prompt-hardening.js +18 -0
  124. package/dist/security/schema-validator.d.ts +234 -0
  125. package/dist/security/schema-validator.js +68 -0
  126. package/dist/security/semantic-injection-classifier.d.ts +19 -0
  127. package/dist/security/semantic-injection-classifier.js +75 -0
  128. package/dist/security/upload-scanner.d.ts +29 -0
  129. package/dist/security/upload-scanner.js +72 -0
  130. package/dist/testing/provider-conformance.d.ts +24 -0
  131. package/dist/testing/provider-conformance.js +97 -0
  132. package/dist/types/agent.d.ts +34 -0
  133. package/dist/types/agent.js +1 -0
  134. package/dist/types/config.d.ts +171 -0
  135. package/dist/types/config.js +1 -0
  136. package/dist/types/messages.d.ts +96 -0
  137. package/dist/types/messages.js +1 -0
  138. package/dist/types/optimizer.d.ts +30 -0
  139. package/dist/types/optimizer.js +1 -0
  140. package/dist/types/planning.d.ts +27 -0
  141. package/dist/types/planning.js +1 -0
  142. package/dist/types/providers.d.ts +39 -0
  143. package/dist/types/providers.js +479 -0
  144. package/dist/types/response.d.ts +55 -0
  145. package/dist/types/response.js +1 -0
  146. package/dist/types/security.d.ts +87 -0
  147. package/dist/types/security.js +1 -0
  148. package/dist/utils/ids.d.ts +1 -0
  149. package/dist/utils/ids.js +4 -0
  150. package/dist/utils/logger.d.ts +7 -0
  151. package/dist/utils/logger.js +17 -0
  152. package/dist/utils/tokenizer.d.ts +6 -0
  153. package/dist/utils/tokenizer.js +38 -0
  154. package/dist/workflow/chains.d.ts +53 -0
  155. package/dist/workflow/chains.js +117 -0
  156. package/dist/workflow/domain.d.ts +22 -0
  157. package/dist/workflow/domain.js +96 -0
  158. package/examples/agent.ts +69 -0
  159. package/examples/basic.ts +37 -0
  160. package/examples/bullmq-worker.ts +36 -0
  161. package/examples/classifier-calibration.ts +7 -0
  162. package/examples/domain-workflows.ts +38 -0
  163. package/examples/feature-flags.ts +65 -0
  164. package/examples/minimal.ts +45 -0
  165. package/examples/ocr-pdf-ingestion.ts +42 -0
  166. package/examples/optimizer.ts +43 -0
  167. package/examples/otel-express.ts +47 -0
  168. package/examples/otel-node-http.ts +46 -0
  169. package/examples/persisted-vector-store.ts +42 -0
  170. package/examples/security.ts +69 -0
  171. package/package.json +165 -0
@@ -0,0 +1,477 @@
1
+ import { OpenAIProvider } from '../providers/openai.js';
2
+ import { AnthropicProvider } from '../providers/anthropic.js';
3
+ import { GoogleProvider } from '../providers/google.js';
4
+ import { OllamaProvider } from '../providers/ollama.js';
5
+ import { OpenRouterProvider } from '../providers/openrouter.js';
6
+ import { GroqProvider } from '../providers/groq.js';
7
+ import { MistralProvider } from '../providers/mistral.js';
8
+ import { CohereProvider } from '../providers/cohere.js';
9
+ import { Router, FailoverExecutor } from '../router/index.js';
10
+ import { Logger } from '../utils/logger.js';
11
+ import { SecurityPipeline } from '../security/index.js';
12
+ import { TokenOptimizer } from '../optimizer/index.js';
13
+ import { AgentLoop } from '../agent/loop.js';
14
+ import { resolveModel } from '../models/registry.js';
15
+ import { createCacheKey, MemoryCache } from '../cache/memory-cache.js';
16
+ import { applyResponseFormat, withResponseFormat } from './response-format.js';
17
+ import { protectStreamOutput } from './secure-stream.js';
18
+ import { AuditLogger } from '../ops/audit-logger.js';
19
+ import { RateLimiter } from '../ops/rate-limiter.js';
20
+ import { completeVerified, } from '../hallucination/verification.js';
21
+ import { completeWithSelfConsistency, } from '../hallucination/consistency.js';
22
+ import { assertWithinCostBudget, estimateCost } from '../optimizer/cost.js';
23
+ import { PipelineRunner, createPipelineContext } from '../pipeline/pipeline.js';
24
+ import { MetricsCollector } from '../ops/metrics.js';
25
+ import { ProviderHealthMonitor } from '../ops/health.js';
26
+ import { SemanticCache } from '../cache/semantic-cache.js';
27
+ import { runBatch } from '../jobs/batch.js';
28
+ import { JobQueue } from '../jobs/queue.js';
29
+ import { EvalRunner } from '../evals/runner.js';
30
+ import { summarizeVerifyFormat } from '../workflow/chains.js';
31
+ export class NexusAI {
32
+ config;
33
+ providers = new Map();
34
+ router = new Router();
35
+ failover = new FailoverExecutor();
36
+ logger;
37
+ security;
38
+ optimizer;
39
+ cache;
40
+ auditLogger;
41
+ rateLimiter = new RateLimiter();
42
+ pipeline;
43
+ metrics;
44
+ health;
45
+ semanticCache;
46
+ constructor(config) {
47
+ this.config = {
48
+ routing: { mode: 'auto', strategy: 'quality' },
49
+ timeout: 60000,
50
+ ...config,
51
+ };
52
+ this.logger = new Logger(this.config.debug);
53
+ this.security = new SecurityPipeline(this.config.security || 'standard');
54
+ this.optimizer = new TokenOptimizer(this.config.tokenOptimizer || {});
55
+ this.cache = new MemoryCache(this.config.cache?.maxEntries || 500);
56
+ this.auditLogger = new AuditLogger(this.config.auditLog);
57
+ this.pipeline = new PipelineRunner(this.config.pipeline || {});
58
+ this.metrics = new MetricsCollector(this.config.metrics || {});
59
+ this.health = new ProviderHealthMonitor(this.config.health || {});
60
+ this.semanticCache = new SemanticCache({
61
+ enabled: this.config.cache?.enabled && (this.config.cache.strategy === 'semantic' || this.config.cache.strategy === 'hybrid'),
62
+ maxEntries: this.config.cache?.maxEntries,
63
+ ttlSeconds: this.config.cache?.ttlSeconds,
64
+ ...this.config.cache?.semantic,
65
+ });
66
+ this.registerConfiguredProviders();
67
+ }
68
+ async complete(request) {
69
+ let context = createPipelineContext(request);
70
+ try {
71
+ await this.metrics.recordRequest({ model: request.model });
72
+ this.rateLimiter.check(request, this.config.rateLimit);
73
+ if (this.isAuditLogEnabled()) {
74
+ await this.pipeline.trace(context, 'auditLog', () => this.auditLogger.log({
75
+ type: 'request',
76
+ userId: request.userId,
77
+ model: request.model,
78
+ timestamp: new Date().toISOString(),
79
+ metadata: this.config.auditLog?.includeInput ? { request } : undefined,
80
+ }));
81
+ }
82
+ context = await this.pipeline.runHook('beforeInput', context);
83
+ context = await this.pipeline.runCustomSteps(context);
84
+ if (this.hasResponseFormat(context.request)) {
85
+ const formattedRequest = await this.pipeline.trace(context, 'responseFormat', () => {
86
+ return withResponseFormat(context.request, this.config.responseFormat);
87
+ });
88
+ context.request = formattedRequest;
89
+ }
90
+ const optimizationResult = this.isTokenOptimizerEnabled()
91
+ ? await this.pipeline.trace(context, 'tokenOptimization', () => {
92
+ return this.optimizer.optimize(context.request);
93
+ })
94
+ : this.optimizer.optimize(context.request);
95
+ context.optimization = optimizationResult;
96
+ context.request = optimizationResult.value;
97
+ if (this.isSecurityEnabled()) {
98
+ const securityResult = await this.pipeline.trace(context, 'inputSecurity', () => {
99
+ return this.security.protectInput(context.request);
100
+ });
101
+ context.request = securityResult.value;
102
+ context.securityFindings.push(...securityResult.findings);
103
+ context.guardrailsApplied.push(...securityResult.guardrailsApplied);
104
+ try {
105
+ this.security.assertSafe(securityResult);
106
+ }
107
+ catch (error) {
108
+ if (this.isAuditLogEnabled()) {
109
+ await this.auditLogger.log({
110
+ type: 'blocked',
111
+ userId: request.userId,
112
+ model: request.model,
113
+ timestamp: new Date().toISOString(),
114
+ metadata: { findings: securityResult.findings },
115
+ });
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ context = await this.pipeline.runHook('afterSecurity', context);
121
+ const decision = await this.pipeline.trace(context, 'routing', () => {
122
+ return this.router.route(context.request, this.config, this.providers, this.health.snapshot());
123
+ });
124
+ context.route = decision;
125
+ this.logger.info('route decision', decision);
126
+ context.request = { ...context.request, model: resolveModel(decision.model, this.config).model };
127
+ if (this.isCostBudgetEnabled(context.request)) {
128
+ await this.pipeline.trace(context, 'costBudget', () => {
129
+ this.enforceCostBudget(context.request);
130
+ });
131
+ }
132
+ const responseFormat = context.request.responseFormat || this.config.responseFormat;
133
+ const cacheKey = createCacheKey({ request: context.request, responseFormat });
134
+ if (this.isCacheEnabled()) {
135
+ const cached = await this.pipeline.trace(context, 'cacheLookup', () => this.getCachedResponse(cacheKey, context.request));
136
+ if (cached) {
137
+ const cachedResponse = this.attachTrace({
138
+ ...cached,
139
+ meta: { ...cached.meta, cacheHit: true },
140
+ }, this.pipeline.finish(context));
141
+ return cachedResponse;
142
+ }
143
+ }
144
+ context = await this.pipeline.runHook('beforeProvider', context);
145
+ const response = await this.pipeline.trace(context, 'providerCall', () => {
146
+ return this.failover.complete(context.request, decision, this.providers, {
147
+ timeoutMs: this.config.timeout,
148
+ retry: this.config.retry,
149
+ onAttemptSuccess: (providerName, latencyMs) => this.health.recordSuccess(providerName, latencyMs),
150
+ onAttemptFailure: (providerName, error) => this.health.recordFailure(providerName, error),
151
+ });
152
+ });
153
+ context.response = response;
154
+ context = await this.pipeline.runHook('afterProvider', context);
155
+ context.response.meta.guardrailsApplied.push(...context.guardrailsApplied);
156
+ context.response.meta.tokensSaved += optimizationResult.usage.savedTokens;
157
+ context.response.meta.routingDecision = {
158
+ reason: decision.reason,
159
+ fallbacksConsidered: decision.fallbacks.length,
160
+ };
161
+ if (this.isSecurityEnabled()) {
162
+ const outputResult = await this.pipeline.trace(context, 'outputSecurity', () => {
163
+ return this.security.protectOutput(context.response);
164
+ });
165
+ context.response = outputResult.value;
166
+ }
167
+ if (responseFormat && responseFormat.type !== 'text') {
168
+ const formattedResponse = await this.pipeline.trace(context, 'responseValidation', () => {
169
+ return applyResponseFormat(context.response, responseFormat);
170
+ });
171
+ context.response = formattedResponse;
172
+ }
173
+ if (this.isCacheEnabled()) {
174
+ await this.pipeline.trace(context, 'cacheWrite', () => this.setCachedResponse(cacheKey, context.request, context.response));
175
+ }
176
+ context = await this.pipeline.runHook('beforeReturn', context);
177
+ const finalContext = this.pipeline.finish(context);
178
+ const finalResponse = this.attachTrace(context.response, finalContext);
179
+ if (this.isAuditLogEnabled()) {
180
+ await this.pipeline.trace(finalContext, 'auditLog', () => this.auditLogger.log({
181
+ type: 'response',
182
+ requestId: finalResponse.meta.requestId,
183
+ userId: request.userId,
184
+ model: finalResponse.meta.modelUsed,
185
+ provider: finalResponse.meta.providerUsed,
186
+ timestamp: new Date().toISOString(),
187
+ metadata: this.config.auditLog?.includeOutput ? { response: finalResponse } : undefined,
188
+ }));
189
+ }
190
+ await this.metrics.recordResponse({
191
+ provider: finalResponse.meta.providerUsed,
192
+ model: finalResponse.meta.modelUsed,
193
+ }, finalResponse.meta.latencyMs, Number(finalResponse.meta.estimatedCost.replace('$', '')));
194
+ for (const step of finalResponse.meta.pipeline?.steps || []) {
195
+ await this.metrics.recordStep(step);
196
+ }
197
+ return finalResponse;
198
+ }
199
+ catch (error) {
200
+ await this.metrics.recordError({ model: request.model });
201
+ throw error;
202
+ }
203
+ }
204
+ async completeVerified(request, options) {
205
+ return completeVerified(this, request, options);
206
+ }
207
+ async completeConsistent(request, options = {}) {
208
+ return completeWithSelfConsistency(this, request, options);
209
+ }
210
+ plan(request) {
211
+ const formattedRequest = this.hasResponseFormat(request)
212
+ ? withResponseFormat(request, this.config.responseFormat)
213
+ : request;
214
+ const optimizationResult = this.optimizer.optimize(formattedRequest);
215
+ const securityResult = this.isSecurityEnabled()
216
+ ? this.security.protectInput(optimizationResult.value)
217
+ : { ok: true, value: optimizationResult.value, findings: [], guardrailsApplied: [] };
218
+ const decision = this.router.route(securityResult.value, this.config, this.providers, this.health.snapshot());
219
+ const resolved = resolveModel(decision.model, this.config);
220
+ const outputTokens = this.estimatedOutputTokens(securityResult.value);
221
+ const cost = estimateCost({
222
+ model: resolved.model,
223
+ inputTokens: optimizationResult.usage.afterTokens,
224
+ outputTokens,
225
+ config: this.config,
226
+ });
227
+ const maxContextTokens = resolved.capabilities?.maxContextTokens;
228
+ const warnings = [
229
+ ...optimizationResult.warnings,
230
+ ...securityResult.findings.map((finding) => finding.message),
231
+ ];
232
+ if (maxContextTokens && optimizationResult.usage.afterTokens + outputTokens > maxContextTokens) {
233
+ warnings.push(`Estimated tokens exceed ${resolved.model} context window`);
234
+ }
235
+ const maxEstimatedCost = request.maxEstimatedCost ?? this.config.costBudget?.maxEstimatedCost;
236
+ if (maxEstimatedCost !== undefined && cost.totalCost > maxEstimatedCost) {
237
+ warnings.push(`Estimated cost ${cost.formatted} exceeds maxEstimatedCost $${maxEstimatedCost.toFixed(4)}`);
238
+ }
239
+ return {
240
+ requestModel: request.model,
241
+ providerName: decision.providerName,
242
+ model: resolved.model,
243
+ route: decision,
244
+ tokenUsage: optimizationResult.usage,
245
+ estimatedCost: cost,
246
+ maxContextTokens,
247
+ fitsContext: maxContextTokens ? optimizationResult.usage.afterTokens + outputTokens <= maxContextTokens : true,
248
+ cacheEligible: Boolean(this.config.cache?.enabled),
249
+ wouldBlock: !securityResult.ok,
250
+ securityFindings: securityResult.findings,
251
+ warnings,
252
+ guardrailsApplied: securityResult.guardrailsApplied,
253
+ };
254
+ }
255
+ stream(request) {
256
+ this.rateLimiter.check(request, this.config.rateLimit);
257
+ const formattedRequest = this.hasResponseFormat(request)
258
+ ? withResponseFormat(request, this.config.responseFormat)
259
+ : request;
260
+ const optimizationResult = this.optimizer.optimize(formattedRequest);
261
+ const securityResult = this.isSecurityEnabled()
262
+ ? this.security.protectInput(optimizationResult.value)
263
+ : { ok: true, value: optimizationResult.value, findings: [], guardrailsApplied: [] };
264
+ if (this.isSecurityEnabled())
265
+ this.security.assertSafe(securityResult);
266
+ const decision = this.router.route(securityResult.value, this.config, this.providers, this.health.snapshot());
267
+ this.logger.info('stream route decision', {
268
+ ...decision,
269
+ tokensSaved: optimizationResult.usage.savedTokens,
270
+ });
271
+ const routedRequest = { ...securityResult.value, model: resolveModel(decision.model, this.config).model };
272
+ const stream = this.failover.stream(routedRequest, decision, this.providers, {
273
+ timeoutMs: this.config.timeout,
274
+ retry: this.config.retry,
275
+ onAttemptSuccess: (providerName, latencyMs) => this.health.recordSuccess(providerName, latencyMs),
276
+ onAttemptFailure: (providerName, error) => this.health.recordFailure(providerName, error),
277
+ });
278
+ return this.isSecurityEnabled() ? protectStreamOutput(stream, this.security) : stream;
279
+ }
280
+ async agent(config) {
281
+ const loop = new AgentLoop(this);
282
+ return loop.run(config);
283
+ }
284
+ registerProvider(name, provider) {
285
+ this.providers.set(name, provider);
286
+ return this;
287
+ }
288
+ hasProvider(name) {
289
+ return this.providers.has(name);
290
+ }
291
+ listProviders() {
292
+ return [...this.providers.keys()];
293
+ }
294
+ use(step) {
295
+ this.pipeline.use(step);
296
+ return this;
297
+ }
298
+ async batchComplete(requests, options = {}) {
299
+ return runBatch(requests, (item) => this.complete(item), options);
300
+ }
301
+ createQueue(options = {}) {
302
+ return new JobQueue((payload) => this.complete(payload), options);
303
+ }
304
+ runEvals(cases) {
305
+ return new EvalRunner(this).run(cases);
306
+ }
307
+ summarizeVerifyFormat(options) {
308
+ return summarizeVerifyFormat(this, options);
309
+ }
310
+ getProviderHealth() {
311
+ return this.health.snapshot();
312
+ }
313
+ async checkProviders() {
314
+ const results = [];
315
+ for (const [providerName, provider] of this.providers) {
316
+ const started = Date.now();
317
+ try {
318
+ const ok = await provider.healthCheck();
319
+ if (ok)
320
+ this.health.recordSuccess(providerName, Date.now() - started);
321
+ else
322
+ this.health.recordFailure(providerName, new Error('Health check returned false'));
323
+ results.push({ providerName, ok });
324
+ }
325
+ catch (error) {
326
+ this.health.recordFailure(providerName, error);
327
+ results.push({
328
+ providerName,
329
+ ok: false,
330
+ error: error instanceof Error ? error.message : String(error),
331
+ });
332
+ }
333
+ }
334
+ return results;
335
+ }
336
+ getMetricsSnapshot() {
337
+ return this.metrics.snapshot();
338
+ }
339
+ getPrometheusMetrics() {
340
+ return this.metrics.toPrometheus();
341
+ }
342
+ clearCache() {
343
+ this.cache.clear();
344
+ this.semanticCache.clear();
345
+ }
346
+ clearExpiredCache() {
347
+ return this.cache.clearExpired();
348
+ }
349
+ getCacheStats() {
350
+ return this.cache.stats();
351
+ }
352
+ isAuditLogEnabled() {
353
+ return this.config.auditLog?.enabled === true;
354
+ }
355
+ isCacheEnabled() {
356
+ return this.config.cache?.enabled === true;
357
+ }
358
+ isCostBudgetEnabled(request) {
359
+ if (request.maxEstimatedCost !== undefined)
360
+ return true;
361
+ if (this.config.costBudget?.enabled === false)
362
+ return false;
363
+ return this.config.costBudget?.enabled === true || this.config.costBudget?.maxEstimatedCost !== undefined;
364
+ }
365
+ isSecurityEnabled() {
366
+ const security = this.config.security;
367
+ if (security === 'off')
368
+ return false;
369
+ if (typeof security === 'object' && security.level === 'off')
370
+ return false;
371
+ return true;
372
+ }
373
+ isTokenOptimizerEnabled() {
374
+ return this.config.tokenOptimizer?.enabled !== false;
375
+ }
376
+ hasResponseFormat(request) {
377
+ const responseFormat = request.responseFormat || this.config.responseFormat;
378
+ return Boolean(responseFormat && responseFormat.type !== 'text');
379
+ }
380
+ registerConfiguredProviders() {
381
+ const providers = this.config.providers;
382
+ if (providers.openai) {
383
+ this.providers.set('openai', new OpenAIProvider(providers.openai));
384
+ }
385
+ if (providers.anthropic) {
386
+ this.providers.set('anthropic', new AnthropicProvider(providers.anthropic));
387
+ }
388
+ if (providers.google) {
389
+ this.providers.set('google', new GoogleProvider(providers.google));
390
+ }
391
+ if (providers.ollama) {
392
+ this.providers.set('ollama', new OllamaProvider(providers.ollama));
393
+ }
394
+ if (providers.openrouter) {
395
+ this.providers.set('openrouter', new OpenRouterProvider(providers.openrouter));
396
+ }
397
+ if (providers.groq) {
398
+ this.providers.set('groq', new GroqProvider(providers.groq));
399
+ }
400
+ if (providers.mistral) {
401
+ this.providers.set('mistral', new MistralProvider(providers.mistral));
402
+ }
403
+ if (providers.cohere) {
404
+ this.providers.set('cohere', new CohereProvider(providers.cohere));
405
+ }
406
+ }
407
+ attachTrace(response, context) {
408
+ if (this.config.pipeline?.trace === false)
409
+ return response;
410
+ if (this.config.pipeline?.includeTraceInResponse === false)
411
+ return response;
412
+ return {
413
+ ...response,
414
+ meta: {
415
+ ...response.meta,
416
+ pipeline: context.trace,
417
+ },
418
+ };
419
+ }
420
+ async getCachedResponse(cacheKey, request) {
421
+ if (!this.config.cache?.enabled)
422
+ return undefined;
423
+ if (this.config.cache.strategy === 'semantic' || this.config.cache.strategy === 'hybrid') {
424
+ const semantic = await this.semanticCache.get(request);
425
+ if (semantic)
426
+ return semantic;
427
+ }
428
+ if (this.config.cache.strategy === 'semantic')
429
+ return undefined;
430
+ if (this.config.cache.adapter) {
431
+ return await this.config.cache.adapter.get(cacheKey);
432
+ }
433
+ return this.cache.get(cacheKey);
434
+ }
435
+ async setCachedResponse(cacheKey, request, response) {
436
+ if (!this.config.cache?.enabled)
437
+ return;
438
+ const ttl = this.config.cache.ttlSeconds || 300;
439
+ if (this.config.cache.strategy === 'semantic' || this.config.cache.strategy === 'hybrid') {
440
+ await this.semanticCache.set(cacheKey, request, response);
441
+ }
442
+ if (this.config.cache.strategy === 'semantic')
443
+ return;
444
+ if (this.config.cache.adapter) {
445
+ await this.config.cache.adapter.set(cacheKey, response, ttl);
446
+ return;
447
+ }
448
+ this.cache.set(cacheKey, response, ttl);
449
+ }
450
+ enforceCostBudget(request) {
451
+ const maxEstimatedCost = request.maxEstimatedCost ?? this.config.costBudget?.maxEstimatedCost;
452
+ if (maxEstimatedCost === undefined)
453
+ return;
454
+ const estimate = estimateCost({
455
+ model: request.model,
456
+ inputTokens: this.optimizer.optimize(request).usage.afterTokens,
457
+ outputTokens: this.estimatedOutputTokens(request),
458
+ config: this.config,
459
+ });
460
+ try {
461
+ assertWithinCostBudget(estimate, maxEstimatedCost);
462
+ }
463
+ catch (error) {
464
+ if (this.config.costBudget?.onExceeded === 'warn') {
465
+ this.logger.warn(error instanceof Error ? error.message : String(error));
466
+ return;
467
+ }
468
+ throw error;
469
+ }
470
+ }
471
+ estimatedOutputTokens(request) {
472
+ return request.estimatedOutputTokens
473
+ ?? this.config.costBudget?.estimatedOutputTokens
474
+ ?? request.maxTokens
475
+ ?? 1000;
476
+ }
477
+ }
@@ -0,0 +1,8 @@
1
+ import type { CompletionRequest } from '../types/messages.js';
2
+ import type { NexusResponse } from '../types/response.js';
3
+ import type { ResponseFormatConfig } from '../types/config.js';
4
+ export declare class ResponseFormatError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export declare function applyResponseFormat(response: NexusResponse, config?: ResponseFormatConfig): NexusResponse;
8
+ export declare function withResponseFormat(request: CompletionRequest, config?: ResponseFormatConfig): CompletionRequest;
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod';
2
+ export class ResponseFormatError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'ResponseFormatError';
6
+ }
7
+ }
8
+ export function applyResponseFormat(response, config) {
9
+ if (!config || config.type === 'text')
10
+ return response;
11
+ let parsed;
12
+ try {
13
+ parsed = JSON.parse(response.content);
14
+ }
15
+ catch {
16
+ throw new ResponseFormatError('Expected model output to be valid JSON');
17
+ }
18
+ if (config.type === 'json_schema' && config.schema) {
19
+ if (isZodShape(config.schema)) {
20
+ const schema = z.object(config.schema).passthrough();
21
+ const result = schema.safeParse(parsed);
22
+ if (!result.success) {
23
+ throw new ResponseFormatError(`Model output failed JSON schema validation: ${result.error.message}`);
24
+ }
25
+ }
26
+ else {
27
+ const errors = validateJsonSchemaLike(parsed, config.schema);
28
+ if (errors.length) {
29
+ throw new ResponseFormatError(`Model output failed JSON schema validation: ${errors.join('; ')}`);
30
+ }
31
+ }
32
+ }
33
+ return response;
34
+ }
35
+ export function withResponseFormat(request, config) {
36
+ const responseFormat = request.responseFormat || config;
37
+ if (!responseFormat || responseFormat.type === 'text')
38
+ return request;
39
+ const requestResponseFormat = {
40
+ type: responseFormat.type,
41
+ schema: responseFormat.schema,
42
+ };
43
+ const schemaInstruction = responseFormat.type === 'json_schema' && responseFormat.schema
44
+ ? `The JSON must match this schema description: ${JSON.stringify(responseFormat.schema)}`
45
+ : 'Use a stable object shape with explicit keys.';
46
+ const systemMessage = {
47
+ role: 'system',
48
+ content: [
49
+ 'Return only valid JSON.',
50
+ 'Do not include markdown, prose, comments, or code fences.',
51
+ 'If a value is unknown, use null or an explicit "unknown" value instead of inventing facts.',
52
+ schemaInstruction,
53
+ ].join('\n'),
54
+ };
55
+ return {
56
+ ...request,
57
+ temperature: request.temperature ?? 0,
58
+ topP: request.topP ?? 0.1,
59
+ responseFormat: requestResponseFormat,
60
+ messages: [systemMessage, ...request.messages],
61
+ };
62
+ }
63
+ function isZodShape(schema) {
64
+ return Object.values(schema).some((value) => {
65
+ return Boolean(value && typeof value === 'object' && 'safeParse' in value);
66
+ });
67
+ }
68
+ function validateJsonSchemaLike(value, schema, path = '$') {
69
+ const errors = [];
70
+ const type = schema.type;
71
+ if (typeof type === 'string' && !matchesJsonType(value, type)) {
72
+ errors.push(`${path} expected ${type}`);
73
+ return errors;
74
+ }
75
+ if (type === 'object' || schema.properties) {
76
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
77
+ errors.push(`${path} expected object`);
78
+ return errors;
79
+ }
80
+ const objectValue = value;
81
+ const required = Array.isArray(schema.required) ? schema.required : [];
82
+ for (const key of required) {
83
+ if (typeof key === 'string' && !(key in objectValue)) {
84
+ errors.push(`${path}.${key} is required`);
85
+ }
86
+ }
87
+ const properties = schema.properties;
88
+ if (properties && typeof properties === 'object' && !Array.isArray(properties)) {
89
+ for (const [key, childSchema] of Object.entries(properties)) {
90
+ if (key in objectValue && childSchema && typeof childSchema === 'object' && !Array.isArray(childSchema)) {
91
+ errors.push(...validateJsonSchemaLike(objectValue[key], childSchema, `${path}.${key}`));
92
+ }
93
+ }
94
+ }
95
+ }
96
+ if (type === 'array' && Array.isArray(value) && schema.items && typeof schema.items === 'object') {
97
+ value.forEach((item, index) => {
98
+ errors.push(...validateJsonSchemaLike(item, schema.items, `${path}[${index}]`));
99
+ });
100
+ }
101
+ return errors;
102
+ }
103
+ function matchesJsonType(value, type) {
104
+ switch (type) {
105
+ case 'object':
106
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
107
+ case 'array':
108
+ return Array.isArray(value);
109
+ case 'string':
110
+ return typeof value === 'string';
111
+ case 'number':
112
+ case 'integer':
113
+ return typeof value === 'number' && Number.isFinite(value) && (type === 'number' || Number.isInteger(value));
114
+ case 'boolean':
115
+ return typeof value === 'boolean';
116
+ case 'null':
117
+ return value === null;
118
+ default:
119
+ return true;
120
+ }
121
+ }
@@ -0,0 +1,3 @@
1
+ import type { SecurityPipeline } from '../security/index.js';
2
+ import type { NexusStream } from '../types/response.js';
3
+ export declare function protectStreamOutput(stream: NexusStream, security: SecurityPipeline): NexusStream;
@@ -0,0 +1,41 @@
1
+ export function protectStreamOutput(stream, security) {
2
+ let aborted = false;
3
+ return {
4
+ async *[Symbol.asyncIterator]() {
5
+ let buffered = '';
6
+ for await (const chunk of stream) {
7
+ if (aborted)
8
+ return;
9
+ if (chunk.type !== 'text' || !chunk.content) {
10
+ yield chunk;
11
+ continue;
12
+ }
13
+ buffered += chunk.content;
14
+ const pseudoResponse = {
15
+ content: buffered,
16
+ role: 'assistant',
17
+ finishReason: 'stop',
18
+ meta: {
19
+ requestId: chunk.meta?.requestId || 'stream',
20
+ providerUsed: chunk.meta?.providerUsed || 'unknown',
21
+ modelUsed: chunk.meta?.modelUsed || 'unknown',
22
+ latencyMs: chunk.meta?.latencyMs || 0,
23
+ tokensInput: chunk.meta?.tokensInput || 0,
24
+ tokensOutput: chunk.meta?.tokensOutput || 0,
25
+ tokensSaved: chunk.meta?.tokensSaved || 0,
26
+ estimatedCost: chunk.meta?.estimatedCost || '$0.00',
27
+ cacheHit: chunk.meta?.cacheHit || false,
28
+ guardrailsApplied: chunk.meta?.guardrailsApplied || [],
29
+ },
30
+ };
31
+ const protectedOutput = security.protectOutput(pseudoResponse).value.content;
32
+ const safeDelta = protectedOutput.slice(Math.max(0, protectedOutput.length - chunk.content.length));
33
+ yield { ...chunk, content: safeDelta };
34
+ }
35
+ },
36
+ abort() {
37
+ aborted = true;
38
+ stream.abort();
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,4 @@
1
+ import type { NexusStream, StreamChunk } from '../types/response.js';
2
+ export declare function collectStream(stream: NexusStream): Promise<string>;
3
+ export declare function mapStream(stream: NexusStream, mapChunk: (chunk: StreamChunk) => StreamChunk | Promise<StreamChunk>): AsyncGenerator<StreamChunk>;
4
+ export declare function createTextStream(text: string): NexusStream;