@khanglvm/llm-router 1.3.1 → 2.0.0-beta.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +337 -41
  3. package/package.json +19 -3
  4. package/src/cli/router-module.js +7331 -3805
  5. package/src/cli/wrangler-toml.js +1 -1
  6. package/src/cli-entry.js +162 -24
  7. package/src/node/amp-client-config.js +426 -0
  8. package/src/node/coding-tool-config.js +763 -0
  9. package/src/node/config-store.js +49 -18
  10. package/src/node/instance-state.js +213 -12
  11. package/src/node/listen-port.js +5 -37
  12. package/src/node/local-server-settings.js +122 -0
  13. package/src/node/local-server.js +3 -2
  14. package/src/node/provider-probe.js +13 -0
  15. package/src/node/start-command.js +282 -40
  16. package/src/node/startup-manager.js +64 -29
  17. package/src/node/web-command.js +106 -0
  18. package/src/node/web-console-assets.js +26 -0
  19. package/src/node/web-console-client.js +56 -0
  20. package/src/node/web-console-dev-assets.js +258 -0
  21. package/src/node/web-console-server.js +3146 -0
  22. package/src/node/web-console-styles.generated.js +1 -0
  23. package/src/node/web-console-ui/config-editor-utils.js +616 -0
  24. package/src/node/web-console-ui/lib/utils.js +6 -0
  25. package/src/node/web-console-ui/rate-limit-utils.js +144 -0
  26. package/src/node/web-console-ui/select-search-utils.js +36 -0
  27. package/src/runtime/codex-request-transformer.js +46 -5
  28. package/src/runtime/codex-response-transformer.js +268 -35
  29. package/src/runtime/config.js +1394 -35
  30. package/src/runtime/handler/amp-gemini.js +913 -0
  31. package/src/runtime/handler/amp-response.js +308 -0
  32. package/src/runtime/handler/amp.js +290 -0
  33. package/src/runtime/handler/auth.js +17 -2
  34. package/src/runtime/handler/provider-call.js +168 -50
  35. package/src/runtime/handler/provider-translation.js +937 -26
  36. package/src/runtime/handler/request.js +149 -6
  37. package/src/runtime/handler/route-debug.js +22 -1
  38. package/src/runtime/handler.js +449 -9
  39. package/src/runtime/subscription-auth.js +1 -6
  40. package/src/shared/local-router-defaults.js +62 -0
  41. package/src/translator/index.js +3 -1
  42. package/src/translator/request/openai-to-claude.js +217 -6
  43. package/src/translator/response/openai-to-claude.js +206 -58
@@ -0,0 +1,258 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promises as fs, watch as fsWatch } from "node:fs";
6
+ import { build, context as createEsbuildContext } from "esbuild";
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const repoDir = path.resolve(scriptDir, "../..");
10
+ const appEntry = path.join(repoDir, "src/node/web-console-ui/main.jsx");
11
+ const stylesInput = path.join(repoDir, "src/node/web-console-ui/styles.css");
12
+ const tailwindCli = process.platform === "win32" ? "npx.cmd" : "npx";
13
+
14
+ function toErrorMessage(error) {
15
+ return error instanceof Error ? error.message : String(error);
16
+ }
17
+
18
+ function createBuildErrorScript(label, error) {
19
+ const lines = [`[llm-router] ${label} build error`, toErrorMessage(error)];
20
+ const escaped = JSON.stringify(lines.join("\n\n"));
21
+ return `(() => {
22
+ const message = ${escaped};
23
+ console.error(message);
24
+ const pre = document.createElement("pre");
25
+ pre.textContent = message;
26
+ pre.style.whiteSpace = "pre-wrap";
27
+ pre.style.padding = "24px";
28
+ pre.style.margin = "0";
29
+ pre.style.font = "14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace";
30
+ pre.style.color = "#7f1d1d";
31
+ pre.style.background = "#fff7ed";
32
+ pre.style.minHeight = "100vh";
33
+ document.body.innerHTML = "";
34
+ document.body.appendChild(pre);
35
+ })();`;
36
+ }
37
+
38
+ function buildEsbuildOptions(outputFile) {
39
+ return {
40
+ entryPoints: [appEntry],
41
+ bundle: true,
42
+ outfile: outputFile,
43
+ write: true,
44
+ format: "iife",
45
+ jsx: "automatic",
46
+ legalComments: "none",
47
+ minify: false,
48
+ platform: "browser",
49
+ target: ["es2020"],
50
+ sourcemap: "inline",
51
+ define: {
52
+ "process.env.NODE_ENV": JSON.stringify("development")
53
+ }
54
+ };
55
+ }
56
+
57
+ async function readTextIfExists(filePath) {
58
+ try {
59
+ return await fs.readFile(filePath, "utf8");
60
+ } catch (error) {
61
+ if (error && typeof error === "object" && error.code === "ENOENT") {
62
+ return "";
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ function runProcess(command, args, { cwd, stdio = ["ignore", "ignore", "pipe"] } = {}) {
69
+ return new Promise((resolve, reject) => {
70
+ const child = spawn(command, args, { cwd, stdio });
71
+ let stderr = "";
72
+
73
+ child.once("error", reject);
74
+ child.stderr?.on("data", (chunk) => {
75
+ stderr += String(chunk || "");
76
+ });
77
+ child.once("exit", (code) => {
78
+ if (code === 0) {
79
+ resolve();
80
+ return;
81
+ }
82
+ reject(new Error(stderr.trim() || `${command} exited with code ${code || 1}.`));
83
+ });
84
+ });
85
+ }
86
+
87
+ export async function startWebConsoleDevAssets({
88
+ onChange,
89
+ onError,
90
+ cwd = repoDir
91
+ } = {}) {
92
+ let closed = false;
93
+ let appJs = 'console.info("[llm-router] Waiting for dev bundle…");';
94
+ let stylesCss = "";
95
+ let changeTimer = null;
96
+ let esbuildContext = null;
97
+ let cssWatcher = null;
98
+ let tailwindProcess = null;
99
+ let tempDir = "";
100
+
101
+ const emitError = (message) => {
102
+ if (typeof onError === "function") onError(message);
103
+ };
104
+
105
+ const scheduleChange = (kind) => {
106
+ if (closed) return;
107
+ if (changeTimer) clearTimeout(changeTimer);
108
+ changeTimer = setTimeout(() => {
109
+ changeTimer = null;
110
+ if (typeof onChange === "function") onChange({ kind, changedAt: new Date().toISOString() });
111
+ }, 90);
112
+ };
113
+
114
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "llm-router-web-dev-"));
115
+ const appOutput = path.join(tempDir, "app.js");
116
+ const stylesOutput = path.join(tempDir, "styles.css");
117
+
118
+ async function refreshAppBundle(kind = "app") {
119
+ const nextJs = await readTextIfExists(appOutput);
120
+ if (!nextJs) {
121
+ throw new Error("Web console bundle output is empty.");
122
+ }
123
+ appJs = nextJs;
124
+ scheduleChange(kind);
125
+ }
126
+
127
+ async function rebuildAppBundle() {
128
+ try {
129
+ await build(buildEsbuildOptions(appOutput));
130
+ await refreshAppBundle("app");
131
+ } catch (error) {
132
+ appJs = createBuildErrorScript("Web console", error);
133
+ emitError(`Web console build error: ${toErrorMessage(error)}`);
134
+ scheduleChange("app-error");
135
+ }
136
+ }
137
+
138
+ async function refreshStyles(kind = "styles") {
139
+ try {
140
+ const nextCss = await readTextIfExists(stylesOutput);
141
+ if (!nextCss) return;
142
+ stylesCss = nextCss;
143
+ scheduleChange(kind);
144
+ } catch (error) {
145
+ emitError(`Web console styles error: ${toErrorMessage(error)}`);
146
+ }
147
+ }
148
+
149
+ async function buildStylesOnce() {
150
+ await runProcess(tailwindCli, [
151
+ "@tailwindcss/cli",
152
+ "-i",
153
+ stylesInput,
154
+ "-o",
155
+ stylesOutput,
156
+ "--cwd",
157
+ cwd
158
+ ], { cwd });
159
+ await refreshStyles("styles");
160
+ }
161
+
162
+ await rebuildAppBundle();
163
+ await buildStylesOnce();
164
+
165
+ esbuildContext = await createEsbuildContext({
166
+ ...buildEsbuildOptions(appOutput),
167
+ plugins: [
168
+ {
169
+ name: "llm-router-dev-refresh",
170
+ setup(buildApi) {
171
+ buildApi.onEnd(async (result) => {
172
+ if (closed) return;
173
+ if (Array.isArray(result.errors) && result.errors.length > 0) {
174
+ const message = result.errors.map((entry) => entry.text).filter(Boolean).join("\n") || "Unknown esbuild error.";
175
+ appJs = createBuildErrorScript("Web console", message);
176
+ emitError(`Web console build error: ${message}`);
177
+ scheduleChange("app-error");
178
+ return;
179
+ }
180
+
181
+ try {
182
+ await refreshAppBundle("app");
183
+ } catch (error) {
184
+ appJs = createBuildErrorScript("Web console", error);
185
+ emitError(`Web console build error: ${toErrorMessage(error)}`);
186
+ scheduleChange("app-error");
187
+ }
188
+ });
189
+ }
190
+ }
191
+ ]
192
+ });
193
+ await esbuildContext.watch();
194
+
195
+ tailwindProcess = spawn(tailwindCli, [
196
+ "@tailwindcss/cli",
197
+ "-i",
198
+ stylesInput,
199
+ "-o",
200
+ stylesOutput,
201
+ "--cwd",
202
+ cwd,
203
+ "--watch"
204
+ ], {
205
+ cwd,
206
+ stdio: ["ignore", "ignore", "pipe"]
207
+ });
208
+
209
+ tailwindProcess.once("error", (error) => {
210
+ emitError(`Tailwind watch error: ${toErrorMessage(error)}`);
211
+ });
212
+ tailwindProcess.stderr?.on("data", (chunk) => {
213
+ const message = String(chunk || "").trim();
214
+ if (message) emitError(`Tailwind watch error: ${message}`);
215
+ });
216
+
217
+ cssWatcher = fsWatch(tempDir, (eventType, filename) => {
218
+ if (closed) return;
219
+ if (filename && String(filename) !== "styles.css") return;
220
+ if ((eventType || "change") !== "rename" && (eventType || "change") !== "change") return;
221
+ void refreshStyles("styles");
222
+ });
223
+
224
+ return {
225
+ isDevMode: true,
226
+ getAppJs() {
227
+ return appJs;
228
+ },
229
+ getStylesCss() {
230
+ return stylesCss;
231
+ },
232
+ async close() {
233
+ if (closed) return;
234
+ closed = true;
235
+
236
+ if (changeTimer) {
237
+ clearTimeout(changeTimer);
238
+ changeTimer = null;
239
+ }
240
+ if (cssWatcher) {
241
+ cssWatcher.close();
242
+ cssWatcher = null;
243
+ }
244
+ if (tailwindProcess && tailwindProcess.exitCode === null) {
245
+ tailwindProcess.kill("SIGTERM");
246
+ }
247
+ tailwindProcess = null;
248
+ if (esbuildContext) {
249
+ await esbuildContext.dispose();
250
+ esbuildContext = null;
251
+ }
252
+ if (tempDir) {
253
+ await fs.rm(tempDir, { recursive: true, force: true });
254
+ tempDir = "";
255
+ }
256
+ }
257
+ };
258
+ }