clawmux 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +0 -13
  2. package/dist/cli.cjs +3768 -112
  3. package/dist/index.cjs +235 -34
  4. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1,4 +1,27 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __toCommonJS = (from) => {
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
+ if (entry)
11
+ return entry;
12
+ entry = __defProp({}, "__esModule", { value: true });
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ __moduleCache.set(from, entry);
22
+ return entry;
23
+ };
24
+ var __moduleCache;
2
25
  var __returnValue = (v) => v;
3
26
  function __exportSetter(name, newValue) {
4
27
  this[name] = __returnValue.bind(null, newValue);
@@ -77,6 +100,13 @@ async function writeWebResponse(res, response) {
77
100
  }
78
101
  }
79
102
 
103
+ // src/index.ts
104
+ var exports_src = {};
105
+ __export(exports_src, {
106
+ bootstrap: () => bootstrap
107
+ });
108
+ module.exports = __toCommonJS(exports_src);
109
+
80
110
  // src/proxy/router.ts
81
111
  var VERSION = "0.1.0";
82
112
  function jsonResponse(body, status = 200) {
@@ -579,14 +609,7 @@ function getAuthProfilesPath(agentId) {
579
609
  const id = agentId ?? "main";
580
610
  return import_node_path2.join(getHomeDir(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
581
611
  }
582
- async function readAuthProfiles(agentId, profilesPath) {
583
- const path = profilesPath ?? getAuthProfilesPath(agentId);
584
- let text;
585
- try {
586
- text = await import_promises4.readFile(path, "utf-8");
587
- } catch {
588
- return [];
589
- }
612
+ function parseAuthProfilesFile(text) {
590
613
  try {
591
614
  const parsed = JSON.parse(text);
592
615
  if (Array.isArray(parsed))
@@ -594,7 +617,7 @@ async function readAuthProfiles(agentId, profilesPath) {
594
617
  if (parsed && typeof parsed === "object" && parsed.profiles) {
595
618
  return Object.entries(parsed.profiles).map(([key, profile]) => ({
596
619
  provider: profile.provider ?? key.split(":")[0],
597
- apiKey: profile.access ?? profile.apiKey,
620
+ apiKey: profile.access ?? profile.apiKey ?? profile.key,
598
621
  token: profile.token
599
622
  })).filter((p) => {
600
623
  const token = p.apiKey ?? p.token;
@@ -617,6 +640,33 @@ async function readAuthProfiles(agentId, profilesPath) {
617
640
  throw new Error(`Failed to parse auth-profiles.json: ${message}`);
618
641
  }
619
642
  }
643
+ async function readAuthProfiles(_agentId, _profilesPath, agentsDirOverride) {
644
+ const agentsDir = agentsDirOverride ?? import_node_path2.join(getHomeDir(), ".openclaw", "agents");
645
+ let agentDirs;
646
+ try {
647
+ agentDirs = (await import_promises4.readdir(agentsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
648
+ } catch {
649
+ const path = getAuthProfilesPath(_agentId);
650
+ try {
651
+ return parseAuthProfilesFile(await import_promises4.readFile(path, "utf-8"));
652
+ } catch {
653
+ return [];
654
+ }
655
+ }
656
+ const ordered = ["main", ...agentDirs.filter((d) => d !== "main")];
657
+ const merged = new Map;
658
+ for (const agentId of ordered) {
659
+ const profilePath = import_node_path2.join(agentsDir, agentId, "agent", "auth-profiles.json");
660
+ try {
661
+ const text = await import_promises4.readFile(profilePath, "utf-8");
662
+ const profiles = parseAuthProfilesFile(text);
663
+ for (const p of profiles) {
664
+ merged.set(p.provider, p);
665
+ }
666
+ } catch {}
667
+ }
668
+ return Array.from(merged.values());
669
+ }
620
670
  function getProviderConfig(provider, config) {
621
671
  return config.models?.providers?.[provider];
622
672
  }
@@ -765,6 +815,38 @@ function getAdapter(apiType) {
765
815
  return adapters.get(apiType);
766
816
  }
767
817
 
818
+ // src/adapters/tool-converter.ts
819
+ function toOpenAITools(tools) {
820
+ if (!Array.isArray(tools) || tools.length === 0)
821
+ return;
822
+ return tools.map((tool) => {
823
+ if (tool.type === "function" && tool.function)
824
+ return tool;
825
+ return {
826
+ type: "function",
827
+ function: {
828
+ name: tool.name ?? "",
829
+ description: tool.description ?? "",
830
+ parameters: tool.input_schema ?? tool.parameters ?? { type: "object", properties: {} }
831
+ }
832
+ };
833
+ });
834
+ }
835
+ function toAnthropicTools(tools) {
836
+ if (!Array.isArray(tools) || tools.length === 0)
837
+ return;
838
+ return tools.map((tool) => {
839
+ if (tool.input_schema && !tool.type)
840
+ return tool;
841
+ const fn = tool.function ?? tool;
842
+ return {
843
+ name: fn.name ?? "",
844
+ description: fn.description ?? "",
845
+ input_schema: fn.parameters ?? { type: "object", properties: {} }
846
+ };
847
+ });
848
+ }
849
+
768
850
  // src/adapters/anthropic.ts
769
851
  class AnthropicAdapter {
770
852
  apiType = "anthropic-messages";
@@ -791,18 +873,42 @@ class AnthropicAdapter {
791
873
  "anthropic-version": "2023-06-01",
792
874
  "content-type": "application/json"
793
875
  };
794
- let bodyObj = {
795
- ...parsed.rawBody,
796
- model: targetModel
797
- };
798
876
  const isHaiku = targetModel.toLowerCase().includes("haiku");
799
877
  const hasThinking = "thinking" in parsed.rawBody;
800
878
  if (hasThinking && !isHaiku) {
801
879
  headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
802
880
  }
803
- if (isHaiku && "thinking" in bodyObj) {
804
- const { thinking: _, ...rest } = bodyObj;
805
- bodyObj = rest;
881
+ const ANTHROPIC_SAMPLING_KEYS = [
882
+ "temperature",
883
+ "top_p",
884
+ "top_k",
885
+ "stop_sequences",
886
+ "metadata",
887
+ "service_tier"
888
+ ];
889
+ const samplingParams = {};
890
+ for (const key of ANTHROPIC_SAMPLING_KEYS) {
891
+ if (key in parsed.rawBody) {
892
+ samplingParams[key] = parsed.rawBody[key];
893
+ }
894
+ }
895
+ let bodyObj = {
896
+ model: targetModel,
897
+ messages: parsed.messages,
898
+ stream: parsed.stream,
899
+ ...samplingParams
900
+ };
901
+ if (parsed.system !== undefined) {
902
+ bodyObj.system = parsed.system;
903
+ }
904
+ if (parsed.maxTokens !== undefined) {
905
+ bodyObj.max_tokens = parsed.maxTokens;
906
+ }
907
+ if (parsed.rawBody.tools) {
908
+ bodyObj.tools = toAnthropicTools(parsed.rawBody.tools);
909
+ }
910
+ if (!isHaiku && hasThinking) {
911
+ bodyObj.thinking = parsed.rawBody.thinking;
806
912
  }
807
913
  return {
808
914
  url,
@@ -1069,11 +1175,43 @@ class OpenAICompletionsAdapter {
1069
1175
  return parseOpenAIBody(body);
1070
1176
  }
1071
1177
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1072
- const { rawBody } = parsed;
1178
+ const messages = [];
1179
+ if (parsed.system !== undefined) {
1180
+ messages.push({ role: "system", content: parsed.system });
1181
+ }
1182
+ messages.push(...parsed.messages);
1183
+ const OPENAI_SAMPLING_KEYS = [
1184
+ "temperature",
1185
+ "top_p",
1186
+ "frequency_penalty",
1187
+ "presence_penalty",
1188
+ "logprobs",
1189
+ "top_logprobs",
1190
+ "seed",
1191
+ "stop",
1192
+ "n",
1193
+ "logit_bias",
1194
+ "response_format",
1195
+ "reasoning_effort"
1196
+ ];
1197
+ const samplingParams = {};
1198
+ for (const key of OPENAI_SAMPLING_KEYS) {
1199
+ if (key in parsed.rawBody) {
1200
+ samplingParams[key] = parsed.rawBody[key];
1201
+ }
1202
+ }
1073
1203
  const upstreamBody = {
1074
- ...rawBody,
1075
- model: targetModel
1204
+ model: targetModel,
1205
+ messages,
1206
+ stream: parsed.stream,
1207
+ ...samplingParams
1076
1208
  };
1209
+ if (parsed.maxTokens !== undefined) {
1210
+ upstreamBody.max_tokens = parsed.maxTokens;
1211
+ }
1212
+ if (parsed.rawBody.tools) {
1213
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1214
+ }
1077
1215
  return {
1078
1216
  url: /\/v\d+\/?$/.test(baseUrl) ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : `${baseUrl}/v1/chat/completions`,
1079
1217
  method: "POST",
@@ -1100,8 +1238,11 @@ class OpenAICompletionsAdapter {
1100
1238
  if (Array.isArray(choices) && choices.length > 0) {
1101
1239
  const choice = choices[0];
1102
1240
  const message = choice.message;
1103
- if (message && typeof message.content === "string") {
1104
- content = message.content;
1241
+ if (message) {
1242
+ const messageText = message.content ?? message.reasoning_content;
1243
+ if (typeof messageText === "string") {
1244
+ content = messageText;
1245
+ }
1105
1246
  }
1106
1247
  if (typeof choice.finish_reason === "string") {
1107
1248
  stopReason = choice.finish_reason;
@@ -1171,16 +1312,17 @@ class OpenAICompletionsAdapter {
1171
1312
  const choice = choices[0];
1172
1313
  const delta = choice.delta;
1173
1314
  const finishReason = choice.finish_reason;
1174
- if (delta?.role === "assistant" && !delta.content) {
1315
+ const textContent = delta?.content ?? delta?.reasoning_content;
1316
+ if (delta?.role === "assistant" && textContent == null) {
1175
1317
  events.push({
1176
1318
  type: "message_start",
1177
1319
  id: String(data.id ?? ""),
1178
1320
  model: String(data.model ?? "")
1179
1321
  });
1180
- } else if (typeof delta?.content === "string") {
1322
+ } else if (typeof textContent === "string") {
1181
1323
  events.push({
1182
1324
  type: "content_delta",
1183
- text: delta.content,
1325
+ text: textContent,
1184
1326
  index: typeof choice.index === "number" ? choice.index : 0
1185
1327
  });
1186
1328
  }
@@ -1212,7 +1354,7 @@ class OpenAICompletionsAdapter {
1212
1354
  choices: [
1213
1355
  {
1214
1356
  index: 0,
1215
- delta: { role: "assistant", content: "" },
1357
+ delta: { role: "assistant" },
1216
1358
  finish_reason: null
1217
1359
  }
1218
1360
  ]
@@ -1272,11 +1414,40 @@ class OpenAIResponsesAdapter {
1272
1414
  return parseOpenAIBody(body);
1273
1415
  }
1274
1416
  buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
1275
- const { rawBody } = parsed;
1417
+ const input = [];
1418
+ if (parsed.system !== undefined) {
1419
+ input.push({ role: "system", content: parsed.system });
1420
+ }
1421
+ input.push(...parsed.messages);
1422
+ const OPENAI_RESPONSES_SAMPLING_KEYS = [
1423
+ "temperature",
1424
+ "top_p",
1425
+ "truncation",
1426
+ "reasoning",
1427
+ "reasoning_effort",
1428
+ "text",
1429
+ "metadata",
1430
+ "store",
1431
+ "include"
1432
+ ];
1433
+ const samplingParams = {};
1434
+ for (const key of OPENAI_RESPONSES_SAMPLING_KEYS) {
1435
+ if (key in parsed.rawBody) {
1436
+ samplingParams[key] = parsed.rawBody[key];
1437
+ }
1438
+ }
1276
1439
  const upstreamBody = {
1277
- ...rawBody,
1278
- model: targetModel
1440
+ model: targetModel,
1441
+ input,
1442
+ stream: parsed.stream,
1443
+ ...samplingParams
1279
1444
  };
1445
+ if (parsed.maxTokens !== undefined) {
1446
+ upstreamBody.max_output_tokens = parsed.maxTokens;
1447
+ }
1448
+ if (parsed.rawBody.tools) {
1449
+ upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
1450
+ }
1280
1451
  return {
1281
1452
  url: `${baseUrl}/v1/responses`,
1282
1453
  method: "POST",
@@ -2285,6 +2456,9 @@ class OpenAICodexAdapter {
2285
2456
  }
2286
2457
  delete upstreamBody.max_tokens;
2287
2458
  delete upstreamBody.max_output_tokens;
2459
+ if (upstreamBody.tools) {
2460
+ upstreamBody.tools = toOpenAITools(upstreamBody.tools);
2461
+ }
2288
2462
  return {
2289
2463
  url: `${baseUrl}/codex/responses`,
2290
2464
  method: "POST",
@@ -2941,6 +3115,7 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2941
3115
  return new TransformStream;
2942
3116
  }
2943
3117
  let buffer = "";
3118
+ let messageStarted = false;
2944
3119
  return new TransformStream({
2945
3120
  transform(chunk, controller) {
2946
3121
  if (!sourceAdapter.parseStreamChunk || !targetAdapter.buildStreamChunk) {
@@ -2958,6 +3133,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2958
3133
  continue;
2959
3134
  const events = sourceAdapter.parseStreamChunk(frame);
2960
3135
  for (const event of events) {
3136
+ if (event.type === "message_start") {
3137
+ messageStarted = true;
3138
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3139
+ messageStarted = true;
3140
+ const synthetic = targetAdapter.buildStreamChunk({
3141
+ type: "message_start",
3142
+ id: "",
3143
+ model: ""
3144
+ });
3145
+ if (synthetic)
3146
+ controller.enqueue(encoder.encode(synthetic));
3147
+ }
2961
3148
  const translated = targetAdapter.buildStreamChunk(event);
2962
3149
  controller.enqueue(encoder.encode(translated));
2963
3150
  }
@@ -2967,6 +3154,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
2967
3154
  if (buffer.trim() !== "" && sourceAdapter.parseStreamChunk && targetAdapter.buildStreamChunk) {
2968
3155
  const events = sourceAdapter.parseStreamChunk(buffer);
2969
3156
  for (const event of events) {
3157
+ if (event.type === "message_start") {
3158
+ messageStarted = true;
3159
+ } else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
3160
+ messageStarted = true;
3161
+ const synthetic = targetAdapter.buildStreamChunk({
3162
+ type: "message_start",
3163
+ id: "",
3164
+ model: ""
3165
+ });
3166
+ if (synthetic)
3167
+ controller.enqueue(encoder.encode(synthetic));
3168
+ }
2970
3169
  const translated = targetAdapter.buildStreamChunk(event);
2971
3170
  controller.enqueue(encoder.encode(translated));
2972
3171
  }
@@ -3658,7 +3857,7 @@ function setupPipelineRoutes(config, openclawConfig, authProfiles, compressionMi
3658
3857
 
3659
3858
  // src/index.ts
3660
3859
  var import_node_path4 = require("node:path");
3661
- async function bootstrap() {
3860
+ async function bootstrap(portOverride) {
3662
3861
  const configPath = process.env.CLAWMUX_CONFIG ? import_node_path4.resolve(process.env.CLAWMUX_CONFIG) : import_node_path4.resolve(process.cwd(), "clawmux.json");
3663
3862
  const result = await loadConfig(configPath);
3664
3863
  if (!result.valid) {
@@ -3673,7 +3872,7 @@ async function bootstrap() {
3673
3872
  const piAiCatalog = await loadPiAiCatalog();
3674
3873
  const compressionMiddleware = createResolvedCompressionMiddleware(config, openclawConfig, piAiCatalog);
3675
3874
  setupPipelineRoutes(config, openclawConfig, authProfiles, compressionMiddleware);
3676
- const port = parseInt(process.env.CLAWMUX_PORT ?? "3456", 10);
3875
+ const port = portOverride ?? parseInt(process.env.CLAWMUX_PORT ?? "3456", 10);
3677
3876
  const server = createServer({ port, host: "127.0.0.1" });
3678
3877
  server.start();
3679
3878
  console.log(`[clawmux] Proxy server running on http://127.0.0.1:${port}`);
@@ -3685,7 +3884,9 @@ async function bootstrap() {
3685
3884
  });
3686
3885
  watcher.start();
3687
3886
  }
3688
- bootstrap().catch((err) => {
3689
- console.error(`[clawmux] Fatal: ${err.message}`);
3690
- process.exit(1);
3691
- });
3887
+ if (require.main == module || typeof Bun !== "undefined" && Bun.main === "/home/runner/work/ClawMux/ClawMux/src/index.ts") {
3888
+ bootstrap().catch((err) => {
3889
+ console.error(`[clawmux] Fatal: ${err.message}`);
3890
+ process.exit(1);
3891
+ });
3892
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmux",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Smart model routing + context compression proxy for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {