@@ -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 // ─── 私有方法 ─────────────────────────────────────────────
0 commit comments