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.
Files changed (229) hide show
  1. package/API_STABILITY.md +119 -3
  2. package/CHANGELOG.md +241 -1
  3. package/README.md +21 -3
  4. package/SECURITY.md +41 -1
  5. package/dist/batch/manager.js +16 -33
  6. package/dist/batch/openai-client.d.ts +83 -0
  7. package/dist/batch/openai-client.js +120 -0
  8. package/dist/batch/openai.d.ts +12 -21
  9. package/dist/batch/openai.js +33 -125
  10. package/dist/batch/poll.d.ts +22 -0
  11. package/dist/batch/poll.js +30 -0
  12. package/dist/cli/models.d.ts +36 -0
  13. package/dist/cli/models.js +170 -0
  14. package/dist/cli.js +11 -1
  15. package/dist/deep-agent/helpers.d.ts +4 -0
  16. package/dist/deep-agent/helpers.js +56 -4
  17. package/dist/doctor/index.d.ts +16 -1
  18. package/dist/doctor/index.js +10 -5
  19. package/dist/embeddings/adapters.d.ts +2 -2
  20. package/dist/embeddings/adapters.js +24 -13
  21. package/dist/embeddings/batch-google.d.ts +40 -0
  22. package/dist/embeddings/batch-google.js +128 -0
  23. package/dist/embeddings/batch-mock.d.ts +45 -0
  24. package/dist/embeddings/batch-mock.js +71 -0
  25. package/dist/embeddings/batch-openai.d.ts +23 -0
  26. package/dist/embeddings/batch-openai.js +55 -0
  27. package/dist/embeddings/batch.d.ts +230 -0
  28. package/dist/embeddings/batch.js +282 -0
  29. package/dist/embeddings/cache.d.ts +81 -0
  30. package/dist/embeddings/cache.js +116 -0
  31. package/dist/embeddings/engine.d.ts +7 -0
  32. package/dist/embeddings/engine.js +110 -68
  33. package/dist/embeddings/google-wire.d.ts +32 -0
  34. package/dist/embeddings/google-wire.js +33 -0
  35. package/dist/embeddings/index.d.ts +1 -1
  36. package/dist/embeddings/inputs.d.ts +30 -0
  37. package/dist/embeddings/inputs.js +103 -0
  38. package/dist/embeddings/mock.d.ts +7 -1
  39. package/dist/embeddings/mock.js +8 -2
  40. package/dist/embeddings/models.js +21 -0
  41. package/dist/evaluate/online.d.ts +5 -0
  42. package/dist/evaluate/online.js +74 -5
  43. package/dist/images/asset-stores.d.ts +47 -1
  44. package/dist/images/asset-stores.js +135 -2
  45. package/dist/images/asset-support.d.ts +94 -1
  46. package/dist/images/asset-support.js +159 -3
  47. package/dist/images/assets.d.ts +10 -3
  48. package/dist/images/assets.js +15 -2
  49. package/dist/lifecycle/budget.d.ts +60 -3
  50. package/dist/lifecycle/budget.js +147 -3
  51. package/dist/lifecycle/index.d.ts +2 -2
  52. package/dist/lifecycle/reservations.d.ts +46 -0
  53. package/dist/lifecycle/reservations.js +67 -0
  54. package/dist/media/index.d.ts +244 -0
  55. package/dist/media/index.js +262 -0
  56. package/dist/media/mistral.d.ts +34 -0
  57. package/dist/media/mistral.js +90 -0
  58. package/dist/media/mock.d.ts +36 -0
  59. package/dist/media/mock.js +45 -0
  60. package/dist/media/rag.d.ts +216 -0
  61. package/dist/media/rag.js +340 -0
  62. package/dist/models/data.d.ts +87 -0
  63. package/dist/models/data.js +235 -0
  64. package/dist/models/registry.d.ts +2 -1
  65. package/dist/models/registry.js +3 -2
  66. package/dist/postgres/embedding-cache.d.ts +29 -0
  67. package/dist/postgres/embedding-cache.js +58 -0
  68. package/dist/postgres/tenancy.d.ts +26 -1
  69. package/dist/postgres/tenancy.js +116 -1
  70. package/dist/postgres/traces.d.ts +2 -1
  71. package/dist/postgres/traces.js +13 -1
  72. package/dist/rag/chroma.d.ts +13 -1
  73. package/dist/rag/chroma.js +36 -13
  74. package/dist/rag/pinecone.js +2 -2
  75. package/dist/rag/pipeline.d.ts +17 -2
  76. package/dist/rag/pipeline.js +95 -14
  77. package/dist/rag/qdrant.js +2 -2
  78. package/dist/rag/redis.d.ts +2 -1
  79. package/dist/rag/redis.js +56 -18
  80. package/dist/rag/vector-helpers.d.ts +5 -0
  81. package/dist/rag/vector-helpers.js +7 -0
  82. package/dist/rag/weaviate.d.ts +15 -1
  83. package/dist/rag/weaviate.js +75 -12
  84. package/dist/server/tenancy.d.ts +33 -2
  85. package/dist/server/tenancy.js +162 -0
  86. package/dist/sqlite/embedding-cache.d.ts +34 -0
  87. package/dist/sqlite/embedding-cache.js +61 -0
  88. package/dist/tracing/index.d.ts +2 -2
  89. package/dist/tracing/index.js +1 -1
  90. package/dist/tracing/query.d.ts +5 -1
  91. package/dist/tracing/query.js +14 -1
  92. package/dist/tracing/stores.d.ts +2 -2
  93. package/dist/tracing/stores.js +2 -2
  94. package/dist/types/config.d.ts +5 -0
  95. package/dist/types/embeddings.d.ts +87 -5
  96. package/dist/types/images.d.ts +2 -2
  97. package/dist/types/lifecycle.d.ts +92 -1
  98. package/dist/types/tracing.d.ts +20 -1
  99. package/dist/utils/redis-command.d.ts +15 -0
  100. package/dist/utils/redis-command.js +5 -0
  101. package/dist/utils/sha256.d.ts +9 -0
  102. package/dist/utils/sha256.js +6 -0
  103. package/dist/utils/vector-bytes.d.ts +11 -0
  104. package/dist/utils/vector-bytes.js +18 -0
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/dist/video/errors.d.ts +37 -0
  108. package/dist/video/errors.js +22 -0
  109. package/dist/video/google.d.ts +52 -0
  110. package/dist/video/google.js +248 -0
  111. package/dist/video/index.d.ts +426 -0
  112. package/dist/video/index.js +666 -0
  113. package/dist/video/mock.d.ts +62 -0
  114. package/dist/video/mock.js +97 -0
  115. package/dist/video/runway.d.ts +47 -0
  116. package/dist/video/runway.js +241 -0
  117. package/dist-cjs/batch/manager.js +16 -33
  118. package/dist-cjs/batch/openai-client.d.ts +83 -0
  119. package/dist-cjs/batch/openai-client.js +125 -0
  120. package/dist-cjs/batch/openai.d.ts +12 -21
  121. package/dist-cjs/batch/openai.js +32 -124
  122. package/dist-cjs/batch/poll.d.ts +22 -0
  123. package/dist-cjs/batch/poll.js +33 -0
  124. package/dist-cjs/cli/models.d.ts +36 -0
  125. package/dist-cjs/cli/models.js +175 -0
  126. package/dist-cjs/deep-agent/helpers.d.ts +4 -0
  127. package/dist-cjs/deep-agent/helpers.js +56 -4
  128. package/dist-cjs/doctor/index.d.ts +16 -1
  129. package/dist-cjs/doctor/index.js +10 -5
  130. package/dist-cjs/embeddings/adapters.d.ts +2 -2
  131. package/dist-cjs/embeddings/adapters.js +23 -12
  132. package/dist-cjs/embeddings/batch-google.d.ts +40 -0
  133. package/dist-cjs/embeddings/batch-google.js +132 -0
  134. package/dist-cjs/embeddings/batch-mock.d.ts +45 -0
  135. package/dist-cjs/embeddings/batch-mock.js +75 -0
  136. package/dist-cjs/embeddings/batch-openai.d.ts +23 -0
  137. package/dist-cjs/embeddings/batch-openai.js +59 -0
  138. package/dist-cjs/embeddings/batch.d.ts +230 -0
  139. package/dist-cjs/embeddings/batch.js +320 -0
  140. package/dist-cjs/embeddings/cache.d.ts +81 -0
  141. package/dist-cjs/embeddings/cache.js +125 -0
  142. package/dist-cjs/embeddings/engine.d.ts +7 -0
  143. package/dist-cjs/embeddings/engine.js +143 -68
  144. package/dist-cjs/embeddings/google-wire.d.ts +32 -0
  145. package/dist-cjs/embeddings/google-wire.js +38 -0
  146. package/dist-cjs/embeddings/index.d.ts +1 -1
  147. package/dist-cjs/embeddings/inputs.d.ts +30 -0
  148. package/dist-cjs/embeddings/inputs.js +141 -0
  149. package/dist-cjs/embeddings/mock.d.ts +7 -1
  150. package/dist-cjs/embeddings/mock.js +8 -2
  151. package/dist-cjs/embeddings/models.js +21 -0
  152. package/dist-cjs/evaluate/online.d.ts +5 -0
  153. package/dist-cjs/evaluate/online.js +74 -5
  154. package/dist-cjs/images/asset-stores.d.ts +47 -1
  155. package/dist-cjs/images/asset-stores.js +133 -0
  156. package/dist-cjs/images/asset-support.d.ts +94 -1
  157. package/dist-cjs/images/asset-support.js +169 -3
  158. package/dist-cjs/images/assets.d.ts +10 -3
  159. package/dist-cjs/images/assets.js +17 -1
  160. package/dist-cjs/lifecycle/budget.d.ts +60 -3
  161. package/dist-cjs/lifecycle/budget.js +147 -3
  162. package/dist-cjs/lifecycle/index.d.ts +2 -2
  163. package/dist-cjs/lifecycle/reservations.d.ts +46 -0
  164. package/dist-cjs/lifecycle/reservations.js +71 -0
  165. package/dist-cjs/media/index.d.ts +244 -0
  166. package/dist-cjs/media/index.js +269 -0
  167. package/dist-cjs/media/mistral.d.ts +34 -0
  168. package/dist-cjs/media/mistral.js +94 -0
  169. package/dist-cjs/media/mock.d.ts +36 -0
  170. package/dist-cjs/media/mock.js +49 -0
  171. package/dist-cjs/media/rag.d.ts +216 -0
  172. package/dist-cjs/media/rag.js +344 -0
  173. package/dist-cjs/models/data.d.ts +87 -0
  174. package/dist-cjs/models/data.js +245 -0
  175. package/dist-cjs/models/registry.d.ts +2 -1
  176. package/dist-cjs/models/registry.js +3 -2
  177. package/dist-cjs/postgres/embedding-cache.d.ts +29 -0
  178. package/dist-cjs/postgres/embedding-cache.js +96 -0
  179. package/dist-cjs/postgres/tenancy.d.ts +26 -1
  180. package/dist-cjs/postgres/tenancy.js +115 -0
  181. package/dist-cjs/postgres/traces.d.ts +2 -1
  182. package/dist-cjs/postgres/traces.js +13 -1
  183. package/dist-cjs/rag/chroma.d.ts +13 -1
  184. package/dist-cjs/rag/chroma.js +35 -12
  185. package/dist-cjs/rag/pinecone.js +1 -1
  186. package/dist-cjs/rag/pipeline.d.ts +17 -2
  187. package/dist-cjs/rag/pipeline.js +95 -14
  188. package/dist-cjs/rag/qdrant.js +1 -1
  189. package/dist-cjs/rag/redis.d.ts +2 -1
  190. package/dist-cjs/rag/redis.js +55 -17
  191. package/dist-cjs/rag/vector-helpers.d.ts +5 -0
  192. package/dist-cjs/rag/vector-helpers.js +8 -0
  193. package/dist-cjs/rag/weaviate.d.ts +15 -1
  194. package/dist-cjs/rag/weaviate.js +74 -11
  195. package/dist-cjs/server/tenancy.d.ts +33 -2
  196. package/dist-cjs/server/tenancy.js +162 -0
  197. package/dist-cjs/sqlite/embedding-cache.d.ts +34 -0
  198. package/dist-cjs/sqlite/embedding-cache.js +99 -0
  199. package/dist-cjs/tracing/index.d.ts +2 -2
  200. package/dist-cjs/tracing/index.js +3 -1
  201. package/dist-cjs/tracing/query.d.ts +5 -1
  202. package/dist-cjs/tracing/query.js +16 -1
  203. package/dist-cjs/tracing/stores.d.ts +2 -2
  204. package/dist-cjs/tracing/stores.js +3 -1
  205. package/dist-cjs/types/config.d.ts +5 -0
  206. package/dist-cjs/types/embeddings.d.ts +87 -5
  207. package/dist-cjs/types/images.d.ts +2 -2
  208. package/dist-cjs/types/lifecycle.d.ts +92 -1
  209. package/dist-cjs/types/tracing.d.ts +20 -1
  210. package/dist-cjs/utils/redis-command.d.ts +15 -0
  211. package/dist-cjs/utils/redis-command.js +8 -0
  212. package/dist-cjs/utils/sha256.d.ts +9 -0
  213. package/dist-cjs/utils/sha256.js +7 -0
  214. package/dist-cjs/utils/vector-bytes.d.ts +11 -0
  215. package/dist-cjs/utils/vector-bytes.js +22 -0
  216. package/dist-cjs/version.d.ts +1 -1
  217. package/dist-cjs/version.js +1 -1
  218. package/dist-cjs/video/errors.d.ts +37 -0
  219. package/dist-cjs/video/errors.js +27 -0
  220. package/dist-cjs/video/google.d.ts +52 -0
  221. package/dist-cjs/video/google.js +252 -0
  222. package/dist-cjs/video/index.d.ts +426 -0
  223. package/dist-cjs/video/index.js +708 -0
  224. package/dist-cjs/video/mock.d.ts +62 -0
  225. package/dist-cjs/video/mock.js +101 -0
  226. package/dist-cjs/video/runway.d.ts +47 -0
  227. package/dist-cjs/video/runway.js +245 -0
  228. package/llms.txt +3 -1
  229. 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
+ }
@@ -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
- /** 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
- /** 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 a JSONL download — three
21
- * different content types across four endpoints, which is why this adapter is larger than a
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 the output file exists. */
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
  }
@@ -1,15 +1,6 @@
1
1
  import { buildMeta } from '../core/usage.js';
2
- import { BatchProviderResponseError } from './errors.js';
3
- const STATUS_MAP = {
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
- const jsonl = request.items
31
- .map((item) => JSON.stringify({
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
- .join('\n');
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
- const batch = (await this.request(`/batches/${encodeURIComponent(ref.id)}`, {}, context));
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
- const batch = (await this.request(`/batches/${encodeURIComponent(ref.id)}`, {}, context));
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
- const batch = (await this.request(`/batches/${encodeURIComponent(ref.id)}/cancel`, { method: 'POST' }, context));
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
- async function safeText(response) {
155
- try {
156
- return (await response.text()).slice(0, 500);
157
- }
158
- catch {
159
- return '';
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 {};