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.
- package/API_STABILITY.md +93 -0
- package/CHANGELOG.md +208 -1
- package/README.md +11 -6
- package/SECURITY.md +16 -2
- package/dist/adapter-kit/gather.d.ts +76 -0
- package/dist/adapter-kit/gather.js +147 -0
- package/dist/adapter-kit/index.d.ts +1 -0
- package/dist/adapter-kit/index.js +1 -0
- package/dist/adapter-kit/record.d.ts +3 -1
- package/dist/adapter-kit/record.js +20 -5
- package/dist/agent/openapi.d.ts +94 -8
- package/dist/agent/openapi.js +528 -32
- package/dist/cli/adapters.d.ts +4 -3
- package/dist/cli/adapters.js +71 -2
- package/dist/cli/doctor.js +3 -0
- package/dist/cli.js +3 -1
- package/dist/core/config-builder.d.ts +1 -0
- package/dist/core/config-builder.js +1 -0
- package/dist/core/defaults.d.ts +34 -0
- package/dist/core/defaults.js +39 -0
- package/dist/core/secure-stream.js +4 -1
- package/dist/decisions/completion.d.ts +10 -7
- package/dist/decisions/completion.js +82 -8
- package/dist/decisions/core.d.ts +88 -43
- package/dist/decisions/core.js +115 -28
- package/dist/decisions/errors.d.ts +25 -0
- package/dist/decisions/errors.js +12 -0
- package/dist/decisions/index.d.ts +6 -5
- package/dist/decisions/index.js +2 -1
- package/dist/decisions/integrations.d.ts +14 -5
- package/dist/decisions/integrations.js +16 -1
- package/dist/decisions/mock.d.ts +5 -3
- package/dist/decisions/mock.js +4 -1
- package/dist/decisions/pictures.d.ts +43 -0
- package/dist/decisions/pictures.js +103 -0
- package/dist/decisions/typesafe.d.ts +33 -5
- package/dist/decisions/typesafe.js +121 -8
- package/dist/doctor/index.d.ts +6 -9
- package/dist/doctor/index.js +41 -0
- package/dist/embeddings/adapters.d.ts +1 -1
- package/dist/embeddings/adapters.js +3 -3
- package/dist/embeddings/batch-google.d.ts +27 -8
- package/dist/embeddings/batch-google.js +112 -36
- package/dist/embeddings/batch-mock.js +1 -1
- package/dist/embeddings/batch.js +10 -4
- package/dist/embeddings/engine.js +14 -13
- package/dist/embeddings/index.d.ts +1 -1
- package/dist/embeddings/inputs.d.ts +26 -3
- package/dist/embeddings/inputs.js +84 -5
- package/dist/embeddings/mock.js +1 -1
- package/dist/embeddings/models.js +39 -4
- package/dist/loaders/csv.d.ts +23 -1
- package/dist/loaders/csv.js +125 -72
- package/dist/loaders/index.d.ts +7 -2
- package/dist/loaders/input.d.ts +6 -0
- package/dist/loaders/input.js +42 -1
- package/dist/loaders/json.d.ts +14 -3
- package/dist/loaders/json.js +171 -20
- package/dist/loaders/web.d.ts +55 -0
- package/dist/loaders/web.js +20 -1
- package/dist/mcp/client.d.ts +54 -5
- package/dist/mcp/client.js +171 -20
- package/dist/mcp/http.d.ts +45 -0
- package/dist/mcp/http.js +233 -0
- package/dist/mcp/index.d.ts +4 -2
- package/dist/mcp/index.js +3 -1
- package/dist/mcp/protocol.d.ts +107 -0
- package/dist/mcp/sampling.d.ts +41 -0
- package/dist/mcp/sampling.js +52 -0
- package/dist/mcp/server.d.ts +96 -14
- package/dist/mcp/server.js +274 -59
- package/dist/models/data.js +20 -1
- package/dist/operations/adapters.d.ts +1 -0
- package/dist/operations/adapters.js +3 -1
- package/dist/protocols/a2a-grpc.js +10 -3
- package/dist/protocols/a2a-signing.d.ts +65 -0
- package/dist/protocols/a2a-signing.js +133 -0
- package/dist/protocols/a2a.d.ts +14 -0
- package/dist/protocols/a2a.js +60 -5
- package/dist/providers/cohere.js +16 -2
- package/dist/providers/google.js +7 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +35 -27
- package/dist/providers/openai.js +19 -2
- package/dist/rag/pinecone.d.ts +63 -2
- package/dist/rag/pinecone.js +80 -0
- package/dist/realtime/conversation.js +18 -0
- package/dist/realtime/gemini-live.transport.d.ts +28 -3
- package/dist/realtime/gemini-live.transport.js +170 -32
- package/dist/realtime/session.d.ts +2 -0
- package/dist/realtime/session.js +19 -4
- package/dist/realtime/types.d.ts +35 -3
- package/dist/security/output-guard.js +4 -1
- package/dist/server/auth.d.ts +263 -2
- package/dist/server/auth.js +432 -5
- package/dist/server/deployments.d.ts +3 -2
- package/dist/server/deployments.js +6 -1
- package/dist/telephony/index.d.ts +1 -1
- package/dist/telephony/manager.d.ts +8 -1
- package/dist/telephony/manager.js +13 -0
- package/dist/telephony/providers/telnyx.d.ts +18 -5
- package/dist/telephony/providers/telnyx.js +44 -7
- package/dist/telephony/providers/twilio.d.ts +3 -1
- package/dist/telephony/providers/twilio.js +16 -0
- package/dist/tracing/stores.d.ts +2 -0
- package/dist/tracing/stores.js +1 -0
- package/dist/tracing/tracer.d.ts +5 -2
- package/dist/tracing/tracer.js +6 -3
- package/dist/types/embeddings.d.ts +47 -5
- package/dist/types/messages.d.ts +7 -0
- package/dist/types/principal.d.ts +1 -1
- package/dist/types/response.d.ts +5 -0
- package/dist/types/telephony.d.ts +38 -0
- package/dist/types/tracing.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/providers/google.d.ts +44 -0
- package/dist/voice/providers/google.js +215 -0
- package/dist-cjs/adapter-kit/gather.d.ts +76 -0
- package/dist-cjs/adapter-kit/gather.js +152 -0
- package/dist-cjs/adapter-kit/index.d.ts +1 -0
- package/dist-cjs/adapter-kit/index.js +4 -1
- package/dist-cjs/adapter-kit/record.d.ts +3 -1
- package/dist-cjs/adapter-kit/record.js +20 -5
- package/dist-cjs/agent/openapi.d.ts +94 -8
- package/dist-cjs/agent/openapi.js +530 -32
- package/dist-cjs/cli/adapters.d.ts +4 -3
- package/dist-cjs/cli/adapters.js +70 -1
- package/dist-cjs/cli/doctor.js +3 -0
- package/dist-cjs/core/config-builder.d.ts +1 -0
- package/dist-cjs/core/config-builder.js +5 -1
- package/dist-cjs/core/defaults.d.ts +34 -0
- package/dist-cjs/core/defaults.js +44 -0
- package/dist-cjs/core/secure-stream.js +4 -1
- package/dist-cjs/decisions/completion.d.ts +10 -7
- package/dist-cjs/decisions/completion.js +81 -7
- package/dist-cjs/decisions/core.d.ts +88 -43
- package/dist-cjs/decisions/core.js +134 -46
- package/dist-cjs/decisions/errors.d.ts +25 -0
- package/dist-cjs/decisions/errors.js +16 -0
- package/dist-cjs/decisions/index.d.ts +6 -5
- package/dist-cjs/decisions/index.js +5 -1
- package/dist-cjs/decisions/integrations.d.ts +14 -5
- package/dist-cjs/decisions/integrations.js +16 -1
- package/dist-cjs/decisions/mock.d.ts +5 -3
- package/dist-cjs/decisions/mock.js +4 -1
- package/dist-cjs/decisions/pictures.d.ts +43 -0
- package/dist-cjs/decisions/pictures.js +109 -0
- package/dist-cjs/decisions/typesafe.d.ts +33 -5
- package/dist-cjs/decisions/typesafe.js +126 -13
- package/dist-cjs/doctor/index.d.ts +6 -9
- package/dist-cjs/doctor/index.js +41 -0
- package/dist-cjs/embeddings/adapters.d.ts +1 -1
- package/dist-cjs/embeddings/adapters.js +3 -3
- package/dist-cjs/embeddings/batch-google.d.ts +27 -8
- package/dist-cjs/embeddings/batch-google.js +112 -36
- package/dist-cjs/embeddings/batch-mock.js +1 -1
- package/dist-cjs/embeddings/batch.js +9 -3
- package/dist-cjs/embeddings/engine.js +13 -12
- package/dist-cjs/embeddings/index.d.ts +1 -1
- package/dist-cjs/embeddings/inputs.d.ts +26 -3
- package/dist-cjs/embeddings/inputs.js +89 -5
- package/dist-cjs/embeddings/mock.js +1 -1
- package/dist-cjs/embeddings/models.js +39 -4
- package/dist-cjs/loaders/csv.d.ts +23 -1
- package/dist-cjs/loaders/csv.js +126 -71
- package/dist-cjs/loaders/index.d.ts +7 -2
- package/dist-cjs/loaders/input.d.ts +6 -0
- package/dist-cjs/loaders/input.js +43 -1
- package/dist-cjs/loaders/json.d.ts +14 -3
- package/dist-cjs/loaders/json.js +170 -19
- package/dist-cjs/loaders/web.d.ts +55 -0
- package/dist-cjs/loaders/web.js +21 -1
- package/dist-cjs/mcp/client.d.ts +54 -5
- package/dist-cjs/mcp/client.js +171 -20
- package/dist-cjs/mcp/http.d.ts +45 -0
- package/dist-cjs/mcp/http.js +236 -0
- package/dist-cjs/mcp/index.d.ts +4 -2
- package/dist-cjs/mcp/index.js +5 -1
- package/dist-cjs/mcp/protocol.d.ts +107 -0
- package/dist-cjs/mcp/sampling.d.ts +41 -0
- package/dist-cjs/mcp/sampling.js +55 -0
- package/dist-cjs/mcp/server.d.ts +96 -14
- package/dist-cjs/mcp/server.js +273 -58
- package/dist-cjs/models/data.js +20 -1
- package/dist-cjs/operations/adapters.d.ts +1 -0
- package/dist-cjs/operations/adapters.js +3 -1
- package/dist-cjs/protocols/a2a-grpc.js +10 -3
- package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
- package/dist-cjs/protocols/a2a-signing.js +140 -0
- package/dist-cjs/protocols/a2a.d.ts +14 -0
- package/dist-cjs/protocols/a2a.js +65 -6
- package/dist-cjs/providers/cohere.js +16 -2
- package/dist-cjs/providers/google.js +7 -1
- package/dist-cjs/providers/ollama.d.ts +1 -1
- package/dist-cjs/providers/ollama.js +35 -27
- package/dist-cjs/providers/openai.js +19 -2
- package/dist-cjs/rag/pinecone.d.ts +63 -2
- package/dist-cjs/rag/pinecone.js +81 -0
- package/dist-cjs/realtime/conversation.js +18 -0
- package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
- package/dist-cjs/realtime/gemini-live.transport.js +170 -32
- package/dist-cjs/realtime/session.d.ts +2 -0
- package/dist-cjs/realtime/session.js +19 -4
- package/dist-cjs/realtime/types.d.ts +35 -3
- package/dist-cjs/security/output-guard.js +4 -1
- package/dist-cjs/server/auth.d.ts +263 -2
- package/dist-cjs/server/auth.js +437 -5
- package/dist-cjs/server/deployments.d.ts +3 -2
- package/dist-cjs/server/deployments.js +6 -1
- package/dist-cjs/telephony/index.d.ts +1 -1
- package/dist-cjs/telephony/manager.d.ts +8 -1
- package/dist-cjs/telephony/manager.js +13 -0
- package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
- package/dist-cjs/telephony/providers/telnyx.js +44 -7
- package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
- package/dist-cjs/telephony/providers/twilio.js +16 -0
- package/dist-cjs/tracing/stores.d.ts +2 -0
- package/dist-cjs/tracing/stores.js +1 -0
- package/dist-cjs/tracing/tracer.d.ts +5 -2
- package/dist-cjs/tracing/tracer.js +6 -3
- package/dist-cjs/types/embeddings.d.ts +47 -5
- package/dist-cjs/types/messages.d.ts +7 -0
- package/dist-cjs/types/principal.d.ts +1 -1
- package/dist-cjs/types/response.d.ts +5 -0
- package/dist-cjs/types/telephony.d.ts +38 -0
- package/dist-cjs/types/tracing.d.ts +6 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/voice/providers/google.d.ts +44 -0
- package/dist-cjs/voice/providers/google.js +252 -0
- package/llms.txt +1 -1
- 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
|
-
/**
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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:
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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 {
|
|
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?.
|
|
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
|
|
70
|
-
const
|
|
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
|
|
116
|
-
const
|
|
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
|
|
177
|
+
const response = await (this.options.fetch ?? fetch)(url, {
|
|
119
178
|
...init,
|
|
120
179
|
headers: {
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
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
|
};
|
package/dist/embeddings/batch.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
232
|
-
const amount = (cost.amount + (notes.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
411
|
-
|
|
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 +
|
|
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
|
|
533
|
-
const
|
|
534
|
-
if (!
|
|
534
|
+
function withMediaCost(cost, media, model) {
|
|
535
|
+
const extra = mediaCost(media, model);
|
|
536
|
+
if (!extra)
|
|
535
537
|
return cost;
|
|
536
|
-
|
|
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
|
-
/**
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
units.push(measure(parts));
|
|
34
36
|
}
|
|
35
|
-
return { texts, ...(media.size ? { parts: resolved } : {}), media,
|
|
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
|
}
|
package/dist/embeddings/mock.js
CHANGED
|
@@ -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
|
},
|