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