@openviking/openclaw-plugin 2026.5.26 → 2026.6.17-dev.1

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 (112) hide show
  1. package/INSTALL-AGENT.md +355 -355
  2. package/INSTALL-ZH.md +405 -345
  3. package/INSTALL.md +366 -362
  4. package/README.md +364 -338
  5. package/adapters/http-transport.ts +3 -0
  6. package/adapters/resource-packager.ts +141 -0
  7. package/auto-recall.ts +518 -257
  8. package/client.ts +963 -939
  9. package/commands/setup.ts +1169 -1002
  10. package/config/feature-gates.json +10 -0
  11. package/config.ts +819 -476
  12. package/context-engine.ts +408 -1737
  13. package/dist/adapters/http-transport.js +1 -0
  14. package/dist/adapters/resource-packager.js +114 -0
  15. package/dist/auto-recall.js +228 -32
  16. package/dist/client.js +125 -161
  17. package/dist/commands/setup.js +170 -32
  18. package/dist/config.js +285 -64
  19. package/dist/context-engine.js +64 -1179
  20. package/dist/index.js +209 -1470
  21. package/dist/memory-ranking.js +35 -12
  22. package/dist/plugin/command-registration.js +5 -0
  23. package/dist/plugin/openviking-archive-tools.js +210 -0
  24. package/dist/plugin/openviking-bypass-runtime.js +8 -0
  25. package/dist/plugin/openviking-client-runtime.js +28 -0
  26. package/dist/plugin/openviking-command-args.js +146 -0
  27. package/dist/plugin/openviking-command-definitions.js +206 -0
  28. package/dist/plugin/openviking-context-engine-ref.js +11 -0
  29. package/dist/plugin/openviking-context-engine-registration.js +23 -0
  30. package/dist/plugin/openviking-feature-gates.js +250 -0
  31. package/dist/plugin/openviking-import-runtime.js +50 -0
  32. package/dist/plugin/openviking-import-tools.js +86 -0
  33. package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
  34. package/dist/plugin/openviking-memory-recall-tools.js +233 -0
  35. package/dist/plugin/openviking-memory-tools.js +199 -0
  36. package/dist/plugin/openviking-query-config-command.js +203 -0
  37. package/dist/plugin/openviking-query-runtime.js +367 -0
  38. package/dist/plugin/openviking-query-tools.js +91 -0
  39. package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
  40. package/dist/plugin/openviking-recall-trace-tools.js +38 -0
  41. package/dist/plugin/openviking-runtime-state.js +26 -0
  42. package/dist/plugin/openviking-runtime-utils.js +62 -0
  43. package/dist/plugin/openviking-services.js +20 -0
  44. package/dist/plugin/openviking-session-routing-runtime.js +61 -0
  45. package/dist/plugin/openviking-tool-result-tools.js +268 -0
  46. package/dist/plugin/recall-trace-routes.js +77 -0
  47. package/dist/plugin/tool-registration.js +23 -0
  48. package/dist/process-manager.js +5 -17
  49. package/dist/query-config.js +336 -0
  50. package/dist/recall-trace.js +309 -0
  51. package/dist/registries/recall-resource-types.js +58 -0
  52. package/dist/routing/identity-routing.js +152 -0
  53. package/dist/routing/memory-uri.js +7 -0
  54. package/dist/services/context-lifecycle-service.js +847 -0
  55. package/dist/services/context-message-adapter.js +340 -0
  56. package/dist/session-transcript-repair.js +0 -1
  57. package/dist/text-utils.js +14 -31
  58. package/dist/token-estimator.js +51 -0
  59. package/index.ts +408 -1985
  60. package/install-manifest.json +57 -47
  61. package/memory-ranking.ts +303 -267
  62. package/openclaw.plugin.json +497 -335
  63. package/package.json +86 -76
  64. package/plugin/command-registration.ts +34 -0
  65. package/plugin/openviking-archive-tools.ts +301 -0
  66. package/plugin/openviking-bypass-runtime.ts +26 -0
  67. package/plugin/openviking-client-runtime.ts +74 -0
  68. package/plugin/openviking-command-args.ts +187 -0
  69. package/plugin/openviking-command-definitions.ts +294 -0
  70. package/plugin/openviking-context-engine-ref.ts +16 -0
  71. package/plugin/openviking-context-engine-registration.ts +97 -0
  72. package/plugin/openviking-feature-gates.ts +335 -0
  73. package/plugin/openviking-import-runtime.ts +105 -0
  74. package/plugin/openviking-import-tools.ts +133 -0
  75. package/plugin/openviking-lifecycle-hooks.ts +65 -0
  76. package/plugin/openviking-memory-recall-tools.ts +368 -0
  77. package/plugin/openviking-memory-tools.ts +299 -0
  78. package/plugin/openviking-query-config-command.ts +230 -0
  79. package/plugin/openviking-query-formatters.ts +108 -0
  80. package/plugin/openviking-query-runtime.ts +476 -0
  81. package/plugin/openviking-query-tools.ts +161 -0
  82. package/plugin/openviking-recall-trace-runtime.ts +482 -0
  83. package/plugin/openviking-recall-trace-tools.ts +99 -0
  84. package/plugin/openviking-runtime-state.ts +52 -0
  85. package/plugin/openviking-runtime-utils.ts +67 -0
  86. package/plugin/openviking-services.ts +49 -0
  87. package/plugin/openviking-session-routing-runtime.ts +105 -0
  88. package/plugin/openviking-tool-result-tools.ts +397 -0
  89. package/plugin/recall-trace-routes.ts +133 -0
  90. package/plugin/tool-registration.ts +51 -0
  91. package/process-manager.ts +41 -45
  92. package/query-config.ts +400 -0
  93. package/recall-trace.ts +442 -0
  94. package/registries/openviking-tools.ts +84 -0
  95. package/registries/recall-resource-types.ts +74 -0
  96. package/routing/identity-routing.ts +210 -0
  97. package/routing/memory-uri.ts +8 -0
  98. package/runtime-utils.ts +5 -5
  99. package/services/context-lifecycle-service.ts +1209 -0
  100. package/services/context-message-adapter.ts +375 -0
  101. package/services/setup/config-writer.ts +125 -0
  102. package/services/setup/exit-utils.ts +5 -0
  103. package/services/setup/package-metadata.ts +56 -0
  104. package/services/setup/probe-service.ts +143 -0
  105. package/services/setup/setup-flow.ts +306 -0
  106. package/services/setup/version-compatibility.ts +41 -0
  107. package/session-transcript-repair.ts +528 -530
  108. package/skills/install-openviking-memory/SKILL.md +752 -665
  109. package/skills/openviking-context-database/SKILL.md +286 -0
  110. package/text-utils.ts +488 -507
  111. package/token-estimator.ts +65 -0
  112. package/tool-call-id.ts +331 -331
@@ -0,0 +1,340 @@
1
+ import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js";
2
+ export function toRoleId(senderId) {
3
+ if (!senderId) {
4
+ return undefined;
5
+ }
6
+ const normalized = senderId
7
+ .trim()
8
+ .replace(/[^a-zA-Z0-9_-]+/g, "_")
9
+ .replace(/^_+|_+$/g, "")
10
+ .replace(/_+/g, "_");
11
+ return normalized || undefined;
12
+ }
13
+ /**
14
+ * Convert an OpenViking stored message (parts-based format) into one or more
15
+ * OpenClaw AgentMessages (content-blocks format).
16
+ *
17
+ * For assistant messages with ToolParts, this produces:
18
+ * 1. The assistant message with canonical toolCall blocks in its content array
19
+ * 2. A separate toolResult message per ToolPart (carrying tool_output)
20
+ */
21
+ export function convertToAgentMessages(msg) {
22
+ const parts = msg.parts ?? [];
23
+ const contentBlocks = [];
24
+ const toolCallBlocks = [];
25
+ const toolResults = [];
26
+ for (const part of parts) {
27
+ if (!part || typeof part !== "object")
28
+ continue;
29
+ const p = part;
30
+ if (p.type === "text" && typeof p.text === "string") {
31
+ contentBlocks.push({ type: "text", text: p.text });
32
+ }
33
+ else if (p.type === "context") {
34
+ if (typeof p.abstract === "string" && p.abstract) {
35
+ contentBlocks.push({ type: "text", text: p.abstract });
36
+ }
37
+ }
38
+ else if (p.type === "tool") {
39
+ const toolId = typeof p.tool_id === "string" ? p.tool_id : "";
40
+ const toolName = typeof p.tool_name === "string" ? p.tool_name : undefined;
41
+ const status = typeof p.tool_status === "string" ? p.tool_status : "unknown";
42
+ const output = typeof p.tool_output === "string" ? p.tool_output : "";
43
+ const ref = typeof p.tool_output_ref === "string" ? p.tool_output_ref : "";
44
+ const originalChars = typeof p.tool_output_original_chars === "number"
45
+ ? p.tool_output_original_chars
46
+ : undefined;
47
+ if (toolId) {
48
+ toolCallBlocks.push({
49
+ type: "toolCall",
50
+ id: toolId,
51
+ name: toolName ?? "unknown",
52
+ arguments: p.tool_input ?? {},
53
+ });
54
+ let resultText = (status === "completed" || status === "error")
55
+ ? (output || "(no output)")
56
+ : "(interrupted — tool did not complete)";
57
+ if (ref && !resultText.includes(ref)) {
58
+ const suffix = originalChars !== undefined
59
+ ? `\n[tool-result-ref] ${ref} original_chars=${originalChars}`
60
+ : `\n[tool-result-ref] ${ref}`;
61
+ resultText += suffix;
62
+ }
63
+ const resultPayload = {
64
+ role: "toolResult",
65
+ toolCallId: toolId,
66
+ content: [{ type: "text", text: resultText }],
67
+ isError: status === "error",
68
+ };
69
+ if (toolName) {
70
+ resultPayload.toolName = toolName;
71
+ }
72
+ toolResults.push(resultPayload);
73
+ }
74
+ else {
75
+ const fallbackName = toolName ?? "unknown";
76
+ const segments = [`[${fallbackName}] (${status})`];
77
+ if (p.tool_input) {
78
+ try {
79
+ segments.push(`Input: ${JSON.stringify(p.tool_input)}`);
80
+ }
81
+ catch {
82
+ // non-serializable input, skip
83
+ }
84
+ }
85
+ if (output) {
86
+ segments.push(`Output: ${output}`);
87
+ }
88
+ contentBlocks.push({ type: "text", text: segments.join("\n") });
89
+ }
90
+ }
91
+ }
92
+ const result = [];
93
+ if (msg.role === "assistant") {
94
+ result.push({ role: "assistant", content: [...contentBlocks, ...toolCallBlocks] });
95
+ result.push(...toolResults);
96
+ }
97
+ else {
98
+ const texts = contentBlocks
99
+ .filter((b) => b.type === "text")
100
+ .map((b) => b.text);
101
+ if (texts.length > 0) {
102
+ result.push({ role: msg.role, content: texts.join("\n") });
103
+ }
104
+ else if (toolCallBlocks.length === 0) {
105
+ result.push({ role: msg.role, content: "" });
106
+ }
107
+ if (toolCallBlocks.length > 0) {
108
+ result.push({ role: "assistant", content: toolCallBlocks });
109
+ result.push(...toolResults);
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+ export function formatMessageFaithful(msg) {
115
+ const roleTag = `[${msg.role}]`;
116
+ if (!msg.parts || msg.parts.length === 0) {
117
+ return `${roleTag}: (empty)`;
118
+ }
119
+ const sections = [];
120
+ for (const part of msg.parts) {
121
+ if (!part || typeof part !== "object")
122
+ continue;
123
+ switch (part.type) {
124
+ case "text":
125
+ if (part.text)
126
+ sections.push(part.text);
127
+ break;
128
+ case "tool": {
129
+ const status = part.tool_status ?? "unknown";
130
+ const header = `[Tool: ${part.tool_name ?? "unknown"}] (${status})`;
131
+ const inputStr = part.tool_input
132
+ ? `Input: ${JSON.stringify(part.tool_input, null, 2)}`
133
+ : "";
134
+ const outputStr = part.tool_output ? `Output:\n${part.tool_output}` : "";
135
+ sections.push([header, inputStr, outputStr].filter(Boolean).join("\n"));
136
+ break;
137
+ }
138
+ case "context":
139
+ sections.push(`[Context: ${part.uri ?? "?"}]${part.abstract ? ` ${part.abstract}` : ""}`);
140
+ break;
141
+ default:
142
+ sections.push(`[${part.type}]: ${JSON.stringify(part)}`);
143
+ }
144
+ }
145
+ return `${roleTag}:\n${sections.join("\n\n")}`;
146
+ }
147
+ /** Merge consecutive assistant messages by concatenating their content arrays. */
148
+ export function mergeConsecutiveAssistants(messages) {
149
+ const result = [];
150
+ for (const msg of messages) {
151
+ const prev = result[result.length - 1];
152
+ if (msg.role === "assistant" && prev?.role === "assistant") {
153
+ const prevContent = Array.isArray(prev.content) ? prev.content : [{ type: "text", text: prev.content }];
154
+ const currContent = Array.isArray(msg.content) ? msg.content : [{ type: "text", text: msg.content }];
155
+ prev.content = [...prevContent, ...currContent];
156
+ }
157
+ else {
158
+ result.push({ ...msg });
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+ /**
164
+ * Hoist tool_result blocks to the front of a content array.
165
+ *
166
+ * The Anthropic / Bedrock / Gemini APIs require tool_result blocks to appear
167
+ * at the START of a user message's content array (a tool_result must follow
168
+ * the assistant tool_use that produced it). When mergeConsecutiveUsers
169
+ * merges two user messages, the previous content's text blocks may end up
170
+ * before tool_results from the second message — this function fixes the order.
171
+ *
172
+ * Same pattern as Claude Code's hoistToolResults in src/utils/messages.ts.
173
+ */
174
+ function hoistToolResults(content) {
175
+ const toolResults = [];
176
+ const others = [];
177
+ for (const block of content) {
178
+ if (block &&
179
+ typeof block === "object" &&
180
+ "type" in block &&
181
+ block.type === "tool_result") {
182
+ toolResults.push(block);
183
+ }
184
+ else {
185
+ others.push(block);
186
+ }
187
+ }
188
+ return [...toolResults, ...others];
189
+ }
190
+ /**
191
+ * Merge consecutive user messages by concatenating their content arrays.
192
+ *
193
+ * Mirror of mergeConsecutiveAssistants. Required because Gemini and Anthropic
194
+ * APIs reject consecutive same-role messages with stopReason=stop payloads=0
195
+ * (empty response). Three independent sources can inject role: "user":
196
+ *
197
+ * 1. Archive commit: "[Session History Summary]" via buildArchiveMemory
198
+ * 2. OpenClaw yield events: "[sessions_yield interrupt]" / "Turn yielded. ..."
199
+ * 3. Audio/Telegram metadata: "[Audio] User text: [Telegram <name>...]"
200
+ *
201
+ * Without merging, these can stack into 2-5 consecutive user turns. The
202
+ * 1P Anthropic API would merge server-side, but Bedrock/Gemini won't —
203
+ * we merge client-side for wire-format consistency.
204
+ *
205
+ * Note: this MUST run AFTER sanitizeToolUseResultPairing because that pass
206
+ * may strip orphaned tool_use / tool_result blocks and thereby create new
207
+ * user-user adjacencies that didn't exist in the input.
208
+ *
209
+ * Tracks issue #1724.
210
+ */
211
+ export function mergeConsecutiveUsers(messages) {
212
+ const result = [];
213
+ for (const msg of messages) {
214
+ const prev = result[result.length - 1];
215
+ if (msg.role === "user" && prev?.role === "user") {
216
+ const prevContent = Array.isArray(prev.content) ? prev.content : [{ type: "text", text: prev.content }];
217
+ const currContent = Array.isArray(msg.content) ? msg.content : [{ type: "text", text: msg.content }];
218
+ prev.content = hoistToolResults([...prevContent, ...currContent]);
219
+ }
220
+ else {
221
+ result.push({ ...msg });
222
+ }
223
+ }
224
+ return result;
225
+ }
226
+ /**
227
+ * Defensive role-alternation invariant check.
228
+ *
229
+ * After mergeConsecutiveUsers + mergeConsecutiveAssistants, the message stream
230
+ * should already alternate user/assistant. But sanitizeToolUseResultPairing
231
+ * can in rare cases strip a user_with_tool_result message that was the only
232
+ * thing separating two assistant messages, leaving an assistant-assistant
233
+ * adjacency that upstream merge passes can't fix.
234
+ *
235
+ * When detected, we insert a placeholder user message — matching Claude Code's
236
+ * NO_CONTENT_MESSAGE pattern (see CC src/utils/messages.ts:5375-5388) — to
237
+ * preserve the alternation contract that Gemini / Anthropic require.
238
+ */
239
+ export function ensureAlternation(messages) {
240
+ const result = [];
241
+ for (const msg of messages) {
242
+ const prev = result[result.length - 1];
243
+ if (prev && prev.role === "assistant" && msg.role === "assistant") {
244
+ result.push({
245
+ role: "user",
246
+ content: "(no content)",
247
+ });
248
+ }
249
+ result.push(msg);
250
+ }
251
+ return result;
252
+ }
253
+ function normalizeAssistantContent(messages) {
254
+ for (let i = 0; i < messages.length; i++) {
255
+ const msg = messages[i];
256
+ if (msg?.role === "assistant" && typeof msg.content === "string") {
257
+ messages[i] = {
258
+ ...msg,
259
+ content: [{ type: "text", text: msg.content }],
260
+ };
261
+ }
262
+ }
263
+ }
264
+ function canonicalizeAssistantBlock(block) {
265
+ if (!block || typeof block !== "object") {
266
+ return block;
267
+ }
268
+ const rec = block;
269
+ const type = typeof rec.type === "string" ? rec.type : "";
270
+ if (type === "toolCall") {
271
+ if (rec.arguments !== undefined) {
272
+ return rec;
273
+ }
274
+ return {
275
+ ...rec,
276
+ arguments: rec.input ?? rec.toolInput ?? {},
277
+ };
278
+ }
279
+ if (type === "toolUse" || type === "functionCall" || type === "tool_call") {
280
+ return {
281
+ type: "toolCall",
282
+ id: rec.id ?? rec.toolCallId ?? rec.toolUseId,
283
+ name: rec.name,
284
+ arguments: rec.arguments ?? rec.input ?? rec.toolInput ?? {},
285
+ };
286
+ }
287
+ return rec;
288
+ }
289
+ function canonicalizeAgentMessages(messages) {
290
+ let changed = false;
291
+ const next = messages.map((msg) => {
292
+ if (!msg || typeof msg !== "object") {
293
+ return msg;
294
+ }
295
+ if (msg.role === "assistant") {
296
+ const content = Array.isArray(msg.content)
297
+ ? msg.content.map((block) => canonicalizeAssistantBlock(block))
298
+ : typeof msg.content === "string"
299
+ ? [{ type: "text", text: msg.content }]
300
+ : msg.content;
301
+ if (content !== msg.content) {
302
+ changed = true;
303
+ return { ...msg, content };
304
+ }
305
+ return msg;
306
+ }
307
+ if (msg.role === "toolResult") {
308
+ const raw = msg;
309
+ const toolCallId = (typeof raw.toolCallId === "string" && raw.toolCallId) ||
310
+ (typeof raw.toolUseId === "string" && raw.toolUseId) ||
311
+ undefined;
312
+ const toolName = typeof raw.toolName === "string" && raw.toolName.trim()
313
+ ? raw.toolName.trim()
314
+ : undefined;
315
+ const nextMsg = {
316
+ ...msg,
317
+ ...(toolCallId ? { toolCallId } : {}),
318
+ ...(toolName ? { toolName } : {}),
319
+ };
320
+ if (nextMsg !== msg) {
321
+ changed = true;
322
+ }
323
+ return nextMsg;
324
+ }
325
+ return msg;
326
+ });
327
+ return changed ? next : messages;
328
+ }
329
+ export function sanitizeAgentMessagesForProvider(messages) {
330
+ normalizeAssistantContent(messages);
331
+ const canonical = canonicalizeAgentMessages(messages);
332
+ // Defense in depth (issue #1724):
333
+ // 1) sanitizeToolUseResultPairing may strip orphaned tool_use/tool_result,
334
+ // potentially creating new user-user or assistant-assistant adjacencies.
335
+ // 2) mergeConsecutiveUsers fixes user-user (mirror of mergeConsecutiveAssistants
336
+ // already running inside buildSessionContext).
337
+ // 3) ensureAlternation is a final invariant check for the rare
338
+ // assistant-assistant case that the merges can't reach.
339
+ return ensureAlternation(mergeConsecutiveUsers(sanitizeToolUseResultPairing(canonical)));
340
+ }
@@ -153,7 +153,6 @@ function normalizeToolResultName(message, fallbackName) {
153
153
  }
154
154
  return message;
155
155
  }
156
- export { makeMissingToolResult };
157
156
  export function stripToolResultDetails(messages) {
158
157
  let touched = false;
159
158
  const out = [];
@@ -9,6 +9,7 @@ export const MEMORY_TRIGGERS = [
9
9
  ];
10
10
  const CJK_CHAR_REGEX = /[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff\uac00-\ud7af]/;
11
11
  const RELEVANT_MEMORIES_BLOCK_RE = /<relevant-memories>[\s\S]*?<\/relevant-memories>/gi;
12
+ const OPENVIKING_CONTEXT_BLOCK_RE = /<openviking-context\b[^>]*>[\s\S]*?<\/openviking-context>/gi;
12
13
  const CONVERSATION_METADATA_BLOCK_RE = /(?:^|\n)\s*(?:Conversation info|Conversation metadata|会话信息|对话信息)\s*(?:\([^)]+\))?\s*:\s*```[\s\S]*?```/gi;
13
14
  /** Strips "Sender (untrusted metadata): ```json ... ```" so capture sends clean text to OpenViking extract. */
14
15
  const SENDER_METADATA_BLOCK_RE = /Sender\s*\([^)]*\)\s*:\s*```[\s\S]*?```/gi;
@@ -36,11 +37,16 @@ function looksLikeMetadataJsonBlock(content) {
36
37
  return matchedKeys.size >= 3;
37
38
  }
38
39
  const HEARTBEAT_RE = /\bHEARTBEAT(?:\.md|_OK)\b/;
40
+ const TOOL_PLACEHOLDER_RE = /^\s*\[tool(?::\s*|Use:\s*)[^\]]+\]\s*$/i;
39
41
  export function sanitizeUserTextForCapture(text) {
40
42
  // 过滤 HEARTBEAT 健康检查消息
41
43
  if (HEARTBEAT_RE.test(text)) {
42
44
  return "";
43
45
  }
46
+ // Drop legacy synthetic tool placeholders before they reach memory extraction.
47
+ if (TOOL_PLACEHOLDER_RE.test(text)) {
48
+ return "";
49
+ }
44
50
  // 处理 Compactor 系统消息,提取实际用户输入
45
51
  // 格式: "System: [时间] Compacted ... Context ... [时间] 实际内容"
46
52
  if (COMPACTED_SYSTEM_MSG_RE.test(text)) {
@@ -51,6 +57,7 @@ export function sanitizeUserTextForCapture(text) {
51
57
  return "";
52
58
  }
53
59
  return text
60
+ .replace(OPENVIKING_CONTEXT_BLOCK_RE, " ")
54
61
  .replace(RELEVANT_MEMORIES_BLOCK_RE, " ")
55
62
  .replace(CONVERSATION_METADATA_BLOCK_RE, " ")
56
63
  .replace(SENDER_METADATA_BLOCK_RE, " ")
@@ -61,6 +68,13 @@ export function sanitizeUserTextForCapture(text) {
61
68
  .replace(/\s+/g, " ")
62
69
  .trim();
63
70
  }
71
+ export function stripOpenVikingContextInjection(text) {
72
+ return text
73
+ .replace(OPENVIKING_CONTEXT_BLOCK_RE, " ")
74
+ .replace(RELEVANT_MEMORIES_BLOCK_RE, " ")
75
+ .replace(/\s+/g, " ")
76
+ .trim();
77
+ }
64
78
  export function looksLikeQuestionOnlyText(text) {
65
79
  if (!QUESTION_CUE_RE.test(text) || MEMORY_INTENT_RE.test(text)) {
66
80
  return false;
@@ -361,37 +375,6 @@ export function extractNewTurnMessages(messages, startIndex) {
361
375
  });
362
376
  }
363
377
  }
364
- else {
365
- /// 如果原始消息有 toolCall,提取所有工具名并生成占位符
366
- if (role === "assistant" && Array.isArray(content)) {
367
- const toolNames = [];
368
- // 收集所有 toolCall 的 tool_name
369
- for (const block of content) {
370
- const b = block;
371
- const blockType = b?.type;
372
- if (blockType === "toolCall" ||
373
- blockType === "toolUse" ||
374
- blockType === "tool_use" ||
375
- blockType === "tool_call") {
376
- const name = b?.name || b?.toolName;
377
- if (name && typeof name === "string" && name.trim()) {
378
- toolNames.push(name.trim());
379
- }
380
- }
381
- }
382
- // 只有找到 toolCall 时才添加占位符
383
- if (toolNames.length > 0) {
384
- const toolNamesStr = toolNames.join(", ");
385
- result.push({
386
- role: "assistant",
387
- parts: [{
388
- type: "text",
389
- text: `[tool: ${toolNamesStr}]`,
390
- }],
391
- });
392
- }
393
- }
394
- }
395
378
  }
396
379
  return { messages: result, newCount: count };
397
380
  }
@@ -0,0 +1,51 @@
1
+ function isCjkCodePoint(codePoint) {
2
+ return ((codePoint >= 0x3400 && codePoint <= 0x4dbf) ||
3
+ (codePoint >= 0x4e00 && codePoint <= 0x9fff) ||
4
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
5
+ (codePoint >= 0x20000 && codePoint <= 0x2ebef) ||
6
+ (codePoint >= 0x3040 && codePoint <= 0x30ff) ||
7
+ (codePoint >= 0x31f0 && codePoint <= 0x31ff) ||
8
+ (codePoint >= 0xac00 && codePoint <= 0xd7af) ||
9
+ (codePoint >= 0x1100 && codePoint <= 0x11ff) ||
10
+ (codePoint >= 0x3130 && codePoint <= 0x318f) ||
11
+ (codePoint >= 0xff00 && codePoint <= 0xffef) ||
12
+ (codePoint >= 0x3000 && codePoint <= 0x303f));
13
+ }
14
+ function codePointWeight(codePoint) {
15
+ if (isCjkCodePoint(codePoint)) {
16
+ return 1.5;
17
+ }
18
+ if (codePoint > 0xffff) {
19
+ return 2;
20
+ }
21
+ return 0.25;
22
+ }
23
+ export function estimateTextTokens(text) {
24
+ if (!text) {
25
+ return 0;
26
+ }
27
+ let weightedTokens = 0;
28
+ for (const char of text) {
29
+ weightedTokens += codePointWeight(char.codePointAt(0) ?? 0);
30
+ }
31
+ return Math.ceil(weightedTokens);
32
+ }
33
+ export function estimateSerializedTokens(value) {
34
+ if (value === null || value === undefined) {
35
+ return 0;
36
+ }
37
+ if (typeof value === "string") {
38
+ return estimateTextTokens(value);
39
+ }
40
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
41
+ return estimateTextTokens(String(value));
42
+ }
43
+ const serialized = JSON.stringify(value);
44
+ return estimateTextTokens(serialized ?? "");
45
+ }
46
+ export function estimateAgentMessageTokens(message) {
47
+ return estimateSerializedTokens(message);
48
+ }
49
+ export function estimateAgentMessagesTokens(messages) {
50
+ return estimateSerializedTokens(messages);
51
+ }