@researai/deepscientist 1.5.8 → 1.5.11
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/LICENSE +186 -21
- package/README.md +108 -95
- package/assets/branding/connector-qq.png +0 -0
- package/assets/branding/connector-rokid.png +0 -0
- package/assets/branding/connector-weixin.png +0 -0
- package/assets/branding/projects.png +0 -0
- package/bin/ds.js +172 -13
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +308 -70
- package/docs/en/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +41 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
- package/docs/en/11_LICENSE_AND_RISK.md +256 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +427 -0
- package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +79 -0
- package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
- package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
- package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
- package/docs/images/weixin/weixin-settings-bind.svg +57 -0
- package/docs/zh/00_QUICK_START.md +315 -74
- package/docs/zh/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +41 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
- package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +423 -0
- package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +126 -0
- package/install.sh +0 -34
- package/package.json +3 -3
- package/pyproject.toml +2 -2
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/metrics.py +1 -3
- package/src/deepscientist/artifact/service.py +1347 -111
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/service.py +9 -0
- package/src/deepscientist/bridges/builtins.py +2 -0
- package/src/deepscientist/bridges/connectors.py +447 -0
- package/src/deepscientist/channels/__init__.py +2 -0
- package/src/deepscientist/channels/builtins.py +3 -1
- package/src/deepscientist/channels/qq.py +1 -1
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +7 -1
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +317 -0
- package/src/deepscientist/config/models.py +22 -2
- package/src/deepscientist/config/service.py +431 -60
- package/src/deepscientist/connector/__init__.py +4 -0
- package/src/deepscientist/connector/connector_profiles.py +481 -0
- package/src/deepscientist/connector/lingzhu_support.py +668 -0
- package/src/deepscientist/connector/qq_profiles.py +206 -0
- package/src/deepscientist/connector/weixin_support.py +663 -0
- package/src/deepscientist/connector_profiles.py +1 -374
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +295 -5
- package/src/deepscientist/daemon/api/router.py +16 -1
- package/src/deepscientist/daemon/app.py +1130 -61
- package/src/deepscientist/doctor.py +5 -2
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +14 -5
- package/src/deepscientist/prompts/builder.py +29 -1
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +152 -2
- package/src/deepscientist/quest/service.py +169 -43
- package/src/deepscientist/quest/stage_views.py +172 -9
- package/src/deepscientist/registries/baseline.py +56 -4
- package/src/deepscientist/runners/codex.py +55 -3
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/weixin.md +230 -0
- package/src/prompts/system.md +9 -0
- package/src/skills/idea/SKILL.md +16 -0
- package/src/skills/idea/references/literature-survey-template.md +24 -0
- package/src/skills/idea/references/related-work-playbook.md +4 -0
- package/src/skills/idea/references/selection-gate.md +9 -0
- package/src/skills/write/SKILL.md +1 -1
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-m2FNtwbn.js → AiManusChatView-D0mTXG4-.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-BMTF8EGL.js → AnalysisPlugin-Db0cTXxm.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BEOWgxCI.js → CliPlugin-DrV8je02.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BCXvjqmb.js → CodeEditorPlugin-QXMSCH71.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DaJcy3nD.js → CodeViewerPlugin-7hhtWj_E.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ByfeIq4K.js → DocViewerPlugin-BWMSnRJe.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-Cksf3VZ-.js → GitDiffViewerPlugin-7J9h9Vy_.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-CFz-OsTS.js → ImageViewerPlugin-CHJl_0lr.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-CJ1cJzoX.js → LabCopilotPanel-1qSow1es.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-BF3dVJwa.js → LabPlugin-eQpPPCEp.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DDkwZ6Sj.js → LatexPlugin-BwRfi89Z.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-HAuvurcT.js → MarkdownViewerPlugin-836PVQWV.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-BtoTYy2C.js → MarketplacePlugin-C2y_556i.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-CSJYx7b-.js → NotebookEditor-BRzJbGsn.js} +12 -12
- package/src/ui/dist/assets/{NotebookEditor-DQgRezm_.js → NotebookEditor-DIX7Mlzu.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-DPa_-fv6.js → PdfLoader-DzRaTAlq.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-BZpXOEjm.js → PdfMarkdownPlugin-DZUfIUnp.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BT8a6wGR.js → PdfViewerPlugin-BwtICzue.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-D_blveZi.js → SearchPlugin-DHeIAMsx.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Btx0M3hX.js → TextViewerPlugin-C3tCmFox.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-DImJO4rO.js → VNCViewer-CQsKVm3t.js} +10 -10
- package/src/ui/dist/assets/bot-BEA2vWuK.js +21 -0
- package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
- package/src/ui/dist/assets/{code-BUfXGJSl.js → code-XfbSR8K2.js} +1 -1
- package/src/ui/dist/assets/{file-content-VqamwI3X.js → file-content-BjxNaIfy.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-C_wOoS7a.js → file-diff-panel-D_lLVQk0.js} +1 -1
- package/src/ui/dist/assets/{file-socket-D2bTuMVP.js → file-socket-D9x_5vlY.js} +1 -1
- package/src/ui/dist/assets/{image-BZkGJ4mM.js → image-BhWT33W1.js} +1 -1
- package/src/ui/dist/assets/{index-DdRW6RMJ.js → index--c4iXtuy.js} +12 -12
- package/src/ui/dist/assets/{index-CxkvSeKw.js → index-BDxipwrC.js} +2 -2
- package/src/ui/dist/assets/{index-DjggJovS.js → index-DZTZ8mWP.js} +14934 -9613
- package/src/ui/dist/assets/{index-DXZ1daiJ.css → index-Dqj-Mjb4.css} +2 -13
- package/src/ui/dist/assets/index-PJbSbPTy.js +25 -0
- package/src/ui/dist/assets/{monaco-DHMc7kKM.js → monaco-K8izTGgo.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DfBors6y.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-B85oCgCS.js → popover-yFK1J4fL.js} +1 -1
- package/src/ui/dist/assets/{project-sync-DOMCcPac.js → project-sync-PENr2zcz.js} +1 -74
- package/src/ui/dist/assets/select-CAbJDfYv.js +1690 -0
- package/src/ui/dist/assets/{sigma-BO2rQrl3.js → sigma-DEuYJqTl.js} +1 -1
- package/src/ui/dist/assets/{index-D9QIGcmc.js → square-check-big-omoSUmcd.js} +2 -13
- package/src/ui/dist/assets/{trash-BsVEH_dV.js → trash--F119N47.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-b8L6JuZm.js → useCliAccess-D31UR23I.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-BY7uA9hV.js → useFileDiffOverlay-BH6KcMzq.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-BwyVuUIK.js → wrap-text-CZ613PM5.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-RDpLugQP.js → zoom-out-BgDLAv3z.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
- package/src/ui/dist/assets/AutoFigurePlugin-DxPdMUNb.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-DH2k75Vo.js +0 -158
- package/src/ui/dist/assets/bibtex-B-Hqu0Sg.js +0 -189
- package/src/ui/dist/assets/file-utils--zJCPN1i.js +0 -109
- package/src/ui/dist/assets/message-square-FUIPIhU2.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-B1OspAkx.js +0 -108
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import ipaddress
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import secrets
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DEFAULT_LINGZHU_GATEWAY_PORT = 18789
|
|
13
|
+
DEFAULT_LINGZHU_LOCAL_HOST = "127.0.0.1"
|
|
14
|
+
DEFAULT_LINGZHU_AGENT_ID = "main"
|
|
15
|
+
DEFAULT_LINGZHU_SESSION_NAMESPACE = "lingzhu"
|
|
16
|
+
DEFAULT_LINGZHU_TASK_PREFIX = "我现在的任务是"
|
|
17
|
+
DEFAULT_LINGZHU_PASSIVE_CHAT_TYPE = "passive"
|
|
18
|
+
|
|
19
|
+
_AUTH_AK_SEGMENTS = (8, 4, 4, 4, 12)
|
|
20
|
+
_AUTH_AK_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
21
|
+
_EXAMPLE_AUTH_AKS = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"abcd1234-abcd-abcd-abcd-abcdefghijkl",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
_PRIVATE_IPV4_NETWORKS = tuple(
|
|
27
|
+
ipaddress.ip_network(item)
|
|
28
|
+
for item in (
|
|
29
|
+
"0.0.0.0/8",
|
|
30
|
+
"10.0.0.0/8",
|
|
31
|
+
"100.64.0.0/10",
|
|
32
|
+
"127.0.0.0/8",
|
|
33
|
+
"169.254.0.0/16",
|
|
34
|
+
"172.16.0.0/12",
|
|
35
|
+
"192.168.0.0/16",
|
|
36
|
+
"198.18.0.0/15",
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
_PRIVATE_IPV6_NETWORKS = tuple(
|
|
40
|
+
ipaddress.ip_network(item)
|
|
41
|
+
for item in (
|
|
42
|
+
"::/128",
|
|
43
|
+
"::1/128",
|
|
44
|
+
"fc00::/7",
|
|
45
|
+
"fe80::/10",
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
_LINGZHU_EXPERIMENTAL_COMMANDS = {
|
|
49
|
+
"send_notification",
|
|
50
|
+
"send_toast",
|
|
51
|
+
"speak_tts",
|
|
52
|
+
"start_video_record",
|
|
53
|
+
"stop_video_record",
|
|
54
|
+
"open_custom_view",
|
|
55
|
+
}
|
|
56
|
+
_LINGZHU_TOOL_COMMAND_ALIASES = {
|
|
57
|
+
"take_photo": "take_photo",
|
|
58
|
+
"camera": "take_photo",
|
|
59
|
+
"photo": "take_photo",
|
|
60
|
+
"takepicture": "take_photo",
|
|
61
|
+
"take_picture": "take_photo",
|
|
62
|
+
"snapshot": "take_photo",
|
|
63
|
+
"take_navigation": "take_navigation",
|
|
64
|
+
"navigate": "take_navigation",
|
|
65
|
+
"navigation": "take_navigation",
|
|
66
|
+
"maps": "take_navigation",
|
|
67
|
+
"route": "take_navigation",
|
|
68
|
+
"directions": "take_navigation",
|
|
69
|
+
"control_calendar": "control_calendar",
|
|
70
|
+
"calendar": "control_calendar",
|
|
71
|
+
"add_calendar": "control_calendar",
|
|
72
|
+
"schedule": "control_calendar",
|
|
73
|
+
"reminder": "control_calendar",
|
|
74
|
+
"add_reminder": "control_calendar",
|
|
75
|
+
"create_event": "control_calendar",
|
|
76
|
+
"set_schedule": "control_calendar",
|
|
77
|
+
"notify_agent_off": "notify_agent_off",
|
|
78
|
+
"exit_agent": "notify_agent_off",
|
|
79
|
+
"exit": "notify_agent_off",
|
|
80
|
+
"quit": "notify_agent_off",
|
|
81
|
+
"close_agent": "notify_agent_off",
|
|
82
|
+
"leave_agent": "notify_agent_off",
|
|
83
|
+
"send_notification": "send_notification",
|
|
84
|
+
"notification": "send_notification",
|
|
85
|
+
"notify": "send_notification",
|
|
86
|
+
"send_toast": "send_toast",
|
|
87
|
+
"toast": "send_toast",
|
|
88
|
+
"speak_tts": "speak_tts",
|
|
89
|
+
"tts": "speak_tts",
|
|
90
|
+
"speak": "speak_tts",
|
|
91
|
+
"start_video_record": "start_video_record",
|
|
92
|
+
"start_recording": "start_video_record",
|
|
93
|
+
"record_video": "start_video_record",
|
|
94
|
+
"stop_video_record": "stop_video_record",
|
|
95
|
+
"stop_recording": "stop_video_record",
|
|
96
|
+
"open_custom_view": "open_custom_view",
|
|
97
|
+
"custom_view": "open_custom_view",
|
|
98
|
+
"show_view": "open_custom_view",
|
|
99
|
+
}
|
|
100
|
+
_LINGZHU_TOOL_MARKER_RE = re.compile(r"<LINGZHU_TOOL_CALL:([^:>]+):([^>]*)>")
|
|
101
|
+
_LINGZHU_PHOTO_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:拍照|拍张照|照相|拍一张|帮我拍)(?:[^\n]*)$", re.IGNORECASE)
|
|
102
|
+
_LINGZHU_EXIT_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:退出智能体|退出当前会话|结束对话|关闭智能体)(?:[^\n]*)$", re.IGNORECASE)
|
|
103
|
+
_LINGZHU_NOTIFICATION_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:发(?:一条|个)?通知|发送通知)(?:[^\n]*)$", re.IGNORECASE)
|
|
104
|
+
_LINGZHU_TOAST_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:toast|轻提示|弹出提示)(?:[^\n]*)$", re.IGNORECASE)
|
|
105
|
+
_LINGZHU_TTS_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:播报|朗读|语音提示|念一段)(?:[^\n]*)$", re.IGNORECASE)
|
|
106
|
+
_LINGZHU_START_RECORD_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:开始录像|录一段视频|开始录制)(?:[^\n]*)$", re.IGNORECASE)
|
|
107
|
+
_LINGZHU_STOP_RECORD_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:停止录像|结束录像|停止录制)(?:[^\n]*)$", re.IGNORECASE)
|
|
108
|
+
_LINGZHU_OPEN_VIEW_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:打开.*页面|显示.*页面|展示.*页面)(?:[^\n]*)$", re.IGNORECASE)
|
|
109
|
+
_LINGZHU_NAVIGATION_REQUEST_RE = re.compile(r"^(?:请|先|帮我|麻烦|现在|立即)?(?:导航(?:到|去)?|前往|带我去|带路去)\s*[::]?\s*([^\n,。!?]+)", re.IGNORECASE)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def generate_lingzhu_auth_ak() -> str:
|
|
113
|
+
parts: list[str] = []
|
|
114
|
+
for segment_length in _AUTH_AK_SEGMENTS:
|
|
115
|
+
parts.append("".join(secrets.choice(_AUTH_AK_CHARS) for _ in range(segment_length)))
|
|
116
|
+
return "-".join(parts)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def lingzhu_auth_ak_needs_rotation(value: Any) -> bool:
|
|
120
|
+
return str(value or "").strip() in _EXAMPLE_AUTH_AKS
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def lingzhu_local_host(config: dict[str, Any] | None) -> str:
|
|
124
|
+
value = str((config or {}).get("local_host") or DEFAULT_LINGZHU_LOCAL_HOST).strip()
|
|
125
|
+
return value or DEFAULT_LINGZHU_LOCAL_HOST
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def lingzhu_gateway_port(config: dict[str, Any] | None) -> int:
|
|
129
|
+
raw = (config or {}).get("gateway_port")
|
|
130
|
+
try:
|
|
131
|
+
value = int(raw)
|
|
132
|
+
except (TypeError, ValueError):
|
|
133
|
+
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
134
|
+
if value < 1 or value > 65535:
|
|
135
|
+
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
136
|
+
return value
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def normalize_public_base_url(value: Any) -> str | None:
|
|
140
|
+
text = str(value or "").strip()
|
|
141
|
+
if not text:
|
|
142
|
+
return None
|
|
143
|
+
parsed = urlparse(text)
|
|
144
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
145
|
+
return None
|
|
146
|
+
return text.rstrip("/")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def public_base_url_looks_public(value: Any) -> bool:
|
|
150
|
+
normalized = normalize_public_base_url(value)
|
|
151
|
+
if normalized is None:
|
|
152
|
+
return False
|
|
153
|
+
parsed = urlparse(normalized)
|
|
154
|
+
hostname = str(parsed.hostname or "").strip().lower()
|
|
155
|
+
if not hostname:
|
|
156
|
+
return False
|
|
157
|
+
if hostname in {"localhost", "0.0.0.0", "::", "::1"}:
|
|
158
|
+
return False
|
|
159
|
+
if hostname.endswith(".local"):
|
|
160
|
+
return False
|
|
161
|
+
try:
|
|
162
|
+
ip = ipaddress.ip_address(hostname)
|
|
163
|
+
except ValueError:
|
|
164
|
+
return True
|
|
165
|
+
if ip.is_multicast:
|
|
166
|
+
return False
|
|
167
|
+
networks = _PRIVATE_IPV4_NETWORKS if ip.version == 4 else _PRIVATE_IPV6_NETWORKS
|
|
168
|
+
return not any(ip in network for network in networks)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def lingzhu_local_base_url(config: dict[str, Any] | None) -> str:
|
|
172
|
+
return f"http://{lingzhu_local_host(config)}:{lingzhu_gateway_port(config)}"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def lingzhu_public_base_url(config: dict[str, Any] | None) -> str | None:
|
|
176
|
+
return normalize_public_base_url((config or {}).get("public_base_url"))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def lingzhu_health_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
180
|
+
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
181
|
+
if not base:
|
|
182
|
+
return None
|
|
183
|
+
return f"{base}/metis/agent/api/health"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def lingzhu_sse_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
187
|
+
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
188
|
+
if not base:
|
|
189
|
+
return None
|
|
190
|
+
return f"{base}/metis/agent/api/sse"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def lingzhu_agent_id(config: dict[str, Any] | None) -> str:
|
|
194
|
+
value = str((config or {}).get("agent_id") or DEFAULT_LINGZHU_AGENT_ID).strip()
|
|
195
|
+
return value or DEFAULT_LINGZHU_AGENT_ID
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def lingzhu_passive_conversation_id(config: dict[str, Any] | None) -> str:
|
|
199
|
+
return f"lingzhu:{DEFAULT_LINGZHU_PASSIVE_CHAT_TYPE}:{lingzhu_agent_id(config)}"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def lingzhu_is_passive_conversation_id(value: Any, config: dict[str, Any] | None = None) -> bool:
|
|
203
|
+
normalized = str(value or "").strip()
|
|
204
|
+
if not normalized:
|
|
205
|
+
return False
|
|
206
|
+
if config is not None:
|
|
207
|
+
return normalized == lingzhu_passive_conversation_id(config)
|
|
208
|
+
return normalized.startswith(f"lingzhu:{DEFAULT_LINGZHU_PASSIVE_CHAT_TYPE}:")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def lingzhu_request_sender_id(body: dict[str, Any] | None) -> str:
|
|
212
|
+
value = str((body or {}).get("user_id") or (body or {}).get("agent_id") or "anonymous").strip()
|
|
213
|
+
return value or "anonymous"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def lingzhu_request_conversation_id(body: dict[str, Any] | None) -> str:
|
|
217
|
+
return f"lingzhu:direct:{lingzhu_request_sender_id(body)}"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def lingzhu_extract_user_text(messages: Any) -> str:
|
|
221
|
+
if not isinstance(messages, list):
|
|
222
|
+
return ""
|
|
223
|
+
preferred: list[str] = []
|
|
224
|
+
fallback: list[str] = []
|
|
225
|
+
for item in messages:
|
|
226
|
+
if not isinstance(item, dict):
|
|
227
|
+
continue
|
|
228
|
+
text = str(item.get("text") or item.get("content") or "").strip()
|
|
229
|
+
if not text:
|
|
230
|
+
continue
|
|
231
|
+
role = str(item.get("role") or "").strip().lower()
|
|
232
|
+
if role in {"", "user"}:
|
|
233
|
+
preferred.append(text)
|
|
234
|
+
else:
|
|
235
|
+
fallback.append(text)
|
|
236
|
+
parts = preferred or fallback
|
|
237
|
+
return "\n".join(parts).strip()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def lingzhu_extract_task_text(text: Any) -> str | None:
|
|
241
|
+
normalized = str(text or "").strip()
|
|
242
|
+
if not normalized.startswith(DEFAULT_LINGZHU_TASK_PREFIX):
|
|
243
|
+
return None
|
|
244
|
+
remainder = normalized[len(DEFAULT_LINGZHU_TASK_PREFIX) :].strip()
|
|
245
|
+
remainder = remainder.lstrip("::,,。.;;!!?? ")
|
|
246
|
+
return remainder or None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def lingzhu_verify_auth_header(auth_header: Any, expected_ak: str) -> bool:
|
|
250
|
+
if not expected_ak:
|
|
251
|
+
return True
|
|
252
|
+
if isinstance(auth_header, list):
|
|
253
|
+
header = str(auth_header[0] or "").strip()
|
|
254
|
+
else:
|
|
255
|
+
header = str(auth_header or "").strip()
|
|
256
|
+
if not header.lower().startswith("bearer "):
|
|
257
|
+
return False
|
|
258
|
+
return header[7:].strip() == expected_ak
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def lingzhu_probe_payload(
|
|
262
|
+
config: dict[str, Any] | None,
|
|
263
|
+
*,
|
|
264
|
+
message_id: str = "ds-lingzhu-probe-001",
|
|
265
|
+
text: str = "你好",
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
return {
|
|
268
|
+
"message_id": message_id,
|
|
269
|
+
"agent_id": lingzhu_agent_id(config),
|
|
270
|
+
"message": [
|
|
271
|
+
{
|
|
272
|
+
"role": "user",
|
|
273
|
+
"type": "text",
|
|
274
|
+
"text": text,
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def lingzhu_generated_openclaw_config(config: dict[str, Any] | None) -> dict[str, Any]:
|
|
281
|
+
resolved = dict(config or {})
|
|
282
|
+
return {
|
|
283
|
+
"gateway": {
|
|
284
|
+
"port": lingzhu_gateway_port(resolved),
|
|
285
|
+
"http": {
|
|
286
|
+
"endpoints": {
|
|
287
|
+
"chatCompletions": {
|
|
288
|
+
"enabled": True,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
"plugins": {
|
|
294
|
+
"entries": {
|
|
295
|
+
"lingzhu": {
|
|
296
|
+
"enabled": bool(resolved.get("enabled", False)),
|
|
297
|
+
"config": {
|
|
298
|
+
"authAk": str(resolved.get("auth_ak") or "").strip(),
|
|
299
|
+
"agentId": lingzhu_agent_id(resolved),
|
|
300
|
+
"includeMetadata": bool(resolved.get("include_metadata", True)),
|
|
301
|
+
"requestTimeoutMs": int(resolved.get("request_timeout_ms") or 60000),
|
|
302
|
+
"systemPrompt": str(resolved.get("system_prompt") or ""),
|
|
303
|
+
"defaultNavigationMode": str(resolved.get("default_navigation_mode") or "0"),
|
|
304
|
+
"enableFollowUp": bool(resolved.get("enable_follow_up", True)),
|
|
305
|
+
"followUpMaxCount": int(resolved.get("follow_up_max_count") or 3),
|
|
306
|
+
"maxImageBytes": int(resolved.get("max_image_bytes") or 5 * 1024 * 1024),
|
|
307
|
+
"sessionMode": str(resolved.get("session_mode") or "per_user"),
|
|
308
|
+
"sessionNamespace": str(
|
|
309
|
+
resolved.get("session_namespace") or DEFAULT_LINGZHU_SESSION_NAMESPACE
|
|
310
|
+
),
|
|
311
|
+
"autoReceiptAck": bool(resolved.get("auto_receipt_ack", True)),
|
|
312
|
+
"visibleProgressHeartbeat": bool(
|
|
313
|
+
resolved.get("visible_progress_heartbeat", True)
|
|
314
|
+
),
|
|
315
|
+
"visibleProgressHeartbeatSec": int(
|
|
316
|
+
resolved.get("visible_progress_heartbeat_sec") or 10
|
|
317
|
+
),
|
|
318
|
+
"debugLogging": bool(resolved.get("debug_logging", False)),
|
|
319
|
+
"debugLogPayloads": bool(resolved.get("debug_log_payloads", False)),
|
|
320
|
+
"debugLogDir": str(resolved.get("debug_log_dir") or ""),
|
|
321
|
+
"enableExperimentalNativeActions": bool(
|
|
322
|
+
resolved.get("enable_experimental_native_actions", False)
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def lingzhu_generated_openclaw_config_text(config: dict[str, Any] | None) -> str:
|
|
332
|
+
return json.dumps(lingzhu_generated_openclaw_config(config), indent=2, ensure_ascii=False)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def lingzhu_generated_curl(config: dict[str, Any] | None, *, text: str = "你好") -> str:
|
|
336
|
+
auth_ak = str((config or {}).get("auth_ak") or "").strip()
|
|
337
|
+
payload = lingzhu_probe_payload(config, text=text)
|
|
338
|
+
endpoint_url = lingzhu_sse_url(config) or ""
|
|
339
|
+
return (
|
|
340
|
+
f"curl -X POST '{endpoint_url}' \\\n"
|
|
341
|
+
f" --header 'Authorization: Bearer {auth_ak}' \\\n"
|
|
342
|
+
" --header 'Content-Type: application/json' \\\n"
|
|
343
|
+
f" --data '{json.dumps(payload, ensure_ascii=False)}'"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def lingzhu_health_payload(
|
|
348
|
+
config: dict[str, Any] | None,
|
|
349
|
+
*,
|
|
350
|
+
chat_completions_enabled: bool = True,
|
|
351
|
+
) -> dict[str, Any]:
|
|
352
|
+
resolved = dict(config or {})
|
|
353
|
+
experimental_enabled = bool(resolved.get("enable_experimental_native_actions", False))
|
|
354
|
+
return {
|
|
355
|
+
"ok": True,
|
|
356
|
+
"status": "ok",
|
|
357
|
+
"endpoint": "/metis/agent/api/sse",
|
|
358
|
+
"enabled": bool(resolved.get("enabled", False)),
|
|
359
|
+
"agentId": lingzhu_agent_id(resolved),
|
|
360
|
+
"supportedCommands": lingzhu_supported_commands(experimental_enabled=experimental_enabled),
|
|
361
|
+
"followUpEnabled": bool(resolved.get("enable_follow_up", True)),
|
|
362
|
+
"sessionMode": str(resolved.get("session_mode") or "per_user"),
|
|
363
|
+
"debugLogging": bool(resolved.get("debug_logging", False)),
|
|
364
|
+
"experimentalNativeActions": experimental_enabled,
|
|
365
|
+
"chatCompletionsEnabled": bool(chat_completions_enabled),
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def lingzhu_sse_answer(
|
|
370
|
+
*,
|
|
371
|
+
message_id: str,
|
|
372
|
+
agent_id: str,
|
|
373
|
+
answer_stream: str,
|
|
374
|
+
is_finish: bool = True,
|
|
375
|
+
) -> dict[str, Any]:
|
|
376
|
+
return {
|
|
377
|
+
"role": "agent",
|
|
378
|
+
"type": "answer",
|
|
379
|
+
"answer_stream": answer_stream,
|
|
380
|
+
"message_id": str(message_id or "").strip(),
|
|
381
|
+
"agent_id": str(agent_id or "").strip(),
|
|
382
|
+
"is_finish": bool(is_finish),
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def lingzhu_sse_follow_up(
|
|
387
|
+
*,
|
|
388
|
+
message_id: str,
|
|
389
|
+
agent_id: str,
|
|
390
|
+
suggestions: list[str],
|
|
391
|
+
) -> dict[str, Any]:
|
|
392
|
+
return {
|
|
393
|
+
"role": "agent",
|
|
394
|
+
"type": "follow_up",
|
|
395
|
+
"message_id": str(message_id or "").strip(),
|
|
396
|
+
"agent_id": str(agent_id or "").strip(),
|
|
397
|
+
"is_finish": True,
|
|
398
|
+
"follow_up": [str(item).strip() for item in suggestions if str(item).strip()],
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def lingzhu_sse_tool_call(
|
|
403
|
+
*,
|
|
404
|
+
message_id: str,
|
|
405
|
+
agent_id: str,
|
|
406
|
+
tool_call: dict[str, Any],
|
|
407
|
+
is_finish: bool = True,
|
|
408
|
+
) -> dict[str, Any]:
|
|
409
|
+
return {
|
|
410
|
+
"role": "agent",
|
|
411
|
+
"type": "tool_call",
|
|
412
|
+
"message_id": str(message_id or "").strip(),
|
|
413
|
+
"agent_id": str(agent_id or "").strip(),
|
|
414
|
+
"is_finish": bool(is_finish),
|
|
415
|
+
"tool_call": dict(tool_call or {}),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def lingzhu_resolve_command(raw_command: Any, *, experimental_enabled: bool = False) -> str | None:
|
|
420
|
+
normalized = str(raw_command or "").strip().lower()
|
|
421
|
+
if not normalized:
|
|
422
|
+
return None
|
|
423
|
+
resolved = _LINGZHU_TOOL_COMMAND_ALIASES.get(normalized, normalized)
|
|
424
|
+
if resolved in _LINGZHU_EXPERIMENTAL_COMMANDS and not experimental_enabled:
|
|
425
|
+
return None
|
|
426
|
+
if resolved not in lingzhu_supported_commands(experimental_enabled=experimental_enabled):
|
|
427
|
+
return None
|
|
428
|
+
return resolved
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _lingzhu_action_text_value(action: dict[str, Any], *keys: str) -> str:
|
|
432
|
+
for key in keys:
|
|
433
|
+
value = str(action.get(key) or "").strip()
|
|
434
|
+
if value:
|
|
435
|
+
return value
|
|
436
|
+
return ""
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _lingzhu_action_bool_value(action: dict[str, Any], key: str) -> bool | None:
|
|
440
|
+
raw = action.get(key)
|
|
441
|
+
if isinstance(raw, bool):
|
|
442
|
+
return raw
|
|
443
|
+
if isinstance(raw, str):
|
|
444
|
+
normalized = raw.strip().lower()
|
|
445
|
+
if normalized in {"true", "1", "yes", "on"}:
|
|
446
|
+
return True
|
|
447
|
+
if normalized in {"false", "0", "no", "off"}:
|
|
448
|
+
return False
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _lingzhu_action_int_value(action: dict[str, Any], key: str) -> int | None:
|
|
453
|
+
raw = action.get(key)
|
|
454
|
+
try:
|
|
455
|
+
value = int(raw)
|
|
456
|
+
except (TypeError, ValueError):
|
|
457
|
+
return None
|
|
458
|
+
return value
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _lingzhu_command_payload_tool_call(
|
|
462
|
+
command: str,
|
|
463
|
+
payload: dict[str, Any],
|
|
464
|
+
*,
|
|
465
|
+
default_navigation_mode: str = "0",
|
|
466
|
+
) -> dict[str, Any]:
|
|
467
|
+
resolved_navigation_mode = default_navigation_mode if default_navigation_mode in {"0", "1", "2"} else "0"
|
|
468
|
+
tool_call: dict[str, Any] = {
|
|
469
|
+
"handling_required": True,
|
|
470
|
+
"command": command,
|
|
471
|
+
"is_recall": bool(payload.get("is_recall", True)),
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if command == "take_navigation":
|
|
475
|
+
tool_call["action"] = _lingzhu_action_text_value(payload, "action") or "open"
|
|
476
|
+
destination = _lingzhu_action_text_value(payload, "poi_name", "destination", "address", "name", "query")
|
|
477
|
+
if destination:
|
|
478
|
+
tool_call["poi_name"] = destination
|
|
479
|
+
navigation_mode = _lingzhu_action_text_value(payload, "navi_type", "type")
|
|
480
|
+
tool_call["navi_type"] = navigation_mode if navigation_mode in {"0", "1", "2"} else resolved_navigation_mode
|
|
481
|
+
return tool_call
|
|
482
|
+
|
|
483
|
+
if command == "control_calendar":
|
|
484
|
+
tool_call["action"] = _lingzhu_action_text_value(payload, "action") or "create"
|
|
485
|
+
title = _lingzhu_action_text_value(payload, "title", "name")
|
|
486
|
+
if title:
|
|
487
|
+
tool_call["title"] = title
|
|
488
|
+
start_time = _lingzhu_action_text_value(payload, "start_time", "startTime")
|
|
489
|
+
if start_time:
|
|
490
|
+
tool_call["start_time"] = start_time
|
|
491
|
+
end_time = _lingzhu_action_text_value(payload, "end_time", "endTime")
|
|
492
|
+
if end_time:
|
|
493
|
+
tool_call["end_time"] = end_time
|
|
494
|
+
return tool_call
|
|
495
|
+
|
|
496
|
+
if command in {"send_notification", "send_toast", "speak_tts"}:
|
|
497
|
+
title = _lingzhu_action_text_value(payload, "title")
|
|
498
|
+
body = _lingzhu_action_text_value(payload, "content", "body", "text", "message")
|
|
499
|
+
if title and body and body != title:
|
|
500
|
+
tool_call["content"] = f"{title}\n{body}"
|
|
501
|
+
else:
|
|
502
|
+
content = body or title
|
|
503
|
+
if content:
|
|
504
|
+
tool_call["content"] = content
|
|
505
|
+
play_tts = _lingzhu_action_bool_value(payload, "play_tts")
|
|
506
|
+
if play_tts is not None:
|
|
507
|
+
tool_call["play_tts"] = play_tts
|
|
508
|
+
icon_type = _lingzhu_action_text_value(payload, "icon_type")
|
|
509
|
+
if icon_type:
|
|
510
|
+
tool_call["icon_type"] = icon_type
|
|
511
|
+
return tool_call
|
|
512
|
+
|
|
513
|
+
if command == "start_video_record":
|
|
514
|
+
for key in ("duration_sec", "width", "height", "quality"):
|
|
515
|
+
value = _lingzhu_action_int_value(payload, key)
|
|
516
|
+
if value is not None:
|
|
517
|
+
tool_call[key] = value
|
|
518
|
+
return tool_call
|
|
519
|
+
|
|
520
|
+
if command == "open_custom_view":
|
|
521
|
+
view_name = _lingzhu_action_text_value(payload, "view_name", "title", "name")
|
|
522
|
+
if view_name:
|
|
523
|
+
tool_call["view_name"] = view_name
|
|
524
|
+
raw_payload = payload.get("view_payload", payload.get("payload", payload.get("data")))
|
|
525
|
+
if raw_payload is not None and raw_payload != "":
|
|
526
|
+
if isinstance(raw_payload, str):
|
|
527
|
+
rendered_payload = raw_payload.strip()
|
|
528
|
+
else:
|
|
529
|
+
rendered_payload = json.dumps(raw_payload, ensure_ascii=False)
|
|
530
|
+
if rendered_payload:
|
|
531
|
+
tool_call["view_payload"] = rendered_payload
|
|
532
|
+
return tool_call
|
|
533
|
+
|
|
534
|
+
return tool_call
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _lingzhu_decode_marker_params(raw_value: str) -> dict[str, Any]:
|
|
538
|
+
value = str(raw_value or "").strip()
|
|
539
|
+
if not value:
|
|
540
|
+
return {}
|
|
541
|
+
try:
|
|
542
|
+
decoded = json.loads(value)
|
|
543
|
+
return decoded if isinstance(decoded, dict) else {}
|
|
544
|
+
except json.JSONDecodeError:
|
|
545
|
+
pass
|
|
546
|
+
try:
|
|
547
|
+
padded = value + ("=" * (-len(value) % 4))
|
|
548
|
+
decoded_text = base64.urlsafe_b64decode(padded.encode("utf-8")).decode("utf-8")
|
|
549
|
+
decoded = json.loads(decoded_text)
|
|
550
|
+
return decoded if isinstance(decoded, dict) else {}
|
|
551
|
+
except (ValueError, json.JSONDecodeError, UnicodeDecodeError):
|
|
552
|
+
return {}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def lingzhu_detect_tool_call_from_text(
|
|
556
|
+
text: Any,
|
|
557
|
+
*,
|
|
558
|
+
default_navigation_mode: str = "0",
|
|
559
|
+
experimental_enabled: bool = False,
|
|
560
|
+
) -> tuple[dict[str, Any] | None, str]:
|
|
561
|
+
normalized = str(text or "").strip()
|
|
562
|
+
if not normalized:
|
|
563
|
+
return None, ""
|
|
564
|
+
|
|
565
|
+
marker_match = _LINGZHU_TOOL_MARKER_RE.search(normalized)
|
|
566
|
+
if marker_match:
|
|
567
|
+
command = lingzhu_resolve_command(marker_match.group(1), experimental_enabled=experimental_enabled)
|
|
568
|
+
if command:
|
|
569
|
+
params = _lingzhu_decode_marker_params(marker_match.group(2))
|
|
570
|
+
cleaned = f"{normalized[:marker_match.start()]} {normalized[marker_match.end():]}".strip()
|
|
571
|
+
cleaned = re.sub(r"\s+", " ", cleaned)
|
|
572
|
+
return (
|
|
573
|
+
_lingzhu_command_payload_tool_call(
|
|
574
|
+
command,
|
|
575
|
+
params,
|
|
576
|
+
default_navigation_mode=default_navigation_mode,
|
|
577
|
+
),
|
|
578
|
+
cleaned,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
explicit_command_match = re.match(r"^\s*([A-Za-z_]+)\b", normalized)
|
|
582
|
+
if explicit_command_match:
|
|
583
|
+
command = lingzhu_resolve_command(explicit_command_match.group(1), experimental_enabled=experimental_enabled)
|
|
584
|
+
if command:
|
|
585
|
+
return (
|
|
586
|
+
_lingzhu_command_payload_tool_call(
|
|
587
|
+
command,
|
|
588
|
+
{},
|
|
589
|
+
default_navigation_mode=default_navigation_mode,
|
|
590
|
+
),
|
|
591
|
+
normalized,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
if _LINGZHU_PHOTO_REQUEST_RE.match(normalized):
|
|
595
|
+
return _lingzhu_command_payload_tool_call("take_photo", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
596
|
+
if _LINGZHU_EXIT_REQUEST_RE.match(normalized):
|
|
597
|
+
return _lingzhu_command_payload_tool_call("notify_agent_off", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
598
|
+
|
|
599
|
+
navigation_match = _LINGZHU_NAVIGATION_REQUEST_RE.match(normalized)
|
|
600
|
+
if navigation_match and navigation_match.group(1).strip():
|
|
601
|
+
return (
|
|
602
|
+
_lingzhu_command_payload_tool_call(
|
|
603
|
+
"take_navigation",
|
|
604
|
+
{"poi_name": navigation_match.group(1).strip()},
|
|
605
|
+
default_navigation_mode=default_navigation_mode,
|
|
606
|
+
),
|
|
607
|
+
normalized,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
if experimental_enabled:
|
|
611
|
+
if _LINGZHU_NOTIFICATION_REQUEST_RE.match(normalized):
|
|
612
|
+
return _lingzhu_command_payload_tool_call("send_notification", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
613
|
+
if _LINGZHU_TOAST_REQUEST_RE.match(normalized):
|
|
614
|
+
return _lingzhu_command_payload_tool_call("send_toast", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
615
|
+
if _LINGZHU_TTS_REQUEST_RE.match(normalized):
|
|
616
|
+
return _lingzhu_command_payload_tool_call("speak_tts", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
617
|
+
if _LINGZHU_START_RECORD_RE.match(normalized):
|
|
618
|
+
return _lingzhu_command_payload_tool_call("start_video_record", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
619
|
+
if _LINGZHU_STOP_RECORD_RE.match(normalized):
|
|
620
|
+
return _lingzhu_command_payload_tool_call("stop_video_record", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
621
|
+
if _LINGZHU_OPEN_VIEW_RE.match(normalized):
|
|
622
|
+
return _lingzhu_command_payload_tool_call("open_custom_view", {}, default_navigation_mode=default_navigation_mode), normalized
|
|
623
|
+
|
|
624
|
+
return None, normalized
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def lingzhu_surface_action_tool_call(
|
|
628
|
+
action: Any,
|
|
629
|
+
*,
|
|
630
|
+
default_navigation_mode: str = "0",
|
|
631
|
+
experimental_enabled: bool = False,
|
|
632
|
+
) -> dict[str, Any] | None:
|
|
633
|
+
if not isinstance(action, dict):
|
|
634
|
+
return None
|
|
635
|
+
|
|
636
|
+
command = lingzhu_resolve_command(
|
|
637
|
+
action.get("command") or action.get("type"),
|
|
638
|
+
experimental_enabled=experimental_enabled,
|
|
639
|
+
)
|
|
640
|
+
if not command:
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
return _lingzhu_command_payload_tool_call(
|
|
644
|
+
command,
|
|
645
|
+
dict(action),
|
|
646
|
+
default_navigation_mode=default_navigation_mode,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def lingzhu_supported_commands(*, experimental_enabled: bool) -> list[str]:
|
|
651
|
+
commands = [
|
|
652
|
+
"take_photo",
|
|
653
|
+
"take_navigation",
|
|
654
|
+
"control_calendar",
|
|
655
|
+
"notify_agent_off",
|
|
656
|
+
]
|
|
657
|
+
if experimental_enabled:
|
|
658
|
+
commands.extend(
|
|
659
|
+
[
|
|
660
|
+
"send_notification",
|
|
661
|
+
"send_toast",
|
|
662
|
+
"speak_tts",
|
|
663
|
+
"start_video_record",
|
|
664
|
+
"stop_video_record",
|
|
665
|
+
"open_custom_view",
|
|
666
|
+
]
|
|
667
|
+
)
|
|
668
|
+
return commands
|