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.
- package/LICENSE +21 -0
- package/README.md +1249 -0
- package/assets/nexus-ai-pro-presentation.jpg +0 -0
- package/dist/agent/loop.d.ts +12 -0
- package/dist/agent/loop.js +89 -0
- package/dist/agent/tool.d.ts +16 -0
- package/dist/agent/tool.js +43 -0
- package/dist/cache/adapters.d.ts +42 -0
- package/dist/cache/adapters.js +76 -0
- package/dist/cache/memory-cache.d.ts +20 -0
- package/dist/cache/memory-cache.js +63 -0
- package/dist/cache/semantic-cache.d.ts +19 -0
- package/dist/cache/semantic-cache.js +71 -0
- package/dist/connectors/web.d.ts +8 -0
- package/dist/connectors/web.js +60 -0
- package/dist/core/nexus.d.ts +71 -0
- package/dist/core/nexus.js +477 -0
- package/dist/core/response-format.d.ts +8 -0
- package/dist/core/response-format.js +121 -0
- package/dist/core/secure-stream.d.ts +3 -0
- package/dist/core/secure-stream.js +41 -0
- package/dist/core/streaming.d.ts +4 -0
- package/dist/core/streaming.js +30 -0
- package/dist/embeddings/providers.d.ts +20 -0
- package/dist/embeddings/providers.js +60 -0
- package/dist/evals/metrics.d.ts +75 -0
- package/dist/evals/metrics.js +157 -0
- package/dist/evals/runner.d.ts +35 -0
- package/dist/evals/runner.js +47 -0
- package/dist/hallucination/consistency.d.ts +14 -0
- package/dist/hallucination/consistency.js +58 -0
- package/dist/hallucination/factual.d.ts +14 -0
- package/dist/hallucination/factual.js +35 -0
- package/dist/hallucination/knowledge-graph.d.ts +27 -0
- package/dist/hallucination/knowledge-graph.js +70 -0
- package/dist/hallucination/rag.d.ts +21 -0
- package/dist/hallucination/rag.js +46 -0
- package/dist/hallucination/retrieval.d.ts +24 -0
- package/dist/hallucination/retrieval.js +71 -0
- package/dist/hallucination/verification.d.ts +36 -0
- package/dist/hallucination/verification.js +110 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +53 -0
- package/dist/jobs/batch.d.ts +11 -0
- package/dist/jobs/batch.js +32 -0
- package/dist/jobs/durable-adapters.d.ts +48 -0
- package/dist/jobs/durable-adapters.js +50 -0
- package/dist/jobs/queue.d.ts +24 -0
- package/dist/jobs/queue.js +57 -0
- package/dist/models/registry.d.ts +15 -0
- package/dist/models/registry.js +43 -0
- package/dist/next/route-handler.d.ts +6 -0
- package/dist/next/route-handler.js +35 -0
- package/dist/ops/audit-logger.d.ts +6 -0
- package/dist/ops/audit-logger.js +19 -0
- package/dist/ops/health.d.ts +29 -0
- package/dist/ops/health.js +69 -0
- package/dist/ops/metrics.d.ts +53 -0
- package/dist/ops/metrics.js +122 -0
- package/dist/ops/otel-tracing.d.ts +20 -0
- package/dist/ops/otel-tracing.js +50 -0
- package/dist/ops/rate-limiter.d.ts +11 -0
- package/dist/ops/rate-limiter.js +33 -0
- package/dist/optimizer/budget.d.ts +19 -0
- package/dist/optimizer/budget.js +88 -0
- package/dist/optimizer/cost.d.ts +13 -0
- package/dist/optimizer/cost.js +31 -0
- package/dist/optimizer/densifier.d.ts +10 -0
- package/dist/optimizer/densifier.js +77 -0
- package/dist/optimizer/index.d.ts +12 -0
- package/dist/optimizer/index.js +59 -0
- package/dist/pipeline/pipeline.d.ts +14 -0
- package/dist/pipeline/pipeline.js +106 -0
- package/dist/pipeline/types.d.ts +46 -0
- package/dist/pipeline/types.js +1 -0
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.js +183 -0
- package/dist/providers/base.d.ts +19 -0
- package/dist/providers/base.js +75 -0
- package/dist/providers/cohere.d.ts +16 -0
- package/dist/providers/cohere.js +104 -0
- package/dist/providers/google.d.ts +22 -0
- package/dist/providers/google.js +226 -0
- package/dist/providers/groq.d.ts +15 -0
- package/dist/providers/groq.js +50 -0
- package/dist/providers/mistral.d.ts +15 -0
- package/dist/providers/mistral.js +50 -0
- package/dist/providers/ollama.d.ts +15 -0
- package/dist/providers/ollama.js +108 -0
- package/dist/providers/openai.d.ts +21 -0
- package/dist/providers/openai.js +304 -0
- package/dist/providers/openrouter.d.ts +14 -0
- package/dist/providers/openrouter.js +19 -0
- package/dist/rag/file-ingestion.d.ts +20 -0
- package/dist/rag/file-ingestion.js +55 -0
- package/dist/rag/ingestion.d.ts +21 -0
- package/dist/rag/ingestion.js +47 -0
- package/dist/router/auto-router.d.ts +17 -0
- package/dist/router/auto-router.js +228 -0
- package/dist/router/failover.d.ts +21 -0
- package/dist/router/failover.js +126 -0
- package/dist/router/index.d.ts +12 -0
- package/dist/router/index.js +31 -0
- package/dist/router/rules-router.d.ts +7 -0
- package/dist/router/rules-router.js +68 -0
- package/dist/router/types.d.ts +19 -0
- package/dist/router/types.js +1 -0
- package/dist/security/index.d.ts +31 -0
- package/dist/security/index.js +173 -0
- package/dist/security/injection-calibration.d.ts +17 -0
- package/dist/security/injection-calibration.js +48 -0
- package/dist/security/injection-detector.d.ts +8 -0
- package/dist/security/injection-detector.js +66 -0
- package/dist/security/input-guard.d.ts +7 -0
- package/dist/security/input-guard.js +109 -0
- package/dist/security/output-guard.d.ts +6 -0
- package/dist/security/output-guard.js +144 -0
- package/dist/security/pii-detector.d.ts +8 -0
- package/dist/security/pii-detector.js +66 -0
- package/dist/security/policies.d.ts +4 -0
- package/dist/security/policies.js +61 -0
- package/dist/security/prompt-hardening.d.ts +6 -0
- package/dist/security/prompt-hardening.js +18 -0
- package/dist/security/schema-validator.d.ts +234 -0
- package/dist/security/schema-validator.js +68 -0
- package/dist/security/semantic-injection-classifier.d.ts +19 -0
- package/dist/security/semantic-injection-classifier.js +75 -0
- package/dist/security/upload-scanner.d.ts +29 -0
- package/dist/security/upload-scanner.js +72 -0
- package/dist/testing/provider-conformance.d.ts +24 -0
- package/dist/testing/provider-conformance.js +97 -0
- package/dist/types/agent.d.ts +34 -0
- package/dist/types/agent.js +1 -0
- package/dist/types/config.d.ts +171 -0
- package/dist/types/config.js +1 -0
- package/dist/types/messages.d.ts +96 -0
- package/dist/types/messages.js +1 -0
- package/dist/types/optimizer.d.ts +30 -0
- package/dist/types/optimizer.js +1 -0
- package/dist/types/planning.d.ts +27 -0
- package/dist/types/planning.js +1 -0
- package/dist/types/providers.d.ts +39 -0
- package/dist/types/providers.js +479 -0
- package/dist/types/response.d.ts +55 -0
- package/dist/types/response.js +1 -0
- package/dist/types/security.d.ts +87 -0
- package/dist/types/security.js +1 -0
- package/dist/utils/ids.d.ts +1 -0
- package/dist/utils/ids.js +4 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +17 -0
- package/dist/utils/tokenizer.d.ts +6 -0
- package/dist/utils/tokenizer.js +38 -0
- package/dist/workflow/chains.d.ts +53 -0
- package/dist/workflow/chains.js +117 -0
- package/dist/workflow/domain.d.ts +22 -0
- package/dist/workflow/domain.js +96 -0
- package/examples/agent.ts +69 -0
- package/examples/basic.ts +37 -0
- package/examples/bullmq-worker.ts +36 -0
- package/examples/classifier-calibration.ts +7 -0
- package/examples/domain-workflows.ts +38 -0
- package/examples/feature-flags.ts +65 -0
- package/examples/minimal.ts +45 -0
- package/examples/ocr-pdf-ingestion.ts +42 -0
- package/examples/optimizer.ts +43 -0
- package/examples/otel-express.ts +47 -0
- package/examples/otel-node-http.ts +46 -0
- package/examples/persisted-vector-store.ts +42 -0
- package/examples/security.ts +69 -0
- package/package.json +165 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export class PromptDensifier {
|
|
2
|
+
densifyRequest(request, config = {}) {
|
|
3
|
+
if (config.enabled === false)
|
|
4
|
+
return { request, techniques: [] };
|
|
5
|
+
const techniques = config.techniques || ['whitespace-cleanup', 'phrase-compression', 'list-compaction'];
|
|
6
|
+
const applied = new Set();
|
|
7
|
+
const next = {
|
|
8
|
+
...request,
|
|
9
|
+
messages: request.messages.map((message) => ({
|
|
10
|
+
...message,
|
|
11
|
+
content: typeof message.content === 'string'
|
|
12
|
+
? this.densifyText(message.content, techniques, applied, config)
|
|
13
|
+
: message.content.map((part) => part.type === 'text'
|
|
14
|
+
? { ...part, text: this.densifyText(part.text, techniques, applied, config) }
|
|
15
|
+
: part),
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
return { request: next, techniques: [...applied] };
|
|
19
|
+
}
|
|
20
|
+
densifyText(text, techniques, applied, config) {
|
|
21
|
+
const segments = this.splitCodeBlocks(text, config.preserveCodeBlocks !== false);
|
|
22
|
+
return segments.map((segment) => {
|
|
23
|
+
if (segment.type === 'code')
|
|
24
|
+
return segment.value;
|
|
25
|
+
let output = segment.value;
|
|
26
|
+
if (techniques.includes('whitespace-cleanup')) {
|
|
27
|
+
const before = output;
|
|
28
|
+
output = output
|
|
29
|
+
.replace(/[ \t]+/g, ' ')
|
|
30
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
31
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
32
|
+
.trim();
|
|
33
|
+
if (output !== before)
|
|
34
|
+
applied.add('whitespace-cleanup');
|
|
35
|
+
}
|
|
36
|
+
if (techniques.includes('phrase-compression')) {
|
|
37
|
+
const before = output;
|
|
38
|
+
output = output
|
|
39
|
+
.replace(/please make sure that/gi, 'ensure')
|
|
40
|
+
.replace(/you should/gi, 'do')
|
|
41
|
+
.replace(/it is important to/gi, 'must')
|
|
42
|
+
.replace(/in order to/gi, 'to')
|
|
43
|
+
.replace(/due to the fact that/gi, 'because')
|
|
44
|
+
.replace(/at this point in time/gi, 'now')
|
|
45
|
+
.replace(/as soon as possible/gi, 'ASAP');
|
|
46
|
+
if (output !== before)
|
|
47
|
+
applied.add('phrase-compression');
|
|
48
|
+
}
|
|
49
|
+
if (techniques.includes('list-compaction')) {
|
|
50
|
+
const before = output;
|
|
51
|
+
output = output.replace(/\n\s*[-*]\s+/g, '\n- ');
|
|
52
|
+
if (output !== before)
|
|
53
|
+
applied.add('list-compaction');
|
|
54
|
+
}
|
|
55
|
+
return output;
|
|
56
|
+
}).join('');
|
|
57
|
+
}
|
|
58
|
+
splitCodeBlocks(text, preserveCodeBlocks) {
|
|
59
|
+
if (!preserveCodeBlocks)
|
|
60
|
+
return [{ type: 'text', value: text }];
|
|
61
|
+
const parts = [];
|
|
62
|
+
const regex = /```[\s\S]*?```/g;
|
|
63
|
+
let lastIndex = 0;
|
|
64
|
+
let match;
|
|
65
|
+
while ((match = regex.exec(text)) !== null) {
|
|
66
|
+
if (match.index > lastIndex) {
|
|
67
|
+
parts.push({ type: 'text', value: text.slice(lastIndex, match.index) });
|
|
68
|
+
}
|
|
69
|
+
parts.push({ type: 'code', value: match[0] });
|
|
70
|
+
lastIndex = match.index + match[0].length;
|
|
71
|
+
}
|
|
72
|
+
if (lastIndex < text.length) {
|
|
73
|
+
parts.push({ type: 'text', value: text.slice(lastIndex) });
|
|
74
|
+
}
|
|
75
|
+
return parts;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
2
|
+
import type { OptimizationResult, TokenOptimizerConfig } from '../types/optimizer.js';
|
|
3
|
+
export declare class TokenOptimizer {
|
|
4
|
+
private config;
|
|
5
|
+
private tokenizer;
|
|
6
|
+
private densifier;
|
|
7
|
+
private budget;
|
|
8
|
+
constructor(config?: TokenOptimizerConfig);
|
|
9
|
+
optimize(request: CompletionRequest): OptimizationResult<CompletionRequest>;
|
|
10
|
+
}
|
|
11
|
+
export { PromptDensifier } from './densifier.js';
|
|
12
|
+
export { BudgetEnforcer, TokenBudgetError } from './budget.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Tokenizer } from '../utils/tokenizer.js';
|
|
2
|
+
import { PromptDensifier } from './densifier.js';
|
|
3
|
+
import { BudgetEnforcer } from './budget.js';
|
|
4
|
+
export class TokenOptimizer {
|
|
5
|
+
config;
|
|
6
|
+
tokenizer = new Tokenizer();
|
|
7
|
+
densifier = new PromptDensifier();
|
|
8
|
+
budget = new BudgetEnforcer(this.tokenizer);
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
optimize(request) {
|
|
13
|
+
if (this.config.enabled === false) {
|
|
14
|
+
return {
|
|
15
|
+
value: request,
|
|
16
|
+
usage: {
|
|
17
|
+
beforeTokens: 0,
|
|
18
|
+
afterTokens: 0,
|
|
19
|
+
savedTokens: 0,
|
|
20
|
+
savedPercent: 0,
|
|
21
|
+
},
|
|
22
|
+
techniquesApplied: [],
|
|
23
|
+
warnings: [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const beforeTokens = this.tokenizer.estimateRequestTokens(request);
|
|
27
|
+
let value = request;
|
|
28
|
+
const techniquesApplied = [];
|
|
29
|
+
const warnings = [];
|
|
30
|
+
const budgetConfig = this.config.budget || {};
|
|
31
|
+
const preBudget = this.budget.check(value, budgetConfig);
|
|
32
|
+
warnings.push(...preBudget.warnings);
|
|
33
|
+
const shouldDensify = this.config.densification?.enabled !== false && (this.config.densification?.enabled === true ||
|
|
34
|
+
budgetConfig.onExceeded === 'densify' ||
|
|
35
|
+
preBudget.exceeded);
|
|
36
|
+
if (shouldDensify) {
|
|
37
|
+
const densified = this.densifier.densifyRequest(value, this.config.densification);
|
|
38
|
+
value = densified.request;
|
|
39
|
+
techniquesApplied.push(...densified.techniques);
|
|
40
|
+
}
|
|
41
|
+
value = this.budget.enforce(value, budgetConfig);
|
|
42
|
+
const afterTokens = this.tokenizer.estimateRequestTokens(value);
|
|
43
|
+
const savedTokens = Math.max(0, beforeTokens - afterTokens);
|
|
44
|
+
const savedPercent = beforeTokens === 0 ? 0 : Math.round((savedTokens / beforeTokens) * 10000) / 100;
|
|
45
|
+
return {
|
|
46
|
+
value,
|
|
47
|
+
usage: {
|
|
48
|
+
beforeTokens,
|
|
49
|
+
afterTokens,
|
|
50
|
+
savedTokens,
|
|
51
|
+
savedPercent,
|
|
52
|
+
},
|
|
53
|
+
techniquesApplied: [...new Set(techniquesApplied)],
|
|
54
|
+
warnings,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export { PromptDensifier } from './densifier.js';
|
|
59
|
+
export { BudgetEnforcer, TokenBudgetError } from './budget.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PipelineConfig, PipelineContext, PipelineHookName, PipelineStep, PipelineStepName } from './types.js';
|
|
2
|
+
export declare class PipelineRunner {
|
|
3
|
+
private config;
|
|
4
|
+
private steps;
|
|
5
|
+
constructor(config?: PipelineConfig);
|
|
6
|
+
use(step: PipelineStep): this;
|
|
7
|
+
runHook(name: PipelineHookName, context: PipelineContext): Promise<PipelineContext>;
|
|
8
|
+
runCustomSteps(context: PipelineContext): Promise<PipelineContext>;
|
|
9
|
+
trace<T>(context: PipelineContext, name: PipelineStepName, fn: () => T | Promise<T>, metadata?: Record<string, unknown>): Promise<T>;
|
|
10
|
+
finish(context: PipelineContext): PipelineContext;
|
|
11
|
+
private runMiddleware;
|
|
12
|
+
private handlersForHook;
|
|
13
|
+
}
|
|
14
|
+
export declare function createPipelineContext(request: import('../types/messages.js').CompletionRequest): PipelineContext;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export class PipelineRunner {
|
|
2
|
+
config;
|
|
3
|
+
steps = [];
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
for (const step of config.steps || []) {
|
|
7
|
+
this.use(step);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
use(step) {
|
|
11
|
+
this.steps.push(step);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
async runHook(name, context) {
|
|
15
|
+
if (this.config.enabled === false)
|
|
16
|
+
return context;
|
|
17
|
+
const handlers = this.handlersForHook(name);
|
|
18
|
+
let next = context;
|
|
19
|
+
for (const handler of handlers) {
|
|
20
|
+
next = await this.runMiddleware(name, handler, next);
|
|
21
|
+
}
|
|
22
|
+
return next;
|
|
23
|
+
}
|
|
24
|
+
async runCustomSteps(context) {
|
|
25
|
+
if (this.config.enabled === false)
|
|
26
|
+
return context;
|
|
27
|
+
let next = context;
|
|
28
|
+
for (const step of this.steps) {
|
|
29
|
+
next = await this.runMiddleware(step.name, step.run, next);
|
|
30
|
+
}
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
async trace(context, name, fn, metadata) {
|
|
34
|
+
if (this.config.trace === false) {
|
|
35
|
+
return await fn();
|
|
36
|
+
}
|
|
37
|
+
const started = Date.now();
|
|
38
|
+
const startedAt = new Date(started).toISOString();
|
|
39
|
+
try {
|
|
40
|
+
const result = await fn();
|
|
41
|
+
const ended = Date.now();
|
|
42
|
+
context.trace.steps.push({
|
|
43
|
+
name,
|
|
44
|
+
startedAt,
|
|
45
|
+
endedAt: new Date(ended).toISOString(),
|
|
46
|
+
durationMs: ended - started,
|
|
47
|
+
ok: true,
|
|
48
|
+
metadata,
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const ended = Date.now();
|
|
54
|
+
context.trace.steps.push({
|
|
55
|
+
name,
|
|
56
|
+
startedAt,
|
|
57
|
+
endedAt: new Date(ended).toISOString(),
|
|
58
|
+
durationMs: ended - started,
|
|
59
|
+
ok: false,
|
|
60
|
+
metadata,
|
|
61
|
+
error: error instanceof Error ? error.message : String(error),
|
|
62
|
+
});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finish(context) {
|
|
67
|
+
const ended = Date.now();
|
|
68
|
+
context.trace.endedAt = new Date(ended).toISOString();
|
|
69
|
+
context.trace.durationMs = ended - Date.parse(context.trace.startedAt);
|
|
70
|
+
return context;
|
|
71
|
+
}
|
|
72
|
+
async runMiddleware(name, handler, context) {
|
|
73
|
+
return this.trace(context, name, async () => {
|
|
74
|
+
const result = await handler(context);
|
|
75
|
+
if (!result)
|
|
76
|
+
return context;
|
|
77
|
+
if ('messages' in result && 'model' in result) {
|
|
78
|
+
context.request = result;
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
if ('content' in result && 'role' in result && 'meta' in result) {
|
|
82
|
+
context.response = result;
|
|
83
|
+
return context;
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
handlersForHook(name) {
|
|
89
|
+
const hook = this.config.hooks?.[name];
|
|
90
|
+
if (!hook)
|
|
91
|
+
return [];
|
|
92
|
+
return Array.isArray(hook) ? hook : [hook];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function createPipelineContext(request) {
|
|
96
|
+
return {
|
|
97
|
+
request,
|
|
98
|
+
securityFindings: [],
|
|
99
|
+
guardrailsApplied: [],
|
|
100
|
+
metadata: {},
|
|
101
|
+
trace: {
|
|
102
|
+
startedAt: new Date().toISOString(),
|
|
103
|
+
steps: [],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
2
|
+
import type { NexusResponse } from '../types/response.js';
|
|
3
|
+
import type { RouteDecision } from '../router/types.js';
|
|
4
|
+
import type { OptimizationResult } from '../types/optimizer.js';
|
|
5
|
+
import type { SecurityFinding } from '../types/security.js';
|
|
6
|
+
export type PipelineHookName = 'beforeInput' | 'afterSecurity' | 'beforeProvider' | 'afterProvider' | 'beforeReturn';
|
|
7
|
+
export type PipelineStepName = PipelineHookName | 'responseFormat' | 'tokenOptimization' | 'inputSecurity' | 'routing' | 'costBudget' | 'cacheLookup' | 'providerCall' | 'outputSecurity' | 'responseValidation' | 'cacheWrite' | 'auditLog' | string;
|
|
8
|
+
export interface PipelineTraceStep {
|
|
9
|
+
name: PipelineStepName;
|
|
10
|
+
startedAt: string;
|
|
11
|
+
endedAt: string;
|
|
12
|
+
durationMs: number;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PipelineTrace {
|
|
18
|
+
requestId?: string;
|
|
19
|
+
startedAt: string;
|
|
20
|
+
endedAt?: string;
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
steps: PipelineTraceStep[];
|
|
23
|
+
}
|
|
24
|
+
export interface PipelineContext {
|
|
25
|
+
request: CompletionRequest;
|
|
26
|
+
response?: NexusResponse;
|
|
27
|
+
route?: RouteDecision;
|
|
28
|
+
optimization?: OptimizationResult<CompletionRequest>;
|
|
29
|
+
securityFindings: SecurityFinding[];
|
|
30
|
+
guardrailsApplied: string[];
|
|
31
|
+
metadata: Record<string, unknown>;
|
|
32
|
+
trace: PipelineTrace;
|
|
33
|
+
}
|
|
34
|
+
export type PipelineMiddleware = (context: PipelineContext) => void | PipelineContext | CompletionRequest | NexusResponse | Promise<void | PipelineContext | CompletionRequest | NexusResponse>;
|
|
35
|
+
export interface PipelineStep {
|
|
36
|
+
name: PipelineStepName;
|
|
37
|
+
run: PipelineMiddleware;
|
|
38
|
+
}
|
|
39
|
+
export type PipelineHooksConfig = Partial<Record<PipelineHookName, PipelineMiddleware | PipelineMiddleware[]>>;
|
|
40
|
+
export interface PipelineConfig {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
trace?: boolean;
|
|
43
|
+
includeTraceInResponse?: boolean;
|
|
44
|
+
hooks?: PipelineHooksConfig;
|
|
45
|
+
steps?: PipelineStep[];
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseProvider, type ProviderInfo } from './base.js';
|
|
2
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
3
|
+
import type { NexusResponse, NexusStream } from '../types/response.js';
|
|
4
|
+
import type { AnthropicProviderConfig } from '../types/config.js';
|
|
5
|
+
export declare class AnthropicProvider extends BaseProvider {
|
|
6
|
+
readonly info: ProviderInfo;
|
|
7
|
+
private client;
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config: AnthropicProviderConfig);
|
|
10
|
+
private getClient;
|
|
11
|
+
private formatMessages;
|
|
12
|
+
private formatToolsAnthropic;
|
|
13
|
+
complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
14
|
+
stream(request: CompletionRequest): NexusStream;
|
|
15
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { BaseProvider } from './base.js';
|
|
2
|
+
import { generateRequestId } from '../utils/ids.js';
|
|
3
|
+
export class AnthropicProvider extends BaseProvider {
|
|
4
|
+
info = { name: 'anthropic', isLocal: false };
|
|
5
|
+
client;
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
super();
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
async getClient() {
|
|
12
|
+
if (!this.client) {
|
|
13
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
14
|
+
this.client = new Anthropic({
|
|
15
|
+
apiKey: this.config.apiKey,
|
|
16
|
+
baseURL: this.config.baseUrl,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return this.client;
|
|
20
|
+
}
|
|
21
|
+
formatMessages(messages) {
|
|
22
|
+
let system;
|
|
23
|
+
const formatted = [];
|
|
24
|
+
for (const msg of messages) {
|
|
25
|
+
if (msg.role === 'system') {
|
|
26
|
+
system = typeof msg.content === 'string'
|
|
27
|
+
? msg.content
|
|
28
|
+
: msg.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n');
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (typeof msg.content === 'string') {
|
|
32
|
+
formatted.push({ role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const parts = msg.content.map((part) => {
|
|
36
|
+
switch (part.type) {
|
|
37
|
+
case 'text':
|
|
38
|
+
return { type: 'text', text: part.text };
|
|
39
|
+
case 'image': {
|
|
40
|
+
const src = part.source;
|
|
41
|
+
if ('base64' in src) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'image',
|
|
44
|
+
source: { type: 'base64', media_type: src.mimeType || 'image/png', data: src.base64 },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if ('buffer' in src) {
|
|
48
|
+
return {
|
|
49
|
+
type: 'image',
|
|
50
|
+
source: {
|
|
51
|
+
type: 'base64',
|
|
52
|
+
media_type: src.mimeType || 'image/png',
|
|
53
|
+
data: src.buffer.toString('base64'),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if ('url' in src) {
|
|
58
|
+
return { type: 'image', source: { type: 'url', url: src.url } };
|
|
59
|
+
}
|
|
60
|
+
return { type: 'text', text: '[image from path — requires preprocessing]' };
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
return { type: 'text', text: `[${part.type} content — not yet supported for this provider]` };
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
formatted.push({ role: msg.role === 'assistant' ? 'assistant' : 'user', content: parts });
|
|
67
|
+
}
|
|
68
|
+
return { system, messages: formatted };
|
|
69
|
+
}
|
|
70
|
+
formatToolsAnthropic(tools) {
|
|
71
|
+
if (!tools || tools.length === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
return tools.map((t) => ({
|
|
74
|
+
name: t.name,
|
|
75
|
+
description: t.description,
|
|
76
|
+
input_schema: t.parameters,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
async complete(request) {
|
|
80
|
+
const client = await this.getClient();
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
const { system, messages } = this.formatMessages(request.messages);
|
|
83
|
+
const params = {
|
|
84
|
+
model: request.model,
|
|
85
|
+
messages,
|
|
86
|
+
max_tokens: request.maxTokens || 4096,
|
|
87
|
+
temperature: request.temperature,
|
|
88
|
+
top_p: request.topP,
|
|
89
|
+
stop_sequences: request.stop ? (Array.isArray(request.stop) ? request.stop : [request.stop]) : undefined,
|
|
90
|
+
};
|
|
91
|
+
if (system)
|
|
92
|
+
params.system = system;
|
|
93
|
+
const tools = this.formatToolsAnthropic(request.tools);
|
|
94
|
+
if (tools)
|
|
95
|
+
params.tools = tools;
|
|
96
|
+
const result = await client.messages.create(params);
|
|
97
|
+
const latency = Date.now() - startTime;
|
|
98
|
+
let content = '';
|
|
99
|
+
const toolCalls = [];
|
|
100
|
+
for (const block of result.content) {
|
|
101
|
+
if (block.type === 'text') {
|
|
102
|
+
content += block.text;
|
|
103
|
+
}
|
|
104
|
+
else if (block.type === 'tool_use') {
|
|
105
|
+
toolCalls.push({
|
|
106
|
+
id: block.id,
|
|
107
|
+
type: 'function',
|
|
108
|
+
function: { name: block.name, arguments: JSON.stringify(block.input) },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
content,
|
|
114
|
+
role: 'assistant',
|
|
115
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
116
|
+
finishReason: result.stop_reason === 'tool_use' ? 'tool_calls' : 'stop',
|
|
117
|
+
meta: {
|
|
118
|
+
requestId: generateRequestId(),
|
|
119
|
+
providerUsed: 'anthropic',
|
|
120
|
+
modelUsed: result.model,
|
|
121
|
+
latencyMs: latency,
|
|
122
|
+
tokensInput: result.usage?.input_tokens || 0,
|
|
123
|
+
tokensOutput: result.usage?.output_tokens || 0,
|
|
124
|
+
tokensSaved: 0,
|
|
125
|
+
estimatedCost: `$${((result.usage?.input_tokens || 0) / 1000 * 0.003 + (result.usage?.output_tokens || 0) / 1000 * 0.015).toFixed(4)}`,
|
|
126
|
+
cacheHit: false,
|
|
127
|
+
guardrailsApplied: [],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
stream(request) {
|
|
132
|
+
const self = this;
|
|
133
|
+
return this.createStream(async function* () {
|
|
134
|
+
const client = await self.getClient();
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
const { system, messages } = self.formatMessages(request.messages);
|
|
137
|
+
const params = {
|
|
138
|
+
model: request.model,
|
|
139
|
+
messages,
|
|
140
|
+
max_tokens: request.maxTokens || 4096,
|
|
141
|
+
temperature: request.temperature,
|
|
142
|
+
top_p: request.topP,
|
|
143
|
+
stream: true,
|
|
144
|
+
};
|
|
145
|
+
if (system)
|
|
146
|
+
params.system = system;
|
|
147
|
+
const tools = self.formatToolsAnthropic(request.tools);
|
|
148
|
+
if (tools)
|
|
149
|
+
params.tools = tools;
|
|
150
|
+
const stream = client.messages.stream(params);
|
|
151
|
+
for await (const event of stream) {
|
|
152
|
+
if (event.type === 'content_block_delta') {
|
|
153
|
+
if (event.delta.type === 'text_delta') {
|
|
154
|
+
yield { type: 'text', content: event.delta.text };
|
|
155
|
+
}
|
|
156
|
+
else if (event.delta.type === 'input_json_delta') {
|
|
157
|
+
// Tool call argument streaming — buffered by Anthropic SDK
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
161
|
+
// Tool call started
|
|
162
|
+
}
|
|
163
|
+
if (event.type === 'message_stop') {
|
|
164
|
+
yield {
|
|
165
|
+
type: 'done',
|
|
166
|
+
meta: {
|
|
167
|
+
requestId: generateRequestId(),
|
|
168
|
+
providerUsed: 'anthropic',
|
|
169
|
+
modelUsed: request.model,
|
|
170
|
+
latencyMs: Date.now() - startTime,
|
|
171
|
+
tokensInput: 0,
|
|
172
|
+
tokensOutput: 0,
|
|
173
|
+
tokensSaved: 0,
|
|
174
|
+
estimatedCost: '$0.00',
|
|
175
|
+
cacheHit: false,
|
|
176
|
+
guardrailsApplied: [],
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
|
|
2
|
+
import type { NexusResponse, NexusStream, StreamChunk } from '../types/response.js';
|
|
3
|
+
export interface ProviderInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
isLocal: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare abstract class BaseProvider {
|
|
8
|
+
abstract readonly info: ProviderInfo;
|
|
9
|
+
abstract complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
10
|
+
abstract stream(request: CompletionRequest): NexusStream;
|
|
11
|
+
healthCheck(): Promise<boolean>;
|
|
12
|
+
protected createBaseResponse(provider: string, model: string): NexusResponse;
|
|
13
|
+
protected extractTextContent(messages: Message[]): Array<{
|
|
14
|
+
role: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}>;
|
|
17
|
+
protected formatTools(tools?: ToolDefinition[]): unknown[] | undefined;
|
|
18
|
+
protected createStream(generator: () => AsyncGenerator<StreamChunk>): NexusStream;
|
|
19
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { generateRequestId } from '../utils/ids.js';
|
|
2
|
+
export class BaseProvider {
|
|
3
|
+
async healthCheck() {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
createBaseResponse(provider, model) {
|
|
7
|
+
return {
|
|
8
|
+
content: '',
|
|
9
|
+
role: 'assistant',
|
|
10
|
+
finishReason: 'stop',
|
|
11
|
+
meta: {
|
|
12
|
+
requestId: generateRequestId(),
|
|
13
|
+
providerUsed: provider,
|
|
14
|
+
modelUsed: model,
|
|
15
|
+
latencyMs: 0,
|
|
16
|
+
tokensInput: 0,
|
|
17
|
+
tokensOutput: 0,
|
|
18
|
+
tokensSaved: 0,
|
|
19
|
+
estimatedCost: '$0.00',
|
|
20
|
+
cacheHit: false,
|
|
21
|
+
guardrailsApplied: [],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
extractTextContent(messages) {
|
|
26
|
+
return messages.map((msg) => ({
|
|
27
|
+
role: msg.role,
|
|
28
|
+
content: typeof msg.content === 'string'
|
|
29
|
+
? msg.content
|
|
30
|
+
: msg.content
|
|
31
|
+
.filter((p) => p.type === 'text')
|
|
32
|
+
.map((p) => p.text)
|
|
33
|
+
.join('\n'),
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
formatTools(tools) {
|
|
37
|
+
if (!tools || tools.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
return tools.map((t) => ({
|
|
40
|
+
type: 'function',
|
|
41
|
+
function: {
|
|
42
|
+
name: t.name,
|
|
43
|
+
description: t.description,
|
|
44
|
+
parameters: t.parameters,
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
createStream(generator) {
|
|
49
|
+
let aborted = false;
|
|
50
|
+
const stream = {
|
|
51
|
+
[Symbol.asyncIterator]() {
|
|
52
|
+
const gen = generator();
|
|
53
|
+
return {
|
|
54
|
+
async next() {
|
|
55
|
+
if (aborted)
|
|
56
|
+
return { done: true, value: undefined };
|
|
57
|
+
return gen.next();
|
|
58
|
+
},
|
|
59
|
+
async return() {
|
|
60
|
+
aborted = true;
|
|
61
|
+
return { done: true, value: undefined };
|
|
62
|
+
},
|
|
63
|
+
async throw(e) {
|
|
64
|
+
aborted = true;
|
|
65
|
+
return gen.throw(e);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
abort() {
|
|
70
|
+
aborted = true;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
return stream;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseProvider, type ProviderInfo } from './base.js';
|
|
2
|
+
import type { CohereProviderConfig } from '../types/config.js';
|
|
3
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
4
|
+
import type { NexusResponse, NexusStream } from '../types/response.js';
|
|
5
|
+
export declare class CohereProvider extends BaseProvider {
|
|
6
|
+
readonly info: ProviderInfo;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: CohereProviderConfig);
|
|
9
|
+
complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
10
|
+
stream(request: CompletionRequest): NexusStream;
|
|
11
|
+
healthCheck(): Promise<boolean>;
|
|
12
|
+
private baseUrl;
|
|
13
|
+
private stripPrefix;
|
|
14
|
+
private extractResponseText;
|
|
15
|
+
private estimateCost;
|
|
16
|
+
}
|