clawmux 0.3.1 → 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.
- package/README.md +0 -13
- package/dist/cli.cjs +236 -33
- package/dist/index.cjs +197 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,19 +14,6 @@ Smart model routing + context compression proxy for OpenClaw.
|
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
|
-
### For Humans
|
|
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
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Or install manually, but seriously, let an agent do it. Agents don't fat-finger configs.
|
|
27
|
-
|
|
28
|
-
### For OpenClaw Agents
|
|
29
|
-
|
|
30
17
|
Fetch the installation guide and follow it:
|
|
31
18
|
|
|
32
19
|
```bash
|
package/dist/cli.cjs
CHANGED
|
@@ -586,14 +586,7 @@ function getAuthProfilesPath(agentId) {
|
|
|
586
586
|
const id = agentId ?? "main";
|
|
587
587
|
return import_node_path2.join(getHomeDir(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
|
|
588
588
|
}
|
|
589
|
-
|
|
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
|
-
}
|
|
589
|
+
function parseAuthProfilesFile(text) {
|
|
597
590
|
try {
|
|
598
591
|
const parsed = JSON.parse(text);
|
|
599
592
|
if (Array.isArray(parsed))
|
|
@@ -601,7 +594,7 @@ async function readAuthProfiles(agentId, profilesPath) {
|
|
|
601
594
|
if (parsed && typeof parsed === "object" && parsed.profiles) {
|
|
602
595
|
return Object.entries(parsed.profiles).map(([key, profile]) => ({
|
|
603
596
|
provider: profile.provider ?? key.split(":")[0],
|
|
604
|
-
apiKey: profile.access ?? profile.apiKey,
|
|
597
|
+
apiKey: profile.access ?? profile.apiKey ?? profile.key,
|
|
605
598
|
token: profile.token
|
|
606
599
|
})).filter((p) => {
|
|
607
600
|
const token = p.apiKey ?? p.token;
|
|
@@ -624,6 +617,33 @@ async function readAuthProfiles(agentId, profilesPath) {
|
|
|
624
617
|
throw new Error(`Failed to parse auth-profiles.json: ${message}`);
|
|
625
618
|
}
|
|
626
619
|
}
|
|
620
|
+
async function readAuthProfiles(_agentId, _profilesPath, agentsDirOverride) {
|
|
621
|
+
const agentsDir = agentsDirOverride ?? import_node_path2.join(getHomeDir(), ".openclaw", "agents");
|
|
622
|
+
let agentDirs;
|
|
623
|
+
try {
|
|
624
|
+
agentDirs = (await import_promises4.readdir(agentsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
625
|
+
} catch {
|
|
626
|
+
const path = getAuthProfilesPath(_agentId);
|
|
627
|
+
try {
|
|
628
|
+
return parseAuthProfilesFile(await import_promises4.readFile(path, "utf-8"));
|
|
629
|
+
} catch {
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const ordered = ["main", ...agentDirs.filter((d) => d !== "main")];
|
|
634
|
+
const merged = new Map;
|
|
635
|
+
for (const agentId of ordered) {
|
|
636
|
+
const profilePath = import_node_path2.join(agentsDir, agentId, "agent", "auth-profiles.json");
|
|
637
|
+
try {
|
|
638
|
+
const text = await import_promises4.readFile(profilePath, "utf-8");
|
|
639
|
+
const profiles = parseAuthProfilesFile(text);
|
|
640
|
+
for (const p of profiles) {
|
|
641
|
+
merged.set(p.provider, p);
|
|
642
|
+
}
|
|
643
|
+
} catch {}
|
|
644
|
+
}
|
|
645
|
+
return Array.from(merged.values());
|
|
646
|
+
}
|
|
627
647
|
function getProviderConfig(provider, config) {
|
|
628
648
|
return config.models?.providers?.[provider];
|
|
629
649
|
}
|
|
@@ -772,6 +792,38 @@ function getAdapter(apiType) {
|
|
|
772
792
|
return adapters.get(apiType);
|
|
773
793
|
}
|
|
774
794
|
|
|
795
|
+
// src/adapters/tool-converter.ts
|
|
796
|
+
function toOpenAITools(tools) {
|
|
797
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
798
|
+
return;
|
|
799
|
+
return tools.map((tool) => {
|
|
800
|
+
if (tool.type === "function" && tool.function)
|
|
801
|
+
return tool;
|
|
802
|
+
return {
|
|
803
|
+
type: "function",
|
|
804
|
+
function: {
|
|
805
|
+
name: tool.name ?? "",
|
|
806
|
+
description: tool.description ?? "",
|
|
807
|
+
parameters: tool.input_schema ?? tool.parameters ?? { type: "object", properties: {} }
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function toAnthropicTools(tools) {
|
|
813
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
814
|
+
return;
|
|
815
|
+
return tools.map((tool) => {
|
|
816
|
+
if (tool.input_schema && !tool.type)
|
|
817
|
+
return tool;
|
|
818
|
+
const fn = tool.function ?? tool;
|
|
819
|
+
return {
|
|
820
|
+
name: fn.name ?? "",
|
|
821
|
+
description: fn.description ?? "",
|
|
822
|
+
input_schema: fn.parameters ?? { type: "object", properties: {} }
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
775
827
|
// src/adapters/anthropic.ts
|
|
776
828
|
class AnthropicAdapter {
|
|
777
829
|
apiType = "anthropic-messages";
|
|
@@ -798,18 +850,42 @@ class AnthropicAdapter {
|
|
|
798
850
|
"anthropic-version": "2023-06-01",
|
|
799
851
|
"content-type": "application/json"
|
|
800
852
|
};
|
|
801
|
-
let bodyObj = {
|
|
802
|
-
...parsed.rawBody,
|
|
803
|
-
model: targetModel
|
|
804
|
-
};
|
|
805
853
|
const isHaiku = targetModel.toLowerCase().includes("haiku");
|
|
806
854
|
const hasThinking = "thinking" in parsed.rawBody;
|
|
807
855
|
if (hasThinking && !isHaiku) {
|
|
808
856
|
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
809
857
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
858
|
+
const ANTHROPIC_SAMPLING_KEYS = [
|
|
859
|
+
"temperature",
|
|
860
|
+
"top_p",
|
|
861
|
+
"top_k",
|
|
862
|
+
"stop_sequences",
|
|
863
|
+
"metadata",
|
|
864
|
+
"service_tier"
|
|
865
|
+
];
|
|
866
|
+
const samplingParams = {};
|
|
867
|
+
for (const key of ANTHROPIC_SAMPLING_KEYS) {
|
|
868
|
+
if (key in parsed.rawBody) {
|
|
869
|
+
samplingParams[key] = parsed.rawBody[key];
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
let bodyObj = {
|
|
873
|
+
model: targetModel,
|
|
874
|
+
messages: parsed.messages,
|
|
875
|
+
stream: parsed.stream,
|
|
876
|
+
...samplingParams
|
|
877
|
+
};
|
|
878
|
+
if (parsed.system !== undefined) {
|
|
879
|
+
bodyObj.system = parsed.system;
|
|
880
|
+
}
|
|
881
|
+
if (parsed.maxTokens !== undefined) {
|
|
882
|
+
bodyObj.max_tokens = parsed.maxTokens;
|
|
883
|
+
}
|
|
884
|
+
if (parsed.rawBody.tools) {
|
|
885
|
+
bodyObj.tools = toAnthropicTools(parsed.rawBody.tools);
|
|
886
|
+
}
|
|
887
|
+
if (!isHaiku && hasThinking) {
|
|
888
|
+
bodyObj.thinking = parsed.rawBody.thinking;
|
|
813
889
|
}
|
|
814
890
|
return {
|
|
815
891
|
url,
|
|
@@ -1076,11 +1152,43 @@ class OpenAICompletionsAdapter {
|
|
|
1076
1152
|
return parseOpenAIBody(body);
|
|
1077
1153
|
}
|
|
1078
1154
|
buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
|
|
1079
|
-
const
|
|
1155
|
+
const messages = [];
|
|
1156
|
+
if (parsed.system !== undefined) {
|
|
1157
|
+
messages.push({ role: "system", content: parsed.system });
|
|
1158
|
+
}
|
|
1159
|
+
messages.push(...parsed.messages);
|
|
1160
|
+
const OPENAI_SAMPLING_KEYS = [
|
|
1161
|
+
"temperature",
|
|
1162
|
+
"top_p",
|
|
1163
|
+
"frequency_penalty",
|
|
1164
|
+
"presence_penalty",
|
|
1165
|
+
"logprobs",
|
|
1166
|
+
"top_logprobs",
|
|
1167
|
+
"seed",
|
|
1168
|
+
"stop",
|
|
1169
|
+
"n",
|
|
1170
|
+
"logit_bias",
|
|
1171
|
+
"response_format",
|
|
1172
|
+
"reasoning_effort"
|
|
1173
|
+
];
|
|
1174
|
+
const samplingParams = {};
|
|
1175
|
+
for (const key of OPENAI_SAMPLING_KEYS) {
|
|
1176
|
+
if (key in parsed.rawBody) {
|
|
1177
|
+
samplingParams[key] = parsed.rawBody[key];
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1080
1180
|
const upstreamBody = {
|
|
1081
|
-
|
|
1082
|
-
|
|
1181
|
+
model: targetModel,
|
|
1182
|
+
messages,
|
|
1183
|
+
stream: parsed.stream,
|
|
1184
|
+
...samplingParams
|
|
1083
1185
|
};
|
|
1186
|
+
if (parsed.maxTokens !== undefined) {
|
|
1187
|
+
upstreamBody.max_tokens = parsed.maxTokens;
|
|
1188
|
+
}
|
|
1189
|
+
if (parsed.rawBody.tools) {
|
|
1190
|
+
upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
|
|
1191
|
+
}
|
|
1084
1192
|
return {
|
|
1085
1193
|
url: /\/v\d+\/?$/.test(baseUrl) ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : `${baseUrl}/v1/chat/completions`,
|
|
1086
1194
|
method: "POST",
|
|
@@ -1107,8 +1215,11 @@ class OpenAICompletionsAdapter {
|
|
|
1107
1215
|
if (Array.isArray(choices) && choices.length > 0) {
|
|
1108
1216
|
const choice = choices[0];
|
|
1109
1217
|
const message = choice.message;
|
|
1110
|
-
if (message
|
|
1111
|
-
|
|
1218
|
+
if (message) {
|
|
1219
|
+
const messageText = message.content ?? message.reasoning_content;
|
|
1220
|
+
if (typeof messageText === "string") {
|
|
1221
|
+
content = messageText;
|
|
1222
|
+
}
|
|
1112
1223
|
}
|
|
1113
1224
|
if (typeof choice.finish_reason === "string") {
|
|
1114
1225
|
stopReason = choice.finish_reason;
|
|
@@ -1178,16 +1289,17 @@ class OpenAICompletionsAdapter {
|
|
|
1178
1289
|
const choice = choices[0];
|
|
1179
1290
|
const delta = choice.delta;
|
|
1180
1291
|
const finishReason = choice.finish_reason;
|
|
1181
|
-
|
|
1292
|
+
const textContent = delta?.content ?? delta?.reasoning_content;
|
|
1293
|
+
if (delta?.role === "assistant" && textContent == null) {
|
|
1182
1294
|
events.push({
|
|
1183
1295
|
type: "message_start",
|
|
1184
1296
|
id: String(data.id ?? ""),
|
|
1185
1297
|
model: String(data.model ?? "")
|
|
1186
1298
|
});
|
|
1187
|
-
} else if (typeof
|
|
1299
|
+
} else if (typeof textContent === "string") {
|
|
1188
1300
|
events.push({
|
|
1189
1301
|
type: "content_delta",
|
|
1190
|
-
text:
|
|
1302
|
+
text: textContent,
|
|
1191
1303
|
index: typeof choice.index === "number" ? choice.index : 0
|
|
1192
1304
|
});
|
|
1193
1305
|
}
|
|
@@ -1219,7 +1331,7 @@ class OpenAICompletionsAdapter {
|
|
|
1219
1331
|
choices: [
|
|
1220
1332
|
{
|
|
1221
1333
|
index: 0,
|
|
1222
|
-
delta: { role: "assistant"
|
|
1334
|
+
delta: { role: "assistant" },
|
|
1223
1335
|
finish_reason: null
|
|
1224
1336
|
}
|
|
1225
1337
|
]
|
|
@@ -1279,11 +1391,40 @@ class OpenAIResponsesAdapter {
|
|
|
1279
1391
|
return parseOpenAIBody(body);
|
|
1280
1392
|
}
|
|
1281
1393
|
buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
|
|
1282
|
-
const
|
|
1394
|
+
const input = [];
|
|
1395
|
+
if (parsed.system !== undefined) {
|
|
1396
|
+
input.push({ role: "system", content: parsed.system });
|
|
1397
|
+
}
|
|
1398
|
+
input.push(...parsed.messages);
|
|
1399
|
+
const OPENAI_RESPONSES_SAMPLING_KEYS = [
|
|
1400
|
+
"temperature",
|
|
1401
|
+
"top_p",
|
|
1402
|
+
"truncation",
|
|
1403
|
+
"reasoning",
|
|
1404
|
+
"reasoning_effort",
|
|
1405
|
+
"text",
|
|
1406
|
+
"metadata",
|
|
1407
|
+
"store",
|
|
1408
|
+
"include"
|
|
1409
|
+
];
|
|
1410
|
+
const samplingParams = {};
|
|
1411
|
+
for (const key of OPENAI_RESPONSES_SAMPLING_KEYS) {
|
|
1412
|
+
if (key in parsed.rawBody) {
|
|
1413
|
+
samplingParams[key] = parsed.rawBody[key];
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1283
1416
|
const upstreamBody = {
|
|
1284
|
-
|
|
1285
|
-
|
|
1417
|
+
model: targetModel,
|
|
1418
|
+
input,
|
|
1419
|
+
stream: parsed.stream,
|
|
1420
|
+
...samplingParams
|
|
1286
1421
|
};
|
|
1422
|
+
if (parsed.maxTokens !== undefined) {
|
|
1423
|
+
upstreamBody.max_output_tokens = parsed.maxTokens;
|
|
1424
|
+
}
|
|
1425
|
+
if (parsed.rawBody.tools) {
|
|
1426
|
+
upstreamBody.tools = toOpenAITools(parsed.rawBody.tools);
|
|
1427
|
+
}
|
|
1287
1428
|
return {
|
|
1288
1429
|
url: `${baseUrl}/v1/responses`,
|
|
1289
1430
|
method: "POST",
|
|
@@ -2292,6 +2433,9 @@ class OpenAICodexAdapter {
|
|
|
2292
2433
|
}
|
|
2293
2434
|
delete upstreamBody.max_tokens;
|
|
2294
2435
|
delete upstreamBody.max_output_tokens;
|
|
2436
|
+
if (upstreamBody.tools) {
|
|
2437
|
+
upstreamBody.tools = toOpenAITools(upstreamBody.tools);
|
|
2438
|
+
}
|
|
2295
2439
|
return {
|
|
2296
2440
|
url: `${baseUrl}/codex/responses`,
|
|
2297
2441
|
method: "POST",
|
|
@@ -2948,6 +3092,7 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2948
3092
|
return new TransformStream;
|
|
2949
3093
|
}
|
|
2950
3094
|
let buffer = "";
|
|
3095
|
+
let messageStarted = false;
|
|
2951
3096
|
return new TransformStream({
|
|
2952
3097
|
transform(chunk, controller) {
|
|
2953
3098
|
if (!sourceAdapter.parseStreamChunk || !targetAdapter.buildStreamChunk) {
|
|
@@ -2965,6 +3110,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2965
3110
|
continue;
|
|
2966
3111
|
const events = sourceAdapter.parseStreamChunk(frame);
|
|
2967
3112
|
for (const event of events) {
|
|
3113
|
+
if (event.type === "message_start") {
|
|
3114
|
+
messageStarted = true;
|
|
3115
|
+
} else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
|
|
3116
|
+
messageStarted = true;
|
|
3117
|
+
const synthetic = targetAdapter.buildStreamChunk({
|
|
3118
|
+
type: "message_start",
|
|
3119
|
+
id: "",
|
|
3120
|
+
model: ""
|
|
3121
|
+
});
|
|
3122
|
+
if (synthetic)
|
|
3123
|
+
controller.enqueue(encoder.encode(synthetic));
|
|
3124
|
+
}
|
|
2968
3125
|
const translated = targetAdapter.buildStreamChunk(event);
|
|
2969
3126
|
controller.enqueue(encoder.encode(translated));
|
|
2970
3127
|
}
|
|
@@ -2974,6 +3131,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2974
3131
|
if (buffer.trim() !== "" && sourceAdapter.parseStreamChunk && targetAdapter.buildStreamChunk) {
|
|
2975
3132
|
const events = sourceAdapter.parseStreamChunk(buffer);
|
|
2976
3133
|
for (const event of events) {
|
|
3134
|
+
if (event.type === "message_start") {
|
|
3135
|
+
messageStarted = true;
|
|
3136
|
+
} else if (!messageStarted && (event.type === "content_delta" || event.type === "content_stop")) {
|
|
3137
|
+
messageStarted = true;
|
|
3138
|
+
const synthetic = targetAdapter.buildStreamChunk({
|
|
3139
|
+
type: "message_start",
|
|
3140
|
+
id: "",
|
|
3141
|
+
model: ""
|
|
3142
|
+
});
|
|
3143
|
+
if (synthetic)
|
|
3144
|
+
controller.enqueue(encoder.encode(synthetic));
|
|
3145
|
+
}
|
|
2977
3146
|
const translated = targetAdapter.buildStreamChunk(event);
|
|
2978
3147
|
controller.enqueue(encoder.encode(translated));
|
|
2979
3148
|
}
|
|
@@ -3769,7 +3938,7 @@ function getLogDir() {
|
|
|
3769
3938
|
}
|
|
3770
3939
|
|
|
3771
3940
|
// src/cli.ts
|
|
3772
|
-
var VERSION2 = process.env.npm_package_version ?? "0.3.
|
|
3941
|
+
var VERSION2 = process.env.npm_package_version ?? "0.3.2";
|
|
3773
3942
|
var SERVICE_NAME = "clawmux";
|
|
3774
3943
|
var HELP = `Usage: clawmux <command>
|
|
3775
3944
|
|
|
@@ -3791,7 +3960,17 @@ Environment:
|
|
|
3791
3960
|
CLAWMUX_PORT Server port override
|
|
3792
3961
|
OPENCLAW_CONFIG_PATH Path to openclaw.json`;
|
|
3793
3962
|
var PROVIDER_KEY = "clawmux";
|
|
3794
|
-
var
|
|
3963
|
+
var PROVIDER_API_FALLBACK = "openai-responses";
|
|
3964
|
+
function resolveProviderApi(mediumModel, openclawProviders) {
|
|
3965
|
+
const providerName = mediumModel.split("/")[0];
|
|
3966
|
+
if (!providerName)
|
|
3967
|
+
return PROVIDER_API_FALLBACK;
|
|
3968
|
+
const providerConfig = openclawProviders[providerName];
|
|
3969
|
+
const api = providerConfig?.["api"];
|
|
3970
|
+
if (typeof api === "string" && api.length > 0)
|
|
3971
|
+
return api;
|
|
3972
|
+
return PROVIDER_API_FALLBACK;
|
|
3973
|
+
}
|
|
3795
3974
|
async function fileExistsLocal(path) {
|
|
3796
3975
|
try {
|
|
3797
3976
|
await import_promises6.access(path);
|
|
@@ -4083,17 +4262,41 @@ async function init() {
|
|
|
4083
4262
|
if (!models.providers)
|
|
4084
4263
|
models.providers = {};
|
|
4085
4264
|
const providers = models.providers;
|
|
4265
|
+
let providerApi = PROVIDER_API_FALLBACK;
|
|
4266
|
+
try {
|
|
4267
|
+
const clawmuxRaw = await import_promises6.readFile(clawmuxJsonPath, "utf-8");
|
|
4268
|
+
const clawmuxConfig = JSON.parse(clawmuxRaw);
|
|
4269
|
+
const routing = clawmuxConfig["routing"];
|
|
4270
|
+
const routingModels = routing?.["models"];
|
|
4271
|
+
const mediumModel = routingModels?.["MEDIUM"];
|
|
4272
|
+
if (typeof mediumModel === "string" && mediumModel.length > 0) {
|
|
4273
|
+
providerApi = resolveProviderApi(mediumModel, providers);
|
|
4274
|
+
console.log(`[info] MEDIUM model: ${mediumModel} → provider api: ${providerApi}`);
|
|
4275
|
+
} else {
|
|
4276
|
+
console.log(`[info] MEDIUM model not configured yet, using default api: ${providerApi}`);
|
|
4277
|
+
}
|
|
4278
|
+
} catch {
|
|
4279
|
+
console.log(`[info] clawmux.json not readable, using default api: ${providerApi}`);
|
|
4280
|
+
}
|
|
4086
4281
|
if (providers[PROVIDER_KEY]) {
|
|
4087
|
-
|
|
4282
|
+
const existing = providers[PROVIDER_KEY];
|
|
4283
|
+
if (existing["api"] !== providerApi) {
|
|
4284
|
+
existing["api"] = providerApi;
|
|
4285
|
+
await import_promises6.writeFile(openclawConfigPath, JSON.stringify(config, null, 2) + `
|
|
4286
|
+
`);
|
|
4287
|
+
console.log(` updated ${PROVIDER_KEY} provider api → ${providerApi}`);
|
|
4288
|
+
} else {
|
|
4289
|
+
console.log(` skip ${PROVIDER_KEY} (already exists, api=${providerApi})`);
|
|
4290
|
+
}
|
|
4088
4291
|
} else {
|
|
4089
4292
|
providers[PROVIDER_KEY] = {
|
|
4090
4293
|
baseUrl: "http://localhost:3456",
|
|
4091
|
-
api:
|
|
4294
|
+
api: providerApi,
|
|
4092
4295
|
models: [{ id: "auto", name: "ClawMux Auto Router" }]
|
|
4093
4296
|
};
|
|
4094
4297
|
await import_promises6.writeFile(openclawConfigPath, JSON.stringify(config, null, 2) + `
|
|
4095
4298
|
`);
|
|
4096
|
-
console.log(` added ${PROVIDER_KEY} provider to openclaw.json`);
|
|
4299
|
+
console.log(` added ${PROVIDER_KEY} provider to openclaw.json (api=${providerApi})`);
|
|
4097
4300
|
}
|
|
4098
4301
|
const port = process.env.CLAWMUX_PORT ?? "3456";
|
|
4099
4302
|
if (!noService) {
|
package/dist/index.cjs
CHANGED
|
@@ -609,14 +609,7 @@ function getAuthProfilesPath(agentId) {
|
|
|
609
609
|
const id = agentId ?? "main";
|
|
610
610
|
return import_node_path2.join(getHomeDir(), ".openclaw", "agents", id, "agent", "auth-profiles.json");
|
|
611
611
|
}
|
|
612
|
-
|
|
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
|
-
}
|
|
612
|
+
function parseAuthProfilesFile(text) {
|
|
620
613
|
try {
|
|
621
614
|
const parsed = JSON.parse(text);
|
|
622
615
|
if (Array.isArray(parsed))
|
|
@@ -624,7 +617,7 @@ async function readAuthProfiles(agentId, profilesPath) {
|
|
|
624
617
|
if (parsed && typeof parsed === "object" && parsed.profiles) {
|
|
625
618
|
return Object.entries(parsed.profiles).map(([key, profile]) => ({
|
|
626
619
|
provider: profile.provider ?? key.split(":")[0],
|
|
627
|
-
apiKey: profile.access ?? profile.apiKey,
|
|
620
|
+
apiKey: profile.access ?? profile.apiKey ?? profile.key,
|
|
628
621
|
token: profile.token
|
|
629
622
|
})).filter((p) => {
|
|
630
623
|
const token = p.apiKey ?? p.token;
|
|
@@ -647,6 +640,33 @@ async function readAuthProfiles(agentId, profilesPath) {
|
|
|
647
640
|
throw new Error(`Failed to parse auth-profiles.json: ${message}`);
|
|
648
641
|
}
|
|
649
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
|
+
}
|
|
650
670
|
function getProviderConfig(provider, config) {
|
|
651
671
|
return config.models?.providers?.[provider];
|
|
652
672
|
}
|
|
@@ -795,6 +815,38 @@ function getAdapter(apiType) {
|
|
|
795
815
|
return adapters.get(apiType);
|
|
796
816
|
}
|
|
797
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
|
+
|
|
798
850
|
// src/adapters/anthropic.ts
|
|
799
851
|
class AnthropicAdapter {
|
|
800
852
|
apiType = "anthropic-messages";
|
|
@@ -821,18 +873,42 @@ class AnthropicAdapter {
|
|
|
821
873
|
"anthropic-version": "2023-06-01",
|
|
822
874
|
"content-type": "application/json"
|
|
823
875
|
};
|
|
824
|
-
let bodyObj = {
|
|
825
|
-
...parsed.rawBody,
|
|
826
|
-
model: targetModel
|
|
827
|
-
};
|
|
828
876
|
const isHaiku = targetModel.toLowerCase().includes("haiku");
|
|
829
877
|
const hasThinking = "thinking" in parsed.rawBody;
|
|
830
878
|
if (hasThinking && !isHaiku) {
|
|
831
879
|
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
832
880
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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;
|
|
836
912
|
}
|
|
837
913
|
return {
|
|
838
914
|
url,
|
|
@@ -1099,11 +1175,43 @@ class OpenAICompletionsAdapter {
|
|
|
1099
1175
|
return parseOpenAIBody(body);
|
|
1100
1176
|
}
|
|
1101
1177
|
buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
|
|
1102
|
-
const
|
|
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
|
+
}
|
|
1103
1203
|
const upstreamBody = {
|
|
1104
|
-
|
|
1105
|
-
|
|
1204
|
+
model: targetModel,
|
|
1205
|
+
messages,
|
|
1206
|
+
stream: parsed.stream,
|
|
1207
|
+
...samplingParams
|
|
1106
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
|
+
}
|
|
1107
1215
|
return {
|
|
1108
1216
|
url: /\/v\d+\/?$/.test(baseUrl) ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : `${baseUrl}/v1/chat/completions`,
|
|
1109
1217
|
method: "POST",
|
|
@@ -1130,8 +1238,11 @@ class OpenAICompletionsAdapter {
|
|
|
1130
1238
|
if (Array.isArray(choices) && choices.length > 0) {
|
|
1131
1239
|
const choice = choices[0];
|
|
1132
1240
|
const message = choice.message;
|
|
1133
|
-
if (message
|
|
1134
|
-
|
|
1241
|
+
if (message) {
|
|
1242
|
+
const messageText = message.content ?? message.reasoning_content;
|
|
1243
|
+
if (typeof messageText === "string") {
|
|
1244
|
+
content = messageText;
|
|
1245
|
+
}
|
|
1135
1246
|
}
|
|
1136
1247
|
if (typeof choice.finish_reason === "string") {
|
|
1137
1248
|
stopReason = choice.finish_reason;
|
|
@@ -1201,16 +1312,17 @@ class OpenAICompletionsAdapter {
|
|
|
1201
1312
|
const choice = choices[0];
|
|
1202
1313
|
const delta = choice.delta;
|
|
1203
1314
|
const finishReason = choice.finish_reason;
|
|
1204
|
-
|
|
1315
|
+
const textContent = delta?.content ?? delta?.reasoning_content;
|
|
1316
|
+
if (delta?.role === "assistant" && textContent == null) {
|
|
1205
1317
|
events.push({
|
|
1206
1318
|
type: "message_start",
|
|
1207
1319
|
id: String(data.id ?? ""),
|
|
1208
1320
|
model: String(data.model ?? "")
|
|
1209
1321
|
});
|
|
1210
|
-
} else if (typeof
|
|
1322
|
+
} else if (typeof textContent === "string") {
|
|
1211
1323
|
events.push({
|
|
1212
1324
|
type: "content_delta",
|
|
1213
|
-
text:
|
|
1325
|
+
text: textContent,
|
|
1214
1326
|
index: typeof choice.index === "number" ? choice.index : 0
|
|
1215
1327
|
});
|
|
1216
1328
|
}
|
|
@@ -1242,7 +1354,7 @@ class OpenAICompletionsAdapter {
|
|
|
1242
1354
|
choices: [
|
|
1243
1355
|
{
|
|
1244
1356
|
index: 0,
|
|
1245
|
-
delta: { role: "assistant"
|
|
1357
|
+
delta: { role: "assistant" },
|
|
1246
1358
|
finish_reason: null
|
|
1247
1359
|
}
|
|
1248
1360
|
]
|
|
@@ -1302,11 +1414,40 @@ class OpenAIResponsesAdapter {
|
|
|
1302
1414
|
return parseOpenAIBody(body);
|
|
1303
1415
|
}
|
|
1304
1416
|
buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
|
|
1305
|
-
const
|
|
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
|
+
}
|
|
1306
1439
|
const upstreamBody = {
|
|
1307
|
-
|
|
1308
|
-
|
|
1440
|
+
model: targetModel,
|
|
1441
|
+
input,
|
|
1442
|
+
stream: parsed.stream,
|
|
1443
|
+
...samplingParams
|
|
1309
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
|
+
}
|
|
1310
1451
|
return {
|
|
1311
1452
|
url: `${baseUrl}/v1/responses`,
|
|
1312
1453
|
method: "POST",
|
|
@@ -2315,6 +2456,9 @@ class OpenAICodexAdapter {
|
|
|
2315
2456
|
}
|
|
2316
2457
|
delete upstreamBody.max_tokens;
|
|
2317
2458
|
delete upstreamBody.max_output_tokens;
|
|
2459
|
+
if (upstreamBody.tools) {
|
|
2460
|
+
upstreamBody.tools = toOpenAITools(upstreamBody.tools);
|
|
2461
|
+
}
|
|
2318
2462
|
return {
|
|
2319
2463
|
url: `${baseUrl}/codex/responses`,
|
|
2320
2464
|
method: "POST",
|
|
@@ -2971,6 +3115,7 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2971
3115
|
return new TransformStream;
|
|
2972
3116
|
}
|
|
2973
3117
|
let buffer = "";
|
|
3118
|
+
let messageStarted = false;
|
|
2974
3119
|
return new TransformStream({
|
|
2975
3120
|
transform(chunk, controller) {
|
|
2976
3121
|
if (!sourceAdapter.parseStreamChunk || !targetAdapter.buildStreamChunk) {
|
|
@@ -2988,6 +3133,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2988
3133
|
continue;
|
|
2989
3134
|
const events = sourceAdapter.parseStreamChunk(frame);
|
|
2990
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
|
+
}
|
|
2991
3148
|
const translated = targetAdapter.buildStreamChunk(event);
|
|
2992
3149
|
controller.enqueue(encoder.encode(translated));
|
|
2993
3150
|
}
|
|
@@ -2997,6 +3154,18 @@ function createStreamTranslator(sourceAdapter, targetAdapter) {
|
|
|
2997
3154
|
if (buffer.trim() !== "" && sourceAdapter.parseStreamChunk && targetAdapter.buildStreamChunk) {
|
|
2998
3155
|
const events = sourceAdapter.parseStreamChunk(buffer);
|
|
2999
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
|
+
}
|
|
3000
3169
|
const translated = targetAdapter.buildStreamChunk(event);
|
|
3001
3170
|
controller.enqueue(encoder.encode(translated));
|
|
3002
3171
|
}
|