clawmux 0.2.2 → 0.3.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/dist/cli.cjs +1 -1
- package/dist/index.cjs +236 -10
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -317,7 +317,7 @@ function getLogDir() {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// src/cli.ts
|
|
320
|
-
var VERSION2 = process.env.npm_package_version ?? "0.
|
|
320
|
+
var VERSION2 = process.env.npm_package_version ?? "0.3.0";
|
|
321
321
|
var SERVICE_NAME = "clawmux";
|
|
322
322
|
var HELP = `Usage: clawmux <command>
|
|
323
323
|
|
package/dist/index.cjs
CHANGED
|
@@ -596,7 +596,20 @@ async function readAuthProfiles(agentId, profilesPath) {
|
|
|
596
596
|
provider: profile.provider ?? key.split(":")[0],
|
|
597
597
|
apiKey: profile.access ?? profile.apiKey,
|
|
598
598
|
token: profile.token
|
|
599
|
-
}))
|
|
599
|
+
})).filter((p) => {
|
|
600
|
+
const token = p.apiKey ?? p.token;
|
|
601
|
+
if (!token || !token.includes("."))
|
|
602
|
+
return true;
|
|
603
|
+
try {
|
|
604
|
+
const payload = token.split(".")[1];
|
|
605
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64").toString());
|
|
606
|
+
if (decoded.exp && decoded.exp * 1000 < Date.now())
|
|
607
|
+
return false;
|
|
608
|
+
} catch (_) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
return true;
|
|
612
|
+
});
|
|
600
613
|
}
|
|
601
614
|
return [];
|
|
602
615
|
} catch (err) {
|
|
@@ -1892,6 +1905,98 @@ class OllamaAdapter {
|
|
|
1892
1905
|
var ollamaAdapter = new OllamaAdapter;
|
|
1893
1906
|
registerAdapter(ollamaAdapter);
|
|
1894
1907
|
|
|
1908
|
+
// src/utils/aws-sigv4.ts
|
|
1909
|
+
var import_node_crypto = require("node:crypto");
|
|
1910
|
+
var SERVICE = "bedrock";
|
|
1911
|
+
function sha256(data) {
|
|
1912
|
+
return import_node_crypto.createHash("sha256").update(data).digest("hex");
|
|
1913
|
+
}
|
|
1914
|
+
function hmacSha256(key, data) {
|
|
1915
|
+
return import_node_crypto.createHmac("sha256", key).update(data).digest();
|
|
1916
|
+
}
|
|
1917
|
+
function hmacSha256Hex(key, data) {
|
|
1918
|
+
return import_node_crypto.createHmac("sha256", key).update(data).digest("hex");
|
|
1919
|
+
}
|
|
1920
|
+
function awsUriEncode(str) {
|
|
1921
|
+
return encodeURIComponent(str).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A");
|
|
1922
|
+
}
|
|
1923
|
+
function buildCanonicalUri(pathname) {
|
|
1924
|
+
if (pathname === "" || pathname === "/")
|
|
1925
|
+
return "/";
|
|
1926
|
+
const segments = pathname.split("/");
|
|
1927
|
+
return segments.map((segment) => segment === "" ? "" : awsUriEncode(awsUriEncode(segment))).join("/");
|
|
1928
|
+
}
|
|
1929
|
+
function formatDateStamp(date) {
|
|
1930
|
+
return date.toISOString().slice(0, 10).replace(/-/g, "");
|
|
1931
|
+
}
|
|
1932
|
+
function formatAmzDate(date) {
|
|
1933
|
+
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
1934
|
+
}
|
|
1935
|
+
function deriveSigningKey(secretKey, dateStamp, region, service) {
|
|
1936
|
+
const kDate = hmacSha256(`AWS4${secretKey}`, dateStamp);
|
|
1937
|
+
const kRegion = hmacSha256(kDate, region);
|
|
1938
|
+
const kService = hmacSha256(kRegion, service);
|
|
1939
|
+
return hmacSha256(kService, "aws4_request");
|
|
1940
|
+
}
|
|
1941
|
+
function signRequest(request, credentials, now) {
|
|
1942
|
+
const date = now ?? new Date;
|
|
1943
|
+
const dateStamp = formatDateStamp(date);
|
|
1944
|
+
const amzDate = formatAmzDate(date);
|
|
1945
|
+
const url = new URL(request.url);
|
|
1946
|
+
const canonicalUri = buildCanonicalUri(url.pathname);
|
|
1947
|
+
const sortedParams = [...url.searchParams.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1948
|
+
const canonicalQueryString = sortedParams.map(([k, v]) => `${awsUriEncode(k)}=${awsUriEncode(v)}`).join("&");
|
|
1949
|
+
const payloadHash = sha256(request.body);
|
|
1950
|
+
const headersToSign = {};
|
|
1951
|
+
for (const [k, v] of Object.entries(request.headers)) {
|
|
1952
|
+
headersToSign[k.toLowerCase()] = v.trim();
|
|
1953
|
+
}
|
|
1954
|
+
headersToSign["host"] = url.host;
|
|
1955
|
+
headersToSign["x-amz-date"] = amzDate;
|
|
1956
|
+
headersToSign["x-amz-content-sha256"] = payloadHash;
|
|
1957
|
+
if (credentials.sessionToken) {
|
|
1958
|
+
headersToSign["x-amz-security-token"] = credentials.sessionToken;
|
|
1959
|
+
}
|
|
1960
|
+
const sortedHeaderKeys = Object.keys(headersToSign).sort();
|
|
1961
|
+
const canonicalHeaders = sortedHeaderKeys.map((k) => `${k}:${headersToSign[k]}`).join(`
|
|
1962
|
+
`) + `
|
|
1963
|
+
`;
|
|
1964
|
+
const signedHeaders = sortedHeaderKeys.join(";");
|
|
1965
|
+
const canonicalRequest = [
|
|
1966
|
+
request.method,
|
|
1967
|
+
canonicalUri,
|
|
1968
|
+
canonicalQueryString,
|
|
1969
|
+
canonicalHeaders,
|
|
1970
|
+
signedHeaders,
|
|
1971
|
+
payloadHash
|
|
1972
|
+
].join(`
|
|
1973
|
+
`);
|
|
1974
|
+
const credentialScope = `${dateStamp}/${credentials.region}/${SERVICE}/aws4_request`;
|
|
1975
|
+
const stringToSign = [
|
|
1976
|
+
"AWS4-HMAC-SHA256",
|
|
1977
|
+
amzDate,
|
|
1978
|
+
credentialScope,
|
|
1979
|
+
sha256(canonicalRequest)
|
|
1980
|
+
].join(`
|
|
1981
|
+
`);
|
|
1982
|
+
const signingKey = deriveSigningKey(credentials.secretAccessKey, dateStamp, credentials.region, SERVICE);
|
|
1983
|
+
const signature = hmacSha256Hex(signingKey, stringToSign);
|
|
1984
|
+
const authorization = `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${credentialScope}, ` + `SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
1985
|
+
const result = {
|
|
1986
|
+
Authorization: authorization,
|
|
1987
|
+
"x-amz-date": amzDate,
|
|
1988
|
+
"x-amz-content-sha256": payloadHash
|
|
1989
|
+
};
|
|
1990
|
+
if (credentials.sessionToken) {
|
|
1991
|
+
result["x-amz-security-token"] = credentials.sessionToken;
|
|
1992
|
+
}
|
|
1993
|
+
return result;
|
|
1994
|
+
}
|
|
1995
|
+
function extractRegionFromUrl(url) {
|
|
1996
|
+
const match = url.match(/\.([a-z0-9-]+)\.amazonaws\.com/);
|
|
1997
|
+
return match?.[1];
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1895
2000
|
// src/adapters/bedrock.ts
|
|
1896
2001
|
function bedrockMessagesToStandard(messages) {
|
|
1897
2002
|
return messages.map((m) => {
|
|
@@ -1988,18 +2093,23 @@ class BedrockAdapter {
|
|
|
1988
2093
|
maxTokens: parsed.maxTokens
|
|
1989
2094
|
};
|
|
1990
2095
|
}
|
|
2096
|
+
const url = `${baseUrl}/model/${targetModel}/converse-stream`;
|
|
2097
|
+
const body = JSON.stringify(requestBody);
|
|
1991
2098
|
const headers = {
|
|
1992
2099
|
"Content-Type": "application/json"
|
|
1993
2100
|
};
|
|
1994
|
-
if (auth.
|
|
2101
|
+
if (auth.awsAccessKeyId && auth.awsSecretKey && auth.awsRegion) {
|
|
2102
|
+
const sigv4Headers = signRequest({ method: "POST", url, headers, body }, {
|
|
2103
|
+
accessKeyId: auth.awsAccessKeyId,
|
|
2104
|
+
secretAccessKey: auth.awsSecretKey,
|
|
2105
|
+
sessionToken: auth.awsSessionToken,
|
|
2106
|
+
region: auth.awsRegion
|
|
2107
|
+
});
|
|
2108
|
+
Object.assign(headers, sigv4Headers);
|
|
2109
|
+
} else if (auth.apiKey) {
|
|
1995
2110
|
headers[auth.headerName || "Authorization"] = auth.headerValue || auth.apiKey;
|
|
1996
2111
|
}
|
|
1997
|
-
return {
|
|
1998
|
-
url: `${baseUrl}/model/${targetModel}/converse-stream`,
|
|
1999
|
-
method: "POST",
|
|
2000
|
-
headers,
|
|
2001
|
-
body: JSON.stringify(requestBody)
|
|
2002
|
-
};
|
|
2112
|
+
return { url, method: "POST", headers, body };
|
|
2003
2113
|
}
|
|
2004
2114
|
modifyMessages(rawBody, compressedMessages) {
|
|
2005
2115
|
return {
|
|
@@ -2153,6 +2263,57 @@ ${JSON.stringify({
|
|
|
2153
2263
|
var bedrockAdapter = new BedrockAdapter;
|
|
2154
2264
|
registerAdapter(bedrockAdapter);
|
|
2155
2265
|
|
|
2266
|
+
// src/adapters/openai-codex.ts
|
|
2267
|
+
class OpenAICodexAdapter {
|
|
2268
|
+
apiType = "openai-codex-responses";
|
|
2269
|
+
parseRequest(body) {
|
|
2270
|
+
return parseOpenAIBody(body);
|
|
2271
|
+
}
|
|
2272
|
+
buildUpstreamRequest(parsed, targetModel, baseUrl, auth) {
|
|
2273
|
+
const { rawBody } = parsed;
|
|
2274
|
+
const upstreamBody = { ...rawBody };
|
|
2275
|
+
upstreamBody.model = targetModel;
|
|
2276
|
+
upstreamBody.stream = true;
|
|
2277
|
+
upstreamBody.store = false;
|
|
2278
|
+
if (!upstreamBody.instructions) {
|
|
2279
|
+
upstreamBody.instructions = upstreamBody.system ?? "You are a helpful assistant.";
|
|
2280
|
+
}
|
|
2281
|
+
delete upstreamBody.system;
|
|
2282
|
+
if (!upstreamBody.input && upstreamBody.messages) {
|
|
2283
|
+
upstreamBody.input = upstreamBody.messages;
|
|
2284
|
+
delete upstreamBody.messages;
|
|
2285
|
+
}
|
|
2286
|
+
delete upstreamBody.max_tokens;
|
|
2287
|
+
delete upstreamBody.max_output_tokens;
|
|
2288
|
+
return {
|
|
2289
|
+
url: `${baseUrl}/codex/responses`,
|
|
2290
|
+
method: "POST",
|
|
2291
|
+
headers: {
|
|
2292
|
+
"Content-Type": "application/json",
|
|
2293
|
+
Authorization: `Bearer ${auth.apiKey}`
|
|
2294
|
+
},
|
|
2295
|
+
body: JSON.stringify(upstreamBody)
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
modifyMessages(rawBody, compressedMessages) {
|
|
2299
|
+
return openaiResponsesAdapter.modifyMessages(rawBody, compressedMessages);
|
|
2300
|
+
}
|
|
2301
|
+
parseResponse(body) {
|
|
2302
|
+
return openaiResponsesAdapter.parseResponse(body);
|
|
2303
|
+
}
|
|
2304
|
+
buildResponse(parsed) {
|
|
2305
|
+
return openaiResponsesAdapter.buildResponse(parsed);
|
|
2306
|
+
}
|
|
2307
|
+
parseStreamChunk(chunk) {
|
|
2308
|
+
return openaiResponsesAdapter.parseStreamChunk(chunk);
|
|
2309
|
+
}
|
|
2310
|
+
buildStreamChunk(event) {
|
|
2311
|
+
return openaiResponsesAdapter.buildStreamChunk(event);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
var openaiCodexAdapter = new OpenAICodexAdapter;
|
|
2315
|
+
registerAdapter(openaiCodexAdapter);
|
|
2316
|
+
|
|
2156
2317
|
// src/compression/compaction-detector.ts
|
|
2157
2318
|
var COMPACTION_PATTERNS = [
|
|
2158
2319
|
"merge these partial summaries into a single cohesive summary",
|
|
@@ -2726,10 +2887,17 @@ function formatAuth(apiKey, providerConfig) {
|
|
|
2726
2887
|
return { apiKey, headerName: "x-goog-api-key", headerValue: apiKey };
|
|
2727
2888
|
}
|
|
2728
2889
|
if (api === "bedrock-converse-stream") {
|
|
2890
|
+
const secretKey = process.env.AWS_SECRET_ACCESS_KEY ?? "";
|
|
2891
|
+
const sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
2892
|
+
const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? extractRegionFromUrl(providerConfig?.baseUrl ?? "") ?? "us-east-1";
|
|
2729
2893
|
return {
|
|
2730
2894
|
apiKey,
|
|
2731
2895
|
headerName: "Authorization",
|
|
2732
|
-
headerValue: "
|
|
2896
|
+
headerValue: "",
|
|
2897
|
+
awsAccessKeyId: apiKey,
|
|
2898
|
+
awsSecretKey: secretKey,
|
|
2899
|
+
awsSessionToken: sessionToken,
|
|
2900
|
+
awsRegion: region
|
|
2733
2901
|
};
|
|
2734
2902
|
}
|
|
2735
2903
|
return { apiKey, headerName: "Authorization", headerValue: `Bearer ${apiKey}` };
|
|
@@ -3282,6 +3450,48 @@ function createCompressionMiddleware(config) {
|
|
|
3282
3450
|
|
|
3283
3451
|
// src/proxy/pipeline.ts
|
|
3284
3452
|
registerAdapter(new AnthropicAdapter);
|
|
3453
|
+
async function collectCodexStream(sourceAdapter, response) {
|
|
3454
|
+
const reader = response.body.getReader();
|
|
3455
|
+
const decoder2 = new TextDecoder;
|
|
3456
|
+
let buffer = "";
|
|
3457
|
+
let id = "";
|
|
3458
|
+
let model = "";
|
|
3459
|
+
const textParts = [];
|
|
3460
|
+
let usage;
|
|
3461
|
+
for (;; ) {
|
|
3462
|
+
const { done, value } = await reader.read();
|
|
3463
|
+
if (done)
|
|
3464
|
+
break;
|
|
3465
|
+
buffer += decoder2.decode(value, { stream: true });
|
|
3466
|
+
let idx;
|
|
3467
|
+
while ((idx = buffer.indexOf(`
|
|
3468
|
+
|
|
3469
|
+
`)) !== -1) {
|
|
3470
|
+
const frame = buffer.slice(0, idx);
|
|
3471
|
+
buffer = buffer.slice(idx + 2);
|
|
3472
|
+
if (!frame.trim() || !sourceAdapter.parseStreamChunk)
|
|
3473
|
+
continue;
|
|
3474
|
+
for (const event of sourceAdapter.parseStreamChunk(frame)) {
|
|
3475
|
+
if (event.type === "message_start") {
|
|
3476
|
+
id = event.id ?? "";
|
|
3477
|
+
model = event.model ?? "";
|
|
3478
|
+
} else if (event.type === "content_delta") {
|
|
3479
|
+
textParts.push(event.text ?? "");
|
|
3480
|
+
} else if (event.type === "message_stop" && event.usage) {
|
|
3481
|
+
usage = event.usage;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
return {
|
|
3487
|
+
id,
|
|
3488
|
+
model,
|
|
3489
|
+
content: textParts.join(""),
|
|
3490
|
+
role: "assistant",
|
|
3491
|
+
stopReason: "completed",
|
|
3492
|
+
usage
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3285
3495
|
function findProviderForModel(modelString, openclawConfig) {
|
|
3286
3496
|
const providers = openclawConfig.models?.providers;
|
|
3287
3497
|
if (!providers)
|
|
@@ -3365,7 +3575,11 @@ async function handleApiRequest(req, body, apiType, config, openclawConfig, auth
|
|
|
3365
3575
|
const authInfo = {
|
|
3366
3576
|
apiKey: auth.apiKey,
|
|
3367
3577
|
headerName: auth.headerName,
|
|
3368
|
-
headerValue: auth.headerValue
|
|
3578
|
+
headerValue: auth.headerValue,
|
|
3579
|
+
awsAccessKeyId: auth.awsAccessKeyId,
|
|
3580
|
+
awsSecretKey: auth.awsSecretKey,
|
|
3581
|
+
awsSessionToken: auth.awsSessionToken,
|
|
3582
|
+
awsRegion: auth.awsRegion
|
|
3369
3583
|
};
|
|
3370
3584
|
const actualModelId = decision.model.split("/").slice(1).join("/");
|
|
3371
3585
|
const isCrossProvider = targetApiType !== "" && targetApiType !== apiType;
|
|
@@ -3387,6 +3601,18 @@ async function handleApiRequest(req, body, apiType, config, openclawConfig, auth
|
|
|
3387
3601
|
if (compressionMiddleware && upstreamResponse.ok) {
|
|
3388
3602
|
compressionMiddleware.afterResponse(parsed, adapter, baseUrl, authInfo);
|
|
3389
3603
|
}
|
|
3604
|
+
const isCodexUpstream = targetApiType === "openai-codex-responses";
|
|
3605
|
+
if (isCodexUpstream && upstreamResponse.ok && upstreamResponse.body) {
|
|
3606
|
+
if (effectiveParsed.stream) {
|
|
3607
|
+
return translateResponse(requestAdapter, adapter, upstreamResponse, true);
|
|
3608
|
+
}
|
|
3609
|
+
const collected = await collectCodexStream(requestAdapter, upstreamResponse);
|
|
3610
|
+
const translated = adapter.buildResponse(collected);
|
|
3611
|
+
return new Response(JSON.stringify(translated), {
|
|
3612
|
+
status: 200,
|
|
3613
|
+
headers: { "content-type": "application/json" }
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3390
3616
|
if (targetAdapter && upstreamResponse.ok) {
|
|
3391
3617
|
return translateResponse(targetAdapter, adapter, upstreamResponse, effectiveParsed.stream);
|
|
3392
3618
|
}
|