@yeaft/webchat-agent 0.1.1016 → 0.1.1018

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.
@@ -142,7 +142,8 @@ async function nodeGrep(pattern, searchPath, options) {
142
142
 
143
143
  export default defineTool({
144
144
  name: 'Grep',
145
- description: `Search file contents for a regex pattern.
145
+ description: {
146
+ en: `Search file contents for a regex pattern.
146
147
 
147
148
  Uses ripgrep (rg) when available for fast searching, with a Node.js fallback.
148
149
 
@@ -156,53 +157,101 @@ Guidelines:
156
157
  - Use glob or type filters to narrow the search
157
158
  - Skips binary files and common large directories (node_modules, .git)
158
159
  - Results are limited to 500 matches by default`,
160
+ zh: `用正则表达式搜索文件内容。
161
+
162
+ 优先使用 ripgrep (rg) 快速搜索,回退到 Node.js 实现。
163
+
164
+ 输出模式:
165
+ - "content" — 显示匹配行,含文件路径和行号
166
+ - "files_with_matches" — 仅显示匹配的文件路径(默认)
167
+ - "count" — 显示每个文件的匹配数量
168
+
169
+ 使用指南:
170
+ - 使用正则语法(特殊字符需转义:\\.、\\{ 等)
171
+ - 用 glob 或 type 过滤缩小搜索范围
172
+ - 跳过二进制文件和常见大目录(node_modules、.git)
173
+ - 默认结果限制 500 条`
174
+ },
159
175
  parameters: {
160
176
  type: 'object',
161
177
  properties: {
162
178
  pattern: {
163
179
  type: 'string',
164
- description: 'Regex pattern to search for',
180
+ description: {
181
+ en: 'Regex pattern to search for',
182
+ zh: '要搜索的正则表达式模式',
183
+ },
165
184
  },
166
185
  path: {
167
186
  type: 'string',
168
- description: 'File or directory to search (default: cwd)',
187
+ description: {
188
+ en: 'File or directory to search (default: cwd)',
189
+ zh: '要搜索的文件或目录(默认当前工作目录)',
190
+ },
169
191
  },
170
192
  output_mode: {
171
193
  type: 'string',
172
194
  enum: ['content', 'files_with_matches', 'count'],
173
- description: 'Output format (default: "files_with_matches")',
195
+ description: {
196
+ en: 'Output format (default: "files_with_matches")',
197
+ zh: '输出格式(默认 "files_with_matches")',
198
+ },
174
199
  },
175
200
  glob: {
176
201
  type: 'string',
177
- description: 'Glob filter for file names (e.g. "*.js", "*.{ts,tsx}")',
202
+ description: {
203
+ en: 'Glob filter for file names (e.g. "*.js", "*.{ts,tsx}")',
204
+ zh: '文件名 glob 过滤(如 "*.js"、"*.{ts,tsx}")',
205
+ },
178
206
  },
179
207
  type: {
180
208
  type: 'string',
181
- description: 'File type filter (e.g. "js", "py", "rust")',
209
+ description: {
210
+ en: 'File type filter (e.g. "js", "py", "rust")',
211
+ zh: '文件类型过滤(如 "js"、"py"、"rust")',
212
+ },
182
213
  },
183
214
  case_insensitive: {
184
215
  type: 'boolean',
185
- description: 'Case-insensitive search (default: false)',
216
+ description: {
217
+ en: 'Case-insensitive search (default: false)',
218
+ zh: '不区分大小写搜索(默认 false)',
219
+ },
186
220
  },
187
221
  context: {
188
222
  type: 'number',
189
- description: 'Lines of context around matches (for "content" mode)',
223
+ description: {
224
+ en: 'Lines of context around matches (for "content" mode)',
225
+ zh: '匹配行周围的上下文行数(用于 "content" 模式)',
226
+ },
190
227
  },
191
228
  before: {
192
229
  type: 'number',
193
- description: 'Lines before each match',
230
+ description: {
231
+ en: 'Lines before each match',
232
+ zh: '每个匹配之前显示的行数',
233
+ },
194
234
  },
195
235
  after: {
196
236
  type: 'number',
197
- description: 'Lines after each match',
237
+ description: {
238
+ en: 'Lines after each match',
239
+ zh: '每个匹配之后显示的行数',
240
+ },
198
241
  },
199
242
  multiline: {
200
243
  type: 'boolean',
201
- description: 'Enable multiline matching',
244
+ description: {
245
+ en: 'Enable multiline matching',
246
+ zh: '启用多行匹配',
247
+ },
202
248
  },
203
249
  head_limit: {
204
250
  type: 'number',
205
- description: 'Limit output to first N results (default: 250)',
251
+ description: {
252
+ en: 'Limit output to first N results (default: 250)',
253
+ zh: '限制输出前 N 条结果(默认 250)',
254
+ },
206
255
  },
207
256
  },
208
257
  required: ['pattern'],
@@ -10,22 +10,35 @@ import { searchMessages } from '../conversation/search.js';
10
10
 
11
11
  export default defineTool({
12
12
  name: 'HistorySearch',
13
- description: `Search through past conversation history.
13
+ description: {
14
+ en: `Search through past conversation history.
14
15
 
15
16
  Searches for keywords in previously persisted conversation messages.
16
17
  Useful for finding previous discussions, decisions, or code snippets.
17
18
 
18
19
  Results are returned newest-first with message role and content.`,
20
+ zh: `搜索历史对话记录。
21
+
22
+ 在已持久化的对话消息中按关键词搜索。用于查找之前的讨论、决策或代码片段。
23
+
24
+ 结果按最新优先返回,包含消息角色和内容。`
25
+ },
19
26
  parameters: {
20
27
  type: 'object',
21
28
  properties: {
22
29
  keyword: {
23
30
  type: 'string',
24
- description: 'Search keyword (case-insensitive)',
31
+ description: {
32
+ en: 'Search keyword (case-insensitive)',
33
+ zh: '搜索关键词(不区分大小写)',
34
+ },
25
35
  },
26
36
  limit: {
27
37
  type: 'number',
28
- description: 'Maximum number of results (default: 20)',
38
+ description: {
39
+ en: 'Maximum number of results (default: 20)',
40
+ zh: '最多返回结果数(默认 20)',
41
+ },
29
42
  },
30
43
  },
31
44
  required: ['keyword'],
@@ -8,7 +8,8 @@ import { defineTool } from './types.js';
8
8
 
9
9
  export default defineTool({
10
10
  name: 'ImageGeneration',
11
- description: `Generate an image from a text description.
11
+ description: {
12
+ en: `Generate an image from a text description.
12
13
 
13
14
  Uses a configured image generation API to create images.
14
15
  Requires an image generation API endpoint in config.
@@ -17,21 +18,39 @@ Guidelines:
17
18
  - Provide detailed, specific descriptions for best results
18
19
  - Specify style, composition, and mood
19
20
  - Images are saved to the working directory`,
21
+ zh: `根据文字描述生成图片。
22
+
23
+ 使用配置的图片生成 API 创建图片。需要配置中设置图片生成 API 端点。
24
+
25
+ 使用指南:
26
+ - 提供详细具体的描述以获得最佳效果
27
+ - 指定风格、构图和氛围
28
+ - 图片保存到工作目录`
29
+ },
20
30
  parameters: {
21
31
  type: 'object',
22
32
  properties: {
23
33
  prompt: {
24
34
  type: 'string',
25
- description: 'Text description of the image to generate',
35
+ description: {
36
+ en: 'Text description of the image to generate',
37
+ zh: '要生成图像的文本描述',
38
+ },
26
39
  },
27
40
  output_path: {
28
41
  type: 'string',
29
- description: 'File path to save the generated image',
42
+ description: {
43
+ en: 'File path to save the generated image',
44
+ zh: '保存生成图像的文件路径',
45
+ },
30
46
  },
31
47
  size: {
32
48
  type: 'string',
33
49
  enum: ['256x256', '512x512', '1024x1024'],
34
- description: 'Image size (default: "1024x1024")',
50
+ description: {
51
+ en: 'Image size (default: "1024x1024")',
52
+ zh: '图像大小(默认 "1024x1024")',
53
+ },
35
54
  },
36
55
  },
37
56
  required: ['prompt'],
@@ -53,7 +53,8 @@ function getContext() {
53
53
 
54
54
  export const jsRepl = defineTool({
55
55
  name: 'JsRepl',
56
- description: `Evaluate JavaScript code in a persistent REPL environment.
56
+ description: {
57
+ en: `Evaluate JavaScript code in a persistent REPL environment.
57
58
 
58
59
  The REPL context persists across calls — variables and functions
59
60
  defined in one call are available in subsequent calls.
@@ -66,16 +67,34 @@ Guidelines:
66
67
  - console.log output is captured and returned
67
68
  - Returns the last expression's value plus any console output
68
69
  - No filesystem or network access from within the REPL`,
70
+ zh: `在持久 REPL 环境中执行 JavaScript 代码。
71
+
72
+ REPL 上下文在多次调用间保持——一次调用中定义的变量和函数可在后续调用中使用。
73
+
74
+ 使用指南:
75
+ - 用于计算、数据转换和快速实验
76
+ - 状态在调用间保留
77
+ - 传入 reset: true 可在执行前清除所有状态(干净环境)。此模式下 code 可选——只传 reset 即可清除
78
+ - console.log 输出被捕获并返回
79
+ - 返回最后一个表达式的值及所有 console 输出
80
+ - REPL 内不能访问文件系统或网络`
81
+ },
69
82
  parameters: {
70
83
  type: 'object',
71
84
  properties: {
72
85
  code: {
73
86
  type: 'string',
74
- description: 'JavaScript code to evaluate. Optional when reset=true and you only want to clear state.',
87
+ description: {
88
+ en: 'JavaScript code to evaluate. Optional when reset=true and you only want to clear state.',
89
+ zh: '要执行的 JavaScript 代码。当 reset=true 且只想清空状态时可省略。',
90
+ },
75
91
  },
76
92
  reset: {
77
93
  type: 'boolean',
78
- description: 'When true, reset the REPL context BEFORE evaluating `code`. If `code` is omitted, just resets.',
94
+ description: {
95
+ en: 'When true, reset the REPL context BEFORE evaluating `code`. If `code` is omitted, just resets.',
96
+ zh: '为 true 时,在求值前先重置 REPL 上下文。若省略 code,则仅重置。',
97
+ },
79
98
  },
80
99
  },
81
100
  },
@@ -136,7 +155,10 @@ function warnJsReplResetDeprecated() {
136
155
 
137
156
  export const jsReplReset = defineTool({
138
157
  name: 'JsReplReset',
139
- description: 'DEPRECATED — use JsRepl with `reset: true` instead. Resets the persistent REPL context. Removal target: v0.2.0.',
158
+ description: {
159
+ en: 'DEPRECATED — use JsRepl with `reset: true` instead. Resets the persistent REPL context. Removal target: v0.2.0.',
160
+ zh: '已废弃——请改用带有 `reset: true` 参数的 JsRepl。它会清空持久 REPL 上下文。计划在 v0.2.0 移除。',
161
+ },
140
162
  parameters: { type: 'object', properties: {} },
141
163
  isConcurrencySafe: () => false,
142
164
  isReadOnly: () => false,
@@ -16,7 +16,8 @@ import { diagnoseAgentLiveness } from '../sub-agent/liveness.js';
16
16
 
17
17
  export default defineTool({
18
18
  name: 'ListAgents',
19
- description: `List all sub-agents and their current status.
19
+ description: {
20
+ en: `List all sub-agents and their current status.
20
21
 
21
22
  Returns id, name, status, mission/task summary, durable outputFile path,
22
23
  liveness counters (toolUseCount, tokenCount, msSinceLastEvent, recentTools),
@@ -26,16 +27,31 @@ this as the primary non-blocking monitor for async sub-agent work, and Read
26
27
 
27
28
  By default only non-closed agents are returned. Pass include_closed=true
28
29
  to also list closed/failed/abandoned/completed agents.`,
30
+ zh: `列出所有子 Agent 及其当前状态。
31
+
32
+ 返回每个 Agent 的 id、name、status、mission/task 摘要、持久化 outputFile 路径、
33
+ liveness 计数器(toolUseCount、tokenCount、msSinceLastEvent、recentTools)、
34
+ stale/stalled 诊断、result 尾部和消息数量。将此作为异步子 Agent 工作的主要非阻塞监控工具;
35
+ 如需查看某个 Agent 的完整时间线,可 Read 其 outputFile。
36
+
37
+ 默认只返回未关闭的 Agent。传 include_closed=true 可同时列出 closed/failed/abandoned/completed 的 Agent。`
38
+ },
29
39
  parameters: {
30
40
  type: 'object',
31
41
  properties: {
32
42
  include_closed: {
33
43
  type: 'boolean',
34
- description: 'Include closed/failed/abandoned/completed agents in the list (default: false)',
44
+ description: {
45
+ en: 'Include closed/failed/abandoned/completed agents in the list (default: false)',
46
+ zh: '在列表中包含已关闭/失败/放弃/完成的 Agent(默认 false)',
47
+ },
35
48
  },
36
49
  include_terminal: {
37
50
  type: 'boolean',
38
- description: 'Alias for include_closed — include all terminal-status agents in the list',
51
+ description: {
52
+ en: 'Alias for include_closed — include all terminal-status agents in the list',
53
+ zh: 'include_closed 的别名 — 列出所有已终止状态的 Agent',
54
+ },
39
55
  },
40
56
  },
41
57
  },
@@ -17,23 +17,36 @@ const SKIP_DIRS = new Set([
17
17
 
18
18
  export default defineTool({
19
19
  name: 'ListDir',
20
- description: `List the contents of a directory.
20
+ description: {
21
+ en: `List the contents of a directory.
21
22
 
22
23
  Shows files and subdirectories with their types and sizes.
23
24
  Directories are listed first, then files, both sorted alphabetically.
24
25
  Common large directories (node_modules, .git) are skipped.
25
26
 
26
27
  This is better than using Bash with 'ls' because it provides structured output.`,
28
+ zh: `列出目录内容。
29
+
30
+ 显示文件和子目录,含类型和大小。目录优先列出,文件次之,均按字母排序。常见大目录(node_modules、.git)被跳过。
31
+
32
+ 比用 Bash 执行 ls 更好,因为它提供结构化输出。`
33
+ },
27
34
  parameters: {
28
35
  type: 'object',
29
36
  properties: {
30
37
  path: {
31
38
  type: 'string',
32
- description: 'Directory path to list (default: cwd)',
39
+ description: {
40
+ en: 'Directory path to list (default: cwd)',
41
+ zh: '要列出的目录路径(默认当前工作目录)',
42
+ },
33
43
  },
34
44
  show_hidden: {
35
45
  type: 'boolean',
36
- description: 'Include hidden files (starting with dot, default: true)',
46
+ description: {
47
+ en: 'Include hidden files (starting with dot, default: true)',
48
+ zh: '是否包含隐藏文件(以点开头的文件,默认 true)',
49
+ },
37
50
  },
38
51
  },
39
52
  },
@@ -135,7 +135,8 @@ export function buildMcpFlattenedTools(mcpManager) {
135
135
 
136
136
  export const mcpListTools = defineTool({
137
137
  name: 'mcp_list_tools',
138
- description: `List all tools available from connected MCP (Model Context Protocol) servers.
138
+ description: {
139
+ en: `List all tools available from connected MCP (Model Context Protocol) servers.
139
140
 
140
141
  Usage guidelines:
141
142
  - Use to discover what MCP tools are available in the current session
@@ -143,12 +144,24 @@ Usage guidelines:
143
144
  - Each tool is prefixed with the server name (e.g. "github__list_prs", "slack__send_message")
144
145
  - Use this before mcp_call_tool to understand available capabilities
145
146
  - MCP servers are configured by the user — if none are connected, returns an empty list`,
147
+ zh: `列出已连接 MCP(Model Context Protocol)服务器提供的所有工具。
148
+
149
+ 使用指南:
150
+ - 用于发现当前会话中可用的 MCP 工具
151
+ - 返回所有已连接 MCP 服务器的工具名称、描述和参数 schema
152
+ - 每个工具名称都带服务器名前缀(例如 "github__list_prs"、"slack__send_message")
153
+ - 调用 mcp_call_tool 前先用此工具确认可用能力和参数
154
+ - MCP 服务器由用户配置;如果当前没有连接服务器,将返回空列表`,
155
+ },
146
156
  parameters: {
147
157
  type: 'object',
148
158
  properties: {
149
159
  server: {
150
160
  type: 'string',
151
- description: 'Filter tools from a specific MCP server (optional)',
161
+ description: {
162
+ en: 'Filter tools from a specific MCP server (optional)',
163
+ zh: '按特定 MCP 服务器过滤工具(可选)',
164
+ },
152
165
  },
153
166
  },
154
167
  },
@@ -181,7 +194,8 @@ Usage guidelines:
181
194
 
182
195
  export const mcpCallTool = defineTool({
183
196
  name: 'mcp_call_tool',
184
- description: `Call a tool on a connected MCP (Model Context Protocol) server.
197
+ description: {
198
+ en: `Call a tool on a connected MCP (Model Context Protocol) server.
185
199
 
186
200
  Usage guidelines:
187
201
  - Use after discovering tools via mcp_list_tools
@@ -190,20 +204,39 @@ Usage guidelines:
190
204
  - The tool executes on the MCP server and returns the result
191
205
  - Timeouts depend on the MCP server — use timeout_ms if the operation is slow
192
206
  - Errors from the MCP server are returned as structured error objects`,
207
+ zh: `调用已连接 MCP(Model Context Protocol)服务器上的某个工具。
208
+
209
+ 使用指南:
210
+ - 先通过 mcp_list_tools 发现工具,再调用此工具
211
+ - 提供包含服务器名前缀的完整工具名(例如 "github__create_issue")
212
+ - arguments 必须严格匹配该 MCP 工具的参数 schema
213
+ - 工具会在 MCP 服务器侧执行,并返回执行结果
214
+ - 超时时间取决于 MCP 服务器;慢操作可显式传入 timeout_ms
215
+ - MCP 服务器返回的错误会以结构化错误对象返回`,
216
+ },
193
217
  parameters: {
194
218
  type: 'object',
195
219
  properties: {
196
220
  tool_name: {
197
221
  type: 'string',
198
- description: 'Full tool name including server prefix (e.g. "github__list_prs")',
222
+ description: {
223
+ en: 'Full tool name including server prefix (e.g. "github__list_prs")',
224
+ zh: '完整工具名称,包括服务器前缀(如 "github__list_prs")',
225
+ },
199
226
  },
200
227
  arguments: {
201
228
  type: 'object',
202
- description: 'Arguments to pass to the MCP tool (must match its schema)',
229
+ description: {
230
+ en: 'Arguments to pass to the MCP tool (must match its schema)',
231
+ zh: '传递给 MCP 工具的参数(必须匹配其 schema)',
232
+ },
203
233
  },
204
234
  timeout_ms: {
205
235
  type: 'number',
206
- description: 'Timeout in milliseconds (default: 30000)',
236
+ description: {
237
+ en: 'Timeout in milliseconds (default: 30000)',
238
+ zh: '超时时间,单位毫秒(默认 30000)',
239
+ },
207
240
  },
208
241
  },
209
242
  required: ['tool_name'],
@@ -11,7 +11,8 @@ import { resolve } from 'path';
11
11
 
12
12
  export default defineTool({
13
13
  name: 'NotebookEdit',
14
- description: `Edit a Jupyter notebook (.ipynb file) cell.
14
+ description: {
15
+ en: `Edit a Jupyter notebook (.ipynb file) cell.
15
16
 
16
17
  Actions:
17
18
  - "replace" — replace the source of a cell at the given index
@@ -20,30 +21,55 @@ Actions:
20
21
  - "read" — read the notebook content (all cells)
21
22
 
22
23
  Cell types: "code" or "markdown"`,
24
+ zh: `编辑 Jupyter notebook(.ipynb 文件)单元格。
25
+
26
+ 操作:
27
+ - "replace" — 替换指定索引处单元格的源码
28
+ - "insert" — 在指定索引后插入新单元格
29
+ - "delete" — 删除指定索引处的单元格
30
+ - "read" — 读取 notebook 内容(全部单元格)
31
+
32
+ 单元格类型:"code" 或 "markdown"`
33
+ },
23
34
  parameters: {
24
35
  type: 'object',
25
36
  properties: {
26
37
  notebook_path: {
27
38
  type: 'string',
28
- description: 'Path to the .ipynb file',
39
+ description: {
40
+ en: 'Path to the .ipynb file',
41
+ zh: '.ipynb 文件的路径',
42
+ },
29
43
  },
30
44
  action: {
31
45
  type: 'string',
32
46
  enum: ['replace', 'insert', 'delete', 'read'],
33
- description: 'Operation to perform (default: "replace")',
47
+ description: {
48
+ en: 'Operation to perform (default: "replace")',
49
+ zh: '要执行的操作(默认 "replace")',
50
+ },
34
51
  },
35
52
  cell_index: {
36
53
  type: 'number',
37
- description: 'Cell index (0-based)',
54
+ description: {
55
+ en: 'Cell index (0-based)',
56
+ zh: '单元格索引(从 0 开始)',
57
+ },
38
58
  },
39
59
  cell_type: {
40
60
  type: 'string',
41
61
  enum: ['code', 'markdown'],
42
- description: 'Cell type for insert/replace',
62
+ description: {
63
+ en: 'Cell type for insert/replace',
64
+ zh: '插入/替换的单元格类型',
65
+ },
43
66
  },
44
67
  source: {
45
68
  type: 'string',
46
- description: 'New cell source content',
69
+ description: {
70
+ en: 'New cell source content',
71
+ zh: '新单元格的源码内容',
72
+ },
47
73
  },
48
74
  },
49
75
  required: ['notebook_path'],
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  import { formatSize } from '../archive/tool-results.js';
13
- import { getBuiltinToolLocalization } from './localized-descriptions.js';
14
13
 
15
14
  /**
16
15
  * Collaboration tools are mutually exclusive per Yeaft group shape:
@@ -52,47 +51,36 @@ function normalizeLanguage(language) {
52
51
  return String(language || '').toLowerCase().startsWith('zh') ? 'zh' : 'en';
53
52
  }
54
53
 
54
+ function isLocalizedTextObject(value) {
55
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
56
+ return ['en', 'zh', 'zh-CN', 'en-US', 'default'].some(key => typeof value[key] === 'string' || typeof value[key] === 'function');
57
+ }
58
+
55
59
  function localizeVisibleText(value, language, toolName) {
56
60
  const lang = normalizeLanguage(language);
57
61
  if (typeof value === 'function') return localizeVisibleText(value(lang), lang, toolName);
58
- if (value && typeof value === 'object') {
62
+ if (isLocalizedTextObject(value)) {
59
63
  const picked = value[lang] || value[lang === 'zh' ? 'zh-CN' : 'en-US'] || value.en || value.default;
60
64
  if (typeof picked === 'string') return picked;
65
+ if (typeof picked === 'function') return localizeVisibleText(picked(lang), lang, toolName);
61
66
  }
62
- const text = typeof value === 'string' ? value : String(value || '');
63
- if (lang !== 'zh') return text;
64
- if (!text.trim()) return text;
65
- return [
66
- `工具说明:${toolName || '该工具'}。请严格按照 schema 调用;工具名、参数名、JSON key 和枚举值保持英文,不要翻译。`,
67
- `原始协议说明(英文,供精确调用参考):${text}`,
68
- ].join('\n');
67
+ // Legacy single-string descriptions are already authored text. Return them
68
+ // unchanged; do not synthesize zh-prefixed wrappers around English strings.
69
+ return typeof value === 'string' ? value : String(value || '');
69
70
  }
70
71
 
71
72
  /**
72
- * Walk a JSON Schema `parameters` object and localize the human-readable
73
- * `description` strings to the requested language. All other schema bits
73
+ * Walk a JSON Schema `parameters` object and localize human-readable
74
+ * `description` values to the requested language. All other schema bits
74
75
  * (`type`, `enum`, `required`, `items`, nested `properties`, etc.) are
75
76
  * preserved by value.
76
77
  *
77
78
  * Critical correctness rule: a JSON Schema can have a *property named*
78
79
  * `description` whose value is itself a sub-schema (e.g.
79
- * `properties: { description: { type: 'string', description: 'Detailed...' }}`).
80
+ * `properties: { description: { type: 'string', description: { en, zh } }}`).
80
81
  * In that case the OUTER `description` key holds the sub-schema and must
81
- * be recursed into; only the INNER `description: 'Detailed...'` value
82
- * (which is a string) should be localized.
83
- *
84
- * Previous bug: this walker localized ANY value under a `description`
85
- * key, including sub-schema objects. `localizeVisibleText` then ran
86
- * `String(value)` on the object, producing the literal string
87
- * `'[object Object]'`. GPT-5's strict schema validator rejected the
88
- * resulting `{ description: '[object Object]' }` with
89
- * `"'[object Object]' is not of type 'object', 'boolean'"`. This made
90
- * FeatureCreate (and any tool whose schema contains a property named
91
- * `description`) unusable in zh locale on strict providers.
92
- *
93
- * The fix: only treat a `description` value as localizable text when it
94
- * is actually a string. Object/array values under a `description` key
95
- * are sub-schemas and must be recursed into normally.
82
+ * be recursed into; only the INNER bilingual `description` value should
83
+ * be localized.
96
84
  */
97
85
  function cloneSchema(value) {
98
86
  if (!value || typeof value !== 'object') return value;
@@ -126,11 +114,11 @@ function applyParameterOverrides(parameters, overrides) {
126
114
 
127
115
  function localizeParameters(parameters, language, toolName, parameterOverrides = null) {
128
116
  const lang = normalizeLanguage(language);
129
- if (lang !== 'zh' || !parameters || typeof parameters !== 'object') return parameters;
117
+ if (!parameters || typeof parameters !== 'object') return parameters;
130
118
  if (Array.isArray(parameters)) return parameters.map(v => localizeParameters(v, lang, toolName));
131
119
  const out = {};
132
120
  for (const [key, value] of Object.entries(parameters)) {
133
- if (key === 'description' && typeof value === 'string') {
121
+ if (key === 'description' && (typeof value === 'string' || typeof value === 'function' || isLocalizedTextObject(value))) {
134
122
  out[key] = localizeVisibleText(value, lang, toolName);
135
123
  } else if (value && typeof value === 'object') {
136
124
  out[key] = localizeParameters(value, lang, toolName, null);
@@ -374,11 +362,10 @@ export class ToolRegistry {
374
362
  return this.getAllTools()
375
363
  .filter(t => !isToolHiddenByCollabPolicy(t.name, collabToolPolicy))
376
364
  .map(t => {
377
- const localized = getBuiltinToolLocalization(t.name, lang);
378
365
  return {
379
366
  name: t.name,
380
- description: localized?.description || localizeVisibleText(t.description, lang, t.name),
381
- parameters: localizeParameters(t.parameters, lang, t.name, localized?.parameters),
367
+ description: localizeVisibleText(t.description, lang, t.name),
368
+ parameters: localizeParameters(t.parameters, lang, t.name),
382
369
  };
383
370
  });
384
371
  }