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
@@ -4,7 +4,10 @@ import type { EmbeddingBatchOutputItem, EmbeddingBatchProvider, EmbeddingBatchPr
4
4
  export interface GoogleEmbeddingBatchProviderOptions {
5
5
  /** Gemini API key, sent as `x-goog-api-key`. */
6
6
  apiKey: string;
7
- /** API base URL. Defaults to `https://generativelanguage.googleapis.com/v1beta`. */
7
+ /**
8
+ * API base URL. Defaults to `https://generativelanguage.googleapis.com/v1beta`. A file is uploaded to,
9
+ * and results downloaded from, the same URL with `/upload` and `/download` before its version.
10
+ */
8
11
  baseUrl?: string;
9
12
  /** Headers added to every request. */
10
13
  headers?: Record<string, string>;
@@ -12,10 +15,11 @@ export interface GoogleEmbeddingBatchProviderOptions {
12
15
  fetch?: typeof fetch;
13
16
  }
14
17
  /**
15
- * Gemini's Batch API for embeddings (`asyncBatchEmbedContent`): half price, inline requests under
16
- * 20 MB, and results inline, in the order submitted. It takes text, images, audio, and PDFs on a model
17
- * that does, such as gemini-embedding-2. The wire shape follows Google's API reference and its own
18
- * clients; it graduates with a recorded live run.
18
+ * Gemini's Batch API for embeddings (`asyncBatchEmbedContent`): half price. Requests under 20 MB go
19
+ * inline; a larger batch, up to 2 GB, is uploaded as a JSONL file through the Files API, a line per
20
+ * input with its id as the key, and its results are read from the file the batch writes. It takes
21
+ * text, images, audio, video, and PDFs on a model that does, such as gemini-embedding-2. The wire shape
22
+ * follows Google's API reference and its own clients; it graduates with a recorded live run.
19
23
  */
20
24
  export declare class GoogleEmbeddingBatchProvider implements EmbeddingBatchProvider {
21
25
  private readonly options;
@@ -23,18 +27,33 @@ export declare class GoogleEmbeddingBatchProvider implements EmbeddingBatchProvi
23
27
  readonly info: EmbeddingBatchProviderInfo;
24
28
  constructor(options: GoogleEmbeddingBatchProviderOptions);
25
29
  /**
26
- * Sends every input inline as one batch. Results come back in the same order, so the ref keeps the
27
- * ids in that order.
30
+ * Sends every input as one batch: inline under 20 MB, or else as a JSONL file uploaded first. Results
31
+ * come back in the same order, so the ref keeps the ids in that order.
28
32
  */
29
33
  submit(request: EmbeddingBatchProviderRequest, context: BatchProviderCallContext): Promise<BatchJobRef>;
30
34
  /** Reads a batch's state and counts. */
31
35
  poll(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
32
- /** Every input's vector or error, matched to its id by position. */
36
+ /**
37
+ * Every input's vector or error: inline, or read from the file the batch wrote. An answer that names
38
+ * its key is matched by it, and one that does not, by its position.
39
+ */
33
40
  results(ref: BatchJobRef, context: BatchProviderCallContext): Promise<EmbeddingBatchOutputItem[]>;
34
41
  /** Cancels a batch. */
35
42
  cancel(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
43
+ /**
44
+ * Uploads an input file through the Files API's resumable protocol, a start and then the bytes, and
45
+ * returns its name, such as `files/abc`. Google keeps it 48 hours.
46
+ */
47
+ private upload;
48
+ /** A results file's text, from the Files API's download URL. */
49
+ private download;
50
+ /** The URL beside the API's own for uploads or downloads: `/upload` or `/download` before the version. */
51
+ private alongside;
36
52
  /** The batch resource, whether Google answers with it or with an operation that holds it. */
37
53
  private read;
38
54
  private state;
39
55
  private request;
56
+ private base;
57
+ /** A call with the key, refused unless it succeeds. */
58
+ private send;
40
59
  }
@@ -1,6 +1,7 @@
1
1
  import { BatchCapabilityError, BatchProviderResponseError } from '../batch/errors.js';
2
2
  import { GOOGLE_TASK_TYPES, googleParts, usesTaskPrefix } from './google-wire.js';
3
3
  const INLINE_BYTES = 20 * 1024 * 1024;
4
+ const FILE_BYTES = 2 * 1024 * 1024 * 1024;
4
5
  const STATES = {
5
6
  PENDING: 'validating',
6
7
  RUNNING: 'in_progress',
@@ -14,10 +15,10 @@ export class GoogleEmbeddingBatchProvider {
14
15
  info = {
15
16
  name: 'google',
16
17
  capabilities: {
17
- maxBytes: INLINE_BYTES,
18
+ maxBytes: FILE_BYTES,
18
19
  supportsCancel: true,
19
20
  discount: 0.5,
20
- inputModalities: ['text', 'image', 'audio', 'document'],
21
+ inputModalities: ['text', 'image', 'audio', 'video', 'document'],
21
22
  },
22
23
  };
23
24
  constructor(options) {
@@ -26,33 +27,44 @@ export class GoogleEmbeddingBatchProvider {
26
27
  async submit(request, context) {
27
28
  const qualified = request.model.startsWith('models/') ? request.model : `models/${request.model}`;
28
29
  const prefixed = usesTaskPrefix(request.model);
29
- const body = JSON.stringify({
30
- batch: {
31
- displayName: request.metadata?.key ?? 'nexus-embeddings',
32
- inputConfig: {
33
- requests: {
34
- requests: request.items.map((item) => ({
35
- request: {
36
- model: qualified,
37
- content: {
38
- parts: googleParts(item.contents ?? [{ type: 'text', text: item.input }], prefixed ? request.inputType : undefined),
39
- },
40
- ...(request.inputType && !prefixed ? { taskType: GOOGLE_TASK_TYPES[request.inputType] } : {}),
41
- ...(request.dimensions !== undefined ? { outputDimensionality: request.dimensions } : {}),
42
- },
43
- })),
44
- },
45
- },
30
+ const displayName = request.metadata?.key ?? 'nexus-embeddings';
31
+ const encoded = request.items.map((item) => JSON.stringify({
32
+ model: qualified,
33
+ content: {
34
+ parts: googleParts(item.contents ?? [{ type: 'text', text: item.input }], prefixed ? request.inputType : undefined),
46
35
  },
47
- });
48
- if (body.length > INLINE_BYTES) {
49
- throw new BatchCapabilityError('google', 'maxBytes', `An inline Gemini batch holds at most 20 MB; this one is ${Math.ceil(body.length / 1024 / 1024)} MB, so split it`);
36
+ ...(request.inputType && !prefixed ? { taskType: GOOGLE_TASK_TYPES[request.inputType] } : {}),
37
+ ...(request.dimensions !== undefined ? { outputDimensionality: request.dimensions } : {}),
38
+ }));
39
+ const opening = `{"batch":{"displayName":${JSON.stringify(displayName)},"inputConfig":{"requests":{"requests":[`;
40
+ const closing = ']}}}}';
41
+ const inlineSize = utf8Length(opening) +
42
+ encoded.reduce((total, line) => total + utf8Length(line) + '{"request":},'.length, 0) +
43
+ closing.length;
44
+ let body;
45
+ let inputFile;
46
+ if (inlineSize <= INLINE_BYTES) {
47
+ body = `${opening}${encoded.map((line) => `{"request":${line}}`).join(',')}${closing}`;
48
+ }
49
+ else {
50
+ const lines = request.items.map((item, index) => `{"key":${JSON.stringify(item.customId)},"request":${encoded[index]}}\n`);
51
+ const size = lines.reduce((total, line) => total + utf8Length(line), 0);
52
+ const most = this.info.capabilities.maxBytes ?? FILE_BYTES;
53
+ if (size > most) {
54
+ throw new BatchCapabilityError('google', 'maxBytes', `A Gemini batch file holds at most ${Math.floor(most / 1024 / 1024)} MB; this one is ${Math.ceil(size / 1024 / 1024)} MB, so split it`);
55
+ }
56
+ inputFile = await this.upload(new Blob(lines, { type: 'application/jsonl' }), displayName, context);
57
+ body = JSON.stringify({ batch: { displayName, inputConfig: { fileName: inputFile } } });
50
58
  }
51
59
  const created = (await this.request(`/${qualified}:asyncBatchEmbedContent`, { method: 'POST', body }, context));
52
60
  const name = created.name ?? created.metadata?.name;
53
61
  if (!name)
54
62
  throw new BatchProviderResponseError('google', 'the batch response returned no name');
55
- return { id: name, provider: 'google', metadata: { customIds: request.items.map((item) => item.customId) } };
63
+ return {
64
+ id: name,
65
+ provider: 'google',
66
+ metadata: { customIds: request.items.map((item) => item.customId), ...(inputFile ? { inputFile } : {}) },
67
+ };
56
68
  }
57
69
  async poll(ref, context) {
58
70
  return this.state(ref, await this.read(ref, context));
@@ -60,14 +72,18 @@ export class GoogleEmbeddingBatchProvider {
60
72
  async results(ref, context) {
61
73
  const batch = await this.read(ref, context);
62
74
  const output = batch.output;
63
- if (output?.responsesFile) {
64
- throw new BatchProviderResponseError('google', 'the batch wrote its results to a file; this adapter reads inline results');
65
- }
66
75
  const ids = (ref.metadata?.customIds ?? []);
67
- const responses = output?.inlinedEmbedContentResponses?.inlinedResponses ?? [];
76
+ const responses = output?.responsesFile
77
+ ? (await this.download(output.responsesFile, context))
78
+ .split('\n')
79
+ .filter((line) => line.trim() !== '')
80
+ .map((line) => JSON.parse(line))
81
+ : (output?.inlinedResponses?.inlinedResponses ?? output?.inlinedEmbedContentResponses?.inlinedResponses ?? []);
82
+ const known = new Set(ids);
68
83
  return responses.map((entry, index) => {
69
- const customId = ids[index] ?? String(index);
70
- const values = entry.response?.embedding?.values;
84
+ const key = entry.key ?? entry.metadata?.key;
85
+ const customId = key !== undefined && known.has(key) ? key : (ids[index] ?? String(index));
86
+ const values = (entry.response ?? entry).embedding?.values;
71
87
  if (Array.isArray(values))
72
88
  return { customId, vector: values };
73
89
  return {
@@ -83,6 +99,43 @@ export class GoogleEmbeddingBatchProvider {
83
99
  await this.request(`/${ref.id}:cancel`, { method: 'POST' }, context);
84
100
  return this.poll(ref, context);
85
101
  }
102
+ async upload(file, displayName, context) {
103
+ const started = await this.send(this.alongside('upload', '/files'), {
104
+ method: 'POST',
105
+ body: JSON.stringify({ file: { displayName } }),
106
+ headers: {
107
+ 'content-type': 'application/json',
108
+ 'x-goog-upload-protocol': 'resumable',
109
+ 'x-goog-upload-command': 'start',
110
+ 'x-goog-upload-header-content-length': String(file.size),
111
+ 'x-goog-upload-header-content-type': 'application/jsonl',
112
+ },
113
+ }, context);
114
+ const target = started.headers.get('x-goog-upload-url');
115
+ if (!target)
116
+ throw new BatchProviderResponseError('google', 'the upload was not given a URL to send the file to');
117
+ const finished = await this.send(target, {
118
+ method: 'POST',
119
+ body: file,
120
+ headers: { 'x-goog-upload-offset': '0', 'x-goog-upload-command': 'upload, finalize' },
121
+ }, context);
122
+ const name = JSON.parse(await finished.text()).file?.name;
123
+ if (!name)
124
+ throw new BatchProviderResponseError('google', 'the upload returned no file name');
125
+ return name;
126
+ }
127
+ async download(file, context) {
128
+ const response = await this.send(this.alongside('download', `/${file}:download?alt=media`), {}, context);
129
+ return response.text();
130
+ }
131
+ alongside(kind, path) {
132
+ const base = this.base();
133
+ const moved = base.replace(/\/(v1[^/]*)$/, `/${kind}/$1`);
134
+ if (moved === base) {
135
+ throw new BatchProviderResponseError('google', `the base URL ${base} names no API version to ${kind} beside`);
136
+ }
137
+ return `${moved}${path}`;
138
+ }
86
139
  async read(ref, context) {
87
140
  const answer = (await this.request(`/${ref.id}`, {}, context));
88
141
  return { ...answer, ...answer.metadata };
@@ -112,23 +165,46 @@ export class GoogleEmbeddingBatchProvider {
112
165
  };
113
166
  }
114
167
  async request(path, init, context) {
115
- const fetchImpl = this.options.fetch ?? fetch;
116
- const base = (this.options.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta').replace(/\/+$/, '');
168
+ const response = await this.send(`${this.base()}${path}`, { ...init, headers: { 'content-type': 'application/json' } }, context);
169
+ const text = await response.text();
170
+ return text ? JSON.parse(text) : {};
171
+ }
172
+ base() {
173
+ return (this.options.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta').replace(/\/+$/, '');
174
+ }
175
+ async send(url, init, context) {
117
176
  const credential = await context.credential?.(this.info.name);
118
- const response = await fetchImpl(`${base}${path}`, {
177
+ const response = await (this.options.fetch ?? fetch)(url, {
119
178
  ...init,
120
179
  headers: {
121
- 'content-type': 'application/json',
180
+ ...init.headers,
122
181
  'x-goog-api-key': credential?.apiKey ?? this.options.apiKey,
123
182
  ...this.options.headers,
124
183
  ...credential?.headers,
125
184
  },
126
185
  signal: context.signal,
127
186
  });
128
- const text = await response.text();
129
187
  if (!response.ok) {
130
- throw new BatchProviderResponseError('google', `${path} failed: ${response.status} ${text.slice(0, 500)}`);
188
+ const path = new URL(url).pathname;
189
+ throw new BatchProviderResponseError('google', `${path} failed: ${response.status} ${(await response.text()).slice(0, 500)}`);
131
190
  }
132
- return text ? JSON.parse(text) : {};
191
+ return response;
192
+ }
193
+ }
194
+ function utf8Length(text) {
195
+ let bytes = 0;
196
+ for (let index = 0; index < text.length; index += 1) {
197
+ const code = text.charCodeAt(index);
198
+ if (code < 0x80)
199
+ bytes += 1;
200
+ else if (code < 0x800)
201
+ bytes += 2;
202
+ else if (code >= 0xd800 && code <= 0xdbff) {
203
+ bytes += 4;
204
+ index += 1;
205
+ }
206
+ else
207
+ bytes += 3;
133
208
  }
209
+ return bytes;
134
210
  }
@@ -12,7 +12,7 @@ export class MockEmbeddingBatchProvider {
12
12
  maxItems: 50_000,
13
13
  supportsCancel: true,
14
14
  discount: 0.5,
15
- inputModalities: ['text', 'image', 'audio', 'document'],
15
+ inputModalities: ['text', 'image', 'audio', 'video', 'document'],
16
16
  ...options.capabilities,
17
17
  },
18
18
  };
@@ -3,7 +3,7 @@ import { pollUntilSettled } from '../batch/poll.js';
3
3
  import { continuing } from '../lifecycle/continue.js';
4
4
  import { generateRequestId } from '../utils/ids.js';
5
5
  import { Tokenizer } from '../utils/tokenizer.js';
6
- import { acceptedModalities, normalizeInputs } from './inputs.js';
6
+ import { acceptedModalities, assertLengths, mediaCost, normalizeInputs, totalUnits, } from './inputs.js';
7
7
  import { priceEmbeddingUsage, resolveEmbeddingModel } from './models.js';
8
8
  const DEFAULT_POLL_INTERVAL_MS = 30_000;
9
9
  const DEFAULT_MAX_POLL_INTERVAL_MS = 300_000;
@@ -150,6 +150,12 @@ export class EmbeddingBatchManager {
150
150
  if (refused.length) {
151
151
  throw new BatchCapabilityError(providerName, 'input', `Embedding model "${request.model}" in the "${providerName}" batch tier takes ${accepted.join(', ')} input, not ${refused.join(' or ')}`);
152
152
  }
153
+ try {
154
+ assertLengths(inputs, request.model, model);
155
+ }
156
+ catch (error) {
157
+ throw new BatchCapabilityError(providerName, 'input', error.message);
158
+ }
153
159
  const parts = inputs.parts;
154
160
  let estimatedInputTokens = 0;
155
161
  for (const text of inputs.texts)
@@ -170,7 +176,7 @@ export class EmbeddingBatchManager {
170
176
  notes: {
171
177
  model: request.model,
172
178
  estimatedInputTokens,
173
- images: inputs.images.reduce((total, count) => total + count, 0),
179
+ media: totalUnits(inputs.units),
174
180
  items: request.items.length,
175
181
  ...(request.normalize ? { normalize: true } : {}),
176
182
  ...(request.tenantId !== undefined ? { tenant: request.tenantId } : {}),
@@ -228,8 +234,8 @@ export class EmbeddingBatchManager {
228
234
  if (!notes.model)
229
235
  return { amount: 0, currency: 'USD', basis: 'estimated' };
230
236
  const cost = priceEmbeddingUsage(notes.model, usage, this.config.models);
231
- const perImage = resolveEmbeddingModel(notes.model, this.config.models).capabilities?.costPerImage ?? 0;
232
- const amount = (cost.amount + (notes.images ?? 0) * perImage) * discount;
237
+ const media = notes.media ?? { images: notes.images ?? 0, audio: [], video: [], unmeasured: 0 };
238
+ const amount = (cost.amount + mediaCost(media, resolveEmbeddingModel(notes.model, this.config.models).capabilities)) * discount;
233
239
  return { ...cost, amount, input: amount, output: 0 };
234
240
  }
235
241
  resolveProvider(preferred) {
@@ -1,7 +1,7 @@
1
1
  import { MemoryCache, createCacheKey } from '../cache/memory-cache.js';
2
2
  import { RateLimiter } from '../ops/rate-limiter.js';
3
3
  import { OperationLifecycle } from '../core/lifecycle.js';
4
- import { acceptedModalities, inputIdentities, normalizeInputs } from './inputs.js';
4
+ import { acceptedModalities, assertLengths, inputIdentities, mediaCost, normalizeInputs, pricesByLength, totalUnits, } from './inputs.js';
5
5
  import { NexusProviderError, createTimeoutProviderError, isAbortError } from '../providers/errors.js';
6
6
  import { assertWithinCostBudget, formatCost } from '../optimizer/cost-budget.js';
7
7
  import { Tokenizer } from '../utils/tokenizer.js';
@@ -55,6 +55,7 @@ export class EmbeddingEngine {
55
55
  route = this.resolveRoute(request);
56
56
  assertRequestedOptions(route, request);
57
57
  assertModalities(route.providerName, route.resolved.model, route.resolved.capabilities, route.provider, inputs);
58
+ assertLengths(inputs, route.resolved.model, route.resolved.capabilities);
58
59
  estimate = this.enforceCostBudget(inputs, route.resolved.model);
59
60
  return { provider: route.providerName, model: route.resolved.model };
60
61
  },
@@ -119,7 +120,7 @@ export class EmbeddingEngine {
119
120
  let truncated = false;
120
121
  let providerUsed = route.providerName;
121
122
  let modelUsed = model;
122
- let images = 0;
123
+ const embedded = [];
123
124
  if (unique.length > 0) {
124
125
  const batchSize = resolveMaxBatchSize(route.resolved.capabilities, route.provider);
125
126
  const chunks = chunkInputs(unique, batchSize);
@@ -146,7 +147,7 @@ export class EmbeddingEngine {
146
147
  usage = estimateUsage(unique.map((index) => inputs.texts[index]), this.tokenizer);
147
148
  }
148
149
  for (const index of unique)
149
- images += inputs.images[index] ?? 0;
150
+ embedded.push(inputs.units[index]);
150
151
  const finished = flatVectors.map((values) => this.postProcess(values, route, request));
151
152
  for (let position = 0; position < pending.length; position += 1) {
152
153
  const index = pending[position];
@@ -172,6 +173,7 @@ export class EmbeddingEngine {
172
173
  truncated: truncated || undefined,
173
174
  };
174
175
  });
176
+ const media = totalUnits(embedded);
175
177
  const meta = {
176
178
  requestId,
177
179
  providerUsed,
@@ -181,7 +183,8 @@ export class EmbeddingEngine {
181
183
  latencyMs: Date.now() - startedAt,
182
184
  batches,
183
185
  usage,
184
- cost: withImageCost(priceEmbeddingUsage(model, usage, this.config.models), images, route.resolved.capabilities),
186
+ cost: withMediaCost(priceEmbeddingUsage(model, usage, this.config.models), media, route.resolved.capabilities),
187
+ ...(media.unmeasured && pricesByLength(route.resolved.capabilities) ? { unpricedMedia: media.unmeasured } : {}),
185
188
  cacheHit: cachedInputs === count && count > 0,
186
189
  cachedInputs: cachedInputs || undefined,
187
190
  deduplicatedInputs: deduplicatedInputs || undefined,
@@ -407,11 +410,10 @@ export class EmbeddingEngine {
407
410
  for (const text of inputs.texts)
408
411
  inputTokens += this.tokenizer.estimateTextTokens(text);
409
412
  const estimate = estimateEmbeddingCost({ model, inputTokens, config: this.config.models });
410
- const images = inputs.images.reduce((total, count) => total + count, 0);
411
- const perImage = images ? resolveEmbeddingModel(model, this.config.models).capabilities?.costPerImage : undefined;
412
- if (!perImage)
413
+ const extra = mediaCost(totalUnits(inputs.units), resolveEmbeddingModel(model, this.config.models).capabilities);
414
+ if (!extra)
413
415
  return estimate;
414
- const totalCost = estimate.totalCost + images * perImage;
416
+ const totalCost = estimate.totalCost + extra;
415
417
  return { ...estimate, totalCost, formatted: formatCost(totalCost) };
416
418
  }
417
419
  cacheContext(route, request) {
@@ -529,12 +531,11 @@ function assertModalities(providerName, modelName, model, provider, inputs) {
529
531
  const refused = [...inputs.media].filter((kind) => !accepted.includes(kind));
530
532
  throw new EmbeddingCapabilityError(providerName, 'input', refused.join(', '), `Embedding model "${modelName}" takes ${accepted.join(', ')} input, not ${refused.join(' or ')}; use a model whose inputModalities include it, such as gemini-embedding-2`);
531
533
  }
532
- function withImageCost(cost, images, model) {
533
- const perImage = model?.costPerImage;
534
- if (!images || !perImage)
534
+ function withMediaCost(cost, media, model) {
535
+ const extra = mediaCost(media, model);
536
+ if (!extra)
535
537
  return cost;
536
- const imageCost = images * perImage;
537
- return { ...cost, amount: cost.amount + imageCost, input: (cost.input ?? 0) + imageCost };
538
+ return { ...cost, amount: cost.amount + extra, input: (cost.input ?? 0) + extra };
538
539
  }
539
540
  function addUsage(usage, inputTokens, totalTokens) {
540
541
  const input = usage.inputTokens + (inputTokens ?? 0);
@@ -5,4 +5,4 @@ export { MockEmbeddingProvider, type MockEmbeddingProviderOptions } from './mock
5
5
  export { createConfiguredEmbeddingProviders } from './register.js';
6
6
  export { EMBEDDING_MODEL_ALIASES, EMBEDDING_REGISTRY_PROVENANCE, KNOWN_EMBEDDING_MODELS, embeddingDimensions, estimateEmbeddingCost, getEmbeddingModelAliases, getEmbeddingModelCapabilities, getEmbeddingModelRegistry, listEmbeddingModels, listEmbeddingModelsForProvider, priceEmbeddingUsage, resolveEmbeddingModel, resolveMaxBatchSize, type EmbeddingCostEstimateInput, type ResolvedEmbeddingModel, } from './models.js';
7
7
  export { createCohereEmbeddingProvider, createGeminiEmbeddingProvider, createOpenAIEmbeddingProvider, toEmbeddingFunction, type CohereEmbeddingOptions, type EmbeddingSource, type GeminiEmbeddingOptions, type OpenAIEmbeddingOptions, } from './providers.js';
8
- export type { Embedding, EmbeddingCacheConfig, EmbeddingConfig, EmbeddingCostBudgetConfig, EmbeddingEncodingFormat, EmbeddingInput, EmbeddingInputType, EmbeddingMeta, EmbeddingModality, EmbeddingModelCapabilities, EmbeddingModelRegistryConfig, EmbeddingPart, EmbeddingParts, EmbeddingProviderCallContext, EmbeddingProviderCapabilities, EmbeddingProviderInfo, EmbeddingProviderMediaPart, EmbeddingProviderPart, EmbeddingProviderRequest, EmbeddingProviderTextPart, EmbeddingProviderResult, EmbeddingProviderUsage, EmbeddingRequest, EmbeddingResponse, EmbeddingTruncateMode, EmbeddingsProvider, } from '../types/embeddings.js';
8
+ export type { Embedding, EmbeddingCacheConfig, EmbeddingConfig, EmbeddingCostBudgetConfig, EmbeddingEncodingFormat, EmbeddingInput, EmbeddingInputType, EmbeddingMeta, EmbeddingModality, EmbeddingModelCapabilities, EmbeddingModelRegistryConfig, EmbeddingPart, EmbeddingParts, EmbeddingProviderCallContext, EmbeddingProviderCapabilities, EmbeddingProviderInfo, EmbeddingProviderMediaPart, EmbeddingProviderPart, EmbeddingProviderRequest, EmbeddingProviderTextPart, EmbeddingProviderResult, EmbeddingProviderUsage, EmbeddingRequest, EmbeddingResponse, EmbeddingTruncateMode, EmbeddingVideoPart, EmbeddingsProvider, } from '../types/embeddings.js';
@@ -1,4 +1,4 @@
1
- import type { EmbeddingInput, EmbeddingModality, EmbeddingProviderPart } from '../types/embeddings.js';
1
+ import type { EmbeddingInput, EmbeddingModality, EmbeddingModelCapabilities, EmbeddingProviderPart } from '../types/embeddings.js';
2
2
  /** A media kind an embedding input can hold. */
3
3
  export type EmbeddingMediaKind = Exclude<EmbeddingModality, 'text'>;
4
4
  /** A request's inputs, checked and ready for routing, the cache, and the provider. */
@@ -9,11 +9,34 @@ export interface EmbeddingInputs {
9
9
  parts?: EmbeddingProviderPart[][];
10
10
  /** The media kinds the inputs hold. */
11
11
  media: Set<EmbeddingMediaKind>;
12
- /** How many images each input holds, for pricing. */
13
- images: number[];
12
+ /** What media each input holds, for pricing and for a model's limits. */
13
+ units: MediaUnits[];
14
+ }
15
+ /**
16
+ * What an input's media comes to: its images, each audio part's and each video part's length in
17
+ * seconds, read from its own headers, and the audio and video parts whose length could not be read.
18
+ */
19
+ export interface MediaUnits {
20
+ images: number;
21
+ audio: number[];
22
+ video: number[];
23
+ unmeasured: number;
14
24
  }
15
25
  /** Checks a request's input and resolves every media part to base64 data or a URL. */
16
26
  export declare function normalizeInputs(input: EmbeddingInput): EmbeddingInputs;
27
+ /** Every input's media together. */
28
+ export declare function totalUnits(units: readonly MediaUnits[]): MediaUnits;
29
+ /** How many frames a model takes from a video of this length, by its `videoFrames`. */
30
+ export declare function framesOf(seconds: number, model: EmbeddingModelCapabilities): number;
31
+ /**
32
+ * What a model charges for media, in US dollars: each image, each second of audio, counted up, and
33
+ * each frame it takes from a video. A model with no media prices charges nothing for it.
34
+ */
35
+ export declare function mediaCost(units: MediaUnits, model: EmbeddingModelCapabilities | undefined): number;
36
+ /** Whether a model prices a medium by its length, so a part whose length is unknown is unpriced. */
37
+ export declare function pricesByLength(model: EmbeddingModelCapabilities | undefined): boolean;
38
+ /** Refuses audio or video longer than a model takes, naming the input, before anything is sent. */
39
+ export declare function assertLengths(inputs: EmbeddingInputs, modelName: string, model: EmbeddingModelCapabilities | undefined): void;
17
40
  /**
18
41
  * Each input's identity, which repeats and the cache are keyed by: a text itself, or its parts with
19
42
  * every medium's content hashed, so a megabyte image is never a key. Loaded only for media.
@@ -1,4 +1,6 @@
1
1
  import { wireAudio, wireDocument, wireImage } from '../providers/content.js';
2
+ import { decodeBase64, encodeBase64 } from '../utils/base64.js';
3
+ import { readMediaDuration } from '../utils/media-duration.js';
2
4
  import { EmbeddingValidationError } from './errors.js';
3
5
  export function normalizeInputs(input) {
4
6
  const items = typeof input === 'string' || !Array.isArray(input) ? [input] : input;
@@ -8,14 +10,14 @@ export function normalizeInputs(input) {
8
10
  const texts = [];
9
11
  const resolved = [];
10
12
  const media = new Set();
11
- const images = [];
13
+ const units = [];
12
14
  for (const item of items) {
13
15
  if (typeof item === 'string') {
14
16
  if (!item)
15
17
  throw new EmbeddingValidationError('Every embedding input must be a non-empty string');
16
18
  texts.push(item);
17
19
  resolved.push([{ type: 'text', text: item }]);
18
- images.push(0);
20
+ units.push({ images: 0, audio: [], video: [], unmeasured: 0 });
19
21
  continue;
20
22
  }
21
23
  if (!item || typeof item !== 'object' || !Array.isArray(item.parts) || item.parts.length === 0) {
@@ -30,9 +32,60 @@ export function normalizeInputs(input) {
30
32
  .map((part) => part.text)
31
33
  .join('\n'));
32
34
  resolved.push(parts);
33
- images.push(parts.filter((part) => part.type === 'image').length);
35
+ units.push(measure(parts));
34
36
  }
35
- return { texts, ...(media.size ? { parts: resolved } : {}), media, images };
37
+ return { texts, ...(media.size ? { parts: resolved } : {}), media, units };
38
+ }
39
+ function measure(parts) {
40
+ const units = { images: 0, audio: [], video: [], unmeasured: 0 };
41
+ for (const part of parts) {
42
+ if (part.type === 'image')
43
+ units.images += 1;
44
+ if (part.type !== 'audio' && part.type !== 'video')
45
+ continue;
46
+ const read = part.data === undefined ? undefined : readMediaDuration(decodeBase64(part.data));
47
+ if (read)
48
+ units[part.type].push(read.durationMs / 1000);
49
+ else
50
+ units.unmeasured += 1;
51
+ }
52
+ return units;
53
+ }
54
+ export function totalUnits(units) {
55
+ return {
56
+ images: units.reduce((total, unit) => total + unit.images, 0),
57
+ audio: units.flatMap((unit) => unit.audio),
58
+ video: units.flatMap((unit) => unit.video),
59
+ unmeasured: units.reduce((total, unit) => total + unit.unmeasured, 0),
60
+ };
61
+ }
62
+ export function framesOf(seconds, model) {
63
+ const sampling = model.videoFrames ?? { perSecond: 1 };
64
+ const frames = Math.ceil(seconds * sampling.perSecond);
65
+ return sampling.max === undefined ? frames : Math.min(sampling.max, frames);
66
+ }
67
+ export function mediaCost(units, model) {
68
+ if (!model)
69
+ return 0;
70
+ const seconds = units.audio.reduce((total, length) => total + Math.ceil(length), 0);
71
+ const frames = units.video.reduce((total, length) => total + framesOf(length, model), 0);
72
+ return (units.images * (model.costPerImage ?? 0) +
73
+ seconds * (model.costPerAudioSecond ?? 0) +
74
+ frames * (model.costPerVideoFrame ?? 0));
75
+ }
76
+ export function pricesByLength(model) {
77
+ return model?.costPerAudioSecond !== undefined || model?.costPerVideoFrame !== undefined;
78
+ }
79
+ export function assertLengths(inputs, modelName, model) {
80
+ inputs.units.forEach((unit, index) => {
81
+ for (const kind of ['audio', 'video']) {
82
+ const most = kind === 'audio' ? model?.maxAudioSeconds : model?.maxVideoSeconds;
83
+ const longest = Math.max(0, ...unit[kind]);
84
+ if (most !== undefined && longest > most) {
85
+ throw new EmbeddingValidationError(`Input ${index} holds ${longest.toFixed(1)} s of ${kind}, and ${modelName} takes at most ${most} s; split it`);
86
+ }
87
+ }
88
+ });
36
89
  }
37
90
  function toProviderPart(part) {
38
91
  switch (part?.type) {
@@ -63,10 +116,36 @@ function toProviderPart(part) {
63
116
  ? { type: 'document', mimeType: wire.mimeType, url: wire.url }
64
117
  : { type: 'document', mimeType: wire.mimeType, data: wire.data };
65
118
  }
119
+ case 'video':
120
+ return videoPart(part);
66
121
  default:
67
- throw new EmbeddingValidationError(`An embedding part is text, image, audio, or document, not ${JSON.stringify(part?.type)}`);
122
+ throw new EmbeddingValidationError(`An embedding part is text, image, audio, video, or document, not ${JSON.stringify(part?.type)}`);
68
123
  }
69
124
  }
125
+ function videoPart(part) {
126
+ const source = (part.source ?? {});
127
+ const format = typeof source.format === 'string' ? source.format.toLowerCase() : undefined;
128
+ const named = format ? (VIDEO_TYPES[format] ?? `video/${format}`) : undefined;
129
+ if (typeof source.url === 'string' && source.url) {
130
+ const extension = /\.([A-Za-z0-9]+)(?:[?#]|$)/.exec(source.url)?.[1]?.toLowerCase();
131
+ return { type: 'video', mimeType: named ?? (extension && VIDEO_TYPES[extension]) ?? 'video/mp4', url: source.url };
132
+ }
133
+ const data = typeof source.base64 === 'string'
134
+ ? source.base64
135
+ : source.buffer && typeof source.buffer.length === 'number'
136
+ ? encodeBase64(source.buffer)
137
+ : undefined;
138
+ if (!data) {
139
+ throw new EmbeddingValidationError('An embedding video part must hold its content, as bytes, base64, or a URL; read a file on disk first');
140
+ }
141
+ return { type: 'video', mimeType: named ?? 'video/mp4', data };
142
+ }
143
+ const VIDEO_TYPES = {
144
+ mp4: 'video/mp4',
145
+ mov: 'video/quicktime',
146
+ webm: 'video/webm',
147
+ mpeg: 'video/mpeg',
148
+ };
70
149
  function unreadable(kind) {
71
150
  return new EmbeddingValidationError(`An embedding ${kind} part must hold its content, as bytes, base64, or a URL; read a file or a stream first`);
72
151
  }
@@ -16,7 +16,7 @@ export class MockEmbeddingProvider {
16
16
  dimensions: true,
17
17
  encodingFormats: ['float'],
18
18
  inputTypes: ['document', 'query', 'classification', 'clustering'],
19
- inputModalities: ['text', 'image', 'audio', 'document'],
19
+ inputModalities: ['text', 'image', 'audio', 'video', 'document'],
20
20
  truncate: true,
21
21
  ...options.capabilities,
22
22
  },