nexus-ai-pro 2.7.0 → 2.9.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 +119 -3
- package/CHANGELOG.md +241 -1
- package/README.md +21 -3
- package/SECURITY.md +41 -1
- package/dist/batch/manager.js +16 -33
- package/dist/batch/openai-client.d.ts +83 -0
- package/dist/batch/openai-client.js +120 -0
- package/dist/batch/openai.d.ts +12 -21
- package/dist/batch/openai.js +33 -125
- package/dist/batch/poll.d.ts +22 -0
- package/dist/batch/poll.js +30 -0
- package/dist/cli/models.d.ts +36 -0
- package/dist/cli/models.js +170 -0
- package/dist/cli.js +11 -1
- package/dist/deep-agent/helpers.d.ts +4 -0
- package/dist/deep-agent/helpers.js +56 -4
- package/dist/doctor/index.d.ts +16 -1
- package/dist/doctor/index.js +10 -5
- package/dist/embeddings/adapters.d.ts +2 -2
- package/dist/embeddings/adapters.js +24 -13
- package/dist/embeddings/batch-google.d.ts +40 -0
- package/dist/embeddings/batch-google.js +128 -0
- package/dist/embeddings/batch-mock.d.ts +45 -0
- package/dist/embeddings/batch-mock.js +71 -0
- package/dist/embeddings/batch-openai.d.ts +23 -0
- package/dist/embeddings/batch-openai.js +55 -0
- package/dist/embeddings/batch.d.ts +230 -0
- package/dist/embeddings/batch.js +282 -0
- package/dist/embeddings/cache.d.ts +81 -0
- package/dist/embeddings/cache.js +116 -0
- package/dist/embeddings/engine.d.ts +7 -0
- package/dist/embeddings/engine.js +110 -68
- package/dist/embeddings/google-wire.d.ts +32 -0
- package/dist/embeddings/google-wire.js +33 -0
- package/dist/embeddings/index.d.ts +1 -1
- package/dist/embeddings/inputs.d.ts +30 -0
- package/dist/embeddings/inputs.js +103 -0
- package/dist/embeddings/mock.d.ts +7 -1
- package/dist/embeddings/mock.js +8 -2
- package/dist/embeddings/models.js +21 -0
- package/dist/evaluate/online.d.ts +5 -0
- package/dist/evaluate/online.js +74 -5
- package/dist/images/asset-stores.d.ts +47 -1
- package/dist/images/asset-stores.js +135 -2
- package/dist/images/asset-support.d.ts +94 -1
- package/dist/images/asset-support.js +159 -3
- package/dist/images/assets.d.ts +10 -3
- package/dist/images/assets.js +15 -2
- package/dist/lifecycle/budget.d.ts +60 -3
- package/dist/lifecycle/budget.js +147 -3
- package/dist/lifecycle/index.d.ts +2 -2
- package/dist/lifecycle/reservations.d.ts +46 -0
- package/dist/lifecycle/reservations.js +67 -0
- package/dist/media/index.d.ts +244 -0
- package/dist/media/index.js +262 -0
- package/dist/media/mistral.d.ts +34 -0
- package/dist/media/mistral.js +90 -0
- package/dist/media/mock.d.ts +36 -0
- package/dist/media/mock.js +45 -0
- package/dist/media/rag.d.ts +216 -0
- package/dist/media/rag.js +340 -0
- package/dist/models/data.d.ts +87 -0
- package/dist/models/data.js +235 -0
- package/dist/models/registry.d.ts +2 -1
- package/dist/models/registry.js +3 -2
- package/dist/postgres/embedding-cache.d.ts +29 -0
- package/dist/postgres/embedding-cache.js +58 -0
- package/dist/postgres/tenancy.d.ts +26 -1
- package/dist/postgres/tenancy.js +116 -1
- package/dist/postgres/traces.d.ts +2 -1
- package/dist/postgres/traces.js +13 -1
- package/dist/rag/chroma.d.ts +13 -1
- package/dist/rag/chroma.js +36 -13
- package/dist/rag/pinecone.js +2 -2
- package/dist/rag/pipeline.d.ts +17 -2
- package/dist/rag/pipeline.js +95 -14
- package/dist/rag/qdrant.js +2 -2
- package/dist/rag/redis.d.ts +2 -1
- package/dist/rag/redis.js +56 -18
- package/dist/rag/vector-helpers.d.ts +5 -0
- package/dist/rag/vector-helpers.js +7 -0
- package/dist/rag/weaviate.d.ts +15 -1
- package/dist/rag/weaviate.js +75 -12
- package/dist/server/tenancy.d.ts +33 -2
- package/dist/server/tenancy.js +162 -0
- package/dist/sqlite/embedding-cache.d.ts +34 -0
- package/dist/sqlite/embedding-cache.js +61 -0
- package/dist/tracing/index.d.ts +2 -2
- package/dist/tracing/index.js +1 -1
- package/dist/tracing/query.d.ts +5 -1
- package/dist/tracing/query.js +14 -1
- package/dist/tracing/stores.d.ts +2 -2
- package/dist/tracing/stores.js +2 -2
- package/dist/types/config.d.ts +5 -0
- package/dist/types/embeddings.d.ts +87 -5
- package/dist/types/images.d.ts +2 -2
- package/dist/types/lifecycle.d.ts +92 -1
- package/dist/types/tracing.d.ts +20 -1
- package/dist/utils/redis-command.d.ts +15 -0
- package/dist/utils/redis-command.js +5 -0
- package/dist/utils/sha256.d.ts +9 -0
- package/dist/utils/sha256.js +6 -0
- package/dist/utils/vector-bytes.d.ts +11 -0
- package/dist/utils/vector-bytes.js +18 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/video/errors.d.ts +37 -0
- package/dist/video/errors.js +22 -0
- package/dist/video/google.d.ts +52 -0
- package/dist/video/google.js +248 -0
- package/dist/video/index.d.ts +426 -0
- package/dist/video/index.js +666 -0
- package/dist/video/mock.d.ts +62 -0
- package/dist/video/mock.js +97 -0
- package/dist/video/runway.d.ts +47 -0
- package/dist/video/runway.js +241 -0
- package/dist-cjs/batch/manager.js +16 -33
- package/dist-cjs/batch/openai-client.d.ts +83 -0
- package/dist-cjs/batch/openai-client.js +125 -0
- package/dist-cjs/batch/openai.d.ts +12 -21
- package/dist-cjs/batch/openai.js +32 -124
- package/dist-cjs/batch/poll.d.ts +22 -0
- package/dist-cjs/batch/poll.js +33 -0
- package/dist-cjs/cli/models.d.ts +36 -0
- package/dist-cjs/cli/models.js +175 -0
- package/dist-cjs/deep-agent/helpers.d.ts +4 -0
- package/dist-cjs/deep-agent/helpers.js +56 -4
- package/dist-cjs/doctor/index.d.ts +16 -1
- package/dist-cjs/doctor/index.js +10 -5
- package/dist-cjs/embeddings/adapters.d.ts +2 -2
- package/dist-cjs/embeddings/adapters.js +23 -12
- package/dist-cjs/embeddings/batch-google.d.ts +40 -0
- package/dist-cjs/embeddings/batch-google.js +132 -0
- package/dist-cjs/embeddings/batch-mock.d.ts +45 -0
- package/dist-cjs/embeddings/batch-mock.js +75 -0
- package/dist-cjs/embeddings/batch-openai.d.ts +23 -0
- package/dist-cjs/embeddings/batch-openai.js +59 -0
- package/dist-cjs/embeddings/batch.d.ts +230 -0
- package/dist-cjs/embeddings/batch.js +320 -0
- package/dist-cjs/embeddings/cache.d.ts +81 -0
- package/dist-cjs/embeddings/cache.js +125 -0
- package/dist-cjs/embeddings/engine.d.ts +7 -0
- package/dist-cjs/embeddings/engine.js +143 -68
- package/dist-cjs/embeddings/google-wire.d.ts +32 -0
- package/dist-cjs/embeddings/google-wire.js +38 -0
- package/dist-cjs/embeddings/index.d.ts +1 -1
- package/dist-cjs/embeddings/inputs.d.ts +30 -0
- package/dist-cjs/embeddings/inputs.js +141 -0
- package/dist-cjs/embeddings/mock.d.ts +7 -1
- package/dist-cjs/embeddings/mock.js +8 -2
- package/dist-cjs/embeddings/models.js +21 -0
- package/dist-cjs/evaluate/online.d.ts +5 -0
- package/dist-cjs/evaluate/online.js +74 -5
- package/dist-cjs/images/asset-stores.d.ts +47 -1
- package/dist-cjs/images/asset-stores.js +133 -0
- package/dist-cjs/images/asset-support.d.ts +94 -1
- package/dist-cjs/images/asset-support.js +169 -3
- package/dist-cjs/images/assets.d.ts +10 -3
- package/dist-cjs/images/assets.js +17 -1
- package/dist-cjs/lifecycle/budget.d.ts +60 -3
- package/dist-cjs/lifecycle/budget.js +147 -3
- package/dist-cjs/lifecycle/index.d.ts +2 -2
- package/dist-cjs/lifecycle/reservations.d.ts +46 -0
- package/dist-cjs/lifecycle/reservations.js +71 -0
- package/dist-cjs/media/index.d.ts +244 -0
- package/dist-cjs/media/index.js +269 -0
- package/dist-cjs/media/mistral.d.ts +34 -0
- package/dist-cjs/media/mistral.js +94 -0
- package/dist-cjs/media/mock.d.ts +36 -0
- package/dist-cjs/media/mock.js +49 -0
- package/dist-cjs/media/rag.d.ts +216 -0
- package/dist-cjs/media/rag.js +344 -0
- package/dist-cjs/models/data.d.ts +87 -0
- package/dist-cjs/models/data.js +245 -0
- package/dist-cjs/models/registry.d.ts +2 -1
- package/dist-cjs/models/registry.js +3 -2
- package/dist-cjs/postgres/embedding-cache.d.ts +29 -0
- package/dist-cjs/postgres/embedding-cache.js +96 -0
- package/dist-cjs/postgres/tenancy.d.ts +26 -1
- package/dist-cjs/postgres/tenancy.js +115 -0
- package/dist-cjs/postgres/traces.d.ts +2 -1
- package/dist-cjs/postgres/traces.js +13 -1
- package/dist-cjs/rag/chroma.d.ts +13 -1
- package/dist-cjs/rag/chroma.js +35 -12
- package/dist-cjs/rag/pinecone.js +1 -1
- package/dist-cjs/rag/pipeline.d.ts +17 -2
- package/dist-cjs/rag/pipeline.js +95 -14
- package/dist-cjs/rag/qdrant.js +1 -1
- package/dist-cjs/rag/redis.d.ts +2 -1
- package/dist-cjs/rag/redis.js +55 -17
- package/dist-cjs/rag/vector-helpers.d.ts +5 -0
- package/dist-cjs/rag/vector-helpers.js +8 -0
- package/dist-cjs/rag/weaviate.d.ts +15 -1
- package/dist-cjs/rag/weaviate.js +74 -11
- package/dist-cjs/server/tenancy.d.ts +33 -2
- package/dist-cjs/server/tenancy.js +162 -0
- package/dist-cjs/sqlite/embedding-cache.d.ts +34 -0
- package/dist-cjs/sqlite/embedding-cache.js +99 -0
- package/dist-cjs/tracing/index.d.ts +2 -2
- package/dist-cjs/tracing/index.js +3 -1
- package/dist-cjs/tracing/query.d.ts +5 -1
- package/dist-cjs/tracing/query.js +16 -1
- package/dist-cjs/tracing/stores.d.ts +2 -2
- package/dist-cjs/tracing/stores.js +3 -1
- package/dist-cjs/types/config.d.ts +5 -0
- package/dist-cjs/types/embeddings.d.ts +87 -5
- package/dist-cjs/types/images.d.ts +2 -2
- package/dist-cjs/types/lifecycle.d.ts +92 -1
- package/dist-cjs/types/tracing.d.ts +20 -1
- package/dist-cjs/utils/redis-command.d.ts +15 -0
- package/dist-cjs/utils/redis-command.js +8 -0
- package/dist-cjs/utils/sha256.d.ts +9 -0
- package/dist-cjs/utils/sha256.js +7 -0
- package/dist-cjs/utils/vector-bytes.d.ts +11 -0
- package/dist-cjs/utils/vector-bytes.js +22 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/video/errors.d.ts +37 -0
- package/dist-cjs/video/errors.js +27 -0
- package/dist-cjs/video/google.d.ts +52 -0
- package/dist-cjs/video/google.js +252 -0
- package/dist-cjs/video/index.d.ts +426 -0
- package/dist-cjs/video/index.js +708 -0
- package/dist-cjs/video/mock.d.ts +62 -0
- package/dist-cjs/video/mock.js +101 -0
- package/dist-cjs/video/runway.d.ts +47 -0
- package/dist-cjs/video/runway.js +245 -0
- package/llms.txt +3 -1
- package/package.json +215 -4
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { BatchJobRef, BatchJobState, BatchProviderCallContext } from '../types/batch.js';
|
|
2
|
+
/** What every OpenAI batch adapter needs to reach the API. */
|
|
3
|
+
export interface OpenAIBatchClientOptions {
|
|
4
|
+
/** OpenAI API key. */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** API base URL. Defaults to `https://api.openai.com/v1`. */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/** Sent as the `OpenAI-Organization` header. */
|
|
9
|
+
organization?: string;
|
|
10
|
+
/** Headers added to every request. */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
/** Replaces the global `fetch`. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
/** OpenAI's batch object, as far as the adapters read it. */
|
|
16
|
+
export interface OpenAIBatchPayload {
|
|
17
|
+
id?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
output_file_id?: string | null;
|
|
20
|
+
error_file_id?: string | null;
|
|
21
|
+
created_at?: number;
|
|
22
|
+
completed_at?: number;
|
|
23
|
+
expires_at?: number;
|
|
24
|
+
request_counts?: {
|
|
25
|
+
total?: number;
|
|
26
|
+
completed?: number;
|
|
27
|
+
failed?: number;
|
|
28
|
+
};
|
|
29
|
+
errors?: {
|
|
30
|
+
data?: Array<{
|
|
31
|
+
message?: string;
|
|
32
|
+
code?: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** One line of a batch's output or error file. */
|
|
37
|
+
export interface OpenAIBatchLine {
|
|
38
|
+
custom_id?: string;
|
|
39
|
+
response?: {
|
|
40
|
+
status_code?: number;
|
|
41
|
+
body?: Record<string, unknown>;
|
|
42
|
+
};
|
|
43
|
+
error?: {
|
|
44
|
+
message?: string;
|
|
45
|
+
code?: string;
|
|
46
|
+
} | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* OpenAI's Batch API wire protocol, shared by the completion and embedding adapters: a JSONL upload,
|
|
50
|
+
* a batch pointing at it, and JSONL downloads of what succeeded and what failed.
|
|
51
|
+
*/
|
|
52
|
+
export declare class OpenAIBatchClient {
|
|
53
|
+
private readonly options;
|
|
54
|
+
constructor(options: OpenAIBatchClientOptions);
|
|
55
|
+
/** Uploads the lines as a JSONL file and creates a batch over them. */
|
|
56
|
+
create(lines: readonly unknown[], batch: {
|
|
57
|
+
endpoint: string;
|
|
58
|
+
completionWindow?: string;
|
|
59
|
+
metadata?: Record<string, string>;
|
|
60
|
+
}, context: BatchProviderCallContext): Promise<{
|
|
61
|
+
batch: OpenAIBatchPayload;
|
|
62
|
+
fileId: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Reads a batch. */
|
|
65
|
+
read(id: string, context: BatchProviderCallContext): Promise<OpenAIBatchPayload>;
|
|
66
|
+
/** Cancels a batch. */
|
|
67
|
+
cancel(id: string, context: BatchProviderCallContext): Promise<OpenAIBatchPayload>;
|
|
68
|
+
/**
|
|
69
|
+
* Every line a finished batch wrote: the output file holds what succeeded and the error file what
|
|
70
|
+
* failed, so both are read, or a failed request would be missing from the results altogether.
|
|
71
|
+
*/
|
|
72
|
+
lines(id: string, context: BatchProviderCallContext): Promise<OpenAIBatchLine[]>;
|
|
73
|
+
/** A batch as the neutral state. */
|
|
74
|
+
state(ref: BatchJobRef, batch: OpenAIBatchPayload): BatchJobState;
|
|
75
|
+
private json;
|
|
76
|
+
private send;
|
|
77
|
+
}
|
|
78
|
+
/** A failed line's reason: the error the line carries, or the error body of its response. */
|
|
79
|
+
export declare function lineError(line: OpenAIBatchLine): {
|
|
80
|
+
message: string;
|
|
81
|
+
code?: string;
|
|
82
|
+
status?: number;
|
|
83
|
+
} | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { BatchProviderResponseError } from './errors.js';
|
|
2
|
+
const STATUS_MAP = {
|
|
3
|
+
validating: 'validating',
|
|
4
|
+
in_progress: 'in_progress',
|
|
5
|
+
finalizing: 'finalizing',
|
|
6
|
+
completed: 'completed',
|
|
7
|
+
failed: 'failed',
|
|
8
|
+
expired: 'expired',
|
|
9
|
+
cancelling: 'cancelling',
|
|
10
|
+
cancelled: 'cancelled',
|
|
11
|
+
};
|
|
12
|
+
export class OpenAIBatchClient {
|
|
13
|
+
options;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async create(lines, batch, context) {
|
|
18
|
+
const form = new FormData();
|
|
19
|
+
form.append('purpose', 'batch');
|
|
20
|
+
form.append('file', new Blob([lines.map((line) => JSON.stringify(line)).join('\n')], { type: 'application/jsonl' }), 'batch.jsonl');
|
|
21
|
+
const file = (await this.json('/files', { method: 'POST', body: form }, context));
|
|
22
|
+
if (!file.id)
|
|
23
|
+
throw new BatchProviderResponseError('openai', 'the file upload returned no id');
|
|
24
|
+
const created = (await this.json('/batches', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'content-type': 'application/json' },
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
input_file_id: file.id,
|
|
29
|
+
endpoint: batch.endpoint,
|
|
30
|
+
completion_window: batch.completionWindow ?? '24h',
|
|
31
|
+
metadata: batch.metadata,
|
|
32
|
+
}),
|
|
33
|
+
}, context));
|
|
34
|
+
if (!created.id)
|
|
35
|
+
throw new BatchProviderResponseError('openai', 'the batch response returned no id');
|
|
36
|
+
return { batch: created, fileId: file.id };
|
|
37
|
+
}
|
|
38
|
+
read(id, context) {
|
|
39
|
+
return this.json(`/batches/${encodeURIComponent(id)}`, {}, context);
|
|
40
|
+
}
|
|
41
|
+
cancel(id, context) {
|
|
42
|
+
return this.json(`/batches/${encodeURIComponent(id)}/cancel`, { method: 'POST' }, context);
|
|
43
|
+
}
|
|
44
|
+
async lines(id, context) {
|
|
45
|
+
const batch = await this.read(id, context);
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (const fileId of [batch.output_file_id, batch.error_file_id]) {
|
|
48
|
+
if (!fileId)
|
|
49
|
+
continue;
|
|
50
|
+
const response = await this.send(`/files/${encodeURIComponent(fileId)}/content`, {}, context);
|
|
51
|
+
for (const line of (await response.text()).split('\n')) {
|
|
52
|
+
if (!line.trim())
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
lines.push(JSON.parse(line));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new BatchProviderResponseError('openai', 'an output line was not valid JSON', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
|
64
|
+
state(ref, batch) {
|
|
65
|
+
const counts = batch.request_counts;
|
|
66
|
+
return {
|
|
67
|
+
ref,
|
|
68
|
+
status: STATUS_MAP[batch.status ?? ''] ?? 'in_progress',
|
|
69
|
+
counts: counts
|
|
70
|
+
? { total: counts.total ?? 0, completed: counts.completed ?? 0, failed: counts.failed ?? 0 }
|
|
71
|
+
: undefined,
|
|
72
|
+
createdAt: batch.created_at ? new Date(batch.created_at * 1000).toISOString() : undefined,
|
|
73
|
+
completedAt: batch.completed_at ? new Date(batch.completed_at * 1000).toISOString() : undefined,
|
|
74
|
+
expiresAt: batch.expires_at ? new Date(batch.expires_at * 1000).toISOString() : undefined,
|
|
75
|
+
error: batch.errors?.data?.[0]
|
|
76
|
+
? { message: batch.errors.data[0].message ?? 'batch failed', code: batch.errors.data[0].code }
|
|
77
|
+
: undefined,
|
|
78
|
+
raw: batch,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async json(path, init, context) {
|
|
82
|
+
return (await this.send(path, init, context)).json();
|
|
83
|
+
}
|
|
84
|
+
async send(path, init, context) {
|
|
85
|
+
const fetchImpl = this.options.fetch ?? fetch;
|
|
86
|
+
const baseUrl = (this.options.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
|
|
87
|
+
const headers = {
|
|
88
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
89
|
+
...(this.options.organization ? { 'openai-organization': this.options.organization } : {}),
|
|
90
|
+
...this.options.headers,
|
|
91
|
+
...(init.headers ?? {}),
|
|
92
|
+
};
|
|
93
|
+
const response = await fetchImpl(`${baseUrl}${path}`, { ...init, headers, signal: context.signal });
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new BatchProviderResponseError('openai', `${path} failed: ${response.status} ${await safeText(response)}`);
|
|
96
|
+
}
|
|
97
|
+
return response;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function lineError(line) {
|
|
101
|
+
if (line.error)
|
|
102
|
+
return { message: line.error.message ?? 'batch item failed', code: line.error.code };
|
|
103
|
+
const status = line.response?.status_code ?? 200;
|
|
104
|
+
if (status < 400 && line.response?.body)
|
|
105
|
+
return undefined;
|
|
106
|
+
const body = line.response?.body;
|
|
107
|
+
return {
|
|
108
|
+
message: body?.error?.message ?? `batch item failed with status ${status}`,
|
|
109
|
+
...(body?.error?.code ? { code: body.error.code } : {}),
|
|
110
|
+
status,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function safeText(response) {
|
|
114
|
+
try {
|
|
115
|
+
return (await response.text()).slice(0, 500);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/batch/openai.d.ts
CHANGED
|
@@ -1,42 +1,33 @@
|
|
|
1
1
|
import type { BatchJobRef, BatchJobState, BatchOutputItem, BatchProvider, BatchProviderCallContext, BatchProviderInfo, BatchSubmitRequest } from '../types/batch.js';
|
|
2
|
+
import { type OpenAIBatchClientOptions } from './openai-client.js';
|
|
2
3
|
/** Options for the OpenAI batch provider. */
|
|
3
|
-
export interface OpenAIBatchProviderOptions {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
organization?: string;
|
|
10
|
-
/** Headers added to every request. */
|
|
11
|
-
headers?: Record<string, string>;
|
|
12
|
-
/** Replaces the global `fetch`. */
|
|
13
|
-
fetch?: typeof fetch;
|
|
14
|
-
/** Endpoint every item targets. Defaults to chat completions. */
|
|
4
|
+
export interface OpenAIBatchProviderOptions extends OpenAIBatchClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Endpoint every item targets. Defaults to chat completions. Embeddings are batched by
|
|
7
|
+
* `OpenAIEmbeddingBatchProvider`, on `nexus-ai-pro/embeddings/batch/openai`; this adapter refuses
|
|
8
|
+
* `/v1/embeddings`, since its items are completions.
|
|
9
|
+
*/
|
|
15
10
|
endpoint?: '/v1/chat/completions' | '/v1/embeddings' | (string & {});
|
|
16
11
|
}
|
|
17
12
|
/**
|
|
18
13
|
* OpenAI's Batch API: half price, 24-hour window.
|
|
19
14
|
*
|
|
20
|
-
* The wire protocol is a JSONL upload, a batch pointing at it, then
|
|
21
|
-
* different content types across four endpoints, which is why this adapter
|
|
22
|
-
* synchronous one.
|
|
15
|
+
* The wire protocol is a JSONL upload, a batch pointing at it, then JSONL downloads of what succeeded
|
|
16
|
+
* and what failed — three different content types across four endpoints, which is why this adapter
|
|
17
|
+
* is larger than a synchronous one.
|
|
23
18
|
*/
|
|
24
19
|
export declare class OpenAIBatchProvider implements BatchProvider {
|
|
25
20
|
private readonly options;
|
|
26
21
|
/** Always `openai`, with its batch limits. */
|
|
27
22
|
readonly info: BatchProviderInfo;
|
|
23
|
+
private readonly client;
|
|
28
24
|
constructor(options: OpenAIBatchProviderOptions);
|
|
29
25
|
/** Uploads the items as JSONL and creates a batch over them. */
|
|
30
26
|
submit(request: BatchSubmitRequest, context: BatchProviderCallContext): Promise<BatchJobRef>;
|
|
31
27
|
/** Reads a batch's status and counts. */
|
|
32
28
|
poll(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
|
|
33
|
-
/** Downloads a finished batch's results. Empty until
|
|
29
|
+
/** Downloads a finished batch's results: what succeeded and what failed. Empty until they exist. */
|
|
34
30
|
results(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchOutputItem[]>;
|
|
35
31
|
/** Cancels a batch. Items already processed keep their results. */
|
|
36
32
|
cancel(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
|
|
37
|
-
private toState;
|
|
38
|
-
private toOutputItem;
|
|
39
|
-
private request;
|
|
40
|
-
private requestText;
|
|
41
|
-
private send;
|
|
42
33
|
}
|
package/dist/batch/openai.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { buildMeta } from '../core/usage.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
validating: 'validating',
|
|
5
|
-
in_progress: 'in_progress',
|
|
6
|
-
finalizing: 'finalizing',
|
|
7
|
-
completed: 'completed',
|
|
8
|
-
failed: 'failed',
|
|
9
|
-
expired: 'expired',
|
|
10
|
-
cancelling: 'cancelling',
|
|
11
|
-
cancelled: 'cancelled',
|
|
12
|
-
};
|
|
2
|
+
import { BatchCapabilityError } from './errors.js';
|
|
3
|
+
import { lineError, OpenAIBatchClient } from './openai-client.js';
|
|
13
4
|
export class OpenAIBatchProvider {
|
|
14
5
|
options;
|
|
15
6
|
info = {
|
|
@@ -22,13 +13,17 @@ export class OpenAIBatchProvider {
|
|
|
22
13
|
discount: 0.5,
|
|
23
14
|
},
|
|
24
15
|
};
|
|
16
|
+
client;
|
|
25
17
|
constructor(options) {
|
|
26
18
|
this.options = options;
|
|
19
|
+
this.client = new OpenAIBatchClient(options);
|
|
27
20
|
}
|
|
28
21
|
async submit(request, context) {
|
|
29
22
|
const endpoint = this.options.endpoint ?? '/v1/chat/completions';
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
if (endpoint === '/v1/embeddings') {
|
|
24
|
+
throw new BatchCapabilityError('openai', 'endpoint', 'OpenAIBatchProvider batches completions; batch embeddings with OpenAIEmbeddingBatchProvider from nexus-ai-pro/embeddings/batch/openai');
|
|
25
|
+
}
|
|
26
|
+
const { batch, fileId } = await this.client.create(request.items.map((item) => ({
|
|
32
27
|
custom_id: item.customId,
|
|
33
28
|
method: 'POST',
|
|
34
29
|
url: endpoint,
|
|
@@ -38,124 +33,37 @@ export class OpenAIBatchProvider {
|
|
|
38
33
|
max_tokens: item.request.maxTokens,
|
|
39
34
|
temperature: item.request.temperature,
|
|
40
35
|
},
|
|
41
|
-
}))
|
|
42
|
-
|
|
43
|
-
const form = new FormData();
|
|
44
|
-
form.append('purpose', 'batch');
|
|
45
|
-
form.append('file', new Blob([jsonl], { type: 'application/jsonl' }), 'batch.jsonl');
|
|
46
|
-
const file = (await this.request('/files', { method: 'POST', body: form }, context));
|
|
47
|
-
if (!file.id)
|
|
48
|
-
throw new BatchProviderResponseError('openai', 'the file upload returned no id');
|
|
49
|
-
const batch = (await this.request('/batches', {
|
|
50
|
-
method: 'POST',
|
|
51
|
-
headers: { 'content-type': 'application/json' },
|
|
52
|
-
body: JSON.stringify({
|
|
53
|
-
input_file_id: file.id,
|
|
54
|
-
endpoint,
|
|
55
|
-
completion_window: request.completionWindow ?? '24h',
|
|
56
|
-
metadata: request.metadata,
|
|
57
|
-
}),
|
|
58
|
-
}, context));
|
|
59
|
-
if (!batch.id)
|
|
60
|
-
throw new BatchProviderResponseError('openai', 'the batch response returned no id');
|
|
61
|
-
return { id: batch.id, provider: 'openai', metadata: { inputFileId: file.id, endpoint } };
|
|
36
|
+
})), { endpoint, completionWindow: request.completionWindow, metadata: request.metadata }, context);
|
|
37
|
+
return { id: batch.id, provider: 'openai', metadata: { inputFileId: fileId, endpoint } };
|
|
62
38
|
}
|
|
63
39
|
async poll(ref, context) {
|
|
64
|
-
|
|
65
|
-
return this.toState(ref, batch);
|
|
40
|
+
return this.client.state(ref, await this.client.read(ref.id, context));
|
|
66
41
|
}
|
|
67
42
|
async results(ref, context) {
|
|
68
|
-
|
|
69
|
-
if (!batch.output_file_id)
|
|
70
|
-
return [];
|
|
71
|
-
const body = await this.requestText(`/files/${encodeURIComponent(batch.output_file_id)}/content`, context);
|
|
72
|
-
return body
|
|
73
|
-
.split('\n')
|
|
74
|
-
.filter((line) => line.trim().length > 0)
|
|
75
|
-
.map((line) => this.toOutputItem(line));
|
|
43
|
+
return (await this.client.lines(ref.id, context)).map(toOutputItem);
|
|
76
44
|
}
|
|
77
45
|
async cancel(ref, context) {
|
|
78
|
-
|
|
79
|
-
return this.toState(ref, batch);
|
|
80
|
-
}
|
|
81
|
-
toState(ref, batch) {
|
|
82
|
-
const counts = batch.request_counts;
|
|
83
|
-
return {
|
|
84
|
-
ref,
|
|
85
|
-
status: STATUS_MAP[batch.status ?? ''] ?? 'in_progress',
|
|
86
|
-
counts: counts
|
|
87
|
-
? { total: counts.total ?? 0, completed: counts.completed ?? 0, failed: counts.failed ?? 0 }
|
|
88
|
-
: undefined,
|
|
89
|
-
createdAt: batch.created_at ? new Date(batch.created_at * 1000).toISOString() : undefined,
|
|
90
|
-
completedAt: batch.completed_at ? new Date(batch.completed_at * 1000).toISOString() : undefined,
|
|
91
|
-
expiresAt: batch.expires_at ? new Date(batch.expires_at * 1000).toISOString() : undefined,
|
|
92
|
-
error: batch.errors?.data?.[0]
|
|
93
|
-
? { message: batch.errors.data[0].message ?? 'batch failed', code: batch.errors.data[0].code }
|
|
94
|
-
: undefined,
|
|
95
|
-
raw: batch,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
toOutputItem(line) {
|
|
99
|
-
let parsed;
|
|
100
|
-
try {
|
|
101
|
-
parsed = JSON.parse(line);
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
throw new BatchProviderResponseError('openai', 'an output line was not valid JSON', error);
|
|
105
|
-
}
|
|
106
|
-
const customId = parsed.custom_id ?? '';
|
|
107
|
-
if (parsed.error) {
|
|
108
|
-
return { customId, error: { message: parsed.error.message ?? 'batch item failed', code: parsed.error.code } };
|
|
109
|
-
}
|
|
110
|
-
const body = parsed.response?.body;
|
|
111
|
-
const status = parsed.response?.status_code ?? 200;
|
|
112
|
-
if (!body || status >= 400) {
|
|
113
|
-
return { customId, error: { message: `batch item failed with status ${status}`, status } };
|
|
114
|
-
}
|
|
115
|
-
const choice = body.choices?.[0];
|
|
116
|
-
const response = {
|
|
117
|
-
content: choice?.message?.content ?? '',
|
|
118
|
-
role: 'assistant',
|
|
119
|
-
finishReason: choice?.finish_reason === 'length' ? 'length' : 'stop',
|
|
120
|
-
meta: buildMeta({
|
|
121
|
-
provider: 'openai',
|
|
122
|
-
model: body.model ?? '',
|
|
123
|
-
latencyMs: 0,
|
|
124
|
-
inputTokens: body.usage?.prompt_tokens,
|
|
125
|
-
outputTokens: body.usage?.completion_tokens,
|
|
126
|
-
}),
|
|
127
|
-
};
|
|
128
|
-
return { customId, response };
|
|
129
|
-
}
|
|
130
|
-
async request(path, init, context) {
|
|
131
|
-
const response = await this.send(path, init, context);
|
|
132
|
-
return response.json();
|
|
133
|
-
}
|
|
134
|
-
async requestText(path, context) {
|
|
135
|
-
const response = await this.send(path, {}, context);
|
|
136
|
-
return response.text();
|
|
137
|
-
}
|
|
138
|
-
async send(path, init, context) {
|
|
139
|
-
const fetchImpl = this.options.fetch ?? fetch;
|
|
140
|
-
const baseUrl = (this.options.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
|
|
141
|
-
const headers = {
|
|
142
|
-
authorization: `Bearer ${this.options.apiKey}`,
|
|
143
|
-
...(this.options.organization ? { 'openai-organization': this.options.organization } : {}),
|
|
144
|
-
...this.options.headers,
|
|
145
|
-
...(init.headers ?? {}),
|
|
146
|
-
};
|
|
147
|
-
const response = await fetchImpl(`${baseUrl}${path}`, { ...init, headers, signal: context.signal });
|
|
148
|
-
if (!response.ok) {
|
|
149
|
-
throw new BatchProviderResponseError('openai', `${path} failed: ${response.status} ${await safeText(response)}`);
|
|
150
|
-
}
|
|
151
|
-
return response;
|
|
46
|
+
return this.client.state(ref, await this.client.cancel(ref.id, context));
|
|
152
47
|
}
|
|
153
48
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
49
|
+
function toOutputItem(line) {
|
|
50
|
+
const customId = line.custom_id ?? '';
|
|
51
|
+
const error = lineError(line);
|
|
52
|
+
if (error)
|
|
53
|
+
return { customId, error };
|
|
54
|
+
const body = line.response?.body;
|
|
55
|
+
const choice = body.choices?.[0];
|
|
56
|
+
const response = {
|
|
57
|
+
content: choice?.message?.content ?? '',
|
|
58
|
+
role: 'assistant',
|
|
59
|
+
finishReason: choice?.finish_reason === 'length' ? 'length' : 'stop',
|
|
60
|
+
meta: buildMeta({
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
model: body.model ?? '',
|
|
63
|
+
latencyMs: 0,
|
|
64
|
+
inputTokens: body.usage?.prompt_tokens,
|
|
65
|
+
outputTokens: body.usage?.completion_tokens,
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
return { customId, response };
|
|
161
69
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type BatchJobState } from '../types/batch.js';
|
|
2
|
+
/** How a batch, or any job with a status, is waited on. */
|
|
3
|
+
export interface BatchPollOptions<State = BatchJobState> {
|
|
4
|
+
/** The first wait between polls, in milliseconds. */
|
|
5
|
+
intervalMs: number;
|
|
6
|
+
/** The longest wait, once backoff has grown it. */
|
|
7
|
+
maxIntervalMs: number;
|
|
8
|
+
/** Stops waiting. The provider-side batch keeps running. */
|
|
9
|
+
signal: AbortSignal;
|
|
10
|
+
/** Called with each state that is not yet final. */
|
|
11
|
+
onState?: (state: State) => void;
|
|
12
|
+
/** Whether a state is final. Defaults to a batch's terminal statuses. */
|
|
13
|
+
settled?: (state: State) => boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Polls a batch until it reaches a status it never leaves. A batch measured in hours does not deserve
|
|
17
|
+
* a fixed short interval: the wait grows by half each time up to the maximum, which keeps the poll
|
|
18
|
+
* count sane without delaying a fast batch, which the first few short polls still catch.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pollUntilSettled<State extends {
|
|
21
|
+
status: string;
|
|
22
|
+
} = BatchJobState>(poll: () => Promise<State>, options: BatchPollOptions<State>): Promise<State>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isTerminalBatchStatus } from '../types/batch.js';
|
|
2
|
+
export async function pollUntilSettled(poll, options) {
|
|
3
|
+
const settled = options.settled ?? ((state) => isTerminalBatchStatus(state.status));
|
|
4
|
+
let interval = options.intervalMs;
|
|
5
|
+
let state = await poll();
|
|
6
|
+
while (!settled(state)) {
|
|
7
|
+
if (options.signal.aborted)
|
|
8
|
+
throw options.signal.reason ?? new Error('aborted');
|
|
9
|
+
options.onState?.(state);
|
|
10
|
+
await delay(interval, options.signal);
|
|
11
|
+
interval = Math.min(Math.round(interval * 1.5), options.maxIntervalMs);
|
|
12
|
+
state = await poll();
|
|
13
|
+
}
|
|
14
|
+
return state;
|
|
15
|
+
}
|
|
16
|
+
function delay(ms, signal) {
|
|
17
|
+
if (ms <= 0)
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
signal?.removeEventListener('abort', onAbort);
|
|
22
|
+
resolve();
|
|
23
|
+
}, ms);
|
|
24
|
+
function onAbort() {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
resolve();
|
|
27
|
+
}
|
|
28
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type RegistryData } from '../models/data.js';
|
|
2
|
+
import { type ParsedArgs } from './args.js';
|
|
3
|
+
/** How the command reaches the outside: replaced in tests. */
|
|
4
|
+
export interface ModelsCommandIo {
|
|
5
|
+
fetch: typeof fetch;
|
|
6
|
+
env: Record<string, string | undefined>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `nexus models export | check | refresh`: registry data as a file the application keeps.
|
|
10
|
+
*
|
|
11
|
+
* `export` writes the bundled data, as a file to start from. `check` validates a file and says how it
|
|
12
|
+
* differs from the bundled data. `refresh` lists each provider's models with the keys in the
|
|
13
|
+
* environment and compares them with the data: what the data lacks, and what the provider no longer
|
|
14
|
+
* lists. Prices are not in any provider's model list, so they stay a check against its pricing page.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runModelsCommand(subcommand: string, { positionals, flags }: ParsedArgs, io?: ModelsCommandIo): Promise<void>;
|
|
17
|
+
/** How the data compares with what one provider lists. */
|
|
18
|
+
export interface RefreshRow {
|
|
19
|
+
/** The provider. */
|
|
20
|
+
provider: string;
|
|
21
|
+
/** Models the provider lists that the data does not have. */
|
|
22
|
+
missing: string[];
|
|
23
|
+
/** Models the data has for the provider that it no longer lists. */
|
|
24
|
+
unlisted: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Compares registry data with each provider's model list, completion and embedding models alike. */
|
|
27
|
+
export declare function refreshReport(data: RegistryData, listings: Record<string, string[]>): RefreshRow[];
|
|
28
|
+
interface ProviderList {
|
|
29
|
+
/** The environment variable that holds its key. */
|
|
30
|
+
env: string;
|
|
31
|
+
/** Every model id it lists. */
|
|
32
|
+
list(key: string, fetch: typeof globalThis.fetch): Promise<string[]>;
|
|
33
|
+
}
|
|
34
|
+
/** Each provider's own model list, read with the application's key: nothing else is contacted. */
|
|
35
|
+
export declare const PROVIDER_LISTS: Record<string, ProviderList>;
|
|
36
|
+
export {};
|