@leoustc/service-llm 0.2.2 → 0.2.5
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 +18 -1
- package/VERSION +1 -1
- package/dist/cli.js +5 -1
- package/dist/http.js +35 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,14 @@ Use your ChatGPT Codex subscription through OpenAI-compatible HTTP APIs or MCP.
|
|
|
15
15
|
npm install --global @leoustc/service-llm
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
Show the installed CLI version:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
service-llm -v
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use `service-llm --help` to show all available commands and server options.
|
|
25
|
+
|
|
18
26
|
## Log in
|
|
19
27
|
|
|
20
28
|
```sh
|
|
@@ -144,9 +152,10 @@ Connect an MCP client to:
|
|
|
144
152
|
http://127.0.0.1:7060/mcp
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
The server provides
|
|
155
|
+
The server provides two tools:
|
|
148
156
|
|
|
149
157
|
- `ask`: accepts `prompt` and optional `model`, `tools`, and `tool_choice` arguments.
|
|
158
|
+
- `list_models`: lists available models and reports the currently configured default model and reasoning level.
|
|
150
159
|
|
|
151
160
|
Example JSON-RPC request:
|
|
152
161
|
|
|
@@ -168,6 +177,14 @@ curl http://127.0.0.1:7060/mcp \
|
|
|
168
177
|
|
|
169
178
|
Specify a model with the request's `model` field. If it is omitted or unavailable, the service uses your configured default. On first login, the lowest-cost available model is selected with low reasoning effort. Responses always report the model actually used.
|
|
170
179
|
|
|
180
|
+
List the models available to both Chat Completions and Responses clients:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
curl http://127.0.0.1:7060/v1/models
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
When client authentication is enabled, include the same Bearer or `X-API-Key` header used for generation requests. The response includes `default_model` and `default_reasoning_effort` in addition to the OpenAI-compatible model list.
|
|
187
|
+
|
|
171
188
|
## Debug logging
|
|
172
189
|
|
|
173
190
|
Enable request, response, and SSE logs with:
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.5
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { listModels, login, setDefaultModel } from "./auth.js";
|
|
3
3
|
import { startServer } from "./http.js";
|
|
4
|
+
import { version } from "./version.js";
|
|
4
5
|
|
|
5
6
|
function usage() {
|
|
6
7
|
console.error(`Usage:
|
|
8
|
+
service-llm -v | --version
|
|
7
9
|
service-llm login [--device-code]
|
|
8
10
|
service-llm model [list]
|
|
9
11
|
service-llm model set <model-id>
|
|
@@ -39,7 +41,9 @@ function valueAfter(args, names, fallback) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const args = process.argv.slice(2);
|
|
42
|
-
if (args.
|
|
44
|
+
if (args.length === 1 && ["-v", "--version"].includes(args[0])) {
|
|
45
|
+
console.log(`service-llm v${version}`);
|
|
46
|
+
} else if (args.includes("--help") || args.includes("-h")) {
|
|
43
47
|
usage();
|
|
44
48
|
} else if (args[0] === "login") {
|
|
45
49
|
await login({ deviceCode: args.includes("--device-code") });
|
package/dist/http.js
CHANGED
|
@@ -5,7 +5,6 @@ import { version } from "./version.js";
|
|
|
5
5
|
import {
|
|
6
6
|
contentFromMessage,
|
|
7
7
|
createCompletionStream,
|
|
8
|
-
defaultModel,
|
|
9
8
|
messagesFromChat,
|
|
10
9
|
messagesFromResponses,
|
|
11
10
|
toolCallsFromMessage,
|
|
@@ -26,6 +25,23 @@ function sendJson(response, status, body) {
|
|
|
26
25
|
response.end(JSON.stringify(body));
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
export function modelCatalogResponse(catalog) {
|
|
29
|
+
const created = Math.floor(new Date(catalog.updated_at ?? 0).getTime() / 1000) || 0;
|
|
30
|
+
return {
|
|
31
|
+
object: "list",
|
|
32
|
+
data: catalog.models.map((model) => ({
|
|
33
|
+
id: model.id,
|
|
34
|
+
object: "model",
|
|
35
|
+
created,
|
|
36
|
+
owned_by: "openai-codex",
|
|
37
|
+
...(model.name ? { name: model.name } : {}),
|
|
38
|
+
})),
|
|
39
|
+
default_model: catalog.default_model,
|
|
40
|
+
default_reasoning_effort: catalog.default_reasoning_effort ?? "low",
|
|
41
|
+
updated_at: catalog.updated_at,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
async function readJson(request) {
|
|
30
46
|
const chunks = [];
|
|
31
47
|
let size = 0;
|
|
@@ -151,8 +167,8 @@ async function responses(request, response, body) {
|
|
|
151
167
|
sendJson(response, 200, responseObject(id, model, await collect(streamResult)));
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
async function ask({ prompt, model
|
|
155
|
-
const completion = await
|
|
170
|
+
export async function ask({ prompt, model, tools, toolChoice }, createCompletion = createCompletionStream) {
|
|
171
|
+
const completion = await createCompletion({
|
|
156
172
|
context: messagesFromResponses(prompt),
|
|
157
173
|
model,
|
|
158
174
|
tools,
|
|
@@ -167,7 +183,20 @@ async function mcp(response, body) {
|
|
|
167
183
|
if (body.method === "initialize") return sendJson(response, 200, { ...base, result: { protocolVersion: body.params?.protocolVersion ?? "2025-03-26", capabilities: { tools: {} }, serverInfo: { name: "service-llm", version } } });
|
|
168
184
|
if (body.method === "notifications/initialized") return response.writeHead(202).end();
|
|
169
185
|
if (body.method === "ping") return sendJson(response, 200, { ...base, result: {} });
|
|
170
|
-
if (body.method === "tools/list") return sendJson(response, 200, { ...base, result: { tools: [
|
|
186
|
+
if (body.method === "tools/list") return sendJson(response, 200, { ...base, result: { tools: [
|
|
187
|
+
{ name: "ask", description: "Ask a Codex model a question and optionally allow it to return function tool calls. Tool calls are never executed.", inputSchema: { type: "object", properties: { prompt: { type: "string" }, model: { type: "string" }, tools: { type: "array", items: { type: "object" } }, tool_choice: {} }, required: ["prompt"], additionalProperties: false } },
|
|
188
|
+
{ name: "list_models", description: "List the available Codex models and report the model currently configured as the default.", inputSchema: { type: "object", properties: {}, additionalProperties: false } },
|
|
189
|
+
] } });
|
|
190
|
+
if (body.method === "tools/call" && body.params?.name === "list_models") {
|
|
191
|
+
const catalog = modelCatalogResponse(await listModels());
|
|
192
|
+
return sendJson(response, 200, {
|
|
193
|
+
...base,
|
|
194
|
+
result: {
|
|
195
|
+
content: [{ type: "text", text: JSON.stringify(catalog, null, 2) }],
|
|
196
|
+
structuredContent: catalog,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
171
200
|
if (body.method === "tools/call" && body.params?.name === "ask") {
|
|
172
201
|
if (typeof body.params.arguments?.prompt !== "string") return sendJson(response, 200, { ...base, error: { code: -32602, message: "ask requires a string prompt" } });
|
|
173
202
|
const answer = await ask({
|
|
@@ -209,6 +238,7 @@ export function startServer({ port = 7060, host = "127.0.0.1", apiKeys = [] } =
|
|
|
209
238
|
});
|
|
210
239
|
if (request.method === "GET" && url.pathname === "/health") return sendJson(response, 200, { status: "ok" });
|
|
211
240
|
if (!isAuthorized(request, allowedKeys)) return sendJson(response, 401, { error: { message: "Invalid API key", type: "authentication_error" } });
|
|
241
|
+
if (request.method === "GET" && url.pathname === "/v1/models") return sendJson(response, 200, modelCatalogResponse(await listModels()));
|
|
212
242
|
if (request.method !== "POST") return sendJson(response, 405, { error: { message: "Method not allowed" } });
|
|
213
243
|
const body = await readJson(request);
|
|
214
244
|
debug("request body", body);
|
|
@@ -245,6 +275,7 @@ export function startServer({ port = 7060, host = "127.0.0.1", apiKeys = [] } =
|
|
|
245
275
|
}
|
|
246
276
|
console.error("Endpoints:");
|
|
247
277
|
console.error(` Health: ${baseUrl}/health`);
|
|
278
|
+
console.error(` Models: ${baseUrl}/v1/models`);
|
|
248
279
|
console.error(` Chat Completions: ${baseUrl}/v1/chat/completions`);
|
|
249
280
|
console.error(` Responses: ${baseUrl}/v1/responses`);
|
|
250
281
|
console.error(` MCP: ${baseUrl}/mcp`);
|
package/package.json
CHANGED