tao-shen Claude Opus 4.6 (1M context) commited on
Commit
29370f7
·
1 Parent(s): e8e5621

fix: stop overriding OpenClaw provider config — pass all env vars through

Browse files

sync_hf.py was hardcoding a provider registry and overwriting
models.providers on every startup, blocking OpenClaw's native env var
support. Users setting NVIDIA_API_KEY or other provider keys got no
effect because the code didn't know about them.

Now HuggingClaw acts as a transparent deployment layer:
- Removed PROVIDER_REGISTRY and all per-provider if/else blocks
- Removed redundant env var pass-through (os.environ.copy() already
includes everything)
- Only clean up stale providers from dataset backup (keys no longer
in environment)
- Let OpenClaw handle provider discovery from env vars natively

Fixes #2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. .env.example +11 -25
  2. scripts/sync_hf.py +37 -84
.env.example CHANGED
@@ -71,30 +71,20 @@ OPENCLAW_DATASET_REPO=your-username/HuggingClaw-data
71
  # SYNC_INTERVAL=60
72
 
73
 
74
- # ─── LLM / OPENAI-COMPATIBLE API ───────────────────────────────────────────
75
  #
76
- # OpenClaw supports any OpenAI-compatible API. Set the API key for the
77
- # provider(s) you use. See OpenClaw docs: https://openclawdoc.com/docs/reference/environment-variables
 
78
  #
79
- # OpenAI (or any OpenAI-compatible endpoint)
80
- # Use OPENAI_API_KEY alone for api.openai.com, or set OPENAI_BASE_URL for
81
- # compatible endpoints (e.g. OpenRouter, local LLM servers, Azure OpenAI).
82
  #
83
  # [RECOMMENDED] At least one of the following for AI conversations
84
  #
85
  OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
86
-
87
- # Optional: base URL for OpenAI-compatible API (default: https://api.openai.com/v1)
88
- # Examples: https://openrouter.ai/api/v1, http://localhost:11434/v1 (Ollama), etc.
89
- #
90
  # OPENAI_BASE_URL=https://api.openai.com/v1
91
-
92
- # OpenRouter — one key, 200+ models, free tier: https://openrouter.ai/keys
93
- #
94
- OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
95
-
96
- # Other providers (OpenClaw reads these from the environment)
97
- #
98
  # ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
99
  # GOOGLE_API_KEY=AIza...
100
  # MISTRAL_API_KEY=mis-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@@ -185,14 +175,10 @@ OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
185
  # HF_HUB_UPLOAD_TIMEOUT [可选] 上传超时(秒),默认 600
186
  #
187
  # ─── LLM / 对话 API(至少配置其一以启用 AI 对话)────────────────────────
188
- # OPENAI_API_KEY [推荐] OpenAI 或兼容端点 API Key
189
- # OPENAI_BASE_URL [可选] 兼容 API 基地址,默认 https://api.openai.com/v1
190
- # OPENROUTER_API_KEY [可选] OpenRouter,200+ 模型、免费额度
191
- # ANTHROPIC_API_KEY [可选] Anthropic Claude
192
- # GOOGLE_API_KEY [可选] Google / Gemini
193
- # MISTRAL_API_KEY [可选] Mistral
194
- # COHERE_API_KEY [可选] Cohere
195
- # OPENCLAW_DEFAULT_MODEL [可选] 默认模型 ID
196
  #
197
  # ─── 消息渠道 ─────────────────────────────────────────────────────────
198
  # Telegram、WhatsApp 等消息渠道均可在 Control UI 中配置,无需环境变量。
 
71
  # SYNC_INTERVAL=60
72
 
73
 
74
+ # ─── LLM PROVIDERS ────────────────────────────────────────────────────────
75
  #
76
+ # HuggingClaw passes ALL environment variables directly to OpenClaw.
77
+ # Any provider that OpenClaw supports works out of the box — just set the
78
+ # API key as a Secret in your HF Space settings.
79
  #
80
+ # See OpenClaw docs: https://openclawdoc.com/docs/reference/environment-variables
 
 
81
  #
82
  # [RECOMMENDED] At least one of the following for AI conversations
83
  #
84
  OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
 
 
 
85
  # OPENAI_BASE_URL=https://api.openai.com/v1
86
+ # OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
87
+ # NVIDIA_API_KEY=nvapi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
 
 
 
 
88
  # ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
89
  # GOOGLE_API_KEY=AIza...
90
  # MISTRAL_API_KEY=mis-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
175
  # HF_HUB_UPLOAD_TIMEOUT [可选] 上传超时(秒),默认 600
176
  #
177
  # ─── LLM / 对话 API(至少配置其一以启用 AI 对话)────────────────────────
178
+ # 所有 OpenClaw 支持的 API Key 环境变量均可直接使用,无需额外配置。
179
+ # 完整列表见 OpenClaw 官方文档。常见:
180
+ # OPENAI_API_KEY, OPENROUTER_API_KEY, NVIDIA_API_KEY, ANTHROPIC_API_KEY,
181
+ # GOOGLE_API_KEY, MISTRAL_API_KEY, COHERE_API_KEY, OPENCLAW_DEFAULT_MODEL
 
 
 
 
182
  #
183
  # ─── 消息渠道 ─────────────────────────────────────────────────────────
184
  # Telegram、WhatsApp 等消息渠道均可在 Control UI 中配置,无需环境变量。
scripts/sync_hf.py CHANGED
@@ -66,16 +66,6 @@ APP_DIR = Path("/app/openclaw")
66
  # Use ".openclaw" - directly read/write the .openclaw folder in dataset
67
  DATASET_PATH = ".openclaw"
68
 
69
- # OpenAI-compatible API (OpenAI, OpenRouter, or any compatible endpoint)
70
- OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
71
- OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1").rstrip("/")
72
-
73
- # OpenRouter API key (optional; alternative to OPENAI_API_KEY + OPENAI_BASE_URL)
74
- OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
75
-
76
- # Zhipu AI (z.ai) API key (optional; GLM-4 series, Anthropic-compatible endpoint)
77
- ZHIPU_API_KEY = os.environ.get("ZHIPU_API_KEY", "")
78
-
79
  # Z.AI API key (optional; used by Claude Code backend via api.z.ai)
80
  ZAI_API_KEY = os.environ.get("ZAI_API_KEY", "")
81
 
@@ -86,12 +76,8 @@ GATEWAY_TOKEN = os.environ.get("GATEWAY_TOKEN", "huggingclaw")
86
  AGENT_NAME = os.environ.get("AGENT_NAME", "HuggingClaw")
87
  A2A_PEERS = os.environ.get("A2A_PEERS", "") # comma-separated peer URLs
88
 
89
- # Default model for new conversations (infer from provider if not set)
90
- OPENCLAW_DEFAULT_MODEL = os.environ.get("OPENCLAW_DEFAULT_MODEL") or (
91
- "openai/gpt-5-nano" if OPENAI_API_KEY
92
- else "zhipu/glm-4.5-air" if ZHIPU_API_KEY
93
- else "openrouter/openai/gpt-oss-20b:free"
94
- )
95
 
96
  # HF Spaces built-in env vars (auto-set by HF runtime)
97
  SPACE_HOST = os.environ.get("SPACE_HOST", "") # e.g. "tao-shen-huggingclaw.hf.space"
@@ -363,20 +349,13 @@ class OpenClawFullSync:
363
  # Set gateway token
364
  if "gateway" in cfg:
365
  cfg["gateway"]["auth"] = {"token": GATEWAY_TOKEN}
366
- if OPENAI_API_KEY and "models" in cfg and "providers" in cfg["models"] and "openai" in cfg["models"]["providers"]:
367
- cfg["models"]["providers"]["openai"]["apiKey"] = OPENAI_API_KEY
368
- if OPENAI_BASE_URL:
369
- cfg["models"]["providers"]["openai"]["baseUrl"] = OPENAI_BASE_URL
370
- elif "models" in cfg and "providers" in cfg["models"]:
371
- if not OPENAI_API_KEY:
372
- cfg["models"]["providers"].pop("openai", None)
373
- if OPENROUTER_API_KEY:
374
- if "models" in cfg and "providers" in cfg["models"] and "openrouter" in cfg["models"]["providers"]:
375
- cfg["models"]["providers"]["openrouter"]["apiKey"] = OPENROUTER_API_KEY
376
- else:
377
- if "models" in cfg and "providers" in cfg["models"]:
378
- cfg["models"]["providers"].pop("openrouter", None)
379
- print("[SYNC] No OPENROUTER_API_KEY — removed openrouter provider from config")
380
  with open(config_path, "w") as f:
381
  json.dump(cfg, f, indent=2)
382
  except Exception as e:
@@ -396,7 +375,6 @@ class OpenClawFullSync:
396
  }
397
  },
398
  "session": {"scope": "global"},
399
- "models": {"mode": "merge", "providers": {}},
400
  "agents": {"defaults": {"workspace": "~/.openclaw/workspace"}}
401
  }, f)
402
  print("[SYNC] Created minimal openclaw.json")
@@ -469,44 +447,30 @@ class OpenClawFullSync:
469
  data.setdefault("agents", {}).setdefault("defaults", {}).setdefault("model", {})
470
  data.setdefault("session", {})["scope"] = "global"
471
 
472
- # Build providers from scratch — only include providers with active API keys.
473
- # This ensures removed secrets don't leave stale providers from backup.
474
- providers = {}
475
- if OPENAI_API_KEY:
476
- providers["openai"] = {
477
- "baseUrl": OPENAI_BASE_URL,
478
- "apiKey": OPENAI_API_KEY,
479
- "api": "openai-completions",
480
- }
481
- print(f"[SYNC] Set OpenAI-compatible provider (baseUrl={OPENAI_BASE_URL})")
482
- if OPENROUTER_API_KEY:
483
- providers["openrouter"] = {
484
- "baseUrl": "https://openrouter.ai/api/v1",
485
- "apiKey": OPENROUTER_API_KEY,
486
- "api": "openai-completions",
487
- "models": [
488
- {"id": "openai/gpt-oss-20b:free", "name": "GPT-OSS-20B (Free)"},
489
- {"id": "deepseek/deepseek-chat:free", "name": "DeepSeek V3 (Free)"}
490
- ]
491
- }
492
- print("[SYNC] Set OpenRouter provider")
493
- if ZHIPU_API_KEY:
494
- providers["zhipu"] = {
495
- "baseUrl": "https://open.bigmodel.cn/api/anthropic",
496
- "apiKey": ZHIPU_API_KEY,
497
- "api": "anthropic-messages",
498
- "models": [
499
- {"id": "glm-4.5-air", "name": "GLM-4.5 Air"},
500
- {"id": "glm-4.5", "name": "GLM-4.5"},
501
- {"id": "glm-4.6", "name": "GLM-4.6"},
502
- {"id": "glm-4.7", "name": "GLM-4.7"},
503
- ]
504
- }
505
- print("[SYNC] Set Zhipu AI provider")
506
- if not providers:
507
- print("[SYNC] WARNING: No API key set (OPENAI/OPENROUTER/ZHIPU), LLM features may not work")
508
- data.setdefault("models", {})["providers"] = providers
509
- data["agents"]["defaults"]["model"]["primary"] = OPENCLAW_DEFAULT_MODEL
510
 
511
  # ── ACP (Agent Client Protocol) — native Claude Code integration ──
512
  data["acp"] = {
@@ -545,10 +509,10 @@ class OpenClawFullSync:
545
  "targetSpace": CODING_TARGET_SPACE,
546
  "targetDataset": CODING_TARGET_DATASET,
547
  "hfToken": HF_TOKEN or "",
548
- "zaiApiKey": ZAI_API_KEY or ZHIPU_API_KEY or "",
549
  }
550
  }
551
- print(f"[SYNC] Coding agent configured: space={CODING_TARGET_SPACE}, dataset={CODING_TARGET_DATASET}, zaiKey={'set' if (ZAI_API_KEY or ZHIPU_API_KEY) else 'missing'}")
552
  if "telegram" not in data["plugins"]["entries"]:
553
  data["plugins"]["entries"]["telegram"] = {"enabled": True}
554
  elif isinstance(data["plugins"]["entries"]["telegram"], dict):
@@ -689,19 +653,8 @@ class OpenClawFullSync:
689
  # Open log file
690
  log_fh = open(log_file, "a")
691
 
692
- # Prepare environment (all API keys passed through for OpenClaw)
693
  env = os.environ.copy()
694
- if OPENAI_API_KEY:
695
- env["OPENAI_API_KEY"] = OPENAI_API_KEY
696
- env["OPENAI_BASE_URL"] = OPENAI_BASE_URL
697
- if OPENROUTER_API_KEY:
698
- env["OPENROUTER_API_KEY"] = OPENROUTER_API_KEY
699
- if ZHIPU_API_KEY:
700
- env["ZHIPU_API_KEY"] = ZHIPU_API_KEY
701
- if ZAI_API_KEY:
702
- env["ZAI_API_KEY"] = ZAI_API_KEY
703
- if not OPENAI_API_KEY and not OPENROUTER_API_KEY and not ZHIPU_API_KEY:
704
- print(f"[SYNC] WARNING: No API key set (OPENAI/OPENROUTER/ZHIPU), LLM features may not work")
705
 
706
  # ── Telegram API base probe ──────────────────────────────────────
707
  # Determine working Telegram API endpoint and set env var for
@@ -731,7 +684,7 @@ class OpenClawFullSync:
731
  stderr=subprocess.STDOUT,
732
  text=True,
733
  bufsize=1, # Line buffered
734
- env=env # Pass environment with OPENROUTER_API_KEY
735
  )
736
 
737
  # Create a thread to copy output to log file; only print key lines to console
 
66
  # Use ".openclaw" - directly read/write the .openclaw folder in dataset
67
  DATASET_PATH = ".openclaw"
68
 
 
 
 
 
 
 
 
 
 
 
69
  # Z.AI API key (optional; used by Claude Code backend via api.z.ai)
70
  ZAI_API_KEY = os.environ.get("ZAI_API_KEY", "")
71
 
 
76
  AGENT_NAME = os.environ.get("AGENT_NAME", "HuggingClaw")
77
  A2A_PEERS = os.environ.get("A2A_PEERS", "") # comma-separated peer URLs
78
 
79
+ # Default model only set if user explicitly provides it; otherwise let OpenClaw decide.
80
+ OPENCLAW_DEFAULT_MODEL = os.environ.get("OPENCLAW_DEFAULT_MODEL", "")
 
 
 
 
81
 
82
  # HF Spaces built-in env vars (auto-set by HF runtime)
83
  SPACE_HOST = os.environ.get("SPACE_HOST", "") # e.g. "tao-shen-huggingclaw.hf.space"
 
349
  # Set gateway token
350
  if "gateway" in cfg:
351
  cfg["gateway"]["auth"] = {"token": GATEWAY_TOKEN}
352
+ # Don't manage providers leave them for OpenClaw to handle.
353
+ # Just remove template entries with unresolved ${...} placeholders
354
+ # so OpenClaw starts clean and discovers providers from env vars.
355
+ providers = cfg.get("models", {}).get("providers", {})
356
+ for pid in list(providers.keys()):
357
+ if str(providers[pid].get("apiKey", "")).startswith("${"):
358
+ providers.pop(pid, None)
 
 
 
 
 
 
 
359
  with open(config_path, "w") as f:
360
  json.dump(cfg, f, indent=2)
361
  except Exception as e:
 
375
  }
376
  },
377
  "session": {"scope": "global"},
 
378
  "agents": {"defaults": {"workspace": "~/.openclaw/workspace"}}
379
  }, f)
380
  print("[SYNC] Created minimal openclaw.json")
 
447
  data.setdefault("agents", {}).setdefault("defaults", {}).setdefault("model", {})
448
  data.setdefault("session", {})["scope"] = "global"
449
 
450
+ # ── Provider cleanup ─────────────────────────────────────────────
451
+ # Don't manage providers let OpenClaw handle them natively from
452
+ # its own env var support. We only clean up stale providers that
453
+ # were restored from the dataset backup with hardcoded API keys
454
+ # that no longer exist in the current environment.
455
+ restored_providers = data.get("models", {}).get("providers", {})
456
+ if restored_providers:
457
+ stale = []
458
+ for pid, pcfg in list(restored_providers.items()):
459
+ api_key = pcfg.get("apiKey", "")
460
+ # Skip placeholder-style keys (${VAR}) — OpenClaw resolves these
461
+ if not api_key or str(api_key).startswith("${"):
462
+ continue
463
+ # Hardcoded key from backup — check if it's still in env
464
+ # We can't know which env var it came from, so check if
465
+ # the exact key value exists in any current env var
466
+ if api_key not in os.environ.values():
467
+ stale.append(pid)
468
+ for pid in stale:
469
+ del restored_providers[pid]
470
+ print(f"[SYNC] Removed stale provider '{pid}' (API key no longer in environment)")
471
+
472
+ if OPENCLAW_DEFAULT_MODEL:
473
+ data["agents"]["defaults"]["model"]["primary"] = OPENCLAW_DEFAULT_MODEL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
  # ── ACP (Agent Client Protocol) — native Claude Code integration ──
476
  data["acp"] = {
 
509
  "targetSpace": CODING_TARGET_SPACE,
510
  "targetDataset": CODING_TARGET_DATASET,
511
  "hfToken": HF_TOKEN or "",
512
+ "zaiApiKey": ZAI_API_KEY or os.environ.get("ZHIPU_API_KEY", ""),
513
  }
514
  }
515
+ print(f"[SYNC] Coding agent configured: space={CODING_TARGET_SPACE}, dataset={CODING_TARGET_DATASET}, zaiKey={'set' if (ZAI_API_KEY or os.environ.get('ZHIPU_API_KEY')) else 'missing'}")
516
  if "telegram" not in data["plugins"]["entries"]:
517
  data["plugins"]["entries"]["telegram"] = {"enabled": True}
518
  elif isinstance(data["plugins"]["entries"]["telegram"], dict):
 
653
  # Open log file
654
  log_fh = open(log_file, "a")
655
 
656
+ # Pass entire environment to OpenClaw (all API keys are already in os.environ)
657
  env = os.environ.copy()
 
 
 
 
 
 
 
 
 
 
 
658
 
659
  # ── Telegram API base probe ──────────────────────────────────────
660
  # Determine working Telegram API endpoint and set env var for
 
684
  stderr=subprocess.STDOUT,
685
  text=True,
686
  bufsize=1, # Line buffered
687
+ env=env # Pass full environment (all API keys)
688
  )
689
 
690
  # Create a thread to copy output to log file; only print key lines to console