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,304 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { generateRequestId } from '../utils/ids.js';
3
+ import { KNOWN_MODELS } from '../types/providers.js';
4
+ export class OpenAIProvider extends BaseProvider {
5
+ info = { name: 'openai', isLocal: false };
6
+ client;
7
+ config;
8
+ constructor(config) {
9
+ super();
10
+ this.config = config;
11
+ }
12
+ async getClient() {
13
+ if (!this.client) {
14
+ const { default: OpenAI } = await import('openai');
15
+ this.client = new OpenAI({
16
+ apiKey: this.config.apiKey,
17
+ baseURL: this.config.baseUrl,
18
+ organization: this.config.organization,
19
+ });
20
+ }
21
+ return this.client;
22
+ }
23
+ formatMessages(messages) {
24
+ return messages.map((msg) => {
25
+ if (typeof msg.content === 'string') {
26
+ return { role: msg.role, content: msg.content };
27
+ }
28
+ const parts = msg.content.map((part) => {
29
+ switch (part.type) {
30
+ case 'text':
31
+ return { type: 'text', text: part.text };
32
+ case 'image': {
33
+ const src = part.source;
34
+ if ('url' in src) {
35
+ return { type: 'image_url', image_url: { url: src.url } };
36
+ }
37
+ if ('base64' in src) {
38
+ const mime = src.mimeType || 'image/png';
39
+ return { type: 'image_url', image_url: { url: `data:${mime};base64,${src.base64}` } };
40
+ }
41
+ if ('buffer' in src) {
42
+ const mime = src.mimeType || 'image/png';
43
+ const b64 = src.buffer.toString('base64');
44
+ return { type: 'image_url', image_url: { url: `data:${mime};base64,${b64}` } };
45
+ }
46
+ return { type: 'text', text: '[image from path — requires preprocessing]' };
47
+ }
48
+ default:
49
+ return { type: 'text', text: `[${part.type} content — not yet supported for this provider]` };
50
+ }
51
+ });
52
+ return { role: msg.role, content: parts };
53
+ });
54
+ }
55
+ async complete(request) {
56
+ if (this.requiresResponsesApi(request.model)) {
57
+ return this.completeResponses(request);
58
+ }
59
+ const client = await this.getClient();
60
+ const startTime = Date.now();
61
+ const params = {
62
+ model: request.model,
63
+ messages: this.formatMessages(request.messages),
64
+ temperature: request.temperature,
65
+ max_tokens: request.maxTokens,
66
+ top_p: request.topP,
67
+ logit_bias: request.logitBias,
68
+ stop: request.stop,
69
+ };
70
+ if (request.responseFormat?.type === 'json') {
71
+ params.response_format = { type: 'json_object' };
72
+ }
73
+ else if (request.responseFormat?.type === 'json_schema' && request.responseFormat.schema) {
74
+ params.response_format = {
75
+ type: 'json_schema',
76
+ json_schema: {
77
+ name: 'nexus_response',
78
+ schema: request.responseFormat.schema,
79
+ strict: true,
80
+ },
81
+ };
82
+ }
83
+ const tools = this.formatTools(request.tools);
84
+ if (tools)
85
+ params.tools = tools;
86
+ const result = await client.chat.completions.create(params);
87
+ const choice = result.choices[0];
88
+ const latency = Date.now() - startTime;
89
+ const toolCalls = choice.message.tool_calls?.map((tc) => ({
90
+ id: tc.id,
91
+ type: 'function',
92
+ function: { name: tc.function.name, arguments: tc.function.arguments },
93
+ }));
94
+ const inputCost = (result.usage?.prompt_tokens || 0) / 1000;
95
+ const outputCost = (result.usage?.completion_tokens || 0) / 1000;
96
+ return {
97
+ content: choice.message.content || '',
98
+ role: 'assistant',
99
+ toolCalls,
100
+ finishReason: this.mapFinishReason(choice.finish_reason),
101
+ meta: {
102
+ requestId: generateRequestId(),
103
+ providerUsed: 'openai',
104
+ modelUsed: result.model,
105
+ latencyMs: latency,
106
+ tokensInput: result.usage?.prompt_tokens || 0,
107
+ tokensOutput: result.usage?.completion_tokens || 0,
108
+ tokensSaved: 0,
109
+ estimatedCost: `$${(inputCost * 0.0025 + outputCost * 0.01).toFixed(4)}`,
110
+ cacheHit: false,
111
+ guardrailsApplied: [],
112
+ },
113
+ };
114
+ }
115
+ stream(request) {
116
+ const self = this;
117
+ return this.createStream(async function* () {
118
+ if (self.requiresResponsesApi(request.model)) {
119
+ yield {
120
+ type: 'error',
121
+ error: `Model "${request.model}" requires the OpenAI Responses API. Streaming for Responses-only models is not implemented yet.`,
122
+ };
123
+ return;
124
+ }
125
+ const client = await self.getClient();
126
+ const startTime = Date.now();
127
+ const params = {
128
+ model: request.model,
129
+ messages: self.formatMessages(request.messages),
130
+ temperature: request.temperature,
131
+ max_tokens: request.maxTokens,
132
+ top_p: request.topP,
133
+ logit_bias: request.logitBias,
134
+ stop: request.stop,
135
+ stream: true,
136
+ };
137
+ if (request.responseFormat?.type === 'json') {
138
+ params.response_format = { type: 'json_object' };
139
+ }
140
+ else if (request.responseFormat?.type === 'json_schema' && request.responseFormat.schema) {
141
+ params.response_format = {
142
+ type: 'json_schema',
143
+ json_schema: {
144
+ name: 'nexus_response',
145
+ schema: request.responseFormat.schema,
146
+ strict: true,
147
+ },
148
+ };
149
+ }
150
+ const tools = self.formatTools(request.tools);
151
+ if (tools)
152
+ params.tools = tools;
153
+ const stream = await client.chat.completions.create(params);
154
+ let fullContent = '';
155
+ const toolCallBuffers = new Map();
156
+ for await (const chunk of stream) {
157
+ const delta = chunk.choices?.[0]?.delta;
158
+ if (!delta)
159
+ continue;
160
+ if (delta.content) {
161
+ fullContent += delta.content;
162
+ yield { type: 'text', content: delta.content };
163
+ }
164
+ if (delta.tool_calls) {
165
+ for (const tc of delta.tool_calls) {
166
+ if (!toolCallBuffers.has(tc.index)) {
167
+ toolCallBuffers.set(tc.index, { id: tc.id || '', name: '', args: '' });
168
+ }
169
+ const buf = toolCallBuffers.get(tc.index);
170
+ if (tc.id)
171
+ buf.id = tc.id;
172
+ if (tc.function?.name)
173
+ buf.name += tc.function.name;
174
+ if (tc.function?.arguments)
175
+ buf.args += tc.function.arguments;
176
+ }
177
+ }
178
+ if (chunk.choices?.[0]?.finish_reason) {
179
+ for (const [, buf] of toolCallBuffers) {
180
+ yield {
181
+ type: 'tool_call',
182
+ toolCall: {
183
+ id: buf.id,
184
+ type: 'function',
185
+ function: { name: buf.name, arguments: buf.args },
186
+ },
187
+ };
188
+ }
189
+ yield {
190
+ type: 'done',
191
+ meta: {
192
+ requestId: generateRequestId(),
193
+ providerUsed: 'openai',
194
+ modelUsed: request.model,
195
+ latencyMs: Date.now() - startTime,
196
+ tokensInput: 0,
197
+ tokensOutput: 0,
198
+ tokensSaved: 0,
199
+ estimatedCost: '$0.00',
200
+ cacheHit: false,
201
+ guardrailsApplied: [],
202
+ },
203
+ };
204
+ }
205
+ }
206
+ });
207
+ }
208
+ mapFinishReason(reason) {
209
+ switch (reason) {
210
+ case 'stop': return 'stop';
211
+ case 'tool_calls': return 'tool_calls';
212
+ case 'length': return 'length';
213
+ case 'content_filter': return 'content_filter';
214
+ default: return 'stop';
215
+ }
216
+ }
217
+ async completeResponses(request) {
218
+ const client = await this.getClient();
219
+ if (!client.responses?.create) {
220
+ throw new Error(`Model "${request.model}" requires the OpenAI Responses API, but the installed openai SDK does not expose client.responses.create. Upgrade the openai package.`);
221
+ }
222
+ const startTime = Date.now();
223
+ const params = {
224
+ model: request.model,
225
+ input: this.formatResponsesInput(request.messages),
226
+ temperature: request.temperature,
227
+ max_output_tokens: request.maxTokens,
228
+ top_p: request.topP,
229
+ store: false,
230
+ };
231
+ const tools = this.formatResponsesTools(request.tools);
232
+ if (tools)
233
+ params.tools = tools;
234
+ if (request.responseFormat?.type === 'json') {
235
+ params.text = { format: { type: 'json_object' } };
236
+ }
237
+ else if (request.responseFormat?.type === 'json_schema' && request.responseFormat.schema) {
238
+ params.text = {
239
+ format: {
240
+ type: 'json_schema',
241
+ name: 'nexus_response',
242
+ schema: request.responseFormat.schema,
243
+ strict: true,
244
+ },
245
+ };
246
+ }
247
+ const result = await client.responses.create(params);
248
+ const latency = Date.now() - startTime;
249
+ const content = result.output_text || this.extractResponsesText(result);
250
+ const usage = result.usage || {};
251
+ const inputTokens = usage.input_tokens || 0;
252
+ const outputTokens = usage.output_tokens || 0;
253
+ return {
254
+ content,
255
+ role: 'assistant',
256
+ finishReason: result.status === 'incomplete' ? 'length' : 'stop',
257
+ meta: {
258
+ requestId: generateRequestId(),
259
+ providerUsed: 'openai',
260
+ modelUsed: result.model || request.model,
261
+ latencyMs: latency,
262
+ tokensInput: inputTokens,
263
+ tokensOutput: outputTokens,
264
+ tokensSaved: 0,
265
+ estimatedCost: this.estimateCost(request.model, inputTokens, outputTokens),
266
+ cacheHit: false,
267
+ guardrailsApplied: [],
268
+ },
269
+ };
270
+ }
271
+ requiresResponsesApi(model) {
272
+ const endpoints = KNOWN_MODELS[model]?.endpoints;
273
+ return Boolean(endpoints?.includes('responses') && !endpoints.includes('chat'));
274
+ }
275
+ formatResponsesInput(messages) {
276
+ return this.extractTextContent(messages).map((message) => ({
277
+ role: message.role === 'tool' ? 'user' : message.role,
278
+ content: message.content,
279
+ }));
280
+ }
281
+ formatResponsesTools(tools) {
282
+ if (!tools?.length)
283
+ return undefined;
284
+ return tools.map((tool) => ({
285
+ type: 'function',
286
+ name: tool.name,
287
+ description: tool.description,
288
+ parameters: tool.parameters,
289
+ }));
290
+ }
291
+ extractResponsesText(result) {
292
+ return (result.output || [])
293
+ .flatMap((item) => item.content || [])
294
+ .filter((part) => part.type === 'output_text' || part.type === 'text')
295
+ .map((part) => part.text || '')
296
+ .join('');
297
+ }
298
+ estimateCost(model, inputTokens, outputTokens) {
299
+ const caps = KNOWN_MODELS[model];
300
+ if (!caps)
301
+ return '$0.00';
302
+ return `$${(inputTokens / 1000 * caps.costPer1kInput + outputTokens / 1000 * caps.costPer1kOutput).toFixed(4)}`;
303
+ }
304
+ }
@@ -0,0 +1,14 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import type { OpenRouterProviderConfig } 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 OpenRouterProvider extends OpenAIProvider {
6
+ readonly info: {
7
+ name: string;
8
+ isLocal: boolean;
9
+ };
10
+ constructor(config: OpenRouterProviderConfig);
11
+ complete(request: CompletionRequest): Promise<NexusResponse>;
12
+ stream(request: CompletionRequest): NexusStream;
13
+ private stripPrefix;
14
+ }
@@ -0,0 +1,19 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ export class OpenRouterProvider extends OpenAIProvider {
3
+ info = { name: 'openrouter', isLocal: false };
4
+ constructor(config) {
5
+ super({
6
+ apiKey: config.apiKey,
7
+ baseUrl: config.baseUrl || 'https://openrouter.ai/api/v1',
8
+ });
9
+ }
10
+ async complete(request) {
11
+ return super.complete({ ...request, model: this.stripPrefix(request.model) });
12
+ }
13
+ stream(request) {
14
+ return super.stream({ ...request, model: this.stripPrefix(request.model) });
15
+ }
16
+ stripPrefix(model) {
17
+ return model.startsWith('openrouter/') ? model.slice('openrouter/'.length) : model;
18
+ }
19
+ }
@@ -0,0 +1,20 @@
1
+ import { type FileUpload, type UploadScannerOptions } from '../security/upload-scanner.js';
2
+ import { type IngestionOptions, type IngestionResult } from './ingestion.js';
3
+ export interface FileTextExtractor {
4
+ supports(file: FileUpload): boolean;
5
+ extract(file: FileUpload): Promise<string> | string;
6
+ }
7
+ export interface FileIngestionOptions extends IngestionOptions {
8
+ scan?: UploadScannerOptions;
9
+ extractors?: FileTextExtractor[];
10
+ }
11
+ export interface FileIngestionResult extends IngestionResult {
12
+ scannedFiles: number;
13
+ skippedFiles: Array<{
14
+ name: string;
15
+ reason: string;
16
+ }>;
17
+ }
18
+ export declare function ingestFilesAfterScan(files: FileUpload[], options?: FileIngestionOptions): Promise<FileIngestionResult>;
19
+ export declare function createPdfExtractor(extractPdfText: (file: FileUpload) => Promise<string> | string): FileTextExtractor;
20
+ export declare function createOcrExtractor(ocrImage: (file: FileUpload) => Promise<string> | string, mimeTypes?: string[]): FileTextExtractor;
@@ -0,0 +1,55 @@
1
+ import { scanUploads } from '../security/upload-scanner.js';
2
+ import { ingestDocuments } from './ingestion.js';
3
+ export async function ingestFilesAfterScan(files, options = {}) {
4
+ const scan = scanUploads(files, options.scan);
5
+ if (!scan.ok) {
6
+ throw new Error(`Upload scan failed: ${scan.findings.map((finding) => `${finding.fileName}: ${finding.message}`).join('; ')}`);
7
+ }
8
+ const documents = [];
9
+ const skippedFiles = [];
10
+ for (const file of files) {
11
+ const extractor = options.extractors?.find((item) => item.supports(file));
12
+ if (extractor) {
13
+ documents.push({
14
+ id: file.name,
15
+ source: file.name,
16
+ text: await extractor.extract(file),
17
+ metadata: { mimeType: file.mimeType, sizeBytes: file.sizeBytes },
18
+ });
19
+ continue;
20
+ }
21
+ if (typeof file.content === 'string' && isTextMime(file.mimeType)) {
22
+ documents.push({
23
+ id: file.name,
24
+ source: file.name,
25
+ text: file.content,
26
+ metadata: { mimeType: file.mimeType, sizeBytes: file.sizeBytes },
27
+ });
28
+ continue;
29
+ }
30
+ skippedFiles.push({ name: file.name, reason: 'No extractor available for file type' });
31
+ }
32
+ const result = ingestDocuments(documents, options);
33
+ return {
34
+ ...result,
35
+ scannedFiles: files.length,
36
+ skippedFiles,
37
+ };
38
+ }
39
+ export function createPdfExtractor(extractPdfText) {
40
+ return {
41
+ supports: (file) => file.mimeType === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'),
42
+ extract: extractPdfText,
43
+ };
44
+ }
45
+ export function createOcrExtractor(ocrImage, mimeTypes = ['image/png', 'image/jpeg', 'image/webp']) {
46
+ return {
47
+ supports: (file) => Boolean(file.mimeType && mimeTypes.includes(file.mimeType)),
48
+ extract: ocrImage,
49
+ };
50
+ }
51
+ function isTextMime(mimeType) {
52
+ if (!mimeType)
53
+ return true;
54
+ return mimeType.startsWith('text/') || ['application/json', 'application/xml', 'application/markdown'].includes(mimeType);
55
+ }
@@ -0,0 +1,21 @@
1
+ import type { RagChunk } from '../hallucination/rag.js';
2
+ export interface DocumentSource {
3
+ id?: string;
4
+ text: string;
5
+ source?: string;
6
+ metadata?: Record<string, unknown>;
7
+ }
8
+ export interface IngestionOptions {
9
+ chunkSize?: number;
10
+ overlap?: number;
11
+ splitOnMarkdownHeadings?: boolean;
12
+ }
13
+ export interface IngestionResult {
14
+ chunks: RagChunk[];
15
+ documents: number;
16
+ totalCharacters: number;
17
+ }
18
+ export declare function ingestDocuments(documents: DocumentSource[], options?: IngestionOptions): IngestionResult;
19
+ export declare function ingestText(text: string, options?: IngestionOptions & {
20
+ source?: string;
21
+ }): IngestionResult;
@@ -0,0 +1,47 @@
1
+ export function ingestDocuments(documents, options = {}) {
2
+ const chunks = [];
3
+ const chunkSize = options.chunkSize || 1200;
4
+ const overlap = Math.min(options.overlap || 150, Math.max(0, chunkSize - 1));
5
+ for (const [docIndex, doc] of documents.entries()) {
6
+ const sections = options.splitOnMarkdownHeadings
7
+ ? splitMarkdownSections(doc.text)
8
+ : [doc.text];
9
+ for (const [sectionIndex, section] of sections.entries()) {
10
+ let start = 0;
11
+ let chunkIndex = 0;
12
+ while (start < section.length) {
13
+ const end = Math.min(section.length, start + chunkSize);
14
+ const content = section.slice(start, end).trim();
15
+ if (content) {
16
+ chunks.push({
17
+ id: `${doc.id || `doc-${docIndex + 1}`}-${sectionIndex + 1}-${chunkIndex + 1}`,
18
+ content,
19
+ source: doc.source,
20
+ metadata: {
21
+ ...doc.metadata,
22
+ documentId: doc.id,
23
+ sectionIndex,
24
+ chunkIndex,
25
+ },
26
+ });
27
+ }
28
+ if (end >= section.length)
29
+ break;
30
+ start = Math.max(0, end - overlap);
31
+ chunkIndex += 1;
32
+ }
33
+ }
34
+ }
35
+ return {
36
+ chunks,
37
+ documents: documents.length,
38
+ totalCharacters: documents.reduce((sum, doc) => sum + doc.text.length, 0),
39
+ };
40
+ }
41
+ export function ingestText(text, options = {}) {
42
+ return ingestDocuments([{ text, source: options.source }], options);
43
+ }
44
+ function splitMarkdownSections(text) {
45
+ const sections = text.split(/(?=^#{1,6}\s+)/m).map((section) => section.trim()).filter(Boolean);
46
+ return sections.length ? sections : [text];
47
+ }
@@ -0,0 +1,17 @@
1
+ import type { RouterContext, RouteDecision } from './types.js';
2
+ export declare class AutoRouter {
3
+ route(ctx: RouterContext): RouteDecision;
4
+ private getCandidates;
5
+ private score;
6
+ private localFallbackCapabilities;
7
+ private costScore;
8
+ private speedScore;
9
+ private qualityScore;
10
+ private preferencesForProvider;
11
+ private isAllowed;
12
+ private matchesRequirements;
13
+ private matchesModelPattern;
14
+ private isProviderRunnable;
15
+ private healthScore;
16
+ }
17
+ export declare function routeDirectModel(model: string, ctx: RouterContext): RouteDecision;