2525
2626Usage::
2727
28+ # Set OPENAI_BASE_URL / OPENAI_API_KEY, or pass ``proxy=...`` and let
29+ # the adapter inject them into the OpenClaw subprocess.
2830 from lagent.adapters.openclaw import OpenClawAdapter
2931
30- agent = OpenClawAdapter(thinking='medium', timeout=120)
32+ agent = OpenClawAdapter(model='gpt-4o-mini', thinking='medium', timeout=120)
3133 r1 = await agent("What is 2+2?")
3234 r2 = await agent("Now multiply by 3") # multi-turn via --session-id
3335"""
3436
3537import json
3638import os
3739import shlex
40+ import shutil
41+ from pathlib import Path
3842from typing import List , Optional
3943
4044from .cli_adapter import CLIAgentAdapter
@@ -44,12 +48,15 @@ class OpenClawAdapter(CLIAgentAdapter):
4448 """Wraps the ``openclaw`` CLI as an :class:`AsyncExternalAgent`.
4549
4650 Args:
51+ model: Model id exposed by the configured OpenAI-compatible backend.
4752 thinking: Thinking level (``off`` / ``minimal`` / ``low`` /
4853 ``medium`` / ``high`` / ``xhigh``).
4954 agent_id: OpenClaw agent id (``--agent``). Default: ``"main"``.
5055 json_output: Pass ``--json`` and parse the JSON envelope to
5156 extract ``sessionId`` (multi-turn) and the reply text.
5257 Default: True.
58+ openclaw_home: OpenClaw state/config directory. Default:
59+ ``OPENCLAW_HOME`` / ``OPENCLAW_STATE_DIR`` / ``~/.openclaw``.
5360 nvm_dir: If set, wrap the spawn in ``bash -lc`` and source
5461 ``$nvm_dir/nvm.sh`` before invoking ``openclaw``. Use this
5562 when the binary is provided by nvm and not on PATH.
@@ -66,6 +73,7 @@ def __init__(
6673 thinking : str = 'medium' ,
6774 agent_id : Optional [str ] = 'main' ,
6875 json_output : bool = True ,
76+ openclaw_home : Optional [str ] = None ,
6977 nvm_dir : Optional [str ] = None ,
7078 node_version : str = '22' ,
7179 binary : str = 'openclaw' ,
@@ -74,14 +82,34 @@ def __init__(
7482 kwargs .setdefault ('name' , 'openclaw' )
7583 kwargs .setdefault ('description' , 'OpenClaw personal AI assistant' )
7684 super ().__init__ (binary = binary , ** kwargs )
85+ if not model :
86+ raise ValueError ('OpenClawAdapter requires `model`.' )
7787 self .model = model
7888 self .thinking = thinking
7989 self .agent_id = agent_id
8090 self .json_output = json_output
8191 self .nvm_dir = nvm_dir
8292 self .node_version = node_version
93+ self .provider = 'custom-openai'
94+ self .openclaw_home = Path (
95+ openclaw_home
96+ or self .env_vars .get ('OPENCLAW_HOME' )
97+ or self .env_vars .get ('OPENCLAW_STATE_DIR' )
98+ or os .environ .get ('OPENCLAW_HOME' )
99+ or os .environ .get ('OPENCLAW_STATE_DIR' )
100+ or Path .home () / '.openclaw'
101+ ).expanduser ()
102+ self .openclaw_config_path = Path (
103+ self .env_vars .get ('OPENCLAW_CONFIG_PATH' )
104+ or os .environ .get ('OPENCLAW_CONFIG_PATH' )
105+ or self .openclaw_home / 'openclaw.json'
106+ ).expanduser ()
107+ self .env_vars .setdefault ('OPENCLAW_HOME' , str (self .openclaw_home ))
108+ self .env_vars .setdefault ('OPENCLAW_STATE_DIR' , str (self .openclaw_home ))
109+ self .env_vars .setdefault ('OPENCLAW_CONFIG_PATH' , str (self .openclaw_config_path ))
110+ self .env_vars .setdefault ('NO_COLOR' , '1' )
83111 self ._cli_session_id : Optional [str ] = None
84- self ._write_openclaw_config ()
112+ self ._runtime_config_written = False
85113
86114 def setup (self ) -> None :
87115 if self .nvm_dir :
@@ -95,6 +123,11 @@ def setup(self) -> None:
95123 return
96124 super ().setup ()
97125
126+ async def run_external_async (self , task : str , ** kwargs ) -> str :
127+ # 关键点:SessionClient 的端口在 forward() 里启动后才确定,因此配置必须运行前写。
128+ self ._write_openclaw_config ()
129+ return await super ().run_external_async (task , ** kwargs )
130+
98131 def _build_argv (self , task : str ) -> List [str ]:
99132 cli_args = ['agent' , '--local' , '--message' , task , '--thinking' , self .thinking ]
100133 if self .json_output :
@@ -123,6 +156,73 @@ def reset_session(self) -> None:
123156 """Forget the captured session id; the next call starts fresh."""
124157 self ._cli_session_id = None
125158
159+ def _write_openclaw_config (self ) -> None :
160+ env = self ._build_env ()
161+ base_url = (env .get ('OPENAI_BASE_URL' ) or '' ).rstrip ('/' )
162+ api_key = env .get ('OPENAI_API_KEY' )
163+ if not base_url :
164+ raise RuntimeError (
165+ 'OpenClawAdapter needs OPENAI_BASE_URL in subprocess env.'
166+ )
167+ if not api_key :
168+ raise RuntimeError (
169+ 'OpenClawAdapter needs OPENAI_API_KEY in subprocess env.'
170+ )
171+
172+ agent_id = self .agent_id or 'main'
173+ model_ref = f'{ self .provider } /{ self .model } '
174+ workspace = self .working_dir or os .environ .get ('TASK_WORKSPACE' ) or os .getcwd ()
175+ agents = {
176+ 'defaults' : {
177+ 'model' : {'primary' : model_ref },
178+ 'workspace' : workspace ,
179+ }
180+ }
181+ if agent_id != 'main' :
182+ agents ['list' ] = [
183+ {
184+ 'id' : agent_id ,
185+ 'model' : {'primary' : model_ref },
186+ 'workspace' : workspace ,
187+ }
188+ ]
189+
190+ config = {
191+ 'models' : {
192+ 'mode' : 'merge' ,
193+ 'providers' : {
194+ self .provider : {
195+ 'baseUrl' : base_url ,
196+ 'apiKey' : '$OPENAI_API_KEY' ,
197+ 'api' : os .environ .get ('OPENCLAW_PROVIDER_API' , 'openai-completions' ),
198+ 'models' : [
199+ {
200+ 'id' : self .model ,
201+ 'name' : self .model ,
202+ 'reasoning' : True ,
203+ 'input' : ['text' ],
204+ 'contextWindow' : int (os .environ .get ('OPENCLAW_CONTEXT_WINDOW' , '128000' )),
205+ 'maxTokens' : int (os .environ .get ('OPENCLAW_MAX_TOKENS' , '16384' )),
206+ }
207+ ],
208+ }
209+ },
210+ },
211+ 'agents' : agents ,
212+ }
213+
214+ self .openclaw_config_path .parent .mkdir (parents = True , exist_ok = True )
215+ self .openclaw_config_path .write_text (
216+ json .dumps (config , indent = 2 , ensure_ascii = False ),
217+ encoding = 'utf-8' ,
218+ )
219+
220+ if not self ._runtime_config_written :
221+ sessions_dir = self .openclaw_home / 'agents' / agent_id / 'sessions'
222+ shutil .rmtree (sessions_dir , ignore_errors = True )
223+ sessions_dir .mkdir (parents = True , exist_ok = True )
224+ self ._runtime_config_written = True
225+
126226 def _default_parse (self , stdout : str , stderr : str ) -> str :
127227 if not self .json_output :
128228 return stdout .strip ()
0 commit comments