openclaw-freerouter 1.3.0 → 2.0.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/LICENSE +1 -1
- package/README.md +123 -64
- package/openclaw.plugin.json +96 -12
- package/package.json +32 -20
- package/src/cli.ts +378 -0
- package/src/index.ts +144 -0
- package/src/models.ts +1 -1
- package/src/provider.ts +563 -676
- package/src/router/config.ts +34 -21
- package/src/router/index.ts +9 -12
- package/src/router/rules.ts +1 -1
- package/src/router/selector.ts +1 -1
- package/src/router/types.ts +1 -2
- package/src/service.ts +773 -0
- package/CHANGELOG.md +0 -26
- package/index.ts +0 -63
- package/src/auth.ts +0 -128
- package/src/config.ts +0 -220
- package/src/logger.ts +0 -32
- package/src/server.ts +0 -381
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,48 +1,63 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FreeRouter — OpenClaw Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Smart LLM router that classifies your requests and routes them to the best model automatically. Uses a 14-dimension weighted scorer (<1ms) with configurable tier→model mapping.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Or install from local path:
|
|
8
|
+
# From local directory
|
|
9
|
+
openclaw plugins install -l /path/to/freerouter-plugin
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
openclaw plugins install
|
|
11
|
+
# Or copy to extensions
|
|
12
|
+
openclaw plugins install /path/to/freerouter-plugin
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
##
|
|
15
|
+
## Configure
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
After install, configure tiers in `openclaw.json`:
|
|
20
18
|
|
|
21
|
-
```
|
|
19
|
+
```json5
|
|
22
20
|
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
// Set FreeRouter as your default model
|
|
22
|
+
agents: {
|
|
23
|
+
defaults: {
|
|
24
|
+
model: { primary: "freerouter/auto" }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Add FreeRouter as a provider pointing to its HTTP proxy
|
|
29
|
+
providers: {
|
|
30
|
+
freerouter: {
|
|
31
|
+
baseUrl: "http://127.0.0.1:18801/v1",
|
|
32
|
+
api: "openai-completions"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Plugin config
|
|
37
|
+
plugins: {
|
|
38
|
+
entries: {
|
|
39
|
+
freerouter: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
config: {
|
|
42
|
+
port: 18801, // HTTP proxy port (0 = disabled)
|
|
43
|
+
host: "127.0.0.1", // Bind address
|
|
44
|
+
|
|
45
|
+
// Customize which models handle each tier
|
|
46
|
+
tiers: {
|
|
47
|
+
SIMPLE: { primary: "kimi-coding/kimi-for-coding", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
48
|
+
MEDIUM: { primary: "anthropic/claude-sonnet-4-5", fallback: ["anthropic/claude-opus-4-6"] },
|
|
49
|
+
COMPLEX: { primary: "anthropic/claude-opus-4-6", fallback: [] },
|
|
50
|
+
REASONING: { primary: "anthropic/claude-opus-4-6", fallback: [] }
|
|
39
51
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
52
|
+
|
|
53
|
+
// Thinking/reasoning config
|
|
54
|
+
thinking: {
|
|
55
|
+
adaptive: ["claude-opus-4-6"],
|
|
56
|
+
enabled: { models: ["claude-sonnet-4-5"], budget: 4096 }
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Default tier for ambiguous requests
|
|
60
|
+
defaultTier: "MEDIUM"
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
63
|
}
|
|
@@ -50,54 +65,98 @@ Add to your `openclaw.json`:
|
|
|
50
65
|
}
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
All config fields are optional — sensible defaults are built in.
|
|
54
|
-
|
|
55
68
|
## How It Works
|
|
56
69
|
|
|
57
|
-
1.
|
|
58
|
-
2.
|
|
59
|
-
3.
|
|
60
|
-
4.
|
|
70
|
+
1. OpenClaw sends a request with model `freerouter/auto`
|
|
71
|
+
2. FreeRouter's HTTP proxy receives it
|
|
72
|
+
3. The 14-dimension classifier scores the request in <1ms
|
|
73
|
+
4. Routes to the best model for the task (e.g., Kimi for simple, Opus for reasoning)
|
|
74
|
+
5. Forwards to the real provider API
|
|
75
|
+
6. **Returns the actual model name** (e.g., `anthropic/claude-opus-4-6`) so OpenClaw displays what's really running
|
|
61
76
|
|
|
62
77
|
## Tiers
|
|
63
78
|
|
|
64
79
|
| Tier | Default Model | Use Case |
|
|
65
80
|
|------|--------------|----------|
|
|
66
|
-
| SIMPLE |
|
|
67
|
-
| MEDIUM |
|
|
68
|
-
| COMPLEX |
|
|
69
|
-
| REASONING |
|
|
81
|
+
| SIMPLE | kimi-coding/kimi-for-coding | Quick lookups, translations, simple Q&A |
|
|
82
|
+
| MEDIUM | anthropic/claude-sonnet-4-5 | Code generation, creative writing, moderate complexity |
|
|
83
|
+
| COMPLEX | anthropic/claude-opus-4-6 | Architecture design, multi-step reasoning |
|
|
84
|
+
| REASONING | anthropic/claude-opus-4-6 | Mathematical proofs, formal logic, deep analysis |
|
|
70
85
|
|
|
71
|
-
##
|
|
86
|
+
## Per-Prompt Model Override
|
|
72
87
|
|
|
73
|
-
Force a specific
|
|
88
|
+
Force a specific model for one message:
|
|
74
89
|
|
|
75
|
-
- `/
|
|
76
|
-
-
|
|
77
|
-
-
|
|
90
|
+
- `/opus Explain quantum computing` → Claude Opus 4.6
|
|
91
|
+
- `/sonnet Write a poem` → Claude Sonnet 4.5
|
|
92
|
+
- `/kimi What's 2+2?` → Kimi K2.5
|
|
93
|
+
- `/haiku Translate this` → Claude Haiku 4.5
|
|
94
|
+
- `[opus] Deep analysis...` → Claude Opus 4.6
|
|
78
95
|
|
|
79
|
-
##
|
|
96
|
+
## Per-Prompt Tier Override
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
|----------|--------|-------------|
|
|
83
|
-
| `/v1/chat/completions` | POST | OpenAI-compatible chat |
|
|
84
|
-
| `/v1/models` | GET | List available models |
|
|
85
|
-
| `/health` | GET | Health check |
|
|
86
|
-
| `/stats` | GET | Request statistics |
|
|
87
|
-
| `/config` | GET | Show sanitized config |
|
|
88
|
-
| `/reload` | POST | Reload auth keys |
|
|
89
|
-
| `/reload-config` | POST | Reload config + auth |
|
|
98
|
+
Force a tier (uses that tier's primary model):
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
- `/simple What's 2+2?` → SIMPLE tier
|
|
101
|
+
- `/max Prove the Riemann hypothesis` → REASONING tier
|
|
102
|
+
- `[reasoning] Analyze this code...` → REASONING tier
|
|
92
103
|
|
|
93
|
-
|
|
104
|
+
## Session Lock
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
Lock an entire session to a specific model:
|
|
107
|
+
|
|
108
|
+
- `/lock opus` → 🔒 All messages use Opus until unlocked
|
|
109
|
+
- `/lock sonnet` → 🔒 All messages use Sonnet
|
|
110
|
+
- `/lock simple` → 🔒 Lock to SIMPLE tier's primary model
|
|
111
|
+
- `/lock anthropic/claude-opus-4-6` → 🔒 Full model ID
|
|
112
|
+
- `/unlock` → 🔓 Return to auto-routing
|
|
113
|
+
- `/lock auto` → 🔓 Same as unlock
|
|
114
|
+
- `/lock status` → Show current lock state
|
|
115
|
+
|
|
116
|
+
Session locks expire after 4 hours of inactivity.
|
|
117
|
+
|
|
118
|
+
### Supported Aliases
|
|
119
|
+
|
|
120
|
+
| Alias | Model |
|
|
121
|
+
|-------|-------|
|
|
122
|
+
| `opus`, `opus-4`, `opus-4.6` | anthropic/claude-opus-4-6 |
|
|
123
|
+
| `sonnet`, `sonnet-4`, `sonnet-4.5` | anthropic/claude-sonnet-4-5 |
|
|
124
|
+
| `sonnet-4.6` | anthropic/claude-sonnet-4-6 |
|
|
125
|
+
| `haiku`, `haiku-4`, `haiku-4.5` | anthropic/claude-haiku-4-5 |
|
|
126
|
+
| `kimi`, `kimi-k2`, `k2.5` | kimi-coding/kimi-for-coding |
|
|
127
|
+
|
|
128
|
+
## Scoring Dimensions
|
|
129
|
+
|
|
130
|
+
The classifier evaluates 14 weighted dimensions:
|
|
131
|
+
- Token count, code presence, reasoning markers, technical terms
|
|
132
|
+
- Creative markers, simple indicators, multi-step patterns
|
|
133
|
+
- Question complexity, imperative verbs, constraints
|
|
134
|
+
- Output format, references, negation, domain specificity
|
|
135
|
+
- Agentic task indicators
|
|
136
|
+
|
|
137
|
+
## Port Conflicts
|
|
138
|
+
|
|
139
|
+
If port 18801 is in use, change it in the plugin config:
|
|
140
|
+
|
|
141
|
+
```json5
|
|
142
|
+
{ plugins: { entries: { freerouter: { config: { port: 18802 } } } } }
|
|
99
143
|
```
|
|
100
144
|
|
|
145
|
+
Set `port: 0` to disable the HTTP proxy entirely.
|
|
146
|
+
|
|
147
|
+
## Commands
|
|
148
|
+
|
|
149
|
+
- `/freerouter` — Show routing stats in chat
|
|
150
|
+
- `openclaw freerouter status` — CLI status and stats
|
|
151
|
+
|
|
152
|
+
## Response Headers
|
|
153
|
+
|
|
154
|
+
Every proxied response includes:
|
|
155
|
+
- `X-FreeRouter-Model` — Actual model used
|
|
156
|
+
- `X-FreeRouter-Tier` — Classification tier
|
|
157
|
+
- `X-FreeRouter-Thinking` — Thinking mode (off/adaptive/enabled)
|
|
158
|
+
- `X-FreeRouter-Reasoning` — Classification reasoning
|
|
159
|
+
|
|
101
160
|
## License
|
|
102
161
|
|
|
103
162
|
MIT
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,25 +1,109 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "freerouter",
|
|
3
3
|
"name": "FreeRouter",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"description": "Smart LLM router — classify requests and route to the best model using your own API keys. 14-dimension weighted scoring, <1ms classification, configurable tiers.",
|
|
6
6
|
"providers": ["freerouter"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"additionalProperties": false,
|
|
10
10
|
"properties": {
|
|
11
|
-
"port": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
"port": {
|
|
12
|
+
"type": "number",
|
|
13
|
+
"description": "HTTP proxy port (0 = disabled, in-process only)",
|
|
14
|
+
"default": 18801
|
|
15
|
+
},
|
|
16
|
+
"host": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "HTTP proxy bind address",
|
|
19
|
+
"default": "127.0.0.1"
|
|
20
|
+
},
|
|
21
|
+
"tiers": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Model assignments per classification tier",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"SIMPLE": { "$ref": "#/$defs/tierConfig" },
|
|
27
|
+
"MEDIUM": { "$ref": "#/$defs/tierConfig" },
|
|
28
|
+
"COMPLEX": { "$ref": "#/$defs/tierConfig" },
|
|
29
|
+
"REASONING": { "$ref": "#/$defs/tierConfig" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"thinking": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "Thinking/reasoning config per tier",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"properties": {
|
|
37
|
+
"adaptive": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "type": "string" },
|
|
40
|
+
"description": "Model patterns that support adaptive thinking (e.g. ['claude-opus-4-6'])"
|
|
41
|
+
},
|
|
42
|
+
"enabled": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"models": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": { "type": "string" },
|
|
48
|
+
"description": "Model patterns that get thinking enabled"
|
|
49
|
+
},
|
|
50
|
+
"budget": {
|
|
51
|
+
"type": "number",
|
|
52
|
+
"description": "Thinking token budget",
|
|
53
|
+
"default": 4096
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"scoring": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"description": "Override scoring weights, boundaries, or keyword lists",
|
|
62
|
+
"additionalProperties": true
|
|
63
|
+
},
|
|
64
|
+
"defaultTier": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"enum": ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"],
|
|
67
|
+
"description": "Default tier when classification is ambiguous",
|
|
68
|
+
"default": "MEDIUM"
|
|
69
|
+
},
|
|
70
|
+
"logLevel": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"enum": ["debug", "info", "warn", "error"],
|
|
73
|
+
"default": "info"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"$defs": {
|
|
77
|
+
"tierConfig": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"primary": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Primary model (e.g. 'anthropic/claude-opus-4-6')"
|
|
83
|
+
},
|
|
84
|
+
"fallback": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": { "type": "string" },
|
|
87
|
+
"description": "Fallback models if primary fails"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"required": ["primary"]
|
|
91
|
+
}
|
|
19
92
|
}
|
|
20
93
|
},
|
|
21
94
|
"uiHints": {
|
|
22
|
-
"port": {
|
|
23
|
-
|
|
95
|
+
"port": {
|
|
96
|
+
"label": "Proxy Port",
|
|
97
|
+
"placeholder": "18801 (0 to disable HTTP proxy)"
|
|
98
|
+
},
|
|
99
|
+
"tiers": {
|
|
100
|
+
"label": "Tier → Model Mapping"
|
|
101
|
+
},
|
|
102
|
+
"defaultTier": {
|
|
103
|
+
"label": "Default Tier (ambiguous requests)"
|
|
104
|
+
},
|
|
105
|
+
"logLevel": {
|
|
106
|
+
"label": "Log Level"
|
|
107
|
+
}
|
|
24
108
|
}
|
|
25
109
|
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-freerouter",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Smart LLM router plugin for OpenClaw — classify requests and route to the best model using your own API keys. 14-dimension scoring, <1ms classification, per-prompt/session model switching.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"openclaw": {
|
|
7
|
+
"extensions": ["./src/index.ts"]
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "npx tsx test/test-freerouter.mjs && npx tsx test/test-resilience.mjs && npx tsx test/test-overrides.mjs",
|
|
12
|
+
"test:classification": "npx tsx test/test-freerouter.mjs",
|
|
13
|
+
"test:resilience": "npx tsx test/test-resilience.mjs",
|
|
14
|
+
"test:overrides": "npx tsx test/test-overrides.mjs",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
9
16
|
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src/",
|
|
13
|
-
"openclaw.plugin.json",
|
|
14
|
-
"README.md",
|
|
15
|
-
"LICENSE",
|
|
16
|
-
"CHANGELOG.md"
|
|
17
|
-
],
|
|
18
17
|
"keywords": [
|
|
19
18
|
"openclaw",
|
|
20
19
|
"openclaw-plugin",
|
|
21
|
-
"
|
|
20
|
+
"llm-router",
|
|
22
21
|
"model-router",
|
|
22
|
+
"smart-routing",
|
|
23
23
|
"freerouter",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"anthropic",
|
|
25
|
+
"claude",
|
|
26
|
+
"kimi",
|
|
27
|
+
"openai"
|
|
26
28
|
],
|
|
27
|
-
"author": "OpenFreeRouter",
|
|
28
|
-
"license": "MIT",
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
31
|
"url": "https://github.com/openfreerouter/openclaw-freerouter"
|
|
32
32
|
},
|
|
33
|
+
"homepage": "https://github.com/openfreerouter/openclaw-freerouter#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/openfreerouter/openclaw-freerouter/issues"
|
|
36
|
+
},
|
|
37
|
+
"author": "dusama",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"files": [
|
|
40
|
+
"src/",
|
|
41
|
+
"openclaw.plugin.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
33
45
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^22.
|
|
35
|
-
"typescript": "^5.
|
|
46
|
+
"@types/node": "^22.0.0",
|
|
47
|
+
"typescript": "^5.7.0"
|
|
36
48
|
}
|
|
37
49
|
}
|