@probeo/anymodel 0.2.0 → 0.3.1
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/README.md +83 -6
- package/dist/cli.cjs +799 -104
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +777 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +820 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -22
- package/dist/index.d.ts +106 -22
- package/dist/index.js +798 -105
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -25,9 +35,20 @@ __export(src_exports, {
|
|
|
25
35
|
BatchManager: () => BatchManager,
|
|
26
36
|
BatchStore: () => BatchStore,
|
|
27
37
|
GenerationStatsStore: () => GenerationStatsStore,
|
|
38
|
+
appendFileQueued: () => appendFileQueued,
|
|
39
|
+
configureFsIO: () => configureFsIO,
|
|
40
|
+
createAnthropicBatchAdapter: () => createAnthropicBatchAdapter,
|
|
28
41
|
createAnyModelServer: () => createAnyModelServer,
|
|
42
|
+
createOpenAIBatchAdapter: () => createOpenAIBatchAdapter,
|
|
43
|
+
ensureDir: () => ensureDir,
|
|
44
|
+
getFsQueueStatus: () => getFsQueueStatus,
|
|
45
|
+
joinPath: () => joinPath,
|
|
46
|
+
readFileQueued: () => readFileQueued,
|
|
29
47
|
resolveConfig: () => resolveConfig,
|
|
30
|
-
startServer: () => startServer
|
|
48
|
+
startServer: () => startServer,
|
|
49
|
+
waitForFsQueuesIdle: () => waitForFsQueuesIdle,
|
|
50
|
+
writeFileFlushedQueued: () => writeFileFlushedQueued,
|
|
51
|
+
writeFileQueued: () => writeFileQueued
|
|
31
52
|
});
|
|
32
53
|
module.exports = __toCommonJS(src_exports);
|
|
33
54
|
|
|
@@ -175,7 +196,7 @@ async function withRetry(fn, options = {}) {
|
|
|
175
196
|
throw error;
|
|
176
197
|
}
|
|
177
198
|
const delay = computeDelay(attempt, opts, error);
|
|
178
|
-
await new Promise((
|
|
199
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
179
200
|
}
|
|
180
201
|
}
|
|
181
202
|
throw lastError;
|
|
@@ -534,8 +555,8 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
534
555
|
]);
|
|
535
556
|
function createOpenAIAdapter(apiKey, baseURL) {
|
|
536
557
|
const base = baseURL || OPENAI_API_BASE;
|
|
537
|
-
async function makeRequest(
|
|
538
|
-
const res = await fetch(`${base}${
|
|
558
|
+
async function makeRequest(path2, body, method = "POST") {
|
|
559
|
+
const res = await fetch(`${base}${path2}`, {
|
|
539
560
|
method,
|
|
540
561
|
headers: {
|
|
541
562
|
"Content-Type": "application/json",
|
|
@@ -686,6 +707,9 @@ function createOpenAIAdapter(apiKey, baseURL) {
|
|
|
686
707
|
supportsParameter(param) {
|
|
687
708
|
return SUPPORTED_PARAMS.has(param);
|
|
688
709
|
},
|
|
710
|
+
supportsBatch() {
|
|
711
|
+
return true;
|
|
712
|
+
},
|
|
689
713
|
async sendRequest(request) {
|
|
690
714
|
const body = buildRequestBody(request);
|
|
691
715
|
const res = await makeRequest("/chat/completions", body);
|
|
@@ -740,8 +764,8 @@ var FALLBACK_MODELS = [
|
|
|
740
764
|
{ id: "anthropic/claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku", created: 0, description: "Legacy fast model", context_length: 2e5, pricing: { prompt: "0.0000008", completion: "0.000004" }, architecture: { modality: "text+image->text", input_modalities: ["text", "image"], output_modalities: ["text"], tokenizer: "claude" }, top_provider: { context_length: 2e5, max_completion_tokens: 8192, is_moderated: false }, supported_parameters: Array.from(SUPPORTED_PARAMS2) }
|
|
741
765
|
];
|
|
742
766
|
function createAnthropicAdapter(apiKey) {
|
|
743
|
-
async function makeRequest(
|
|
744
|
-
const res = await fetch(`${ANTHROPIC_API_BASE}${
|
|
767
|
+
async function makeRequest(path2, body, stream = false) {
|
|
768
|
+
const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
|
|
745
769
|
method: "POST",
|
|
746
770
|
headers: {
|
|
747
771
|
"Content-Type": "application/json",
|
|
@@ -1035,6 +1059,9 @@ ${body.system}` : jsonInstruction;
|
|
|
1035
1059
|
supportsParameter(param) {
|
|
1036
1060
|
return SUPPORTED_PARAMS2.has(param);
|
|
1037
1061
|
},
|
|
1062
|
+
supportsBatch() {
|
|
1063
|
+
return true;
|
|
1064
|
+
},
|
|
1038
1065
|
async sendRequest(request) {
|
|
1039
1066
|
const body = translateRequest(request);
|
|
1040
1067
|
const res = await makeRequest("/messages", body);
|
|
@@ -1314,6 +1341,9 @@ function createGoogleAdapter(apiKey) {
|
|
|
1314
1341
|
supportsParameter(param) {
|
|
1315
1342
|
return SUPPORTED_PARAMS3.has(param);
|
|
1316
1343
|
},
|
|
1344
|
+
supportsBatch() {
|
|
1345
|
+
return false;
|
|
1346
|
+
},
|
|
1317
1347
|
async sendRequest(request) {
|
|
1318
1348
|
const body = translateRequest(request);
|
|
1319
1349
|
const url = getModelEndpoint(request.model, false);
|
|
@@ -1372,6 +1402,9 @@ function createCustomAdapter(name, config) {
|
|
|
1372
1402
|
return {
|
|
1373
1403
|
...openaiAdapter,
|
|
1374
1404
|
name,
|
|
1405
|
+
supportsBatch() {
|
|
1406
|
+
return false;
|
|
1407
|
+
},
|
|
1375
1408
|
async listModels() {
|
|
1376
1409
|
if (config.models && config.models.length > 0) {
|
|
1377
1410
|
return config.models.map((modelId) => ({
|
|
@@ -1437,10 +1470,10 @@ function interpolateDeep(obj) {
|
|
|
1437
1470
|
}
|
|
1438
1471
|
return obj;
|
|
1439
1472
|
}
|
|
1440
|
-
function loadJsonFile(
|
|
1441
|
-
if (!(0, import_node_fs.existsSync)(
|
|
1473
|
+
function loadJsonFile(path2) {
|
|
1474
|
+
if (!(0, import_node_fs.existsSync)(path2)) return null;
|
|
1442
1475
|
try {
|
|
1443
|
-
const raw = (0, import_node_fs.readFileSync)(
|
|
1476
|
+
const raw = (0, import_node_fs.readFileSync)(path2, "utf-8");
|
|
1444
1477
|
const parsed = JSON.parse(raw);
|
|
1445
1478
|
return interpolateDeep(parsed);
|
|
1446
1479
|
} catch {
|
|
@@ -1541,93 +1574,237 @@ var GenerationStatsStore = class {
|
|
|
1541
1574
|
}
|
|
1542
1575
|
};
|
|
1543
1576
|
|
|
1544
|
-
// src/
|
|
1577
|
+
// src/utils/fs-io.ts
|
|
1578
|
+
var import_promises = require("fs/promises");
|
|
1545
1579
|
var import_node_fs2 = require("fs");
|
|
1546
|
-
var import_node_path2 = require("path");
|
|
1547
|
-
var
|
|
1548
|
-
var
|
|
1580
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1581
|
+
var import_p_queue = __toESM(require("p-queue"), 1);
|
|
1582
|
+
var writeQueue = new import_p_queue.default({ concurrency: 10 });
|
|
1583
|
+
var readQueue = new import_p_queue.default({ concurrency: 20 });
|
|
1584
|
+
function configureFsIO(options) {
|
|
1585
|
+
if (options.readConcurrency !== void 0) {
|
|
1586
|
+
readQueue.concurrency = options.readConcurrency;
|
|
1587
|
+
}
|
|
1588
|
+
if (options.writeConcurrency !== void 0) {
|
|
1589
|
+
writeQueue.concurrency = options.writeConcurrency;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
var ensuredDirs = /* @__PURE__ */ new Set();
|
|
1593
|
+
var joinPathCache = /* @__PURE__ */ new Map();
|
|
1594
|
+
var dirnameCache = /* @__PURE__ */ new Map();
|
|
1595
|
+
var resolvePathCache = /* @__PURE__ */ new Map();
|
|
1596
|
+
async function ensureDir(dir) {
|
|
1597
|
+
if (!dir) return;
|
|
1598
|
+
if (ensuredDirs.has(dir)) return;
|
|
1599
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
1600
|
+
ensuredDirs.add(dir);
|
|
1601
|
+
}
|
|
1602
|
+
async function readFileQueued(filePath, encoding = "utf8") {
|
|
1603
|
+
return readQueue.add(async () => {
|
|
1604
|
+
return (0, import_promises.readFile)(filePath, encoding);
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
async function readJsonQueued(filePath) {
|
|
1608
|
+
const raw = await readFileQueued(filePath, "utf8");
|
|
1609
|
+
return JSON.parse(raw);
|
|
1610
|
+
}
|
|
1611
|
+
async function readDirQueued(dirPath) {
|
|
1612
|
+
return readQueue.add(async () => {
|
|
1613
|
+
return (0, import_promises.readdir)(dirPath, { withFileTypes: true });
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
async function pathExistsQueued(p) {
|
|
1617
|
+
return readQueue.add(async () => {
|
|
1618
|
+
try {
|
|
1619
|
+
await (0, import_promises.stat)(p);
|
|
1620
|
+
return true;
|
|
1621
|
+
} catch {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
async function fileExistsQueued(filePath) {
|
|
1627
|
+
return readQueue.add(async () => {
|
|
1628
|
+
try {
|
|
1629
|
+
const s = await (0, import_promises.stat)(filePath);
|
|
1630
|
+
return s.isFile();
|
|
1631
|
+
} catch {
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
async function writeFileQueued(filePath, data) {
|
|
1637
|
+
await writeQueue.add(async () => {
|
|
1638
|
+
const dir = dirnameOf(filePath);
|
|
1639
|
+
await ensureDir(dir);
|
|
1640
|
+
await (0, import_promises.writeFile)(filePath, data);
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
async function appendFileQueued(filePath, data) {
|
|
1644
|
+
await writeQueue.add(async () => {
|
|
1645
|
+
const dir = dirnameOf(filePath);
|
|
1646
|
+
await ensureDir(dir);
|
|
1647
|
+
await (0, import_promises.writeFile)(filePath, data, { flag: "a" });
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
async function writeFileFlushedQueued(filePath, data) {
|
|
1651
|
+
await writeQueue.add(async () => {
|
|
1652
|
+
const dir = dirnameOf(filePath);
|
|
1653
|
+
await ensureDir(dir);
|
|
1654
|
+
const tmpPath = joinPath(
|
|
1655
|
+
dir,
|
|
1656
|
+
`.${import_node_path2.default.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
1657
|
+
);
|
|
1658
|
+
const fh = await (0, import_promises.open)(tmpPath, "w");
|
|
1659
|
+
try {
|
|
1660
|
+
await fh.writeFile(data);
|
|
1661
|
+
await fh.sync();
|
|
1662
|
+
} finally {
|
|
1663
|
+
await fh.close();
|
|
1664
|
+
}
|
|
1665
|
+
await (0, import_promises.rename)(tmpPath, filePath);
|
|
1666
|
+
try {
|
|
1667
|
+
const dh = await (0, import_promises.open)(dir, "r");
|
|
1668
|
+
try {
|
|
1669
|
+
await dh.sync();
|
|
1670
|
+
} finally {
|
|
1671
|
+
await dh.close();
|
|
1672
|
+
}
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
function joinPath(...segments) {
|
|
1678
|
+
const key = segments.join("\0");
|
|
1679
|
+
const cached = joinPathCache.get(key);
|
|
1680
|
+
if (cached !== void 0) return cached;
|
|
1681
|
+
const out = import_node_path2.default.join(...segments);
|
|
1682
|
+
joinPathCache.set(key, out);
|
|
1683
|
+
return out;
|
|
1684
|
+
}
|
|
1685
|
+
function dirnameOf(p) {
|
|
1686
|
+
const cached = dirnameCache.get(p);
|
|
1687
|
+
if (cached !== void 0) return cached;
|
|
1688
|
+
const out = import_node_path2.default.dirname(p);
|
|
1689
|
+
dirnameCache.set(p, out);
|
|
1690
|
+
return out;
|
|
1691
|
+
}
|
|
1692
|
+
function resolvePath(...segments) {
|
|
1693
|
+
const key = segments.join("\0");
|
|
1694
|
+
const cached = resolvePathCache.get(key);
|
|
1695
|
+
if (cached !== void 0) return cached;
|
|
1696
|
+
const out = import_node_path2.default.resolve(...segments);
|
|
1697
|
+
resolvePathCache.set(key, out);
|
|
1698
|
+
return out;
|
|
1699
|
+
}
|
|
1700
|
+
function getFsQueueStatus() {
|
|
1701
|
+
return {
|
|
1702
|
+
read: { size: readQueue.size, pending: readQueue.pending },
|
|
1703
|
+
write: { size: writeQueue.size, pending: writeQueue.pending }
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
async function waitForFsQueuesIdle() {
|
|
1707
|
+
await Promise.all([writeQueue.onIdle(), readQueue.onIdle()]);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// src/batch/store.ts
|
|
1711
|
+
var DEFAULT_BATCH_DIR = joinPath(process.cwd(), ".anymodel", "batches");
|
|
1549
1712
|
var BatchStore = class {
|
|
1550
1713
|
dir;
|
|
1714
|
+
initialized = false;
|
|
1551
1715
|
constructor(dir) {
|
|
1552
|
-
this.dir = (
|
|
1553
|
-
|
|
1716
|
+
this.dir = resolvePath(dir || DEFAULT_BATCH_DIR);
|
|
1717
|
+
}
|
|
1718
|
+
async init() {
|
|
1719
|
+
if (this.initialized) return;
|
|
1720
|
+
await ensureDir(this.dir);
|
|
1721
|
+
this.initialized = true;
|
|
1554
1722
|
}
|
|
1555
1723
|
batchDir(id) {
|
|
1556
|
-
return (
|
|
1724
|
+
return joinPath(this.dir, id);
|
|
1557
1725
|
}
|
|
1558
1726
|
/**
|
|
1559
1727
|
* Create a new batch directory and save initial metadata.
|
|
1560
1728
|
*/
|
|
1561
|
-
create(batch) {
|
|
1729
|
+
async create(batch) {
|
|
1730
|
+
await this.init();
|
|
1562
1731
|
const dir = this.batchDir(batch.id);
|
|
1563
|
-
|
|
1564
|
-
|
|
1732
|
+
await ensureDir(dir);
|
|
1733
|
+
await writeFileFlushedQueued(joinPath(dir, "meta.json"), JSON.stringify(batch, null, 2));
|
|
1565
1734
|
}
|
|
1566
1735
|
/**
|
|
1567
|
-
* Update batch metadata.
|
|
1736
|
+
* Update batch metadata (atomic write).
|
|
1568
1737
|
*/
|
|
1569
|
-
updateMeta(batch) {
|
|
1570
|
-
|
|
1571
|
-
|
|
1738
|
+
async updateMeta(batch) {
|
|
1739
|
+
await writeFileFlushedQueued(
|
|
1740
|
+
joinPath(this.batchDir(batch.id), "meta.json"),
|
|
1741
|
+
JSON.stringify(batch, null, 2)
|
|
1742
|
+
);
|
|
1572
1743
|
}
|
|
1573
1744
|
/**
|
|
1574
1745
|
* Save requests as JSONL.
|
|
1575
1746
|
*/
|
|
1576
|
-
saveRequests(id, requests) {
|
|
1577
|
-
const dir = this.batchDir(id);
|
|
1747
|
+
async saveRequests(id, requests) {
|
|
1578
1748
|
const lines = requests.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
1579
|
-
|
|
1749
|
+
await writeFileQueued(joinPath(this.batchDir(id), "requests.jsonl"), lines);
|
|
1580
1750
|
}
|
|
1581
1751
|
/**
|
|
1582
1752
|
* Append a result to results.jsonl.
|
|
1583
1753
|
*/
|
|
1584
|
-
appendResult(id, result) {
|
|
1585
|
-
|
|
1586
|
-
|
|
1754
|
+
async appendResult(id, result) {
|
|
1755
|
+
await appendFileQueued(
|
|
1756
|
+
joinPath(this.batchDir(id), "results.jsonl"),
|
|
1757
|
+
JSON.stringify(result) + "\n"
|
|
1758
|
+
);
|
|
1587
1759
|
}
|
|
1588
1760
|
/**
|
|
1589
1761
|
* Save provider-specific state (e.g., provider batch ID).
|
|
1590
1762
|
*/
|
|
1591
|
-
saveProviderState(id, state) {
|
|
1592
|
-
|
|
1593
|
-
|
|
1763
|
+
async saveProviderState(id, state) {
|
|
1764
|
+
await writeFileFlushedQueued(
|
|
1765
|
+
joinPath(this.batchDir(id), "provider.json"),
|
|
1766
|
+
JSON.stringify(state, null, 2)
|
|
1767
|
+
);
|
|
1594
1768
|
}
|
|
1595
1769
|
/**
|
|
1596
1770
|
* Load provider state.
|
|
1597
1771
|
*/
|
|
1598
|
-
loadProviderState(id) {
|
|
1599
|
-
const
|
|
1600
|
-
if (!
|
|
1601
|
-
return
|
|
1772
|
+
async loadProviderState(id) {
|
|
1773
|
+
const p = joinPath(this.batchDir(id), "provider.json");
|
|
1774
|
+
if (!await fileExistsQueued(p)) return null;
|
|
1775
|
+
return readJsonQueued(p);
|
|
1602
1776
|
}
|
|
1603
1777
|
/**
|
|
1604
1778
|
* Get batch metadata.
|
|
1605
1779
|
*/
|
|
1606
|
-
getMeta(id) {
|
|
1607
|
-
const
|
|
1608
|
-
if (!
|
|
1609
|
-
return
|
|
1780
|
+
async getMeta(id) {
|
|
1781
|
+
const p = joinPath(this.batchDir(id), "meta.json");
|
|
1782
|
+
if (!await fileExistsQueued(p)) return null;
|
|
1783
|
+
return readJsonQueued(p);
|
|
1610
1784
|
}
|
|
1611
1785
|
/**
|
|
1612
1786
|
* Get all results for a batch.
|
|
1613
1787
|
*/
|
|
1614
|
-
getResults(id) {
|
|
1615
|
-
const
|
|
1616
|
-
if (!
|
|
1617
|
-
|
|
1788
|
+
async getResults(id) {
|
|
1789
|
+
const p = joinPath(this.batchDir(id), "results.jsonl");
|
|
1790
|
+
if (!await fileExistsQueued(p)) return [];
|
|
1791
|
+
const raw = await readFileQueued(p, "utf8");
|
|
1792
|
+
return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1618
1793
|
}
|
|
1619
1794
|
/**
|
|
1620
1795
|
* List all batch IDs.
|
|
1621
1796
|
*/
|
|
1622
|
-
listBatches() {
|
|
1623
|
-
|
|
1624
|
-
|
|
1797
|
+
async listBatches() {
|
|
1798
|
+
await this.init();
|
|
1799
|
+
if (!await pathExistsQueued(this.dir)) return [];
|
|
1800
|
+
const entries = await readDirQueued(this.dir);
|
|
1801
|
+
return entries.filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
1625
1802
|
}
|
|
1626
1803
|
/**
|
|
1627
1804
|
* Check if a batch exists.
|
|
1628
1805
|
*/
|
|
1629
|
-
exists(id) {
|
|
1630
|
-
return (
|
|
1806
|
+
async exists(id) {
|
|
1807
|
+
return fileExistsQueued(joinPath(this.batchDir(id), "meta.json"));
|
|
1631
1808
|
}
|
|
1632
1809
|
};
|
|
1633
1810
|
|
|
@@ -1636,10 +1813,27 @@ var BatchManager = class {
|
|
|
1636
1813
|
store;
|
|
1637
1814
|
router;
|
|
1638
1815
|
concurrencyLimit;
|
|
1816
|
+
defaultPollInterval;
|
|
1817
|
+
batchAdapters = /* @__PURE__ */ new Map();
|
|
1639
1818
|
constructor(router, options) {
|
|
1640
1819
|
this.store = new BatchStore(options?.dir);
|
|
1641
1820
|
this.router = router;
|
|
1642
1821
|
this.concurrencyLimit = options?.concurrency ?? 5;
|
|
1822
|
+
this.defaultPollInterval = options?.pollInterval ?? 5e3;
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Register a native batch adapter for a provider.
|
|
1826
|
+
*/
|
|
1827
|
+
registerBatchAdapter(providerName, adapter) {
|
|
1828
|
+
this.batchAdapters.set(providerName, adapter);
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Check if a provider has native batch support.
|
|
1832
|
+
*/
|
|
1833
|
+
getNativeBatchAdapter(model) {
|
|
1834
|
+
const providerName = model.split("/")[0];
|
|
1835
|
+
const adapter = this.batchAdapters.get(providerName);
|
|
1836
|
+
return adapter ? { adapter, providerName } : null;
|
|
1643
1837
|
}
|
|
1644
1838
|
/**
|
|
1645
1839
|
* Create a batch and return immediately (no polling).
|
|
@@ -1647,13 +1841,16 @@ var BatchManager = class {
|
|
|
1647
1841
|
async create(request) {
|
|
1648
1842
|
const id = generateId("batch");
|
|
1649
1843
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1844
|
+
const providerName = request.model.split("/")[0] || "unknown";
|
|
1845
|
+
const native = this.getNativeBatchAdapter(request.model);
|
|
1846
|
+
const batchMode = native ? "native" : "concurrent";
|
|
1650
1847
|
const batch = {
|
|
1651
1848
|
id,
|
|
1652
1849
|
object: "batch",
|
|
1653
1850
|
status: "pending",
|
|
1654
1851
|
model: request.model,
|
|
1655
|
-
provider_name:
|
|
1656
|
-
batch_mode:
|
|
1852
|
+
provider_name: providerName,
|
|
1853
|
+
batch_mode: batchMode,
|
|
1657
1854
|
total: request.requests.length,
|
|
1658
1855
|
completed: 0,
|
|
1659
1856
|
failed: 0,
|
|
@@ -1661,10 +1858,15 @@ var BatchManager = class {
|
|
|
1661
1858
|
completed_at: null,
|
|
1662
1859
|
expires_at: null
|
|
1663
1860
|
};
|
|
1664
|
-
this.store.create(batch);
|
|
1665
|
-
this.store.saveRequests(id, request.requests);
|
|
1666
|
-
|
|
1667
|
-
|
|
1861
|
+
await this.store.create(batch);
|
|
1862
|
+
await this.store.saveRequests(id, request.requests);
|
|
1863
|
+
if (native) {
|
|
1864
|
+
this.processNativeBatch(id, request, native.adapter).catch(() => {
|
|
1865
|
+
});
|
|
1866
|
+
} else {
|
|
1867
|
+
this.processConcurrentBatch(id, request).catch(() => {
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1668
1870
|
return batch;
|
|
1669
1871
|
}
|
|
1670
1872
|
/**
|
|
@@ -1678,14 +1880,19 @@ var BatchManager = class {
|
|
|
1678
1880
|
* Poll an existing batch until completion.
|
|
1679
1881
|
*/
|
|
1680
1882
|
async poll(id, options = {}) {
|
|
1681
|
-
const interval = options.interval ??
|
|
1883
|
+
const interval = options.interval ?? this.defaultPollInterval;
|
|
1682
1884
|
const timeout = options.timeout ?? 0;
|
|
1683
1885
|
const startTime = Date.now();
|
|
1684
1886
|
while (true) {
|
|
1685
|
-
|
|
1887
|
+
let batch = await this.store.getMeta(id);
|
|
1686
1888
|
if (!batch) {
|
|
1687
1889
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1688
1890
|
}
|
|
1891
|
+
if (batch.batch_mode === "native" && batch.status === "processing") {
|
|
1892
|
+
await this.syncNativeBatchStatus(id);
|
|
1893
|
+
batch = await this.store.getMeta(id);
|
|
1894
|
+
if (!batch) throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1895
|
+
}
|
|
1689
1896
|
if (options.onProgress) {
|
|
1690
1897
|
options.onProgress(batch);
|
|
1691
1898
|
}
|
|
@@ -1695,24 +1902,24 @@ var BatchManager = class {
|
|
|
1695
1902
|
if (timeout > 0 && Date.now() - startTime > timeout) {
|
|
1696
1903
|
throw new AnyModelError(408, `Batch ${id} timed out after ${timeout}ms`);
|
|
1697
1904
|
}
|
|
1698
|
-
await new Promise((
|
|
1905
|
+
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
1699
1906
|
}
|
|
1700
1907
|
}
|
|
1701
1908
|
/**
|
|
1702
1909
|
* Get the current status of a batch.
|
|
1703
1910
|
*/
|
|
1704
|
-
get(id) {
|
|
1911
|
+
async get(id) {
|
|
1705
1912
|
return this.store.getMeta(id);
|
|
1706
1913
|
}
|
|
1707
1914
|
/**
|
|
1708
1915
|
* Get results for a completed batch.
|
|
1709
1916
|
*/
|
|
1710
|
-
getResults(id) {
|
|
1711
|
-
const batch = this.store.getMeta(id);
|
|
1917
|
+
async getResults(id) {
|
|
1918
|
+
const batch = await this.store.getMeta(id);
|
|
1712
1919
|
if (!batch) {
|
|
1713
1920
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1714
1921
|
}
|
|
1715
|
-
const results = this.store.getResults(id);
|
|
1922
|
+
const results = await this.store.getResults(id);
|
|
1716
1923
|
const usage = {
|
|
1717
1924
|
total_prompt_tokens: 0,
|
|
1718
1925
|
total_completion_tokens: 0,
|
|
@@ -1734,37 +1941,119 @@ var BatchManager = class {
|
|
|
1734
1941
|
/**
|
|
1735
1942
|
* List all batches.
|
|
1736
1943
|
*/
|
|
1737
|
-
list() {
|
|
1738
|
-
|
|
1944
|
+
async list() {
|
|
1945
|
+
const ids = await this.store.listBatches();
|
|
1946
|
+
const batches = [];
|
|
1947
|
+
for (const id of ids) {
|
|
1948
|
+
const meta = await this.store.getMeta(id);
|
|
1949
|
+
if (meta) batches.push(meta);
|
|
1950
|
+
}
|
|
1951
|
+
return batches;
|
|
1739
1952
|
}
|
|
1740
1953
|
/**
|
|
1741
1954
|
* Cancel a batch.
|
|
1742
1955
|
*/
|
|
1743
|
-
cancel(id) {
|
|
1744
|
-
const batch = this.store.getMeta(id);
|
|
1956
|
+
async cancel(id) {
|
|
1957
|
+
const batch = await this.store.getMeta(id);
|
|
1745
1958
|
if (!batch) {
|
|
1746
1959
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1747
1960
|
}
|
|
1748
1961
|
if (batch.status === "completed" || batch.status === "cancelled") {
|
|
1749
1962
|
return batch;
|
|
1750
1963
|
}
|
|
1964
|
+
if (batch.batch_mode === "native") {
|
|
1965
|
+
const providerState = await this.store.loadProviderState(id);
|
|
1966
|
+
const adapter = this.batchAdapters.get(batch.provider_name);
|
|
1967
|
+
if (adapter && providerState?.providerBatchId) {
|
|
1968
|
+
try {
|
|
1969
|
+
await adapter.cancelBatch(providerState.providerBatchId);
|
|
1970
|
+
} catch {
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1751
1974
|
batch.status = "cancelled";
|
|
1752
1975
|
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1753
|
-
this.store.updateMeta(batch);
|
|
1976
|
+
await this.store.updateMeta(batch);
|
|
1754
1977
|
return batch;
|
|
1755
1978
|
}
|
|
1756
1979
|
/**
|
|
1757
|
-
* Process batch
|
|
1980
|
+
* Process batch via native provider batch API.
|
|
1758
1981
|
*/
|
|
1759
|
-
async
|
|
1760
|
-
const batch = this.store.getMeta(batchId);
|
|
1982
|
+
async processNativeBatch(batchId, request, adapter) {
|
|
1983
|
+
const batch = await this.store.getMeta(batchId);
|
|
1984
|
+
if (!batch) return;
|
|
1985
|
+
try {
|
|
1986
|
+
const model = request.model.includes("/") ? request.model.split("/").slice(1).join("/") : request.model;
|
|
1987
|
+
const { providerBatchId, metadata } = await adapter.createBatch(
|
|
1988
|
+
model,
|
|
1989
|
+
request.requests,
|
|
1990
|
+
request.options
|
|
1991
|
+
);
|
|
1992
|
+
await this.store.saveProviderState(batchId, {
|
|
1993
|
+
providerBatchId,
|
|
1994
|
+
providerName: batch.provider_name,
|
|
1995
|
+
...metadata
|
|
1996
|
+
});
|
|
1997
|
+
batch.status = "processing";
|
|
1998
|
+
await this.store.updateMeta(batch);
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
batch.status = "failed";
|
|
2001
|
+
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2002
|
+
await this.store.updateMeta(batch);
|
|
2003
|
+
throw err;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Sync native batch status from provider.
|
|
2008
|
+
*/
|
|
2009
|
+
async syncNativeBatchStatus(batchId) {
|
|
2010
|
+
const batch = await this.store.getMeta(batchId);
|
|
2011
|
+
if (!batch) return;
|
|
2012
|
+
const providerState = await this.store.loadProviderState(batchId);
|
|
2013
|
+
if (!providerState?.providerBatchId) return;
|
|
2014
|
+
const adapter = this.batchAdapters.get(batch.provider_name);
|
|
2015
|
+
if (!adapter) return;
|
|
2016
|
+
try {
|
|
2017
|
+
const status = await adapter.pollBatch(providerState.providerBatchId);
|
|
2018
|
+
batch.total = status.total || batch.total;
|
|
2019
|
+
batch.completed = status.completed;
|
|
2020
|
+
batch.failed = status.failed;
|
|
2021
|
+
if (status.status === "completed" || status.status === "failed" || status.status === "cancelled") {
|
|
2022
|
+
batch.status = status.status;
|
|
2023
|
+
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2024
|
+
if (status.status === "completed" || status.status === "failed") {
|
|
2025
|
+
try {
|
|
2026
|
+
const results = await adapter.getBatchResults(providerState.providerBatchId);
|
|
2027
|
+
for (const result of results) {
|
|
2028
|
+
await this.store.appendResult(batchId, result);
|
|
2029
|
+
}
|
|
2030
|
+
batch.completed = results.filter((r) => r.status === "success").length;
|
|
2031
|
+
batch.failed = results.filter((r) => r.status === "error").length;
|
|
2032
|
+
} catch {
|
|
2033
|
+
if (batch.status !== "failed") {
|
|
2034
|
+
batch.status = "failed";
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
} else {
|
|
2039
|
+
batch.status = "processing";
|
|
2040
|
+
}
|
|
2041
|
+
await this.store.updateMeta(batch);
|
|
2042
|
+
} catch {
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Process batch requests concurrently (fallback path).
|
|
2047
|
+
*/
|
|
2048
|
+
async processConcurrentBatch(batchId, request) {
|
|
2049
|
+
const batch = await this.store.getMeta(batchId);
|
|
2050
|
+
if (!batch) return;
|
|
1761
2051
|
batch.status = "processing";
|
|
1762
|
-
this.store.updateMeta(batch);
|
|
2052
|
+
await this.store.updateMeta(batch);
|
|
1763
2053
|
const items = request.requests;
|
|
1764
|
-
const queue = [...items];
|
|
1765
2054
|
const active = /* @__PURE__ */ new Set();
|
|
1766
2055
|
const processItem = async (item) => {
|
|
1767
|
-
const current = this.store.getMeta(batchId);
|
|
2056
|
+
const current = await this.store.getMeta(batchId);
|
|
1768
2057
|
if (current?.status === "cancelled") return;
|
|
1769
2058
|
const chatRequest = {
|
|
1770
2059
|
model: request.model,
|
|
@@ -1796,17 +2085,19 @@ var BatchManager = class {
|
|
|
1796
2085
|
error: { code: error.code, message: error.message }
|
|
1797
2086
|
};
|
|
1798
2087
|
}
|
|
1799
|
-
this.store.appendResult(batchId, result);
|
|
1800
|
-
const meta = this.store.getMeta(batchId);
|
|
1801
|
-
if (
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2088
|
+
await this.store.appendResult(batchId, result);
|
|
2089
|
+
const meta = await this.store.getMeta(batchId);
|
|
2090
|
+
if (meta) {
|
|
2091
|
+
if (result.status === "success") {
|
|
2092
|
+
meta.completed++;
|
|
2093
|
+
} else {
|
|
2094
|
+
meta.failed++;
|
|
2095
|
+
}
|
|
2096
|
+
await this.store.updateMeta(meta);
|
|
1805
2097
|
}
|
|
1806
|
-
this.store.updateMeta(meta);
|
|
1807
2098
|
};
|
|
1808
|
-
for (const item of
|
|
1809
|
-
const current = this.store.getMeta(batchId);
|
|
2099
|
+
for (const item of items) {
|
|
2100
|
+
const current = await this.store.getMeta(batchId);
|
|
1810
2101
|
if (current?.status === "cancelled") break;
|
|
1811
2102
|
if (active.size >= this.concurrencyLimit) {
|
|
1812
2103
|
await Promise.race(active);
|
|
@@ -1817,15 +2108,411 @@ var BatchManager = class {
|
|
|
1817
2108
|
active.add(promise);
|
|
1818
2109
|
}
|
|
1819
2110
|
await Promise.all(active);
|
|
1820
|
-
const finalMeta = this.store.getMeta(batchId);
|
|
1821
|
-
if (finalMeta.status !== "cancelled") {
|
|
2111
|
+
const finalMeta = await this.store.getMeta(batchId);
|
|
2112
|
+
if (finalMeta && finalMeta.status !== "cancelled") {
|
|
1822
2113
|
finalMeta.status = finalMeta.failed === finalMeta.total ? "failed" : "completed";
|
|
1823
2114
|
finalMeta.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1824
|
-
this.store.updateMeta(finalMeta);
|
|
2115
|
+
await this.store.updateMeta(finalMeta);
|
|
1825
2116
|
}
|
|
1826
2117
|
}
|
|
1827
2118
|
};
|
|
1828
2119
|
|
|
2120
|
+
// src/providers/openai-batch.ts
|
|
2121
|
+
var OPENAI_API_BASE2 = "https://api.openai.com/v1";
|
|
2122
|
+
function createOpenAIBatchAdapter(apiKey) {
|
|
2123
|
+
async function apiRequest(path2, options = {}) {
|
|
2124
|
+
const headers = {
|
|
2125
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2126
|
+
};
|
|
2127
|
+
let fetchBody;
|
|
2128
|
+
if (options.formData) {
|
|
2129
|
+
fetchBody = options.formData;
|
|
2130
|
+
} else if (options.body) {
|
|
2131
|
+
headers["Content-Type"] = "application/json";
|
|
2132
|
+
fetchBody = JSON.stringify(options.body);
|
|
2133
|
+
}
|
|
2134
|
+
const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
|
|
2135
|
+
method: options.method || "GET",
|
|
2136
|
+
headers,
|
|
2137
|
+
body: fetchBody
|
|
2138
|
+
});
|
|
2139
|
+
if (!res.ok) {
|
|
2140
|
+
let errorBody;
|
|
2141
|
+
try {
|
|
2142
|
+
errorBody = await res.json();
|
|
2143
|
+
} catch {
|
|
2144
|
+
errorBody = { message: res.statusText };
|
|
2145
|
+
}
|
|
2146
|
+
const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
|
|
2147
|
+
throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
|
|
2148
|
+
provider_name: "openai",
|
|
2149
|
+
raw: errorBody
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
return res;
|
|
2153
|
+
}
|
|
2154
|
+
function buildJSONL(model, requests) {
|
|
2155
|
+
return requests.map((req) => {
|
|
2156
|
+
const body = {
|
|
2157
|
+
model,
|
|
2158
|
+
messages: req.messages
|
|
2159
|
+
};
|
|
2160
|
+
if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
|
|
2161
|
+
if (req.temperature !== void 0) body.temperature = req.temperature;
|
|
2162
|
+
if (req.top_p !== void 0) body.top_p = req.top_p;
|
|
2163
|
+
if (req.stop !== void 0) body.stop = req.stop;
|
|
2164
|
+
if (req.response_format !== void 0) body.response_format = req.response_format;
|
|
2165
|
+
if (req.tools !== void 0) body.tools = req.tools;
|
|
2166
|
+
if (req.tool_choice !== void 0) body.tool_choice = req.tool_choice;
|
|
2167
|
+
return JSON.stringify({
|
|
2168
|
+
custom_id: req.custom_id,
|
|
2169
|
+
method: "POST",
|
|
2170
|
+
url: "/v1/chat/completions",
|
|
2171
|
+
body
|
|
2172
|
+
});
|
|
2173
|
+
}).join("\n");
|
|
2174
|
+
}
|
|
2175
|
+
function rePrefixId(id) {
|
|
2176
|
+
if (id && id.startsWith("chatcmpl-")) {
|
|
2177
|
+
return `gen-${id.substring(9)}`;
|
|
2178
|
+
}
|
|
2179
|
+
return id.startsWith("gen-") ? id : `gen-${id}`;
|
|
2180
|
+
}
|
|
2181
|
+
function translateOpenAIResponse(body) {
|
|
2182
|
+
return {
|
|
2183
|
+
id: rePrefixId(body.id || generateId()),
|
|
2184
|
+
object: "chat.completion",
|
|
2185
|
+
created: body.created || Math.floor(Date.now() / 1e3),
|
|
2186
|
+
model: `openai/${body.model}`,
|
|
2187
|
+
choices: body.choices,
|
|
2188
|
+
usage: body.usage
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
function mapStatus(openaiStatus) {
|
|
2192
|
+
switch (openaiStatus) {
|
|
2193
|
+
case "validating":
|
|
2194
|
+
case "finalizing":
|
|
2195
|
+
return "processing";
|
|
2196
|
+
case "in_progress":
|
|
2197
|
+
return "processing";
|
|
2198
|
+
case "completed":
|
|
2199
|
+
return "completed";
|
|
2200
|
+
case "failed":
|
|
2201
|
+
return "failed";
|
|
2202
|
+
case "expired":
|
|
2203
|
+
return "failed";
|
|
2204
|
+
case "cancelled":
|
|
2205
|
+
case "cancelling":
|
|
2206
|
+
return "cancelled";
|
|
2207
|
+
default:
|
|
2208
|
+
return "pending";
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return {
|
|
2212
|
+
async createBatch(model, requests, options) {
|
|
2213
|
+
const jsonlContent = buildJSONL(model, requests);
|
|
2214
|
+
const blob = new Blob([jsonlContent], { type: "application/jsonl" });
|
|
2215
|
+
const formData = new FormData();
|
|
2216
|
+
formData.append("purpose", "batch");
|
|
2217
|
+
formData.append("file", blob, "batch_input.jsonl");
|
|
2218
|
+
const uploadRes = await apiRequest("/files", { method: "POST", formData });
|
|
2219
|
+
const fileData = await uploadRes.json();
|
|
2220
|
+
const inputFileId = fileData.id;
|
|
2221
|
+
const batchRes = await apiRequest("/batches", {
|
|
2222
|
+
method: "POST",
|
|
2223
|
+
body: {
|
|
2224
|
+
input_file_id: inputFileId,
|
|
2225
|
+
endpoint: "/v1/chat/completions",
|
|
2226
|
+
completion_window: "24h",
|
|
2227
|
+
metadata: options?.metadata
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
const batchData = await batchRes.json();
|
|
2231
|
+
return {
|
|
2232
|
+
providerBatchId: batchData.id,
|
|
2233
|
+
metadata: {
|
|
2234
|
+
input_file_id: inputFileId,
|
|
2235
|
+
openai_status: batchData.status
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
},
|
|
2239
|
+
async pollBatch(providerBatchId) {
|
|
2240
|
+
const res = await apiRequest(`/batches/${providerBatchId}`);
|
|
2241
|
+
const data = await res.json();
|
|
2242
|
+
const requestCounts = data.request_counts || {};
|
|
2243
|
+
return {
|
|
2244
|
+
status: mapStatus(data.status),
|
|
2245
|
+
total: requestCounts.total || 0,
|
|
2246
|
+
completed: requestCounts.completed || 0,
|
|
2247
|
+
failed: requestCounts.failed || 0
|
|
2248
|
+
};
|
|
2249
|
+
},
|
|
2250
|
+
async getBatchResults(providerBatchId) {
|
|
2251
|
+
const batchRes = await apiRequest(`/batches/${providerBatchId}`);
|
|
2252
|
+
const batchData = await batchRes.json();
|
|
2253
|
+
const results = [];
|
|
2254
|
+
if (batchData.output_file_id) {
|
|
2255
|
+
const outputRes = await apiRequest(`/files/${batchData.output_file_id}/content`);
|
|
2256
|
+
const outputText = await outputRes.text();
|
|
2257
|
+
for (const line of outputText.trim().split("\n")) {
|
|
2258
|
+
if (!line) continue;
|
|
2259
|
+
const item = JSON.parse(line);
|
|
2260
|
+
if (item.response?.status_code === 200) {
|
|
2261
|
+
results.push({
|
|
2262
|
+
custom_id: item.custom_id,
|
|
2263
|
+
status: "success",
|
|
2264
|
+
response: translateOpenAIResponse(item.response.body),
|
|
2265
|
+
error: null
|
|
2266
|
+
});
|
|
2267
|
+
} else {
|
|
2268
|
+
results.push({
|
|
2269
|
+
custom_id: item.custom_id,
|
|
2270
|
+
status: "error",
|
|
2271
|
+
response: null,
|
|
2272
|
+
error: {
|
|
2273
|
+
code: item.response?.status_code || 500,
|
|
2274
|
+
message: item.error?.message || item.response?.body?.error?.message || "Unknown error"
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (batchData.error_file_id) {
|
|
2281
|
+
const errorRes = await apiRequest(`/files/${batchData.error_file_id}/content`);
|
|
2282
|
+
const errorText = await errorRes.text();
|
|
2283
|
+
for (const line of errorText.trim().split("\n")) {
|
|
2284
|
+
if (!line) continue;
|
|
2285
|
+
const item = JSON.parse(line);
|
|
2286
|
+
const existing = results.find((r) => r.custom_id === item.custom_id);
|
|
2287
|
+
if (!existing) {
|
|
2288
|
+
results.push({
|
|
2289
|
+
custom_id: item.custom_id,
|
|
2290
|
+
status: "error",
|
|
2291
|
+
response: null,
|
|
2292
|
+
error: {
|
|
2293
|
+
code: item.response?.status_code || 500,
|
|
2294
|
+
message: item.error?.message || "Batch item error"
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
return results;
|
|
2301
|
+
},
|
|
2302
|
+
async cancelBatch(providerBatchId) {
|
|
2303
|
+
await apiRequest(`/batches/${providerBatchId}/cancel`, { method: "POST" });
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/providers/anthropic-batch.ts
|
|
2309
|
+
var ANTHROPIC_API_BASE2 = "https://api.anthropic.com/v1";
|
|
2310
|
+
var ANTHROPIC_VERSION2 = "2023-06-01";
|
|
2311
|
+
var DEFAULT_MAX_TOKENS2 = 4096;
|
|
2312
|
+
function createAnthropicBatchAdapter(apiKey) {
|
|
2313
|
+
async function apiRequest(path2, options = {}) {
|
|
2314
|
+
const headers = {
|
|
2315
|
+
"x-api-key": apiKey,
|
|
2316
|
+
"anthropic-version": ANTHROPIC_VERSION2,
|
|
2317
|
+
"Content-Type": "application/json"
|
|
2318
|
+
};
|
|
2319
|
+
const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
|
|
2320
|
+
method: options.method || "GET",
|
|
2321
|
+
headers,
|
|
2322
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
2323
|
+
});
|
|
2324
|
+
if (!res.ok) {
|
|
2325
|
+
let errorBody;
|
|
2326
|
+
try {
|
|
2327
|
+
errorBody = await res.json();
|
|
2328
|
+
} catch {
|
|
2329
|
+
errorBody = { message: res.statusText };
|
|
2330
|
+
}
|
|
2331
|
+
const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
|
|
2332
|
+
throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
|
|
2333
|
+
provider_name: "anthropic",
|
|
2334
|
+
raw: errorBody
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
return res;
|
|
2338
|
+
}
|
|
2339
|
+
function translateToAnthropicParams(model, req) {
|
|
2340
|
+
const params = {
|
|
2341
|
+
model,
|
|
2342
|
+
max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
|
|
2343
|
+
};
|
|
2344
|
+
const systemMessages = req.messages.filter((m) => m.role === "system");
|
|
2345
|
+
const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
|
|
2346
|
+
if (systemMessages.length > 0) {
|
|
2347
|
+
params.system = systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n");
|
|
2348
|
+
}
|
|
2349
|
+
params.messages = nonSystemMessages.map((m) => ({
|
|
2350
|
+
role: m.role === "tool" ? "user" : m.role,
|
|
2351
|
+
content: m.tool_call_id ? [{ type: "tool_result", tool_use_id: m.tool_call_id, content: typeof m.content === "string" ? m.content : "" }] : m.content
|
|
2352
|
+
}));
|
|
2353
|
+
if (req.temperature !== void 0) params.temperature = req.temperature;
|
|
2354
|
+
if (req.top_p !== void 0) params.top_p = req.top_p;
|
|
2355
|
+
if (req.top_k !== void 0) params.top_k = req.top_k;
|
|
2356
|
+
if (req.stop !== void 0) params.stop_sequences = Array.isArray(req.stop) ? req.stop : [req.stop];
|
|
2357
|
+
if (req.tools && req.tools.length > 0) {
|
|
2358
|
+
params.tools = req.tools.map((t) => ({
|
|
2359
|
+
name: t.function.name,
|
|
2360
|
+
description: t.function.description || "",
|
|
2361
|
+
input_schema: t.function.parameters || { type: "object", properties: {} }
|
|
2362
|
+
}));
|
|
2363
|
+
if (req.tool_choice) {
|
|
2364
|
+
if (req.tool_choice === "auto") {
|
|
2365
|
+
params.tool_choice = { type: "auto" };
|
|
2366
|
+
} else if (req.tool_choice === "required") {
|
|
2367
|
+
params.tool_choice = { type: "any" };
|
|
2368
|
+
} else if (req.tool_choice === "none") {
|
|
2369
|
+
delete params.tools;
|
|
2370
|
+
} else if (typeof req.tool_choice === "object") {
|
|
2371
|
+
params.tool_choice = { type: "tool", name: req.tool_choice.function.name };
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (req.response_format) {
|
|
2376
|
+
if (req.response_format.type === "json_object" || req.response_format.type === "json_schema") {
|
|
2377
|
+
const jsonInstruction = "Respond with valid JSON only. Do not include any text outside the JSON object.";
|
|
2378
|
+
params.system = params.system ? `${jsonInstruction}
|
|
2379
|
+
|
|
2380
|
+
${params.system}` : jsonInstruction;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
return params;
|
|
2384
|
+
}
|
|
2385
|
+
function mapStopReason(reason) {
|
|
2386
|
+
switch (reason) {
|
|
2387
|
+
case "end_turn":
|
|
2388
|
+
return "stop";
|
|
2389
|
+
case "max_tokens":
|
|
2390
|
+
return "length";
|
|
2391
|
+
case "tool_use":
|
|
2392
|
+
return "tool_calls";
|
|
2393
|
+
case "stop_sequence":
|
|
2394
|
+
return "stop";
|
|
2395
|
+
default:
|
|
2396
|
+
return "stop";
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
function translateAnthropicMessage(msg) {
|
|
2400
|
+
let content = "";
|
|
2401
|
+
const toolCalls = [];
|
|
2402
|
+
for (const block of msg.content || []) {
|
|
2403
|
+
if (block.type === "text") {
|
|
2404
|
+
content += block.text;
|
|
2405
|
+
} else if (block.type === "tool_use") {
|
|
2406
|
+
toolCalls.push({
|
|
2407
|
+
id: block.id,
|
|
2408
|
+
type: "function",
|
|
2409
|
+
function: {
|
|
2410
|
+
name: block.name,
|
|
2411
|
+
arguments: JSON.stringify(block.input)
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
const message = { role: "assistant", content };
|
|
2417
|
+
if (toolCalls.length > 0) {
|
|
2418
|
+
message.tool_calls = toolCalls;
|
|
2419
|
+
}
|
|
2420
|
+
return {
|
|
2421
|
+
id: generateId(),
|
|
2422
|
+
object: "chat.completion",
|
|
2423
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2424
|
+
model: `anthropic/${msg.model}`,
|
|
2425
|
+
choices: [{
|
|
2426
|
+
index: 0,
|
|
2427
|
+
message,
|
|
2428
|
+
finish_reason: mapStopReason(msg.stop_reason)
|
|
2429
|
+
}],
|
|
2430
|
+
usage: {
|
|
2431
|
+
prompt_tokens: msg.usage?.input_tokens || 0,
|
|
2432
|
+
completion_tokens: msg.usage?.output_tokens || 0,
|
|
2433
|
+
total_tokens: (msg.usage?.input_tokens || 0) + (msg.usage?.output_tokens || 0)
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
return {
|
|
2438
|
+
async createBatch(model, requests, _options) {
|
|
2439
|
+
const batchRequests = requests.map((req) => ({
|
|
2440
|
+
custom_id: req.custom_id,
|
|
2441
|
+
params: translateToAnthropicParams(model, req)
|
|
2442
|
+
}));
|
|
2443
|
+
const res = await apiRequest("/messages/batches", {
|
|
2444
|
+
method: "POST",
|
|
2445
|
+
body: { requests: batchRequests }
|
|
2446
|
+
});
|
|
2447
|
+
const data = await res.json();
|
|
2448
|
+
return {
|
|
2449
|
+
providerBatchId: data.id,
|
|
2450
|
+
metadata: {
|
|
2451
|
+
anthropic_type: data.type,
|
|
2452
|
+
created_at: data.created_at
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
},
|
|
2456
|
+
async pollBatch(providerBatchId) {
|
|
2457
|
+
const res = await apiRequest(`/messages/batches/${providerBatchId}`);
|
|
2458
|
+
const data = await res.json();
|
|
2459
|
+
const counts = data.request_counts || {};
|
|
2460
|
+
const total = (counts.processing || 0) + (counts.succeeded || 0) + (counts.errored || 0) + (counts.canceled || 0) + (counts.expired || 0);
|
|
2461
|
+
let status;
|
|
2462
|
+
if (data.processing_status === "ended") {
|
|
2463
|
+
if (counts.succeeded === 0 && (counts.errored > 0 || counts.expired > 0 || counts.canceled > 0)) {
|
|
2464
|
+
status = "failed";
|
|
2465
|
+
} else if (data.cancel_initiated_at) {
|
|
2466
|
+
status = "cancelled";
|
|
2467
|
+
} else {
|
|
2468
|
+
status = "completed";
|
|
2469
|
+
}
|
|
2470
|
+
} else {
|
|
2471
|
+
status = "processing";
|
|
2472
|
+
}
|
|
2473
|
+
return {
|
|
2474
|
+
status,
|
|
2475
|
+
total,
|
|
2476
|
+
completed: counts.succeeded || 0,
|
|
2477
|
+
failed: (counts.errored || 0) + (counts.expired || 0) + (counts.canceled || 0)
|
|
2478
|
+
};
|
|
2479
|
+
},
|
|
2480
|
+
async getBatchResults(providerBatchId) {
|
|
2481
|
+
const res = await apiRequest(`/messages/batches/${providerBatchId}/results`);
|
|
2482
|
+
const text = await res.text();
|
|
2483
|
+
const results = [];
|
|
2484
|
+
for (const line of text.trim().split("\n")) {
|
|
2485
|
+
if (!line) continue;
|
|
2486
|
+
const item = JSON.parse(line);
|
|
2487
|
+
if (item.result?.type === "succeeded") {
|
|
2488
|
+
results.push({
|
|
2489
|
+
custom_id: item.custom_id,
|
|
2490
|
+
status: "success",
|
|
2491
|
+
response: translateAnthropicMessage(item.result.message),
|
|
2492
|
+
error: null
|
|
2493
|
+
});
|
|
2494
|
+
} else {
|
|
2495
|
+
const errorType = item.result?.type || "unknown";
|
|
2496
|
+
const errorMsg = item.result?.error?.message || `Batch item ${errorType}`;
|
|
2497
|
+
results.push({
|
|
2498
|
+
custom_id: item.custom_id,
|
|
2499
|
+
status: "error",
|
|
2500
|
+
response: null,
|
|
2501
|
+
error: {
|
|
2502
|
+
code: errorType === "expired" ? 408 : 500,
|
|
2503
|
+
message: errorMsg
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return results;
|
|
2509
|
+
},
|
|
2510
|
+
async cancelBatch(providerBatchId) {
|
|
2511
|
+
await apiRequest(`/messages/batches/${providerBatchId}/cancel`, { method: "POST" });
|
|
2512
|
+
}
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
|
|
1829
2516
|
// src/client.ts
|
|
1830
2517
|
var AnyModel = class {
|
|
1831
2518
|
registry;
|
|
@@ -1841,6 +2528,9 @@ var AnyModel = class {
|
|
|
1841
2528
|
constructor(config = {}) {
|
|
1842
2529
|
this.config = resolveConfig(config);
|
|
1843
2530
|
this.registry = new ProviderRegistry();
|
|
2531
|
+
if (this.config.io) {
|
|
2532
|
+
configureFsIO(this.config.io);
|
|
2533
|
+
}
|
|
1844
2534
|
this.registerProviders();
|
|
1845
2535
|
this.router = new Router(this.registry, this.config.aliases, this.config);
|
|
1846
2536
|
this.chat = {
|
|
@@ -1890,8 +2580,10 @@ var AnyModel = class {
|
|
|
1890
2580
|
};
|
|
1891
2581
|
this.batchManager = new BatchManager(this.router, {
|
|
1892
2582
|
dir: this.config.batch?.dir,
|
|
1893
|
-
concurrency: this.config.batch?.concurrencyFallback
|
|
2583
|
+
concurrency: this.config.batch?.concurrencyFallback,
|
|
2584
|
+
pollInterval: this.config.batch?.pollInterval
|
|
1894
2585
|
});
|
|
2586
|
+
this.registerBatchAdapters();
|
|
1895
2587
|
this.batches = {
|
|
1896
2588
|
create: (request) => this.batchManager.create(request),
|
|
1897
2589
|
createAndPoll: (request, options) => this.batchManager.createAndPoll(request, options),
|
|
@@ -1943,6 +2635,17 @@ var AnyModel = class {
|
|
|
1943
2635
|
}
|
|
1944
2636
|
}
|
|
1945
2637
|
}
|
|
2638
|
+
registerBatchAdapters() {
|
|
2639
|
+
const config = this.config;
|
|
2640
|
+
const openaiKey = config.openai?.apiKey || process.env.OPENAI_API_KEY;
|
|
2641
|
+
if (openaiKey) {
|
|
2642
|
+
this.batchManager.registerBatchAdapter("openai", createOpenAIBatchAdapter(openaiKey));
|
|
2643
|
+
}
|
|
2644
|
+
const anthropicKey = config.anthropic?.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
2645
|
+
if (anthropicKey) {
|
|
2646
|
+
this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
1946
2649
|
applyDefaults(request) {
|
|
1947
2650
|
const defaults = this.config.defaults;
|
|
1948
2651
|
if (!defaults) return request;
|
|
@@ -1971,10 +2674,10 @@ var AnyModel = class {
|
|
|
1971
2674
|
// src/server.ts
|
|
1972
2675
|
var import_node_http = require("http");
|
|
1973
2676
|
function parseBody(req) {
|
|
1974
|
-
return new Promise((
|
|
2677
|
+
return new Promise((resolve2, reject) => {
|
|
1975
2678
|
const chunks = [];
|
|
1976
2679
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
1977
|
-
req.on("end", () =>
|
|
2680
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString()));
|
|
1978
2681
|
req.on("error", reject);
|
|
1979
2682
|
});
|
|
1980
2683
|
}
|
|
@@ -2004,7 +2707,7 @@ function createAnyModelServer(options = {}) {
|
|
|
2004
2707
|
const basePath = "/api/v1";
|
|
2005
2708
|
const server = (0, import_node_http.createServer)(async (req, res) => {
|
|
2006
2709
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
2007
|
-
const
|
|
2710
|
+
const path2 = url.pathname;
|
|
2008
2711
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2009
2712
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2010
2713
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
@@ -2014,11 +2717,11 @@ function createAnyModelServer(options = {}) {
|
|
|
2014
2717
|
return;
|
|
2015
2718
|
}
|
|
2016
2719
|
try {
|
|
2017
|
-
if (
|
|
2720
|
+
if (path2 === "/health" && req.method === "GET") {
|
|
2018
2721
|
sendJSON(res, 200, { status: "ok" });
|
|
2019
2722
|
return;
|
|
2020
2723
|
}
|
|
2021
|
-
if (
|
|
2724
|
+
if (path2 === `${basePath}/chat/completions` && req.method === "POST") {
|
|
2022
2725
|
const body = JSON.parse(await parseBody(req));
|
|
2023
2726
|
if (body.stream) {
|
|
2024
2727
|
const stream = await client.chat.completions.create(body);
|
|
@@ -2029,14 +2732,14 @@ function createAnyModelServer(options = {}) {
|
|
|
2029
2732
|
}
|
|
2030
2733
|
return;
|
|
2031
2734
|
}
|
|
2032
|
-
if (
|
|
2735
|
+
if (path2 === `${basePath}/models` && req.method === "GET") {
|
|
2033
2736
|
const provider = url.searchParams.get("provider") || void 0;
|
|
2034
2737
|
const models = await client.models.list({ provider });
|
|
2035
2738
|
sendJSON(res, 200, { object: "list", data: models });
|
|
2036
2739
|
return;
|
|
2037
2740
|
}
|
|
2038
|
-
if (
|
|
2039
|
-
const id =
|
|
2741
|
+
if (path2.startsWith(`${basePath}/generation/`) && req.method === "GET") {
|
|
2742
|
+
const id = path2.substring(`${basePath}/generation/`.length);
|
|
2040
2743
|
const stats = client.generation.get(id);
|
|
2041
2744
|
if (!stats) {
|
|
2042
2745
|
sendError(res, 404, `Generation ${id} not found`);
|
|
@@ -2045,26 +2748,26 @@ function createAnyModelServer(options = {}) {
|
|
|
2045
2748
|
sendJSON(res, 200, stats);
|
|
2046
2749
|
return;
|
|
2047
2750
|
}
|
|
2048
|
-
if (
|
|
2751
|
+
if (path2 === `${basePath}/batches` && req.method === "POST") {
|
|
2049
2752
|
const body = JSON.parse(await parseBody(req));
|
|
2050
2753
|
const batch = await client.batches.create(body);
|
|
2051
2754
|
sendJSON(res, 201, batch);
|
|
2052
2755
|
return;
|
|
2053
2756
|
}
|
|
2054
|
-
if (
|
|
2055
|
-
const batches = client.batches.list();
|
|
2757
|
+
if (path2 === `${basePath}/batches` && req.method === "GET") {
|
|
2758
|
+
const batches = await client.batches.list();
|
|
2056
2759
|
sendJSON(res, 200, { object: "list", data: batches });
|
|
2057
2760
|
return;
|
|
2058
2761
|
}
|
|
2059
|
-
if (
|
|
2060
|
-
const parts =
|
|
2762
|
+
if (path2.startsWith(`${basePath}/batches/`) && req.method === "GET") {
|
|
2763
|
+
const parts = path2.substring(`${basePath}/batches/`.length).split("/");
|
|
2061
2764
|
const id = parts[0];
|
|
2062
2765
|
if (parts[1] === "results") {
|
|
2063
|
-
const results = client.batches.results(id);
|
|
2766
|
+
const results = await client.batches.results(id);
|
|
2064
2767
|
sendJSON(res, 200, results);
|
|
2065
2768
|
return;
|
|
2066
2769
|
}
|
|
2067
|
-
const batch = client.batches.get(id);
|
|
2770
|
+
const batch = await client.batches.get(id);
|
|
2068
2771
|
if (!batch) {
|
|
2069
2772
|
sendError(res, 404, `Batch ${id} not found`);
|
|
2070
2773
|
return;
|
|
@@ -2072,16 +2775,16 @@ function createAnyModelServer(options = {}) {
|
|
|
2072
2775
|
sendJSON(res, 200, batch);
|
|
2073
2776
|
return;
|
|
2074
2777
|
}
|
|
2075
|
-
if (
|
|
2076
|
-
const parts =
|
|
2778
|
+
if (path2.startsWith(`${basePath}/batches/`) && req.method === "POST") {
|
|
2779
|
+
const parts = path2.substring(`${basePath}/batches/`.length).split("/");
|
|
2077
2780
|
const id = parts[0];
|
|
2078
2781
|
if (parts[1] === "cancel") {
|
|
2079
|
-
const batch = client.batches.cancel(id);
|
|
2782
|
+
const batch = await client.batches.cancel(id);
|
|
2080
2783
|
sendJSON(res, 200, batch);
|
|
2081
2784
|
return;
|
|
2082
2785
|
}
|
|
2083
2786
|
}
|
|
2084
|
-
sendError(res, 404, `Not found: ${
|
|
2787
|
+
sendError(res, 404, `Not found: ${path2}`);
|
|
2085
2788
|
} catch (err) {
|
|
2086
2789
|
const code = err?.code || 500;
|
|
2087
2790
|
const message = err?.message || "Internal server error";
|
|
@@ -2117,8 +2820,19 @@ function startServer(options = {}) {
|
|
|
2117
2820
|
BatchManager,
|
|
2118
2821
|
BatchStore,
|
|
2119
2822
|
GenerationStatsStore,
|
|
2823
|
+
appendFileQueued,
|
|
2824
|
+
configureFsIO,
|
|
2825
|
+
createAnthropicBatchAdapter,
|
|
2120
2826
|
createAnyModelServer,
|
|
2827
|
+
createOpenAIBatchAdapter,
|
|
2828
|
+
ensureDir,
|
|
2829
|
+
getFsQueueStatus,
|
|
2830
|
+
joinPath,
|
|
2831
|
+
readFileQueued,
|
|
2121
2832
|
resolveConfig,
|
|
2122
|
-
startServer
|
|
2833
|
+
startServer,
|
|
2834
|
+
waitForFsQueuesIdle,
|
|
2835
|
+
writeFileFlushedQueued,
|
|
2836
|
+
writeFileQueued
|
|
2123
2837
|
});
|
|
2124
2838
|
//# sourceMappingURL=index.cjs.map
|