openclaw-abacusai-auth 1.2.3 → 1.2.5

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/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readFileSync, readdirSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
@@ -15,8 +15,9 @@ const DEFAULT_MAX_TOKENS = 8192;
15
15
 
16
16
  // Proxy configuration
17
17
  const PROXY_HOST = "127.0.0.1";
18
- // Dynamic port - will be assigned when proxy starts
19
- let proxyPort = 0;
18
+ // Fixed port for the proxy so the baseUrl saved at auth time always works
19
+ const PROXY_PORT_DEFAULT = 18862;
20
+ let proxyPort = PROXY_PORT_DEFAULT;
20
21
 
21
22
  // Models available on AbacusAI RouteLLM endpoint (OpenAI-compatible, with
22
23
  // function calling support). Verified 2026-02.
@@ -443,6 +444,13 @@ function normalizeToolsForRouteLLM(tools: unknown[]): unknown[] {
443
444
  fn.parameters = cleanSchema(fn.parameters);
444
445
  }
445
446
 
447
+ // RouteLLM REQUIRES every tool to have a `parameters` field.
448
+ // If a tool has no parameters (e.g. cognitive_assess, flare_plan),
449
+ // add a default empty object schema.
450
+ if (!fn.parameters) {
451
+ fn.parameters = { type: "object", properties: {} };
452
+ }
453
+
446
454
  copy.function = fn;
447
455
 
448
456
  // Promote name and parameters to top level for RouteLLM
@@ -559,6 +567,8 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
559
567
  const parsed = JSON.parse(raw.toString()) as Record<string, unknown>;
560
568
  // Normalize tools for RouteLLM: remove `strict` field, clean schemas
561
569
  // (remove patternProperties, add additionalProperties: false, etc.)
570
+
571
+
562
572
  if (Array.isArray(parsed.tools)) {
563
573
  parsed.tools = normalizeToolsForRouteLLM(parsed.tools);
564
574
  }
@@ -677,18 +687,34 @@ function startProxy(apiKey: string): Promise<void> {
677
687
  sendJsonResponse(res, 500, { error: { message: String(err) } });
678
688
  });
679
689
  });
680
- // Use port 0 to let the OS assign a random available port
681
- proxyServer.listen(0, PROXY_HOST, () => {
682
- const addr = proxyServer?.address();
683
- if (addr && typeof addr === "object") {
684
- proxyPort = addr.port;
685
- }
686
- console.log(`[abacusai] proxy listening on http://${PROXY_HOST}:${proxyPort}`);
687
- resolve();
688
- });
689
- proxyServer.on("error", (err: NodeJS.ErrnoException) => {
690
- reject(err);
691
- });
690
+
691
+ // Try fixed port first, then retry with port+1, +2, etc.
692
+ const tryListen = (port: number, attempt: number) => {
693
+ proxyServer!.listen(port, PROXY_HOST, () => {
694
+ proxyPort = port;
695
+ console.log(`[abacusai] proxy listening on http://${PROXY_HOST}:${proxyPort}`);
696
+ resolve();
697
+ });
698
+ proxyServer!.once("error", (err: NodeJS.ErrnoException) => {
699
+ if (err.code === "EADDRINUSE" && attempt < 10) {
700
+ console.log(`[abacusai] port ${port} in use, trying ${port + 1}...`);
701
+ proxyServer!.removeAllListeners("error");
702
+ proxyServer!.close(() => {
703
+ proxyServer = createServer((req, res) => {
704
+ handleProxyRequest(req, res).catch((e) => {
705
+ console.error("[abacusai] proxy error:", e);
706
+ sendJsonResponse(res, 500, { error: { message: String(e) } });
707
+ });
708
+ });
709
+ tryListen(port + 1, attempt + 1);
710
+ });
711
+ } else {
712
+ reject(err);
713
+ }
714
+ });
715
+ };
716
+
717
+ tryListen(PROXY_PORT_DEFAULT, 0);
692
718
  });
693
719
  }
694
720
 
@@ -720,6 +746,50 @@ function buildModelDefinition(modelId: string) {
720
746
  };
721
747
  }
722
748
 
749
+ // ---------------------------------------------------------------------------
750
+ // Dynamic baseUrl updater — keep config in sync with current proxy port
751
+ // ---------------------------------------------------------------------------
752
+
753
+ /**
754
+ * Update the `models.providers.abacusai.baseUrl` in openclaw.json to match
755
+ * the current proxy port. This is necessary because the proxy uses port 0
756
+ * (OS-assigned random port) and gets a new port every time the gateway starts,
757
+ * but the config still stores the port from when `openclaw models auth login`
758
+ * was first run.
759
+ */
760
+ function updateBaseUrlInConfig(): void {
761
+ if (!proxyPort) return;
762
+ const newBaseUrl = `http://${PROXY_HOST}:${proxyPort}`;
763
+ try {
764
+ const stateDir =
765
+ process.env.OPENCLAW_STATE_DIR ||
766
+ process.env.CLAWDBOT_STATE_DIR ||
767
+ join(homedir(), ".openclaw");
768
+ const configPath = join(stateDir, "openclaw.json");
769
+ if (!existsSync(configPath)) return;
770
+
771
+ let raw = readFileSync(configPath, "utf-8");
772
+ // Strip UTF-8 BOM if present
773
+ if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
774
+ const config = JSON.parse(raw);
775
+ const currentUrl = config?.models?.providers?.abacusai?.baseUrl;
776
+
777
+ if (currentUrl === newBaseUrl) {
778
+ // Already up to date
779
+ return;
780
+ }
781
+
782
+ // Update the baseUrl
783
+ if (config.models?.providers?.abacusai) {
784
+ config.models.providers.abacusai.baseUrl = newBaseUrl;
785
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
786
+ console.log(`[abacusai] Updated config baseUrl: ${currentUrl} → ${newBaseUrl}`);
787
+ }
788
+ } catch (err) {
789
+ console.error("[abacusai] Failed to update baseUrl in config:", err);
790
+ }
791
+ }
792
+
723
793
  // ---------------------------------------------------------------------------
724
794
  // Plugin
725
795
  // ---------------------------------------------------------------------------
@@ -741,7 +811,7 @@ interface PluginAuthContext {
741
811
  }
742
812
 
743
813
  const abacusaiPlugin = {
744
- id: "abacusai-auth",
814
+ id: "openclaw-abacusai-auth",
745
815
  name: "AbacusAI Auth",
746
816
  description: "AbacusAI RouteLLM provider plugin with direct connection and schema normalization",
747
817
  configSchema: emptyPluginConfigSchema(),
@@ -760,9 +830,16 @@ const abacusaiPlugin = {
760
830
  // Auto-start proxy if we have a saved API key
761
831
  const savedKey = tryRecoverApiKey();
762
832
  if (savedKey) {
763
- startProxy(savedKey).catch((err) => {
764
- console.error("[abacusai] Failed to auto-start proxy:", err);
765
- });
833
+ startProxy(savedKey)
834
+ .then(() => {
835
+ // Update baseUrl in config to match the new proxy port
836
+ // (The proxy gets a new random port each time the gateway starts,
837
+ // but the config still has the port from when auth was first run)
838
+ updateBaseUrlInConfig();
839
+ })
840
+ .catch((err) => {
841
+ console.error("[abacusai] Failed to auto-start proxy:", err);
842
+ });
766
843
  }
767
844
 
768
845
  pluginApi.registerProvider({
@@ -885,7 +962,7 @@ const abacusaiPlugin = {
885
962
  // and adding `additionalProperties: false`
886
963
  baseUrl: `http://${PROXY_HOST}:${proxyPort}`,
887
964
  api: "openai-completions",
888
- auth: "token",
965
+ apiKey: "abacusai-proxy",
889
966
  models: modelIds.map((id) => buildModelDefinition(id)),
890
967
  },
891
968
  },
@@ -916,3 +993,4 @@ const abacusaiPlugin = {
916
993
  };
917
994
 
918
995
  export default abacusaiPlugin;
996
+
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "abacusai-auth",
2
+ "id": "openclaw-abacusai-auth",
3
3
  "providers": [
4
4
  "abacusai"
5
5
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-abacusai-auth",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "OpenClaw AbacusAI provider plugin - Third-party plugin for AbacusAI RouteLLM integration",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -18,6 +18,9 @@
18
18
  ],
19
19
  "author": "tonyhu2006",
20
20
  "license": "MIT",
21
+ "scripts": {
22
+ "build": "tsc"
23
+ },
21
24
  "peerDependencies": {
22
25
  "openclaw": ">=2026.2.0"
23
26
  },
@@ -25,5 +28,9 @@
25
28
  "extensions": [
26
29
  "./index.ts"
27
30
  ]
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.3.3",
34
+ "typescript": "^5.9.3"
28
35
  }
29
36
  }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "declaration": true
12
+ },
13
+ "include": [
14
+ "index.ts"
15
+ ]
16
+ }