nexus-ai-pro 2.20.2 → 2.22.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 (233) hide show
  1. package/API_STABILITY.md +93 -0
  2. package/CHANGELOG.md +208 -1
  3. package/README.md +11 -6
  4. package/SECURITY.md +16 -2
  5. package/dist/adapter-kit/gather.d.ts +76 -0
  6. package/dist/adapter-kit/gather.js +147 -0
  7. package/dist/adapter-kit/index.d.ts +1 -0
  8. package/dist/adapter-kit/index.js +1 -0
  9. package/dist/adapter-kit/record.d.ts +3 -1
  10. package/dist/adapter-kit/record.js +20 -5
  11. package/dist/agent/openapi.d.ts +94 -8
  12. package/dist/agent/openapi.js +528 -32
  13. package/dist/cli/adapters.d.ts +4 -3
  14. package/dist/cli/adapters.js +71 -2
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/core/config-builder.d.ts +1 -0
  18. package/dist/core/config-builder.js +1 -0
  19. package/dist/core/defaults.d.ts +34 -0
  20. package/dist/core/defaults.js +39 -0
  21. package/dist/core/secure-stream.js +4 -1
  22. package/dist/decisions/completion.d.ts +10 -7
  23. package/dist/decisions/completion.js +82 -8
  24. package/dist/decisions/core.d.ts +88 -43
  25. package/dist/decisions/core.js +115 -28
  26. package/dist/decisions/errors.d.ts +25 -0
  27. package/dist/decisions/errors.js +12 -0
  28. package/dist/decisions/index.d.ts +6 -5
  29. package/dist/decisions/index.js +2 -1
  30. package/dist/decisions/integrations.d.ts +14 -5
  31. package/dist/decisions/integrations.js +16 -1
  32. package/dist/decisions/mock.d.ts +5 -3
  33. package/dist/decisions/mock.js +4 -1
  34. package/dist/decisions/pictures.d.ts +43 -0
  35. package/dist/decisions/pictures.js +103 -0
  36. package/dist/decisions/typesafe.d.ts +33 -5
  37. package/dist/decisions/typesafe.js +121 -8
  38. package/dist/doctor/index.d.ts +6 -9
  39. package/dist/doctor/index.js +41 -0
  40. package/dist/embeddings/adapters.d.ts +1 -1
  41. package/dist/embeddings/adapters.js +3 -3
  42. package/dist/embeddings/batch-google.d.ts +27 -8
  43. package/dist/embeddings/batch-google.js +112 -36
  44. package/dist/embeddings/batch-mock.js +1 -1
  45. package/dist/embeddings/batch.js +10 -4
  46. package/dist/embeddings/engine.js +14 -13
  47. package/dist/embeddings/index.d.ts +1 -1
  48. package/dist/embeddings/inputs.d.ts +26 -3
  49. package/dist/embeddings/inputs.js +84 -5
  50. package/dist/embeddings/mock.js +1 -1
  51. package/dist/embeddings/models.js +39 -4
  52. package/dist/loaders/csv.d.ts +23 -1
  53. package/dist/loaders/csv.js +125 -72
  54. package/dist/loaders/index.d.ts +7 -2
  55. package/dist/loaders/input.d.ts +6 -0
  56. package/dist/loaders/input.js +42 -1
  57. package/dist/loaders/json.d.ts +14 -3
  58. package/dist/loaders/json.js +171 -20
  59. package/dist/loaders/web.d.ts +55 -0
  60. package/dist/loaders/web.js +20 -1
  61. package/dist/mcp/client.d.ts +54 -5
  62. package/dist/mcp/client.js +171 -20
  63. package/dist/mcp/http.d.ts +45 -0
  64. package/dist/mcp/http.js +233 -0
  65. package/dist/mcp/index.d.ts +4 -2
  66. package/dist/mcp/index.js +3 -1
  67. package/dist/mcp/protocol.d.ts +107 -0
  68. package/dist/mcp/sampling.d.ts +41 -0
  69. package/dist/mcp/sampling.js +52 -0
  70. package/dist/mcp/server.d.ts +96 -14
  71. package/dist/mcp/server.js +274 -59
  72. package/dist/models/data.js +20 -1
  73. package/dist/operations/adapters.d.ts +1 -0
  74. package/dist/operations/adapters.js +3 -1
  75. package/dist/protocols/a2a-grpc.js +10 -3
  76. package/dist/protocols/a2a-signing.d.ts +65 -0
  77. package/dist/protocols/a2a-signing.js +133 -0
  78. package/dist/protocols/a2a.d.ts +14 -0
  79. package/dist/protocols/a2a.js +60 -5
  80. package/dist/providers/cohere.js +16 -2
  81. package/dist/providers/google.js +7 -1
  82. package/dist/providers/ollama.d.ts +1 -1
  83. package/dist/providers/ollama.js +35 -27
  84. package/dist/providers/openai.js +19 -2
  85. package/dist/rag/pinecone.d.ts +63 -2
  86. package/dist/rag/pinecone.js +80 -0
  87. package/dist/realtime/conversation.js +18 -0
  88. package/dist/realtime/gemini-live.transport.d.ts +28 -3
  89. package/dist/realtime/gemini-live.transport.js +170 -32
  90. package/dist/realtime/session.d.ts +2 -0
  91. package/dist/realtime/session.js +19 -4
  92. package/dist/realtime/types.d.ts +35 -3
  93. package/dist/security/output-guard.js +4 -1
  94. package/dist/server/auth.d.ts +263 -2
  95. package/dist/server/auth.js +432 -5
  96. package/dist/server/deployments.d.ts +3 -2
  97. package/dist/server/deployments.js +6 -1
  98. package/dist/telephony/index.d.ts +1 -1
  99. package/dist/telephony/manager.d.ts +8 -1
  100. package/dist/telephony/manager.js +13 -0
  101. package/dist/telephony/providers/telnyx.d.ts +18 -5
  102. package/dist/telephony/providers/telnyx.js +44 -7
  103. package/dist/telephony/providers/twilio.d.ts +3 -1
  104. package/dist/telephony/providers/twilio.js +16 -0
  105. package/dist/tracing/stores.d.ts +2 -0
  106. package/dist/tracing/stores.js +1 -0
  107. package/dist/tracing/tracer.d.ts +5 -2
  108. package/dist/tracing/tracer.js +6 -3
  109. package/dist/types/embeddings.d.ts +47 -5
  110. package/dist/types/messages.d.ts +7 -0
  111. package/dist/types/principal.d.ts +1 -1
  112. package/dist/types/response.d.ts +5 -0
  113. package/dist/types/telephony.d.ts +38 -0
  114. package/dist/types/tracing.d.ts +6 -0
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/voice/providers/google.d.ts +44 -0
  118. package/dist/voice/providers/google.js +215 -0
  119. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  120. package/dist-cjs/adapter-kit/gather.js +152 -0
  121. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  122. package/dist-cjs/adapter-kit/index.js +4 -1
  123. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  124. package/dist-cjs/adapter-kit/record.js +20 -5
  125. package/dist-cjs/agent/openapi.d.ts +94 -8
  126. package/dist-cjs/agent/openapi.js +530 -32
  127. package/dist-cjs/cli/adapters.d.ts +4 -3
  128. package/dist-cjs/cli/adapters.js +70 -1
  129. package/dist-cjs/cli/doctor.js +3 -0
  130. package/dist-cjs/core/config-builder.d.ts +1 -0
  131. package/dist-cjs/core/config-builder.js +5 -1
  132. package/dist-cjs/core/defaults.d.ts +34 -0
  133. package/dist-cjs/core/defaults.js +44 -0
  134. package/dist-cjs/core/secure-stream.js +4 -1
  135. package/dist-cjs/decisions/completion.d.ts +10 -7
  136. package/dist-cjs/decisions/completion.js +81 -7
  137. package/dist-cjs/decisions/core.d.ts +88 -43
  138. package/dist-cjs/decisions/core.js +134 -46
  139. package/dist-cjs/decisions/errors.d.ts +25 -0
  140. package/dist-cjs/decisions/errors.js +16 -0
  141. package/dist-cjs/decisions/index.d.ts +6 -5
  142. package/dist-cjs/decisions/index.js +5 -1
  143. package/dist-cjs/decisions/integrations.d.ts +14 -5
  144. package/dist-cjs/decisions/integrations.js +16 -1
  145. package/dist-cjs/decisions/mock.d.ts +5 -3
  146. package/dist-cjs/decisions/mock.js +4 -1
  147. package/dist-cjs/decisions/pictures.d.ts +43 -0
  148. package/dist-cjs/decisions/pictures.js +109 -0
  149. package/dist-cjs/decisions/typesafe.d.ts +33 -5
  150. package/dist-cjs/decisions/typesafe.js +126 -13
  151. package/dist-cjs/doctor/index.d.ts +6 -9
  152. package/dist-cjs/doctor/index.js +41 -0
  153. package/dist-cjs/embeddings/adapters.d.ts +1 -1
  154. package/dist-cjs/embeddings/adapters.js +3 -3
  155. package/dist-cjs/embeddings/batch-google.d.ts +27 -8
  156. package/dist-cjs/embeddings/batch-google.js +112 -36
  157. package/dist-cjs/embeddings/batch-mock.js +1 -1
  158. package/dist-cjs/embeddings/batch.js +9 -3
  159. package/dist-cjs/embeddings/engine.js +13 -12
  160. package/dist-cjs/embeddings/index.d.ts +1 -1
  161. package/dist-cjs/embeddings/inputs.d.ts +26 -3
  162. package/dist-cjs/embeddings/inputs.js +89 -5
  163. package/dist-cjs/embeddings/mock.js +1 -1
  164. package/dist-cjs/embeddings/models.js +39 -4
  165. package/dist-cjs/loaders/csv.d.ts +23 -1
  166. package/dist-cjs/loaders/csv.js +126 -71
  167. package/dist-cjs/loaders/index.d.ts +7 -2
  168. package/dist-cjs/loaders/input.d.ts +6 -0
  169. package/dist-cjs/loaders/input.js +43 -1
  170. package/dist-cjs/loaders/json.d.ts +14 -3
  171. package/dist-cjs/loaders/json.js +170 -19
  172. package/dist-cjs/loaders/web.d.ts +55 -0
  173. package/dist-cjs/loaders/web.js +21 -1
  174. package/dist-cjs/mcp/client.d.ts +54 -5
  175. package/dist-cjs/mcp/client.js +171 -20
  176. package/dist-cjs/mcp/http.d.ts +45 -0
  177. package/dist-cjs/mcp/http.js +236 -0
  178. package/dist-cjs/mcp/index.d.ts +4 -2
  179. package/dist-cjs/mcp/index.js +5 -1
  180. package/dist-cjs/mcp/protocol.d.ts +107 -0
  181. package/dist-cjs/mcp/sampling.d.ts +41 -0
  182. package/dist-cjs/mcp/sampling.js +55 -0
  183. package/dist-cjs/mcp/server.d.ts +96 -14
  184. package/dist-cjs/mcp/server.js +273 -58
  185. package/dist-cjs/models/data.js +20 -1
  186. package/dist-cjs/operations/adapters.d.ts +1 -0
  187. package/dist-cjs/operations/adapters.js +3 -1
  188. package/dist-cjs/protocols/a2a-grpc.js +10 -3
  189. package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
  190. package/dist-cjs/protocols/a2a-signing.js +140 -0
  191. package/dist-cjs/protocols/a2a.d.ts +14 -0
  192. package/dist-cjs/protocols/a2a.js +65 -6
  193. package/dist-cjs/providers/cohere.js +16 -2
  194. package/dist-cjs/providers/google.js +7 -1
  195. package/dist-cjs/providers/ollama.d.ts +1 -1
  196. package/dist-cjs/providers/ollama.js +35 -27
  197. package/dist-cjs/providers/openai.js +19 -2
  198. package/dist-cjs/rag/pinecone.d.ts +63 -2
  199. package/dist-cjs/rag/pinecone.js +81 -0
  200. package/dist-cjs/realtime/conversation.js +18 -0
  201. package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
  202. package/dist-cjs/realtime/gemini-live.transport.js +170 -32
  203. package/dist-cjs/realtime/session.d.ts +2 -0
  204. package/dist-cjs/realtime/session.js +19 -4
  205. package/dist-cjs/realtime/types.d.ts +35 -3
  206. package/dist-cjs/security/output-guard.js +4 -1
  207. package/dist-cjs/server/auth.d.ts +263 -2
  208. package/dist-cjs/server/auth.js +437 -5
  209. package/dist-cjs/server/deployments.d.ts +3 -2
  210. package/dist-cjs/server/deployments.js +6 -1
  211. package/dist-cjs/telephony/index.d.ts +1 -1
  212. package/dist-cjs/telephony/manager.d.ts +8 -1
  213. package/dist-cjs/telephony/manager.js +13 -0
  214. package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
  215. package/dist-cjs/telephony/providers/telnyx.js +44 -7
  216. package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
  217. package/dist-cjs/telephony/providers/twilio.js +16 -0
  218. package/dist-cjs/tracing/stores.d.ts +2 -0
  219. package/dist-cjs/tracing/stores.js +1 -0
  220. package/dist-cjs/tracing/tracer.d.ts +5 -2
  221. package/dist-cjs/tracing/tracer.js +6 -3
  222. package/dist-cjs/types/embeddings.d.ts +47 -5
  223. package/dist-cjs/types/messages.d.ts +7 -0
  224. package/dist-cjs/types/principal.d.ts +1 -1
  225. package/dist-cjs/types/response.d.ts +5 -0
  226. package/dist-cjs/types/telephony.d.ts +38 -0
  227. package/dist-cjs/types/tracing.d.ts +6 -0
  228. package/dist-cjs/version.d.ts +1 -1
  229. package/dist-cjs/version.js +1 -1
  230. package/dist-cjs/voice/providers/google.d.ts +44 -0
  231. package/dist-cjs/voice/providers/google.js +252 -0
  232. package/llms.txt +1 -1
  233. package/package.json +17 -1
@@ -60,13 +60,18 @@ export const KNOWN_EMBEDDING_MODELS = {
60
60
  maxBatchSize: 100,
61
61
  costPer1kInput: 0.0002,
62
62
  costPerImage: 0.00012,
63
+ costPerAudioSecond: 0.00016,
64
+ costPerVideoFrame: 0.00079,
65
+ videoFrames: { perSecond: 1, max: 32 },
66
+ maxAudioSeconds: 180,
67
+ maxVideoSeconds: 120,
63
68
  normalized: true,
64
69
  inputTypes: ['document', 'query', 'classification', 'clustering'],
65
- inputModalities: ['text', 'image', 'audio', 'document'],
70
+ inputModalities: ['text', 'image', 'audio', 'video', 'document'],
66
71
  status: 'stable',
67
- verifiedAt: '2026-10-13',
68
- source: 'https://ai.google.dev/gemini-api/docs/embeddings',
69
- notes: 'One vector for text, images (up to 6), audio (up to 180 s), and a PDF (up to 6 pages) together. Reads its task from a text prefix, not a task type. Audio and video are billed per second and frame, which is not priced here.',
72
+ verifiedAt: '2026-10-28',
73
+ source: 'https://ai.google.dev/gemini-api/docs/pricing',
74
+ notes: 'One vector for text, images (up to 6, PNG or JPEG), audio (up to 180 s, MP3 or WAV), video (up to 120 s, MP4 or MOV, its sound not heard), and a PDF (up to 6 pages) together. Reads its task from a text prefix, not a task type. Audio is billed by the second, video by the frame it takes: one a second, at most 32.',
70
75
  }),
71
76
  'text-embedding-004': embeddingModel({
72
77
  provider: 'google',
@@ -93,6 +98,36 @@ export const KNOWN_EMBEDDING_MODELS = {
93
98
  status: 'stable',
94
99
  notes: 'Takes images, and text with images, as data URIs: JPEG, PNG, WebP, or GIF, 20 MB a request in total.',
95
100
  }),
101
+ 'embed-v5.0-pro': embeddingModel({
102
+ provider: 'cohere',
103
+ family: 'embed-v5',
104
+ dimensions: 2048,
105
+ supportedDimensions: [256, 512, 768, 1024, 1536, 2048],
106
+ maxInputTokens: 128000,
107
+ maxBatchSize: 96,
108
+ costPer1kInput: 0,
109
+ inputTypes: ['document', 'query', 'classification', 'clustering'],
110
+ inputModalities: ['text', 'image'],
111
+ status: 'stable',
112
+ verifiedAt: '2026-10-29',
113
+ source: 'https://docs.cohere.com/docs/cohere-embed',
114
+ notes: 'The quality model, for indexing. Shares one vector space with embed-v5.0-fast, so a corpus indexed with Pro is searched with Fast. Cohere publishes no per-token price for it, so its cost reads as zero: give one through embeddings.models.registry.',
115
+ }),
116
+ 'embed-v5.0-fast': embeddingModel({
117
+ provider: 'cohere',
118
+ family: 'embed-v5',
119
+ dimensions: 2048,
120
+ supportedDimensions: [256, 512, 768, 1024, 1536, 2048],
121
+ maxInputTokens: 128000,
122
+ maxBatchSize: 96,
123
+ costPer1kInput: 0,
124
+ inputTypes: ['document', 'query', 'classification', 'clustering'],
125
+ inputModalities: ['text', 'image'],
126
+ status: 'stable',
127
+ verifiedAt: '2026-10-29',
128
+ source: 'https://docs.cohere.com/docs/cohere-embed',
129
+ notes: 'The fast model, for queries and agent loops. Shares one vector space with embed-v5.0-pro, so a corpus indexed with Pro is searched with Fast. Cohere publishes no per-token price for it, so its cost reads as zero: give one through embeddings.models.registry.',
130
+ }),
96
131
  'embed-english-v3.0': embeddingModel({
97
132
  provider: 'cohere',
98
133
  family: 'embed-v3',
@@ -5,6 +5,27 @@ export interface CsvParseOptions {
5
5
  /** The field separator. Defaults to a comma; use `'\t'` for TSV. */
6
6
  delimiter?: string;
7
7
  }
8
+ /**
9
+ * CSV parsed as it arrives: `push()` takes the next piece of text, of any length, and returns the rows
10
+ * it completed; `end()` returns the last. A quote, or a CRLF, split between two pieces is read as one.
11
+ * `parseCsv()` is this, given the whole text at once.
12
+ */
13
+ export declare class CsvRowParser {
14
+ private readonly delimiter;
15
+ private row;
16
+ private field;
17
+ private quoted;
18
+ /** A quote inside a quoted field, waiting for the next character: a second quote, or the field's end. */
19
+ private quote;
20
+ /** A carriage return that ended a row, whose line feed, if one follows, belongs to it. */
21
+ private carriage;
22
+ private started;
23
+ constructor(options?: CsvParseOptions);
24
+ /** Reads the next piece of text, and returns the rows it completed. */
25
+ push(text: string): string[][];
26
+ /** Ends the text, and returns its last row, unless it ended with a line break. */
27
+ end(): string[][];
28
+ }
8
29
  /**
9
30
  * Parses CSV into rows of fields, following RFC 4180: quoted fields may hold the delimiter, line
10
31
  * breaks, and doubled quotes; lines may end in CRLF or LF; and a trailing line break adds no row.
@@ -24,6 +45,7 @@ export interface CsvLoaderOptions extends FileLoaderOptions, CsvParseOptions {
24
45
  }
25
46
  /**
26
47
  * CSV files, one document per row. The first row names the columns. Each row's text lists its columns
27
- * by name, so a model reading a chunk knows what each value is.
48
+ * by name, so a model reading a chunk knows what each value is. Rows are read as the file arrives, so
49
+ * a file larger than memory streams through.
28
50
  */
29
51
  export declare function loadCsv(inputs: FileInput | Iterable<FileInput>, options?: CsvLoaderOptions): AsyncGenerator<DocumentSource>;
@@ -1,87 +1,140 @@
1
- import { inputsOf, readText } from './input.js';
2
- export function parseCsv(text, options = {}) {
3
- const delimiter = options.delimiter ?? ',';
4
- if (delimiter.length !== 1 || delimiter === '"' || delimiter === '\n' || delimiter === '\r') {
5
- throw new RangeError('delimiter must be one character other than a quote or a line break');
1
+ import { inputsOf, sourceOf, textChunks } from './input.js';
2
+ export class CsvRowParser {
3
+ delimiter;
4
+ row = [];
5
+ field = '';
6
+ quoted = false;
7
+ quote = false;
8
+ carriage = false;
9
+ started = false;
10
+ constructor(options = {}) {
11
+ const delimiter = options.delimiter ?? ',';
12
+ if (delimiter.length !== 1 || delimiter === '"' || delimiter === '\n' || delimiter === '\r') {
13
+ throw new RangeError('delimiter must be one character other than a quote or a line break');
14
+ }
15
+ this.delimiter = delimiter;
6
16
  }
7
- const rows = [];
8
- let row = [];
9
- let field = '';
10
- let quoted = false;
11
- let index = text.charCodeAt(0) === 0xfeff ? 1 : 0;
12
- for (; index < text.length; index++) {
13
- const char = text[index];
14
- if (quoted) {
15
- if (char !== '"')
16
- field += char;
17
- else if (text[index + 1] === '"') {
18
- field += '"';
19
- index++;
17
+ push(text) {
18
+ const rows = [];
19
+ let index = 0;
20
+ if (!this.started && text.length > 0) {
21
+ this.started = true;
22
+ if (text.charCodeAt(0) === 0xfeff)
23
+ index = 1;
24
+ }
25
+ for (; index < text.length; index++) {
26
+ const char = text[index];
27
+ if (this.carriage) {
28
+ this.carriage = false;
29
+ if (char === '\n')
30
+ continue;
31
+ }
32
+ if (this.quote) {
33
+ this.quote = false;
34
+ if (char === '"') {
35
+ this.field += '"';
36
+ continue;
37
+ }
38
+ this.quoted = false;
39
+ }
40
+ if (this.quoted) {
41
+ if (char === '"')
42
+ this.quote = true;
43
+ else
44
+ this.field += char;
45
+ }
46
+ else if (char === '"' && this.field === '')
47
+ this.quoted = true;
48
+ else if (char === this.delimiter) {
49
+ this.row.push(this.field);
50
+ this.field = '';
51
+ }
52
+ else if (char === '\n' || char === '\r') {
53
+ this.carriage = char === '\r';
54
+ this.row.push(this.field);
55
+ rows.push(this.row);
56
+ this.row = [];
57
+ this.field = '';
20
58
  }
21
59
  else
22
- quoted = false;
60
+ this.field += char;
23
61
  }
24
- else if (char === '"' && field === '')
25
- quoted = true;
26
- else if (char === delimiter) {
27
- row.push(field);
28
- field = '';
62
+ return rows;
63
+ }
64
+ end() {
65
+ if (this.quote) {
66
+ this.quote = false;
67
+ this.quoted = false;
29
68
  }
30
- else if (char === '\n' || char === '\r') {
31
- if (char === '\r' && text[index + 1] === '\n')
32
- index++;
33
- row.push(field);
34
- rows.push(row);
35
- row = [];
36
- field = '';
69
+ if (this.quoted)
70
+ throw new SyntaxError('The CSV ends inside a quoted field');
71
+ const rows = [];
72
+ if (this.field !== '' || this.row.length > 0) {
73
+ this.row.push(this.field);
74
+ rows.push(this.row);
37
75
  }
38
- else
39
- field += char;
40
- }
41
- if (quoted)
42
- throw new SyntaxError('The CSV ends inside a quoted field');
43
- if (field !== '' || row.length > 0) {
44
- row.push(field);
45
- rows.push(row);
76
+ this.row = [];
77
+ this.field = '';
78
+ return rows;
46
79
  }
47
- return rows;
80
+ }
81
+ export function parseCsv(text, options = {}) {
82
+ const parser = new CsvRowParser(options);
83
+ return [...parser.push(text), ...parser.end()];
48
84
  }
49
85
  export async function* loadCsv(inputs, options = {}) {
50
86
  for (const input of inputsOf(inputs)) {
51
- const { source, text } = await readText(input);
52
- const [header, ...rows] = parseCsv(text, options);
53
- if (!header)
54
- continue;
55
- const column = (name) => {
56
- const index = header.indexOf(name);
57
- if (index < 0)
58
- throw new RangeError(`${source} has no column "${name}"`);
59
- return index;
87
+ const source = sourceOf(input);
88
+ const parser = new CsvRowParser(options);
89
+ let reader;
90
+ const rows = async function* () {
91
+ for await (const chunk of textChunks(input))
92
+ yield* parser.push(chunk);
93
+ yield* parser.end();
60
94
  };
61
- const idIndex = options.idColumn === undefined ? -1 : column(options.idColumn);
62
- const metadataIndexes = (options.metadataColumns ?? []).map((name) => [name, column(name)]);
63
- const textIndexes = options.textColumns
64
- ? options.textColumns.map(column)
65
- : header
66
- .map((_, index) => index)
67
- .filter((index) => index !== idIndex && !metadataIndexes.some(([, metadata]) => metadata === index));
68
- for (const [rowIndex, fields] of rows.entries()) {
69
- if (fields.length === 1 && fields[0] === '')
95
+ for await (const fields of rows()) {
96
+ if (!reader) {
97
+ reader = rowReader(source, fields, options);
70
98
  continue;
71
- const key = idIndex >= 0 ? fields[idIndex] : String(rowIndex + 1);
72
- yield {
73
- id: `${source}#${key}`,
74
- text: textIndexes
75
- .map((index) => `${header[index]}: ${fields[index] ?? ''}`)
76
- .filter((line) => !line.endsWith(': '))
77
- .join('\n'),
78
- source,
79
- metadata: {
80
- ...options.metadata,
81
- ...Object.fromEntries(metadataIndexes.map(([name, index]) => [name, fields[index] ?? ''])),
82
- row: rowIndex + 1,
83
- },
84
- };
99
+ }
100
+ const document = reader(fields);
101
+ if (document)
102
+ yield document;
85
103
  }
86
104
  }
87
105
  }
106
+ function rowReader(source, header, options) {
107
+ const column = (name) => {
108
+ const index = header.indexOf(name);
109
+ if (index < 0)
110
+ throw new RangeError(`${source} has no column "${name}"`);
111
+ return index;
112
+ };
113
+ const idIndex = options.idColumn === undefined ? -1 : column(options.idColumn);
114
+ const metadataIndexes = (options.metadataColumns ?? []).map((name) => [name, column(name)]);
115
+ const textIndexes = options.textColumns
116
+ ? options.textColumns.map(column)
117
+ : header
118
+ .map((_, index) => index)
119
+ .filter((index) => index !== idIndex && !metadataIndexes.some(([, metadata]) => metadata === index));
120
+ let rowIndex = 0;
121
+ return (fields) => {
122
+ rowIndex += 1;
123
+ if (fields.length === 1 && fields[0] === '')
124
+ return undefined;
125
+ const key = idIndex >= 0 ? fields[idIndex] : String(rowIndex);
126
+ return {
127
+ id: `${source}#${key}`,
128
+ text: textIndexes
129
+ .map((index) => `${header[index]}: ${fields[index] ?? ''}`)
130
+ .filter((line) => !line.endsWith(': '))
131
+ .join('\n'),
132
+ source,
133
+ metadata: {
134
+ ...options.metadata,
135
+ ...Object.fromEntries(metadataIndexes.map(([name, index]) => [name, fields[index] ?? ''])),
136
+ row: rowIndex,
137
+ },
138
+ };
139
+ };
140
+ }
@@ -7,12 +7,17 @@ export type { DocumentSource } from '../rag/ingestion.js';
7
7
  */
8
8
  export type DocumentLoader = AsyncIterable<DocumentSource> | Iterable<DocumentSource>;
9
9
  /**
10
- * A file to load: a path, a `file:` URL, or content already in memory with a name for it. In-memory
11
- * content is how a loader reads an upload, a database blob, or a file on a runtime without a disk.
10
+ * A file to load: a path, a `file:` URL, content already in memory with a name for it, or a stream of
11
+ * it, such as a response's body or an object store's download. In-memory content is how a loader reads
12
+ * an upload, a database blob, or a file on a runtime without a disk; a stream, how the CSV and JSON
13
+ * loaders read a file larger than memory, as it arrives.
12
14
  */
13
15
  export type FileInput = string | URL | {
14
16
  source: string;
15
17
  content: string | Uint8Array;
18
+ } | {
19
+ source: string;
20
+ stream: AsyncIterable<Uint8Array | string> | Iterable<Uint8Array | string>;
16
21
  };
17
22
  /** Reads every document from one or more loaders, in order, into an array. */
18
23
  export declare function collectDocuments(...loaders: DocumentLoader[]): Promise<DocumentSource[]>;
@@ -18,3 +18,9 @@ export declare function readText(input: FileInput): Promise<{
18
18
  source: string;
19
19
  text: string;
20
20
  }>;
21
+ /**
22
+ * An input's text a piece at a time, so a large file is never held whole: a path or file URL read from
23
+ * disk in chunks of 64 KiB, a stream as it arrives, and content in memory as it is. A character split
24
+ * between two chunks is decoded whole, and a byte-order mark is dropped.
25
+ */
26
+ export declare function textChunks(input: FileInput): AsyncGenerator<string>;
@@ -14,6 +14,19 @@ export function sourceOf(input) {
14
14
  }
15
15
  export async function readInput(input) {
16
16
  if (typeof input === 'object' && !(input instanceof URL)) {
17
+ if ('stream' in input) {
18
+ const pieces = [];
19
+ for await (const piece of input.stream) {
20
+ pieces.push(typeof piece === 'string' ? new TextEncoder().encode(piece) : piece);
21
+ }
22
+ const bytes = new Uint8Array(pieces.reduce((total, piece) => total + piece.byteLength, 0));
23
+ let offset = 0;
24
+ for (const piece of pieces) {
25
+ bytes.set(piece, offset);
26
+ offset += piece.byteLength;
27
+ }
28
+ return { source: input.source, bytes };
29
+ }
17
30
  const bytes = typeof input.content === 'string' ? new TextEncoder().encode(input.content) : input.content;
18
31
  return { source: input.source, bytes };
19
32
  }
@@ -21,12 +34,40 @@ export async function readInput(input) {
21
34
  return { source: sourceOf(input), bytes: new Uint8Array(await readFile(input)) };
22
35
  }
23
36
  export async function readText(input) {
24
- if (typeof input === 'object' && !(input instanceof URL) && typeof input.content === 'string') {
37
+ if (typeof input === 'object' && !(input instanceof URL) && 'content' in input && typeof input.content === 'string') {
25
38
  return { source: input.source, text: stripBom(input.content) };
26
39
  }
27
40
  const { source, bytes } = await readInput(input);
28
41
  return { source, text: stripBom(new TextDecoder().decode(bytes)) };
29
42
  }
43
+ export async function* textChunks(input) {
44
+ const decoder = new TextDecoder();
45
+ let first = true;
46
+ for await (const piece of piecesOf(input)) {
47
+ let text = typeof piece === 'string' ? piece : decoder.decode(piece, { stream: true });
48
+ if (first && text) {
49
+ first = false;
50
+ text = stripBom(text);
51
+ }
52
+ if (text)
53
+ yield text;
54
+ }
55
+ const rest = decoder.decode();
56
+ if (rest)
57
+ yield rest;
58
+ }
59
+ async function* piecesOf(input) {
60
+ if (typeof input === 'object' && !(input instanceof URL)) {
61
+ if ('stream' in input)
62
+ yield* input.stream;
63
+ else
64
+ yield input.content;
65
+ return;
66
+ }
67
+ const { createReadStream } = await import('node:fs');
68
+ for await (const chunk of createReadStream(input, { highWaterMark: 64 * 1024 }))
69
+ yield chunk;
70
+ }
30
71
  function stripBom(text) {
31
72
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
32
73
  }
@@ -3,10 +3,17 @@ import type { FileLoaderOptions } from './text.js';
3
3
  /** Options for `loadJson()`. */
4
4
  export interface JsonLoaderOptions extends FileLoaderOptions {
5
5
  /**
6
- * Picks the records out of the parsed file. Defaults to the items of a top-level array, or else the
7
- * value itself as one record.
6
+ * Picks the records out of the parsed file, which is then read whole. Defaults to the items of the
7
+ * array at `recordsAt`, or of a top-level array, read as the file arrives; or else the value itself as
8
+ * one record.
8
9
  */
9
10
  records?: (value: unknown) => Iterable<unknown>;
11
+ /**
12
+ * Where the array of records is: object keys from the top, dotted, such as `data.items`. Its items
13
+ * are read one at a time as the file arrives, so a file larger than memory streams through. A file
14
+ * with no array there is refused.
15
+ */
16
+ recordsAt?: string;
10
17
  /**
11
18
  * A record's text: a field, by dotted path such as `body.text`, or a function. Defaults to a string
12
19
  * record itself, or else the record as indented JSON.
@@ -22,5 +29,9 @@ export interface JsonLoaderOptions extends FileLoaderOptions {
22
29
  */
23
30
  lines?: boolean;
24
31
  }
25
- /** JSON and JSON Lines files, one document per record. */
32
+ /**
33
+ * JSON and JSON Lines files, one document per record. A JSON Lines file, and the array of records in a
34
+ * JSON one, are read as the file arrives, a record at a time, so a file larger than memory streams
35
+ * through; a `records` function reads the file whole.
36
+ */
26
37
  export declare function loadJson(inputs: FileInput | Iterable<FileInput>, options?: JsonLoaderOptions): AsyncGenerator<DocumentSource>;
@@ -1,28 +1,15 @@
1
- import { inputsOf, readText } from './input.js';
1
+ import { inputsOf, readText, sourceOf, textChunks } from './input.js';
2
2
  export async function* loadJson(inputs, options = {}) {
3
3
  for (const input of inputsOf(inputs)) {
4
- const { source, text } = await readText(input);
4
+ const source = sourceOf(input);
5
5
  const lines = options.lines ?? /\.(jsonl|ndjson)$/i.test(source);
6
- const parsed = lines
7
- ? text
8
- .split(/\r?\n/)
9
- .filter((line) => line.trim())
10
- .map((line, index) => {
11
- try {
12
- return JSON.parse(line);
13
- }
14
- catch (error) {
15
- throw new SyntaxError(`${source} line ${index + 1}: ${error.message}`);
16
- }
17
- })
18
- : JSON.parse(text);
19
- const records = options.records ? options.records(parsed) : Array.isArray(parsed) ? parsed : [parsed];
20
6
  let index = 0;
21
- for (const record of records) {
7
+ const documentOf = (record) => {
22
8
  const key = typeof options.id === 'function' ? options.id(record, index) : pick(record, options.id);
23
9
  const value = typeof options.text === 'function' ? options.text(record) : pick(record, options.text, record);
24
- yield {
25
- id: `${source}#${key === undefined || key === null ? index + 1 : String(key)}`,
10
+ index++;
11
+ return {
12
+ id: `${source}#${key === undefined || key === null ? index : String(key)}`,
26
13
  text: typeof value === 'string' ? value : JSON.stringify(value, null, 2),
27
14
  source,
28
15
  metadata: {
@@ -32,8 +19,172 @@ export async function* loadJson(inputs, options = {}) {
32
19
  : Object.fromEntries((options.metadataFields ?? []).map((field) => [field, pick(record, field)]))),
33
20
  },
34
21
  };
35
- index++;
22
+ };
23
+ if (options.records) {
24
+ const { text } = await readText(input);
25
+ const values = [];
26
+ if (lines)
27
+ for await (const value of jsonLines([text], source))
28
+ values.push(value);
29
+ const parsed = lines ? values : JSON.parse(text);
30
+ for (const record of options.records(parsed))
31
+ yield documentOf(record);
32
+ }
33
+ else if (lines) {
34
+ for await (const record of jsonLines(textChunks(input), source))
35
+ yield documentOf(record);
36
+ }
37
+ else {
38
+ const scanner = new JsonRecordScanner(options.recordsAt?.split('.') ?? []);
39
+ for await (const chunk of textChunks(input)) {
40
+ for (const record of scanner.push(chunk))
41
+ yield documentOf(record);
42
+ if (scanner.finished)
43
+ break;
44
+ }
45
+ for (const record of scanner.end(source))
46
+ yield documentOf(record);
47
+ }
48
+ }
49
+ }
50
+ async function* jsonLines(chunks, source) {
51
+ let rest = '';
52
+ let number = 0;
53
+ const parse = (line) => {
54
+ number += 1;
55
+ if (!line.trim())
56
+ return undefined;
57
+ try {
58
+ return { value: JSON.parse(line) };
59
+ }
60
+ catch (error) {
61
+ throw new SyntaxError(`${source} line ${number}: ${error.message}`);
62
+ }
63
+ };
64
+ for await (const chunk of chunks) {
65
+ const pieces = (rest + chunk).split('\n');
66
+ rest = pieces.pop() ?? '';
67
+ for (const line of pieces) {
68
+ const parsed = parse(line);
69
+ if (parsed)
70
+ yield parsed.value;
71
+ }
72
+ }
73
+ const last = parse(rest);
74
+ if (last)
75
+ yield last.value;
76
+ }
77
+ class JsonRecordScanner {
78
+ path;
79
+ stack = [];
80
+ inString = false;
81
+ escaped = false;
82
+ keyText;
83
+ target = -1;
84
+ found = false;
85
+ done = false;
86
+ record;
87
+ whole;
88
+ started = false;
89
+ explicit;
90
+ constructor(path = []) {
91
+ this.path = path;
92
+ this.explicit = path.length > 0;
93
+ }
94
+ get finished() {
95
+ return this.done;
96
+ }
97
+ push(text) {
98
+ const records = [];
99
+ if (this.whole) {
100
+ this.whole.push(text);
101
+ return records;
36
102
  }
103
+ let from = this.record ? 0 : -1;
104
+ for (let index = 0; index < text.length; index++) {
105
+ const char = text[index];
106
+ if (!this.started) {
107
+ if (/\s/.test(char))
108
+ continue;
109
+ this.started = true;
110
+ if (char !== '[' && !this.explicit) {
111
+ this.whole = [text.slice(index)];
112
+ return records;
113
+ }
114
+ }
115
+ if (this.inString) {
116
+ if (this.keyText)
117
+ this.keyText.push(char);
118
+ if (this.escaped)
119
+ this.escaped = false;
120
+ else if (char === '\\')
121
+ this.escaped = true;
122
+ else if (char === '"') {
123
+ this.inString = false;
124
+ if (this.keyText) {
125
+ const frame = this.stack.at(-1);
126
+ frame.key = JSON.parse(`"${this.keyText.slice(0, -1).join('')}"`);
127
+ frame.expectKey = false;
128
+ this.keyText = undefined;
129
+ }
130
+ }
131
+ continue;
132
+ }
133
+ const atTarget = this.target >= 0 && this.stack.length === this.target;
134
+ if (atTarget && (char === ',' || char === ']')) {
135
+ if (this.record) {
136
+ this.record.push(text.slice(from, index));
137
+ records.push(JSON.parse(this.record.join('')));
138
+ this.record = undefined;
139
+ }
140
+ if (char === ']') {
141
+ this.done = true;
142
+ return records;
143
+ }
144
+ continue;
145
+ }
146
+ if (atTarget && !this.record && !/\s/.test(char)) {
147
+ this.record = [];
148
+ from = index;
149
+ }
150
+ const frame = this.stack.at(-1);
151
+ if (char === '"') {
152
+ this.inString = true;
153
+ if (frame?.kind === 'object' && frame.expectKey && !this.record)
154
+ this.keyText = [];
155
+ }
156
+ else if (char === '{' || char === '[') {
157
+ const opened = char === '{' ? { kind: 'object', expectKey: true } : { kind: 'array' };
158
+ if (char === '[' && this.at(this.path)) {
159
+ this.found = true;
160
+ this.target = this.stack.length + 1;
161
+ }
162
+ this.stack.push(opened);
163
+ }
164
+ else if (char === '}' || char === ']') {
165
+ this.stack.pop();
166
+ }
167
+ else if (char === ',' && frame?.kind === 'object')
168
+ frame.expectKey = true;
169
+ }
170
+ if (this.record)
171
+ this.record.push(text.slice(Math.max(from, 0)));
172
+ return records;
173
+ }
174
+ end(source) {
175
+ if (this.whole)
176
+ return [JSON.parse(this.whole.join(''))];
177
+ if (!this.found) {
178
+ throw new SyntaxError(this.explicit ? `${source} has no array at "${this.path.join('.')}"` : `${source} holds no JSON value`);
179
+ }
180
+ if (!this.done)
181
+ throw new SyntaxError(`${source} ends inside its array of records`);
182
+ return [];
183
+ }
184
+ at(path) {
185
+ if (this.stack.length !== path.length)
186
+ return false;
187
+ return this.stack.every((frame, depth) => frame.key === path[depth]);
37
188
  }
38
189
  }
39
190
  function pick(record, path, fallback) {