batchwork 0.0.0 → 1.0.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/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/batch.d.ts +27 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/body.d.ts +18 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/chunk-ab2d71gk.js +325 -0
- package/dist/chunk-ab2d71gk.js.map +12 -0
- package/dist/chunk-kv3847wy.js +1048 -0
- package/dist/chunk-kv3847wy.js.map +24 -0
- package/dist/chunk-v0bahtg2.js +7 -0
- package/dist/chunk-v0bahtg2.js.map +9 -0
- package/dist/chunk-zp2cxkyb.js +226 -0
- package/dist/chunk-zp2cxkyb.js.map +13 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +9 -0
- package/dist/job.d.ts +31 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/jsonl.d.ts +17 -0
- package/dist/jsonl.d.ts.map +1 -0
- package/dist/model.d.ts +32 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/next/index.d.ts +59 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +61 -0
- package/dist/next/index.js.map +10 -0
- package/dist/postgres/batch-store.d.ts +19 -0
- package/dist/postgres/batch-store.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +6 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +101 -0
- package/dist/postgres/index.js.map +12 -0
- package/dist/postgres/migrate.d.ts +15 -0
- package/dist/postgres/migrate.d.ts.map +1 -0
- package/dist/postgres/types.d.ts +26 -0
- package/dist/postgres/types.d.ts.map +1 -0
- package/dist/providers/adapter.d.ts +24 -0
- package/dist/providers/adapter.d.ts.map +1 -0
- package/dist/providers/anthropic.d.ts +3 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/google.d.ts +8 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/groq.d.ts +8 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/mistral.d.ts +8 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/openai-compatible.d.ts +41 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai.d.ts +7 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/shared.d.ts +23 -0
- package/dist/providers/shared.d.ts.map +1 -0
- package/dist/providers/together.d.ts +8 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/xai.d.ts +8 -0
- package/dist/providers/xai.d.ts.map +1 -0
- package/dist/redis/batch-store.d.ts +15 -0
- package/dist/redis/batch-store.d.ts.map +1 -0
- package/dist/redis/index.d.ts +4 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +55 -0
- package/dist/redis/index.js.map +11 -0
- package/dist/redis/types.d.ts +19 -0
- package/dist/redis/types.d.ts.map +1 -0
- package/dist/server/events.d.ts +5 -0
- package/dist/server/events.d.ts.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +19 -0
- package/dist/server/index.js.map +9 -0
- package/dist/server/poller.d.ts +60 -0
- package/dist/server/poller.d.ts.map +1 -0
- package/dist/server/signing.d.ts +20 -0
- package/dist/server/signing.d.ts.map +1 -0
- package/dist/server/store.d.ts +4 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/types.d.ts +37 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/util.d.ts +10 -0
- package/dist/util.d.ts.map +1 -0
- package/package.json +119 -7
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
class BatchworkError extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "BatchworkError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class UnsupportedProviderError extends BatchworkError {
|
|
10
|
+
provider;
|
|
11
|
+
constructor(provider) {
|
|
12
|
+
super(`batchwork: provider "${provider}" is not supported yet. Supported providers: openai, anthropic, google, groq, mistral, together, xai.`);
|
|
13
|
+
this.name = "UnsupportedProviderError";
|
|
14
|
+
this.provider = provider;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class MissingDependencyError extends BatchworkError {
|
|
19
|
+
constructor(pkg, provider) {
|
|
20
|
+
super(`batchwork: install \`${pkg}\` to batch ${provider} models (\`npm install ${pkg}\`).`);
|
|
21
|
+
this.name = "MissingDependencyError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/job.ts
|
|
26
|
+
var DEFAULT_POLL_INTERVAL_MS = 15000;
|
|
27
|
+
var TERMINAL_STATUSES = new Set([
|
|
28
|
+
"completed",
|
|
29
|
+
"failed",
|
|
30
|
+
"expired",
|
|
31
|
+
"cancelled"
|
|
32
|
+
]);
|
|
33
|
+
var isTerminalStatus = (status) => TERMINAL_STATUSES.has(status);
|
|
34
|
+
var delay = (ms, signal) => new Promise((resolve, reject) => {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
reject(new BatchworkError("batchwork: wait aborted."));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const timer = setTimeout(resolve, ms);
|
|
40
|
+
signal?.addEventListener("abort", () => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
reject(new BatchworkError("batchwork: wait aborted."));
|
|
43
|
+
}, { once: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
class BatchJob {
|
|
47
|
+
provider;
|
|
48
|
+
id;
|
|
49
|
+
#adapter;
|
|
50
|
+
#credentials;
|
|
51
|
+
#snapshot;
|
|
52
|
+
constructor(adapter, credentials, snapshot) {
|
|
53
|
+
this.#adapter = adapter;
|
|
54
|
+
this.#credentials = credentials;
|
|
55
|
+
this.#snapshot = snapshot;
|
|
56
|
+
this.id = snapshot.id;
|
|
57
|
+
this.provider = snapshot.provider;
|
|
58
|
+
}
|
|
59
|
+
get status() {
|
|
60
|
+
return this.#snapshot.status;
|
|
61
|
+
}
|
|
62
|
+
get requestCounts() {
|
|
63
|
+
return this.#snapshot.requestCounts;
|
|
64
|
+
}
|
|
65
|
+
get snapshot() {
|
|
66
|
+
return this.#snapshot;
|
|
67
|
+
}
|
|
68
|
+
async poll() {
|
|
69
|
+
this.#snapshot = await this.#adapter.retrieve(this.id, this.#credentials);
|
|
70
|
+
return this.#snapshot;
|
|
71
|
+
}
|
|
72
|
+
async wait(options = {}) {
|
|
73
|
+
const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
74
|
+
const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : undefined;
|
|
75
|
+
let snapshot = await this.poll();
|
|
76
|
+
options.onPoll?.(snapshot);
|
|
77
|
+
while (!isTerminalStatus(snapshot.status)) {
|
|
78
|
+
if (options.signal?.aborted) {
|
|
79
|
+
throw new BatchworkError("batchwork: wait aborted.");
|
|
80
|
+
}
|
|
81
|
+
if (deadline !== undefined && Date.now() > deadline) {
|
|
82
|
+
throw new BatchworkError(`batchwork: timed out waiting for batch "${this.id}".`);
|
|
83
|
+
}
|
|
84
|
+
await delay(interval, options.signal);
|
|
85
|
+
snapshot = await this.poll();
|
|
86
|
+
options.onPoll?.(snapshot);
|
|
87
|
+
}
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
results() {
|
|
91
|
+
return this.#adapter.results(this.id, this.#credentials);
|
|
92
|
+
}
|
|
93
|
+
async collect() {
|
|
94
|
+
const out = [];
|
|
95
|
+
for await (const result of this.results()) {
|
|
96
|
+
out.push(result);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
async cancel() {
|
|
101
|
+
await this.#adapter.cancel(this.id, this.#credentials);
|
|
102
|
+
return await this.poll();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/http.ts
|
|
107
|
+
var safeText = async (response) => {
|
|
108
|
+
try {
|
|
109
|
+
return await response.text();
|
|
110
|
+
} catch {
|
|
111
|
+
return "<no body>";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var assertOk = async (url, init, response) => {
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const detail = await safeText(response);
|
|
117
|
+
throw new BatchworkError(`batchwork: ${init.method ?? "GET"} ${url} failed with ${response.status}: ${detail}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var requestJson = async (url, init) => {
|
|
121
|
+
const response = await fetch(url, init);
|
|
122
|
+
await assertOk(url, init, response);
|
|
123
|
+
return await response.json();
|
|
124
|
+
};
|
|
125
|
+
var requestStream = async (url, init) => {
|
|
126
|
+
const response = await fetch(url, init);
|
|
127
|
+
await assertOk(url, init, response);
|
|
128
|
+
if (!response.body) {
|
|
129
|
+
throw new BatchworkError(`batchwork: ${url} returned an empty body.`);
|
|
130
|
+
}
|
|
131
|
+
return response.body;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/jsonl.ts
|
|
135
|
+
var NEWLINE = `
|
|
136
|
+
`;
|
|
137
|
+
var encodeJsonl = (items) => {
|
|
138
|
+
if (items.length === 0) {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
const body = items.map((item) => JSON.stringify(item)).join(NEWLINE);
|
|
142
|
+
return `${body}${NEWLINE}`;
|
|
143
|
+
};
|
|
144
|
+
var isReadableStream = (source) => ("getReader" in source) && typeof source.getReader === "function";
|
|
145
|
+
async function* toByteIterable(source) {
|
|
146
|
+
if (isReadableStream(source)) {
|
|
147
|
+
const reader = source.getReader();
|
|
148
|
+
try {
|
|
149
|
+
let chunk = await reader.read();
|
|
150
|
+
while (!chunk.done) {
|
|
151
|
+
if (chunk.value) {
|
|
152
|
+
yield chunk.value;
|
|
153
|
+
}
|
|
154
|
+
chunk = await reader.read();
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
reader.releaseLock();
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
yield* source;
|
|
162
|
+
}
|
|
163
|
+
async function* streamJsonl(source) {
|
|
164
|
+
const decoder = new TextDecoder;
|
|
165
|
+
let buffer = "";
|
|
166
|
+
for await (const chunk of toByteIterable(source)) {
|
|
167
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
168
|
+
let newlineIndex = buffer.indexOf(NEWLINE);
|
|
169
|
+
while (newlineIndex !== -1) {
|
|
170
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
171
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
172
|
+
if (line.length > 0) {
|
|
173
|
+
yield JSON.parse(line);
|
|
174
|
+
}
|
|
175
|
+
newlineIndex = buffer.indexOf(NEWLINE);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
buffer += decoder.decode();
|
|
179
|
+
const tail = buffer.trim();
|
|
180
|
+
if (tail.length > 0) {
|
|
181
|
+
yield JSON.parse(tail);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/util.ts
|
|
186
|
+
var asRecord = (value) => {
|
|
187
|
+
if (typeof value === "object" && value !== null) {
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
return {};
|
|
191
|
+
};
|
|
192
|
+
var asString = (value) => typeof value === "string" ? value : undefined;
|
|
193
|
+
var asNumber = (value) => typeof value === "number" ? value : undefined;
|
|
194
|
+
var asArray = (value) => Array.isArray(value) ? value : [];
|
|
195
|
+
var omit = (obj, key) => {
|
|
196
|
+
const result = {};
|
|
197
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
198
|
+
if (k !== key) {
|
|
199
|
+
result[k] = v;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
};
|
|
204
|
+
var toDate = (value) => {
|
|
205
|
+
if (typeof value === "string") {
|
|
206
|
+
return new Date(value);
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "number") {
|
|
209
|
+
return new Date(value * 1000);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/providers/anthropic.ts
|
|
214
|
+
var ANTHROPIC_BASE = "https://api.anthropic.com";
|
|
215
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
216
|
+
var apiKey = (credentials) => {
|
|
217
|
+
const key = credentials.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
218
|
+
if (!key) {
|
|
219
|
+
throw new BatchworkError("batchwork: missing Anthropic API key. Set ANTHROPIC_API_KEY or pass `apiKey`.");
|
|
220
|
+
}
|
|
221
|
+
return key;
|
|
222
|
+
};
|
|
223
|
+
var baseUrl = (credentials) => credentials.baseURL ?? ANTHROPIC_BASE;
|
|
224
|
+
var headers = (credentials) => ({
|
|
225
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
226
|
+
"content-type": "application/json",
|
|
227
|
+
"x-api-key": apiKey(credentials),
|
|
228
|
+
...credentials.headers
|
|
229
|
+
});
|
|
230
|
+
var mapStatus = (status) => {
|
|
231
|
+
if (status === "ended") {
|
|
232
|
+
return "completed";
|
|
233
|
+
}
|
|
234
|
+
if (status === "canceling") {
|
|
235
|
+
return "cancelling";
|
|
236
|
+
}
|
|
237
|
+
return "in_progress";
|
|
238
|
+
};
|
|
239
|
+
var normalizeSnapshot = (raw) => {
|
|
240
|
+
const obj = asRecord(raw);
|
|
241
|
+
const counts = asRecord(obj.request_counts);
|
|
242
|
+
const succeeded = asNumber(counts.succeeded) ?? 0;
|
|
243
|
+
const errored = asNumber(counts.errored) ?? 0;
|
|
244
|
+
const processing = asNumber(counts.processing) ?? 0;
|
|
245
|
+
const canceled = asNumber(counts.canceled) ?? 0;
|
|
246
|
+
const expired = asNumber(counts.expired) ?? 0;
|
|
247
|
+
return {
|
|
248
|
+
completedAt: toDate(obj.ended_at),
|
|
249
|
+
createdAt: toDate(obj.created_at),
|
|
250
|
+
expiresAt: toDate(obj.expires_at),
|
|
251
|
+
id: asString(obj.id) ?? "",
|
|
252
|
+
provider: "anthropic",
|
|
253
|
+
raw,
|
|
254
|
+
requestCounts: {
|
|
255
|
+
canceled,
|
|
256
|
+
completed: succeeded,
|
|
257
|
+
expired,
|
|
258
|
+
failed: errored,
|
|
259
|
+
processing,
|
|
260
|
+
total: succeeded + errored + processing + canceled + expired
|
|
261
|
+
},
|
|
262
|
+
status: mapStatus(asString(obj.processing_status))
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
var textFromMessage = (message) => {
|
|
266
|
+
const text = asArray(asRecord(message).content).map((block) => asRecord(block)).filter((block) => block.type === "text").map((block) => asString(block.text) ?? "").join("");
|
|
267
|
+
return text.length > 0 ? text : undefined;
|
|
268
|
+
};
|
|
269
|
+
var usageFromMessage = (message) => {
|
|
270
|
+
const usage = asRecord(asRecord(message).usage);
|
|
271
|
+
const inputTokens = asNumber(usage.input_tokens);
|
|
272
|
+
const outputTokens = asNumber(usage.output_tokens);
|
|
273
|
+
if (inputTokens === undefined && outputTokens === undefined) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
inputTokens,
|
|
278
|
+
outputTokens,
|
|
279
|
+
totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0)
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
var normalizeResult = (line) => {
|
|
283
|
+
const obj = asRecord(line);
|
|
284
|
+
const customId = asString(obj.custom_id) ?? "";
|
|
285
|
+
const result = asRecord(obj.result);
|
|
286
|
+
const type = asString(result.type);
|
|
287
|
+
if (type === "succeeded") {
|
|
288
|
+
return {
|
|
289
|
+
customId,
|
|
290
|
+
response: result.message,
|
|
291
|
+
status: "succeeded",
|
|
292
|
+
text: textFromMessage(result.message),
|
|
293
|
+
usage: usageFromMessage(result.message)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (type === "errored") {
|
|
297
|
+
const error = asRecord(result.error);
|
|
298
|
+
const nested = asRecord(error.error);
|
|
299
|
+
const source = Object.keys(nested).length > 0 ? nested : error;
|
|
300
|
+
return {
|
|
301
|
+
customId,
|
|
302
|
+
error: {
|
|
303
|
+
message: asString(source.message) ?? "Request errored.",
|
|
304
|
+
type: asString(source.type)
|
|
305
|
+
},
|
|
306
|
+
response: result.error,
|
|
307
|
+
status: "errored"
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (type === "expired") {
|
|
311
|
+
return { customId, status: "expired" };
|
|
312
|
+
}
|
|
313
|
+
return { customId, status: "canceled" };
|
|
314
|
+
};
|
|
315
|
+
var submit = async (input) => {
|
|
316
|
+
const requests = input.built.map((item) => ({
|
|
317
|
+
custom_id: item.customId,
|
|
318
|
+
params: omit(item.body, "stream")
|
|
319
|
+
}));
|
|
320
|
+
const raw = await requestJson(`${baseUrl(input.credentials)}/v1/messages/batches`, {
|
|
321
|
+
body: JSON.stringify({ requests }),
|
|
322
|
+
headers: headers(input.credentials),
|
|
323
|
+
method: "POST"
|
|
324
|
+
});
|
|
325
|
+
return normalizeSnapshot(raw);
|
|
326
|
+
};
|
|
327
|
+
var retrieve = async (id, credentials) => {
|
|
328
|
+
const raw = await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${id}`, { headers: headers(credentials) });
|
|
329
|
+
return normalizeSnapshot(raw);
|
|
330
|
+
};
|
|
331
|
+
async function* results(id, credentials) {
|
|
332
|
+
const snapshot = await retrieve(id, credentials);
|
|
333
|
+
const resultsUrl = asString(asRecord(snapshot.raw).results_url);
|
|
334
|
+
if (!resultsUrl) {
|
|
335
|
+
throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
|
|
336
|
+
}
|
|
337
|
+
const stream = await requestStream(resultsUrl, {
|
|
338
|
+
headers: headers(credentials)
|
|
339
|
+
});
|
|
340
|
+
for await (const line of streamJsonl(stream)) {
|
|
341
|
+
yield normalizeResult(line);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
var cancel = async (id, credentials) => {
|
|
345
|
+
await requestJson(`${baseUrl(credentials)}/v1/messages/batches/${id}/cancel`, {
|
|
346
|
+
headers: headers(credentials),
|
|
347
|
+
method: "POST"
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
var anthropicAdapter = {
|
|
351
|
+
cancel,
|
|
352
|
+
id: "anthropic",
|
|
353
|
+
results,
|
|
354
|
+
retrieve,
|
|
355
|
+
submit
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/providers/google.ts
|
|
359
|
+
var GOOGLE_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
360
|
+
var apiKey2 = (credentials) => {
|
|
361
|
+
const key = credentials.apiKey ?? process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY;
|
|
362
|
+
if (!key) {
|
|
363
|
+
throw new BatchworkError("batchwork: missing Google Gemini API key. Set GOOGLE_GENERATIVE_AI_API_KEY (or GEMINI_API_KEY) or pass `apiKey`.");
|
|
364
|
+
}
|
|
365
|
+
return key;
|
|
366
|
+
};
|
|
367
|
+
var baseUrl2 = (credentials) => credentials.baseURL ?? GOOGLE_BASE;
|
|
368
|
+
var headers2 = (credentials) => ({
|
|
369
|
+
"content-type": "application/json",
|
|
370
|
+
"x-goog-api-key": apiKey2(credentials),
|
|
371
|
+
...credentials.headers
|
|
372
|
+
});
|
|
373
|
+
var mapState = (state, done) => {
|
|
374
|
+
if (state) {
|
|
375
|
+
if (state.endsWith("SUCCEEDED")) {
|
|
376
|
+
return "completed";
|
|
377
|
+
}
|
|
378
|
+
if (state.endsWith("FAILED")) {
|
|
379
|
+
return "failed";
|
|
380
|
+
}
|
|
381
|
+
if (state.endsWith("CANCELLED")) {
|
|
382
|
+
return "cancelled";
|
|
383
|
+
}
|
|
384
|
+
if (state.endsWith("EXPIRED")) {
|
|
385
|
+
return "expired";
|
|
386
|
+
}
|
|
387
|
+
if (state.endsWith("PENDING")) {
|
|
388
|
+
return "validating";
|
|
389
|
+
}
|
|
390
|
+
if (state.endsWith("RUNNING")) {
|
|
391
|
+
return "in_progress";
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return done ? "completed" : "in_progress";
|
|
395
|
+
};
|
|
396
|
+
var inlinedResponses = (raw) => {
|
|
397
|
+
const obj = asRecord(raw);
|
|
398
|
+
const response = asRecord(obj.response);
|
|
399
|
+
const dest = asRecord(obj.dest);
|
|
400
|
+
const responseInline = response.inlinedResponses ?? response.inlined_responses;
|
|
401
|
+
const destInline = dest.inlinedResponses ?? dest.inlined_responses;
|
|
402
|
+
const nestedResponseInline = asRecord(responseInline);
|
|
403
|
+
const nestedDestInline = asRecord(destInline);
|
|
404
|
+
return [
|
|
405
|
+
...asArray(responseInline),
|
|
406
|
+
...asArray(nestedResponseInline.inlinedResponses),
|
|
407
|
+
...asArray(nestedResponseInline.inlined_responses),
|
|
408
|
+
...asArray(destInline),
|
|
409
|
+
...asArray(nestedDestInline.inlinedResponses),
|
|
410
|
+
...asArray(nestedDestInline.inlined_responses)
|
|
411
|
+
];
|
|
412
|
+
};
|
|
413
|
+
var normalizeSnapshot2 = (raw) => {
|
|
414
|
+
const obj = asRecord(raw);
|
|
415
|
+
const items = inlinedResponses(raw);
|
|
416
|
+
const failed = items.filter((item) => asRecord(item).error).length;
|
|
417
|
+
return {
|
|
418
|
+
id: asString(obj.name) ?? "",
|
|
419
|
+
provider: "google",
|
|
420
|
+
raw,
|
|
421
|
+
requestCounts: {
|
|
422
|
+
completed: items.length - failed,
|
|
423
|
+
failed,
|
|
424
|
+
total: items.length
|
|
425
|
+
},
|
|
426
|
+
status: mapState(asString(obj.state) ?? asString(asRecord(obj.state).name) ?? asString(asRecord(obj.metadata).state), obj.done === true)
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
var textFromResponse = (response) => {
|
|
430
|
+
const candidate = asRecord(asArray(asRecord(response).candidates)[0]);
|
|
431
|
+
const text = asArray(asRecord(candidate.content).parts).map((part) => asString(asRecord(part).text) ?? "").join("");
|
|
432
|
+
return text.length > 0 ? text : undefined;
|
|
433
|
+
};
|
|
434
|
+
var usageFromResponse = (response) => {
|
|
435
|
+
const usage = asRecord(asRecord(response).usageMetadata);
|
|
436
|
+
const inputTokens = asNumber(usage.promptTokenCount);
|
|
437
|
+
const outputTokens = asNumber(usage.candidatesTokenCount);
|
|
438
|
+
const totalTokens = asNumber(usage.totalTokenCount);
|
|
439
|
+
if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
inputTokens,
|
|
444
|
+
outputTokens,
|
|
445
|
+
totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
var normalizeResult2 = (item) => {
|
|
449
|
+
const obj = asRecord(item);
|
|
450
|
+
const customId = asString(asRecord(obj.metadata).key) ?? asString(obj.key) ?? asString(obj.custom_id) ?? "";
|
|
451
|
+
if (obj.error) {
|
|
452
|
+
const error = asRecord(obj.error);
|
|
453
|
+
return {
|
|
454
|
+
customId,
|
|
455
|
+
error: {
|
|
456
|
+
code: asNumber(error.code) ?? asString(error.code),
|
|
457
|
+
message: asString(error.message) ?? "Request errored.",
|
|
458
|
+
type: asString(error.status)
|
|
459
|
+
},
|
|
460
|
+
response: obj.error,
|
|
461
|
+
status: "errored"
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
customId,
|
|
466
|
+
response: obj.response,
|
|
467
|
+
status: "succeeded",
|
|
468
|
+
text: textFromResponse(obj.response),
|
|
469
|
+
usage: usageFromResponse(obj.response)
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
var submit2 = async (input) => {
|
|
473
|
+
const requests = input.built.map((item) => ({
|
|
474
|
+
metadata: { key: item.customId },
|
|
475
|
+
request: omit(item.body, "stream")
|
|
476
|
+
}));
|
|
477
|
+
const raw = await requestJson(`${baseUrl2(input.credentials)}/models/${input.modelId}:batchGenerateContent`, {
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
batch: {
|
|
480
|
+
display_name: "batchwork",
|
|
481
|
+
input_config: { requests: { requests } }
|
|
482
|
+
}
|
|
483
|
+
}),
|
|
484
|
+
headers: headers2(input.credentials),
|
|
485
|
+
method: "POST"
|
|
486
|
+
});
|
|
487
|
+
return normalizeSnapshot2(raw);
|
|
488
|
+
};
|
|
489
|
+
var retrieve2 = async (id, credentials) => {
|
|
490
|
+
const raw = await requestJson(`${baseUrl2(credentials)}/${id}`, {
|
|
491
|
+
headers: headers2(credentials)
|
|
492
|
+
});
|
|
493
|
+
return normalizeSnapshot2(raw);
|
|
494
|
+
};
|
|
495
|
+
async function* results2(id, credentials) {
|
|
496
|
+
const snapshot = await retrieve2(id, credentials);
|
|
497
|
+
const raw = asRecord(snapshot.raw);
|
|
498
|
+
const response = asRecord(raw.response);
|
|
499
|
+
const dest = asRecord(raw.dest);
|
|
500
|
+
const responsesFile = asString(asRecord(response.responsesFile).name) ?? asString(response.responsesFile) ?? asString(asRecord(response.responses_file).name) ?? asString(response.responses_file) ?? asString(dest.fileName) ?? asString(dest.file_name);
|
|
501
|
+
if (responsesFile) {
|
|
502
|
+
throw new BatchworkError(`batchwork: batch "${id}" returned file-mode results, which are not supported yet.`);
|
|
503
|
+
}
|
|
504
|
+
const items = inlinedResponses(raw);
|
|
505
|
+
if (items.length === 0) {
|
|
506
|
+
throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
|
|
507
|
+
}
|
|
508
|
+
for (const item of items) {
|
|
509
|
+
yield normalizeResult2(item);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var cancel2 = async (id, credentials) => {
|
|
513
|
+
await requestJson(`${baseUrl2(credentials)}/${id}:cancel`, {
|
|
514
|
+
headers: headers2(credentials),
|
|
515
|
+
method: "POST"
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
var googleAdapter = {
|
|
519
|
+
cancel: cancel2,
|
|
520
|
+
id: "google",
|
|
521
|
+
results: results2,
|
|
522
|
+
retrieve: retrieve2,
|
|
523
|
+
submit: submit2
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// src/providers/shared.ts
|
|
527
|
+
var HTTP_OK_MIN = 200;
|
|
528
|
+
var HTTP_OK_MAX = 300;
|
|
529
|
+
var resolveApiKey = (credentials, envVar, label) => {
|
|
530
|
+
const key = credentials.apiKey ?? process.env[envVar];
|
|
531
|
+
if (!key) {
|
|
532
|
+
throw new BatchworkError(`batchwork: missing ${label} API key. Set ${envVar} or pass \`apiKey\`.`);
|
|
533
|
+
}
|
|
534
|
+
return key;
|
|
535
|
+
};
|
|
536
|
+
var textFromBody = (body) => {
|
|
537
|
+
const obj = asRecord(body);
|
|
538
|
+
const choices = asArray(obj.choices);
|
|
539
|
+
if (choices.length > 0) {
|
|
540
|
+
const content = asString(asRecord(asRecord(choices[0]).message).content);
|
|
541
|
+
if (content) {
|
|
542
|
+
return content;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return asString(obj.output_text);
|
|
546
|
+
};
|
|
547
|
+
var usageFromBody = (body) => {
|
|
548
|
+
const usage = asRecord(asRecord(body).usage);
|
|
549
|
+
const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens);
|
|
550
|
+
const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens);
|
|
551
|
+
const totalTokens = asNumber(usage.total_tokens);
|
|
552
|
+
if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
inputTokens,
|
|
557
|
+
outputTokens,
|
|
558
|
+
totalTokens: totalTokens ?? (inputTokens ?? 0) + (outputTokens ?? 0)
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
var errorFromValue = (value, fallback) => {
|
|
562
|
+
const obj = asRecord(value);
|
|
563
|
+
const nested = asRecord(obj.error);
|
|
564
|
+
const source = nested.message ? nested : obj;
|
|
565
|
+
return {
|
|
566
|
+
code: asNumber(source.code) ?? asString(source.code),
|
|
567
|
+
message: asString(source.message) ?? fallback,
|
|
568
|
+
type: asString(source.type)
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
var normalizeOpenAIResult = (line) => {
|
|
572
|
+
const obj = asRecord(line);
|
|
573
|
+
const customId = asString(obj.custom_id) ?? "";
|
|
574
|
+
if (obj.error) {
|
|
575
|
+
return {
|
|
576
|
+
customId,
|
|
577
|
+
error: errorFromValue(obj.error, "Request errored."),
|
|
578
|
+
response: obj.error,
|
|
579
|
+
status: "errored"
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const response = asRecord(obj.response);
|
|
583
|
+
const statusCode = asNumber(response.status_code) ?? 0;
|
|
584
|
+
if (statusCode >= HTTP_OK_MIN && statusCode < HTTP_OK_MAX) {
|
|
585
|
+
return {
|
|
586
|
+
customId,
|
|
587
|
+
response: response.body,
|
|
588
|
+
status: "succeeded",
|
|
589
|
+
text: textFromBody(response.body),
|
|
590
|
+
usage: usageFromBody(response.body)
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
customId,
|
|
595
|
+
error: errorFromValue(response.body, `Request failed with status ${statusCode}.`),
|
|
596
|
+
response: response.body,
|
|
597
|
+
status: "errored"
|
|
598
|
+
};
|
|
599
|
+
};
|
|
600
|
+
var uploadInputFile = async (jsonl, baseUrl3, headers3, options = {}) => {
|
|
601
|
+
const form = new FormData;
|
|
602
|
+
const purpose = options.purpose === undefined ? "batch" : options.purpose;
|
|
603
|
+
if (purpose !== null) {
|
|
604
|
+
form.append("purpose", purpose);
|
|
605
|
+
}
|
|
606
|
+
form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batchwork.jsonl");
|
|
607
|
+
const raw = await requestJson(`${baseUrl3}/files`, {
|
|
608
|
+
body: form,
|
|
609
|
+
headers: headers3,
|
|
610
|
+
method: "POST"
|
|
611
|
+
});
|
|
612
|
+
return raw.id;
|
|
613
|
+
};
|
|
614
|
+
async function* streamResultFile(fileId, baseUrl3, headers3) {
|
|
615
|
+
const stream = await requestStream(`${baseUrl3}/files/${fileId}/content`, {
|
|
616
|
+
headers: headers3
|
|
617
|
+
});
|
|
618
|
+
for await (const line of streamJsonl(stream)) {
|
|
619
|
+
yield normalizeOpenAIResult(line);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/providers/openai-compatible.ts
|
|
624
|
+
var DEFAULT_COMPLETION_WINDOW = "24h";
|
|
625
|
+
var mapStatus2 = (status) => {
|
|
626
|
+
const normalized = status?.toLowerCase();
|
|
627
|
+
switch (normalized) {
|
|
628
|
+
case "validating":
|
|
629
|
+
case "in_progress":
|
|
630
|
+
case "finalizing":
|
|
631
|
+
case "completed":
|
|
632
|
+
case "failed":
|
|
633
|
+
case "expired":
|
|
634
|
+
case "cancelling":
|
|
635
|
+
case "cancelled": {
|
|
636
|
+
return normalized;
|
|
637
|
+
}
|
|
638
|
+
default: {
|
|
639
|
+
return "in_progress";
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
var normalizeSnapshot3 = (raw, provider) => {
|
|
644
|
+
const outer = asRecord(raw);
|
|
645
|
+
const obj = asRecord(outer.job);
|
|
646
|
+
const source = Object.keys(obj).length > 0 ? obj : outer;
|
|
647
|
+
const counts = asRecord(source.request_counts);
|
|
648
|
+
return {
|
|
649
|
+
completedAt: toDate(source.completed_at),
|
|
650
|
+
createdAt: toDate(source.created_at),
|
|
651
|
+
expiresAt: toDate(source.expires_at),
|
|
652
|
+
id: asString(source.id) ?? "",
|
|
653
|
+
provider,
|
|
654
|
+
raw: source,
|
|
655
|
+
requestCounts: {
|
|
656
|
+
completed: asNumber(counts.completed) ?? 0,
|
|
657
|
+
failed: asNumber(counts.failed) ?? 0,
|
|
658
|
+
total: asNumber(counts.total) ?? 0
|
|
659
|
+
},
|
|
660
|
+
status: mapStatus2(asString(source.status))
|
|
661
|
+
};
|
|
662
|
+
};
|
|
663
|
+
var createOpenAICompatibleAdapter = (config) => {
|
|
664
|
+
const completionWindow = config.completionWindow ?? DEFAULT_COMPLETION_WINDOW;
|
|
665
|
+
const lineFormat = config.lineFormat ?? "method-url";
|
|
666
|
+
const baseUrl3 = (credentials) => credentials.baseURL ?? config.baseUrl;
|
|
667
|
+
const authHeaders = (credentials) => ({
|
|
668
|
+
Authorization: `Bearer ${resolveApiKey(credentials, config.apiKeyEnv, config.apiKeyLabel)}`,
|
|
669
|
+
...credentials.headers
|
|
670
|
+
});
|
|
671
|
+
const submit3 = async (input) => {
|
|
672
|
+
const endpoint = config.normalizeEndpoint ? config.normalizeEndpoint(input.endpoint) : input.endpoint;
|
|
673
|
+
const jsonl = encodeJsonl(input.built.map((item) => {
|
|
674
|
+
const body = omit(item.body, "stream");
|
|
675
|
+
if (lineFormat === "body-only") {
|
|
676
|
+
return { body, custom_id: item.customId };
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
body,
|
|
680
|
+
custom_id: item.customId,
|
|
681
|
+
method: "POST",
|
|
682
|
+
url: endpoint
|
|
683
|
+
};
|
|
684
|
+
}));
|
|
685
|
+
const headers3 = authHeaders(input.credentials);
|
|
686
|
+
const url = baseUrl3(input.credentials);
|
|
687
|
+
const purpose = config.filePurpose ?? "batch";
|
|
688
|
+
const inputFileId = await (config.uploadFile ? config.uploadFile({ baseUrl: url, headers: headers3, jsonl, purpose }) : uploadInputFile(jsonl, url, headers3, { purpose }));
|
|
689
|
+
const raw = await requestJson(`${url}/batches`, {
|
|
690
|
+
body: JSON.stringify({
|
|
691
|
+
completion_window: completionWindow,
|
|
692
|
+
endpoint,
|
|
693
|
+
input_file_id: inputFileId,
|
|
694
|
+
metadata: input.metadata
|
|
695
|
+
}),
|
|
696
|
+
headers: { ...headers3, "content-type": "application/json" },
|
|
697
|
+
method: "POST"
|
|
698
|
+
});
|
|
699
|
+
return normalizeSnapshot3(raw, config.id);
|
|
700
|
+
};
|
|
701
|
+
const retrieve3 = async (id, credentials) => {
|
|
702
|
+
const raw = await requestJson(`${baseUrl3(credentials)}/batches/${id}`, {
|
|
703
|
+
headers: authHeaders(credentials)
|
|
704
|
+
});
|
|
705
|
+
return normalizeSnapshot3(raw, config.id);
|
|
706
|
+
};
|
|
707
|
+
async function* results3(id, credentials) {
|
|
708
|
+
const snapshot = await retrieve3(id, credentials);
|
|
709
|
+
const raw = asRecord(snapshot.raw);
|
|
710
|
+
const outputFileId = asString(raw.output_file_id);
|
|
711
|
+
const errorFileId = asString(raw.error_file_id);
|
|
712
|
+
if (!(outputFileId || errorFileId)) {
|
|
713
|
+
throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
|
|
714
|
+
}
|
|
715
|
+
const headers3 = authHeaders(credentials);
|
|
716
|
+
if (outputFileId) {
|
|
717
|
+
yield* streamResultFile(outputFileId, baseUrl3(credentials), headers3);
|
|
718
|
+
}
|
|
719
|
+
if (errorFileId) {
|
|
720
|
+
yield* streamResultFile(errorFileId, baseUrl3(credentials), headers3);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const cancel3 = async (id, credentials) => {
|
|
724
|
+
await requestJson(`${baseUrl3(credentials)}/batches/${id}/cancel`, {
|
|
725
|
+
headers: authHeaders(credentials),
|
|
726
|
+
method: "POST"
|
|
727
|
+
});
|
|
728
|
+
};
|
|
729
|
+
return { cancel: cancel3, id: config.id, results: results3, retrieve: retrieve3, submit: submit3 };
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/providers/groq.ts
|
|
733
|
+
var groqAdapter = createOpenAICompatibleAdapter({
|
|
734
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
735
|
+
apiKeyLabel: "Groq",
|
|
736
|
+
baseUrl: "https://api.groq.com/openai/v1",
|
|
737
|
+
id: "groq",
|
|
738
|
+
lineFormat: "method-url",
|
|
739
|
+
normalizeEndpoint: (endpoint) => endpoint.replace(/^\/openai/u, "")
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// src/providers/mistral.ts
|
|
743
|
+
var MISTRAL_BASE = "https://api.mistral.ai/v1";
|
|
744
|
+
var apiKey3 = (credentials) => resolveApiKey(credentials, "MISTRAL_API_KEY", "Mistral");
|
|
745
|
+
var baseUrl3 = (credentials) => credentials.baseURL ?? MISTRAL_BASE;
|
|
746
|
+
var authHeaders = (credentials) => ({
|
|
747
|
+
Authorization: `Bearer ${apiKey3(credentials)}`,
|
|
748
|
+
...credentials.headers
|
|
749
|
+
});
|
|
750
|
+
var mapStatus3 = (status) => {
|
|
751
|
+
switch (status) {
|
|
752
|
+
case "QUEUED": {
|
|
753
|
+
return "validating";
|
|
754
|
+
}
|
|
755
|
+
case "SUCCESS": {
|
|
756
|
+
return "completed";
|
|
757
|
+
}
|
|
758
|
+
case "FAILED": {
|
|
759
|
+
return "failed";
|
|
760
|
+
}
|
|
761
|
+
case "TIMEOUT_EXCEEDED": {
|
|
762
|
+
return "expired";
|
|
763
|
+
}
|
|
764
|
+
case "CANCELLATION_REQUESTED": {
|
|
765
|
+
return "cancelling";
|
|
766
|
+
}
|
|
767
|
+
case "CANCELLED": {
|
|
768
|
+
return "cancelled";
|
|
769
|
+
}
|
|
770
|
+
default: {
|
|
771
|
+
return "in_progress";
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
var normalizeSnapshot4 = (raw) => {
|
|
776
|
+
const obj = asRecord(raw);
|
|
777
|
+
const succeeded = asNumber(obj.succeeded_requests) ?? 0;
|
|
778
|
+
const failed = asNumber(obj.failed_requests) ?? 0;
|
|
779
|
+
return {
|
|
780
|
+
completedAt: toDate(obj.completed_at),
|
|
781
|
+
createdAt: toDate(obj.created_at),
|
|
782
|
+
id: asString(obj.id) ?? "",
|
|
783
|
+
provider: "mistral",
|
|
784
|
+
raw,
|
|
785
|
+
requestCounts: {
|
|
786
|
+
completed: succeeded,
|
|
787
|
+
failed,
|
|
788
|
+
total: asNumber(obj.total_requests) ?? succeeded + failed
|
|
789
|
+
},
|
|
790
|
+
status: mapStatus3(asString(obj.status))
|
|
791
|
+
};
|
|
792
|
+
};
|
|
793
|
+
var submit3 = async (input) => {
|
|
794
|
+
const jsonl = encodeJsonl(input.built.map((item) => ({
|
|
795
|
+
body: omit(omit(item.body, "stream"), "model"),
|
|
796
|
+
custom_id: item.customId
|
|
797
|
+
})));
|
|
798
|
+
const inputFileId = await uploadInputFile(jsonl, baseUrl3(input.credentials), authHeaders(input.credentials));
|
|
799
|
+
const raw = await requestJson(`${baseUrl3(input.credentials)}/batch/jobs`, {
|
|
800
|
+
body: JSON.stringify({
|
|
801
|
+
endpoint: input.endpoint,
|
|
802
|
+
input_files: [inputFileId],
|
|
803
|
+
metadata: input.metadata,
|
|
804
|
+
model: input.modelId
|
|
805
|
+
}),
|
|
806
|
+
headers: {
|
|
807
|
+
...authHeaders(input.credentials),
|
|
808
|
+
"content-type": "application/json"
|
|
809
|
+
},
|
|
810
|
+
method: "POST"
|
|
811
|
+
});
|
|
812
|
+
return normalizeSnapshot4(raw);
|
|
813
|
+
};
|
|
814
|
+
var retrieve3 = async (id, credentials) => {
|
|
815
|
+
const raw = await requestJson(`${baseUrl3(credentials)}/batch/jobs/${id}`, {
|
|
816
|
+
headers: authHeaders(credentials)
|
|
817
|
+
});
|
|
818
|
+
return normalizeSnapshot4(raw);
|
|
819
|
+
};
|
|
820
|
+
async function* results3(id, credentials) {
|
|
821
|
+
const snapshot = await retrieve3(id, credentials);
|
|
822
|
+
const raw = asRecord(snapshot.raw);
|
|
823
|
+
const outputFileId = asString(raw.output_file);
|
|
824
|
+
const errorFileId = asString(raw.error_file);
|
|
825
|
+
const headers3 = authHeaders(credentials);
|
|
826
|
+
if (outputFileId) {
|
|
827
|
+
yield* streamResultFile(outputFileId, baseUrl3(credentials), headers3);
|
|
828
|
+
}
|
|
829
|
+
if (errorFileId) {
|
|
830
|
+
yield* streamResultFile(errorFileId, baseUrl3(credentials), headers3);
|
|
831
|
+
}
|
|
832
|
+
if (!(outputFileId || errorFileId)) {
|
|
833
|
+
throw new BatchworkError(`batchwork: results are not ready for batch "${id}" (status: ${snapshot.status}).`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
var cancel3 = async (id, credentials) => {
|
|
837
|
+
await requestJson(`${baseUrl3(credentials)}/batch/jobs/${id}/cancel`, {
|
|
838
|
+
headers: authHeaders(credentials),
|
|
839
|
+
method: "POST"
|
|
840
|
+
});
|
|
841
|
+
};
|
|
842
|
+
var mistralAdapter = {
|
|
843
|
+
cancel: cancel3,
|
|
844
|
+
id: "mistral",
|
|
845
|
+
results: results3,
|
|
846
|
+
retrieve: retrieve3,
|
|
847
|
+
submit: submit3
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/providers/openai.ts
|
|
851
|
+
var openaiAdapter = createOpenAICompatibleAdapter({
|
|
852
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
853
|
+
apiKeyLabel: "OpenAI",
|
|
854
|
+
baseUrl: "https://api.openai.com/v1",
|
|
855
|
+
id: "openai",
|
|
856
|
+
lineFormat: "method-url"
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// src/providers/together.ts
|
|
860
|
+
var INPUT_FILE_NAME = "batchwork.jsonl";
|
|
861
|
+
var HTTP_FOUND = 302;
|
|
862
|
+
var safeText2 = async (response) => {
|
|
863
|
+
try {
|
|
864
|
+
return await response.text();
|
|
865
|
+
} catch {
|
|
866
|
+
return "<no body>";
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
var uploadTogetherFile = async (args) => {
|
|
870
|
+
const metadata = new FormData;
|
|
871
|
+
metadata.append("purpose", args.purpose);
|
|
872
|
+
metadata.append("file_name", INPUT_FILE_NAME);
|
|
873
|
+
metadata.append("file_type", "jsonl");
|
|
874
|
+
const init = await fetch(`${args.baseUrl}/files`, {
|
|
875
|
+
body: metadata,
|
|
876
|
+
headers: args.headers,
|
|
877
|
+
method: "POST",
|
|
878
|
+
redirect: "manual"
|
|
879
|
+
});
|
|
880
|
+
const location = init.headers.get("location");
|
|
881
|
+
const fileId = init.headers.get("x-together-file-id");
|
|
882
|
+
if (init.status !== HTTP_FOUND || !(location && fileId)) {
|
|
883
|
+
throw new BatchworkError(`batchwork: Together upload could not be initiated (${init.status}): ${await safeText2(init)}`);
|
|
884
|
+
}
|
|
885
|
+
const upload = await fetch(location, { body: args.jsonl, method: "PUT" });
|
|
886
|
+
if (!upload.ok) {
|
|
887
|
+
throw new BatchworkError(`batchwork: Together file upload failed (${upload.status}): ${await safeText2(upload)}`);
|
|
888
|
+
}
|
|
889
|
+
await requestJson(`${args.baseUrl}/files/${fileId}/preprocess`, {
|
|
890
|
+
headers: args.headers,
|
|
891
|
+
method: "POST"
|
|
892
|
+
});
|
|
893
|
+
return fileId;
|
|
894
|
+
};
|
|
895
|
+
var togetherAdapter = createOpenAICompatibleAdapter({
|
|
896
|
+
apiKeyEnv: "TOGETHER_API_KEY",
|
|
897
|
+
apiKeyLabel: "Together AI",
|
|
898
|
+
baseUrl: "https://api.together.xyz/v1",
|
|
899
|
+
filePurpose: "batch-api",
|
|
900
|
+
id: "together",
|
|
901
|
+
lineFormat: "body-only",
|
|
902
|
+
uploadFile: uploadTogetherFile
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// src/providers/xai.ts
|
|
906
|
+
var XAI_BASE = "https://api.x.ai/v1";
|
|
907
|
+
var RESULTS_PAGE_SIZE = 100;
|
|
908
|
+
var apiKey4 = (credentials) => resolveApiKey(credentials, "XAI_API_KEY", "xAI");
|
|
909
|
+
var baseUrl4 = (credentials) => credentials.baseURL ?? XAI_BASE;
|
|
910
|
+
var authHeaders2 = (credentials) => ({
|
|
911
|
+
Authorization: `Bearer ${apiKey4(credentials)}`,
|
|
912
|
+
...credentials.headers
|
|
913
|
+
});
|
|
914
|
+
var deriveStatus = (state) => {
|
|
915
|
+
const pending = asNumber(state.num_pending);
|
|
916
|
+
if (pending === undefined) {
|
|
917
|
+
return "in_progress";
|
|
918
|
+
}
|
|
919
|
+
const total = asNumber(state.num_requests) ?? 0;
|
|
920
|
+
const cancelled = asNumber(state.num_cancelled) ?? 0;
|
|
921
|
+
if (total === 0) {
|
|
922
|
+
return "in_progress";
|
|
923
|
+
}
|
|
924
|
+
if (pending > 0) {
|
|
925
|
+
return "in_progress";
|
|
926
|
+
}
|
|
927
|
+
if (cancelled > 0 && cancelled === total) {
|
|
928
|
+
return "cancelled";
|
|
929
|
+
}
|
|
930
|
+
return "completed";
|
|
931
|
+
};
|
|
932
|
+
var normalizeSnapshot5 = (raw) => {
|
|
933
|
+
const obj = asRecord(raw);
|
|
934
|
+
const state = asRecord(obj.state);
|
|
935
|
+
return {
|
|
936
|
+
completedAt: toDate(obj.cancel_time),
|
|
937
|
+
createdAt: toDate(obj.create_time),
|
|
938
|
+
expiresAt: toDate(obj.expire_time ?? obj.expires_at),
|
|
939
|
+
id: asString(obj.batch_id) ?? asString(obj.id) ?? "",
|
|
940
|
+
provider: "xai",
|
|
941
|
+
raw,
|
|
942
|
+
requestCounts: {
|
|
943
|
+
canceled: asNumber(state.num_cancelled) ?? 0,
|
|
944
|
+
completed: asNumber(state.num_success) ?? 0,
|
|
945
|
+
failed: asNumber(state.num_error) ?? 0,
|
|
946
|
+
processing: asNumber(state.num_pending) ?? 0,
|
|
947
|
+
total: asNumber(state.num_requests) ?? 0
|
|
948
|
+
},
|
|
949
|
+
status: deriveStatus(state)
|
|
950
|
+
};
|
|
951
|
+
};
|
|
952
|
+
var normalizeResult3 = (item) => {
|
|
953
|
+
const obj = asRecord(item);
|
|
954
|
+
const customId = asString(obj.batch_request_id) ?? "";
|
|
955
|
+
const batchResult = asRecord(obj.batch_result);
|
|
956
|
+
const resultError = batchResult.error;
|
|
957
|
+
const errorMessage = asString(obj.error_message) ?? asString(resultError) ?? asString(asRecord(resultError).message);
|
|
958
|
+
if (errorMessage) {
|
|
959
|
+
return {
|
|
960
|
+
customId,
|
|
961
|
+
error: {
|
|
962
|
+
message: errorMessage,
|
|
963
|
+
type: asString(asRecord(resultError).type)
|
|
964
|
+
},
|
|
965
|
+
response: obj,
|
|
966
|
+
status: "errored"
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const response = asRecord(batchResult.response);
|
|
970
|
+
const completion = response.chat_get_completion ?? Object.values(response)[0];
|
|
971
|
+
return {
|
|
972
|
+
customId,
|
|
973
|
+
response: completion,
|
|
974
|
+
status: "succeeded",
|
|
975
|
+
text: textFromBody(completion),
|
|
976
|
+
usage: usageFromBody(completion)
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
var submit4 = async (input) => {
|
|
980
|
+
const jsonl = encodeJsonl(input.built.map((item) => ({
|
|
981
|
+
body: omit(item.body, "stream"),
|
|
982
|
+
custom_id: item.customId,
|
|
983
|
+
method: "POST",
|
|
984
|
+
url: input.endpoint
|
|
985
|
+
})));
|
|
986
|
+
const inputFileId = await uploadInputFile(jsonl, baseUrl4(input.credentials), authHeaders2(input.credentials), { purpose: null });
|
|
987
|
+
const raw = await requestJson(`${baseUrl4(input.credentials)}/batches`, {
|
|
988
|
+
body: JSON.stringify({ input_file_id: inputFileId, name: "batchwork" }),
|
|
989
|
+
headers: {
|
|
990
|
+
...authHeaders2(input.credentials),
|
|
991
|
+
"content-type": "application/json"
|
|
992
|
+
},
|
|
993
|
+
method: "POST"
|
|
994
|
+
});
|
|
995
|
+
return normalizeSnapshot5(raw);
|
|
996
|
+
};
|
|
997
|
+
var retrieve4 = async (id, credentials) => {
|
|
998
|
+
const raw = await requestJson(`${baseUrl4(credentials)}/batches/${id}`, {
|
|
999
|
+
headers: authHeaders2(credentials)
|
|
1000
|
+
});
|
|
1001
|
+
return normalizeSnapshot5(raw);
|
|
1002
|
+
};
|
|
1003
|
+
async function* results4(id, credentials) {
|
|
1004
|
+
const headers3 = authHeaders2(credentials);
|
|
1005
|
+
let token;
|
|
1006
|
+
do {
|
|
1007
|
+
const query = new URLSearchParams({ limit: String(RESULTS_PAGE_SIZE) });
|
|
1008
|
+
if (token) {
|
|
1009
|
+
query.set("pagination_token", token);
|
|
1010
|
+
}
|
|
1011
|
+
const raw = await requestJson(`${baseUrl4(credentials)}/batches/${id}/results?${query.toString()}`, { headers: headers3 });
|
|
1012
|
+
const page = asRecord(raw);
|
|
1013
|
+
for (const item of Array.isArray(page.results) ? page.results : []) {
|
|
1014
|
+
yield normalizeResult3(item);
|
|
1015
|
+
}
|
|
1016
|
+
token = asString(page.pagination_token);
|
|
1017
|
+
} while (token);
|
|
1018
|
+
}
|
|
1019
|
+
var cancel4 = async (id, credentials) => {
|
|
1020
|
+
await requestJson(`${baseUrl4(credentials)}/batches/${id}:cancel`, {
|
|
1021
|
+
headers: authHeaders2(credentials),
|
|
1022
|
+
method: "POST"
|
|
1023
|
+
});
|
|
1024
|
+
};
|
|
1025
|
+
var xaiAdapter = {
|
|
1026
|
+
cancel: cancel4,
|
|
1027
|
+
id: "xai",
|
|
1028
|
+
results: results4,
|
|
1029
|
+
retrieve: retrieve4,
|
|
1030
|
+
submit: submit4
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// src/providers/index.ts
|
|
1034
|
+
var adapters = {
|
|
1035
|
+
anthropic: anthropicAdapter,
|
|
1036
|
+
google: googleAdapter,
|
|
1037
|
+
groq: groqAdapter,
|
|
1038
|
+
mistral: mistralAdapter,
|
|
1039
|
+
openai: openaiAdapter,
|
|
1040
|
+
together: togetherAdapter,
|
|
1041
|
+
xai: xaiAdapter
|
|
1042
|
+
};
|
|
1043
|
+
var getAdapter = (provider) => adapters[provider];
|
|
1044
|
+
|
|
1045
|
+
export { BatchworkError, UnsupportedProviderError, MissingDependencyError, isTerminalStatus, BatchJob, getAdapter };
|
|
1046
|
+
|
|
1047
|
+
//# debugId=DA60AE45A8F12B3C64756E2164756E21
|
|
1048
|
+
//# sourceMappingURL=chunk-kv3847wy.js.map
|