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/CHANGELOG.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.3.0 (2026-02-14)
|
|
4
|
-
|
|
5
|
-
Initial release as OpenClaw plugin.
|
|
6
|
-
|
|
7
|
-
### Features
|
|
8
|
-
- 14-dimension weighted classifier for smart model routing
|
|
9
|
-
- Auto-classify requests → route to cheapest capable model
|
|
10
|
-
- Tier system: SIMPLE → MEDIUM → COMPLEX → REASONING
|
|
11
|
-
- Agentic task detection with separate tier configs
|
|
12
|
-
- Mode overrides (`/max`, `[simple]`, `reasoning mode:`)
|
|
13
|
-
- Adaptive thinking for Opus 4.6+
|
|
14
|
-
- Configurable providers, tiers, and boundaries via plugin config
|
|
15
|
-
- Fallback chains per tier
|
|
16
|
-
- Timeout + stall detection per tier
|
|
17
|
-
- OpenAI-compatible API (Anthropic Messages ↔ OpenAI translation)
|
|
18
|
-
- Tool call support (OpenAI ↔ Anthropic conversion)
|
|
19
|
-
- Streaming with SSE format translation
|
|
20
|
-
- Zero external dependencies
|
|
21
|
-
|
|
22
|
-
### Plugin Integration
|
|
23
|
-
- Starts/stops with OpenClaw gateway lifecycle
|
|
24
|
-
- Config via `plugins.entries.freerouter.config` in openclaw.json
|
|
25
|
-
- Auto-registers as `freerouter` provider
|
|
26
|
-
- Reads auth from OpenClaw's auth-profiles.json
|
package/index.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FreeRouter — OpenClaw Plugin Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Starts the FreeRouter proxy server as a background service
|
|
5
|
-
* and registers it as a model provider.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { startServer, stopServer } from "./src/server.js";
|
|
9
|
-
|
|
10
|
-
export default {
|
|
11
|
-
id: "freerouter",
|
|
12
|
-
name: "FreeRouter",
|
|
13
|
-
|
|
14
|
-
async register(api: any) {
|
|
15
|
-
// Read plugin config
|
|
16
|
-
const pluginConfig = api.config?.plugins?.entries?.freerouter?.config ?? {};
|
|
17
|
-
const port = pluginConfig.port ?? 18800;
|
|
18
|
-
const host = pluginConfig.host ?? "127.0.0.1";
|
|
19
|
-
|
|
20
|
-
api.log?.info?.(`[FreeRouter] Starting proxy on ${host}:${port}...`);
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// Start the proxy server
|
|
24
|
-
await startServer({
|
|
25
|
-
port,
|
|
26
|
-
host,
|
|
27
|
-
pluginConfig,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
api.log?.info?.(`[FreeRouter] Proxy running on http://${host}:${port}`);
|
|
31
|
-
|
|
32
|
-
// Register as a provider if the API supports it
|
|
33
|
-
if (api.registerProvider) {
|
|
34
|
-
api.registerProvider({
|
|
35
|
-
id: "freerouter",
|
|
36
|
-
name: "FreeRouter",
|
|
37
|
-
baseUrl: `http://${host}:${port}/v1`,
|
|
38
|
-
api: "openai",
|
|
39
|
-
models: [
|
|
40
|
-
{ id: "freerouter/auto", name: "FreeRouter Auto", description: "Auto-routes to best model" },
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
api.log?.info?.("[FreeRouter] Registered as provider 'freerouter'");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Register shutdown handler
|
|
47
|
-
if (api.onShutdown) {
|
|
48
|
-
api.onShutdown(async () => {
|
|
49
|
-
api.log?.info?.("[FreeRouter] Shutting down proxy...");
|
|
50
|
-
await stopServer();
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
} catch (err: any) {
|
|
54
|
-
api.log?.error?.(`[FreeRouter] Failed to start: ${err.message}`);
|
|
55
|
-
throw err;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Re-export router for programmatic use
|
|
61
|
-
export { route, DEFAULT_ROUTING_CONFIG } from "./src/router/index.js";
|
|
62
|
-
export { startServer, stopServer } from "./src/server.js";
|
|
63
|
-
export type { RoutingDecision, Tier, RoutingConfig } from "./src/router/types.js";
|
package/src/auth.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClawRouter Auth — loads API keys from OpenClaw auth-profiles.json
|
|
3
|
-
* Zero-dep, reads from ~/.openclaw/agents/main/agent/auth-profiles.json
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
7
|
-
import { getConfig } from "./config.js";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { logger } from "./logger.js";
|
|
11
|
-
|
|
12
|
-
export type ProviderAuth = {
|
|
13
|
-
provider: string;
|
|
14
|
-
profileName: string;
|
|
15
|
-
token?: string; // Anthropic OAuth token
|
|
16
|
-
apiKey?: string; // API key (Kimi, OpenAI)
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type AuthProfilesFile = {
|
|
20
|
-
version: number;
|
|
21
|
-
profiles: Record<string, {
|
|
22
|
-
type: "token" | "api_key";
|
|
23
|
-
provider: string;
|
|
24
|
-
token?: string;
|
|
25
|
-
key?: string;
|
|
26
|
-
}>;
|
|
27
|
-
lastGood?: Record<string, string>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let authCache: Map<string, ProviderAuth> | null = null;
|
|
31
|
-
|
|
32
|
-
function loadAuthProfiles(): Map<string, ProviderAuth> {
|
|
33
|
-
// Get path from config, fall back to default
|
|
34
|
-
const cfg = getConfig();
|
|
35
|
-
const authCfg = cfg.auth;
|
|
36
|
-
const defaultAuth = authCfg[authCfg.default] as { type?: string; profilesPath?: string } | undefined;
|
|
37
|
-
let filePath: string;
|
|
38
|
-
if (defaultAuth?.profilesPath) {
|
|
39
|
-
const p = defaultAuth.profilesPath;
|
|
40
|
-
filePath = p.startsWith("~/") ? join(homedir(), p.slice(2)) : p;
|
|
41
|
-
} else {
|
|
42
|
-
filePath = join(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
46
|
-
const data: AuthProfilesFile = JSON.parse(raw);
|
|
47
|
-
const map = new Map<string, ProviderAuth>();
|
|
48
|
-
|
|
49
|
-
// Build a map of provider → best profile (prefer lastGood)
|
|
50
|
-
const lastGood = data.lastGood ?? {};
|
|
51
|
-
|
|
52
|
-
for (const [name, profile] of Object.entries(data.profiles)) {
|
|
53
|
-
const provider = profile.provider;
|
|
54
|
-
const existing = map.get(provider);
|
|
55
|
-
|
|
56
|
-
// Prefer lastGood profile
|
|
57
|
-
const isLastGood = lastGood[provider] === name;
|
|
58
|
-
if (existing && !isLastGood) continue;
|
|
59
|
-
|
|
60
|
-
map.set(provider, {
|
|
61
|
-
provider,
|
|
62
|
-
profileName: name,
|
|
63
|
-
token: profile.type === "token" ? profile.token : undefined,
|
|
64
|
-
apiKey: profile.type === "api_key" ? profile.key : undefined,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
logger.info(`Loaded auth for providers: ${[...map.keys()].join(", ")}`);
|
|
69
|
-
return map;
|
|
70
|
-
} catch (err) {
|
|
71
|
-
logger.error("Failed to load auth-profiles.json:", err);
|
|
72
|
-
return new Map();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function getAuth(provider: string): ProviderAuth | undefined {
|
|
77
|
-
// Check env var auth first (per-provider config override)
|
|
78
|
-
const envAuth = getEnvAuth(provider);
|
|
79
|
-
if (envAuth) return envAuth;
|
|
80
|
-
|
|
81
|
-
// Fall back to auth-profiles.json
|
|
82
|
-
if (!authCache) {
|
|
83
|
-
authCache = loadAuthProfiles();
|
|
84
|
-
}
|
|
85
|
-
return authCache.get(provider);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get auth from environment variable (for providers with auth.type=env in config).
|
|
92
|
-
*/
|
|
93
|
-
function getEnvAuth(provider: string): ProviderAuth | undefined {
|
|
94
|
-
const cfg = getConfig();
|
|
95
|
-
const providerCfg = cfg.providers[provider];
|
|
96
|
-
if (!providerCfg?.auth || providerCfg.auth.type !== "env") return undefined;
|
|
97
|
-
const envKey = providerCfg.auth.key;
|
|
98
|
-
if (!envKey) return undefined;
|
|
99
|
-
const value = process.env[envKey];
|
|
100
|
-
if (!value) return undefined;
|
|
101
|
-
return {
|
|
102
|
-
provider,
|
|
103
|
-
profileName: envKey,
|
|
104
|
-
apiKey: value,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function reloadAuth(): void {
|
|
109
|
-
authCache = null;
|
|
110
|
-
logger.info("Auth cache cleared, will reload on next access");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get the authorization header value for a provider.
|
|
115
|
-
*/
|
|
116
|
-
export function getAuthHeader(provider: string): string | undefined {
|
|
117
|
-
const auth = getAuth(provider);
|
|
118
|
-
if (!auth) return undefined;
|
|
119
|
-
|
|
120
|
-
if (auth.token) {
|
|
121
|
-
// Anthropic uses x-api-key header, not Authorization
|
|
122
|
-
return auth.token;
|
|
123
|
-
}
|
|
124
|
-
if (auth.apiKey) {
|
|
125
|
-
return auth.apiKey;
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FreeRouter Config — OpenClaw Plugin Edition
|
|
3
|
-
*
|
|
4
|
-
* Reads config from plugin API (plugins.entries.freerouter.config)
|
|
5
|
-
* instead of freerouter.config.json. Falls back to built-in defaults.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { homedir } from "node:os";
|
|
11
|
-
import { logger } from "./logger.js";
|
|
12
|
-
|
|
13
|
-
// ═══ Config Types ═══
|
|
14
|
-
|
|
15
|
-
export type AuthConfig = {
|
|
16
|
-
type: "openclaw" | "env" | "file" | "keychain";
|
|
17
|
-
key?: string;
|
|
18
|
-
profilesPath?: string;
|
|
19
|
-
filePath?: string;
|
|
20
|
-
service?: string;
|
|
21
|
-
account?: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type ProviderConfigEntry = {
|
|
25
|
-
baseUrl: string;
|
|
26
|
-
api: "anthropic" | "openai";
|
|
27
|
-
headers?: Record<string, string>;
|
|
28
|
-
auth?: AuthConfig;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type TierMapping = {
|
|
32
|
-
primary: string;
|
|
33
|
-
fallback: string[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type ThinkingConfig = {
|
|
37
|
-
adaptive?: string[];
|
|
38
|
-
enabled?: { models: string[]; budget: number };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export type FreeRouterConfig = {
|
|
42
|
-
port: number;
|
|
43
|
-
host: string;
|
|
44
|
-
providers: Record<string, ProviderConfigEntry>;
|
|
45
|
-
tiers: Record<string, TierMapping>;
|
|
46
|
-
agenticTiers?: Record<string, TierMapping>;
|
|
47
|
-
tierBoundaries?: {
|
|
48
|
-
simpleMedium: number;
|
|
49
|
-
mediumComplex: number;
|
|
50
|
-
complexReasoning: number;
|
|
51
|
-
};
|
|
52
|
-
thinking?: ThinkingConfig;
|
|
53
|
-
auth: {
|
|
54
|
-
default: string;
|
|
55
|
-
[strategy: string]: unknown;
|
|
56
|
-
};
|
|
57
|
-
scoring?: Record<string, unknown>;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// ═══ Defaults ═══
|
|
61
|
-
|
|
62
|
-
const DEFAULT_CONFIG: FreeRouterConfig = {
|
|
63
|
-
port: 18800,
|
|
64
|
-
host: "127.0.0.1",
|
|
65
|
-
providers: {
|
|
66
|
-
anthropic: {
|
|
67
|
-
baseUrl: "https://api.anthropic.com",
|
|
68
|
-
api: "anthropic",
|
|
69
|
-
},
|
|
70
|
-
"kimi-coding": {
|
|
71
|
-
baseUrl: "https://api.kimi.com/coding/v1",
|
|
72
|
-
api: "openai",
|
|
73
|
-
headers: { "User-Agent": "KimiCLI/0.77" },
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
tiers: {
|
|
77
|
-
SIMPLE: { primary: "kimi-coding/kimi-for-coding", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
78
|
-
MEDIUM: { primary: "anthropic/claude-sonnet-4-5", fallback: ["anthropic/claude-opus-4-6"] },
|
|
79
|
-
COMPLEX: { primary: "anthropic/claude-opus-4-6", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
80
|
-
REASONING: { primary: "anthropic/claude-opus-4-6", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
81
|
-
},
|
|
82
|
-
agenticTiers: {
|
|
83
|
-
SIMPLE: { primary: "kimi-coding/kimi-for-coding", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
84
|
-
MEDIUM: { primary: "anthropic/claude-sonnet-4-5", fallback: ["anthropic/claude-opus-4-6"] },
|
|
85
|
-
COMPLEX: { primary: "anthropic/claude-opus-4-6", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
86
|
-
REASONING: { primary: "anthropic/claude-opus-4-6", fallback: ["anthropic/claude-haiku-4-5"] },
|
|
87
|
-
},
|
|
88
|
-
thinking: {
|
|
89
|
-
adaptive: ["claude-opus-4-6", "claude-opus-4.6"],
|
|
90
|
-
enabled: { models: ["claude-sonnet-4-5"], budget: 4096 },
|
|
91
|
-
},
|
|
92
|
-
auth: {
|
|
93
|
-
default: "openclaw",
|
|
94
|
-
openclaw: {
|
|
95
|
-
type: "openclaw",
|
|
96
|
-
profilesPath: "~/.openclaw/agents/main/agent/auth-profiles.json",
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// ═══ Singleton ═══
|
|
102
|
-
|
|
103
|
-
let _config: FreeRouterConfig | null = null;
|
|
104
|
-
let _configSource: string = "defaults";
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Deep-merge source into target (source wins). Arrays are replaced, not merged.
|
|
108
|
-
*/
|
|
109
|
-
function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
|
110
|
-
const result = { ...target };
|
|
111
|
-
for (const key of Object.keys(source)) {
|
|
112
|
-
const sv = source[key];
|
|
113
|
-
const tv = target[key];
|
|
114
|
-
if (sv && typeof sv === "object" && !Array.isArray(sv) && tv && typeof tv === "object" && !Array.isArray(tv)) {
|
|
115
|
-
result[key] = deepMerge(tv as Record<string, unknown>, sv as Record<string, unknown>);
|
|
116
|
-
} else {
|
|
117
|
-
result[key] = sv;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Load config from plugin config object (passed from OpenClaw plugin API).
|
|
125
|
-
* Merges with defaults — user only needs to specify overrides.
|
|
126
|
-
*/
|
|
127
|
-
export function loadConfigFromPlugin(pluginConfig: Record<string, unknown>): FreeRouterConfig {
|
|
128
|
-
_config = deepMerge(
|
|
129
|
-
DEFAULT_CONFIG as unknown as Record<string, unknown>,
|
|
130
|
-
pluginConfig as unknown as Record<string, unknown>,
|
|
131
|
-
) as unknown as FreeRouterConfig;
|
|
132
|
-
_configSource = "plugin";
|
|
133
|
-
logger.info("Loaded config from OpenClaw plugin");
|
|
134
|
-
logger.info(` Providers: ${Object.keys(_config.providers).join(", ")}`);
|
|
135
|
-
logger.info(` Tiers: ${Object.keys(_config.tiers).join(", ")}`);
|
|
136
|
-
return _config;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Load config from file (standalone fallback).
|
|
141
|
-
*/
|
|
142
|
-
export function loadConfig(): FreeRouterConfig {
|
|
143
|
-
// Try file-based config as fallback
|
|
144
|
-
const paths = [
|
|
145
|
-
process.env.FREEROUTER_CONFIG,
|
|
146
|
-
join(process.cwd(), "freerouter.config.json"),
|
|
147
|
-
join(homedir(), ".config", "freerouter", "config.json"),
|
|
148
|
-
].filter(Boolean) as string[];
|
|
149
|
-
|
|
150
|
-
for (const p of paths) {
|
|
151
|
-
if (existsSync(p)) {
|
|
152
|
-
try {
|
|
153
|
-
const raw = readFileSync(p, "utf-8");
|
|
154
|
-
const fileConfig = JSON.parse(raw) as Partial<FreeRouterConfig>;
|
|
155
|
-
_config = deepMerge(DEFAULT_CONFIG as unknown as Record<string, unknown>, fileConfig as unknown as Record<string, unknown>) as unknown as FreeRouterConfig;
|
|
156
|
-
_configSource = p;
|
|
157
|
-
logger.info(`Loaded config from ${p}`);
|
|
158
|
-
return _config;
|
|
159
|
-
} catch (err) {
|
|
160
|
-
logger.error(`Failed to load config from ${p}:`, err);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
logger.info("No config file found, using built-in defaults");
|
|
166
|
-
_config = { ...DEFAULT_CONFIG };
|
|
167
|
-
_configSource = "defaults";
|
|
168
|
-
return _config;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function reloadConfig(): FreeRouterConfig {
|
|
172
|
-
_config = null;
|
|
173
|
-
return loadConfig();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function getConfig(): FreeRouterConfig {
|
|
177
|
-
if (!_config) return loadConfig();
|
|
178
|
-
return _config;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function getConfigPath(): string | null {
|
|
182
|
-
return _configSource === "defaults" ? null : _configSource;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function getSanitizedConfig(): Record<string, unknown> {
|
|
186
|
-
const cfg = getConfig();
|
|
187
|
-
const sanitized = JSON.parse(JSON.stringify(cfg));
|
|
188
|
-
if (sanitized.auth) {
|
|
189
|
-
for (const [key, val] of Object.entries(sanitized.auth)) {
|
|
190
|
-
if (key === "default") continue;
|
|
191
|
-
if (val && typeof val === "object" && (val as any).profilesPath) {
|
|
192
|
-
(val as any).profilesPath = "***";
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
for (const prov of Object.values(sanitized.providers ?? {})) {
|
|
197
|
-
if ((prov as any).auth?.key) (prov as any).auth.key = "***";
|
|
198
|
-
}
|
|
199
|
-
return sanitized;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function toInternalApiType(api: "anthropic" | "openai"): "anthropic-messages" | "openai-completions" {
|
|
203
|
-
return api === "anthropic" ? "anthropic-messages" : "openai-completions";
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function supportsAdaptiveThinking(modelId: string): boolean {
|
|
207
|
-
const cfg = getConfig();
|
|
208
|
-
const patterns = cfg.thinking?.adaptive ?? ["claude-opus-4-6", "claude-opus-4.6"];
|
|
209
|
-
return patterns.some(p => modelId.includes(p));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function getThinkingBudget(modelId: string): number | null {
|
|
213
|
-
const cfg = getConfig();
|
|
214
|
-
const enabled = cfg.thinking?.enabled;
|
|
215
|
-
if (!enabled) return null;
|
|
216
|
-
if (enabled.models.some(m => modelId.includes(m))) return enabled.budget;
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export { DEFAULT_CONFIG };
|
package/src/logger.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClawRouter Logger — minimal, zero-dep
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;
|
|
6
|
-
type Level = keyof typeof LEVELS;
|
|
7
|
-
|
|
8
|
-
let currentLevel: Level = "info";
|
|
9
|
-
|
|
10
|
-
export function setLogLevel(level: Level) {
|
|
11
|
-
currentLevel = level;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function log(level: Level, ...args: unknown[]) {
|
|
15
|
-
if (LEVELS[level] < LEVELS[currentLevel]) return;
|
|
16
|
-
const ts = new Date().toISOString().slice(11, 23);
|
|
17
|
-
const prefix = `[${ts}] ${level.toUpperCase().padEnd(5)}`;
|
|
18
|
-
if (level === "error") {
|
|
19
|
-
console.error(prefix, ...args);
|
|
20
|
-
} else if (level === "warn") {
|
|
21
|
-
console.warn(prefix, ...args);
|
|
22
|
-
} else {
|
|
23
|
-
console.log(prefix, ...args);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const logger = {
|
|
28
|
-
debug: (...args: unknown[]) => log("debug", ...args),
|
|
29
|
-
info: (...args: unknown[]) => log("info", ...args),
|
|
30
|
-
warn: (...args: unknown[]) => log("warn", ...args),
|
|
31
|
-
error: (...args: unknown[]) => log("error", ...args),
|
|
32
|
-
};
|