@leoustc/service-llm 0.2.6 → 0.2.7

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 CHANGED
@@ -35,7 +35,7 @@ For a headless or remote machine:
35
35
  service-llm login --device-code
36
36
  ```
37
37
 
38
- Credentials are stored securely at `~/.service-llm/auth.json`. The available Codex models are recorded at `~/.service-llm/models.json`.
38
+ Credentials are stored securely at `~/.service-llm/auth.json`. During login, the service fetches the available Codex API models and records them at `~/.service-llm/models.json`.
39
39
 
40
40
  If you start the service before logging in, the first model request returns a device code and verification URL. Complete the browser login, then retry the request.
41
41
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.6
1
+ 0.2.7
package/dist/auth.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  loginOpenAICodexDeviceCode,
8
8
  refreshOpenAICodexToken,
9
9
  } from "./codex-oauth.js";
10
- import { getModels } from "./codex.js";
10
+ import { fetchModels, getModels } from "./codex.js";
11
11
 
12
12
  export const authPath = join(homedir(), ".service-llm", "auth.json");
13
13
  export const modelsPath = join(homedir(), ".service-llm", "models.json");
@@ -23,8 +23,15 @@ function smallestModel(models) {
23
23
  })[0];
24
24
  }
25
25
 
26
- async function saveModelCatalog() {
27
- const models = getModels("openai-codex");
26
+ async function saveModelCatalog(accessToken) {
27
+ let models = getModels("openai-codex");
28
+ if (accessToken) {
29
+ try {
30
+ models = await fetchModels(accessToken);
31
+ } catch (error) {
32
+ console.error(`Unable to refresh the Codex model catalog; using the bundled fallback: ${error.message}`);
33
+ }
34
+ }
28
35
  const fallback = smallestModel(models);
29
36
  if (!fallback) throw new Error("No OpenAI Codex models are available");
30
37
  let previousDefault;
@@ -108,7 +115,7 @@ export async function login({ deviceCode = false } = {}) {
108
115
  const data = await readAuth();
109
116
  data["openai-codex"] = { type: "oauth", ...credentials };
110
117
  await saveAuth(data);
111
- const catalog = await saveModelCatalog();
118
+ const catalog = await saveModelCatalog(credentials.access);
112
119
  console.error(`Authentication saved to ${authPath}`);
113
120
  console.error(`Loaded ${catalog.models.length} models; default is ${catalog.default_model} with low reasoning`);
114
121
  }
@@ -150,7 +157,7 @@ async function beginDeviceLogin() {
150
157
  const data = await readAuth();
151
158
  data["openai-codex"] = { type: "oauth", ...credentials };
152
159
  await saveAuth(data);
153
- await saveModelCatalog();
160
+ await saveModelCatalog(credentials.access);
154
161
  console.error(`Authentication completed and saved to ${authPath}`);
155
162
  })
156
163
  .catch((error) => {
package/dist/codex.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Codex Responses transport adapted from @earendil-works/pi-ai (MIT).
2
- import { platform, release, arch } from "node:os";
3
2
 
4
3
  const BASE_URL = "https://chatgpt.com/backend-api/codex/responses";
4
+ const MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
5
+ const CODEX_CLIENT_VERSION = "0.144.1";
5
6
  const MODEL_DATA = [
6
7
  ["gpt-5.3-codex-spark", "GPT-5.3 Codex Spark", 1.75, 14],
7
8
  ["gpt-5.4", "GPT-5.4", 2.5, 15],
@@ -24,6 +25,40 @@ export function getModel(provider, id) {
24
25
  return provider === "openai-codex" ? models.find((model) => model.id === id) : undefined;
25
26
  }
26
27
 
28
+ export async function fetchModels(accessToken, request = fetch) {
29
+ const id = accountId(accessToken);
30
+ if (!id) throw new Error("Failed to extract the ChatGPT account ID from the access token");
31
+ const url = new URL(MODELS_URL);
32
+ url.searchParams.set("client_version", CODEX_CLIENT_VERSION);
33
+ const response = await request(url, {
34
+ headers: {
35
+ authorization: `Bearer ${accessToken}`,
36
+ "chatgpt-account-id": id,
37
+ originator: "codex_cli_rs",
38
+ "user-agent": `codex_cli_rs/${CODEX_CLIENT_VERSION}`,
39
+ },
40
+ });
41
+ if (!response.ok) throw new Error(`Codex model discovery failed (${response.status})`);
42
+ const body = await response.json();
43
+ return (body.models ?? [])
44
+ .filter((model) => model.visibility === "list" && model.supported_in_api !== false)
45
+ .map((model) => {
46
+ const builtin = getModel("openai-codex", model.slug);
47
+ if (builtin) return builtin;
48
+ const discovered = {
49
+ id: model.slug,
50
+ name: model.display_name ?? model.slug,
51
+ provider: "openai-codex",
52
+ api: "openai-codex-responses",
53
+ baseUrl: BASE_URL,
54
+ cost: { input: Infinity, output: Infinity },
55
+ input: model.input_modalities ?? ["text"],
56
+ };
57
+ models.push(discovered);
58
+ return discovered;
59
+ });
60
+ }
61
+
27
62
  function accountId(token) {
28
63
  try {
29
64
  return JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"))?.["https://api.openai.com/auth"]?.chatgpt_account_id;
@@ -110,8 +145,8 @@ export function stream(model, context, options = {}) {
110
145
  headers: {
111
146
  authorization: `Bearer ${options.apiKey}`,
112
147
  "chatgpt-account-id": id,
113
- originator: "pi",
114
- "user-agent": `service-llm (${platform()} ${release()}; ${arch()})`,
148
+ originator: "codex_cli_rs",
149
+ "user-agent": `codex_cli_rs/${CODEX_CLIENT_VERSION}`,
115
150
  "openai-beta": "responses=experimental",
116
151
  accept: "text/event-stream",
117
152
  "content-type": "application/json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leoustc/service-llm",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Standalone CLI service exposing a Codex subscription through OpenAI-compatible and MCP endpoints.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",