@anjieyang/uncommon-route 0.3.0 → 0.3.1
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 +105 -7
- package/openclaw.plugin.json +1 -1
- package/openclaw.security.json +1 -1
- package/package.json +1 -1
- package/src/index.js +121 -42
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @anjieyang/uncommon-route
|
|
4
4
|
|
|
5
|
-
OpenClaw plugin for [UncommonRoute](https://github.com/CommonstackAI/UncommonRoute), the local LLM router that
|
|
5
|
+
OpenClaw plugin for [UncommonRoute](https://github.com/CommonstackAI/UncommonRoute), the local LLM router that classifies prompts, scores the discovered upstream pool, and routes virtual model IDs before forwarding requests upstream.
|
|
6
6
|
|
|
7
7
|
If you use OpenClaw and want one local endpoint with smart routing behind it, this plugin is the shortest path.
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@ This plugin:
|
|
|
18
18
|
- starts `uncommon-route serve`
|
|
19
19
|
- registers the local provider with OpenClaw
|
|
20
20
|
- exposes the virtual routing modes like `uncommon-route/auto`
|
|
21
|
+
- syncs the discovered upstream pool into OpenClaw after the local proxy becomes healthy
|
|
21
22
|
|
|
22
23
|
## Install
|
|
23
24
|
|
|
@@ -39,7 +40,7 @@ Example plugin config:
|
|
|
39
40
|
```yaml
|
|
40
41
|
plugins:
|
|
41
42
|
entries:
|
|
42
|
-
|
|
43
|
+
uncommon-route:
|
|
43
44
|
port: 8403
|
|
44
45
|
upstream: "https://api.commonstack.ai/v1"
|
|
45
46
|
spendLimits:
|
|
@@ -47,6 +48,10 @@ plugins:
|
|
|
47
48
|
daily: 20.00
|
|
48
49
|
```
|
|
49
50
|
|
|
51
|
+
> **Note:** OpenClaw uses the unscoped directory name `uncommon-route` as the
|
|
52
|
+
> entries key, not the full npm package name `@anjieyang/uncommon-route`.
|
|
53
|
+
> Config placed under the scoped name will not reach the plugin.
|
|
54
|
+
|
|
50
55
|
Common upstream choices:
|
|
51
56
|
|
|
52
57
|
| Provider | URL |
|
|
@@ -64,7 +69,9 @@ Parallax is best treated as an experimental local upstream for now: its public d
|
|
|
64
69
|
|
|
65
70
|
- a local OpenClaw provider backed by `http://127.0.0.1:8403/v1`
|
|
66
71
|
- `uncommon-route/auto` for balanced smart routing
|
|
67
|
-
-
|
|
72
|
+
- always-available virtual modes: `uncommon-route/fast` and `uncommon-route/best`
|
|
73
|
+
|
|
74
|
+
Once the proxy is up and `/v1/models/mapping` is available, the plugin refreshes the OpenClaw provider catalog from the discovered pool. If discovery is unavailable, the virtual modes still work and explicit passthrough model IDs can still be typed manually.
|
|
68
75
|
|
|
69
76
|
The router also keeps a fallback chain, records local feedback, and exposes a local dashboard at `http://127.0.0.1:8403/dashboard/`.
|
|
70
77
|
|
|
@@ -86,13 +93,104 @@ If the plugin is installed but responses are failing:
|
|
|
86
93
|
3. Open `http://127.0.0.1:8403/health`.
|
|
87
94
|
4. Open `http://127.0.0.1:8403/dashboard/`.
|
|
88
95
|
|
|
96
|
+
## Turn It Off Or Remove It
|
|
97
|
+
|
|
98
|
+
If you want to stop using the OpenClaw plugin, there are three different levels:
|
|
99
|
+
|
|
100
|
+
1. stop routing traffic from OpenClaw
|
|
101
|
+
2. clear all local UncommonRoute records and state
|
|
102
|
+
3. fully uninstall the plugin and the Python package
|
|
103
|
+
|
|
104
|
+
### 1. Stop routing traffic from OpenClaw
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
openclaw plugins uninstall @anjieyang/uncommon-route
|
|
108
|
+
openclaw gateway restart
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If you also started `uncommon-route serve` manually, stop that too:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uncommon-route stop
|
|
115
|
+
# or stop the foreground process with Ctrl+C
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
If you used the config-patch fallback instead of the plugin, remove that registration too:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uncommon-route openclaw uninstall
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 2. Clear all local records
|
|
125
|
+
|
|
126
|
+
By default, UncommonRoute stores local state under:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
~/.uncommon-route
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If you set `UNCOMMON_ROUTE_DATA_DIR`, it uses that directory instead.
|
|
133
|
+
|
|
134
|
+
That local data directory can contain:
|
|
135
|
+
|
|
136
|
+
- route stats and spending history
|
|
137
|
+
- dashboard-saved primary connection and routing overrides
|
|
138
|
+
- BYOK provider keys
|
|
139
|
+
- online-learning weights and feedback buffers
|
|
140
|
+
- learned aliases, model-experience memory, logs, and local artifacts
|
|
141
|
+
|
|
142
|
+
To clear **all** local records, stop the proxy first and then move or delete the active data directory:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Show the active data directory
|
|
146
|
+
echo "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"
|
|
147
|
+
|
|
148
|
+
# Recommended: move it aside as a backup first
|
|
149
|
+
mv "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}" \
|
|
150
|
+
"${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}.backup-$(date +%Y%m%d-%H%M%S)"
|
|
151
|
+
|
|
152
|
+
# Or permanently delete it if you are sure
|
|
153
|
+
# rm -rf "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you only want to clear routing analytics, `uncommon-route stats reset` resets stats and pending feedback. It does **not** remove the rest of the local state.
|
|
157
|
+
|
|
158
|
+
### 3. Fully uninstall
|
|
159
|
+
|
|
160
|
+
First remove the OpenClaw plugin or config-patch registration:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
openclaw plugins uninstall @anjieyang/uncommon-route
|
|
164
|
+
uncommon-route openclaw uninstall
|
|
165
|
+
openclaw gateway restart
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If you set environment variables for UncommonRoute, clear them:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
unset UNCOMMON_ROUTE_UPSTREAM
|
|
172
|
+
unset UNCOMMON_ROUTE_API_KEY
|
|
173
|
+
unset OPENAI_BASE_URL
|
|
174
|
+
unset ANTHROPIC_BASE_URL
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Then remove the Python package with the same tool you used to install it:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pipx uninstall uncommon-route
|
|
181
|
+
# or
|
|
182
|
+
python -m pip uninstall uncommon-route
|
|
183
|
+
# or
|
|
184
|
+
pip uninstall uncommon-route
|
|
185
|
+
```
|
|
186
|
+
|
|
89
187
|
## Benchmarks
|
|
90
188
|
|
|
91
189
|
Current repo benchmarks:
|
|
92
190
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
191
|
+
- 97.4% held-out routing accuracy on the current in-repo benchmark set
|
|
192
|
+
- ECE improves from 2.1% to 1.7% after temperature scaling
|
|
193
|
+
- 68% lower simulated cost than always using Claude Opus in a 131-request coding session
|
|
96
194
|
|
|
97
195
|
## Links
|
|
98
196
|
|
|
@@ -102,4 +200,4 @@ Current repo benchmarks:
|
|
|
102
200
|
|
|
103
201
|
## License
|
|
104
202
|
|
|
105
|
-
MIT
|
|
203
|
+
Modified MIT
|
package/openclaw.plugin.json
CHANGED
package/openclaw.security.json
CHANGED
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -10,33 +10,26 @@
|
|
|
10
10
|
* → ensures `uncommon-route` Python package is installed (pipx/uv/pip)
|
|
11
11
|
* → spawns `uncommon-route serve` as a managed subprocess
|
|
12
12
|
* → registerProvider pointing at localhost proxy
|
|
13
|
+
* → syncs the discovered upstream pool into OpenClaw after startup
|
|
13
14
|
* → registerCommand for /route, /spend, /feedback
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { spawn, execSync } from "node:child_process";
|
|
17
18
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
18
19
|
|
|
19
|
-
const VERSION = "0.3.
|
|
20
|
+
const VERSION = "0.3.1";
|
|
20
21
|
const DEFAULT_PORT = 8403;
|
|
21
22
|
const DEFAULT_UPSTREAM = "";
|
|
22
23
|
const HEALTH_TIMEOUT_MS = 15_000;
|
|
23
24
|
const HEALTH_POLL_MS = 500;
|
|
24
25
|
const PY_PACKAGE = "uncommon-route";
|
|
26
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
27
|
+
const DEFAULT_MAX_TOKENS = 16_384;
|
|
25
28
|
|
|
26
|
-
const
|
|
27
|
-
{ id: "uncommon-route/auto", name: "UncommonRoute Auto", reasoning: false
|
|
28
|
-
{ id: "uncommon-route/fast", name: "UncommonRoute Fast", reasoning: false
|
|
29
|
-
{ id: "uncommon-route/best", name: "UncommonRoute Best", reasoning: true
|
|
30
|
-
{ id: "moonshot/kimi-k2.5", name: "Kimi K2.5", reasoning: false, input: 0.60, output: 3.00, ctx: 128_000, max: 8_192 },
|
|
31
|
-
{ id: "google/gemini-3.1-pro", name: "Gemini 3.1 Pro", reasoning: false, input: 2.00, output: 12.00, ctx: 200_000, max: 16_384 },
|
|
32
|
-
{ id: "xai/grok-4-1-fast-reasoning", name: "Grok 4.1 Fast", reasoning: true, input: 0.20, output: 0.50, ctx: 200_000, max: 16_384 },
|
|
33
|
-
{ id: "deepseek/deepseek-chat", name: "DeepSeek Chat", reasoning: false, input: 0.28, output: 0.42, ctx: 128_000, max: 8_192 },
|
|
34
|
-
{ id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner", reasoning: true, input: 0.28, output: 0.42, ctx: 128_000, max: 8_192 },
|
|
35
|
-
{ id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", reasoning: false, input: 0.30, output: 2.50, ctx: 200_000, max: 16_384 },
|
|
36
|
-
{ id: "google/gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite", reasoning: false, input: 0.10, output: 0.40, ctx: 200_000, max: 16_384 },
|
|
37
|
-
{ id: "openai/gpt-5.2", name: "GPT-5.2", reasoning: false, input: 1.75, output: 14.00, ctx: 200_000, max: 16_384 },
|
|
38
|
-
{ id: "openai/o4-mini", name: "o4 Mini", reasoning: true, input: 1.10, output: 4.40, ctx: 200_000, max: 16_384 },
|
|
39
|
-
{ id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6", reasoning: false, input: 3.00, output: 15.00, ctx: 200_000, max: 16_384 },
|
|
29
|
+
const VIRTUAL_MODELS = [
|
|
30
|
+
{ id: "uncommon-route/auto", name: "UncommonRoute Auto", reasoning: false },
|
|
31
|
+
{ id: "uncommon-route/fast", name: "UncommonRoute Fast", reasoning: false },
|
|
32
|
+
{ id: "uncommon-route/best", name: "UncommonRoute Best", reasoning: true },
|
|
40
33
|
];
|
|
41
34
|
|
|
42
35
|
// ── Python dependency management ─────────────────────────────────────
|
|
@@ -134,21 +127,71 @@ function ensurePythonDeps(logger) {
|
|
|
134
127
|
|
|
135
128
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
136
129
|
|
|
137
|
-
function
|
|
130
|
+
function toFiniteNumber(value) {
|
|
131
|
+
const num = Number(value);
|
|
132
|
+
return Number.isFinite(num) ? num : 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function modelEntry({
|
|
136
|
+
id,
|
|
137
|
+
name = id,
|
|
138
|
+
reasoning = false,
|
|
139
|
+
input = 0,
|
|
140
|
+
output = 0,
|
|
141
|
+
cacheRead = 0,
|
|
142
|
+
cacheWrite = 0,
|
|
143
|
+
ctx = DEFAULT_CONTEXT_WINDOW,
|
|
144
|
+
max = DEFAULT_MAX_TOKENS,
|
|
145
|
+
}) {
|
|
146
|
+
return {
|
|
147
|
+
id,
|
|
148
|
+
name,
|
|
149
|
+
api: "openai-completions",
|
|
150
|
+
reasoning,
|
|
151
|
+
input: ["text"],
|
|
152
|
+
cost: { input, output, cacheRead, cacheWrite },
|
|
153
|
+
contextWindow: ctx,
|
|
154
|
+
maxTokens: max,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function discoveredModelEntries(discoveredPool) {
|
|
159
|
+
if (!Array.isArray(discoveredPool)) return [];
|
|
160
|
+
|
|
161
|
+
const seen = new Set(VIRTUAL_MODELS.map((model) => model.id));
|
|
162
|
+
const models = [];
|
|
163
|
+
|
|
164
|
+
for (const row of discoveredPool) {
|
|
165
|
+
const id = typeof row?.id === "string" ? row.id.trim() : "";
|
|
166
|
+
if (!id || seen.has(id)) continue;
|
|
167
|
+
|
|
168
|
+
seen.add(id);
|
|
169
|
+
const pricing = row?.pricing ?? {};
|
|
170
|
+
const capabilities = row?.capabilities ?? {};
|
|
171
|
+
|
|
172
|
+
models.push(modelEntry({
|
|
173
|
+
id,
|
|
174
|
+
name: id,
|
|
175
|
+
reasoning: Boolean(capabilities.reasoning),
|
|
176
|
+
input: toFiniteNumber(pricing.input),
|
|
177
|
+
output: toFiniteNumber(pricing.output),
|
|
178
|
+
cacheRead: toFiniteNumber(pricing.cached_input),
|
|
179
|
+
cacheWrite: toFiniteNumber(pricing.cache_write),
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return models;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildModels(baseUrl, discoveredPool = []) {
|
|
138
187
|
return {
|
|
139
188
|
baseUrl,
|
|
140
189
|
api: "openai-completions",
|
|
141
190
|
apiKey: "uncommon-route-local-proxy",
|
|
142
|
-
models:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
reasoning: m.reasoning,
|
|
147
|
-
input: ["text"],
|
|
148
|
-
cost: { input: m.input, output: m.output, cacheRead: 0, cacheWrite: 0 },
|
|
149
|
-
contextWindow: m.ctx,
|
|
150
|
-
maxTokens: m.max,
|
|
151
|
-
})),
|
|
191
|
+
models: [
|
|
192
|
+
...VIRTUAL_MODELS.map((model) => modelEntry(model)),
|
|
193
|
+
...discoveredModelEntries(discoveredPool),
|
|
194
|
+
],
|
|
152
195
|
};
|
|
153
196
|
}
|
|
154
197
|
|
|
@@ -190,7 +233,7 @@ async function postJson(url, body) {
|
|
|
190
233
|
let pyProc = null;
|
|
191
234
|
|
|
192
235
|
const plugin = {
|
|
193
|
-
id: "
|
|
236
|
+
id: "uncommon-route",
|
|
194
237
|
name: "UncommonRoute",
|
|
195
238
|
description: "Local LLM router plugin that cuts premium-model spend with smart routing",
|
|
196
239
|
version: VERSION,
|
|
@@ -208,6 +251,34 @@ const plugin = {
|
|
|
208
251
|
const port = cfg.port || Number(process.env.UNCOMMON_ROUTE_PORT) || DEFAULT_PORT;
|
|
209
252
|
const upstream = cfg.upstream || process.env.UNCOMMON_ROUTE_UPSTREAM || DEFAULT_UPSTREAM;
|
|
210
253
|
const baseUrl = `http://127.0.0.1:${port}/v1`;
|
|
254
|
+
let discoveredPool = [];
|
|
255
|
+
let providerCatalog = buildModels(baseUrl, discoveredPool);
|
|
256
|
+
|
|
257
|
+
function applyProviderCatalog(nextPool = discoveredPool) {
|
|
258
|
+
discoveredPool = Array.isArray(nextPool) ? nextPool : [];
|
|
259
|
+
providerCatalog = buildModels(baseUrl, discoveredPool);
|
|
260
|
+
if (!api.config.models) api.config.models = { providers: {} };
|
|
261
|
+
if (!api.config.models.providers) api.config.models.providers = {};
|
|
262
|
+
api.config.models.providers["uncommon-route"] = providerCatalog;
|
|
263
|
+
return providerCatalog;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function syncDiscoveredPool() {
|
|
267
|
+
const mapping = await fetchJson(`http://127.0.0.1:${port}/v1/models/mapping`);
|
|
268
|
+
if (!mapping) {
|
|
269
|
+
api.logger.warn("Could not read /v1/models/mapping; keeping OpenClaw provider catalog on virtual routes only.");
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const nextCatalog = applyProviderCatalog(mapping.pool);
|
|
274
|
+
const discoveredCount = Math.max(nextCatalog.models.length - VIRTUAL_MODELS.length, 0);
|
|
275
|
+
if (mapping.discovered && discoveredCount > 0) {
|
|
276
|
+
api.logger.info(`Synced ${discoveredCount} discovered upstream models into OpenClaw provider catalog`);
|
|
277
|
+
} else {
|
|
278
|
+
api.logger.info("Upstream discovery unavailable; OpenClaw provider catalog remains virtual-mode only");
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
211
282
|
|
|
212
283
|
if (!upstream) {
|
|
213
284
|
api.logger.warn("UncommonRoute: No upstream configured. Set UNCOMMON_ROUTE_UPSTREAM or configure 'upstream' in plugin config.");
|
|
@@ -215,21 +286,17 @@ const plugin = {
|
|
|
215
286
|
}
|
|
216
287
|
|
|
217
288
|
// 1. Register provider immediately (sync, models available right away)
|
|
289
|
+
applyProviderCatalog();
|
|
218
290
|
api.registerProvider({
|
|
219
291
|
id: "uncommon-route",
|
|
220
292
|
label: "UncommonRoute",
|
|
221
293
|
docsPath: "https://github.com/CommonstackAI/UncommonRoute",
|
|
222
294
|
aliases: ["ur", "uncommon"],
|
|
223
295
|
envVars: [],
|
|
224
|
-
get models() { return
|
|
296
|
+
get models() { return providerCatalog; },
|
|
225
297
|
auth: [],
|
|
226
298
|
});
|
|
227
|
-
|
|
228
|
-
if (!api.config.models) api.config.models = { providers: {} };
|
|
229
|
-
if (!api.config.models.providers) api.config.models.providers = {};
|
|
230
|
-
api.config.models.providers["uncommon-route"] = buildModels(baseUrl);
|
|
231
|
-
|
|
232
|
-
api.logger.info(`UncommonRoute provider registered (${MODELS.length} models)`);
|
|
299
|
+
api.logger.info(`UncommonRoute provider registered (${providerCatalog.models.length} virtual route models)`);
|
|
233
300
|
|
|
234
301
|
// 2. Register commands
|
|
235
302
|
api.registerCommand({
|
|
@@ -386,23 +453,34 @@ const plugin = {
|
|
|
386
453
|
|
|
387
454
|
// 6. Auto-install Python deps + spawn proxy
|
|
388
455
|
const bootstrap = async () => {
|
|
389
|
-
|
|
390
|
-
let python = pythonPath;
|
|
456
|
+
let cliBin = which("uncommon-route");
|
|
457
|
+
let python = cfg.pythonPath || process.env.UNCOMMON_ROUTE_PYTHON || null;
|
|
391
458
|
|
|
392
|
-
if (!python || !isPythonPackageInstalled(python)) {
|
|
459
|
+
if (!cliBin && (!python || !isPythonPackageInstalled(python))) {
|
|
393
460
|
api.logger.info("Checking Python dependencies...");
|
|
394
461
|
python = ensurePythonDeps(api.logger);
|
|
395
462
|
if (!python) {
|
|
396
463
|
api.logger.error("Cannot start — Python setup failed. See errors above.");
|
|
397
464
|
return;
|
|
398
465
|
}
|
|
466
|
+
cliBin = which("uncommon-route");
|
|
399
467
|
}
|
|
400
468
|
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
469
|
+
const serveArgs = ["serve", "--port", String(port), "--upstream", upstream];
|
|
470
|
+
if (cliBin) {
|
|
471
|
+
pyProc = spawn(cliBin, serveArgs, {
|
|
472
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
473
|
+
env: { ...process.env, PYTHONUNBUFFERED: "1" },
|
|
474
|
+
});
|
|
475
|
+
} else if (python) {
|
|
476
|
+
pyProc = spawn(python, ["-m", "uncommon_route.cli", ...serveArgs], {
|
|
477
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
478
|
+
env: { ...process.env, PYTHONUNBUFFERED: "1" },
|
|
479
|
+
});
|
|
480
|
+
} else {
|
|
481
|
+
api.logger.error("Cannot start — neither uncommon-route CLI nor Python module found.");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
406
484
|
|
|
407
485
|
pyProc.stdout?.on("data", (chunk) => {
|
|
408
486
|
const line = chunk.toString().trim();
|
|
@@ -420,6 +498,7 @@ const plugin = {
|
|
|
420
498
|
api.logger.info(`Starting proxy on port ${port}...`);
|
|
421
499
|
const healthy = await waitForHealth(port);
|
|
422
500
|
if (healthy) {
|
|
501
|
+
await syncDiscoveredPool();
|
|
423
502
|
api.logger.info(`UncommonRoute ready at http://127.0.0.1:${port}`);
|
|
424
503
|
api.logger.info(`Default model: uncommon-route/auto`);
|
|
425
504
|
} else {
|