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,104 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { generateRequestId } from '../utils/ids.js';
3
+ import { KNOWN_MODELS } from '../types/providers.js';
4
+ export class CohereProvider extends BaseProvider {
5
+ info = { name: 'cohere', isLocal: false };
6
+ config;
7
+ constructor(config) {
8
+ super();
9
+ this.config = config;
10
+ }
11
+ async complete(request) {
12
+ const startTime = Date.now();
13
+ const model = this.stripPrefix(request.model);
14
+ const response = await fetch(`${this.baseUrl()}/v2/chat`, {
15
+ method: 'POST',
16
+ headers: {
17
+ Authorization: `Bearer ${this.config.apiKey}`,
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ body: JSON.stringify({
21
+ model,
22
+ messages: this.extractTextContent(request.messages).map((message) => ({
23
+ role: message.role === 'tool' ? 'user' : message.role,
24
+ content: message.content,
25
+ })),
26
+ temperature: request.temperature,
27
+ max_tokens: request.maxTokens,
28
+ p: request.topP,
29
+ }),
30
+ });
31
+ if (!response.ok) {
32
+ throw new Error(`Cohere request failed: ${response.status} ${await response.text()}`);
33
+ }
34
+ const result = await response.json();
35
+ const content = this.extractResponseText(result);
36
+ const usage = result.usage?.tokens || result.usage || {};
37
+ const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
38
+ const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
39
+ return {
40
+ content,
41
+ role: 'assistant',
42
+ finishReason: 'stop',
43
+ meta: {
44
+ requestId: generateRequestId(),
45
+ providerUsed: 'cohere',
46
+ modelUsed: result.model || model,
47
+ latencyMs: Date.now() - startTime,
48
+ tokensInput: inputTokens,
49
+ tokensOutput: outputTokens,
50
+ tokensSaved: 0,
51
+ estimatedCost: this.estimateCost(model, inputTokens, outputTokens),
52
+ cacheHit: false,
53
+ guardrailsApplied: [],
54
+ },
55
+ };
56
+ }
57
+ stream(request) {
58
+ const self = this;
59
+ return this.createStream(async function* () {
60
+ try {
61
+ const response = await self.complete(request);
62
+ if (response.content) {
63
+ yield { type: 'text', content: response.content };
64
+ }
65
+ yield { type: 'done', meta: response.meta };
66
+ }
67
+ catch (error) {
68
+ yield {
69
+ type: 'error',
70
+ error: error instanceof Error ? error.message : String(error),
71
+ };
72
+ }
73
+ });
74
+ }
75
+ async healthCheck() {
76
+ const response = await fetch(`${this.baseUrl()}/v2/models`, {
77
+ headers: { Authorization: `Bearer ${this.config.apiKey}` },
78
+ });
79
+ return response.ok;
80
+ }
81
+ baseUrl() {
82
+ return this.config.baseUrl || 'https://api.cohere.com';
83
+ }
84
+ stripPrefix(model) {
85
+ return model.startsWith('cohere/') ? model.slice('cohere/'.length) : model;
86
+ }
87
+ extractResponseText(result) {
88
+ const content = result.message?.content || result.content || [];
89
+ if (typeof content === 'string')
90
+ return content;
91
+ if (Array.isArray(content)) {
92
+ return content
93
+ .map((part) => typeof part === 'string' ? part : part.text || '')
94
+ .join('');
95
+ }
96
+ return result.text || '';
97
+ }
98
+ estimateCost(model, inputTokens, outputTokens) {
99
+ const caps = KNOWN_MODELS[model] || KNOWN_MODELS[`cohere/${model}`];
100
+ if (!caps)
101
+ return '$0.00';
102
+ return `$${(inputTokens / 1000 * caps.costPer1kInput + outputTokens / 1000 * caps.costPer1kOutput).toFixed(4)}`;
103
+ }
104
+ }
@@ -0,0 +1,22 @@
1
+ import { BaseProvider, type ProviderInfo } from './base.js';
2
+ import type { CompletionRequest } from '../types/messages.js';
3
+ import type { GoogleProviderConfig } from '../types/config.js';
4
+ import type { NexusResponse, NexusStream } from '../types/response.js';
5
+ export declare class GoogleProvider extends BaseProvider {
6
+ readonly info: ProviderInfo;
7
+ private config;
8
+ constructor(config: GoogleProviderConfig);
9
+ complete(request: CompletionRequest): Promise<NexusResponse>;
10
+ stream(request: CompletionRequest): NexusStream;
11
+ private generate;
12
+ private createBody;
13
+ private formatMessages;
14
+ private formatParts;
15
+ private formatToolsGoogle;
16
+ private extractText;
17
+ private extractToolCalls;
18
+ private mapFinishReason;
19
+ private extractModel;
20
+ private baseUrl;
21
+ private compact;
22
+ }
@@ -0,0 +1,226 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { generateRequestId } from '../utils/ids.js';
3
+ import { KNOWN_MODELS } from '../types/providers.js';
4
+ export class GoogleProvider extends BaseProvider {
5
+ info = { name: 'google', isLocal: false };
6
+ config;
7
+ constructor(config) {
8
+ super();
9
+ this.config = config;
10
+ }
11
+ async complete(request) {
12
+ const startTime = Date.now();
13
+ const model = this.extractModel(request.model);
14
+ const result = await this.generate(model, request, false);
15
+ const latency = Date.now() - startTime;
16
+ const candidate = result.candidates?.[0];
17
+ const content = this.extractText(candidate?.content);
18
+ const toolCalls = this.extractToolCalls(candidate?.content);
19
+ const usage = result.usageMetadata || {};
20
+ const inputTokens = usage.promptTokenCount || 0;
21
+ const outputTokens = usage.candidatesTokenCount || 0;
22
+ const caps = KNOWN_MODELS[model];
23
+ return {
24
+ content,
25
+ role: 'assistant',
26
+ toolCalls: toolCalls.length ? toolCalls : undefined,
27
+ finishReason: toolCalls.length ? 'tool_calls' : this.mapFinishReason(candidate?.finishReason),
28
+ meta: {
29
+ requestId: generateRequestId(),
30
+ providerUsed: 'google',
31
+ modelUsed: model,
32
+ latencyMs: latency,
33
+ tokensInput: inputTokens,
34
+ tokensOutput: outputTokens,
35
+ tokensSaved: 0,
36
+ estimatedCost: caps
37
+ ? `$${(inputTokens / 1000 * caps.costPer1kInput + outputTokens / 1000 * caps.costPer1kOutput).toFixed(4)}`
38
+ : '$0.00',
39
+ cacheHit: false,
40
+ guardrailsApplied: [],
41
+ },
42
+ };
43
+ }
44
+ stream(request) {
45
+ const self = this;
46
+ return this.createStream(async function* () {
47
+ const startTime = Date.now();
48
+ const model = self.extractModel(request.model);
49
+ const response = await self.generate(model, request, true);
50
+ const reader = response.body?.getReader();
51
+ if (!reader) {
52
+ yield { type: 'error', error: 'Google stream response did not include a readable body' };
53
+ return;
54
+ }
55
+ const decoder = new TextDecoder();
56
+ let buffer = '';
57
+ while (true) {
58
+ const { done, value } = await reader.read();
59
+ if (done)
60
+ break;
61
+ buffer += decoder.decode(value, { stream: true });
62
+ const events = buffer.split('\n\n');
63
+ buffer = events.pop() || '';
64
+ for (const event of events) {
65
+ const data = event
66
+ .split('\n')
67
+ .filter((line) => line.startsWith('data:'))
68
+ .map((line) => line.slice(5).trim())
69
+ .join('');
70
+ if (!data || data === '[DONE]')
71
+ continue;
72
+ const parsed = JSON.parse(data);
73
+ const text = self.extractText(parsed.candidates?.[0]?.content);
74
+ if (text)
75
+ yield { type: 'text', content: text };
76
+ }
77
+ }
78
+ yield {
79
+ type: 'done',
80
+ meta: {
81
+ requestId: generateRequestId(),
82
+ providerUsed: 'google',
83
+ modelUsed: model,
84
+ latencyMs: Date.now() - startTime,
85
+ tokensInput: 0,
86
+ tokensOutput: 0,
87
+ tokensSaved: 0,
88
+ estimatedCost: '$0.00',
89
+ cacheHit: false,
90
+ guardrailsApplied: [],
91
+ },
92
+ };
93
+ });
94
+ }
95
+ async generate(model, request, stream) {
96
+ const endpoint = stream ? 'streamGenerateContent' : 'generateContent';
97
+ const url = `${this.baseUrl()}/models/${encodeURIComponent(model)}:${endpoint}?key=${encodeURIComponent(this.config.apiKey)}${stream ? '&alt=sse' : ''}`;
98
+ const body = JSON.stringify(this.createBody(request));
99
+ const response = await fetch(url, {
100
+ method: 'POST',
101
+ headers: { 'content-type': 'application/json' },
102
+ body,
103
+ });
104
+ if (!response.ok) {
105
+ throw new Error(`Google Gemini request failed: ${response.status} ${await response.text()}`);
106
+ }
107
+ return stream ? response : response.json();
108
+ }
109
+ createBody(request) {
110
+ const { systemInstruction, contents } = this.formatMessages(request.messages);
111
+ const generationConfig = {
112
+ temperature: request.temperature,
113
+ topP: request.topP,
114
+ maxOutputTokens: request.maxTokens,
115
+ stopSequences: request.stop ? (Array.isArray(request.stop) ? request.stop : [request.stop]) : undefined,
116
+ };
117
+ if (request.responseFormat?.type === 'json') {
118
+ generationConfig.responseMimeType = 'application/json';
119
+ }
120
+ else if (request.responseFormat?.type === 'json_schema' && request.responseFormat.schema) {
121
+ generationConfig.responseMimeType = 'application/json';
122
+ generationConfig.responseSchema = request.responseFormat.schema;
123
+ }
124
+ return {
125
+ contents,
126
+ systemInstruction,
127
+ tools: this.formatToolsGoogle(request.tools),
128
+ generationConfig: this.compact(generationConfig),
129
+ };
130
+ }
131
+ formatMessages(messages) {
132
+ const systemParts = [];
133
+ const contents = [];
134
+ for (const message of messages) {
135
+ const parts = this.formatParts(message.content);
136
+ if (message.role === 'system') {
137
+ systemParts.push(...parts);
138
+ continue;
139
+ }
140
+ contents.push({
141
+ role: message.role === 'assistant' ? 'model' : 'user',
142
+ parts,
143
+ });
144
+ }
145
+ return {
146
+ systemInstruction: systemParts.length ? { parts: systemParts } : undefined,
147
+ contents,
148
+ };
149
+ }
150
+ formatParts(content) {
151
+ if (typeof content === 'string')
152
+ return [{ text: content }];
153
+ return content.map((part) => {
154
+ if (part.type === 'text')
155
+ return { text: part.text };
156
+ if (part.type === 'image') {
157
+ const source = part.source;
158
+ if ('base64' in source) {
159
+ return { inlineData: { mimeType: source.mimeType || 'image/png', data: source.base64 } };
160
+ }
161
+ if ('buffer' in source) {
162
+ return {
163
+ inlineData: {
164
+ mimeType: source.mimeType || 'image/png',
165
+ data: source.buffer.toString('base64'),
166
+ },
167
+ };
168
+ }
169
+ if ('url' in source)
170
+ return { text: `[image url: ${source.url}]` };
171
+ return { text: '[image from path requires preprocessing]' };
172
+ }
173
+ if (part.type === 'audio' && 'transcript' in part.source) {
174
+ return { text: part.source.transcript };
175
+ }
176
+ return { text: `[${part.type} content is not yet supported for Google provider]` };
177
+ });
178
+ }
179
+ formatToolsGoogle(tools) {
180
+ if (!tools?.length)
181
+ return undefined;
182
+ return [{
183
+ functionDeclarations: tools.map((tool) => ({
184
+ name: tool.name,
185
+ description: tool.description,
186
+ parameters: tool.parameters,
187
+ })),
188
+ }];
189
+ }
190
+ extractText(content) {
191
+ return (content?.parts || [])
192
+ .filter((part) => typeof part.text === 'string')
193
+ .map((part) => part.text)
194
+ .join('');
195
+ }
196
+ extractToolCalls(content) {
197
+ return (content?.parts || [])
198
+ .filter((part) => part.functionCall)
199
+ .map((part) => ({
200
+ id: generateRequestId(),
201
+ type: 'function',
202
+ function: {
203
+ name: part.functionCall.name,
204
+ arguments: JSON.stringify(part.functionCall.args || {}),
205
+ },
206
+ }));
207
+ }
208
+ mapFinishReason(reason) {
209
+ if (reason === 'MAX_TOKENS')
210
+ return 'length';
211
+ if (reason === 'SAFETY' || reason === 'RECITATION')
212
+ return 'content_filter';
213
+ return 'stop';
214
+ }
215
+ extractModel(model) {
216
+ if (model.startsWith('google/'))
217
+ return model.slice('google/'.length);
218
+ return model;
219
+ }
220
+ baseUrl() {
221
+ return (this.config.baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
222
+ }
223
+ compact(value) {
224
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
225
+ }
226
+ }
@@ -0,0 +1,15 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import type { GroqProviderConfig } 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 GroqProvider extends OpenAIProvider {
6
+ readonly info: {
7
+ name: string;
8
+ isLocal: boolean;
9
+ };
10
+ constructor(config: GroqProviderConfig);
11
+ complete(request: CompletionRequest): Promise<NexusResponse>;
12
+ stream(request: CompletionRequest): NexusStream;
13
+ private stripPrefix;
14
+ private estimateProviderCost;
15
+ }
@@ -0,0 +1,50 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import { KNOWN_MODELS } from '../types/providers.js';
3
+ export class GroqProvider extends OpenAIProvider {
4
+ info = { name: 'groq', isLocal: false };
5
+ constructor(config) {
6
+ super({
7
+ apiKey: config.apiKey,
8
+ baseUrl: config.baseUrl || 'https://api.groq.com/openai/v1',
9
+ });
10
+ }
11
+ async complete(request) {
12
+ const response = await super.complete({ ...request, model: this.stripPrefix(request.model) });
13
+ return {
14
+ ...response,
15
+ meta: {
16
+ ...response.meta,
17
+ providerUsed: 'groq',
18
+ modelUsed: response.meta.modelUsed || this.stripPrefix(request.model),
19
+ estimatedCost: this.estimateProviderCost(request.model, response.meta.tokensInput, response.meta.tokensOutput),
20
+ },
21
+ };
22
+ }
23
+ stream(request) {
24
+ const stream = super.stream({ ...request, model: this.stripPrefix(request.model) });
25
+ return this.createStream(async function* () {
26
+ for await (const chunk of stream) {
27
+ if (chunk.type === 'done') {
28
+ yield {
29
+ ...chunk,
30
+ meta: {
31
+ ...chunk.meta,
32
+ providerUsed: 'groq',
33
+ },
34
+ };
35
+ continue;
36
+ }
37
+ yield chunk;
38
+ }
39
+ });
40
+ }
41
+ stripPrefix(model) {
42
+ return model.startsWith('groq/') ? model.slice('groq/'.length) : model;
43
+ }
44
+ estimateProviderCost(model, inputTokens, outputTokens) {
45
+ const caps = KNOWN_MODELS[model];
46
+ if (!caps)
47
+ return '$0.00';
48
+ return `$${(inputTokens / 1000 * caps.costPer1kInput + outputTokens / 1000 * caps.costPer1kOutput).toFixed(4)}`;
49
+ }
50
+ }
@@ -0,0 +1,15 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import type { MistralProviderConfig } 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 MistralProvider extends OpenAIProvider {
6
+ readonly info: {
7
+ name: string;
8
+ isLocal: boolean;
9
+ };
10
+ constructor(config: MistralProviderConfig);
11
+ complete(request: CompletionRequest): Promise<NexusResponse>;
12
+ stream(request: CompletionRequest): NexusStream;
13
+ private stripPrefix;
14
+ private estimateProviderCost;
15
+ }
@@ -0,0 +1,50 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import { KNOWN_MODELS } from '../types/providers.js';
3
+ export class MistralProvider extends OpenAIProvider {
4
+ info = { name: 'mistral', isLocal: false };
5
+ constructor(config) {
6
+ super({
7
+ apiKey: config.apiKey,
8
+ baseUrl: config.baseUrl || 'https://api.mistral.ai/v1',
9
+ });
10
+ }
11
+ async complete(request) {
12
+ const response = await super.complete({ ...request, model: this.stripPrefix(request.model) });
13
+ return {
14
+ ...response,
15
+ meta: {
16
+ ...response.meta,
17
+ providerUsed: 'mistral',
18
+ modelUsed: response.meta.modelUsed || this.stripPrefix(request.model),
19
+ estimatedCost: this.estimateProviderCost(request.model, response.meta.tokensInput, response.meta.tokensOutput),
20
+ },
21
+ };
22
+ }
23
+ stream(request) {
24
+ const stream = super.stream({ ...request, model: this.stripPrefix(request.model) });
25
+ return this.createStream(async function* () {
26
+ for await (const chunk of stream) {
27
+ if (chunk.type === 'done') {
28
+ yield {
29
+ ...chunk,
30
+ meta: {
31
+ ...chunk.meta,
32
+ providerUsed: 'mistral',
33
+ },
34
+ };
35
+ continue;
36
+ }
37
+ yield chunk;
38
+ }
39
+ });
40
+ }
41
+ stripPrefix(model) {
42
+ return model.startsWith('mistral/') ? model.slice('mistral/'.length) : model;
43
+ }
44
+ estimateProviderCost(model, inputTokens, outputTokens) {
45
+ const caps = KNOWN_MODELS[model];
46
+ if (!caps)
47
+ return '$0.00';
48
+ return `$${(inputTokens / 1000 * caps.costPer1kInput + outputTokens / 1000 * caps.costPer1kOutput).toFixed(4)}`;
49
+ }
50
+ }
@@ -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 { OllamaProviderConfig } from '../types/config.js';
5
+ export declare class OllamaProvider extends BaseProvider {
6
+ readonly info: ProviderInfo;
7
+ private client;
8
+ private config;
9
+ constructor(config: OllamaProviderConfig);
10
+ private getClient;
11
+ private formatMessages;
12
+ private extractModel;
13
+ complete(request: CompletionRequest): Promise<NexusResponse>;
14
+ stream(request: CompletionRequest): NexusStream;
15
+ }
@@ -0,0 +1,108 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { generateRequestId } from '../utils/ids.js';
3
+ export class OllamaProvider extends BaseProvider {
4
+ info = { name: 'ollama', isLocal: true };
5
+ client;
6
+ config;
7
+ constructor(config) {
8
+ super();
9
+ this.config = config;
10
+ }
11
+ async getClient() {
12
+ if (!this.client) {
13
+ const { Ollama } = await import('ollama');
14
+ this.client = new Ollama({ host: this.config.baseUrl || 'http://localhost:11434' });
15
+ }
16
+ return this.client;
17
+ }
18
+ formatMessages(messages) {
19
+ return messages.map((msg) => ({
20
+ role: msg.role,
21
+ content: typeof msg.content === 'string'
22
+ ? msg.content
23
+ : msg.content
24
+ .filter((p) => p.type === 'text')
25
+ .map((p) => p.text)
26
+ .join('\n'),
27
+ }));
28
+ }
29
+ extractModel(model) {
30
+ // Support "ollama/llama3.2" syntax — strip prefix
31
+ if (model.startsWith('ollama/'))
32
+ return model.slice(7);
33
+ return model;
34
+ }
35
+ async complete(request) {
36
+ const client = await this.getClient();
37
+ const startTime = Date.now();
38
+ const model = this.extractModel(request.model);
39
+ const result = await client.chat({
40
+ model,
41
+ messages: this.formatMessages(request.messages),
42
+ format: request.responseFormat?.type === 'json' ? 'json' : request.responseFormat?.schema,
43
+ options: {
44
+ temperature: request.temperature,
45
+ top_p: request.topP,
46
+ stop: request.stop ? (Array.isArray(request.stop) ? request.stop : [request.stop]) : undefined,
47
+ },
48
+ });
49
+ const latency = Date.now() - startTime;
50
+ return {
51
+ content: result.message?.content || '',
52
+ role: 'assistant',
53
+ finishReason: 'stop',
54
+ meta: {
55
+ requestId: generateRequestId(),
56
+ providerUsed: 'ollama',
57
+ modelUsed: model,
58
+ latencyMs: latency,
59
+ tokensInput: result.prompt_eval_count || 0,
60
+ tokensOutput: result.eval_count || 0,
61
+ tokensSaved: 0,
62
+ estimatedCost: '$0.00',
63
+ cacheHit: false,
64
+ guardrailsApplied: [],
65
+ },
66
+ };
67
+ }
68
+ stream(request) {
69
+ const self = this;
70
+ return this.createStream(async function* () {
71
+ const client = await self.getClient();
72
+ const startTime = Date.now();
73
+ const model = self.extractModel(request.model);
74
+ const stream = await client.chat({
75
+ model,
76
+ messages: self.formatMessages(request.messages),
77
+ stream: true,
78
+ format: request.responseFormat?.type === 'json' ? 'json' : request.responseFormat?.schema,
79
+ options: {
80
+ temperature: request.temperature,
81
+ top_p: request.topP,
82
+ },
83
+ });
84
+ for await (const chunk of stream) {
85
+ if (chunk.message?.content) {
86
+ yield { type: 'text', content: chunk.message.content };
87
+ }
88
+ if (chunk.done) {
89
+ yield {
90
+ type: 'done',
91
+ meta: {
92
+ requestId: generateRequestId(),
93
+ providerUsed: 'ollama',
94
+ modelUsed: model,
95
+ latencyMs: Date.now() - startTime,
96
+ tokensInput: chunk.prompt_eval_count || 0,
97
+ tokensOutput: chunk.eval_count || 0,
98
+ tokensSaved: 0,
99
+ estimatedCost: '$0.00',
100
+ cacheHit: false,
101
+ guardrailsApplied: [],
102
+ },
103
+ };
104
+ }
105
+ }
106
+ });
107
+ }
108
+ }
@@ -0,0 +1,21 @@
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 { OpenAIProviderConfig } from '../types/config.js';
5
+ export declare class OpenAIProvider extends BaseProvider {
6
+ readonly info: ProviderInfo;
7
+ private client;
8
+ private config;
9
+ constructor(config: OpenAIProviderConfig);
10
+ private getClient;
11
+ private formatMessages;
12
+ complete(request: CompletionRequest): Promise<NexusResponse>;
13
+ stream(request: CompletionRequest): NexusStream;
14
+ private mapFinishReason;
15
+ private completeResponses;
16
+ private requiresResponsesApi;
17
+ private formatResponsesInput;
18
+ private formatResponsesTools;
19
+ private extractResponsesText;
20
+ private estimateCost;
21
+ }