python-codex 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
pycodex/bootstrap.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import typing
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .agent import Agent
|
|
9
|
+
from .context import ContextConfig
|
|
10
|
+
from .model import (
|
|
11
|
+
DEFAULT_CODEX_CONFIG_PATH,
|
|
12
|
+
ResponsesModelClient,
|
|
13
|
+
ResponsesProviderConfig,
|
|
14
|
+
)
|
|
15
|
+
from .runtime import AgentRuntime
|
|
16
|
+
from .runtime_services import AgentRuntimeEnvironment, create_agent_runtime_environment
|
|
17
|
+
from .utils import get_debug_dir, load_codex_dotenv, uuid7_string
|
|
18
|
+
from .utils.session_persist import rollout_path_for_session
|
|
19
|
+
|
|
20
|
+
LOCAL_RESPONSES_SERVER_API_KEY_ENV = "PYCODEX_LOCAL_RESPONSES_SERVER_KEY"
|
|
21
|
+
CLI_ORIGINATOR = "codex-tui"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def launch_chat_completion_compat_server(*args, **kwargs):
|
|
25
|
+
from responses_server import (
|
|
26
|
+
launch_chat_completion_compat_server as launch_compat_server,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return launch_compat_server(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve_vllm_model(
|
|
33
|
+
endpoint: "str",
|
|
34
|
+
provider_config: "ResponsesProviderConfig",
|
|
35
|
+
timeout_seconds: "float",
|
|
36
|
+
) -> "str":
|
|
37
|
+
from responses_server import CompatServerConfig
|
|
38
|
+
|
|
39
|
+
normalized = CompatServerConfig.from_base_url(endpoint)
|
|
40
|
+
probe_config = replace(
|
|
41
|
+
provider_config,
|
|
42
|
+
provider_name="vllm",
|
|
43
|
+
base_url=normalized.outcomming_base_url,
|
|
44
|
+
api_key_env=None,
|
|
45
|
+
query_params={},
|
|
46
|
+
responses_lite_override=False,
|
|
47
|
+
)
|
|
48
|
+
probe_client = ResponsesModelClient(
|
|
49
|
+
probe_config,
|
|
50
|
+
timeout_seconds,
|
|
51
|
+
originator=CLI_ORIGINATOR,
|
|
52
|
+
)
|
|
53
|
+
models = probe_client.list_models_sync()
|
|
54
|
+
if not models:
|
|
55
|
+
raise RuntimeError(
|
|
56
|
+
"vLLM endpoint returned no models from "
|
|
57
|
+
f"{normalized.outcomming_models_url()}"
|
|
58
|
+
)
|
|
59
|
+
return models[-1]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def configure_loguru() -> "None":
|
|
63
|
+
try:
|
|
64
|
+
from loguru import logger
|
|
65
|
+
except ImportError: # pragma: no cover - dependency may be absent in minimal envs
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
logger.remove()
|
|
69
|
+
debug_dir = get_debug_dir()
|
|
70
|
+
if debug_dir is not None:
|
|
71
|
+
logger.add(str(debug_dir / "loguru.log"), level="DEBUG")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if os.environ.get("PYCODEX_DEBUG_STDERR", "").strip().lower() in {
|
|
75
|
+
"1",
|
|
76
|
+
"true",
|
|
77
|
+
"yes",
|
|
78
|
+
"on",
|
|
79
|
+
}:
|
|
80
|
+
logger.add(sys.stderr, level="DEBUG")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_tools(
|
|
84
|
+
runtime_environment: "typing.Union[AgentRuntimeEnvironment, None]" = None,
|
|
85
|
+
exec_mode: "bool" = False,
|
|
86
|
+
cwd: "typing.Union[str, Path, None]" = None,
|
|
87
|
+
toolset: "typing.Union[typing.Iterable[str], None]" = None,
|
|
88
|
+
):
|
|
89
|
+
from .tools import (
|
|
90
|
+
ApplyPatchTool,
|
|
91
|
+
ClockManager,
|
|
92
|
+
ClockTool,
|
|
93
|
+
CloseAgentTool,
|
|
94
|
+
CodeModeManager,
|
|
95
|
+
ExecCommandTool,
|
|
96
|
+
ExecTool,
|
|
97
|
+
GrepFilesTool,
|
|
98
|
+
ListDirTool,
|
|
99
|
+
ReadFileTool,
|
|
100
|
+
Registry,
|
|
101
|
+
RequestPermissionsTool,
|
|
102
|
+
RequestUserInputTool,
|
|
103
|
+
ResumeAgentTool,
|
|
104
|
+
SendInputTool,
|
|
105
|
+
ShellCommandTool,
|
|
106
|
+
ShellTool,
|
|
107
|
+
SpawnAgentTool,
|
|
108
|
+
UnifiedExecManager,
|
|
109
|
+
UpdatePlanTool,
|
|
110
|
+
ViewImageTool,
|
|
111
|
+
WaitAgentTool,
|
|
112
|
+
WaitTool,
|
|
113
|
+
WebSearchTool,
|
|
114
|
+
WriteStdinTool,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
118
|
+
registry = Registry(runtime_environment)
|
|
119
|
+
code_mode_manager = CodeModeManager(registry, cwd=cwd)
|
|
120
|
+
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
121
|
+
clock_manager = ClockManager()
|
|
122
|
+
exec_tool = ExecTool(code_mode_manager)
|
|
123
|
+
wait_tool = WaitTool(code_mode_manager)
|
|
124
|
+
web_search_tool = WebSearchTool()
|
|
125
|
+
update_plan_tool = UpdatePlanTool(runtime_environment.plan_store)
|
|
126
|
+
request_user_input_tool = RequestUserInputTool()
|
|
127
|
+
request_permissions_tool = RequestPermissionsTool(
|
|
128
|
+
runtime_environment.request_permissions_manager
|
|
129
|
+
)
|
|
130
|
+
spawn_agent_tool = SpawnAgentTool(runtime_environment.subagent_manager)
|
|
131
|
+
send_input_tool = SendInputTool(runtime_environment.subagent_manager)
|
|
132
|
+
resume_agent_tool = ResumeAgentTool(runtime_environment.subagent_manager)
|
|
133
|
+
wait_agent_tool = WaitAgentTool(runtime_environment.subagent_manager)
|
|
134
|
+
close_agent_tool = CloseAgentTool(runtime_environment.subagent_manager)
|
|
135
|
+
apply_patch_tool = ApplyPatchTool(cwd=cwd)
|
|
136
|
+
shell_tool = ShellTool(cwd=cwd)
|
|
137
|
+
shell_command_tool = ShellCommandTool(cwd=cwd)
|
|
138
|
+
exec_command_tool = ExecCommandTool(unified_exec_manager)
|
|
139
|
+
write_stdin_tool = WriteStdinTool(unified_exec_manager)
|
|
140
|
+
clock_tool = ClockTool(clock_manager)
|
|
141
|
+
grep_files_tool = GrepFilesTool(cwd=cwd)
|
|
142
|
+
read_file_tool = ReadFileTool()
|
|
143
|
+
list_dir_tool = ListDirTool()
|
|
144
|
+
view_image_tool = ViewImageTool(cwd=cwd)
|
|
145
|
+
tools = (
|
|
146
|
+
shell_tool,
|
|
147
|
+
shell_command_tool,
|
|
148
|
+
exec_command_tool,
|
|
149
|
+
write_stdin_tool,
|
|
150
|
+
clock_tool,
|
|
151
|
+
exec_tool,
|
|
152
|
+
wait_tool,
|
|
153
|
+
web_search_tool,
|
|
154
|
+
update_plan_tool,
|
|
155
|
+
request_user_input_tool,
|
|
156
|
+
request_permissions_tool,
|
|
157
|
+
spawn_agent_tool,
|
|
158
|
+
send_input_tool,
|
|
159
|
+
resume_agent_tool,
|
|
160
|
+
wait_agent_tool,
|
|
161
|
+
close_agent_tool,
|
|
162
|
+
apply_patch_tool,
|
|
163
|
+
grep_files_tool,
|
|
164
|
+
read_file_tool,
|
|
165
|
+
list_dir_tool,
|
|
166
|
+
view_image_tool,
|
|
167
|
+
)
|
|
168
|
+
if toolset is not None:
|
|
169
|
+
available_tools = {tool.name: tool for tool in tools}
|
|
170
|
+
toolset = tuple(toolset)
|
|
171
|
+
unknown_tools = set(toolset) - set(available_tools)
|
|
172
|
+
if unknown_tools:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
"unknown toolset entries: {0}".format(", ".join(sorted(unknown_tools)))
|
|
175
|
+
)
|
|
176
|
+
tools = tuple(available_tools[name] for name in toolset)
|
|
177
|
+
elif exec_mode:
|
|
178
|
+
tools = (
|
|
179
|
+
exec_command_tool,
|
|
180
|
+
write_stdin_tool,
|
|
181
|
+
clock_tool,
|
|
182
|
+
update_plan_tool,
|
|
183
|
+
request_user_input_tool,
|
|
184
|
+
apply_patch_tool,
|
|
185
|
+
web_search_tool,
|
|
186
|
+
view_image_tool,
|
|
187
|
+
spawn_agent_tool,
|
|
188
|
+
send_input_tool,
|
|
189
|
+
resume_agent_tool,
|
|
190
|
+
wait_agent_tool,
|
|
191
|
+
close_agent_tool,
|
|
192
|
+
)
|
|
193
|
+
for tool in tools:
|
|
194
|
+
registry.register(tool)
|
|
195
|
+
return registry
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def get_subagent_tools(
|
|
199
|
+
runtime_environment: "typing.Union[AgentRuntimeEnvironment, None]" = None,
|
|
200
|
+
cwd: "typing.Union[str, Path, None]" = None,
|
|
201
|
+
):
|
|
202
|
+
from .tools import (
|
|
203
|
+
ApplyPatchTool,
|
|
204
|
+
ExecCommandTool,
|
|
205
|
+
Registry,
|
|
206
|
+
UnifiedExecManager,
|
|
207
|
+
UpdatePlanTool,
|
|
208
|
+
ViewImageTool,
|
|
209
|
+
WebSearchTool,
|
|
210
|
+
WriteStdinTool,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
runtime_environment = runtime_environment or create_agent_runtime_environment()
|
|
214
|
+
registry = Registry(runtime_environment)
|
|
215
|
+
unified_exec_manager = UnifiedExecManager(cwd=cwd)
|
|
216
|
+
registry.register(ExecCommandTool(unified_exec_manager))
|
|
217
|
+
registry.register(WriteStdinTool(unified_exec_manager))
|
|
218
|
+
registry.register(UpdatePlanTool(runtime_environment.plan_store))
|
|
219
|
+
registry.register(ApplyPatchTool(cwd=cwd))
|
|
220
|
+
registry.register(WebSearchTool())
|
|
221
|
+
registry.register(ViewImageTool(cwd=cwd))
|
|
222
|
+
return registry
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def build_agent(
|
|
226
|
+
client,
|
|
227
|
+
config_path: "typing.Union[str, Path]" = DEFAULT_CODEX_CONFIG_PATH,
|
|
228
|
+
profile: "typing.Union[str, None]" = None,
|
|
229
|
+
system_prompt: "typing.Union[str, None]" = None,
|
|
230
|
+
extra_contextual_user_messages: "typing.Iterable[str]" = (),
|
|
231
|
+
cwd: "typing.Union[str, Path, None]" = None,
|
|
232
|
+
toolset: "typing.Union[typing.Iterable[str], None]" = None,
|
|
233
|
+
) -> "Agent":
|
|
234
|
+
config_path = str(config_path)
|
|
235
|
+
resolved_cwd = Path(cwd or Path.cwd()).resolve()
|
|
236
|
+
context_config = replace(
|
|
237
|
+
ContextConfig.from_codex_config(config_path, profile),
|
|
238
|
+
base_instructions_override=system_prompt,
|
|
239
|
+
extra_contextual_user_messages=tuple(extra_contextual_user_messages),
|
|
240
|
+
cwd=resolved_cwd,
|
|
241
|
+
)
|
|
242
|
+
runtime_environment = create_agent_runtime_environment()
|
|
243
|
+
|
|
244
|
+
def make_subagent_runtime_builder(base_client):
|
|
245
|
+
def build_subagent_runtime(
|
|
246
|
+
model_override: "typing.Union[str, None]",
|
|
247
|
+
reasoning_effort_override: "typing.Union[str, None]",
|
|
248
|
+
initial_history=(),
|
|
249
|
+
session_id: "typing.Union[str, None]" = None,
|
|
250
|
+
) -> "AgentRuntime":
|
|
251
|
+
nested_client = base_client.with_overrides(
|
|
252
|
+
model_override,
|
|
253
|
+
reasoning_effort_override,
|
|
254
|
+
session_id=session_id,
|
|
255
|
+
openai_subagent="collab_spawn",
|
|
256
|
+
)
|
|
257
|
+
subagent_agent_runtime_environment = create_agent_runtime_environment()
|
|
258
|
+
subagent_agent_runtime_environment.subagent_manager.set_runtime_builder(
|
|
259
|
+
make_subagent_runtime_builder(nested_client)
|
|
260
|
+
)
|
|
261
|
+
sub_agent = Agent(
|
|
262
|
+
nested_client,
|
|
263
|
+
get_subagent_tools(
|
|
264
|
+
subagent_agent_runtime_environment, cwd=resolved_cwd
|
|
265
|
+
),
|
|
266
|
+
context_config,
|
|
267
|
+
initial_history=tuple(initial_history),
|
|
268
|
+
session_id=session_id,
|
|
269
|
+
)
|
|
270
|
+
return AgentRuntime(sub_agent)
|
|
271
|
+
|
|
272
|
+
return build_subagent_runtime
|
|
273
|
+
|
|
274
|
+
runtime_environment.subagent_manager.set_runtime_builder(
|
|
275
|
+
make_subagent_runtime_builder(client)
|
|
276
|
+
)
|
|
277
|
+
session_id = uuid7_string()
|
|
278
|
+
return Agent(
|
|
279
|
+
client,
|
|
280
|
+
get_tools(
|
|
281
|
+
runtime_environment,
|
|
282
|
+
exec_mode=True,
|
|
283
|
+
cwd=resolved_cwd,
|
|
284
|
+
toolset=toolset,
|
|
285
|
+
),
|
|
286
|
+
context_config,
|
|
287
|
+
session_file_path=rollout_path_for_session(
|
|
288
|
+
context_config.codex_home, session_id
|
|
289
|
+
),
|
|
290
|
+
session_id=session_id,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def build_model(
|
|
295
|
+
config_path: "typing.Union[str, Path]" = DEFAULT_CODEX_CONFIG_PATH,
|
|
296
|
+
profile: "typing.Union[str, None]" = None,
|
|
297
|
+
timeout_seconds: "float" = 120.0,
|
|
298
|
+
managed_responses_base_url: "typing.Union[str, None]" = None,
|
|
299
|
+
vllm_endpoint: "typing.Union[str, None]" = None,
|
|
300
|
+
use_chat_completion: "typing.Union[bool, None]" = None,
|
|
301
|
+
use_messages: "bool" = False,
|
|
302
|
+
):
|
|
303
|
+
load_codex_dotenv(config_path)
|
|
304
|
+
provider_config = ResponsesProviderConfig.from_codex_config(
|
|
305
|
+
config_path,
|
|
306
|
+
profile,
|
|
307
|
+
)
|
|
308
|
+
if use_chat_completion is None:
|
|
309
|
+
use_chat_completion = bool(provider_config.use_chat_completion)
|
|
310
|
+
if use_chat_completion and use_messages:
|
|
311
|
+
raise ValueError("--use-chat-completion and --use-messages cannot be combined")
|
|
312
|
+
if vllm_endpoint and use_messages:
|
|
313
|
+
raise ValueError("--vllm-endpoint and --use-messages cannot be combined")
|
|
314
|
+
uses_local_responses_compat = (
|
|
315
|
+
managed_responses_base_url is not None
|
|
316
|
+
or vllm_endpoint is not None
|
|
317
|
+
or bool(use_chat_completion)
|
|
318
|
+
or use_messages
|
|
319
|
+
)
|
|
320
|
+
if vllm_endpoint is not None:
|
|
321
|
+
provider_config = replace(
|
|
322
|
+
provider_config,
|
|
323
|
+
model=_resolve_vllm_model(
|
|
324
|
+
vllm_endpoint,
|
|
325
|
+
provider_config,
|
|
326
|
+
timeout_seconds,
|
|
327
|
+
),
|
|
328
|
+
)
|
|
329
|
+
url, key_env = provider_config.base_url, provider_config.api_key_env
|
|
330
|
+
if managed_responses_base_url is not None:
|
|
331
|
+
url, key_env = (
|
|
332
|
+
managed_responses_base_url,
|
|
333
|
+
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
334
|
+
)
|
|
335
|
+
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
336
|
+
elif vllm_endpoint or use_chat_completion or use_messages:
|
|
337
|
+
if vllm_endpoint:
|
|
338
|
+
managed_server = launch_chat_completion_compat_server(
|
|
339
|
+
vllm_endpoint,
|
|
340
|
+
model_provider="vllm",
|
|
341
|
+
)
|
|
342
|
+
else:
|
|
343
|
+
managed_server = launch_chat_completion_compat_server(
|
|
344
|
+
provider_config.base_url,
|
|
345
|
+
provider_config.api_key_env,
|
|
346
|
+
model_provider=provider_config.provider_name,
|
|
347
|
+
outcomming_api=("messages" if use_messages else "chat_completions"),
|
|
348
|
+
)
|
|
349
|
+
atexit.register(managed_server.stop)
|
|
350
|
+
url, key_env = (
|
|
351
|
+
managed_server.base_url,
|
|
352
|
+
LOCAL_RESPONSES_SERVER_API_KEY_ENV,
|
|
353
|
+
)
|
|
354
|
+
os.environ.setdefault(LOCAL_RESPONSES_SERVER_API_KEY_ENV, "dummy")
|
|
355
|
+
|
|
356
|
+
provider_config = replace(
|
|
357
|
+
provider_config,
|
|
358
|
+
base_url=url,
|
|
359
|
+
api_key_env=key_env,
|
|
360
|
+
responses_lite_override=(
|
|
361
|
+
False
|
|
362
|
+
if uses_local_responses_compat
|
|
363
|
+
else provider_config.responses_lite_override
|
|
364
|
+
),
|
|
365
|
+
)
|
|
366
|
+
return ResponsesModelClient(
|
|
367
|
+
provider_config,
|
|
368
|
+
timeout_seconds,
|
|
369
|
+
originator=CLI_ORIGINATOR,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def build_runtime(agent: "Agent") -> "AgentRuntime":
|
|
374
|
+
runtime = AgentRuntime(agent)
|
|
375
|
+
register_connection_commands(runtime)
|
|
376
|
+
return runtime
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def register_connection_commands(runtime):
|
|
380
|
+
link = None
|
|
381
|
+
|
|
382
|
+
async def unlink(argument):
|
|
383
|
+
nonlocal link
|
|
384
|
+
if argument:
|
|
385
|
+
raise ValueError("Usage: /unlink")
|
|
386
|
+
if link is None:
|
|
387
|
+
return {"kind": "connection", "lines": ["No Feishu card is linked."]}
|
|
388
|
+
link.detach()
|
|
389
|
+
link = None
|
|
390
|
+
return {"kind": "connection", "lines": ["Unlinked Feishu card."]}
|
|
391
|
+
|
|
392
|
+
async def connect(target):
|
|
393
|
+
nonlocal link
|
|
394
|
+
if not target:
|
|
395
|
+
raise ValueError("Usage: /link <feishu-email|open_id|chat_id>")
|
|
396
|
+
if link is not None:
|
|
397
|
+
raise RuntimeError("A Feishu card is already linked. Use /unlink first.")
|
|
398
|
+
from .feishu_link import PycodexRuntimeLink
|
|
399
|
+
|
|
400
|
+
link = await PycodexRuntimeLink(runtime, target).start_async()
|
|
401
|
+
return {
|
|
402
|
+
"kind": "connection",
|
|
403
|
+
"lines": [
|
|
404
|
+
"Linked Feishu card: session_key={0} message_id={1}".format(
|
|
405
|
+
link.session_key,
|
|
406
|
+
link.message_id or "-",
|
|
407
|
+
),
|
|
408
|
+
],
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async def close_link():
|
|
412
|
+
if link is not None:
|
|
413
|
+
await unlink("")
|
|
414
|
+
|
|
415
|
+
runtime.register_command("link", connect)
|
|
416
|
+
runtime.register_command("unlink", unlink)
|
|
417
|
+
runtime.add_close_handler(close_link)
|