@premai/api-sdk 1.0.40 → 1.0.42

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/dist/index.mjs CHANGED
@@ -1,14 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
- // src/index.ts
5
- import dotenv2 from "dotenv";
6
-
7
- // src/server.ts
8
- import dotenv from "dotenv";
9
- import express from "express";
10
- import multer from "multer";
11
-
12
4
  // src/audio/index.ts
13
5
  import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/ciphers/utils.js";
14
6
 
@@ -48,25 +40,127 @@ function getGatewayErrorMessage(err) {
48
40
  return err.kind.message;
49
41
  return null;
50
42
  }
51
- async function attest(apiKey, options = { enabled: true }) {
52
- if (!options.enabled)
53
- return null;
43
+ var ATTEST_TTL_MS = 30000;
44
+ var ATTEST_CACHE_MAX = 500;
45
+ var ATTEST_MAX_ATTEMPTS = 4;
46
+ var ATTEST_RETRY_BASE_MS = 250;
47
+ var ATTEST_RETRY_MAX_MS = 2000;
48
+ var TRANSIENT_PATTERNS = [
49
+ /EOF while parsing/i,
50
+ /error decoding response body/i,
51
+ /connection (reset|closed|refused)/i,
52
+ /socket hang up/i,
53
+ /ETIMEDOUT/i
54
+ ];
55
+ var attestCache = new Map;
56
+ var attestInflight = new Map;
57
+ function attestCacheKey(apiKey, model) {
58
+ return `${apiKey}|${model ?? ""}`;
59
+ }
60
+ function pruneExpired(now) {
61
+ for (const [key, entry] of attestCache) {
62
+ if (entry.expires <= now) {
63
+ attestCache.delete(key);
64
+ } else {
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ function isTransientError(err) {
70
+ const messages = [];
71
+ if (err instanceof Error) {
72
+ messages.push(err.message);
73
+ }
74
+ if (isAttestationError(err) && Array.isArray(err.cause)) {
75
+ messages.push(...err.cause);
76
+ }
77
+ return messages.some((m) => TRANSIENT_PATTERNS.some((re) => re.test(m)));
78
+ }
79
+ function backoffDelayMs(attempt) {
80
+ const exp = ATTEST_RETRY_BASE_MS * 2 ** (attempt - 1);
81
+ const capped = Math.min(exp, ATTEST_RETRY_MAX_MS);
82
+ const jitter = Math.floor(Math.random() * (capped / 2));
83
+ return capped + jitter;
84
+ }
85
+ function delay(ms) {
86
+ return new Promise((resolve) => setTimeout(resolve, ms));
87
+ }
88
+ function safeFree(obj) {
89
+ if (typeof obj?.free !== "function")
90
+ return;
91
+ try {
92
+ obj.free();
93
+ } catch {}
94
+ }
95
+ async function attemptAttest(apiKey, options) {
54
96
  const prem = await loadPrem();
55
- const client = await new prem.ClientBuilder(endpoints.proxy ?? "").with_authorization(apiKey).build();
56
- let query = new prem.QueryParams;
57
- if (options.model)
58
- query = query.with("model", options.model);
97
+ let client;
98
+ let attested;
99
+ let headers;
100
+ let sessionId;
59
101
  try {
60
- client.set_query(query);
61
- const attested = await client.attest();
62
- const headers = attested.headers();
63
- const sessionId = attested.headers().gpu()?.get("x-session-id") ?? null;
64
- headers.free();
65
- attested.free();
66
- return sessionId;
102
+ client = await new prem.ClientBuilder(endpoints.proxy ?? "").with_authorization(apiKey).build();
103
+ if (options.model) {
104
+ client.set_query(new prem.QueryParams().with("model", options.model));
105
+ }
106
+ attested = await client.attest();
107
+ headers = attested.headers();
108
+ sessionId = headers.cpu()?.get("x-session-id") ?? headers.gpu()?.get("x-session-id") ?? null;
67
109
  } finally {
68
- client.free();
110
+ safeFree(headers);
111
+ safeFree(attested);
112
+ safeFree(client);
69
113
  }
114
+ if (sessionId === null) {
115
+ throw new Error("missing x-session-id issued by attestation");
116
+ }
117
+ return sessionId;
118
+ }
119
+ async function runAttest(apiKey, options) {
120
+ let lastErr;
121
+ for (let attempt = 1;attempt <= ATTEST_MAX_ATTEMPTS; attempt++) {
122
+ try {
123
+ return await attemptAttest(apiKey, options);
124
+ } catch (err) {
125
+ lastErr = err;
126
+ if (attempt === ATTEST_MAX_ATTEMPTS || !isTransientError(err)) {
127
+ throw err;
128
+ }
129
+ await delay(backoffDelayMs(attempt));
130
+ }
131
+ }
132
+ throw lastErr;
133
+ }
134
+ async function attest(apiKey, options = { enabled: true }) {
135
+ if (!options.enabled)
136
+ return null;
137
+ const key = attestCacheKey(apiKey, options.model);
138
+ const now = Date.now();
139
+ const cached = attestCache.get(key);
140
+ if (cached) {
141
+ if (cached.expires > now)
142
+ return cached.sessionId;
143
+ attestCache.delete(key);
144
+ }
145
+ const inflight = attestInflight.get(key);
146
+ if (inflight) {
147
+ return inflight;
148
+ }
149
+ const work = runAttest(apiKey, options).then((sessionId) => {
150
+ const insertTime = Date.now();
151
+ pruneExpired(insertTime);
152
+ attestCache.set(key, { sessionId, expires: insertTime + ATTEST_TTL_MS });
153
+ if (attestCache.size > ATTEST_CACHE_MAX) {
154
+ const oldest = attestCache.keys().next().value;
155
+ if (oldest)
156
+ attestCache.delete(oldest);
157
+ }
158
+ return sessionId;
159
+ }).finally(() => {
160
+ attestInflight.delete(key);
161
+ });
162
+ attestInflight.set(key, work);
163
+ return work;
70
164
  }
71
165
 
72
166
  // src/utils/crypto.ts
@@ -246,7 +340,8 @@ async function preprocessAudioRequest(body, encryptionKeys) {
246
340
  const isDeepgram = body.model.startsWith("deepgram/");
247
341
  const requestBody = isDeepgram ? {
248
342
  model: body.model,
249
- diarize: body.diarize
343
+ diarize: body.diarize,
344
+ smart_format: body.smart_format
250
345
  } : {
251
346
  model: body.model,
252
347
  language: body.language,
@@ -1066,10 +1161,14 @@ function createRvencChatClient(apiKey, encryptionKeys, requestTimeoutMs = DEFAUL
1066
1161
  }
1067
1162
  clearTimeout(timeoutId);
1068
1163
  if (isStreaming) {
1069
- return await postprocessStreamingResponse(response, encryptedRequest.sharedSecret, encryptedRequest.nonce, maxBufferSize);
1070
- } else {
1071
- return await postprocessNonStreamingResponse(response, encryptedRequest.sharedSecret);
1164
+ const contentType = response.headers.get("content-type") ?? "";
1165
+ if (contentType.includes("text/event-stream")) {
1166
+ return await postprocessStreamingResponse(response, encryptedRequest.sharedSecret, encryptedRequest.nonce, maxBufferSize);
1167
+ }
1168
+ const completion = await postprocessNonStreamingResponse(response, encryptedRequest.sharedSecret);
1169
+ return completionToChunkStream(completion);
1072
1170
  }
1171
+ return await postprocessNonStreamingResponse(response, encryptedRequest.sharedSecret);
1073
1172
  } catch (error) {
1074
1173
  clearTimeout(timeoutId);
1075
1174
  if (error instanceof Error && error.name === "AbortError") {
@@ -1080,6 +1179,39 @@ function createRvencChatClient(apiKey, encryptionKeys, requestTimeoutMs = DEFAUL
1080
1179
  };
1081
1180
  return client;
1082
1181
  }
1182
+ async function* completionToChunkStream(completion) {
1183
+ const choice = completion.choices[0];
1184
+ const message = choice?.message;
1185
+ const content = typeof message?.content === "string" ? message.content : "";
1186
+ const toolCalls = message?.tool_calls?.filter((tc) => tc.type === "function").map((tc, i) => ({
1187
+ index: i,
1188
+ id: tc.id,
1189
+ type: "function",
1190
+ function: {
1191
+ name: tc.function.name,
1192
+ arguments: tc.function.arguments
1193
+ }
1194
+ }));
1195
+ yield {
1196
+ id: completion.id,
1197
+ object: "chat.completion.chunk",
1198
+ created: completion.created,
1199
+ model: completion.model,
1200
+ choices: [
1201
+ {
1202
+ index: choice?.index ?? 0,
1203
+ delta: {
1204
+ role: "assistant",
1205
+ content,
1206
+ ...toolCalls && toolCalls.length > 0 && { tool_calls: toolCalls }
1207
+ },
1208
+ finish_reason: choice?.finish_reason ?? "stop",
1209
+ logprobs: null
1210
+ }
1211
+ ],
1212
+ usage: completion.usage ?? null
1213
+ };
1214
+ }
1083
1215
  async function* createDecryptedStreamGenerator(reader, sharedSecret, nonce, maxBufferSize) {
1084
1216
  const decoder = new TextDecoder;
1085
1217
  let buffer = "";
@@ -1260,7 +1392,7 @@ async function callFileOutputTool(toolName, params, apiKey, dekStore, clientKEK,
1260
1392
  };
1261
1393
  const response = await callToolRequest(toolName, body, apiKey, timeoutMs, attest2);
1262
1394
  const result = await downloadAndDecryptFile(response, dek, apiKey, timeoutMs);
1263
- if (result && result.fileId) {
1395
+ if (result?.fileId) {
1264
1396
  if (!dekStore.fileDEKs) {
1265
1397
  dekStore.fileDEKs = new Map;
1266
1398
  }
@@ -1317,7 +1449,7 @@ async function callRagTool(toolName, params, apiKey, dekStore, clientKEK, timeou
1317
1449
  }
1318
1450
  const _clientKEK = clientKEK ? hexToBytes6(clientKEK) : getClientKEK();
1319
1451
  const encryptedFileDEKs = fileIds.reduce((acc, fileId) => {
1320
- const fileDEK = dekStore.fileDEKs.get(fileId);
1452
+ const fileDEK = dekStore.fileDEKs?.get(fileId);
1321
1453
  if (!fileDEK) {
1322
1454
  return acc;
1323
1455
  }
@@ -1417,39 +1549,947 @@ async function createRvencClient(options) {
1417
1549
  return client;
1418
1550
  }
1419
1551
  var core_default = createRvencClient;
1552
+ // src/server/create-app.ts
1553
+ import express from "express";
1420
1554
 
1421
- // src/server.ts
1422
- dotenv.config();
1423
- var app = express();
1424
- var HOST = process.env.HOST ?? "127.0.0.1";
1425
- var PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;
1426
- var serverProxyUrl = process.env.PROXY_URL;
1427
- var serverEnclaveUrl = process.env.ENCLAVE_URL;
1428
- var serverKek = process.env.CLIENT_KEK;
1429
- var serverAttest = true;
1430
- app.use(express.json());
1431
- var storage = multer.memoryStorage();
1432
- var upload = multer({
1433
- storage,
1434
- limits: {
1435
- fileSize: 25 * 1024 * 1024
1555
+ // src/anthropic/http.ts
1556
+ import { randomBytes as randomBytes5 } from "node:crypto";
1557
+ var ANTHROPIC_VERSION_DEFAULT = "2023-06-01";
1558
+ var ANTHROPIC_VERSION_DATE = /^\d{4}-\d{2}-\d{2}$/;
1559
+ function isAnthropicApiVersionSupported(version) {
1560
+ if (version === ANTHROPIC_VERSION_DEFAULT) {
1561
+ return true;
1562
+ }
1563
+ return ANTHROPIC_VERSION_DATE.test(version);
1564
+ }
1565
+ function newAnthropicRequestId() {
1566
+ return `req_${randomBytes5(12).toString("hex")}`;
1567
+ }
1568
+ function newAnthropicMessageId() {
1569
+ return `msg_${randomBytes5(12).toString("hex")}`;
1570
+ }
1571
+ function extractAnthropicApiKey(req) {
1572
+ const raw = req.headers["x-api-key"];
1573
+ if (typeof raw === "string" && raw.length > 0) {
1574
+ return raw;
1575
+ }
1576
+ if (Array.isArray(raw) && raw[0]) {
1577
+ return raw[0];
1436
1578
  }
1437
- });
1438
- var clientCache = new Map;
1439
- function extractApiKey(req) {
1440
1579
  const authHeader = req.headers.authorization;
1441
1580
  if (!authHeader) {
1442
1581
  return null;
1443
1582
  }
1444
1583
  if (authHeader.startsWith("Bearer ")) {
1445
- return authHeader.substring(7);
1584
+ return authHeader.slice(7);
1446
1585
  }
1447
1586
  return authHeader;
1448
1587
  }
1449
- async function getOrCreateClient(apiKey) {
1450
- if (clientCache.has(apiKey)) {
1451
- return clientCache.get(apiKey);
1588
+ function getAnthropicVersionHeader(req) {
1589
+ const raw = req.headers["anthropic-version"];
1590
+ if (typeof raw === "string" && raw.length > 0) {
1591
+ return raw;
1592
+ }
1593
+ if (Array.isArray(raw) && raw[0]) {
1594
+ return raw[0];
1452
1595
  }
1596
+ return null;
1597
+ }
1598
+ function resolveAnthropicVersion(req) {
1599
+ const header = getAnthropicVersionHeader(req);
1600
+ const version = header ?? ANTHROPIC_VERSION_DEFAULT;
1601
+ if (!isAnthropicApiVersionSupported(version)) {
1602
+ return {
1603
+ ok: false,
1604
+ message: `Unsupported anthropic-version: ${version}. Expected a dated version (YYYY-MM-DD) or ${ANTHROPIC_VERSION_DEFAULT}.`
1605
+ };
1606
+ }
1607
+ return { ok: true, version };
1608
+ }
1609
+ function sendAnthropicHttpError(res, status, errorType, message, requestId) {
1610
+ res.setHeader("request-id", requestId);
1611
+ res.status(status).json({
1612
+ type: "error",
1613
+ error: { type: errorType, message },
1614
+ request_id: requestId
1615
+ });
1616
+ }
1617
+ function httpStatusToAnthropicErrorType(status) {
1618
+ if (status === 401) {
1619
+ return "authentication_error";
1620
+ }
1621
+ if (status === 402) {
1622
+ return "billing_error";
1623
+ }
1624
+ if (status === 403) {
1625
+ return "permission_error";
1626
+ }
1627
+ if (status === 404) {
1628
+ return "not_found_error";
1629
+ }
1630
+ if (status === 413) {
1631
+ return "request_too_large";
1632
+ }
1633
+ if (status === 429) {
1634
+ return "rate_limit_error";
1635
+ }
1636
+ if (status === 504) {
1637
+ return "timeout_error";
1638
+ }
1639
+ if (status === 529) {
1640
+ return "overloaded_error";
1641
+ }
1642
+ if (status >= 400 && status < 500) {
1643
+ return "invalid_request_error";
1644
+ }
1645
+ return "api_error";
1646
+ }
1647
+ function extractErrorMessage(err) {
1648
+ if (!err || typeof err !== "object") {
1649
+ return null;
1650
+ }
1651
+ const o = err;
1652
+ if (typeof o.message === "string" && o.message.length > 0) {
1653
+ return o.message;
1654
+ }
1655
+ if (typeof o.error === "string" && o.error.length > 0) {
1656
+ return o.error;
1657
+ }
1658
+ if (o.error && typeof o.error === "object") {
1659
+ const nested = o.error.message;
1660
+ if (typeof nested === "string" && nested.length > 0) {
1661
+ return nested;
1662
+ }
1663
+ }
1664
+ return null;
1665
+ }
1666
+ function looksLikeApiErrorResponse(err) {
1667
+ if (!err || typeof err !== "object")
1668
+ return false;
1669
+ const o = err;
1670
+ if (typeof o.status !== "number")
1671
+ return false;
1672
+ return "error" in o || "message" in o;
1673
+ }
1674
+ function mapUnknownErrorToAnthropicResponse(err, res, requestId) {
1675
+ if (looksLikeApiErrorResponse(err)) {
1676
+ const status = err.status >= 400 && err.status < 600 ? err.status : 500;
1677
+ const message2 = extractErrorMessage(err) ?? "Request failed";
1678
+ const errorType = httpStatusToAnthropicErrorType(status);
1679
+ sendAnthropicHttpError(res, status, errorType, message2, requestId);
1680
+ return;
1681
+ }
1682
+ const message = extractErrorMessage(err) ?? (err instanceof Error ? err.message : "Internal server error");
1683
+ sendAnthropicHttpError(res, 500, "api_error", message, requestId);
1684
+ }
1685
+ function writeAnthropicSseEvent(res, event, data) {
1686
+ res.write(`event: ${event}
1687
+ data: ${JSON.stringify(data)}
1688
+
1689
+ `);
1690
+ }
1691
+
1692
+ // src/anthropic/to-openai.ts
1693
+ class AnthropicRequestValidationError extends Error {
1694
+ status = 400;
1695
+ anthropicType = "invalid_request_error";
1696
+ constructor(message) {
1697
+ super(message);
1698
+ this.name = "AnthropicRequestValidationError";
1699
+ }
1700
+ }
1701
+ function systemToOpenAiMessages(system) {
1702
+ if (typeof system === "string") {
1703
+ if (system.length === 0) {
1704
+ return [];
1705
+ }
1706
+ return [{ role: "system", content: system }];
1707
+ }
1708
+ if (Array.isArray(system)) {
1709
+ const parts = [];
1710
+ for (const block of system) {
1711
+ if (block && block.type === "text" && typeof block.text === "string") {
1712
+ parts.push(block.text);
1713
+ } else if (block && typeof block === "object") {
1714
+ console.warn(`[proxy] system block type "${block.type}" is not supported and will be ignored.`);
1715
+ }
1716
+ }
1717
+ if (parts.length === 0) {
1718
+ return [];
1719
+ }
1720
+ return [{ role: "system", content: parts.join(`
1721
+
1722
+ `) }];
1723
+ }
1724
+ if (system.type === "text" && typeof system.text === "string") {
1725
+ return [{ role: "system", content: system.text }];
1726
+ }
1727
+ throw new AnthropicRequestValidationError("Invalid system parameter shape.");
1728
+ }
1729
+ function toolResultContentToString(content) {
1730
+ if (typeof content === "string") {
1731
+ return content;
1732
+ }
1733
+ if (content === null || content === undefined) {
1734
+ return "";
1735
+ }
1736
+ if (Array.isArray(content)) {
1737
+ const parts = [];
1738
+ for (const block of content) {
1739
+ if (block && typeof block === "object" && "type" in block && block.type === "text" && typeof block.text === "string") {
1740
+ parts.push(block.text);
1741
+ } else {
1742
+ parts.push(JSON.stringify(block));
1743
+ }
1744
+ }
1745
+ return parts.join(`
1746
+ `);
1747
+ }
1748
+ return JSON.stringify(content);
1749
+ }
1750
+ function anthropicImageBlockToOpenAIPart(part) {
1751
+ const source = part.source;
1752
+ if (!source || typeof source !== "object") {
1753
+ return null;
1754
+ }
1755
+ const s = source;
1756
+ if (s.type === "base64" && typeof s.data === "string" && s.data.length > 0) {
1757
+ const mediaType = typeof s.media_type === "string" && s.media_type.length > 0 ? s.media_type : "image/png";
1758
+ return {
1759
+ type: "image_url",
1760
+ image_url: { url: `data:${mediaType};base64,${s.data}` }
1761
+ };
1762
+ }
1763
+ if (s.type === "url" && typeof s.url === "string" && s.url.length > 0) {
1764
+ return { type: "image_url", image_url: { url: s.url } };
1765
+ }
1766
+ return null;
1767
+ }
1768
+ function anthropicUserContentToOpenAIMessages(content) {
1769
+ if (typeof content === "string") {
1770
+ return [{ role: "user", content }];
1771
+ }
1772
+ const out = [];
1773
+ const partsBuf = [];
1774
+ const flushParts = () => {
1775
+ if (partsBuf.length === 0) {
1776
+ return;
1777
+ }
1778
+ if (partsBuf.length === 1 && partsBuf[0].type === "text") {
1779
+ out.push({ role: "user", content: partsBuf[0].text });
1780
+ } else {
1781
+ out.push({ role: "user", content: [...partsBuf] });
1782
+ }
1783
+ partsBuf.length = 0;
1784
+ };
1785
+ for (const part of content) {
1786
+ if (!part || typeof part !== "object") {
1787
+ throw new AnthropicRequestValidationError("Invalid message content entry.");
1788
+ }
1789
+ if (part.type === "text" && typeof part.text === "string") {
1790
+ partsBuf.push({
1791
+ type: "text",
1792
+ text: part.text
1793
+ });
1794
+ continue;
1795
+ }
1796
+ if (part.type === "image") {
1797
+ const imgPart = anthropicImageBlockToOpenAIPart(part);
1798
+ if (imgPart) {
1799
+ partsBuf.push(imgPart);
1800
+ }
1801
+ continue;
1802
+ }
1803
+ if (part.type === "tool_result") {
1804
+ flushParts();
1805
+ const id = part.tool_use_id;
1806
+ const rawContent = part.content;
1807
+ if (typeof id !== "string" || id.length === 0) {
1808
+ throw new AnthropicRequestValidationError("tool_result blocks require a non-empty tool_use_id.");
1809
+ }
1810
+ out.push({
1811
+ role: "tool",
1812
+ tool_call_id: id,
1813
+ content: toolResultContentToString(rawContent)
1814
+ });
1815
+ }
1816
+ }
1817
+ flushParts();
1818
+ return out;
1819
+ }
1820
+ function anthropicAssistantContentToOpenAI(content) {
1821
+ if (typeof content === "string") {
1822
+ return { role: "assistant", content };
1823
+ }
1824
+ const textParts = [];
1825
+ const toolCalls = [];
1826
+ for (const part of content) {
1827
+ if (!part || typeof part !== "object") {
1828
+ throw new AnthropicRequestValidationError("Invalid message content entry.");
1829
+ }
1830
+ if (part.type === "text" && typeof part.text === "string") {
1831
+ textParts.push(part.text);
1832
+ continue;
1833
+ }
1834
+ if (part.type === "tool_use") {
1835
+ const p = part;
1836
+ if (typeof p.id !== "string" || p.id.length === 0) {
1837
+ throw new AnthropicRequestValidationError("tool_use blocks require a non-empty id.");
1838
+ }
1839
+ if (typeof p.name !== "string" || p.name.length === 0) {
1840
+ throw new AnthropicRequestValidationError("tool_use blocks require a non-empty name.");
1841
+ }
1842
+ const args = typeof p.input === "string" ? p.input : JSON.stringify(p.input ?? {});
1843
+ toolCalls.push({
1844
+ id: p.id,
1845
+ type: "function",
1846
+ function: { name: p.name, arguments: args }
1847
+ });
1848
+ }
1849
+ }
1850
+ const msg = {
1851
+ role: "assistant",
1852
+ content: textParts.length > 0 ? textParts.join(`
1853
+ `) : null
1854
+ };
1855
+ if (toolCalls.length > 0) {
1856
+ msg.tool_calls = toolCalls;
1857
+ }
1858
+ return msg;
1859
+ }
1860
+ function anthropicToolsToOpenAI(tools) {
1861
+ if (tools === undefined) {
1862
+ return;
1863
+ }
1864
+ if (!Array.isArray(tools)) {
1865
+ throw new AnthropicRequestValidationError("tools must be an array.");
1866
+ }
1867
+ const out = [];
1868
+ for (const t of tools) {
1869
+ if (!t || typeof t !== "object") {
1870
+ throw new AnthropicRequestValidationError("Invalid tool entry.");
1871
+ }
1872
+ const name = t.name;
1873
+ const desc = t.description;
1874
+ const schema = t.input_schema;
1875
+ if (typeof name !== "string" || name.length === 0) {
1876
+ throw new AnthropicRequestValidationError("Each tool must include a non-empty name.");
1877
+ }
1878
+ if (schema !== undefined && (typeof schema !== "object" || schema === null)) {
1879
+ throw new AnthropicRequestValidationError("tool input_schema must be an object when provided.");
1880
+ }
1881
+ out.push({
1882
+ type: "function",
1883
+ function: {
1884
+ name,
1885
+ ...typeof desc === "string" ? { description: desc } : {},
1886
+ parameters: schema ?? {
1887
+ type: "object",
1888
+ properties: {}
1889
+ }
1890
+ }
1891
+ });
1892
+ }
1893
+ return out;
1894
+ }
1895
+ function anthropicToolChoiceToOpenAI(toolChoice) {
1896
+ if (toolChoice === undefined) {
1897
+ return;
1898
+ }
1899
+ if (typeof toolChoice !== "object" || toolChoice === null || !("type" in toolChoice)) {
1900
+ throw new AnthropicRequestValidationError("Invalid tool_choice shape.");
1901
+ }
1902
+ const tc = toolChoice;
1903
+ switch (tc.type) {
1904
+ case "auto":
1905
+ return "auto";
1906
+ case "none":
1907
+ return "none";
1908
+ case "any":
1909
+ return "required";
1910
+ case "tool": {
1911
+ if (typeof tc.name !== "string" || tc.name.length === 0) {
1912
+ throw new AnthropicRequestValidationError('tool_choice type "tool" requires a non-empty name.');
1913
+ }
1914
+ return { type: "function", function: { name: tc.name } };
1915
+ }
1916
+ default:
1917
+ throw new AnthropicRequestValidationError(`Unsupported tool_choice type "${tc.type}".`);
1918
+ }
1919
+ }
1920
+ function anthropicMessagesCreateToOpenAI(body) {
1921
+ if (typeof body.model !== "string" || !body.model) {
1922
+ throw new AnthropicRequestValidationError("model is required.");
1923
+ }
1924
+ if (typeof body.max_tokens !== "number" || !Number.isFinite(body.max_tokens)) {
1925
+ throw new AnthropicRequestValidationError("max_tokens is required and must be a number.");
1926
+ }
1927
+ if (!Array.isArray(body.messages)) {
1928
+ throw new AnthropicRequestValidationError("messages must be an array.");
1929
+ }
1930
+ const messages = [];
1931
+ if (body.system !== undefined) {
1932
+ messages.push(...systemToOpenAiMessages(body.system));
1933
+ }
1934
+ for (const m of body.messages) {
1935
+ if (m.role !== "user" && m.role !== "assistant") {
1936
+ throw new AnthropicRequestValidationError(`Invalid message role "${m.role}".`);
1937
+ }
1938
+ if (m.role === "user") {
1939
+ messages.push(...anthropicUserContentToOpenAIMessages(m.content));
1940
+ } else {
1941
+ messages.push(anthropicAssistantContentToOpenAI(m.content));
1942
+ }
1943
+ }
1944
+ const isStreaming = Boolean(body.stream);
1945
+ const params = {
1946
+ model: body.model,
1947
+ messages,
1948
+ max_tokens: body.max_tokens,
1949
+ stream: isStreaming
1950
+ };
1951
+ if (isStreaming) {
1952
+ params.stream_options = { include_usage: true };
1953
+ }
1954
+ const tools = anthropicToolsToOpenAI(body.tools);
1955
+ if (tools !== undefined && tools.length > 0) {
1956
+ params.tools = tools;
1957
+ }
1958
+ const toolChoice = anthropicToolChoiceToOpenAI(body.tool_choice);
1959
+ if (toolChoice !== undefined) {
1960
+ params.tool_choice = toolChoice;
1961
+ }
1962
+ if (body.stop_sequences !== undefined) {
1963
+ if (!Array.isArray(body.stop_sequences) || !body.stop_sequences.every((s) => typeof s === "string")) {
1964
+ throw new AnthropicRequestValidationError("stop_sequences must be an array of strings.");
1965
+ }
1966
+ params.stop = body.stop_sequences;
1967
+ }
1968
+ if (typeof body.temperature === "number") {
1969
+ params.temperature = body.temperature;
1970
+ }
1971
+ if (typeof body.top_p === "number") {
1972
+ params.top_p = body.top_p;
1973
+ }
1974
+ if (typeof body.top_k === "number") {
1975
+ console.warn("[proxy] top_k is not supported by the OpenAI API and will be ignored.");
1976
+ }
1977
+ return params;
1978
+ }
1979
+
1980
+ // src/anthropic/count-tokens-route.ts
1981
+ function extractTextCharCount(body) {
1982
+ let len = 0;
1983
+ if (typeof body.system === "string") {
1984
+ len += body.system.length;
1985
+ } else if (Array.isArray(body.system)) {
1986
+ for (const block of body.system) {
1987
+ if (block && block.type === "text" && typeof block.text === "string") {
1988
+ len += block.text.length;
1989
+ }
1990
+ }
1991
+ } else if (body.system && typeof body.system === "object" && body.system.type === "text") {
1992
+ len += body.system.text.length;
1993
+ }
1994
+ for (const msg of body.messages) {
1995
+ if (typeof msg.content === "string") {
1996
+ len += msg.content.length;
1997
+ } else if (Array.isArray(msg.content)) {
1998
+ for (const part of msg.content) {
1999
+ if (!part || typeof part !== "object")
2000
+ continue;
2001
+ if (part.type === "text" && typeof part.text === "string") {
2002
+ len += part.text.length;
2003
+ } else if (part.type === "tool_result") {
2004
+ const c = part.content;
2005
+ if (typeof c === "string") {
2006
+ len += c.length;
2007
+ }
2008
+ }
2009
+ }
2010
+ }
2011
+ }
2012
+ if (Array.isArray(body.tools)) {
2013
+ len += JSON.stringify(body.tools).length;
2014
+ }
2015
+ return len;
2016
+ }
2017
+ function registerAnthropicCountTokensRoute(router, _deps) {
2018
+ router.post("/v1/messages/count_tokens", async (req, res) => {
2019
+ const requestId = newAnthropicRequestId();
2020
+ res.setHeader("request-id", requestId);
2021
+ const versionResult = resolveAnthropicVersion(req);
2022
+ if (!versionResult.ok) {
2023
+ return sendAnthropicHttpError(res, 400, "invalid_request_error", versionResult.message, requestId);
2024
+ }
2025
+ const apiKey = extractAnthropicApiKey(req);
2026
+ if (!apiKey) {
2027
+ return sendAnthropicHttpError(res, 401, "authentication_error", "Missing x-api-key header (or Authorization with API key).", requestId);
2028
+ }
2029
+ try {
2030
+ const raw = req.body;
2031
+ const body = {
2032
+ ...raw,
2033
+ max_tokens: typeof raw.max_tokens === "number" && Number.isFinite(raw.max_tokens) ? raw.max_tokens : 4096,
2034
+ stream: false
2035
+ };
2036
+ anthropicMessagesCreateToOpenAI(body);
2037
+ const input_tokens = Math.max(1, Math.ceil(extractTextCharCount(body) / 4));
2038
+ res.json({ input_tokens });
2039
+ } catch (err) {
2040
+ if (err instanceof AnthropicRequestValidationError) {
2041
+ return sendAnthropicHttpError(res, err.status, err.anthropicType, err.message, requestId);
2042
+ }
2043
+ mapUnknownErrorToAnthropicResponse(err, res, requestId);
2044
+ }
2045
+ });
2046
+ }
2047
+
2048
+ // src/anthropic/from-openai.ts
2049
+ function openAiFinishReasonToAnthropic(finish) {
2050
+ if (!finish) {
2051
+ return { stop_reason: null, stop_sequence: null };
2052
+ }
2053
+ switch (finish) {
2054
+ case "stop":
2055
+ return { stop_reason: "end_turn", stop_sequence: null };
2056
+ case "length":
2057
+ return { stop_reason: "max_tokens", stop_sequence: null };
2058
+ case "tool_calls":
2059
+ return { stop_reason: "tool_use", stop_sequence: null };
2060
+ case "content_filter":
2061
+ return { stop_reason: "refusal", stop_sequence: null };
2062
+ default:
2063
+ return { stop_reason: "end_turn", stop_sequence: null };
2064
+ }
2065
+ }
2066
+ function extractTextFromAssistantContent(content) {
2067
+ if (content == null) {
2068
+ return "";
2069
+ }
2070
+ if (typeof content === "string") {
2071
+ return content;
2072
+ }
2073
+ if (!Array.isArray(content)) {
2074
+ return "";
2075
+ }
2076
+ const parts = [];
2077
+ for (const p of content) {
2078
+ if (typeof p === "string") {
2079
+ parts.push(p);
2080
+ continue;
2081
+ }
2082
+ if (p && typeof p === "object" && "type" in p && p.type === "text" && "text" in p) {
2083
+ parts.push(String(p.text));
2084
+ }
2085
+ }
2086
+ return parts.join("");
2087
+ }
2088
+ function openAIChatCompletionToAnthropicMessage(completion, requestModel) {
2089
+ const choice = completion.choices[0];
2090
+ const message = choice?.message;
2091
+ const contentText = message ? extractTextFromAssistantContent(message.content) : "";
2092
+ const content = [];
2093
+ if (contentText.length > 0) {
2094
+ content.push({ type: "text", text: contentText });
2095
+ }
2096
+ if (message?.tool_calls?.length) {
2097
+ for (const tc of message.tool_calls) {
2098
+ if (tc.type !== "function") {
2099
+ continue;
2100
+ }
2101
+ let input = {};
2102
+ try {
2103
+ input = JSON.parse(tc.function.arguments || "{}");
2104
+ } catch {
2105
+ input = { _raw_arguments: tc.function.arguments ?? "" };
2106
+ }
2107
+ content.push({
2108
+ type: "tool_use",
2109
+ id: tc.id,
2110
+ name: tc.function.name,
2111
+ input
2112
+ });
2113
+ }
2114
+ }
2115
+ if (content.length === 0) {
2116
+ content.push({ type: "text", text: "" });
2117
+ }
2118
+ const { stop_reason, stop_sequence } = openAiFinishReasonToAnthropic(choice?.finish_reason);
2119
+ const u = completion.usage;
2120
+ const usage = {
2121
+ input_tokens: u?.prompt_tokens ?? 0,
2122
+ output_tokens: u?.completion_tokens ?? 0
2123
+ };
2124
+ return {
2125
+ id: newAnthropicMessageId(),
2126
+ type: "message",
2127
+ role: "assistant",
2128
+ content,
2129
+ model: requestModel,
2130
+ stop_reason,
2131
+ stop_sequence,
2132
+ usage
2133
+ };
2134
+ }
2135
+ function chunkFinishToAnthropic(finish) {
2136
+ if (!finish) {
2137
+ return null;
2138
+ }
2139
+ return openAiFinishReasonToAnthropic(finish).stop_reason;
2140
+ }
2141
+ async function pipeOpenAIChunkStreamToAnthropicSse(res, stream, options) {
2142
+ const { anthropicModel, messageId } = options;
2143
+ let textBlockOpen = false;
2144
+ let inputTokens = 0;
2145
+ let outputTokens = 0;
2146
+ let stopReason = null;
2147
+ const toolStates = new Map;
2148
+ let nextAnthropicIndex = 0;
2149
+ let textBlockIndex = null;
2150
+ writeAnthropicSseEvent(res, "message_start", {
2151
+ type: "message_start",
2152
+ message: {
2153
+ id: messageId,
2154
+ type: "message",
2155
+ role: "assistant",
2156
+ content: [],
2157
+ model: anthropicModel,
2158
+ stop_reason: null,
2159
+ stop_sequence: null,
2160
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens }
2161
+ }
2162
+ });
2163
+ const ensureTextBlock = () => {
2164
+ if (textBlockOpen) {
2165
+ return;
2166
+ }
2167
+ textBlockIndex = nextAnthropicIndex++;
2168
+ textBlockOpen = true;
2169
+ writeAnthropicSseEvent(res, "content_block_start", {
2170
+ type: "content_block_start",
2171
+ index: textBlockIndex,
2172
+ content_block: { type: "text", text: "" }
2173
+ });
2174
+ };
2175
+ const closeTextBlockIfOpen = () => {
2176
+ if (!textBlockOpen || textBlockIndex === null) {
2177
+ return;
2178
+ }
2179
+ writeAnthropicSseEvent(res, "content_block_stop", {
2180
+ type: "content_block_stop",
2181
+ index: textBlockIndex
2182
+ });
2183
+ textBlockOpen = false;
2184
+ };
2185
+ const getOrCreateTool = (openAiIdx) => {
2186
+ let st = toolStates.get(openAiIdx);
2187
+ if (!st) {
2188
+ st = {
2189
+ anthropicIndex: nextAnthropicIndex++,
2190
+ id: "",
2191
+ name: "",
2192
+ lastArgs: "",
2193
+ argsEmittedLen: 0,
2194
+ started: false,
2195
+ stopped: false
2196
+ };
2197
+ toolStates.set(openAiIdx, st);
2198
+ }
2199
+ return st;
2200
+ };
2201
+ const flushToolArgs = (st) => {
2202
+ if (!st.started || st.lastArgs.length <= st.argsEmittedLen) {
2203
+ return;
2204
+ }
2205
+ const partial = st.lastArgs.slice(st.argsEmittedLen);
2206
+ st.argsEmittedLen = st.lastArgs.length;
2207
+ writeAnthropicSseEvent(res, "content_block_delta", {
2208
+ type: "content_block_delta",
2209
+ index: st.anthropicIndex,
2210
+ delta: {
2211
+ type: "input_json_delta",
2212
+ partial_json: partial
2213
+ }
2214
+ });
2215
+ };
2216
+ try {
2217
+ for await (const chunk of stream) {
2218
+ if (chunk.usage) {
2219
+ const u = chunk.usage;
2220
+ inputTokens = u.prompt_tokens ?? inputTokens;
2221
+ outputTokens = u.completion_tokens ?? outputTokens;
2222
+ }
2223
+ const choice = chunk.choices?.[0];
2224
+ if (!choice) {
2225
+ continue;
2226
+ }
2227
+ const delta = choice.delta;
2228
+ if (typeof delta?.content === "string" && delta.content.length > 0) {
2229
+ ensureTextBlock();
2230
+ if (textBlockIndex !== null) {
2231
+ writeAnthropicSseEvent(res, "content_block_delta", {
2232
+ type: "content_block_delta",
2233
+ index: textBlockIndex,
2234
+ delta: { type: "text_delta", text: delta.content }
2235
+ });
2236
+ }
2237
+ }
2238
+ if (delta?.tool_calls?.length) {
2239
+ closeTextBlockIfOpen();
2240
+ for (const tc of delta.tool_calls) {
2241
+ const idx = typeof tc.index === "number" && Number.isFinite(tc.index) ? tc.index : 0;
2242
+ const st = getOrCreateTool(idx);
2243
+ if (typeof tc.id === "string" && tc.id.length > 0) {
2244
+ st.id = tc.id;
2245
+ }
2246
+ const fn = tc.function;
2247
+ if (fn?.name && fn.name.length > 0) {
2248
+ st.name = fn.name;
2249
+ }
2250
+ if (typeof fn?.arguments === "string") {
2251
+ st.lastArgs += fn.arguments;
2252
+ }
2253
+ if (!st.started && st.id.length > 0 && st.name.length > 0) {
2254
+ writeAnthropicSseEvent(res, "content_block_start", {
2255
+ type: "content_block_start",
2256
+ index: st.anthropicIndex,
2257
+ content_block: {
2258
+ type: "tool_use",
2259
+ id: st.id,
2260
+ name: st.name
2261
+ }
2262
+ });
2263
+ st.started = true;
2264
+ }
2265
+ if (st.started) {
2266
+ flushToolArgs(st);
2267
+ }
2268
+ }
2269
+ }
2270
+ if (choice.finish_reason) {
2271
+ const mapped = chunkFinishToAnthropic(choice.finish_reason);
2272
+ if (mapped) {
2273
+ stopReason = mapped;
2274
+ }
2275
+ }
2276
+ }
2277
+ closeTextBlockIfOpen();
2278
+ const sortedTools = [...toolStates.values()].sort((a, b) => a.anthropicIndex - b.anthropicIndex);
2279
+ for (const st of sortedTools) {
2280
+ if (st.started && !st.stopped) {
2281
+ writeAnthropicSseEvent(res, "content_block_stop", {
2282
+ type: "content_block_stop",
2283
+ index: st.anthropicIndex
2284
+ });
2285
+ st.stopped = true;
2286
+ }
2287
+ }
2288
+ writeAnthropicSseEvent(res, "message_delta", {
2289
+ type: "message_delta",
2290
+ delta: { stop_reason: stopReason, stop_sequence: null },
2291
+ usage: {
2292
+ input_tokens: inputTokens,
2293
+ output_tokens: outputTokens
2294
+ }
2295
+ });
2296
+ writeAnthropicSseEvent(res, "message_stop", { type: "message_stop" });
2297
+ res.end();
2298
+ } catch (err) {
2299
+ const message = err instanceof Error ? err.message : "Stream error";
2300
+ writeAnthropicSseEvent(res, "error", {
2301
+ type: "error",
2302
+ error: { type: "api_error", message }
2303
+ });
2304
+ res.end();
2305
+ }
2306
+ }
2307
+
2308
+ // src/anthropic/messages-route.ts
2309
+ function registerAnthropicMessagesRoute(router, deps) {
2310
+ router.post("/v1/messages", async (req, res) => {
2311
+ const requestId = newAnthropicRequestId();
2312
+ res.setHeader("request-id", requestId);
2313
+ const versionResult = resolveAnthropicVersion(req);
2314
+ if (!versionResult.ok) {
2315
+ return sendAnthropicHttpError(res, 400, "invalid_request_error", versionResult.message, requestId);
2316
+ }
2317
+ const apiKey = extractAnthropicApiKey(req);
2318
+ if (!apiKey) {
2319
+ return sendAnthropicHttpError(res, 401, "authentication_error", "Missing x-api-key header (or Authorization with API key).", requestId);
2320
+ }
2321
+ try {
2322
+ const body = req.body;
2323
+ const openaiParams = anthropicMessagesCreateToOpenAI(body);
2324
+ const client = await deps.getOrCreateClient(apiKey);
2325
+ const completion = await client.chat.completions.create(openaiParams);
2326
+ if (body.stream) {
2327
+ res.status(200);
2328
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
2329
+ res.setHeader("Cache-Control", "no-cache");
2330
+ res.setHeader("Connection", "keep-alive");
2331
+ if (completion && typeof completion === "object" && Symbol.asyncIterator in completion) {
2332
+ const messageId = newAnthropicMessageId();
2333
+ await pipeOpenAIChunkStreamToAnthropicSse(res, completion, {
2334
+ anthropicModel: body.model,
2335
+ messageId
2336
+ });
2337
+ } else {
2338
+ sendAnthropicHttpError(res, 500, "api_error", "Expected streamed completion", requestId);
2339
+ }
2340
+ return;
2341
+ }
2342
+ const message = openAIChatCompletionToAnthropicMessage(completion, body.model);
2343
+ res.json(message);
2344
+ } catch (err) {
2345
+ if (err instanceof AnthropicRequestValidationError) {
2346
+ return sendAnthropicHttpError(res, err.status, err.anthropicType, err.message, requestId);
2347
+ }
2348
+ mapUnknownErrorToAnthropicResponse(err, res, requestId);
2349
+ }
2350
+ });
2351
+ }
2352
+
2353
+ // src/anthropic/models-route.ts
2354
+ function toAnthropicModel(model) {
2355
+ return {
2356
+ type: "model",
2357
+ id: model.model,
2358
+ display_name: model.name || model.model,
2359
+ created_at: model.created_at
2360
+ };
2361
+ }
2362
+ function filterEnabled(models) {
2363
+ return models.filter((m) => m.enabled !== 0);
2364
+ }
2365
+ function parseLimit(raw) {
2366
+ if (typeof raw !== "string" || raw.length === 0) {
2367
+ return 20;
2368
+ }
2369
+ const n = Number.parseInt(raw, 10);
2370
+ if (!Number.isFinite(n) || n <= 0) {
2371
+ return 20;
2372
+ }
2373
+ return Math.min(n, 1000);
2374
+ }
2375
+ function paginate(all, beforeId, afterId, limit) {
2376
+ let start = 0;
2377
+ let end = all.length;
2378
+ if (afterId) {
2379
+ const idx = all.findIndex((m) => m.id === afterId);
2380
+ if (idx >= 0) {
2381
+ start = idx + 1;
2382
+ }
2383
+ }
2384
+ if (beforeId) {
2385
+ const idx = all.findIndex((m) => m.id === beforeId);
2386
+ if (idx >= 0) {
2387
+ end = idx;
2388
+ }
2389
+ }
2390
+ const window = all.slice(start, end);
2391
+ const items = window.slice(0, limit);
2392
+ return { items, hasMore: window.length > items.length };
2393
+ }
2394
+ function registerAnthropicModelsRoute(router, deps) {
2395
+ router.get("/v1/models", async (req, res) => {
2396
+ const requestId = newAnthropicRequestId();
2397
+ res.setHeader("request-id", requestId);
2398
+ const versionResult = resolveAnthropicVersion(req);
2399
+ if (!versionResult.ok) {
2400
+ return sendAnthropicHttpError(res, 400, "invalid_request_error", versionResult.message, requestId);
2401
+ }
2402
+ const apiKey = extractAnthropicApiKey(req);
2403
+ if (!apiKey) {
2404
+ return sendAnthropicHttpError(res, 401, "authentication_error", "Missing x-api-key header (or Authorization with API key).", requestId);
2405
+ }
2406
+ try {
2407
+ const client = await deps.getOrCreateClient(apiKey);
2408
+ const type = typeof req.query.type === "string" ? req.query.type : undefined;
2409
+ const all = filterEnabled(await client.models.list({ type })).map(toAnthropicModel);
2410
+ const beforeId = typeof req.query.before_id === "string" ? req.query.before_id : undefined;
2411
+ const afterId = typeof req.query.after_id === "string" ? req.query.after_id : undefined;
2412
+ const limit = parseLimit(req.query.limit);
2413
+ const { items, hasMore } = paginate(all, beforeId, afterId, limit);
2414
+ res.json({
2415
+ data: items,
2416
+ first_id: items.length > 0 ? items[0].id : null,
2417
+ last_id: items.length > 0 ? items[items.length - 1].id : null,
2418
+ has_more: hasMore
2419
+ });
2420
+ } catch (err) {
2421
+ mapUnknownErrorToAnthropicResponse(err, res, requestId);
2422
+ }
2423
+ });
2424
+ router.get("/v1/models/:model_id", async (req, res) => {
2425
+ const requestId = newAnthropicRequestId();
2426
+ res.setHeader("request-id", requestId);
2427
+ const versionResult = resolveAnthropicVersion(req);
2428
+ if (!versionResult.ok) {
2429
+ return sendAnthropicHttpError(res, 400, "invalid_request_error", versionResult.message, requestId);
2430
+ }
2431
+ const apiKey = extractAnthropicApiKey(req);
2432
+ if (!apiKey) {
2433
+ return sendAnthropicHttpError(res, 401, "authentication_error", "Missing x-api-key header (or Authorization with API key).", requestId);
2434
+ }
2435
+ const modelId = req.params.model_id;
2436
+ if (!modelId) {
2437
+ return sendAnthropicHttpError(res, 400, "invalid_request_error", "Missing model id.", requestId);
2438
+ }
2439
+ try {
2440
+ const client = await deps.getOrCreateClient(apiKey);
2441
+ const found = filterEnabled(await client.models.list()).find((m) => m.model === modelId);
2442
+ if (!found) {
2443
+ return sendAnthropicHttpError(res, 404, "not_found_error", `Model "${modelId}" not found.`, requestId);
2444
+ }
2445
+ res.json(toAnthropicModel(found));
2446
+ } catch (err) {
2447
+ mapUnknownErrorToAnthropicResponse(err, res, requestId);
2448
+ }
2449
+ });
2450
+ }
2451
+
2452
+ // src/server/runtime.ts
2453
+ import multer from "multer";
2454
+ var DEFAULT_HOST = process.env.HOST ?? "127.0.0.1";
2455
+ var DEFAULT_PORT = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 8000;
2456
+ var CLIENT_CACHE_MAX = (() => {
2457
+ let cacheTTL = 256;
2458
+ const raw = process.env.CLIENT_CACHE_MAX;
2459
+ if (raw) {
2460
+ const _cacheTTL = Number.parseInt(raw, 10);
2461
+ if (Number.isSafeInteger(_cacheTTL) && _cacheTTL > 0)
2462
+ cacheTTL = _cacheTTL;
2463
+ }
2464
+ return cacheTTL;
2465
+ })();
2466
+ var serverProxyUrl = process.env.PROXY_URL;
2467
+ var serverEnclaveUrl = process.env.ENCLAVE_URL;
2468
+ var serverKek = process.env.CLIENT_KEK;
2469
+ var serverAttest = true;
2470
+ var clientCache = new Map;
2471
+ var storage = multer.memoryStorage();
2472
+ var audioUpload = multer({
2473
+ storage,
2474
+ limits: { fileSize: 25 * 1024 * 1024 }
2475
+ });
2476
+ function applyServerOptions(options) {
2477
+ const { proxyUrl, enclaveUrl, kek, attest: attest2 } = options;
2478
+ serverAttest = attest2 !== false;
2479
+ if (proxyUrl) {
2480
+ serverProxyUrl = proxyUrl;
2481
+ }
2482
+ if (enclaveUrl) {
2483
+ serverEnclaveUrl = enclaveUrl;
2484
+ }
2485
+ if (kek) {
2486
+ serverKek = kek;
2487
+ }
2488
+ }
2489
+ async function getOrCreateRvencClient(apiKey) {
2490
+ const existing = clientCache.get(apiKey);
2491
+ if (existing)
2492
+ return existing;
1453
2493
  const client = await core_default({
1454
2494
  apiKey,
1455
2495
  clientKEK: serverKek,
@@ -1462,242 +2502,467 @@ async function getOrCreateClient(apiKey) {
1462
2502
  }
1463
2503
  });
1464
2504
  clientCache.set(apiKey, client);
2505
+ if (clientCache.size > CLIENT_CACHE_MAX) {
2506
+ const oldest = clientCache.keys().next().value;
2507
+ if (oldest !== undefined)
2508
+ clientCache.delete(oldest);
2509
+ }
1465
2510
  return client;
1466
2511
  }
1467
- app.get("/", (_, res) => {
1468
- res.json({
1469
- message: "Rvenc OpenAI-compatible API Server",
1470
- version: "1.0.0",
1471
- endpoints: {
1472
- chat_completions: "POST /v1/chat/completions"
2512
+
2513
+ // src/openai/routes.ts
2514
+ function extractApiKey(req) {
2515
+ const authHeader = req.headers.authorization;
2516
+ if (!authHeader) {
2517
+ return null;
2518
+ }
2519
+ if (authHeader.startsWith("Bearer ")) {
2520
+ return authHeader.slice(7);
2521
+ }
2522
+ return authHeader;
2523
+ }
2524
+ function sendUnauthorized(res) {
2525
+ res.status(401).json({
2526
+ error: {
2527
+ message: 'Missing Authorization header. Expected format: "Bearer <api-key>" or "<api-key>"',
2528
+ type: "invalid_request_error",
2529
+ code: "invalid_api_key"
1473
2530
  }
1474
2531
  });
1475
- });
1476
- app.post("/v1/chat/completions", async (req, res) => {
1477
- try {
1478
- const apiKey = extractApiKey(req);
1479
- if (!apiKey) {
1480
- return res.status(401).json({
1481
- error: {
1482
- message: 'Missing Authorization header. Expected format: "Bearer <api-key>" or "<api-key>"',
1483
- type: "invalid_request_error",
1484
- code: "invalid_api_key"
1485
- }
1486
- });
2532
+ }
2533
+ function sendServerError(res, error) {
2534
+ const err = error;
2535
+ res.status(err.status ?? 500).json({
2536
+ error: {
2537
+ message: err.message ?? "Internal server error",
2538
+ type: err.type ?? "server_error",
2539
+ code: err.code
1487
2540
  }
1488
- const client = await getOrCreateClient(apiKey);
1489
- const params = req.body;
1490
- const completion = await client.chat.completions.create(params);
1491
- if (params.stream) {
1492
- res.setHeader("Content-Type", "text/event-stream");
1493
- res.setHeader("Cache-Control", "no-cache");
1494
- res.setHeader("Connection", "keep-alive");
1495
- if (completion && typeof completion === "object" && Symbol.asyncIterator in completion) {
1496
- try {
1497
- for await (const chunk of completion) {
1498
- const data = `data: ${JSON.stringify(chunk)}
2541
+ });
2542
+ }
2543
+ function openAIOwnedBy(modelId) {
2544
+ const slash = modelId.indexOf("/");
2545
+ if (slash > 0) {
2546
+ return modelId.slice(0, slash);
2547
+ }
2548
+ return "prem";
2549
+ }
2550
+ function isoToUnix(iso) {
2551
+ const t = Date.parse(iso);
2552
+ if (!Number.isFinite(t)) {
2553
+ return 0;
2554
+ }
2555
+ return Math.floor(t / 1000);
2556
+ }
2557
+ function registerOpenAICompatRoutes(router, deps) {
2558
+ router.get("/v1/models", async (req, res) => {
2559
+ try {
2560
+ const apiKey = extractApiKey(req);
2561
+ if (!apiKey) {
2562
+ return sendUnauthorized(res);
2563
+ }
2564
+ const client = await deps.getOrCreateClient(apiKey);
2565
+ const all = await client.models.list();
2566
+ const data = all.filter((m) => m.enabled !== 0).map((m) => ({
2567
+ id: m.model,
2568
+ object: "model",
2569
+ created: isoToUnix(m.created_at),
2570
+ owned_by: openAIOwnedBy(m.model)
2571
+ }));
2572
+ res.json({ object: "list", data });
2573
+ } catch (error) {
2574
+ sendServerError(res, error);
2575
+ }
2576
+ });
2577
+ router.post("/v1/chat/completions", async (req, res) => {
2578
+ try {
2579
+ const apiKey = extractApiKey(req);
2580
+ if (!apiKey) {
2581
+ return sendUnauthorized(res);
2582
+ }
2583
+ const client = await deps.getOrCreateClient(apiKey);
2584
+ const params = req.body;
2585
+ const completion = await client.chat.completions.create(params);
2586
+ if (params.stream) {
2587
+ res.setHeader("Content-Type", "text/event-stream");
2588
+ res.setHeader("Cache-Control", "no-cache");
2589
+ res.setHeader("Connection", "keep-alive");
2590
+ if (completion && typeof completion === "object" && Symbol.asyncIterator in completion) {
2591
+ try {
2592
+ for await (const chunk of completion) {
2593
+ res.write(`data: ${JSON.stringify(chunk)}
1499
2594
 
1500
- `;
1501
- res.write(data);
1502
- }
1503
- res.write(`data: [DONE]
2595
+ `);
2596
+ }
2597
+ res.write(`data: [DONE]
1504
2598
 
1505
2599
  `);
1506
- res.end();
1507
- } catch (error) {
1508
- console.error("Streaming error:", error);
1509
- if (!res.headersSent) {
1510
- res.status(500).json({
1511
- error: {
1512
- message: error.message || "Streaming error",
1513
- type: "server_error"
1514
- }
1515
- });
1516
- } else {
1517
2600
  res.end();
2601
+ } catch (streamErr) {
2602
+ if (!res.headersSent) {
2603
+ sendServerError(res, streamErr);
2604
+ } else {
2605
+ res.end();
2606
+ }
1518
2607
  }
1519
- }
1520
- } else {
1521
- res.write(`data: ${JSON.stringify(completion)}
2608
+ } else {
2609
+ res.write(`data: ${JSON.stringify(completion)}
1522
2610
 
1523
2611
  `);
1524
- res.write(`data: [DONE]
2612
+ res.write(`data: [DONE]
1525
2613
 
1526
2614
  `);
1527
- res.end();
2615
+ res.end();
2616
+ }
2617
+ } else {
2618
+ res.json(completion);
1528
2619
  }
1529
- } else {
1530
- res.json(completion);
2620
+ } catch (error) {
2621
+ sendServerError(res, error);
1531
2622
  }
1532
- } catch (error) {
1533
- console.error("Chat completions error:", error);
1534
- const statusCode = error.status || 500;
1535
- res.status(statusCode).json({
1536
- error: {
1537
- message: error.message || "Internal server error",
1538
- type: error.type || "server_error",
1539
- code: error.code
2623
+ });
2624
+ router.post("/v1/audio/transcriptions", audioUpload.single("file"), async (req, res) => {
2625
+ try {
2626
+ const apiKey = extractApiKey(req);
2627
+ if (!apiKey) {
2628
+ return sendUnauthorized(res);
1540
2629
  }
1541
- });
1542
- }
1543
- });
1544
- app.post("/v1/audio/transcriptions", upload.single("file"), async (req, res) => {
1545
- try {
1546
- const apiKey = extractApiKey(req);
1547
- if (!apiKey) {
1548
- return res.status(401).json({
1549
- error: {
1550
- message: 'Missing Authorization header. Expected format: "Bearer <api-key>" or "<api-key>"',
1551
- type: "invalid_request_error",
1552
- code: "invalid_api_key"
1553
- }
2630
+ if (!req.file) {
2631
+ return res.status(400).json({
2632
+ error: {
2633
+ message: "Missing required file parameter",
2634
+ type: "invalid_request_error"
2635
+ }
2636
+ });
2637
+ }
2638
+ const client = await deps.getOrCreateClient(apiKey);
2639
+ const file = new File([req.file.buffer], req.file.originalname, {
2640
+ type: req.file.mimetype
1554
2641
  });
2642
+ const params = {
2643
+ file,
2644
+ model: req.body.model
2645
+ };
2646
+ if (req.body.language) {
2647
+ params.language = req.body.language;
2648
+ }
2649
+ if (req.body.prompt) {
2650
+ params.prompt = req.body.prompt;
2651
+ }
2652
+ if (req.body.response_format) {
2653
+ params.response_format = req.body.response_format;
2654
+ }
2655
+ if (req.body.temperature) {
2656
+ params.temperature = parseFloat(req.body.temperature);
2657
+ }
2658
+ if (req.body.timestamp_granularities) {
2659
+ params.timestamp_granularities = Array.isArray(req.body.timestamp_granularities) ? req.body.timestamp_granularities : JSON.parse(req.body.timestamp_granularities);
2660
+ }
2661
+ const transcription = await client.audio.transcriptions.create(params);
2662
+ res.json(transcription);
2663
+ } catch (error) {
2664
+ sendServerError(res, error);
1555
2665
  }
1556
- if (!req.file) {
1557
- return res.status(400).json({
1558
- error: {
1559
- message: "Missing required file parameter",
1560
- type: "invalid_request_error"
1561
- }
2666
+ });
2667
+ router.post("/v1/audio/translations", audioUpload.single("file"), async (req, res) => {
2668
+ try {
2669
+ const apiKey = extractApiKey(req);
2670
+ if (!apiKey) {
2671
+ return sendUnauthorized(res);
2672
+ }
2673
+ if (!req.file) {
2674
+ return res.status(400).json({
2675
+ error: {
2676
+ message: "Missing required file parameter",
2677
+ type: "invalid_request_error"
2678
+ }
2679
+ });
2680
+ }
2681
+ const client = await deps.getOrCreateClient(apiKey);
2682
+ const file = new File([req.file.buffer], req.file.originalname, {
2683
+ type: req.file.mimetype
1562
2684
  });
2685
+ const params = {
2686
+ file,
2687
+ model: req.body.model
2688
+ };
2689
+ if (req.body.prompt) {
2690
+ params.prompt = req.body.prompt;
2691
+ }
2692
+ if (req.body.response_format) {
2693
+ params.response_format = req.body.response_format;
2694
+ }
2695
+ if (req.body.temperature) {
2696
+ params.temperature = parseFloat(req.body.temperature);
2697
+ }
2698
+ const translation = await client.audio.translations.create(params);
2699
+ res.json(translation);
2700
+ } catch (error) {
2701
+ sendServerError(res, error);
1563
2702
  }
1564
- const client = await getOrCreateClient(apiKey);
1565
- const file = new File([req.file.buffer], req.file.originalname, {
1566
- type: req.file.mimetype
2703
+ });
2704
+ }
2705
+
2706
+ // src/server/route-prefix.ts
2707
+ function normalizeRoutePrefix(raw) {
2708
+ if (raw == null) {
2709
+ return "";
2710
+ }
2711
+ return new URL(String(raw).trim(), "http://localhost").pathname || "";
2712
+ }
2713
+ var DEFAULT_OPENAI_ROUTE_PREFIX_BOTH = "/openai";
2714
+ var DEFAULT_ANTHROPIC_ROUTE_PREFIX_BOTH = "/anthropic";
2715
+ function resolvePrefixesForCompat(compat, openaiRaw, anthropicRaw) {
2716
+ const oNorm = normalizeRoutePrefix(openaiRaw);
2717
+ const aNorm = normalizeRoutePrefix(anthropicRaw);
2718
+ if (compat === "both") {
2719
+ const openaiPrefix = oNorm || DEFAULT_OPENAI_ROUTE_PREFIX_BOTH;
2720
+ const anthropicPrefix = aNorm || DEFAULT_ANTHROPIC_ROUTE_PREFIX_BOTH;
2721
+ if (openaiPrefix === anthropicPrefix) {
2722
+ throw new Error(`When compat is "both", openaiRoutePrefix and anthropicRoutePrefix must differ (both resolved to "${openaiPrefix}").`);
2723
+ }
2724
+ return { openaiPrefix, anthropicPrefix };
2725
+ }
2726
+ if (compat === "openai") {
2727
+ return { openaiPrefix: oNorm, anthropicPrefix: "" };
2728
+ }
2729
+ return { openaiPrefix: "", anthropicPrefix: aNorm };
2730
+ }
2731
+ function prefixedRoute(prefix, path) {
2732
+ const s = path.startsWith("/") ? path : `/${path}`;
2733
+ if (!prefix) {
2734
+ return s;
2735
+ }
2736
+ return `${prefix}${s}`;
2737
+ }
2738
+
2739
+ // src/server/discovery.ts
2740
+ function registerApiDiscoveryRoute(app, mount) {
2741
+ const {
2742
+ openai: mountOpenAI,
2743
+ anthropic: mountAnthropic,
2744
+ openaiPrefix,
2745
+ anthropicPrefix
2746
+ } = mount;
2747
+ app.get("/", (_, res) => {
2748
+ const endpoints2 = {};
2749
+ if (mountOpenAI) {
2750
+ endpoints2.chat_completions = `POST ${prefixedRoute(openaiPrefix, "/v1/chat/completions")}`;
2751
+ endpoints2.audio_transcriptions = `POST ${prefixedRoute(openaiPrefix, "/v1/audio/transcriptions")}`;
2752
+ endpoints2.audio_translations = `POST ${prefixedRoute(openaiPrefix, "/v1/audio/translations")}`;
2753
+ endpoints2.models = `GET ${prefixedRoute(openaiPrefix, "/v1/models")}`;
2754
+ }
2755
+ if (mountAnthropic) {
2756
+ endpoints2.messages = `POST ${prefixedRoute(anthropicPrefix, "/v1/messages")}`;
2757
+ endpoints2.messages_count_tokens = `POST ${prefixedRoute(anthropicPrefix, "/v1/messages/count_tokens")}`;
2758
+ endpoints2.anthropic_models = `GET ${prefixedRoute(anthropicPrefix, "/v1/models")}`;
2759
+ endpoints2.anthropic_model_get = `GET ${prefixedRoute(anthropicPrefix, "/v1/models/{model_id}")}`;
2760
+ }
2761
+ const labels = [];
2762
+ if (mountOpenAI) {
2763
+ labels.push("OpenAI-compatible");
2764
+ }
2765
+ if (mountAnthropic) {
2766
+ labels.push("Anthropic Messages-compatible");
2767
+ }
2768
+ res.json({
2769
+ message: `Rvenc API Server (${labels.join(" + ")})`,
2770
+ version: "1.0.0",
2771
+ compat: resolveCompatLabel(mount),
2772
+ route_prefixes: buildRoutePrefixesPayload(mountOpenAI, mountAnthropic, openaiPrefix, anthropicPrefix),
2773
+ endpoints: endpoints2
1567
2774
  });
1568
- const params = {
1569
- file,
1570
- model: req.body.model
2775
+ });
2776
+ }
2777
+ function buildRoutePrefixesPayload(mountOpenAI, mountAnthropic, openaiPrefix, anthropicPrefix) {
2778
+ const out = {};
2779
+ if (mountOpenAI) {
2780
+ out.openai = openaiPrefix || "/";
2781
+ }
2782
+ if (mountAnthropic) {
2783
+ out.anthropic = anthropicPrefix || "/";
2784
+ }
2785
+ if (Object.keys(out).length === 0) {
2786
+ return;
2787
+ }
2788
+ return out;
2789
+ }
2790
+ function resolveCompatLabel(mount) {
2791
+ if (mount.openai && mount.anthropic) {
2792
+ return "both";
2793
+ }
2794
+ if (mount.anthropic) {
2795
+ return "anthropic";
2796
+ }
2797
+ return "openai";
2798
+ }
2799
+
2800
+ // src/server/create-app.ts
2801
+ var rvencDeps = {
2802
+ getOrCreateClient: getOrCreateRvencClient
2803
+ };
2804
+ function resolveJsonBodyLimit(override) {
2805
+ if (override != null && String(override).trim() !== "") {
2806
+ return String(override).trim();
2807
+ }
2808
+ const env = process.env.JSON_BODY_LIMIT;
2809
+ if (env != null && env !== "") {
2810
+ return env;
2811
+ }
2812
+ return "32mb";
2813
+ }
2814
+ function resolveCreateServerInput(compatOrOptions) {
2815
+ if (typeof compatOrOptions === "string") {
2816
+ const compat2 = compatOrOptions;
2817
+ const { openaiPrefix: openaiPrefix2, anthropicPrefix: anthropicPrefix2 } = resolvePrefixesForCompat(compat2, undefined, undefined);
2818
+ return {
2819
+ compat: compat2,
2820
+ openaiPrefix: openaiPrefix2,
2821
+ anthropicPrefix: anthropicPrefix2,
2822
+ jsonBodyLimit: resolveJsonBodyLimit()
1571
2823
  };
1572
- if (req.body.language)
1573
- params.language = req.body.language;
1574
- if (req.body.prompt)
1575
- params.prompt = req.body.prompt;
1576
- if (req.body.response_format)
1577
- params.response_format = req.body.response_format;
1578
- if (req.body.temperature)
1579
- params.temperature = parseFloat(req.body.temperature);
1580
- if (req.body.timestamp_granularities) {
1581
- params.timestamp_granularities = Array.isArray(req.body.timestamp_granularities) ? req.body.timestamp_granularities : JSON.parse(req.body.timestamp_granularities);
1582
- }
1583
- const transcription = await client.audio.transcriptions.create(params);
1584
- res.json(transcription);
1585
- } catch (error) {
1586
- console.error("Audio transcription error:", error);
1587
- const statusCode = error.status || 500;
1588
- res.status(statusCode).json({
1589
- error: {
1590
- message: error.message || "Internal server error",
1591
- type: error.type || "server_error",
1592
- code: error.code
1593
- }
1594
- });
1595
2824
  }
1596
- });
1597
- app.post("/v1/audio/translations", upload.single("file"), async (req, res) => {
1598
- try {
1599
- const apiKey = extractApiKey(req);
1600
- if (!apiKey) {
1601
- return res.status(401).json({
1602
- error: {
1603
- message: 'Missing Authorization header. Expected format: "Bearer <api-key>" or "<api-key>"',
1604
- type: "invalid_request_error",
1605
- code: "invalid_api_key"
1606
- }
1607
- });
2825
+ const compat = compatOrOptions.compat ?? "openai";
2826
+ const { openaiPrefix, anthropicPrefix } = resolvePrefixesForCompat(compat, compatOrOptions.openaiRoutePrefix, compatOrOptions.anthropicRoutePrefix);
2827
+ return {
2828
+ compat,
2829
+ openaiPrefix,
2830
+ anthropicPrefix,
2831
+ jsonBodyLimit: resolveJsonBodyLimit(compatOrOptions.jsonBodyLimit)
2832
+ };
2833
+ }
2834
+ function httpErrorStatus(err) {
2835
+ if (err && typeof err === "object") {
2836
+ const o = err;
2837
+ const s = o.status ?? o.statusCode;
2838
+ if (typeof s === "number" && s >= 400 && s < 600) {
2839
+ return s;
1608
2840
  }
1609
- if (!req.file) {
1610
- return res.status(400).json({
2841
+ }
2842
+ return 500;
2843
+ }
2844
+ function mountRouter(app, prefix, router) {
2845
+ app.use(prefix || "/", router);
2846
+ }
2847
+ function createServerApp(compatOrOptions = "openai") {
2848
+ const { compat, openaiPrefix, anthropicPrefix, jsonBodyLimit } = resolveCreateServerInput(compatOrOptions);
2849
+ const mountOpenAI = compat === "openai" || compat === "both";
2850
+ const mountAnthropic = compat === "anthropic" || compat === "both";
2851
+ const app = express();
2852
+ app.use(express.json({ limit: jsonBodyLimit }));
2853
+ registerApiDiscoveryRoute(app, {
2854
+ openai: mountOpenAI,
2855
+ anthropic: mountAnthropic,
2856
+ openaiPrefix,
2857
+ anthropicPrefix
2858
+ });
2859
+ if (mountOpenAI) {
2860
+ const router = express.Router();
2861
+ registerOpenAICompatRoutes(router, rvencDeps);
2862
+ mountRouter(app, openaiPrefix, router);
2863
+ }
2864
+ if (mountAnthropic) {
2865
+ const router = express.Router();
2866
+ registerAnthropicMessagesRoute(router, rvencDeps);
2867
+ registerAnthropicCountTokensRoute(router, rvencDeps);
2868
+ registerAnthropicModelsRoute(router, rvencDeps);
2869
+ mountRouter(app, anthropicPrefix, router);
2870
+ }
2871
+ const isAnthropicRequest = (req) => {
2872
+ if (!mountAnthropic) {
2873
+ return false;
2874
+ }
2875
+ if (!mountOpenAI) {
2876
+ return true;
2877
+ }
2878
+ return req.path === anthropicPrefix || req.path.startsWith(`${anthropicPrefix}/`);
2879
+ };
2880
+ app.use((err, req, res, _next) => {
2881
+ const status = httpErrorStatus(err);
2882
+ const message = err instanceof Error ? err.message : "Internal server error";
2883
+ if (isAnthropicRequest(req)) {
2884
+ const requestId = newAnthropicRequestId();
2885
+ res.setHeader("request-id", requestId);
2886
+ res.status(status).json({
2887
+ type: "error",
1611
2888
  error: {
1612
- message: "Missing required file parameter",
1613
- type: "invalid_request_error"
1614
- }
2889
+ type: httpStatusToAnthropicErrorType(status),
2890
+ message
2891
+ },
2892
+ request_id: requestId
1615
2893
  });
2894
+ return;
1616
2895
  }
1617
- const client = await getOrCreateClient(apiKey);
1618
- const file = new File([req.file.buffer], req.file.originalname, {
1619
- type: req.file.mimetype
1620
- });
1621
- const params = {
1622
- file,
1623
- model: req.body.model
1624
- };
1625
- if (req.body.prompt)
1626
- params.prompt = req.body.prompt;
1627
- if (req.body.response_format)
1628
- params.response_format = req.body.response_format;
1629
- if (req.body.temperature)
1630
- params.temperature = parseFloat(req.body.temperature);
1631
- const translation = await client.audio.translations.create(params);
1632
- res.json(translation);
1633
- } catch (error) {
1634
- console.error("Audio translation error:", error);
1635
- const statusCode = error.status || 500;
1636
- res.status(statusCode).json({
2896
+ res.status(status).json({
1637
2897
  error: {
1638
- message: error.message || "Internal server error",
1639
- type: error.type || "server_error",
1640
- code: error.code
2898
+ message,
2899
+ type: "server_error"
1641
2900
  }
1642
2901
  });
1643
- }
1644
- });
1645
- app.use((err, _req, res, _next) => {
1646
- console.error(`Unhandled error: ${err}`);
1647
- res.status(500).json({
1648
- error: {
1649
- message: err.message || "Internal server error",
1650
- type: "server_error"
1651
- }
1652
2902
  });
1653
- });
1654
- app.use((req, res) => {
1655
- res.status(404).json({
1656
- error: {
1657
- message: `Route ${req.method} ${req.path} not found`,
1658
- type: "invalid_request_error"
2903
+ app.use((req, res) => {
2904
+ const message = `Route ${req.method} ${req.path} not found`;
2905
+ if (isAnthropicRequest(req)) {
2906
+ const requestId = newAnthropicRequestId();
2907
+ res.setHeader("request-id", requestId);
2908
+ res.status(404).json({
2909
+ type: "error",
2910
+ error: { type: "not_found_error", message },
2911
+ request_id: requestId
2912
+ });
2913
+ return;
1659
2914
  }
2915
+ res.status(404).json({
2916
+ error: {
2917
+ message,
2918
+ type: "invalid_request_error"
2919
+ }
2920
+ });
1660
2921
  });
1661
- });
2922
+ return app;
2923
+ }
2924
+ // src/server/start.ts
1662
2925
  async function startServer(options = {}) {
1663
- const { host, port, proxyUrl, enclaveUrl, kek, attest: attest2 } = options;
1664
- const serverHost = host || HOST;
1665
- const serverPort = port || PORT;
1666
- serverAttest = attest2 !== false;
1667
- if (proxyUrl) {
1668
- serverProxyUrl = proxyUrl;
1669
- }
1670
- if (enclaveUrl) {
1671
- serverEnclaveUrl = enclaveUrl;
1672
- }
1673
- if (kek) {
1674
- serverKek = kek;
1675
- }
2926
+ const {
2927
+ host,
2928
+ port,
2929
+ compat: compatOpt,
2930
+ openaiRoutePrefix,
2931
+ anthropicRoutePrefix,
2932
+ jsonBodyLimit
2933
+ } = options;
2934
+ const serverHost = host || DEFAULT_HOST;
2935
+ const serverPort = port || DEFAULT_PORT;
2936
+ const compat = compatOpt ?? "openai";
2937
+ applyServerOptions(options);
2938
+ resolvePrefixesForCompat(compat, openaiRoutePrefix, anthropicRoutePrefix);
2939
+ const app = createServerApp({
2940
+ compat,
2941
+ openaiRoutePrefix,
2942
+ anthropicRoutePrefix,
2943
+ jsonBodyLimit
2944
+ });
1676
2945
  return new Promise((resolve, reject) => {
1677
2946
  const server = app.listen(serverPort, serverHost, () => {
1678
- console.log(`
1679
- Rvenc Server running on http://${serverHost}:${serverPort}`);
1680
- resolve();
2947
+ resolve({ close: () => server.close() });
1681
2948
  });
1682
2949
  server.on("error", (error) => {
1683
- if (error.code === "EADDRINUSE") {
1684
- console.error(`❌ Port ${serverPort} is already in use`);
2950
+ if (error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE") {
2951
+ reject(new Error(`Port ${serverPort} is already in use`));
1685
2952
  } else {
1686
- console.error(`❌ Failed to start server: ${error}`);
2953
+ reject(error);
1687
2954
  }
1688
- reject(error);
1689
2955
  });
1690
2956
  });
1691
2957
  }
1692
- if (false) {}
1693
- var server_default = app;
1694
-
1695
- // src/index.ts
1696
- dotenv2.config();
2958
+ // src/server.ts
2959
+ var server_default = createServerApp("both");
1697
2960
  export {
1698
2961
  startServer,
1699
2962
  server_default as serverApp,
1700
2963
  serializeDEKStore,
2964
+ resolvePrefixesForCompat,
2965
+ normalizeRoutePrefix,
1701
2966
  loadPrem,
1702
2967
  isGatewayError,
1703
2968
  isAttestationError,
@@ -1709,5 +2974,8 @@ export {
1709
2974
  generateEncryptionKeys,
1710
2975
  deserializeDEKStore,
1711
2976
  core_default as default,
1712
- createRvencClient
2977
+ createServerApp,
2978
+ createRvencClient,
2979
+ DEFAULT_OPENAI_ROUTE_PREFIX_BOTH,
2980
+ DEFAULT_ANTHROPIC_ROUTE_PREFIX_BOTH
1713
2981
  };