@researai/deepscientist 1.5.0 → 1.5.2

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 (168) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +47 -161
  3. package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
  4. package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
  5. package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
  6. package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
  7. package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
  8. package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
  9. package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
  10. package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
  11. package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
  12. package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
  13. package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
  14. package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
  15. package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
  16. package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
  17. package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
  18. package/bin/ds.js +2048 -166
  19. package/docs/en/00_QUICK_START.md +152 -0
  20. package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
  21. package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
  22. package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
  23. package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
  24. package/docs/en/05_TUI_GUIDE.md +141 -0
  25. package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
  26. package/docs/en/07_MEMORY_AND_MCP.md +253 -0
  27. package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
  28. package/docs/en/09_DOCTOR.md +152 -0
  29. package/docs/en/90_ARCHITECTURE.md +247 -0
  30. package/docs/en/91_DEVELOPMENT.md +195 -0
  31. package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
  32. package/docs/zh/00_QUICK_START.md +152 -0
  33. package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
  34. package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
  35. package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
  36. package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
  37. package/docs/zh/05_TUI_GUIDE.md +128 -0
  38. package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
  39. package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
  40. package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
  41. package/docs/zh/09_DOCTOR.md +154 -0
  42. package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
  43. package/install.sh +41 -16
  44. package/package.json +5 -2
  45. package/pyproject.toml +1 -1
  46. package/src/deepscientist/__init__.py +6 -1
  47. package/src/deepscientist/artifact/guidance.py +9 -2
  48. package/src/deepscientist/artifact/service.py +1026 -39
  49. package/src/deepscientist/bash_exec/monitor.py +27 -5
  50. package/src/deepscientist/bash_exec/runtime.py +639 -0
  51. package/src/deepscientist/bash_exec/service.py +99 -16
  52. package/src/deepscientist/bridges/base.py +3 -0
  53. package/src/deepscientist/bridges/connectors.py +292 -13
  54. package/src/deepscientist/channels/qq.py +19 -2
  55. package/src/deepscientist/channels/relay.py +1 -0
  56. package/src/deepscientist/cli.py +32 -25
  57. package/src/deepscientist/config/models.py +28 -2
  58. package/src/deepscientist/config/service.py +202 -7
  59. package/src/deepscientist/connector_runtime.py +2 -0
  60. package/src/deepscientist/daemon/api/handlers.py +68 -6
  61. package/src/deepscientist/daemon/api/router.py +3 -0
  62. package/src/deepscientist/daemon/app.py +531 -15
  63. package/src/deepscientist/doctor.py +511 -0
  64. package/src/deepscientist/gitops/diff.py +3 -0
  65. package/src/deepscientist/home.py +26 -2
  66. package/src/deepscientist/latex_runtime.py +17 -4
  67. package/src/deepscientist/lingzhu_support.py +182 -0
  68. package/src/deepscientist/mcp/context.py +3 -1
  69. package/src/deepscientist/mcp/server.py +55 -2
  70. package/src/deepscientist/prompts/builder.py +222 -58
  71. package/src/deepscientist/quest/layout.py +2 -0
  72. package/src/deepscientist/quest/service.py +133 -14
  73. package/src/deepscientist/quest/stage_views.py +65 -1
  74. package/src/deepscientist/runners/codex.py +2 -0
  75. package/src/deepscientist/runtime_tools/__init__.py +16 -0
  76. package/src/deepscientist/runtime_tools/builtins.py +19 -0
  77. package/src/deepscientist/runtime_tools/models.py +29 -0
  78. package/src/deepscientist/runtime_tools/registry.py +40 -0
  79. package/src/deepscientist/runtime_tools/service.py +59 -0
  80. package/src/deepscientist/runtime_tools/tinytex.py +25 -0
  81. package/src/deepscientist/shared.py +44 -17
  82. package/src/deepscientist/tinytex.py +276 -0
  83. package/src/prompts/connectors/lingzhu.md +15 -0
  84. package/src/prompts/connectors/qq.md +121 -0
  85. package/src/prompts/system.md +214 -37
  86. package/src/skills/analysis-campaign/SKILL.md +46 -7
  87. package/src/skills/baseline/SKILL.md +12 -5
  88. package/src/skills/decision/SKILL.md +7 -5
  89. package/src/skills/experiment/SKILL.md +22 -5
  90. package/src/skills/finalize/SKILL.md +9 -5
  91. package/src/skills/idea/SKILL.md +6 -5
  92. package/src/skills/intake-audit/SKILL.md +277 -0
  93. package/src/skills/intake-audit/references/state-audit-template.md +41 -0
  94. package/src/skills/rebuttal/SKILL.md +409 -0
  95. package/src/skills/rebuttal/references/action-plan-template.md +63 -0
  96. package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
  97. package/src/skills/rebuttal/references/response-letter-template.md +113 -0
  98. package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
  99. package/src/skills/review/SKILL.md +295 -0
  100. package/src/skills/review/references/experiment-todo-template.md +29 -0
  101. package/src/skills/review/references/review-report-template.md +83 -0
  102. package/src/skills/review/references/revision-log-template.md +40 -0
  103. package/src/skills/scout/SKILL.md +6 -5
  104. package/src/skills/write/SKILL.md +8 -4
  105. package/src/tui/dist/components/WelcomePanel.js +17 -43
  106. package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
  107. package/src/tui/package.json +1 -1
  108. package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-CZpg376x.js} +127 -597
  109. package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-CtHA22g3.js} +1 -1
  110. package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-BSWmLMmF.js} +63 -8
  111. package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CJ7jdm_s.js} +43 -609
  112. package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DhInVGFf.js} +8 -8
  113. package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-D1n8S9r5.js} +5 -5
  114. package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-C4XM_kqk.js} +3 -3
  115. package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-W6kS9r6v.js} +1 -1
  116. package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-DPeUx_Oz.js} +5 -6
  117. package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-eAelUaub.js} +12 -15
  118. package/src/ui/dist/assets/LabPlugin-BbOrBxKY.js +2676 -0
  119. package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-C-HhkVXY.js} +16 -16
  120. package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-BDIzIBfh.js} +4 -4
  121. package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-DAOJphwr.js} +3 -3
  122. package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-BsoMvDoU.js} +3 -3
  123. package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-fiC7RtHf.js} +1 -1
  124. package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-C5OxZBFK.js} +3 -3
  125. package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-CAbxQebk.js} +10 -10
  126. package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-SE33Lb9B.js} +1 -1
  127. package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-0Av7GfV7.js} +1 -1
  128. package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-Daf2gJDI.js} +4 -4
  129. package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-BKrMUIOX.js} +9 -10
  130. package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-JBdOEe45.js} +1 -1
  131. package/src/ui/dist/assets/{code-BnBeNxBc.js → code-B0TDFCZz.js} +1 -1
  132. package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-3YtrSacz.js} +1 -1
  133. package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-CJEg5OG1.js} +1 -1
  134. package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CYQYdmB1.js} +1 -1
  135. package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-Cd1C9Ppl.js} +1 -1
  136. package/src/ui/dist/assets/{image-Boe6ffhu.js → image-B33ctrvC.js} +1 -1
  137. package/src/ui/dist/assets/{index-2Zf65FZt.js → index-9CLPVeZh.js} +1 -1
  138. package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-BNQWqmJ2.js} +60 -2154
  139. package/src/ui/dist/assets/{index-DO43pFZP.js → index-BVXsmS7V.js} +84086 -84365
  140. package/src/ui/dist/assets/{index-BlplpvE1.js → index-Buw_N1VQ.js} +2 -2
  141. package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-SwmFAld3.css} +2622 -2619
  142. package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-D0cUJ9yU.js} +1 -1
  143. package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-UZLYkp2n.js} +1 -1
  144. package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-CTeiY-dK.js} +1 -1
  145. package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-Dbs01Xky.js} +2 -8
  146. package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-CM08S-xT.js} +1 -1
  147. package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-pDtzvU9p.js} +1 -1
  148. package/src/ui/dist/assets/trash-YvPCP-da.js +32 -0
  149. package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-Bavi74Ac.js} +12 -42
  150. package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-CVXY6oeg.js} +1 -1
  151. package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Cf4flRW7.js} +1 -1
  152. package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-Hb0Z1YpT.js} +1 -1
  153. package/src/ui/dist/index.html +2 -2
  154. package/uv.lock +1155 -0
  155. package/assets/fonts/Inter-Variable.ttf +0 -0
  156. package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
  157. package/assets/fonts/NunitoSans-Variable.ttf +0 -0
  158. package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
  159. package/assets/fonts/SourceSans3-Variable.ttf +0 -0
  160. package/assets/fonts/ds-fonts.css +0 -83
  161. package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
  162. package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
  163. package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
  164. package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
  165. package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
  166. package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
  167. package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
  168. package/src/ui/dist/assets/user-plus-BusDx-hF.js +0 -79
@@ -0,0 +1,541 @@
1
+ import type {
2
+ LingzhuMessage,
3
+ LingzhuContext,
4
+ LingzhuSSEData,
5
+ LingzhuToolCall,
6
+ } from "./types.js";
7
+
8
+ interface OpenAIMessage {
9
+ role: "system" | "user" | "assistant";
10
+ content: string | { type: string; image_url?: { url: string } }[];
11
+ }
12
+
13
+ interface OpenAIToolCall {
14
+ id?: string;
15
+ type?: string;
16
+ index?: number;
17
+ function?: {
18
+ name?: string;
19
+ arguments?: string;
20
+ };
21
+ }
22
+
23
+ interface OpenAIChunk {
24
+ choices?: Array<{
25
+ delta?: {
26
+ content?: string;
27
+ tool_calls?: OpenAIToolCall[];
28
+ };
29
+ finish_reason?: string | null;
30
+ }>;
31
+ }
32
+
33
+ interface LingzhuTransformOptions {
34
+ systemPrompt?: string;
35
+ defaultNavigationMode?: "0" | "1" | "2";
36
+ enableExperimentalNativeActions?: boolean;
37
+ }
38
+
39
+ const EXPERIMENTAL_COMMANDS = new Set<LingzhuToolCall["command"]>([
40
+ "send_notification",
41
+ "send_toast",
42
+ "speak_tts",
43
+ "start_video_record",
44
+ "stop_video_record",
45
+ "open_custom_view",
46
+ ]);
47
+
48
+ const ALLOWED_MARKER_COMMANDS = new Set<LingzhuToolCall["command"]>([
49
+ "take_photo",
50
+ "take_navigation",
51
+ "notify_agent_off",
52
+ "control_calendar",
53
+ "send_notification",
54
+ "send_toast",
55
+ "speak_tts",
56
+ "start_video_record",
57
+ "stop_video_record",
58
+ "open_custom_view",
59
+ ]);
60
+
61
+ function resolveNavigationMode(
62
+ value: unknown,
63
+ fallback: "0" | "1" | "2" = "0"
64
+ ): "0" | "1" | "2" {
65
+ return value === "1" || value === "2" ? value : fallback;
66
+ }
67
+
68
+ function createDefaultSystemPrompt(enableExperimentalNativeActions = false): string {
69
+ const lines = [
70
+ "你是灵珠设备桥接助手,需要优先把用户意图转换成设备工具调用。",
71
+ "当用户要求拍照、拍摄、照相或记录当前画面时,必须调用 take_photo。",
72
+ "当用户要求导航、带路、去某地时,必须调用 navigate,并尽量补充 destination。",
73
+ "当用户要求添加日程、设置提醒、安排事项时,必须调用 calendar。",
74
+ "当用户要求退出、结束当前智能体会话时,必须调用 exit_agent。",
75
+ "不要把工具调用伪装成普通文本说明;能调用工具时优先调用工具。",
76
+ ];
77
+
78
+ if (enableExperimentalNativeActions) {
79
+ lines.push("当用户要求发送眼镜通知时,调用 send_notification。");
80
+ lines.push("当用户要求发送轻提示或 Toast 时,调用 send_toast。");
81
+ lines.push("当用户要求眼镜直接播报文本时,调用 speak_tts。");
82
+ lines.push("当用户要求开始录像时,调用 start_video_record;要求停止录像时,调用 stop_video_record。");
83
+ lines.push("当用户要求打开自定义页面或实验界面时,调用 open_custom_view。");
84
+ }
85
+
86
+ return lines.join("\n");
87
+ }
88
+
89
+ const TOOL_COMMAND_MAP: Record<string, LingzhuToolCall["command"]> = {
90
+ take_photo: "take_photo",
91
+ camera: "take_photo",
92
+ photo: "take_photo",
93
+ takepicture: "take_photo",
94
+ snapshot: "take_photo",
95
+
96
+ navigate: "take_navigation",
97
+ navigation: "take_navigation",
98
+ take_navigation: "take_navigation",
99
+ maps: "take_navigation",
100
+ route: "take_navigation",
101
+ directions: "take_navigation",
102
+
103
+ calendar: "control_calendar",
104
+ add_calendar: "control_calendar",
105
+ control_calendar: "control_calendar",
106
+ schedule: "control_calendar",
107
+ reminder: "control_calendar",
108
+ add_reminder: "control_calendar",
109
+ create_event: "control_calendar",
110
+ set_schedule: "control_calendar",
111
+
112
+ exit_agent: "notify_agent_off",
113
+ exit: "notify_agent_off",
114
+ quit: "notify_agent_off",
115
+ notify_agent_off: "notify_agent_off",
116
+ close_agent: "notify_agent_off",
117
+ leave_agent: "notify_agent_off",
118
+
119
+ send_notification: "send_notification",
120
+ notification: "send_notification",
121
+ notify: "send_notification",
122
+ send_toast: "send_toast",
123
+ toast: "send_toast",
124
+ speak_tts: "speak_tts",
125
+ tts: "speak_tts",
126
+ speak: "speak_tts",
127
+ start_video_record: "start_video_record",
128
+ start_recording: "start_video_record",
129
+ record_video: "start_video_record",
130
+ stop_video_record: "stop_video_record",
131
+ stop_recording: "stop_video_record",
132
+ open_custom_view: "open_custom_view",
133
+ custom_view: "open_custom_view",
134
+ show_view: "open_custom_view",
135
+ };
136
+
137
+ function resolveToolCommand(
138
+ toolName: string,
139
+ options: LingzhuTransformOptions = {}
140
+ ): LingzhuToolCall["command"] | null {
141
+ const command = TOOL_COMMAND_MAP[toolName.toLowerCase()] ?? null;
142
+ if (!command) {
143
+ return null;
144
+ }
145
+
146
+ if (EXPERIMENTAL_COMMANDS.has(command) && options.enableExperimentalNativeActions !== true) {
147
+ return null;
148
+ }
149
+
150
+ return command;
151
+ }
152
+
153
+ export class ToolCallAccumulator {
154
+ private tools: Map<number, { id: string; name: string; arguments: string }> = new Map();
155
+
156
+ accumulate(toolCalls: OpenAIToolCall[]): void {
157
+ for (const tc of toolCalls) {
158
+ const index = tc.index ?? 0;
159
+
160
+ if (!this.tools.has(index)) {
161
+ this.tools.set(index, {
162
+ id: tc.id || "",
163
+ name: tc.function?.name || "",
164
+ arguments: "",
165
+ });
166
+ }
167
+
168
+ const existing = this.tools.get(index)!;
169
+ if (tc.id) existing.id = tc.id;
170
+ if (tc.function?.name) existing.name = tc.function.name;
171
+ if (tc.function?.arguments) existing.arguments += tc.function.arguments;
172
+ }
173
+ }
174
+
175
+ getCompleted(): Array<{ id: string; name: string; arguments: string }> {
176
+ return Array.from(this.tools.values()).filter((tool) => tool.name);
177
+ }
178
+
179
+ hasTools(): boolean {
180
+ return this.tools.size > 0;
181
+ }
182
+
183
+ clear(): void {
184
+ this.tools.clear();
185
+ }
186
+ }
187
+
188
+ function decodeMarkerParams(rawValue: string): Record<string, unknown> {
189
+ const value = rawValue.trim();
190
+ if (!value) {
191
+ return {};
192
+ }
193
+
194
+ try {
195
+ return JSON.parse(value) as Record<string, unknown>;
196
+ } catch {
197
+ try {
198
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8")) as Record<string, unknown>;
199
+ } catch {
200
+ return {};
201
+ }
202
+ }
203
+ }
204
+
205
+ function extractLingzhuToolMarker(
206
+ text: string
207
+ ): { command: LingzhuToolCall["command"]; params: Record<string, unknown> } | null {
208
+ const markerPrefix = "<LINGZHU_TOOL_CALL:";
209
+ const markerStart = text.indexOf(markerPrefix);
210
+ if (markerStart < 0) {
211
+ return null;
212
+ }
213
+
214
+ const commandSeparator = text.indexOf(":", markerStart + markerPrefix.length);
215
+ const markerEnd = text.lastIndexOf(">");
216
+ if (commandSeparator < 0 || markerEnd < 0) {
217
+ return null;
218
+ }
219
+
220
+ const command = text.slice(markerStart + markerPrefix.length, commandSeparator).trim();
221
+ if (!ALLOWED_MARKER_COMMANDS.has(command as LingzhuToolCall["command"])) {
222
+ return null;
223
+ }
224
+
225
+ return {
226
+ command: command as LingzhuToolCall["command"],
227
+ params: decodeMarkerParams(text.slice(commandSeparator + 1, markerEnd)),
228
+ };
229
+ }
230
+
231
+ export function detectIntentFromText(
232
+ text: string,
233
+ options: LingzhuTransformOptions = {}
234
+ ): LingzhuSSEData["tool_call"] | null {
235
+ const defaultNavigationMode = resolveNavigationMode(options.defaultNavigationMode);
236
+ const experimentalEnabled = options.enableExperimentalNativeActions === true;
237
+
238
+ const markerMatch = extractLingzhuToolMarker(text);
239
+ if (markerMatch) {
240
+ const command = markerMatch.command;
241
+ if (EXPERIMENTAL_COMMANDS.has(command) && !experimentalEnabled) {
242
+ return null;
243
+ }
244
+
245
+ const rawParams = markerMatch.params;
246
+ const toolCall: LingzhuToolCall = {
247
+ handling_required: true,
248
+ command,
249
+ is_recall: true,
250
+ };
251
+
252
+ if (command === "take_navigation") {
253
+ toolCall.action = "open";
254
+ if (rawParams.destination) toolCall.poi_name = String(rawParams.destination);
255
+ toolCall.navi_type = resolveNavigationMode(rawParams.navi_type, defaultNavigationMode);
256
+ } else if (command === "control_calendar") {
257
+ toolCall.action = "create";
258
+ if (rawParams.title) toolCall.title = String(rawParams.title);
259
+ if (rawParams.start_time) toolCall.start_time = String(rawParams.start_time);
260
+ if (rawParams.end_time) toolCall.end_time = String(rawParams.end_time);
261
+ } else if (command === "send_notification" || command === "send_toast" || command === "speak_tts") {
262
+ if (rawParams.content) toolCall.content = String(rawParams.content);
263
+ if (typeof rawParams.play_tts === "boolean") toolCall.play_tts = rawParams.play_tts;
264
+ if (rawParams.icon_type) toolCall.icon_type = String(rawParams.icon_type);
265
+ } else if (command === "start_video_record") {
266
+ if (typeof rawParams.duration_sec === "number") toolCall.duration_sec = rawParams.duration_sec;
267
+ if (typeof rawParams.width === "number") toolCall.width = rawParams.width;
268
+ if (typeof rawParams.height === "number") toolCall.height = rawParams.height;
269
+ if (typeof rawParams.quality === "number") toolCall.quality = rawParams.quality;
270
+ } else if (command === "open_custom_view") {
271
+ if (rawParams.view_name) toolCall.view_name = String(rawParams.view_name);
272
+ if (rawParams.view_payload) {
273
+ toolCall.view_payload = typeof rawParams.view_payload === "string"
274
+ ? rawParams.view_payload
275
+ : JSON.stringify(rawParams.view_payload);
276
+ }
277
+ }
278
+
279
+ return toolCall;
280
+ }
281
+
282
+ const patterns: Array<{ regex: RegExp; command: LingzhuToolCall["command"] }> = [
283
+ { regex: /拍照|拍张照|照相|拍一张|帮我拍/, command: "take_photo" },
284
+ { regex: /退出智能体|退出当前会话|结束对话|关闭智能体/, command: "notify_agent_off" },
285
+ ];
286
+
287
+ if (experimentalEnabled) {
288
+ patterns.push({ regex: /发(一条|个)?通知|发送通知/, command: "send_notification" });
289
+ patterns.push({ regex: /toast|轻提示|弹出提示/i, command: "send_toast" });
290
+ patterns.push({ regex: /播报|朗读|语音提示|念一段/, command: "speak_tts" });
291
+ patterns.push({ regex: /开始录像|录一段视频|开始录制/, command: "start_video_record" });
292
+ patterns.push({ regex: /停止录像|结束录像|停止录制/, command: "stop_video_record" });
293
+ patterns.push({ regex: /打开.*页面|显示.*页面|展示.*页面/, command: "open_custom_view" });
294
+ }
295
+
296
+ for (const pattern of patterns) {
297
+ if (pattern.regex.test(text)) {
298
+ return {
299
+ handling_required: true,
300
+ command: pattern.command,
301
+ is_recall: true,
302
+ };
303
+ }
304
+ }
305
+
306
+ const navigationMatch = text.match(/(?:导航(?:到|去)?|前往|带我去|带路去)\s*[::]?\s*([^\n,。!?]+)/);
307
+ if (navigationMatch?.[1]) {
308
+ return {
309
+ handling_required: true,
310
+ command: "take_navigation",
311
+ is_recall: true,
312
+ action: "open",
313
+ poi_name: navigationMatch[1].trim(),
314
+ navi_type: defaultNavigationMode,
315
+ };
316
+ }
317
+
318
+ return null;
319
+ }
320
+
321
+ export function lingzhuToOpenAI(
322
+ messages: LingzhuMessage[],
323
+ context?: LingzhuContext,
324
+ options: LingzhuTransformOptions = {}
325
+ ): OpenAIMessage[] {
326
+ const openaiMessages: OpenAIMessage[] = [];
327
+ const systemParts: string[] = [createDefaultSystemPrompt(options.enableExperimentalNativeActions === true)];
328
+
329
+ if (options.systemPrompt) {
330
+ systemParts.push(options.systemPrompt);
331
+ }
332
+
333
+ if (systemParts.length > 0) {
334
+ openaiMessages.push({
335
+ role: "system",
336
+ content: systemParts.join("\n\n"),
337
+ });
338
+ }
339
+
340
+ if (context) {
341
+ const parts: string[] = [];
342
+ if (context.currentTime) parts.push(`当前时间: ${context.currentTime}`);
343
+ if (context.location) parts.push(`位置: ${context.location}`);
344
+ if (context.weather) parts.push(`天气: ${context.weather}`);
345
+ if (context.battery) parts.push(`电量: ${context.battery}%`);
346
+ if (context.latitude && context.longitude) {
347
+ parts.push(`坐标: ${context.latitude}, ${context.longitude}`);
348
+ }
349
+ if (context.lang) parts.push(`语言: ${context.lang}`);
350
+ if (context.runningApp) parts.push(`当前运行应用: ${context.runningApp}`);
351
+
352
+ if (parts.length > 0) {
353
+ openaiMessages.push({
354
+ role: "system",
355
+ content: `[rokid glasses 信息]\n${parts.join("\n")}`,
356
+ });
357
+ }
358
+ }
359
+
360
+ for (const msg of messages) {
361
+ const role = msg.role === "agent" ? "assistant" : "user";
362
+
363
+ if (msg.type === "text" && msg.text) {
364
+ openaiMessages.push({ role, content: msg.text });
365
+ } else if (msg.type === "text" && msg.content) {
366
+ openaiMessages.push({ role, content: msg.content });
367
+ } else if (msg.type === "image" && msg.image_url) {
368
+ openaiMessages.push({
369
+ role,
370
+ content: [{ type: "image_url", image_url: { url: msg.image_url } }],
371
+ });
372
+ }
373
+ }
374
+
375
+ return openaiMessages;
376
+ }
377
+
378
+ export function parseToolCallFromAccumulated(
379
+ toolName: string,
380
+ argsStr: string,
381
+ options: LingzhuTransformOptions = {}
382
+ ): LingzhuSSEData["tool_call"] | null {
383
+ const defaultNavigationMode = resolveNavigationMode(options.defaultNavigationMode);
384
+ const command = resolveToolCommand(toolName, options);
385
+ if (!command) {
386
+ return null;
387
+ }
388
+
389
+ let args: Record<string, unknown> = {};
390
+ try {
391
+ args = JSON.parse(argsStr || "{}");
392
+ } catch {
393
+ args = {};
394
+ }
395
+
396
+ const toolCall: LingzhuToolCall = {
397
+ handling_required: true,
398
+ command,
399
+ is_recall: true,
400
+ };
401
+
402
+ switch (command) {
403
+ case "take_navigation":
404
+ toolCall.action = (args.action as string) || "open";
405
+ if (args.destination || args.poi_name || args.address) {
406
+ toolCall.poi_name = String(args.destination || args.poi_name || args.address);
407
+ }
408
+ toolCall.navi_type = resolveNavigationMode(args.navi_type ?? args.type, defaultNavigationMode);
409
+ break;
410
+
411
+ case "control_calendar":
412
+ toolCall.action = (args.action as string) || "create";
413
+ if (args.title) toolCall.title = String(args.title);
414
+ if (args.start_time || args.startTime) {
415
+ toolCall.start_time = String(args.start_time || args.startTime);
416
+ }
417
+ if (args.end_time || args.endTime) {
418
+ toolCall.end_time = String(args.end_time || args.endTime);
419
+ }
420
+ break;
421
+
422
+ case "send_notification":
423
+ case "send_toast":
424
+ case "speak_tts":
425
+ if (args.content) toolCall.content = String(args.content);
426
+ if (typeof args.play_tts === "boolean") toolCall.play_tts = args.play_tts;
427
+ if (args.icon_type) toolCall.icon_type = String(args.icon_type);
428
+ break;
429
+
430
+ case "start_video_record":
431
+ if (typeof args.duration_sec === "number") toolCall.duration_sec = args.duration_sec;
432
+ if (typeof args.width === "number") toolCall.width = args.width;
433
+ if (typeof args.height === "number") toolCall.height = args.height;
434
+ if (typeof args.quality === "number") toolCall.quality = args.quality;
435
+ break;
436
+
437
+ case "open_custom_view":
438
+ if (args.view_name) toolCall.view_name = String(args.view_name);
439
+ if (args.view_payload) {
440
+ toolCall.view_payload = typeof args.view_payload === "string"
441
+ ? args.view_payload
442
+ : JSON.stringify(args.view_payload);
443
+ }
444
+ break;
445
+ }
446
+
447
+ return toolCall;
448
+ }
449
+
450
+ export function openaiChunkToLingzhu(
451
+ chunk: OpenAIChunk,
452
+ messageId: string,
453
+ agentId: string,
454
+ options: LingzhuTransformOptions = {}
455
+ ): LingzhuSSEData {
456
+ const choice = chunk.choices?.[0];
457
+ const delta = choice?.delta;
458
+ const content = delta?.content || "";
459
+ const isFinish = choice?.finish_reason != null;
460
+ const toolCalls = delta?.tool_calls;
461
+
462
+ if (toolCalls && toolCalls.length > 0) {
463
+ const tc = toolCalls[0];
464
+ if (tc.function?.name) {
465
+ const parsedToolCall = parseToolCallFromAccumulated(
466
+ tc.function.name,
467
+ tc.function.arguments || "{}",
468
+ options
469
+ );
470
+
471
+ if (parsedToolCall) {
472
+ return {
473
+ role: "agent",
474
+ type: "tool_call",
475
+ message_id: messageId,
476
+ agent_id: agentId,
477
+ is_finish: isFinish,
478
+ tool_call: parsedToolCall,
479
+ };
480
+ }
481
+ }
482
+ }
483
+
484
+ return {
485
+ role: "agent",
486
+ type: "answer",
487
+ answer_stream: content,
488
+ message_id: messageId,
489
+ agent_id: agentId,
490
+ is_finish: isFinish,
491
+ };
492
+ }
493
+
494
+ export function createFollowUpResponse(
495
+ suggestions: string[],
496
+ messageId: string,
497
+ agentId: string
498
+ ): LingzhuSSEData {
499
+ return {
500
+ role: "agent",
501
+ type: "follow_up",
502
+ message_id: messageId,
503
+ agent_id: agentId,
504
+ is_finish: true,
505
+ follow_up: suggestions,
506
+ };
507
+ }
508
+
509
+ export function extractFollowUpFromText(text: string, limit = 3): string[] | null {
510
+ const patterns = [
511
+ /你还可以(?:问我|继续问|试试)[::\s]*(.+)/,
512
+ /(?:推荐|建议)(?:问题|提问)?[::\s]*(.+)/,
513
+ /(?:相关|更多)问题[::\s]*(.+)/,
514
+ ];
515
+
516
+ for (const pattern of patterns) {
517
+ const match = text.match(pattern);
518
+ if (!match) {
519
+ continue;
520
+ }
521
+
522
+ const suggestions = match[1]
523
+ .split(/[,,;\n]/)
524
+ .map((item) => item.replace(/^\d+[.、\s]*/, "").trim())
525
+ .filter((item) => item.length > 0 && item.length < 100);
526
+
527
+ if (suggestions.length > 0) {
528
+ return suggestions.slice(0, Math.max(0, limit));
529
+ }
530
+ }
531
+
532
+ return null;
533
+ }
534
+
535
+ export function formatLingzhuSSE(
536
+ event: "message" | "done",
537
+ data: LingzhuSSEData | string
538
+ ): string {
539
+ const dataStr = typeof data === "string" ? data : JSON.stringify(data);
540
+ return `event:${event}\ndata:${dataStr}\n\n`;
541
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 灵珠插件配置类型
3
+ */
4
+ export interface LingzhuConfig {
5
+ enabled?: boolean;
6
+ authAk?: string;
7
+ agentId?: string;
8
+ /** 是否将设备信息(metadata)传递给 OpenClaw,默认 true */
9
+ includeMetadata?: boolean;
10
+ /** OpenClaw /v1/chat/completions 请求超时(毫秒),默认 60000 */
11
+ requestTimeoutMs?: number;
12
+ /** 自定义 system prompt,用于增强模型对设备工具的调用约束 */
13
+ systemPrompt?: string;
14
+ /** 默认导航方式:0=驾车,1=步行,2=骑行 */
15
+ defaultNavigationMode?: "0" | "1" | "2";
16
+ /** 是否启用 follow_up 建议,默认 true */
17
+ enableFollowUp?: boolean;
18
+ /** follow_up 最多返回多少条建议,默认 3 */
19
+ followUpMaxCount?: number;
20
+ /** 下载或解码图片时允许的最大字节数,默认 5MB */
21
+ maxImageBytes?: number;
22
+ /** 会话策略:per_user=按 user_id 保持上下文,shared_agent=同 agent 共用,per_message=每次独立 */
23
+ sessionMode?: "per_user" | "shared_agent" | "per_message";
24
+ /** 会话 key 前缀,默认 lingzhu */
25
+ sessionNamespace?: string;
26
+ /** 是否在请求进入后立刻发送一条可见的“已收到”回执,默认 true */
27
+ autoReceiptAck?: boolean;
28
+ /** 是否在长时间上游静默期间发送可见进度帧,默认 true */
29
+ visibleProgressHeartbeat?: boolean;
30
+ /** 可见进度帧的最小间隔秒数,默认 10 */
31
+ visibleProgressHeartbeatSec?: number;
32
+ /** 是否写入桥接调试日志到文件 */
33
+ debugLogging?: boolean;
34
+ /** 是否在调试日志里写入完整请求/响应载荷 */
35
+ debugLogPayloads?: boolean;
36
+ /** 调试日志目录,留空则写到插件目录下 logs/ */
37
+ debugLogDir?: string;
38
+ /** 是否启用实验性原生动作映射 */
39
+ enableExperimentalNativeActions?: boolean;
40
+ }
41
+
42
+ /**
43
+ * 灵珠请求消息格式
44
+ */
45
+ export interface LingzhuMessage {
46
+ role: "user" | "agent";
47
+ type: "text" | "image";
48
+ text?: string;
49
+ content?: string;
50
+ image_url?: string;
51
+ }
52
+
53
+ /**
54
+ * 灵珠请求上下文
55
+ */
56
+ export interface LingzhuContext {
57
+ location?: string;
58
+ latitude?: string;
59
+ longitude?: string;
60
+ weather?: string;
61
+ battery?: string;
62
+ currentTime?: string;
63
+ lang?: string;
64
+ company_id?: number;
65
+ runningApp?: string;
66
+ }
67
+
68
+ export interface LingzhuMetadataEnvelope {
69
+ context?: LingzhuContext;
70
+ [key: string]: unknown;
71
+ }
72
+
73
+ /**
74
+ * 灵珠请求体
75
+ */
76
+ export interface LingzhuRequest {
77
+ message_id: string;
78
+ agent_id: string;
79
+ message: LingzhuMessage[];
80
+ user_id?: string;
81
+ /** metadata 直接包含设备上下文信息(非嵌套在 context 下) */
82
+ metadata?: LingzhuContext | LingzhuMetadataEnvelope;
83
+ }
84
+
85
+ /**
86
+ * 灵珠工具调用
87
+ */
88
+ export interface LingzhuToolCall {
89
+ handling_required: boolean;
90
+ command:
91
+ | "take_photo"
92
+ | "take_navigation"
93
+ | "notify_agent_off"
94
+ | "control_calendar"
95
+ | "send_notification"
96
+ | "send_toast"
97
+ | "speak_tts"
98
+ | "start_video_record"
99
+ | "stop_video_record"
100
+ | "open_custom_view";
101
+ is_recall?: boolean;
102
+ action?: string;
103
+ poi_name?: string;
104
+ navi_type?: string;
105
+ title?: string;
106
+ start_time?: string;
107
+ end_time?: string;
108
+ content?: string;
109
+ play_tts?: boolean;
110
+ icon_type?: string;
111
+ duration_sec?: number;
112
+ width?: number;
113
+ height?: number;
114
+ quality?: number;
115
+ view_name?: string;
116
+ view_payload?: string;
117
+ }
118
+
119
+ /**
120
+ * 灵珠 SSE 响应数据
121
+ */
122
+ export interface LingzhuSSEData {
123
+ role: "agent";
124
+ type: "answer" | "tool_call" | "follow_up";
125
+ answer_stream?: string;
126
+ message_id: string;
127
+ agent_id: string;
128
+ is_finish: boolean;
129
+ follow_up?: string[];
130
+ tool_call?: LingzhuToolCall;
131
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["index.ts", "src/**/*.ts"]
14
+ }