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 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.2.2";
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.apiKey) {
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: "AWS4-HMAC-SHA256 Credential=placeholder"
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmux",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Smart model routing + context compression proxy for OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {