openclaw-abacusai-auth 1.2.5 → 1.2.7

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 (2) hide show
  1. package/index.ts +101 -17
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -270,6 +270,8 @@ async function validateApiKey(
270
270
 
271
271
  let proxyServer: ReturnType<typeof createServer> | null = null;
272
272
  let proxyApiKey = "";
273
+ let activeProxyRequests = 0;
274
+ let proxyShuttingDown = false;
273
275
 
274
276
  function readBody(req: IncomingMessage): Promise<Buffer> {
275
277
  return new Promise((resolve, reject) => {
@@ -554,6 +556,19 @@ function normalizeResponseToolCalls(json: Record<string, unknown>): Record<strin
554
556
  }
555
557
 
556
558
  async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
559
+ if (proxyShuttingDown) {
560
+ sendJsonResponse(res, 503, { error: { message: "Proxy is shutting down", type: "service_unavailable" } });
561
+ return;
562
+ }
563
+ activeProxyRequests++;
564
+ try {
565
+ await handleProxyRequestInner(req, res);
566
+ } finally {
567
+ activeProxyRequests--;
568
+ }
569
+ }
570
+
571
+ async function handleProxyRequestInner(req: IncomingMessage, res: ServerResponse) {
557
572
  const path = req.url ?? "/";
558
573
  const target = `${ROUTELLM_BASE}${path}`;
559
574
  const headers: Record<string, string> = {
@@ -718,6 +733,48 @@ function startProxy(apiKey: string): Promise<void> {
718
733
  });
719
734
  }
720
735
 
736
+ /**
737
+ * Gracefully stop the RouteLLM proxy server.
738
+ * 1. Stop accepting new connections
739
+ * 2. Wait for all in-flight requests to complete (up to 10s timeout)
740
+ * 3. Close the server and release the port
741
+ */
742
+ function stopProxy(): Promise<void> {
743
+ return new Promise((resolve) => {
744
+ if (!proxyServer) {
745
+ resolve();
746
+ return;
747
+ }
748
+
749
+ proxyShuttingDown = true;
750
+ console.log(`[abacusai] Proxy shutting down (${activeProxyRequests} active requests)...`);
751
+
752
+ // Stop accepting new connections immediately
753
+ proxyServer.close(() => {
754
+ console.log("[abacusai] Proxy server closed, port released.");
755
+ proxyServer = null;
756
+ proxyShuttingDown = false;
757
+ activeProxyRequests = 0;
758
+ resolve();
759
+ });
760
+
761
+ // Force-close after 10s if requests don't drain
762
+ const forceTimeout = setTimeout(() => {
763
+ console.warn(`[abacusai] Force-closing proxy (${activeProxyRequests} requests still active after 10s).`);
764
+ proxyServer?.closeAllConnections?.();
765
+ }, 10_000);
766
+
767
+ // Poll for active requests to finish, resolve early if all done
768
+ const drainInterval = setInterval(() => {
769
+ if (activeProxyRequests <= 0) {
770
+ clearInterval(drainInterval);
771
+ clearTimeout(forceTimeout);
772
+ // server.close callback will resolve
773
+ }
774
+ }, 200);
775
+ });
776
+ }
777
+
721
778
  // ---------------------------------------------------------------------------
722
779
  // Helpers
723
780
  // ---------------------------------------------------------------------------
@@ -757,7 +814,7 @@ function buildModelDefinition(modelId: string) {
757
814
  * but the config still stores the port from when `openclaw models auth login`
758
815
  * was first run.
759
816
  */
760
- function updateBaseUrlInConfig(): void {
817
+ function updateBaseUrlInConfig(pluginApi: any): void {
761
818
  if (!proxyPort) return;
762
819
  const newBaseUrl = `http://${PROXY_HOST}:${proxyPort}`;
763
820
  try {
@@ -766,24 +823,30 @@ function updateBaseUrlInConfig(): void {
766
823
  process.env.CLAWDBOT_STATE_DIR ||
767
824
  join(homedir(), ".openclaw");
768
825
  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
826
 
777
- if (currentUrl === newBaseUrl) {
778
- // Already up to date
779
- return;
827
+ // 1. Update in-memory OpenClaw config if available (so it works immediately and saves correctly)
828
+ let inMemoryUpdated = false;
829
+ if (pluginApi?.config?.models?.providers?.abacusai) {
830
+ if (pluginApi.config.models.providers.abacusai.baseUrl !== newBaseUrl) {
831
+ pluginApi.config.models.providers.abacusai.baseUrl = newBaseUrl;
832
+ inMemoryUpdated = true;
833
+ }
780
834
  }
781
835
 
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}`);
836
+ // 2. Fallback to writing the disk file directly if needed
837
+ if (existsSync(configPath)) {
838
+ let raw = readFileSync(configPath, "utf-8");
839
+ if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
840
+ const config = JSON.parse(raw);
841
+ const currentUrl = config?.models?.providers?.abacusai?.baseUrl;
842
+
843
+ if (currentUrl !== newBaseUrl && config.models?.providers?.abacusai) {
844
+ config.models.providers.abacusai.baseUrl = newBaseUrl;
845
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
846
+ console.log(`[abacusai] Updated config baseUrl on disk: ${currentUrl} → ${newBaseUrl}`);
847
+ } else if (inMemoryUpdated) {
848
+ console.log(`[abacusai] Updated in-memory baseUrl to ${newBaseUrl}`);
849
+ }
787
850
  }
788
851
  } catch (err) {
789
852
  console.error("[abacusai] Failed to update baseUrl in config:", err);
@@ -818,11 +881,32 @@ const abacusaiPlugin = {
818
881
  register(api: unknown) {
819
882
  const pluginApi = api as {
820
883
  registerProvider: (config: unknown) => void;
884
+ registerHook?: (events: string | string[], handler: Function, opts?: { name?: string }) => void;
821
885
  config?: {
822
886
  models?: { providers?: { abacusai?: { compat?: { supportsStrictMode?: boolean } } } };
823
887
  };
824
888
  };
825
889
 
890
+ // ================================================================
891
+ // Register gateway_stop hook for graceful proxy shutdown
892
+ // ================================================================
893
+ if (typeof pluginApi.registerHook === "function") {
894
+ pluginApi.registerHook(
895
+ "gateway_stop",
896
+ async () => {
897
+ await stopProxy();
898
+ },
899
+ { name: "openclaw-abacusai-auth:gateway-stop" },
900
+ );
901
+ }
902
+
903
+ // Fallback: handle process signals if gateway_stop hook is unavailable
904
+ const shutdownHandler = () => {
905
+ stopProxy().then(() => process.exit(0));
906
+ };
907
+ process.once("SIGTERM", shutdownHandler);
908
+ process.once("SIGINT", shutdownHandler);
909
+
826
910
  // Use local proxy mode to handle schema cleaning internally
827
911
  // This is required because OpenClaw core may not support requiresCleanSchema yet
828
912
  // The proxy normalizes tool schemas before forwarding to RouteLLM
@@ -835,7 +919,7 @@ const abacusaiPlugin = {
835
919
  // Update baseUrl in config to match the new proxy port
836
920
  // (The proxy gets a new random port each time the gateway starts,
837
921
  // but the config still has the port from when auth was first run)
838
- updateBaseUrlInConfig();
922
+ updateBaseUrlInConfig(pluginApi);
839
923
  })
840
924
  .catch((err) => {
841
925
  console.error("[abacusai] Failed to auto-start proxy:", err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-abacusai-auth",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "OpenClaw AbacusAI provider plugin - Third-party plugin for AbacusAI RouteLLM integration",
5
5
  "type": "module",
6
6
  "main": "index.ts",