@agentproto/llm-endpoint 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/cli.mjs +60 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +78 -1
- package/dist/index.mjs +206 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -150,11 +150,12 @@ configured from a JSON file instead of a pair of env vars per server:
|
|
|
150
150
|
"defaultRequestFields": { "chat_template_kwargs": { "enable_thinking": false } },
|
|
151
151
|
"timeoutMs": { "firstTokenMs": 180000 }
|
|
152
152
|
},
|
|
153
|
-
{ "id": "ollama", "kind": "openai", "baseUrl": "http://192.168.1.20:11434/v1" },
|
|
153
|
+
{ "id": "ollama", "kind": "openai", "baseUrl": "http://192.168.1.20:11434/v1", "connector": "ollama" },
|
|
154
154
|
{
|
|
155
155
|
"id": "lmstudio",
|
|
156
156
|
"kind": "openai",
|
|
157
157
|
"baseUrl": "http://127.0.0.1:1234/v1",
|
|
158
|
+
"connector": "lmstudio",
|
|
158
159
|
"defaultRequestFields": { "reasoning_effort": "none" }
|
|
159
160
|
}
|
|
160
161
|
]
|
|
@@ -172,6 +173,13 @@ a 500 for the whole listing). `forge` itself keeps working unchanged — it's
|
|
|
172
173
|
the implicit endpoint that `FORGE_BASE_URL`/`FORGE_API_KEY` configure; a file
|
|
173
174
|
entry may not reuse the id `"forge"`.
|
|
174
175
|
|
|
176
|
+
- **`connector`** names which local/LAN runtime `baseUrl` points at —
|
|
177
|
+
`lmstudio`, `ollama`, `vllm`, `llama-server`, or the generic
|
|
178
|
+
`openai-compatible` fallback (see `src/connectors.ts`). Optional: it never
|
|
179
|
+
affects request routing, only which connector's `listModels`/request quirks
|
|
180
|
+
`agentproto llm endpoints test`/`detect`/`doctor` use to show loaded models
|
|
181
|
+
and context size. An entry with no `connector` is treated as
|
|
182
|
+
`openai-compatible`.
|
|
175
183
|
- **`apiKeyEnv`** names an env var (never the key itself). Absent, or the env
|
|
176
184
|
var unset, means the endpoint is always keyless — no `Authorization` header
|
|
177
185
|
sent, same as `forge` with no `FORGE_API_KEY`.
|
package/dist/cli.mjs
CHANGED
|
@@ -575,28 +575,28 @@ function flattenContent(content) {
|
|
|
575
575
|
return content.map((c) => c.text).join("");
|
|
576
576
|
}
|
|
577
577
|
function translateInputToMessages(input, instructions) {
|
|
578
|
-
const
|
|
579
|
-
if (instructions)
|
|
580
|
-
|
|
581
|
-
}
|
|
578
|
+
const sysParts = [];
|
|
579
|
+
if (instructions) sysParts.push(instructions);
|
|
580
|
+
const rest = [];
|
|
582
581
|
if (typeof input === "string") {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
role: "tool",
|
|
595
|
-
|
|
596
|
-
content: item.output
|
|
597
|
-
});
|
|
582
|
+
rest.push({ role: "user", content: input });
|
|
583
|
+
} else {
|
|
584
|
+
for (const item of input) {
|
|
585
|
+
if (item.type === "message") {
|
|
586
|
+
if (item.role === "developer" || item.role === "system") {
|
|
587
|
+
const text = flattenContent(item.content);
|
|
588
|
+
if (text) sysParts.push(text);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
rest.push({ role: item.role, content: flattenContent(item.content) });
|
|
592
|
+
} else if (item.type === "function_call_output") {
|
|
593
|
+
rest.push({ role: "tool", tool_call_id: item.call_id, content: item.output });
|
|
594
|
+
}
|
|
598
595
|
}
|
|
599
596
|
}
|
|
597
|
+
const messages = [];
|
|
598
|
+
if (sysParts.length) messages.push({ role: "system", content: sysParts.join("\n\n") });
|
|
599
|
+
messages.push(...rest);
|
|
600
600
|
return messages;
|
|
601
601
|
}
|
|
602
602
|
function translateToolToChatCompletions(tool) {
|
|
@@ -1536,6 +1536,14 @@ async function resumeIncompleteLocalQueueBatches() {
|
|
|
1536
1536
|
}
|
|
1537
1537
|
}
|
|
1538
1538
|
}
|
|
1539
|
+
|
|
1540
|
+
// src/connectors.ts
|
|
1541
|
+
var CONNECTOR_IDS = ["lmstudio", "ollama", "vllm", "llama-server", "openai-compatible"];
|
|
1542
|
+
function isConnectorId(value) {
|
|
1543
|
+
return typeof value === "string" && CONNECTOR_IDS.includes(value);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/endpoints.ts
|
|
1539
1547
|
var FORBIDDEN_DEFAULT_REQUEST_FIELD_KEYS = /* @__PURE__ */ new Set(["model", "messages", "stream", "tools", "input"]);
|
|
1540
1548
|
var RESERVED_ENDPOINT_IDS = /* @__PURE__ */ new Set(["forge"]);
|
|
1541
1549
|
function validateEndpointConfig(raw, where, errors) {
|
|
@@ -1543,7 +1551,7 @@ function validateEndpointConfig(raw, where, errors) {
|
|
|
1543
1551
|
errors.push(`${where}: expected an object, got ${raw === null ? "null" : typeof raw}`);
|
|
1544
1552
|
return null;
|
|
1545
1553
|
}
|
|
1546
|
-
const { id, kind, baseUrl, apiKeyEnv, defaultRequestFields, timeoutMs } = raw;
|
|
1554
|
+
const { id, kind, baseUrl, connector, apiKeyEnv, defaultRequestFields, timeoutMs } = raw;
|
|
1547
1555
|
let ok = true;
|
|
1548
1556
|
if (typeof id !== "string" || id.length === 0) {
|
|
1549
1557
|
errors.push(`${where}.id: required non-empty string`);
|
|
@@ -1571,6 +1579,10 @@ function validateEndpointConfig(raw, where, errors) {
|
|
|
1571
1579
|
ok = false;
|
|
1572
1580
|
}
|
|
1573
1581
|
}
|
|
1582
|
+
if (connector !== void 0 && !isConnectorId(connector)) {
|
|
1583
|
+
errors.push(`${where}.connector: must be one of ${CONNECTOR_IDS.join(", ")} when present (got ${JSON.stringify(connector)})`);
|
|
1584
|
+
ok = false;
|
|
1585
|
+
}
|
|
1574
1586
|
if (apiKeyEnv !== void 0 && (typeof apiKeyEnv !== "string" || apiKeyEnv.length === 0)) {
|
|
1575
1587
|
errors.push(`${where}.apiKeyEnv: must be a non-empty string when present`);
|
|
1576
1588
|
ok = false;
|
|
@@ -1607,6 +1619,7 @@ function validateEndpointConfig(raw, where, errors) {
|
|
|
1607
1619
|
}
|
|
1608
1620
|
if (!ok || typeof id !== "string" || typeof baseUrl !== "string" || !validUrl) return null;
|
|
1609
1621
|
const built = { id, kind: "openai", baseUrl };
|
|
1622
|
+
if (isConnectorId(connector)) built.connector = connector;
|
|
1610
1623
|
if (typeof apiKeyEnv === "string") built.apiKeyEnv = apiKeyEnv;
|
|
1611
1624
|
if (builtFields) built.defaultRequestFields = builtFields;
|
|
1612
1625
|
if (builtTimeout) built.timeoutMs = builtTimeout;
|
|
@@ -2558,21 +2571,28 @@ function handleChatCompletionsRequest(req, res, opts) {
|
|
|
2558
2571
|
});
|
|
2559
2572
|
}
|
|
2560
2573
|
function adaptAnthropicToOpenAI(payload) {
|
|
2574
|
+
const sysParts = [];
|
|
2561
2575
|
if (payload.system != null) {
|
|
2562
|
-
let sysText = "";
|
|
2563
2576
|
if (typeof payload.system === "string") {
|
|
2564
|
-
|
|
2577
|
+
if (payload.system) sysParts.push(payload.system);
|
|
2565
2578
|
} else if (Array.isArray(payload.system)) {
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
if (sysText) {
|
|
2569
|
-
if (!Array.isArray(payload.messages)) payload.messages = [];
|
|
2570
|
-
if (!payload.messages[0] || payload.messages[0].role !== "system") {
|
|
2571
|
-
payload.messages.unshift({ role: "system", content: sysText });
|
|
2572
|
-
}
|
|
2579
|
+
const text = payload.system.map((b) => typeof b === "string" ? b : b?.text ?? "").filter(Boolean).join("\n\n");
|
|
2580
|
+
if (text) sysParts.push(text);
|
|
2573
2581
|
}
|
|
2574
2582
|
delete payload.system;
|
|
2575
2583
|
}
|
|
2584
|
+
if (Array.isArray(payload.messages)) {
|
|
2585
|
+
payload.messages = payload.messages.filter((m) => {
|
|
2586
|
+
if (m?.role !== "system" && m?.role !== "developer") return true;
|
|
2587
|
+
const text = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((b) => typeof b === "string" ? b : b?.text ?? "").filter(Boolean).join("\n\n") : "";
|
|
2588
|
+
if (text) sysParts.push(text);
|
|
2589
|
+
return false;
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
if (sysParts.length) {
|
|
2593
|
+
if (!Array.isArray(payload.messages)) payload.messages = [];
|
|
2594
|
+
payload.messages.unshift({ role: "system", content: sysParts.join("\n\n") });
|
|
2595
|
+
}
|
|
2576
2596
|
if (payload.tool_choice && typeof payload.tool_choice === "object") {
|
|
2577
2597
|
const tc = payload.tool_choice;
|
|
2578
2598
|
if (tc.type === "any") {
|
|
@@ -3583,6 +3603,17 @@ var server = createServer((req, res) => {
|
|
|
3583
3603
|
);
|
|
3584
3604
|
}
|
|
3585
3605
|
});
|
|
3606
|
+
if (status < 200 || status >= 300) {
|
|
3607
|
+
let errBody = "";
|
|
3608
|
+
proxyRes.on("data", (c) => {
|
|
3609
|
+
if (errBody.length < 300) errBody += c.toString("utf8");
|
|
3610
|
+
});
|
|
3611
|
+
proxyRes.on("end", () => {
|
|
3612
|
+
console.error(
|
|
3613
|
+
`[Proxy] upstream error: ${resolvedTarget.provider}:${resolvedTarget.model} status=${status} body=${JSON.stringify(errBody.slice(0, 300))}`
|
|
3614
|
+
);
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3586
3617
|
const contentType = proxyRes.headers["content-type"] || "";
|
|
3587
3618
|
const isStreaming = payload.stream === true && /text\/event-stream/i.test(contentType);
|
|
3588
3619
|
const retryEmptyTurn = () => {
|