@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/cli.cjs
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
3
25
|
|
|
4
26
|
// src/server.ts
|
|
5
27
|
var import_node_http = require("http");
|
|
@@ -148,7 +170,7 @@ async function withRetry(fn, options = {}) {
|
|
|
148
170
|
throw error;
|
|
149
171
|
}
|
|
150
172
|
const delay = computeDelay(attempt, opts, error);
|
|
151
|
-
await new Promise((
|
|
173
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
176
|
throw lastError;
|
|
@@ -507,8 +529,8 @@ var SUPPORTED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
507
529
|
]);
|
|
508
530
|
function createOpenAIAdapter(apiKey, baseURL) {
|
|
509
531
|
const base = baseURL || OPENAI_API_BASE;
|
|
510
|
-
async function makeRequest(
|
|
511
|
-
const res = await fetch(`${base}${
|
|
532
|
+
async function makeRequest(path2, body, method = "POST") {
|
|
533
|
+
const res = await fetch(`${base}${path2}`, {
|
|
512
534
|
method,
|
|
513
535
|
headers: {
|
|
514
536
|
"Content-Type": "application/json",
|
|
@@ -659,6 +681,9 @@ function createOpenAIAdapter(apiKey, baseURL) {
|
|
|
659
681
|
supportsParameter(param) {
|
|
660
682
|
return SUPPORTED_PARAMS.has(param);
|
|
661
683
|
},
|
|
684
|
+
supportsBatch() {
|
|
685
|
+
return true;
|
|
686
|
+
},
|
|
662
687
|
async sendRequest(request) {
|
|
663
688
|
const body = buildRequestBody(request);
|
|
664
689
|
const res = await makeRequest("/chat/completions", body);
|
|
@@ -713,8 +738,8 @@ var FALLBACK_MODELS = [
|
|
|
713
738
|
{ 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) }
|
|
714
739
|
];
|
|
715
740
|
function createAnthropicAdapter(apiKey) {
|
|
716
|
-
async function makeRequest(
|
|
717
|
-
const res = await fetch(`${ANTHROPIC_API_BASE}${
|
|
741
|
+
async function makeRequest(path2, body, stream = false) {
|
|
742
|
+
const res = await fetch(`${ANTHROPIC_API_BASE}${path2}`, {
|
|
718
743
|
method: "POST",
|
|
719
744
|
headers: {
|
|
720
745
|
"Content-Type": "application/json",
|
|
@@ -1008,6 +1033,9 @@ ${body.system}` : jsonInstruction;
|
|
|
1008
1033
|
supportsParameter(param) {
|
|
1009
1034
|
return SUPPORTED_PARAMS2.has(param);
|
|
1010
1035
|
},
|
|
1036
|
+
supportsBatch() {
|
|
1037
|
+
return true;
|
|
1038
|
+
},
|
|
1011
1039
|
async sendRequest(request) {
|
|
1012
1040
|
const body = translateRequest(request);
|
|
1013
1041
|
const res = await makeRequest("/messages", body);
|
|
@@ -1287,6 +1315,9 @@ function createGoogleAdapter(apiKey) {
|
|
|
1287
1315
|
supportsParameter(param) {
|
|
1288
1316
|
return SUPPORTED_PARAMS3.has(param);
|
|
1289
1317
|
},
|
|
1318
|
+
supportsBatch() {
|
|
1319
|
+
return false;
|
|
1320
|
+
},
|
|
1290
1321
|
async sendRequest(request) {
|
|
1291
1322
|
const body = translateRequest(request);
|
|
1292
1323
|
const url = getModelEndpoint(request.model, false);
|
|
@@ -1345,6 +1376,9 @@ function createCustomAdapter(name, config) {
|
|
|
1345
1376
|
return {
|
|
1346
1377
|
...openaiAdapter,
|
|
1347
1378
|
name,
|
|
1379
|
+
supportsBatch() {
|
|
1380
|
+
return false;
|
|
1381
|
+
},
|
|
1348
1382
|
async listModels() {
|
|
1349
1383
|
if (config.models && config.models.length > 0) {
|
|
1350
1384
|
return config.models.map((modelId) => ({
|
|
@@ -1410,10 +1444,10 @@ function interpolateDeep(obj) {
|
|
|
1410
1444
|
}
|
|
1411
1445
|
return obj;
|
|
1412
1446
|
}
|
|
1413
|
-
function loadJsonFile(
|
|
1414
|
-
if (!(0, import_node_fs.existsSync)(
|
|
1447
|
+
function loadJsonFile(path2) {
|
|
1448
|
+
if (!(0, import_node_fs.existsSync)(path2)) return null;
|
|
1415
1449
|
try {
|
|
1416
|
-
const raw = (0, import_node_fs.readFileSync)(
|
|
1450
|
+
const raw = (0, import_node_fs.readFileSync)(path2, "utf-8");
|
|
1417
1451
|
const parsed = JSON.parse(raw);
|
|
1418
1452
|
return interpolateDeep(parsed);
|
|
1419
1453
|
} catch {
|
|
@@ -1514,93 +1548,228 @@ var GenerationStatsStore = class {
|
|
|
1514
1548
|
}
|
|
1515
1549
|
};
|
|
1516
1550
|
|
|
1517
|
-
// src/
|
|
1551
|
+
// src/utils/fs-io.ts
|
|
1552
|
+
var import_promises = require("fs/promises");
|
|
1518
1553
|
var import_node_fs2 = require("fs");
|
|
1519
|
-
var import_node_path2 = require("path");
|
|
1520
|
-
var
|
|
1521
|
-
var
|
|
1554
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1555
|
+
var import_p_queue = __toESM(require("p-queue"), 1);
|
|
1556
|
+
var writeQueue = new import_p_queue.default({ concurrency: 10 });
|
|
1557
|
+
var readQueue = new import_p_queue.default({ concurrency: 20 });
|
|
1558
|
+
function configureFsIO(options) {
|
|
1559
|
+
if (options.readConcurrency !== void 0) {
|
|
1560
|
+
readQueue.concurrency = options.readConcurrency;
|
|
1561
|
+
}
|
|
1562
|
+
if (options.writeConcurrency !== void 0) {
|
|
1563
|
+
writeQueue.concurrency = options.writeConcurrency;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
var ensuredDirs = /* @__PURE__ */ new Set();
|
|
1567
|
+
var joinPathCache = /* @__PURE__ */ new Map();
|
|
1568
|
+
var dirnameCache = /* @__PURE__ */ new Map();
|
|
1569
|
+
var resolvePathCache = /* @__PURE__ */ new Map();
|
|
1570
|
+
async function ensureDir(dir) {
|
|
1571
|
+
if (!dir) return;
|
|
1572
|
+
if (ensuredDirs.has(dir)) return;
|
|
1573
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
1574
|
+
ensuredDirs.add(dir);
|
|
1575
|
+
}
|
|
1576
|
+
async function readFileQueued(filePath, encoding = "utf8") {
|
|
1577
|
+
return readQueue.add(async () => {
|
|
1578
|
+
return (0, import_promises.readFile)(filePath, encoding);
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
async function readJsonQueued(filePath) {
|
|
1582
|
+
const raw = await readFileQueued(filePath, "utf8");
|
|
1583
|
+
return JSON.parse(raw);
|
|
1584
|
+
}
|
|
1585
|
+
async function readDirQueued(dirPath) {
|
|
1586
|
+
return readQueue.add(async () => {
|
|
1587
|
+
return (0, import_promises.readdir)(dirPath, { withFileTypes: true });
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
async function pathExistsQueued(p) {
|
|
1591
|
+
return readQueue.add(async () => {
|
|
1592
|
+
try {
|
|
1593
|
+
await (0, import_promises.stat)(p);
|
|
1594
|
+
return true;
|
|
1595
|
+
} catch {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
async function fileExistsQueued(filePath) {
|
|
1601
|
+
return readQueue.add(async () => {
|
|
1602
|
+
try {
|
|
1603
|
+
const s = await (0, import_promises.stat)(filePath);
|
|
1604
|
+
return s.isFile();
|
|
1605
|
+
} catch {
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
async function writeFileQueued(filePath, data) {
|
|
1611
|
+
await writeQueue.add(async () => {
|
|
1612
|
+
const dir = dirnameOf(filePath);
|
|
1613
|
+
await ensureDir(dir);
|
|
1614
|
+
await (0, import_promises.writeFile)(filePath, data);
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
async function appendFileQueued(filePath, data) {
|
|
1618
|
+
await writeQueue.add(async () => {
|
|
1619
|
+
const dir = dirnameOf(filePath);
|
|
1620
|
+
await ensureDir(dir);
|
|
1621
|
+
await (0, import_promises.writeFile)(filePath, data, { flag: "a" });
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
async function writeFileFlushedQueued(filePath, data) {
|
|
1625
|
+
await writeQueue.add(async () => {
|
|
1626
|
+
const dir = dirnameOf(filePath);
|
|
1627
|
+
await ensureDir(dir);
|
|
1628
|
+
const tmpPath = joinPath(
|
|
1629
|
+
dir,
|
|
1630
|
+
`.${import_node_path2.default.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
1631
|
+
);
|
|
1632
|
+
const fh = await (0, import_promises.open)(tmpPath, "w");
|
|
1633
|
+
try {
|
|
1634
|
+
await fh.writeFile(data);
|
|
1635
|
+
await fh.sync();
|
|
1636
|
+
} finally {
|
|
1637
|
+
await fh.close();
|
|
1638
|
+
}
|
|
1639
|
+
await (0, import_promises.rename)(tmpPath, filePath);
|
|
1640
|
+
try {
|
|
1641
|
+
const dh = await (0, import_promises.open)(dir, "r");
|
|
1642
|
+
try {
|
|
1643
|
+
await dh.sync();
|
|
1644
|
+
} finally {
|
|
1645
|
+
await dh.close();
|
|
1646
|
+
}
|
|
1647
|
+
} catch {
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
function joinPath(...segments) {
|
|
1652
|
+
const key = segments.join("\0");
|
|
1653
|
+
const cached = joinPathCache.get(key);
|
|
1654
|
+
if (cached !== void 0) return cached;
|
|
1655
|
+
const out = import_node_path2.default.join(...segments);
|
|
1656
|
+
joinPathCache.set(key, out);
|
|
1657
|
+
return out;
|
|
1658
|
+
}
|
|
1659
|
+
function dirnameOf(p) {
|
|
1660
|
+
const cached = dirnameCache.get(p);
|
|
1661
|
+
if (cached !== void 0) return cached;
|
|
1662
|
+
const out = import_node_path2.default.dirname(p);
|
|
1663
|
+
dirnameCache.set(p, out);
|
|
1664
|
+
return out;
|
|
1665
|
+
}
|
|
1666
|
+
function resolvePath(...segments) {
|
|
1667
|
+
const key = segments.join("\0");
|
|
1668
|
+
const cached = resolvePathCache.get(key);
|
|
1669
|
+
if (cached !== void 0) return cached;
|
|
1670
|
+
const out = import_node_path2.default.resolve(...segments);
|
|
1671
|
+
resolvePathCache.set(key, out);
|
|
1672
|
+
return out;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// src/batch/store.ts
|
|
1676
|
+
var DEFAULT_BATCH_DIR = joinPath(process.cwd(), ".anymodel", "batches");
|
|
1522
1677
|
var BatchStore = class {
|
|
1523
1678
|
dir;
|
|
1679
|
+
initialized = false;
|
|
1524
1680
|
constructor(dir) {
|
|
1525
|
-
this.dir = (
|
|
1526
|
-
|
|
1681
|
+
this.dir = resolvePath(dir || DEFAULT_BATCH_DIR);
|
|
1682
|
+
}
|
|
1683
|
+
async init() {
|
|
1684
|
+
if (this.initialized) return;
|
|
1685
|
+
await ensureDir(this.dir);
|
|
1686
|
+
this.initialized = true;
|
|
1527
1687
|
}
|
|
1528
1688
|
batchDir(id) {
|
|
1529
|
-
return (
|
|
1689
|
+
return joinPath(this.dir, id);
|
|
1530
1690
|
}
|
|
1531
1691
|
/**
|
|
1532
1692
|
* Create a new batch directory and save initial metadata.
|
|
1533
1693
|
*/
|
|
1534
|
-
create(batch) {
|
|
1694
|
+
async create(batch) {
|
|
1695
|
+
await this.init();
|
|
1535
1696
|
const dir = this.batchDir(batch.id);
|
|
1536
|
-
|
|
1537
|
-
|
|
1697
|
+
await ensureDir(dir);
|
|
1698
|
+
await writeFileFlushedQueued(joinPath(dir, "meta.json"), JSON.stringify(batch, null, 2));
|
|
1538
1699
|
}
|
|
1539
1700
|
/**
|
|
1540
|
-
* Update batch metadata.
|
|
1701
|
+
* Update batch metadata (atomic write).
|
|
1541
1702
|
*/
|
|
1542
|
-
updateMeta(batch) {
|
|
1543
|
-
|
|
1544
|
-
|
|
1703
|
+
async updateMeta(batch) {
|
|
1704
|
+
await writeFileFlushedQueued(
|
|
1705
|
+
joinPath(this.batchDir(batch.id), "meta.json"),
|
|
1706
|
+
JSON.stringify(batch, null, 2)
|
|
1707
|
+
);
|
|
1545
1708
|
}
|
|
1546
1709
|
/**
|
|
1547
1710
|
* Save requests as JSONL.
|
|
1548
1711
|
*/
|
|
1549
|
-
saveRequests(id, requests) {
|
|
1550
|
-
const dir = this.batchDir(id);
|
|
1712
|
+
async saveRequests(id, requests) {
|
|
1551
1713
|
const lines = requests.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
1552
|
-
|
|
1714
|
+
await writeFileQueued(joinPath(this.batchDir(id), "requests.jsonl"), lines);
|
|
1553
1715
|
}
|
|
1554
1716
|
/**
|
|
1555
1717
|
* Append a result to results.jsonl.
|
|
1556
1718
|
*/
|
|
1557
|
-
appendResult(id, result) {
|
|
1558
|
-
|
|
1559
|
-
|
|
1719
|
+
async appendResult(id, result) {
|
|
1720
|
+
await appendFileQueued(
|
|
1721
|
+
joinPath(this.batchDir(id), "results.jsonl"),
|
|
1722
|
+
JSON.stringify(result) + "\n"
|
|
1723
|
+
);
|
|
1560
1724
|
}
|
|
1561
1725
|
/**
|
|
1562
1726
|
* Save provider-specific state (e.g., provider batch ID).
|
|
1563
1727
|
*/
|
|
1564
|
-
saveProviderState(id, state) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1728
|
+
async saveProviderState(id, state) {
|
|
1729
|
+
await writeFileFlushedQueued(
|
|
1730
|
+
joinPath(this.batchDir(id), "provider.json"),
|
|
1731
|
+
JSON.stringify(state, null, 2)
|
|
1732
|
+
);
|
|
1567
1733
|
}
|
|
1568
1734
|
/**
|
|
1569
1735
|
* Load provider state.
|
|
1570
1736
|
*/
|
|
1571
|
-
loadProviderState(id) {
|
|
1572
|
-
const
|
|
1573
|
-
if (!
|
|
1574
|
-
return
|
|
1737
|
+
async loadProviderState(id) {
|
|
1738
|
+
const p = joinPath(this.batchDir(id), "provider.json");
|
|
1739
|
+
if (!await fileExistsQueued(p)) return null;
|
|
1740
|
+
return readJsonQueued(p);
|
|
1575
1741
|
}
|
|
1576
1742
|
/**
|
|
1577
1743
|
* Get batch metadata.
|
|
1578
1744
|
*/
|
|
1579
|
-
getMeta(id) {
|
|
1580
|
-
const
|
|
1581
|
-
if (!
|
|
1582
|
-
return
|
|
1745
|
+
async getMeta(id) {
|
|
1746
|
+
const p = joinPath(this.batchDir(id), "meta.json");
|
|
1747
|
+
if (!await fileExistsQueued(p)) return null;
|
|
1748
|
+
return readJsonQueued(p);
|
|
1583
1749
|
}
|
|
1584
1750
|
/**
|
|
1585
1751
|
* Get all results for a batch.
|
|
1586
1752
|
*/
|
|
1587
|
-
getResults(id) {
|
|
1588
|
-
const
|
|
1589
|
-
if (!
|
|
1590
|
-
|
|
1753
|
+
async getResults(id) {
|
|
1754
|
+
const p = joinPath(this.batchDir(id), "results.jsonl");
|
|
1755
|
+
if (!await fileExistsQueued(p)) return [];
|
|
1756
|
+
const raw = await readFileQueued(p, "utf8");
|
|
1757
|
+
return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1591
1758
|
}
|
|
1592
1759
|
/**
|
|
1593
1760
|
* List all batch IDs.
|
|
1594
1761
|
*/
|
|
1595
|
-
listBatches() {
|
|
1596
|
-
|
|
1597
|
-
|
|
1762
|
+
async listBatches() {
|
|
1763
|
+
await this.init();
|
|
1764
|
+
if (!await pathExistsQueued(this.dir)) return [];
|
|
1765
|
+
const entries = await readDirQueued(this.dir);
|
|
1766
|
+
return entries.filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
1598
1767
|
}
|
|
1599
1768
|
/**
|
|
1600
1769
|
* Check if a batch exists.
|
|
1601
1770
|
*/
|
|
1602
|
-
exists(id) {
|
|
1603
|
-
return (
|
|
1771
|
+
async exists(id) {
|
|
1772
|
+
return fileExistsQueued(joinPath(this.batchDir(id), "meta.json"));
|
|
1604
1773
|
}
|
|
1605
1774
|
};
|
|
1606
1775
|
|
|
@@ -1609,10 +1778,27 @@ var BatchManager = class {
|
|
|
1609
1778
|
store;
|
|
1610
1779
|
router;
|
|
1611
1780
|
concurrencyLimit;
|
|
1781
|
+
defaultPollInterval;
|
|
1782
|
+
batchAdapters = /* @__PURE__ */ new Map();
|
|
1612
1783
|
constructor(router, options) {
|
|
1613
1784
|
this.store = new BatchStore(options?.dir);
|
|
1614
1785
|
this.router = router;
|
|
1615
1786
|
this.concurrencyLimit = options?.concurrency ?? 5;
|
|
1787
|
+
this.defaultPollInterval = options?.pollInterval ?? 5e3;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Register a native batch adapter for a provider.
|
|
1791
|
+
*/
|
|
1792
|
+
registerBatchAdapter(providerName, adapter) {
|
|
1793
|
+
this.batchAdapters.set(providerName, adapter);
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Check if a provider has native batch support.
|
|
1797
|
+
*/
|
|
1798
|
+
getNativeBatchAdapter(model) {
|
|
1799
|
+
const providerName = model.split("/")[0];
|
|
1800
|
+
const adapter = this.batchAdapters.get(providerName);
|
|
1801
|
+
return adapter ? { adapter, providerName } : null;
|
|
1616
1802
|
}
|
|
1617
1803
|
/**
|
|
1618
1804
|
* Create a batch and return immediately (no polling).
|
|
@@ -1620,13 +1806,16 @@ var BatchManager = class {
|
|
|
1620
1806
|
async create(request) {
|
|
1621
1807
|
const id = generateId("batch");
|
|
1622
1808
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1809
|
+
const providerName = request.model.split("/")[0] || "unknown";
|
|
1810
|
+
const native = this.getNativeBatchAdapter(request.model);
|
|
1811
|
+
const batchMode = native ? "native" : "concurrent";
|
|
1623
1812
|
const batch = {
|
|
1624
1813
|
id,
|
|
1625
1814
|
object: "batch",
|
|
1626
1815
|
status: "pending",
|
|
1627
1816
|
model: request.model,
|
|
1628
|
-
provider_name:
|
|
1629
|
-
batch_mode:
|
|
1817
|
+
provider_name: providerName,
|
|
1818
|
+
batch_mode: batchMode,
|
|
1630
1819
|
total: request.requests.length,
|
|
1631
1820
|
completed: 0,
|
|
1632
1821
|
failed: 0,
|
|
@@ -1634,10 +1823,15 @@ var BatchManager = class {
|
|
|
1634
1823
|
completed_at: null,
|
|
1635
1824
|
expires_at: null
|
|
1636
1825
|
};
|
|
1637
|
-
this.store.create(batch);
|
|
1638
|
-
this.store.saveRequests(id, request.requests);
|
|
1639
|
-
|
|
1640
|
-
|
|
1826
|
+
await this.store.create(batch);
|
|
1827
|
+
await this.store.saveRequests(id, request.requests);
|
|
1828
|
+
if (native) {
|
|
1829
|
+
this.processNativeBatch(id, request, native.adapter).catch(() => {
|
|
1830
|
+
});
|
|
1831
|
+
} else {
|
|
1832
|
+
this.processConcurrentBatch(id, request).catch(() => {
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1641
1835
|
return batch;
|
|
1642
1836
|
}
|
|
1643
1837
|
/**
|
|
@@ -1651,14 +1845,19 @@ var BatchManager = class {
|
|
|
1651
1845
|
* Poll an existing batch until completion.
|
|
1652
1846
|
*/
|
|
1653
1847
|
async poll(id, options = {}) {
|
|
1654
|
-
const interval = options.interval ??
|
|
1848
|
+
const interval = options.interval ?? this.defaultPollInterval;
|
|
1655
1849
|
const timeout = options.timeout ?? 0;
|
|
1656
1850
|
const startTime = Date.now();
|
|
1657
1851
|
while (true) {
|
|
1658
|
-
|
|
1852
|
+
let batch = await this.store.getMeta(id);
|
|
1659
1853
|
if (!batch) {
|
|
1660
1854
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1661
1855
|
}
|
|
1856
|
+
if (batch.batch_mode === "native" && batch.status === "processing") {
|
|
1857
|
+
await this.syncNativeBatchStatus(id);
|
|
1858
|
+
batch = await this.store.getMeta(id);
|
|
1859
|
+
if (!batch) throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1860
|
+
}
|
|
1662
1861
|
if (options.onProgress) {
|
|
1663
1862
|
options.onProgress(batch);
|
|
1664
1863
|
}
|
|
@@ -1668,24 +1867,24 @@ var BatchManager = class {
|
|
|
1668
1867
|
if (timeout > 0 && Date.now() - startTime > timeout) {
|
|
1669
1868
|
throw new AnyModelError(408, `Batch ${id} timed out after ${timeout}ms`);
|
|
1670
1869
|
}
|
|
1671
|
-
await new Promise((
|
|
1870
|
+
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
1672
1871
|
}
|
|
1673
1872
|
}
|
|
1674
1873
|
/**
|
|
1675
1874
|
* Get the current status of a batch.
|
|
1676
1875
|
*/
|
|
1677
|
-
get(id) {
|
|
1876
|
+
async get(id) {
|
|
1678
1877
|
return this.store.getMeta(id);
|
|
1679
1878
|
}
|
|
1680
1879
|
/**
|
|
1681
1880
|
* Get results for a completed batch.
|
|
1682
1881
|
*/
|
|
1683
|
-
getResults(id) {
|
|
1684
|
-
const batch = this.store.getMeta(id);
|
|
1882
|
+
async getResults(id) {
|
|
1883
|
+
const batch = await this.store.getMeta(id);
|
|
1685
1884
|
if (!batch) {
|
|
1686
1885
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1687
1886
|
}
|
|
1688
|
-
const results = this.store.getResults(id);
|
|
1887
|
+
const results = await this.store.getResults(id);
|
|
1689
1888
|
const usage = {
|
|
1690
1889
|
total_prompt_tokens: 0,
|
|
1691
1890
|
total_completion_tokens: 0,
|
|
@@ -1707,37 +1906,119 @@ var BatchManager = class {
|
|
|
1707
1906
|
/**
|
|
1708
1907
|
* List all batches.
|
|
1709
1908
|
*/
|
|
1710
|
-
list() {
|
|
1711
|
-
|
|
1909
|
+
async list() {
|
|
1910
|
+
const ids = await this.store.listBatches();
|
|
1911
|
+
const batches = [];
|
|
1912
|
+
for (const id of ids) {
|
|
1913
|
+
const meta = await this.store.getMeta(id);
|
|
1914
|
+
if (meta) batches.push(meta);
|
|
1915
|
+
}
|
|
1916
|
+
return batches;
|
|
1712
1917
|
}
|
|
1713
1918
|
/**
|
|
1714
1919
|
* Cancel a batch.
|
|
1715
1920
|
*/
|
|
1716
|
-
cancel(id) {
|
|
1717
|
-
const batch = this.store.getMeta(id);
|
|
1921
|
+
async cancel(id) {
|
|
1922
|
+
const batch = await this.store.getMeta(id);
|
|
1718
1923
|
if (!batch) {
|
|
1719
1924
|
throw new AnyModelError(404, `Batch ${id} not found`);
|
|
1720
1925
|
}
|
|
1721
1926
|
if (batch.status === "completed" || batch.status === "cancelled") {
|
|
1722
1927
|
return batch;
|
|
1723
1928
|
}
|
|
1929
|
+
if (batch.batch_mode === "native") {
|
|
1930
|
+
const providerState = await this.store.loadProviderState(id);
|
|
1931
|
+
const adapter = this.batchAdapters.get(batch.provider_name);
|
|
1932
|
+
if (adapter && providerState?.providerBatchId) {
|
|
1933
|
+
try {
|
|
1934
|
+
await adapter.cancelBatch(providerState.providerBatchId);
|
|
1935
|
+
} catch {
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1724
1939
|
batch.status = "cancelled";
|
|
1725
1940
|
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1726
|
-
this.store.updateMeta(batch);
|
|
1941
|
+
await this.store.updateMeta(batch);
|
|
1727
1942
|
return batch;
|
|
1728
1943
|
}
|
|
1729
1944
|
/**
|
|
1730
|
-
* Process batch
|
|
1945
|
+
* Process batch via native provider batch API.
|
|
1731
1946
|
*/
|
|
1732
|
-
async
|
|
1733
|
-
const batch = this.store.getMeta(batchId);
|
|
1947
|
+
async processNativeBatch(batchId, request, adapter) {
|
|
1948
|
+
const batch = await this.store.getMeta(batchId);
|
|
1949
|
+
if (!batch) return;
|
|
1950
|
+
try {
|
|
1951
|
+
const model = request.model.includes("/") ? request.model.split("/").slice(1).join("/") : request.model;
|
|
1952
|
+
const { providerBatchId, metadata } = await adapter.createBatch(
|
|
1953
|
+
model,
|
|
1954
|
+
request.requests,
|
|
1955
|
+
request.options
|
|
1956
|
+
);
|
|
1957
|
+
await this.store.saveProviderState(batchId, {
|
|
1958
|
+
providerBatchId,
|
|
1959
|
+
providerName: batch.provider_name,
|
|
1960
|
+
...metadata
|
|
1961
|
+
});
|
|
1962
|
+
batch.status = "processing";
|
|
1963
|
+
await this.store.updateMeta(batch);
|
|
1964
|
+
} catch (err) {
|
|
1965
|
+
batch.status = "failed";
|
|
1966
|
+
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1967
|
+
await this.store.updateMeta(batch);
|
|
1968
|
+
throw err;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Sync native batch status from provider.
|
|
1973
|
+
*/
|
|
1974
|
+
async syncNativeBatchStatus(batchId) {
|
|
1975
|
+
const batch = await this.store.getMeta(batchId);
|
|
1976
|
+
if (!batch) return;
|
|
1977
|
+
const providerState = await this.store.loadProviderState(batchId);
|
|
1978
|
+
if (!providerState?.providerBatchId) return;
|
|
1979
|
+
const adapter = this.batchAdapters.get(batch.provider_name);
|
|
1980
|
+
if (!adapter) return;
|
|
1981
|
+
try {
|
|
1982
|
+
const status = await adapter.pollBatch(providerState.providerBatchId);
|
|
1983
|
+
batch.total = status.total || batch.total;
|
|
1984
|
+
batch.completed = status.completed;
|
|
1985
|
+
batch.failed = status.failed;
|
|
1986
|
+
if (status.status === "completed" || status.status === "failed" || status.status === "cancelled") {
|
|
1987
|
+
batch.status = status.status;
|
|
1988
|
+
batch.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1989
|
+
if (status.status === "completed" || status.status === "failed") {
|
|
1990
|
+
try {
|
|
1991
|
+
const results = await adapter.getBatchResults(providerState.providerBatchId);
|
|
1992
|
+
for (const result of results) {
|
|
1993
|
+
await this.store.appendResult(batchId, result);
|
|
1994
|
+
}
|
|
1995
|
+
batch.completed = results.filter((r) => r.status === "success").length;
|
|
1996
|
+
batch.failed = results.filter((r) => r.status === "error").length;
|
|
1997
|
+
} catch {
|
|
1998
|
+
if (batch.status !== "failed") {
|
|
1999
|
+
batch.status = "failed";
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
} else {
|
|
2004
|
+
batch.status = "processing";
|
|
2005
|
+
}
|
|
2006
|
+
await this.store.updateMeta(batch);
|
|
2007
|
+
} catch {
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Process batch requests concurrently (fallback path).
|
|
2012
|
+
*/
|
|
2013
|
+
async processConcurrentBatch(batchId, request) {
|
|
2014
|
+
const batch = await this.store.getMeta(batchId);
|
|
2015
|
+
if (!batch) return;
|
|
1734
2016
|
batch.status = "processing";
|
|
1735
|
-
this.store.updateMeta(batch);
|
|
2017
|
+
await this.store.updateMeta(batch);
|
|
1736
2018
|
const items = request.requests;
|
|
1737
|
-
const queue = [...items];
|
|
1738
2019
|
const active = /* @__PURE__ */ new Set();
|
|
1739
2020
|
const processItem = async (item) => {
|
|
1740
|
-
const current = this.store.getMeta(batchId);
|
|
2021
|
+
const current = await this.store.getMeta(batchId);
|
|
1741
2022
|
if (current?.status === "cancelled") return;
|
|
1742
2023
|
const chatRequest = {
|
|
1743
2024
|
model: request.model,
|
|
@@ -1769,17 +2050,19 @@ var BatchManager = class {
|
|
|
1769
2050
|
error: { code: error.code, message: error.message }
|
|
1770
2051
|
};
|
|
1771
2052
|
}
|
|
1772
|
-
this.store.appendResult(batchId, result);
|
|
1773
|
-
const meta = this.store.getMeta(batchId);
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2053
|
+
await this.store.appendResult(batchId, result);
|
|
2054
|
+
const meta = await this.store.getMeta(batchId);
|
|
2055
|
+
if (meta) {
|
|
2056
|
+
if (result.status === "success") {
|
|
2057
|
+
meta.completed++;
|
|
2058
|
+
} else {
|
|
2059
|
+
meta.failed++;
|
|
2060
|
+
}
|
|
2061
|
+
await this.store.updateMeta(meta);
|
|
1778
2062
|
}
|
|
1779
|
-
this.store.updateMeta(meta);
|
|
1780
2063
|
};
|
|
1781
|
-
for (const item of
|
|
1782
|
-
const current = this.store.getMeta(batchId);
|
|
2064
|
+
for (const item of items) {
|
|
2065
|
+
const current = await this.store.getMeta(batchId);
|
|
1783
2066
|
if (current?.status === "cancelled") break;
|
|
1784
2067
|
if (active.size >= this.concurrencyLimit) {
|
|
1785
2068
|
await Promise.race(active);
|
|
@@ -1790,15 +2073,411 @@ var BatchManager = class {
|
|
|
1790
2073
|
active.add(promise);
|
|
1791
2074
|
}
|
|
1792
2075
|
await Promise.all(active);
|
|
1793
|
-
const finalMeta = this.store.getMeta(batchId);
|
|
1794
|
-
if (finalMeta.status !== "cancelled") {
|
|
2076
|
+
const finalMeta = await this.store.getMeta(batchId);
|
|
2077
|
+
if (finalMeta && finalMeta.status !== "cancelled") {
|
|
1795
2078
|
finalMeta.status = finalMeta.failed === finalMeta.total ? "failed" : "completed";
|
|
1796
2079
|
finalMeta.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1797
|
-
this.store.updateMeta(finalMeta);
|
|
2080
|
+
await this.store.updateMeta(finalMeta);
|
|
1798
2081
|
}
|
|
1799
2082
|
}
|
|
1800
2083
|
};
|
|
1801
2084
|
|
|
2085
|
+
// src/providers/openai-batch.ts
|
|
2086
|
+
var OPENAI_API_BASE2 = "https://api.openai.com/v1";
|
|
2087
|
+
function createOpenAIBatchAdapter(apiKey) {
|
|
2088
|
+
async function apiRequest(path2, options = {}) {
|
|
2089
|
+
const headers = {
|
|
2090
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2091
|
+
};
|
|
2092
|
+
let fetchBody;
|
|
2093
|
+
if (options.formData) {
|
|
2094
|
+
fetchBody = options.formData;
|
|
2095
|
+
} else if (options.body) {
|
|
2096
|
+
headers["Content-Type"] = "application/json";
|
|
2097
|
+
fetchBody = JSON.stringify(options.body);
|
|
2098
|
+
}
|
|
2099
|
+
const res = await fetch(`${OPENAI_API_BASE2}${path2}`, {
|
|
2100
|
+
method: options.method || "GET",
|
|
2101
|
+
headers,
|
|
2102
|
+
body: fetchBody
|
|
2103
|
+
});
|
|
2104
|
+
if (!res.ok) {
|
|
2105
|
+
let errorBody;
|
|
2106
|
+
try {
|
|
2107
|
+
errorBody = await res.json();
|
|
2108
|
+
} catch {
|
|
2109
|
+
errorBody = { message: res.statusText };
|
|
2110
|
+
}
|
|
2111
|
+
const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
|
|
2112
|
+
throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
|
|
2113
|
+
provider_name: "openai",
|
|
2114
|
+
raw: errorBody
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
return res;
|
|
2118
|
+
}
|
|
2119
|
+
function buildJSONL(model, requests) {
|
|
2120
|
+
return requests.map((req) => {
|
|
2121
|
+
const body = {
|
|
2122
|
+
model,
|
|
2123
|
+
messages: req.messages
|
|
2124
|
+
};
|
|
2125
|
+
if (req.max_tokens !== void 0) body.max_tokens = req.max_tokens;
|
|
2126
|
+
if (req.temperature !== void 0) body.temperature = req.temperature;
|
|
2127
|
+
if (req.top_p !== void 0) body.top_p = req.top_p;
|
|
2128
|
+
if (req.stop !== void 0) body.stop = req.stop;
|
|
2129
|
+
if (req.response_format !== void 0) body.response_format = req.response_format;
|
|
2130
|
+
if (req.tools !== void 0) body.tools = req.tools;
|
|
2131
|
+
if (req.tool_choice !== void 0) body.tool_choice = req.tool_choice;
|
|
2132
|
+
return JSON.stringify({
|
|
2133
|
+
custom_id: req.custom_id,
|
|
2134
|
+
method: "POST",
|
|
2135
|
+
url: "/v1/chat/completions",
|
|
2136
|
+
body
|
|
2137
|
+
});
|
|
2138
|
+
}).join("\n");
|
|
2139
|
+
}
|
|
2140
|
+
function rePrefixId(id) {
|
|
2141
|
+
if (id && id.startsWith("chatcmpl-")) {
|
|
2142
|
+
return `gen-${id.substring(9)}`;
|
|
2143
|
+
}
|
|
2144
|
+
return id.startsWith("gen-") ? id : `gen-${id}`;
|
|
2145
|
+
}
|
|
2146
|
+
function translateOpenAIResponse(body) {
|
|
2147
|
+
return {
|
|
2148
|
+
id: rePrefixId(body.id || generateId()),
|
|
2149
|
+
object: "chat.completion",
|
|
2150
|
+
created: body.created || Math.floor(Date.now() / 1e3),
|
|
2151
|
+
model: `openai/${body.model}`,
|
|
2152
|
+
choices: body.choices,
|
|
2153
|
+
usage: body.usage
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
function mapStatus(openaiStatus) {
|
|
2157
|
+
switch (openaiStatus) {
|
|
2158
|
+
case "validating":
|
|
2159
|
+
case "finalizing":
|
|
2160
|
+
return "processing";
|
|
2161
|
+
case "in_progress":
|
|
2162
|
+
return "processing";
|
|
2163
|
+
case "completed":
|
|
2164
|
+
return "completed";
|
|
2165
|
+
case "failed":
|
|
2166
|
+
return "failed";
|
|
2167
|
+
case "expired":
|
|
2168
|
+
return "failed";
|
|
2169
|
+
case "cancelled":
|
|
2170
|
+
case "cancelling":
|
|
2171
|
+
return "cancelled";
|
|
2172
|
+
default:
|
|
2173
|
+
return "pending";
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
return {
|
|
2177
|
+
async createBatch(model, requests, options) {
|
|
2178
|
+
const jsonlContent = buildJSONL(model, requests);
|
|
2179
|
+
const blob = new Blob([jsonlContent], { type: "application/jsonl" });
|
|
2180
|
+
const formData = new FormData();
|
|
2181
|
+
formData.append("purpose", "batch");
|
|
2182
|
+
formData.append("file", blob, "batch_input.jsonl");
|
|
2183
|
+
const uploadRes = await apiRequest("/files", { method: "POST", formData });
|
|
2184
|
+
const fileData = await uploadRes.json();
|
|
2185
|
+
const inputFileId = fileData.id;
|
|
2186
|
+
const batchRes = await apiRequest("/batches", {
|
|
2187
|
+
method: "POST",
|
|
2188
|
+
body: {
|
|
2189
|
+
input_file_id: inputFileId,
|
|
2190
|
+
endpoint: "/v1/chat/completions",
|
|
2191
|
+
completion_window: "24h",
|
|
2192
|
+
metadata: options?.metadata
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
const batchData = await batchRes.json();
|
|
2196
|
+
return {
|
|
2197
|
+
providerBatchId: batchData.id,
|
|
2198
|
+
metadata: {
|
|
2199
|
+
input_file_id: inputFileId,
|
|
2200
|
+
openai_status: batchData.status
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
},
|
|
2204
|
+
async pollBatch(providerBatchId) {
|
|
2205
|
+
const res = await apiRequest(`/batches/${providerBatchId}`);
|
|
2206
|
+
const data = await res.json();
|
|
2207
|
+
const requestCounts = data.request_counts || {};
|
|
2208
|
+
return {
|
|
2209
|
+
status: mapStatus(data.status),
|
|
2210
|
+
total: requestCounts.total || 0,
|
|
2211
|
+
completed: requestCounts.completed || 0,
|
|
2212
|
+
failed: requestCounts.failed || 0
|
|
2213
|
+
};
|
|
2214
|
+
},
|
|
2215
|
+
async getBatchResults(providerBatchId) {
|
|
2216
|
+
const batchRes = await apiRequest(`/batches/${providerBatchId}`);
|
|
2217
|
+
const batchData = await batchRes.json();
|
|
2218
|
+
const results = [];
|
|
2219
|
+
if (batchData.output_file_id) {
|
|
2220
|
+
const outputRes = await apiRequest(`/files/${batchData.output_file_id}/content`);
|
|
2221
|
+
const outputText = await outputRes.text();
|
|
2222
|
+
for (const line of outputText.trim().split("\n")) {
|
|
2223
|
+
if (!line) continue;
|
|
2224
|
+
const item = JSON.parse(line);
|
|
2225
|
+
if (item.response?.status_code === 200) {
|
|
2226
|
+
results.push({
|
|
2227
|
+
custom_id: item.custom_id,
|
|
2228
|
+
status: "success",
|
|
2229
|
+
response: translateOpenAIResponse(item.response.body),
|
|
2230
|
+
error: null
|
|
2231
|
+
});
|
|
2232
|
+
} else {
|
|
2233
|
+
results.push({
|
|
2234
|
+
custom_id: item.custom_id,
|
|
2235
|
+
status: "error",
|
|
2236
|
+
response: null,
|
|
2237
|
+
error: {
|
|
2238
|
+
code: item.response?.status_code || 500,
|
|
2239
|
+
message: item.error?.message || item.response?.body?.error?.message || "Unknown error"
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (batchData.error_file_id) {
|
|
2246
|
+
const errorRes = await apiRequest(`/files/${batchData.error_file_id}/content`);
|
|
2247
|
+
const errorText = await errorRes.text();
|
|
2248
|
+
for (const line of errorText.trim().split("\n")) {
|
|
2249
|
+
if (!line) continue;
|
|
2250
|
+
const item = JSON.parse(line);
|
|
2251
|
+
const existing = results.find((r) => r.custom_id === item.custom_id);
|
|
2252
|
+
if (!existing) {
|
|
2253
|
+
results.push({
|
|
2254
|
+
custom_id: item.custom_id,
|
|
2255
|
+
status: "error",
|
|
2256
|
+
response: null,
|
|
2257
|
+
error: {
|
|
2258
|
+
code: item.response?.status_code || 500,
|
|
2259
|
+
message: item.error?.message || "Batch item error"
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return results;
|
|
2266
|
+
},
|
|
2267
|
+
async cancelBatch(providerBatchId) {
|
|
2268
|
+
await apiRequest(`/batches/${providerBatchId}/cancel`, { method: "POST" });
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// src/providers/anthropic-batch.ts
|
|
2274
|
+
var ANTHROPIC_API_BASE2 = "https://api.anthropic.com/v1";
|
|
2275
|
+
var ANTHROPIC_VERSION2 = "2023-06-01";
|
|
2276
|
+
var DEFAULT_MAX_TOKENS2 = 4096;
|
|
2277
|
+
function createAnthropicBatchAdapter(apiKey) {
|
|
2278
|
+
async function apiRequest(path2, options = {}) {
|
|
2279
|
+
const headers = {
|
|
2280
|
+
"x-api-key": apiKey,
|
|
2281
|
+
"anthropic-version": ANTHROPIC_VERSION2,
|
|
2282
|
+
"Content-Type": "application/json"
|
|
2283
|
+
};
|
|
2284
|
+
const res = await fetch(`${ANTHROPIC_API_BASE2}${path2}`, {
|
|
2285
|
+
method: options.method || "GET",
|
|
2286
|
+
headers,
|
|
2287
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
2288
|
+
});
|
|
2289
|
+
if (!res.ok) {
|
|
2290
|
+
let errorBody;
|
|
2291
|
+
try {
|
|
2292
|
+
errorBody = await res.json();
|
|
2293
|
+
} catch {
|
|
2294
|
+
errorBody = { message: res.statusText };
|
|
2295
|
+
}
|
|
2296
|
+
const msg = errorBody?.error?.message || errorBody?.message || res.statusText;
|
|
2297
|
+
throw new AnyModelError(res.status >= 500 ? 502 : res.status, msg, {
|
|
2298
|
+
provider_name: "anthropic",
|
|
2299
|
+
raw: errorBody
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
return res;
|
|
2303
|
+
}
|
|
2304
|
+
function translateToAnthropicParams(model, req) {
|
|
2305
|
+
const params = {
|
|
2306
|
+
model,
|
|
2307
|
+
max_tokens: req.max_tokens || DEFAULT_MAX_TOKENS2
|
|
2308
|
+
};
|
|
2309
|
+
const systemMessages = req.messages.filter((m) => m.role === "system");
|
|
2310
|
+
const nonSystemMessages = req.messages.filter((m) => m.role !== "system");
|
|
2311
|
+
if (systemMessages.length > 0) {
|
|
2312
|
+
params.system = systemMessages.map((m) => typeof m.content === "string" ? m.content : "").join("\n");
|
|
2313
|
+
}
|
|
2314
|
+
params.messages = nonSystemMessages.map((m) => ({
|
|
2315
|
+
role: m.role === "tool" ? "user" : m.role,
|
|
2316
|
+
content: m.tool_call_id ? [{ type: "tool_result", tool_use_id: m.tool_call_id, content: typeof m.content === "string" ? m.content : "" }] : m.content
|
|
2317
|
+
}));
|
|
2318
|
+
if (req.temperature !== void 0) params.temperature = req.temperature;
|
|
2319
|
+
if (req.top_p !== void 0) params.top_p = req.top_p;
|
|
2320
|
+
if (req.top_k !== void 0) params.top_k = req.top_k;
|
|
2321
|
+
if (req.stop !== void 0) params.stop_sequences = Array.isArray(req.stop) ? req.stop : [req.stop];
|
|
2322
|
+
if (req.tools && req.tools.length > 0) {
|
|
2323
|
+
params.tools = req.tools.map((t) => ({
|
|
2324
|
+
name: t.function.name,
|
|
2325
|
+
description: t.function.description || "",
|
|
2326
|
+
input_schema: t.function.parameters || { type: "object", properties: {} }
|
|
2327
|
+
}));
|
|
2328
|
+
if (req.tool_choice) {
|
|
2329
|
+
if (req.tool_choice === "auto") {
|
|
2330
|
+
params.tool_choice = { type: "auto" };
|
|
2331
|
+
} else if (req.tool_choice === "required") {
|
|
2332
|
+
params.tool_choice = { type: "any" };
|
|
2333
|
+
} else if (req.tool_choice === "none") {
|
|
2334
|
+
delete params.tools;
|
|
2335
|
+
} else if (typeof req.tool_choice === "object") {
|
|
2336
|
+
params.tool_choice = { type: "tool", name: req.tool_choice.function.name };
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
if (req.response_format) {
|
|
2341
|
+
if (req.response_format.type === "json_object" || req.response_format.type === "json_schema") {
|
|
2342
|
+
const jsonInstruction = "Respond with valid JSON only. Do not include any text outside the JSON object.";
|
|
2343
|
+
params.system = params.system ? `${jsonInstruction}
|
|
2344
|
+
|
|
2345
|
+
${params.system}` : jsonInstruction;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return params;
|
|
2349
|
+
}
|
|
2350
|
+
function mapStopReason(reason) {
|
|
2351
|
+
switch (reason) {
|
|
2352
|
+
case "end_turn":
|
|
2353
|
+
return "stop";
|
|
2354
|
+
case "max_tokens":
|
|
2355
|
+
return "length";
|
|
2356
|
+
case "tool_use":
|
|
2357
|
+
return "tool_calls";
|
|
2358
|
+
case "stop_sequence":
|
|
2359
|
+
return "stop";
|
|
2360
|
+
default:
|
|
2361
|
+
return "stop";
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
function translateAnthropicMessage(msg) {
|
|
2365
|
+
let content = "";
|
|
2366
|
+
const toolCalls = [];
|
|
2367
|
+
for (const block of msg.content || []) {
|
|
2368
|
+
if (block.type === "text") {
|
|
2369
|
+
content += block.text;
|
|
2370
|
+
} else if (block.type === "tool_use") {
|
|
2371
|
+
toolCalls.push({
|
|
2372
|
+
id: block.id,
|
|
2373
|
+
type: "function",
|
|
2374
|
+
function: {
|
|
2375
|
+
name: block.name,
|
|
2376
|
+
arguments: JSON.stringify(block.input)
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
const message = { role: "assistant", content };
|
|
2382
|
+
if (toolCalls.length > 0) {
|
|
2383
|
+
message.tool_calls = toolCalls;
|
|
2384
|
+
}
|
|
2385
|
+
return {
|
|
2386
|
+
id: generateId(),
|
|
2387
|
+
object: "chat.completion",
|
|
2388
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2389
|
+
model: `anthropic/${msg.model}`,
|
|
2390
|
+
choices: [{
|
|
2391
|
+
index: 0,
|
|
2392
|
+
message,
|
|
2393
|
+
finish_reason: mapStopReason(msg.stop_reason)
|
|
2394
|
+
}],
|
|
2395
|
+
usage: {
|
|
2396
|
+
prompt_tokens: msg.usage?.input_tokens || 0,
|
|
2397
|
+
completion_tokens: msg.usage?.output_tokens || 0,
|
|
2398
|
+
total_tokens: (msg.usage?.input_tokens || 0) + (msg.usage?.output_tokens || 0)
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
return {
|
|
2403
|
+
async createBatch(model, requests, _options) {
|
|
2404
|
+
const batchRequests = requests.map((req) => ({
|
|
2405
|
+
custom_id: req.custom_id,
|
|
2406
|
+
params: translateToAnthropicParams(model, req)
|
|
2407
|
+
}));
|
|
2408
|
+
const res = await apiRequest("/messages/batches", {
|
|
2409
|
+
method: "POST",
|
|
2410
|
+
body: { requests: batchRequests }
|
|
2411
|
+
});
|
|
2412
|
+
const data = await res.json();
|
|
2413
|
+
return {
|
|
2414
|
+
providerBatchId: data.id,
|
|
2415
|
+
metadata: {
|
|
2416
|
+
anthropic_type: data.type,
|
|
2417
|
+
created_at: data.created_at
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
},
|
|
2421
|
+
async pollBatch(providerBatchId) {
|
|
2422
|
+
const res = await apiRequest(`/messages/batches/${providerBatchId}`);
|
|
2423
|
+
const data = await res.json();
|
|
2424
|
+
const counts = data.request_counts || {};
|
|
2425
|
+
const total = (counts.processing || 0) + (counts.succeeded || 0) + (counts.errored || 0) + (counts.canceled || 0) + (counts.expired || 0);
|
|
2426
|
+
let status;
|
|
2427
|
+
if (data.processing_status === "ended") {
|
|
2428
|
+
if (counts.succeeded === 0 && (counts.errored > 0 || counts.expired > 0 || counts.canceled > 0)) {
|
|
2429
|
+
status = "failed";
|
|
2430
|
+
} else if (data.cancel_initiated_at) {
|
|
2431
|
+
status = "cancelled";
|
|
2432
|
+
} else {
|
|
2433
|
+
status = "completed";
|
|
2434
|
+
}
|
|
2435
|
+
} else {
|
|
2436
|
+
status = "processing";
|
|
2437
|
+
}
|
|
2438
|
+
return {
|
|
2439
|
+
status,
|
|
2440
|
+
total,
|
|
2441
|
+
completed: counts.succeeded || 0,
|
|
2442
|
+
failed: (counts.errored || 0) + (counts.expired || 0) + (counts.canceled || 0)
|
|
2443
|
+
};
|
|
2444
|
+
},
|
|
2445
|
+
async getBatchResults(providerBatchId) {
|
|
2446
|
+
const res = await apiRequest(`/messages/batches/${providerBatchId}/results`);
|
|
2447
|
+
const text = await res.text();
|
|
2448
|
+
const results = [];
|
|
2449
|
+
for (const line of text.trim().split("\n")) {
|
|
2450
|
+
if (!line) continue;
|
|
2451
|
+
const item = JSON.parse(line);
|
|
2452
|
+
if (item.result?.type === "succeeded") {
|
|
2453
|
+
results.push({
|
|
2454
|
+
custom_id: item.custom_id,
|
|
2455
|
+
status: "success",
|
|
2456
|
+
response: translateAnthropicMessage(item.result.message),
|
|
2457
|
+
error: null
|
|
2458
|
+
});
|
|
2459
|
+
} else {
|
|
2460
|
+
const errorType = item.result?.type || "unknown";
|
|
2461
|
+
const errorMsg = item.result?.error?.message || `Batch item ${errorType}`;
|
|
2462
|
+
results.push({
|
|
2463
|
+
custom_id: item.custom_id,
|
|
2464
|
+
status: "error",
|
|
2465
|
+
response: null,
|
|
2466
|
+
error: {
|
|
2467
|
+
code: errorType === "expired" ? 408 : 500,
|
|
2468
|
+
message: errorMsg
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
return results;
|
|
2474
|
+
},
|
|
2475
|
+
async cancelBatch(providerBatchId) {
|
|
2476
|
+
await apiRequest(`/messages/batches/${providerBatchId}/cancel`, { method: "POST" });
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
|
|
1802
2481
|
// src/client.ts
|
|
1803
2482
|
var AnyModel = class {
|
|
1804
2483
|
registry;
|
|
@@ -1814,6 +2493,9 @@ var AnyModel = class {
|
|
|
1814
2493
|
constructor(config = {}) {
|
|
1815
2494
|
this.config = resolveConfig(config);
|
|
1816
2495
|
this.registry = new ProviderRegistry();
|
|
2496
|
+
if (this.config.io) {
|
|
2497
|
+
configureFsIO(this.config.io);
|
|
2498
|
+
}
|
|
1817
2499
|
this.registerProviders();
|
|
1818
2500
|
this.router = new Router(this.registry, this.config.aliases, this.config);
|
|
1819
2501
|
this.chat = {
|
|
@@ -1863,8 +2545,10 @@ var AnyModel = class {
|
|
|
1863
2545
|
};
|
|
1864
2546
|
this.batchManager = new BatchManager(this.router, {
|
|
1865
2547
|
dir: this.config.batch?.dir,
|
|
1866
|
-
concurrency: this.config.batch?.concurrencyFallback
|
|
2548
|
+
concurrency: this.config.batch?.concurrencyFallback,
|
|
2549
|
+
pollInterval: this.config.batch?.pollInterval
|
|
1867
2550
|
});
|
|
2551
|
+
this.registerBatchAdapters();
|
|
1868
2552
|
this.batches = {
|
|
1869
2553
|
create: (request) => this.batchManager.create(request),
|
|
1870
2554
|
createAndPoll: (request, options) => this.batchManager.createAndPoll(request, options),
|
|
@@ -1916,6 +2600,17 @@ var AnyModel = class {
|
|
|
1916
2600
|
}
|
|
1917
2601
|
}
|
|
1918
2602
|
}
|
|
2603
|
+
registerBatchAdapters() {
|
|
2604
|
+
const config = this.config;
|
|
2605
|
+
const openaiKey = config.openai?.apiKey || process.env.OPENAI_API_KEY;
|
|
2606
|
+
if (openaiKey) {
|
|
2607
|
+
this.batchManager.registerBatchAdapter("openai", createOpenAIBatchAdapter(openaiKey));
|
|
2608
|
+
}
|
|
2609
|
+
const anthropicKey = config.anthropic?.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
2610
|
+
if (anthropicKey) {
|
|
2611
|
+
this.batchManager.registerBatchAdapter("anthropic", createAnthropicBatchAdapter(anthropicKey));
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
1919
2614
|
applyDefaults(request) {
|
|
1920
2615
|
const defaults = this.config.defaults;
|
|
1921
2616
|
if (!defaults) return request;
|
|
@@ -1943,10 +2638,10 @@ var AnyModel = class {
|
|
|
1943
2638
|
|
|
1944
2639
|
// src/server.ts
|
|
1945
2640
|
function parseBody(req) {
|
|
1946
|
-
return new Promise((
|
|
2641
|
+
return new Promise((resolve2, reject) => {
|
|
1947
2642
|
const chunks = [];
|
|
1948
2643
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
1949
|
-
req.on("end", () =>
|
|
2644
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString()));
|
|
1950
2645
|
req.on("error", reject);
|
|
1951
2646
|
});
|
|
1952
2647
|
}
|
|
@@ -1976,7 +2671,7 @@ function createAnyModelServer(options = {}) {
|
|
|
1976
2671
|
const basePath = "/api/v1";
|
|
1977
2672
|
const server = (0, import_node_http.createServer)(async (req, res) => {
|
|
1978
2673
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
1979
|
-
const
|
|
2674
|
+
const path2 = url.pathname;
|
|
1980
2675
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1981
2676
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1982
2677
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
@@ -1986,11 +2681,11 @@ function createAnyModelServer(options = {}) {
|
|
|
1986
2681
|
return;
|
|
1987
2682
|
}
|
|
1988
2683
|
try {
|
|
1989
|
-
if (
|
|
2684
|
+
if (path2 === "/health" && req.method === "GET") {
|
|
1990
2685
|
sendJSON(res, 200, { status: "ok" });
|
|
1991
2686
|
return;
|
|
1992
2687
|
}
|
|
1993
|
-
if (
|
|
2688
|
+
if (path2 === `${basePath}/chat/completions` && req.method === "POST") {
|
|
1994
2689
|
const body = JSON.parse(await parseBody(req));
|
|
1995
2690
|
if (body.stream) {
|
|
1996
2691
|
const stream = await client.chat.completions.create(body);
|
|
@@ -2001,14 +2696,14 @@ function createAnyModelServer(options = {}) {
|
|
|
2001
2696
|
}
|
|
2002
2697
|
return;
|
|
2003
2698
|
}
|
|
2004
|
-
if (
|
|
2699
|
+
if (path2 === `${basePath}/models` && req.method === "GET") {
|
|
2005
2700
|
const provider = url.searchParams.get("provider") || void 0;
|
|
2006
2701
|
const models = await client.models.list({ provider });
|
|
2007
2702
|
sendJSON(res, 200, { object: "list", data: models });
|
|
2008
2703
|
return;
|
|
2009
2704
|
}
|
|
2010
|
-
if (
|
|
2011
|
-
const id =
|
|
2705
|
+
if (path2.startsWith(`${basePath}/generation/`) && req.method === "GET") {
|
|
2706
|
+
const id = path2.substring(`${basePath}/generation/`.length);
|
|
2012
2707
|
const stats = client.generation.get(id);
|
|
2013
2708
|
if (!stats) {
|
|
2014
2709
|
sendError(res, 404, `Generation ${id} not found`);
|
|
@@ -2017,26 +2712,26 @@ function createAnyModelServer(options = {}) {
|
|
|
2017
2712
|
sendJSON(res, 200, stats);
|
|
2018
2713
|
return;
|
|
2019
2714
|
}
|
|
2020
|
-
if (
|
|
2715
|
+
if (path2 === `${basePath}/batches` && req.method === "POST") {
|
|
2021
2716
|
const body = JSON.parse(await parseBody(req));
|
|
2022
2717
|
const batch = await client.batches.create(body);
|
|
2023
2718
|
sendJSON(res, 201, batch);
|
|
2024
2719
|
return;
|
|
2025
2720
|
}
|
|
2026
|
-
if (
|
|
2027
|
-
const batches = client.batches.list();
|
|
2721
|
+
if (path2 === `${basePath}/batches` && req.method === "GET") {
|
|
2722
|
+
const batches = await client.batches.list();
|
|
2028
2723
|
sendJSON(res, 200, { object: "list", data: batches });
|
|
2029
2724
|
return;
|
|
2030
2725
|
}
|
|
2031
|
-
if (
|
|
2032
|
-
const parts =
|
|
2726
|
+
if (path2.startsWith(`${basePath}/batches/`) && req.method === "GET") {
|
|
2727
|
+
const parts = path2.substring(`${basePath}/batches/`.length).split("/");
|
|
2033
2728
|
const id = parts[0];
|
|
2034
2729
|
if (parts[1] === "results") {
|
|
2035
|
-
const results = client.batches.results(id);
|
|
2730
|
+
const results = await client.batches.results(id);
|
|
2036
2731
|
sendJSON(res, 200, results);
|
|
2037
2732
|
return;
|
|
2038
2733
|
}
|
|
2039
|
-
const batch = client.batches.get(id);
|
|
2734
|
+
const batch = await client.batches.get(id);
|
|
2040
2735
|
if (!batch) {
|
|
2041
2736
|
sendError(res, 404, `Batch ${id} not found`);
|
|
2042
2737
|
return;
|
|
@@ -2044,16 +2739,16 @@ function createAnyModelServer(options = {}) {
|
|
|
2044
2739
|
sendJSON(res, 200, batch);
|
|
2045
2740
|
return;
|
|
2046
2741
|
}
|
|
2047
|
-
if (
|
|
2048
|
-
const parts =
|
|
2742
|
+
if (path2.startsWith(`${basePath}/batches/`) && req.method === "POST") {
|
|
2743
|
+
const parts = path2.substring(`${basePath}/batches/`.length).split("/");
|
|
2049
2744
|
const id = parts[0];
|
|
2050
2745
|
if (parts[1] === "cancel") {
|
|
2051
|
-
const batch = client.batches.cancel(id);
|
|
2746
|
+
const batch = await client.batches.cancel(id);
|
|
2052
2747
|
sendJSON(res, 200, batch);
|
|
2053
2748
|
return;
|
|
2054
2749
|
}
|
|
2055
2750
|
}
|
|
2056
|
-
sendError(res, 404, `Not found: ${
|
|
2751
|
+
sendError(res, 404, `Not found: ${path2}`);
|
|
2057
2752
|
} catch (err) {
|
|
2058
2753
|
const code = err?.code || 500;
|
|
2059
2754
|
const message = err?.message || "Internal server error";
|