clawmux 0.3.1 → 0.3.3

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
@@ -14,28 +14,22 @@ Smart model routing + context compression proxy for OpenClaw.
14
14
 
15
15
  ## Installation
16
16
 
17
- ### For Humans
17
+ Copy and paste this into your OpenClaw agent:
18
18
 
19
- Copy and paste this prompt to your LLM agent (OpenClaw, Claude Code, etc.):
20
-
21
- ```
22
- Read the following guide and execute every step without asking for confirmation:
23
- https://raw.githubusercontent.com/HyeokjaeLee/ClawMux/refs/heads/main/docs/guide/installation.md
19
+ ```bash
20
+ Install and configure oh-my-opencode by following the instructions here:
21
+ curl -s https://raw.githubusercontent.com/HyeokjaeLee/ClawMux/refs/heads/main/docs/guide/installation.md
24
22
  ```
25
23
 
26
- Or install manually, but seriously, let an agent do it. Agents don't fat-finger configs.
27
-
28
- ### For OpenClaw Agents
24
+ ## Configuration
29
25
 
30
- Fetch the installation guide and follow it:
26
+ ClawMux stores its config at `~/.openclaw/clawmux.json` (next to `openclaw.json`). `clawmux init` creates it automatically. You can also copy `clawmux.example.json` as a starting point:
31
27
 
32
28
  ```bash
33
- curl -s https://raw.githubusercontent.com/HyeokjaeLee/ClawMux/refs/heads/main/docs/guide/installation.md
29
+ cp clawmux.example.json ~/.openclaw/clawmux.json
34
30
  ```
35
31
 
36
- ## Configuration
37
-
38
- Copy `clawmux.example.json` to `clawmux.json` and adjust as needed:
32
+ Adjust as needed:
39
33
 
40
34
  ```jsonc
41
35
  {
@@ -59,7 +53,7 @@ Copy `clawmux.example.json` to `clawmux.json` and adjust as needed:
59
53
  }
60
54
  ```
61
55
 
62
- Config is watched for changes. Edit `clawmux.json` while the proxy is running and it reloads automatically.
56
+ Config is watched for changes. Edit `~/.openclaw/clawmux.json` while the proxy is running and it reloads automatically. Override the path with `CLAWMUX_CONFIG=/path/to/clawmux.json`.
63
57
 
64
58
  ### Cross-Provider Routing
65
59
 
@@ -112,7 +106,7 @@ OpenClaw → ClawMux Proxy (localhost:3456) → Upstream Provider(s)
112
106
 
113
107
  ClawMux resolves each model's context window using this priority chain:
114
108
 
115
- 1. **clawmux.json** `routing.contextWindows` — explicit per-model override
109
+ 1. **~/.openclaw/clawmux.json** `routing.contextWindows` — explicit per-model override
116
110
  2. **openclaw.json** `models.providers[provider].models[].contextWindow` — user config
117
111
  3. **OpenClaw built-in catalog** — pi-ai model database (812+ models)
118
112
  4. **Default: 200,000 tokens**
package/dist/cli.cjs CHANGED
@@ -80,7 +80,7 @@ async function writeWebResponse(res, response) {
80
80
 
81
81
  // src/cli.ts
82
82
  var import_promises6 = require("node:fs/promises");
83
- var import_node_path6 = require("node:path");
83
+ var import_node_path5 = require("node:path");
84
84
  var import_node_child_process = require("node:child_process");
85
85
  var import_node_os = require("node:os");
86
86
 
@@ -424,12 +424,17 @@ function validateConfig(raw) {
424
424
  }
425
425
 
426
426
  // src/config/loader.ts
427
- function findConfigPath() {
427
+ function getHomeDir() {
428
+ return process.env.HOME ?? "/root";
429
+ }
430
+ function getClawmuxConfigPath() {
428
431
  const envPath = process.env.CLAWMUX_CONFIG;
429
- if (envPath) {
432
+ if (envPath)
430
433
  return import_node_path.resolve(envPath);
431
- }
432
- return import_node_path.resolve(process.cwd(), "clawmux.json");
434
+ return import_node_path.join(getHomeDir(), ".openclaw", "clawmux.json");
435
+ }
436
+ function findConfigPath() {
437
+ return getClawmuxConfigPath();
433
438
  }
434
439
  async function loadConfig(configPath) {
435
440
  const filePath = configPath ?? findConfigPath();
@@ -553,7 +558,7 @@ function resolveEnvVar(value) {
553
558
  }
554
559
  return value;
555
560
  }
556
- function getHomeDir() {
561
+ function getHomeDir2() {
557
562
  return process.env.HOME ?? "/root";
558
563
  }
559
564
  function getConfigPath(override) {
@@ -561,7 +566,7 @@ function getConfigPath(override) {
561
566
  return override;
562
567
  if (process.env.OPENCLAW_CONFIG_PATH)
563
568
  return process.env.OPENCLAW_CONFIG_PATH;
564
- return import_node_path2.join(getHomeDir(), ".openclaw", "openclaw.json");
569
+ return import_node_path2.join(getHomeDir2(), ".openclaw", "openclaw.json");
565
570
  }
566
571
  async function readOpenClawConfig(configPath) {
567
572
  const path = getConfigPath(configPath);
@@ -584,16 +589,9 @@ async function readOpenClawConfig(configPath) {
584
589
  }
585
590
  function getAuthProfilesPath(agentId) {
586
591
  const id = agentId ?? "main";
587
- return import_node_path2.join(getHomeDir(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
592
+ return import_node_path2.join(getHomeDir2(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
588
593
  }
589
- async function readAuthProfiles(agentId, profilesPath) {
590
- const path = profilesPath ?? getAuthProfilesPath(agentId);
591
- let text;
592
- try {
593
- text = await import_promises4.readFile(path, "utf-8");
594
- } catch {
595
- return [];
596
- }
594
+ function parseAuthProfilesFile(text) {
597
595
  try {
598
596
  const parsed = JSON.parse(text);
599
597
  if (Array.isArray(parsed))
@@ -601,7 +599,7 @@ async function readAuthProfiles(agentId, profilesPath) {
601
599
  if (parsed && typeof parsed === "object" && parsed.profiles) {
602
600
  return Object.entries(parsed.profiles).map(([key, profile]) => ({
603
601
  provider: profile.provider ?? key.split(":")[0],
604
- apiKey: profile.access ?? profile.apiKey,
602
+ apiKey: profile.access ?? profile.apiKey ?? profile.key,
605
603
  token: profile.token
606
604
  })).filter((p) => {
607
605
  const token = p.apiKey ?? p.token;
@@ -624,6 +622,33 @@ async function readAuthProfiles(agentId, profilesPath) {
624
622
  throw new Error(`Failed to parse auth-profiles.json: ${message}`);
625
623
  }
626
624
  }
625
+ async function readAuthProfiles(_agentId, _profilesPath, agentsDirOverride) {
626
+ const agentsDir = agentsDirOverride ?? import_node_path2.join(getHomeDir2(), ".openclaw", "agents");
627
+ let agentDirs;
628
+ try {
629
+ agentDirs = (await import_promises4.readdir(agentsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
630
+ } catch {
631
+ const path = getAuthProfilesPath(_agentId);
632
+ try {
633
+ return parseAuthProfilesFile(await import_promises4.readFile(path, "utf-8"));
634
+ } catch {
635
+ return [];
636
+ }
637
+ }
638
+ const ordered = ["main", ...agentDirs.filter((d) => d !== "main")];
639
+ const merged = new Map;
640
+ for (const agentId of ordered) {
641
+ const profilePath = import_node_path2.join(agentsDir, agentId, "agent", "auth-profiles.json");
642
+ try {
643
+ const text = await import_promises4.readFile(profilePath, "utf-8");
644
+ const profiles = parseAuthProfilesFile(text);
645
+ for (const p of profiles) {
646
+ merged.set(p.provider, p);
647
+ }
648
+ } catch {}
649
+ }
650
+ return Array.from(merged.values());
651
+ }
627
652
  function getProviderConfig(provider, config) {
628
653
  return config.models?.providers?.[provider];
629
654
  }
@@ -772,6 +797,38 @@ function getAdapter(apiType) {
772
797
  return adapters.get(apiType);
773
798
  }
774
799
 
800
+ // src/adapters/tool-converter.ts
801
+ function toOpenAITools(tools) {
802
+ if (!Array.isArray(tools) || tools.length === 0)
803
+ return;
804
+ return tools.map((tool) => {
805
+ if (tool.type === "function" && tool.function)
806
+ return tool;
807
+ return {
808
+ type: "function",
809
+ function: {
810
+ name: tool.name ?? "",
811
+ description: tool.description ?? "",
812
+ parameters: tool.input_schema ?? tool.parameters ?? { type: "object", properties: {} }
813
+ }
814
+ };
815
+ });
816
+ }
817
+ function toAnthropicTools(tools) {
818
+ if (!Array.isArray(tools) || tools.length === 0)
819
+ return;
820
+ return tools.map((tool) => {
821
+ if (tool.input_schema && !tool.type)
822
+ return tool;
823
+ const fn = tool.function ?? tool;
824
+ return {
825
+ name: fn.name ?? "",
826
+ description: fn.description ?? "",
827
+ input_schema: fn.parameters ?? { type: "object", properties: {} }
828
+ };
829
+ });
830
+ }
831
+
775
832
  // src/adapters/anthropic.ts
776
833
  class AnthropicAdapter {
777
834
  apiType = "anthropic-messages";
@@ -798,18 +855,42 @@ class AnthropicAdapter {
798
855
  "anthropic-version": "2023-06-01",
799
856
  "content-type": "application/json"
800
857
  };
801
- let bodyObj = {
802
- ...parsed.rawBody,
803
- model: targetModel
804
- };
805
858
  const isHaiku = targetModel.toLowerCase().includes("haiku");
806
859
  const hasThinking = "thinking" in parsed.rawBody;
807
860
  if (hasThinking && !isHaiku) {
808
861
  headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
809
862
  }
810
- if (isHaiku && "thinking" in bodyObj) {
811
- const { thinking: _, ...rest } = bodyObj;
812
- bodyObj = rest;
863
+ const ANTHROPIC_SAMPLING_KEYS = [
864
+ "temperature",
865
+ "top_p",
866
+ "top_k",
867
+ "stop_sequences",
868
+ "metadata",
869
+ "service_tier"
870
+ ];
871
+ const samplingParams = {};
872
+ for (const key of ANTHROPIC_SAMPLING_KEYS) {
873
+ if (key in parsed.rawBody) {
874
+ samplingParams[key] = parsed.rawBody[key];
875
+ }
876
+ }
877
+ let bodyObj = {
878
+ model: targetModel,
879
+ messages: parsed.messages,
880
+ stream: parsed.stream,
881
+ ...samplingParams
882
+ };
883
+ if (parsed.system !== undefined) {
884
+ bodyObj.system = parsed.system;
885
+ }
886
+ if (parsed.maxTokens !== undefined) {
887
+ bodyObj.max_tokens = parsed.maxTokens;
888
+ }
889
+ if (parsed.rawBody.tools) {
890
+ bodyObj.tools = toAnthropicTools(parsed.rawBody.tools);
891
+ }
892
+ if (!isHaiku && hasThinking) {
893
+ bodyObj.thinking = parsed.rawBody.thinking;
813
894
  }
814
895
  return {
815
896
  url,
@@ -1076,11 +1157,43 @@ class OpenAICompletionsAdapter {
1076
1157
  return parseOpenAIBody(body);
1077
1158
  }
1078
1159
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1079
- const { rawBody } = parsed;
1160
+ const messages = [];
1161
+ if (parsed.system !== undefined) {
1162
+ messages.push({ role: "system", content: parsed.system });
1163
+ }
1164
+ messages.push(...parsed.messages);
1165
+ const OPENAI_SAMPLING_KEYS = [
1166
+ "temperature",
1167
+ "top_p",
1168
+ "frequency_penalty",
1169
+ "presence_penalty",
1170
+ "logprobs",
1171
+ "top_logprobs",
1172
+ "seed",
1173
+ "stop",
1174
+ "n",
1175
+ "logit_bias",
1176
+ "response_format",
1177
+ "reasoning_effort"
1178
+ ];
1179
+ const samplingParams = {};
1180
+ for (const key of OPENAI_SAMPLING_KEYS) {
1181
+ if (key in parsed.rawBody) {
1182
+ samplingParams[key] = parsed.rawBody[key];
1183
+ }
1184
+ }
1080
1185
  const upstreamBody = {
1081
- ...rawBody,
1082
- model: targetModel
1186
+ model: targetModel,
1187
+ messages,
1188
+ stream: parsed.stream,
1189
+ ...samplingParams
1083
1190
  };
1191
+ if (parsed.maxTokens !== undefined) {
1192
+ upstreamBody.max_tokens = parsed.maxTokens;
1193
+ }
1194
+ if (parsed.rawBody.tools) {
1195
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1196
+ }
1084
1197
  return {
1085
1198
  url: /\/v\d+\/?$/.test(baseUrl) ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : `${baseUrl}/v1/chat/completions`,
1086
1199
  method: "POST",
@@ -1107,8 +1220,11 @@ class OpenAICompletionsAdapter {
1107
1220
  if (Array.isArray(choices) && choices.length > 0) {
1108
1221
  const choice = choices[0];
1109
1222
  const message = choice.message;
1110
- if (message && typeof message.content === "string") {
1111
- content = message.content;
1223
+ if (message) {
1224
+ const messageText = message.content ?? message.reasoning_content;
1225
+ if (typeof messageText === "string") {
1226
+ content = messageText;
1227
+ }
1112
1228
  }
1113
1229
  if (typeof choice.finish_reason === "string") {
1114
1230
  stopReason = choice.finish_reason;
@@ -1178,16 +1294,17 @@ class OpenAICompletionsAdapter {
1178
1294
  const choice = choices[0];
1179
1295
  const delta = choice.delta;
1180
1296
  const finishReason = choice.finish_reason;
1181
- if (delta?.role === "assistant" && !delta.content) {
1297
+ const textContent = delta?.content ?? delta?.reasoning_content;
1298
+ if (delta?.role === "assistant" && textContent == null) {
1182
1299
  events.push({
1183
1300
  type: "message_start",
1184
1301
  id: String(data.id ?? ""),
1185
1302
  model: String(data.model ?? "")
1186
1303
  });
1187
- } else if (typeof delta?.content === "string") {
1304
+ } else if (typeof textContent === "string") {
1188
1305
  events.push({
1189
1306
  type: "content_delta",
1190
- text: delta.content,
1307
+ text: textContent,
1191
1308
  index: typeof choice.index === "number" ? choice.index : 0
1192
1309
  });
1193
1310
  }
@@ -1219,7 +1336,7 @@ class OpenAICompletionsAdapter {
1219
1336
  choices: [
1220
1337
  {
1221
1338
  index: 0,
1222
- delta: { role: "assistant", content: "" },
1339
+ delta: { role: "assistant" },
1223
1340
  finish_reason: null
1224
1341
  }
1225
1342
  ]
@@ -1279,11 +1396,40 @@ class OpenAIResponsesAdapter {
1279
1396
  return parseOpenAIBody(body);
1280
1397
  }
1281
1398
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1282
- const { rawBody } = parsed;
1399
+ const input = [];
1400
+ if (parsed.system !== undefined) {
1401
+ input.push({ role: "system", content: parsed.system });
1402
+ }
1403
+ input.push(...parsed.messages);
1404
+ const OPENAI_RESPONSES_SAMPLING_KEYS = [
1405
+ "temperature",
1406
+ "top_p",
1407
+ "truncation",
1408
+ "reasoning",
1409
+ "reasoning_effort",
1410
+ "text",
1411
+ "metadata",
1412
+ "store",
1413
+ "include"
1414
+ ];
1415
+ const samplingParams = {};
1416
+ for (const key of OPENAI_RESPONSES_SAMPLING_KEYS) {
1417
+ if (key in parsed.rawBody) {
1418
+ samplingParams[key] = parsed.rawBody[key];
1419
+ }
1420
+ }
1283
1421
  const upstreamBody = {
1284
- ...rawBody,
1285
- model: targetModel
1422
+ model: targetModel,
1423
+ input,
1424
+ stream: parsed.stream,
1425
+ ...samplingParams
1286
1426
  };
1427
+ if (parsed.maxTokens !== undefined) {
1428
+ upstreamBody.max_output_tokens = parsed.maxTokens;
1429
+ }
1430
+ if (parsed.rawBody.tools) {
1431
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1432
+ }
1287
1433
  return {
1288
1434
  url: `${baseUrl}/v1/responses`,
1289
1435
  method: "POST",
@@ -2292,6 +2438,9 @@ class OpenAICodexAdapter {
2292
2438
  }
2293
2439
  delete upstreamBody.max_tokens;
2294
2440
  delete upstreamBody.max_output_tokens;
2441
+ if (upstreamBody.tools) {
2442
+ upstreamBody.tools = toOpenAITools(upstreamBody.tools);
2443
+ }
2295
2444
  return {
2296
2445
  url: `${baseUrl}/codex/responses`,
2297
2446
  method: "POST",
@@ -2948,6 +3097,7 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2948
3097
  return new TransformStream;
2949
3098
  }
2950
3099
  let buffer = "";
3100
+ let messageStarted = false;
2951
3101
  return new TransformStream({
2952
3102
  transform(chunk, controller) {
2953
3103
  if (!sourceAdapter.parseStreamChunk || !targetAdapter.buildStreamChunk) {
@@ -2965,6 +3115,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2965
3115
  continue;
2966
3116
  const events = sourceAdapter.parseStreamChunk(frame);
2967
3117
  for (const event of events) {
3118
+ if (event.type === "message_start") {
3119
+ messageStarted = true;
3120
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3121
+ messageStarted = true;
3122
+ const synthetic = targetAdapter.buildStreamChunk({
3123
+ type: "message_start",
3124
+ id: "",
3125
+ model: ""
3126
+ });
3127
+ if (synthetic)
3128
+ controller.enqueue(encoder.encode(synthetic));
3129
+ }
2968
3130
  const translated = targetAdapter.buildStreamChunk(event);
2969
3131
  controller.enqueue(encoder.encode(translated));
2970
3132
  }
@@ -2974,6 +3136,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2974
3136
  if (buffer.trim() !== "" && sourceAdapter.parseStreamChunk && targetAdapter.buildStreamChunk) {
2975
3137
  const events = sourceAdapter.parseStreamChunk(buffer);
2976
3138
  for (const event of events) {
3139
+ if (event.type === "message_start") {
3140
+ messageStarted = true;
3141
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3142
+ messageStarted = true;
3143
+ const synthetic = targetAdapter.buildStreamChunk({
3144
+ type: "message_start",
3145
+ id: "",
3146
+ model: ""
3147
+ });
3148
+ if (synthetic)
3149
+ controller.enqueue(encoder.encode(synthetic));
3150
+ }
2977
3151
  const translated = targetAdapter.buildStreamChunk(event);
2978
3152
  controller.enqueue(encoder.encode(translated));
2979
3153
  }
@@ -3664,9 +3838,8 @@ function setupPipelineRoutes(config, openclawConfig, authProfiles, compressionMi
3664
3838
  }
3665
3839
 
3666
3840
  // src/index.ts
3667
- var import_node_path4 = require("node:path");
3668
3841
  async function bootstrap(portOverride) {
3669
- const configPath = process.env.CLAWMUX_CONFIG ? import_node_path4.resolve(process.env.CLAWMUX_CONFIG) : import_node_path4.resolve(process.cwd(), "clawmux.json");
3842
+ const configPath = getClawmuxConfigPath();
3670
3843
  const result = await loadConfig(configPath);
3671
3844
  if (!result.valid) {
3672
3845
  console.error("[clawmux] Config errors:");
@@ -3701,8 +3874,8 @@ if (typeof Bun !== "undefined" && Bun.main === "/home/runner/work/ClawMux/ClawMu
3701
3874
 
3702
3875
  // src/utils/logger.ts
3703
3876
  var import_node_fs2 = require("node:fs");
3704
- var import_node_path5 = require("node:path");
3705
- var LOG_DIR = import_node_path5.join(process.env.HOME ?? "/root", ".openclaw", "clawmux");
3877
+ var import_node_path4 = require("node:path");
3878
+ var LOG_DIR = import_node_path4.join(process.env.HOME ?? "/root", ".openclaw", "clawmux");
3706
3879
  var MAX_DAYS = 7;
3707
3880
  var fileStream = null;
3708
3881
  var currentDate = "";
@@ -3711,7 +3884,7 @@ function todayString() {
3711
3884
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
3712
3885
  }
3713
3886
  function logPath(date) {
3714
- return import_node_path5.join(LOG_DIR, `${date}.log`);
3887
+ return import_node_path4.join(LOG_DIR, `${date}.log`);
3715
3888
  }
3716
3889
  function rotateIfNeeded() {
3717
3890
  const today = todayString();
@@ -3729,7 +3902,7 @@ function purgeOldLogs() {
3729
3902
  const files = import_node_fs2.readdirSync(LOG_DIR).filter((f) => f.endsWith(".log")).sort();
3730
3903
  while (files.length > MAX_DAYS) {
3731
3904
  const oldest = files.shift();
3732
- import_node_fs2.unlinkSync(import_node_path5.join(LOG_DIR, oldest));
3905
+ import_node_fs2.unlinkSync(import_node_path4.join(LOG_DIR, oldest));
3733
3906
  }
3734
3907
  } catch (_) {}
3735
3908
  }
@@ -3769,7 +3942,7 @@ function getLogDir() {
3769
3942
  }
3770
3943
 
3771
3944
  // src/cli.ts
3772
- var VERSION2 = process.env.npm_package_version ?? "0.3.1";
3945
+ var VERSION2 = process.env.npm_package_version ?? "0.3.3";
3773
3946
  var SERVICE_NAME = "clawmux";
3774
3947
  var HELP = `Usage: clawmux <command>
3775
3948
 
@@ -3791,7 +3964,17 @@ Environment:
3791
3964
  CLAWMUX_PORT Server port override
3792
3965
  OPENCLAW_CONFIG_PATH Path to openclaw.json`;
3793
3966
  var PROVIDER_KEY = "clawmux";
3794
- var PROVIDER_API = "anthropic-messages";
3967
+ var PROVIDER_API_FALLBACK = "openai-responses";
3968
+ function resolveProviderApi(mediumModel, openclawProviders) {
3969
+ const providerName = mediumModel.split("/")[0];
3970
+ if (!providerName)
3971
+ return PROVIDER_API_FALLBACK;
3972
+ const providerConfig = openclawProviders[providerName];
3973
+ const api = providerConfig?.["api"];
3974
+ if (typeof api === "string" && api.length > 0)
3975
+ return api;
3976
+ return PROVIDER_API_FALLBACK;
3977
+ }
3795
3978
  async function fileExistsLocal(path) {
3796
3979
  try {
3797
3980
  await import_promises6.access(path);
@@ -3819,13 +4002,13 @@ function resolveClawmuxBin() {
3819
4002
  return detectPackageManager() === "bunx" ? "bunx clawmux" : "npx clawmux";
3820
4003
  }
3821
4004
  }
3822
- function getHomeDir2() {
4005
+ function getHomeDir3() {
3823
4006
  return process.env.HOME ?? "/root";
3824
4007
  }
3825
- var SYSTEMD_DIR = import_node_path6.join(getHomeDir2(), ".config", "systemd", "user");
3826
- var SYSTEMD_PATH = import_node_path6.join(SYSTEMD_DIR, `${SERVICE_NAME}.service`);
3827
- var LAUNCHD_DIR = import_node_path6.join(getHomeDir2(), "Library", "LaunchAgents");
3828
- var LAUNCHD_PATH = import_node_path6.join(LAUNCHD_DIR, `com.${SERVICE_NAME}.plist`);
4008
+ var SYSTEMD_DIR = import_node_path5.join(getHomeDir3(), ".config", "systemd", "user");
4009
+ var SYSTEMD_PATH = import_node_path5.join(SYSTEMD_DIR, `${SERVICE_NAME}.service`);
4010
+ var LAUNCHD_DIR = import_node_path5.join(getHomeDir3(), "Library", "LaunchAgents");
4011
+ var LAUNCHD_PATH = import_node_path5.join(LAUNCHD_DIR, `com.${SERVICE_NAME}.plist`);
3829
4012
  function buildSystemdUnit(bin, port, workDir) {
3830
4013
  return `[Unit]
3831
4014
  Description=ClawMux - Smart model routing proxy
@@ -3844,7 +4027,7 @@ WantedBy=default.target
3844
4027
  `;
3845
4028
  }
3846
4029
  function buildLaunchdPlist(bin, port, workDir) {
3847
- const logDir = import_node_path6.join(getHomeDir2(), ".openclaw", "clawmux");
4030
+ const logDir = import_node_path5.join(getHomeDir3(), ".openclaw", "clawmux");
3848
4031
  return `<?xml version="1.0" encoding="UTF-8"?>
3849
4032
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3850
4033
  <plist version="1.0">
@@ -3899,7 +4082,7 @@ async function installService(port, workDir) {
3899
4082
  }
3900
4083
  } else if (os === "darwin") {
3901
4084
  await import_promises6.mkdir(LAUNCHD_DIR, { recursive: true });
3902
- const logDir = import_node_path6.join(getHomeDir2(), ".openclaw", "clawmux");
4085
+ const logDir = import_node_path5.join(getHomeDir3(), ".openclaw", "clawmux");
3903
4086
  await import_promises6.mkdir(logDir, { recursive: true });
3904
4087
  await import_promises6.writeFile(LAUNCHD_PATH, buildLaunchdPlist(bin, port, workDir));
3905
4088
  try {
@@ -4048,8 +4231,8 @@ async function update() {
4048
4231
  async function init() {
4049
4232
  const args = process.argv.slice(2);
4050
4233
  const noService = args.includes("--no-service");
4051
- const homeDir = getHomeDir2();
4052
- const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path6.join(homeDir, ".openclaw", "openclaw.json");
4234
+ const homeDir = getHomeDir3();
4235
+ const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path5.join(homeDir, ".openclaw", "openclaw.json");
4053
4236
  if (!await fileExistsLocal(openclawConfigPath)) {
4054
4237
  console.error(`[error] OpenClaw config not found at ${openclawConfigPath}`);
4055
4238
  console.error("Set OPENCLAW_CONFIG_PATH or ensure ~/.openclaw/openclaw.json exists");
@@ -4059,8 +4242,9 @@ async function init() {
4059
4242
  const backupPath = `${openclawConfigPath}.bak.${Date.now()}`;
4060
4243
  await import_promises6.copyFile(openclawConfigPath, backupPath);
4061
4244
  console.log(`[info] Backup created: ${backupPath}`);
4062
- const clawmuxJsonPath = import_node_path6.join(process.cwd(), "clawmux.json");
4063
- const examplePath = import_node_path6.join(process.cwd(), "clawmux.example.json");
4245
+ const clawmuxJsonPath = getClawmuxConfigPath();
4246
+ const examplePath = import_node_path5.join(process.cwd(), "clawmux.example.json");
4247
+ console.log(`[info] Using ClawMux config: ${clawmuxJsonPath}`);
4064
4248
  if (!await fileExistsLocal(clawmuxJsonPath)) {
4065
4249
  if (await fileExistsLocal(examplePath)) {
4066
4250
  await import_promises6.copyFile(examplePath, clawmuxJsonPath);
@@ -4083,17 +4267,41 @@ async function init() {
4083
4267
  if (!models.providers)
4084
4268
  models.providers = {};
4085
4269
  const providers = models.providers;
4270
+ let providerApi = PROVIDER_API_FALLBACK;
4271
+ try {
4272
+ const clawmuxRaw = await import_promises6.readFile(clawmuxJsonPath, "utf-8");
4273
+ const clawmuxConfig = JSON.parse(clawmuxRaw);
4274
+ const routing = clawmuxConfig["routing"];
4275
+ const routingModels = routing?.["models"];
4276
+ const mediumModel = routingModels?.["MEDIUM"];
4277
+ if (typeof mediumModel === "string" && mediumModel.length > 0) {
4278
+ providerApi = resolveProviderApi(mediumModel, providers);
4279
+ console.log(`[info] MEDIUM model: ${mediumModel} → provider api: ${providerApi}`);
4280
+ } else {
4281
+ console.log(`[info] MEDIUM model not configured yet, using default api: ${providerApi}`);
4282
+ }
4283
+ } catch {
4284
+ console.log(`[info] clawmux.json not readable, using default api: ${providerApi}`);
4285
+ }
4086
4286
  if (providers[PROVIDER_KEY]) {
4087
- console.log(` skip ${PROVIDER_KEY} (already exists)`);
4287
+ const existing = providers[PROVIDER_KEY];
4288
+ if (existing["api"] !== providerApi) {
4289
+ existing["api"] = providerApi;
4290
+ await import_promises6.writeFile(openclawConfigPath, JSON.stringify(config, null, 2) + `
4291
+ `);
4292
+ console.log(` updated ${PROVIDER_KEY} provider api → ${providerApi}`);
4293
+ } else {
4294
+ console.log(` skip ${PROVIDER_KEY} (already exists, api=${providerApi})`);
4295
+ }
4088
4296
  } else {
4089
4297
  providers[PROVIDER_KEY] = {
4090
4298
  baseUrl: "http://localhost:3456",
4091
- api: PROVIDER_API,
4299
+ api: providerApi,
4092
4300
  models: [{ id: "auto", name: "ClawMux Auto Router" }]
4093
4301
  };
4094
4302
  await import_promises6.writeFile(openclawConfigPath, JSON.stringify(config, null, 2) + `
4095
4303
  `);
4096
- console.log(` added ${PROVIDER_KEY} provider to openclaw.json`);
4304
+ console.log(` added ${PROVIDER_KEY} provider to openclaw.json (api=${providerApi})`);
4097
4305
  }
4098
4306
  const port = process.env.CLAWMUX_PORT ?? "3456";
4099
4307
  if (!noService) {
@@ -4104,7 +4312,7 @@ async function init() {
4104
4312
  [info] ClawMux setup complete!`);
4105
4313
  console.log(`
4106
4314
  Next steps:`);
4107
- console.log(" 1. Edit clawmux.json to configure your models");
4315
+ console.log(` 1. Edit ${clawmuxJsonPath} to configure your models`);
4108
4316
  if (noService) {
4109
4317
  console.log(" 2. Run: clawmux start");
4110
4318
  } else {
@@ -4128,8 +4336,8 @@ async function start() {
4128
4336
  }
4129
4337
  async function uninstall() {
4130
4338
  await removeService();
4131
- const homeDir = getHomeDir2();
4132
- const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path6.join(homeDir, ".openclaw", "openclaw.json");
4339
+ const homeDir = getHomeDir3();
4340
+ const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path5.join(homeDir, ".openclaw", "openclaw.json");
4133
4341
  if (await fileExistsLocal(openclawConfigPath)) {
4134
4342
  const backupPath = `${openclawConfigPath}.bak.${Date.now()}`;
4135
4343
  await import_promises6.copyFile(openclawConfigPath, backupPath);
package/dist/index.cjs CHANGED
@@ -447,12 +447,17 @@ function validateConfig(raw) {
447
447
  }
448
448
 
449
449
  // src/config/loader.ts
450
- function findConfigPath() {
450
+ function getHomeDir() {
451
+ return process.env.HOME ?? "/root";
452
+ }
453
+ function getClawmuxConfigPath() {
451
454
  const envPath = process.env.CLAWMUX_CONFIG;
452
- if (envPath) {
455
+ if (envPath)
453
456
  return import_node_path.resolve(envPath);
454
- }
455
- return import_node_path.resolve(process.cwd(), "clawmux.json");
457
+ return import_node_path.join(getHomeDir(), ".openclaw", "clawmux.json");
458
+ }
459
+ function findConfigPath() {
460
+ return getClawmuxConfigPath();
456
461
  }
457
462
  async function loadConfig(configPath) {
458
463
  const filePath = configPath ?? findConfigPath();
@@ -576,7 +581,7 @@ function resolveEnvVar(value) {
576
581
  }
577
582
  return value;
578
583
  }
579
- function getHomeDir() {
584
+ function getHomeDir2() {
580
585
  return process.env.HOME ?? "/root";
581
586
  }
582
587
  function getConfigPath(override) {
@@ -584,7 +589,7 @@ function getConfigPath(override) {
584
589
  return override;
585
590
  if (process.env.OPENCLAW_CONFIG_PATH)
586
591
  return process.env.OPENCLAW_CONFIG_PATH;
587
- return import_node_path2.join(getHomeDir(), ".openclaw", "openclaw.json");
592
+ return import_node_path2.join(getHomeDir2(), ".openclaw", "openclaw.json");
588
593
  }
589
594
  async function readOpenClawConfig(configPath) {
590
595
  const path = getConfigPath(configPath);
@@ -607,16 +612,9 @@ async function readOpenClawConfig(configPath) {
607
612
  }
608
613
  function getAuthProfilesPath(agentId) {
609
614
  const id = agentId ?? "main";
610
- return import_node_path2.join(getHomeDir(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
615
+ return import_node_path2.join(getHomeDir2(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
611
616
  }
612
- async function readAuthProfiles(agentId, profilesPath) {
613
- const path = profilesPath ?? getAuthProfilesPath(agentId);
614
- let text;
615
- try {
616
- text = await import_promises4.readFile(path, "utf-8");
617
- } catch {
618
- return [];
619
- }
617
+ function parseAuthProfilesFile(text) {
620
618
  try {
621
619
  const parsed = JSON.parse(text);
622
620
  if (Array.isArray(parsed))
@@ -624,7 +622,7 @@ async function readAuthProfiles(agentId, profilesPath) {
624
622
  if (parsed && typeof parsed === "object" && parsed.profiles) {
625
623
  return Object.entries(parsed.profiles).map(([key, profile]) => ({
626
624
  provider: profile.provider ?? key.split(":")[0],
627
- apiKey: profile.access ?? profile.apiKey,
625
+ apiKey: profile.access ?? profile.apiKey ?? profile.key,
628
626
  token: profile.token
629
627
  })).filter((p) => {
630
628
  const token = p.apiKey ?? p.token;
@@ -647,6 +645,33 @@ async function readAuthProfiles(agentId, profilesPath) {
647
645
  throw new Error(`Failed to parse auth-profiles.json: ${message}`);
648
646
  }
649
647
  }
648
+ async function readAuthProfiles(_agentId, _profilesPath, agentsDirOverride) {
649
+ const agentsDir = agentsDirOverride ?? import_node_path2.join(getHomeDir2(), ".openclaw", "agents");
650
+ let agentDirs;
651
+ try {
652
+ agentDirs = (await import_promises4.readdir(agentsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
653
+ } catch {
654
+ const path = getAuthProfilesPath(_agentId);
655
+ try {
656
+ return parseAuthProfilesFile(await import_promises4.readFile(path, "utf-8"));
657
+ } catch {
658
+ return [];
659
+ }
660
+ }
661
+ const ordered = ["main", ...agentDirs.filter((d) => d !== "main")];
662
+ const merged = new Map;
663
+ for (const agentId of ordered) {
664
+ const profilePath = import_node_path2.join(agentsDir, agentId, "agent", "auth-profiles.json");
665
+ try {
666
+ const text = await import_promises4.readFile(profilePath, "utf-8");
667
+ const profiles = parseAuthProfilesFile(text);
668
+ for (const p of profiles) {
669
+ merged.set(p.provider, p);
670
+ }
671
+ } catch {}
672
+ }
673
+ return Array.from(merged.values());
674
+ }
650
675
  function getProviderConfig(provider, config) {
651
676
  return config.models?.providers?.[provider];
652
677
  }
@@ -795,6 +820,38 @@ function getAdapter(apiType) {
795
820
  return adapters.get(apiType);
796
821
  }
797
822
 
823
+ // src/adapters/tool-converter.ts
824
+ function toOpenAITools(tools) {
825
+ if (!Array.isArray(tools) || tools.length === 0)
826
+ return;
827
+ return tools.map((tool) => {
828
+ if (tool.type === "function" && tool.function)
829
+ return tool;
830
+ return {
831
+ type: "function",
832
+ function: {
833
+ name: tool.name ?? "",
834
+ description: tool.description ?? "",
835
+ parameters: tool.input_schema ?? tool.parameters ?? { type: "object", properties: {} }
836
+ }
837
+ };
838
+ });
839
+ }
840
+ function toAnthropicTools(tools) {
841
+ if (!Array.isArray(tools) || tools.length === 0)
842
+ return;
843
+ return tools.map((tool) => {
844
+ if (tool.input_schema && !tool.type)
845
+ return tool;
846
+ const fn = tool.function ?? tool;
847
+ return {
848
+ name: fn.name ?? "",
849
+ description: fn.description ?? "",
850
+ input_schema: fn.parameters ?? { type: "object", properties: {} }
851
+ };
852
+ });
853
+ }
854
+
798
855
  // src/adapters/anthropic.ts
799
856
  class AnthropicAdapter {
800
857
  apiType = "anthropic-messages";
@@ -821,18 +878,42 @@ class AnthropicAdapter {
821
878
  "anthropic-version": "2023-06-01",
822
879
  "content-type": "application/json"
823
880
  };
824
- let bodyObj = {
825
- ...parsed.rawBody,
826
- model: targetModel
827
- };
828
881
  const isHaiku = targetModel.toLowerCase().includes("haiku");
829
882
  const hasThinking = "thinking" in parsed.rawBody;
830
883
  if (hasThinking && !isHaiku) {
831
884
  headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
832
885
  }
833
- if (isHaiku && "thinking" in bodyObj) {
834
- const { thinking: _, ...rest } = bodyObj;
835
- bodyObj = rest;
886
+ const ANTHROPIC_SAMPLING_KEYS = [
887
+ "temperature",
888
+ "top_p",
889
+ "top_k",
890
+ "stop_sequences",
891
+ "metadata",
892
+ "service_tier"
893
+ ];
894
+ const samplingParams = {};
895
+ for (const key of ANTHROPIC_SAMPLING_KEYS) {
896
+ if (key in parsed.rawBody) {
897
+ samplingParams[key] = parsed.rawBody[key];
898
+ }
899
+ }
900
+ let bodyObj = {
901
+ model: targetModel,
902
+ messages: parsed.messages,
903
+ stream: parsed.stream,
904
+ ...samplingParams
905
+ };
906
+ if (parsed.system !== undefined) {
907
+ bodyObj.system = parsed.system;
908
+ }
909
+ if (parsed.maxTokens !== undefined) {
910
+ bodyObj.max_tokens = parsed.maxTokens;
911
+ }
912
+ if (parsed.rawBody.tools) {
913
+ bodyObj.tools = toAnthropicTools(parsed.rawBody.tools);
914
+ }
915
+ if (!isHaiku && hasThinking) {
916
+ bodyObj.thinking = parsed.rawBody.thinking;
836
917
  }
837
918
  return {
838
919
  url,
@@ -1099,11 +1180,43 @@ class OpenAICompletionsAdapter {
1099
1180
  return parseOpenAIBody(body);
1100
1181
  }
1101
1182
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1102
- const { rawBody } = parsed;
1183
+ const messages = [];
1184
+ if (parsed.system !== undefined) {
1185
+ messages.push({ role: "system", content: parsed.system });
1186
+ }
1187
+ messages.push(...parsed.messages);
1188
+ const OPENAI_SAMPLING_KEYS = [
1189
+ "temperature",
1190
+ "top_p",
1191
+ "frequency_penalty",
1192
+ "presence_penalty",
1193
+ "logprobs",
1194
+ "top_logprobs",
1195
+ "seed",
1196
+ "stop",
1197
+ "n",
1198
+ "logit_bias",
1199
+ "response_format",
1200
+ "reasoning_effort"
1201
+ ];
1202
+ const samplingParams = {};
1203
+ for (const key of OPENAI_SAMPLING_KEYS) {
1204
+ if (key in parsed.rawBody) {
1205
+ samplingParams[key] = parsed.rawBody[key];
1206
+ }
1207
+ }
1103
1208
  const upstreamBody = {
1104
- ...rawBody,
1105
- model: targetModel
1209
+ model: targetModel,
1210
+ messages,
1211
+ stream: parsed.stream,
1212
+ ...samplingParams
1106
1213
  };
1214
+ if (parsed.maxTokens !== undefined) {
1215
+ upstreamBody.max_tokens = parsed.maxTokens;
1216
+ }
1217
+ if (parsed.rawBody.tools) {
1218
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1219
+ }
1107
1220
  return {
1108
1221
  url: /\/v\d+\/?$/.test(baseUrl) ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : `${baseUrl}/v1/chat/completions`,
1109
1222
  method: "POST",
@@ -1130,8 +1243,11 @@ class OpenAICompletionsAdapter {
1130
1243
  if (Array.isArray(choices) && choices.length > 0) {
1131
1244
  const choice = choices[0];
1132
1245
  const message = choice.message;
1133
- if (message && typeof message.content === "string") {
1134
- content = message.content;
1246
+ if (message) {
1247
+ const messageText = message.content ?? message.reasoning_content;
1248
+ if (typeof messageText === "string") {
1249
+ content = messageText;
1250
+ }
1135
1251
  }
1136
1252
  if (typeof choice.finish_reason === "string") {
1137
1253
  stopReason = choice.finish_reason;
@@ -1201,16 +1317,17 @@ class OpenAICompletionsAdapter {
1201
1317
  const choice = choices[0];
1202
1318
  const delta = choice.delta;
1203
1319
  const finishReason = choice.finish_reason;
1204
- if (delta?.role === "assistant" && !delta.content) {
1320
+ const textContent = delta?.content ?? delta?.reasoning_content;
1321
+ if (delta?.role === "assistant" && textContent == null) {
1205
1322
  events.push({
1206
1323
  type: "message_start",
1207
1324
  id: String(data.id ?? ""),
1208
1325
  model: String(data.model ?? "")
1209
1326
  });
1210
- } else if (typeof delta?.content === "string") {
1327
+ } else if (typeof textContent === "string") {
1211
1328
  events.push({
1212
1329
  type: "content_delta",
1213
- text: delta.content,
1330
+ text: textContent,
1214
1331
  index: typeof choice.index === "number" ? choice.index : 0
1215
1332
  });
1216
1333
  }
@@ -1242,7 +1359,7 @@ class OpenAICompletionsAdapter {
1242
1359
  choices: [
1243
1360
  {
1244
1361
  index: 0,
1245
- delta: { role: "assistant", content: "" },
1362
+ delta: { role: "assistant" },
1246
1363
  finish_reason: null
1247
1364
  }
1248
1365
  ]
@@ -1302,11 +1419,40 @@ class OpenAIResponsesAdapter {
1302
1419
  return parseOpenAIBody(body);
1303
1420
  }
1304
1421
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1305
- const { rawBody } = parsed;
1422
+ const input = [];
1423
+ if (parsed.system !== undefined) {
1424
+ input.push({ role: "system", content: parsed.system });
1425
+ }
1426
+ input.push(...parsed.messages);
1427
+ const OPENAI_RESPONSES_SAMPLING_KEYS = [
1428
+ "temperature",
1429
+ "top_p",
1430
+ "truncation",
1431
+ "reasoning",
1432
+ "reasoning_effort",
1433
+ "text",
1434
+ "metadata",
1435
+ "store",
1436
+ "include"
1437
+ ];
1438
+ const samplingParams = {};
1439
+ for (const key of OPENAI_RESPONSES_SAMPLING_KEYS) {
1440
+ if (key in parsed.rawBody) {
1441
+ samplingParams[key] = parsed.rawBody[key];
1442
+ }
1443
+ }
1306
1444
  const upstreamBody = {
1307
- ...rawBody,
1308
- model: targetModel
1445
+ model: targetModel,
1446
+ input,
1447
+ stream: parsed.stream,
1448
+ ...samplingParams
1309
1449
  };
1450
+ if (parsed.maxTokens !== undefined) {
1451
+ upstreamBody.max_output_tokens = parsed.maxTokens;
1452
+ }
1453
+ if (parsed.rawBody.tools) {
1454
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1455
+ }
1310
1456
  return {
1311
1457
  url: `${baseUrl}/v1/responses`,
1312
1458
  method: "POST",
@@ -2315,6 +2461,9 @@ class OpenAICodexAdapter {
2315
2461
  }
2316
2462
  delete upstreamBody.max_tokens;
2317
2463
  delete upstreamBody.max_output_tokens;
2464
+ if (upstreamBody.tools) {
2465
+ upstreamBody.tools = toOpenAITools(upstreamBody.tools);
2466
+ }
2318
2467
  return {
2319
2468
  url: `${baseUrl}/codex/responses`,
2320
2469
  method: "POST",
@@ -2971,6 +3120,7 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2971
3120
  return new TransformStream;
2972
3121
  }
2973
3122
  let buffer = "";
3123
+ let messageStarted = false;
2974
3124
  return new TransformStream({
2975
3125
  transform(chunk, controller) {
2976
3126
  if (!sourceAdapter.parseStreamChunk || !targetAdapter.buildStreamChunk) {
@@ -2988,6 +3138,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2988
3138
  continue;
2989
3139
  const events = sourceAdapter.parseStreamChunk(frame);
2990
3140
  for (const event of events) {
3141
+ if (event.type === "message_start") {
3142
+ messageStarted = true;
3143
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3144
+ messageStarted = true;
3145
+ const synthetic = targetAdapter.buildStreamChunk({
3146
+ type: "message_start",
3147
+ id: "",
3148
+ model: ""
3149
+ });
3150
+ if (synthetic)
3151
+ controller.enqueue(encoder.encode(synthetic));
3152
+ }
2991
3153
  const translated = targetAdapter.buildStreamChunk(event);
2992
3154
  controller.enqueue(encoder.encode(translated));
2993
3155
  }
@@ -2997,6 +3159,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2997
3159
  if (buffer.trim() !== "" && sourceAdapter.parseStreamChunk && targetAdapter.buildStreamChunk) {
2998
3160
  const events = sourceAdapter.parseStreamChunk(buffer);
2999
3161
  for (const event of events) {
3162
+ if (event.type === "message_start") {
3163
+ messageStarted = true;
3164
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3165
+ messageStarted = true;
3166
+ const synthetic = targetAdapter.buildStreamChunk({
3167
+ type: "message_start",
3168
+ id: "",
3169
+ model: ""
3170
+ });
3171
+ if (synthetic)
3172
+ controller.enqueue(encoder.encode(synthetic));
3173
+ }
3000
3174
  const translated = targetAdapter.buildStreamChunk(event);
3001
3175
  controller.enqueue(encoder.encode(translated));
3002
3176
  }
@@ -3687,9 +3861,8 @@ function setupPipelineRoutes(config, openclawConfig, authProfiles, compressionMi
3687
3861
  }
3688
3862
 
3689
3863
  // src/index.ts
3690
- var import_node_path4 = require("node:path");
3691
3864
  async function bootstrap(portOverride) {
3692
- const configPath = process.env.CLAWMUX_CONFIG ? import_node_path4.resolve(process.env.CLAWMUX_CONFIG) : import_node_path4.resolve(process.cwd(), "clawmux.json");
3865
+ const configPath = getClawmuxConfigPath();
3693
3866
  const result = await loadConfig(configPath);
3694
3867
  if (!result.valid) {
3695
3868
  console.error("[clawmux] Config errors:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmux",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Smart model routing + context compression proxy for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {