Skip to content

Commit eea13a5

Browse files
committed
Version 1.0.3 is expected to be released.
1 parent 18c0bd6 commit eea13a5

3 files changed

Lines changed: 74 additions & 6 deletions

File tree

lib/core/skill/skill_registry.dart

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,80 @@ class SkillRegistry {
353353
}
354354

355355
/// 解析技能的 tools.json
356+
///
357+
/// 健壮处理多种顶层形态,任何解析失败都只返回空列表、绝不抛异常——
358+
/// 单个坏技能不能拖垮整个 Agent 上下文构建(曾因强转 `as List` 崩溃)。
359+
/// 支持的顶层形态:
360+
/// - 数组 `[ {...}, {...} ]`(标准)
361+
/// - 对象包裹 `{ "tools": [ ... ] }`(部分技能写法)
362+
/// - 单个工具对象
363+
///
364+
/// 同时把**扁平格式**的工具 `{ "name", "description", "parameters" }`
365+
/// 归一化为 **OpenAI 标准嵌套格式** `{ "type":"function", "function":{...} }`
366+
/// 使下游(sourceMapping 的 `t['function']`、LLM API)拿到统一 schema。
356367
Future<List<Map<String, dynamic>>> loadSkillTools(Skill skill) async {
357368
final file = File(p.join(skill.path, 'tools.json'));
358369
if (!await file.exists()) return [];
359-
final content = await file.readAsString();
360-
final list = jsonDecode(content) as List;
361-
return list.cast<Map<String, dynamic>>();
370+
try {
371+
final content = await file.readAsString();
372+
if (content.trim().isEmpty) return [];
373+
final decoded = jsonDecode(content);
374+
375+
List<dynamic>? rawList;
376+
if (decoded is List) {
377+
rawList = decoded;
378+
} else if (decoded is Map<String, dynamic>) {
379+
final inner = decoded['tools'];
380+
if (inner is List) {
381+
rawList = inner; // { "tools": [...] }
382+
} else if (decoded['function'] != null ||
383+
decoded['name'] != null ||
384+
decoded['type'] != null) {
385+
rawList = [decoded]; // 单个工具对象
386+
}
387+
}
388+
389+
if (rawList == null) {
390+
print('[SKILL] ⚠ 技能「${skill.name}」tools.json 顶层格式无法识别,已跳过');
391+
return [];
392+
}
393+
394+
// 归一化每个工具为 OpenAI 标准嵌套格式,丢弃无法识别的项
395+
final result = <Map<String, dynamic>>[];
396+
for (final item in rawList) {
397+
if (item is! Map<String, dynamic>) continue;
398+
final normalized = _normalizeToolDef(item);
399+
if (normalized != null) result.add(normalized);
400+
}
401+
return result;
402+
} catch (e) {
403+
print('[SKILL] ⚠ 技能「${skill.name}」tools.json 解析失败,已跳过: $e');
404+
return [];
405+
}
406+
}
407+
408+
/// 把单个工具定义归一化为 OpenAI 标准嵌套格式
409+
/// `{ "type":"function", "function":{ "name","description","parameters" } }`
410+
/// 已是嵌套格式则原样返回;扁平格式则包装;无 name 则视为非法返回 null。
411+
Map<String, dynamic>? _normalizeToolDef(Map<String, dynamic> tool) {
412+
// 已是标准嵌套格式
413+
final fn = tool['function'];
414+
if (fn is Map<String, dynamic> && fn['name'] is String) {
415+
return tool;
416+
}
417+
// 扁平格式 { name, description, parameters } → 包装
418+
if (tool['name'] is String) {
419+
return {
420+
'type': 'function',
421+
'function': {
422+
'name': tool['name'],
423+
if (tool['description'] != null) 'description': tool['description'],
424+
'parameters':
425+
tool['parameters'] ?? {'type': 'object', 'properties': {}},
426+
},
427+
};
428+
}
429+
return null; // 无 name,无法识别
362430
}
363431

364432
// ─── 私有方法 ─────────────────────────────────────────────

lib/features/tools/formula_ocr/formula_ocr_tool.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ class _FormulaOcrSettingsState extends State<_FormulaOcrSettings> {
606606
Expanded(
607607
child: OutlinedButton.icon(
608608
onPressed: () => launchUrl(
609-
Uri.parse('https://www.breezedeus.com/pix2text'),
609+
Uri.parse('https://p2t.breezedeus.com/api-keys'),
610610
mode: LaunchMode.externalApplication,
611611
),
612612
icon: const Icon(Icons.open_in_new, size: 14),

lib/features/tools/image_gen/image_gen_tool.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ImageGenTool extends ToolPlugin {
3636
label: 'API 地址',
3737
type: ConfigFieldType.url,
3838
required: true,
39-
hint: '可以是中转站地址,如: https://yunwu.ai',
39+
hint: '可以是中转站地址,如: https://example.ai',
4040
),
4141
ConfigField(
4242
key: 'apiKey',
@@ -719,7 +719,7 @@ class _ImageGenSettingsState extends State<_ImageGenSettings> {
719719
controller: _apiUrlCtrl,
720720
decoration: const InputDecoration(
721721
labelText: 'API 地址',
722-
hintText: 'https://yunwu.ai',
722+
hintText: 'https://example.ai',
723723
),
724724
),
725725
const SizedBox(height: 12),

0 commit comments

Comments
 (0)