Skill: xinmh1338863/metaso-search-v2 (version 2.1.0)

--- name: metaso-search description: 提供对 Metaso 搜索 API 的直接访问，支持网页搜索、内容读取和智能问答功能 metadata: { "openclaw": { "requires": { "env": ["METASO_API_KEY"] } }, } --- # Metaso Search 技能 ## 描述 提供对 Metaso 搜索 API 的直接访问，支持网页搜索、内容读取和智能问答功能。 ## 功能 ### 1. 网页搜索 (`metaso_web_search`) 根据关键词搜索网页、文档、论文、图片、视频、播客等内容。 **参数：** - `q`: 搜索查询关键词（必填） - `size`: 返回结果数量（必填，1-100） - `scope`: 搜索范围（必填） - `includeSummary`: 是否包含精简的原文匹配信息（必填） - `includeRawContent`: 是否通过网页的摘要信息进行召回增强（必填） - `conciseSnippet`: 是否抓取所有来源网页原文（必填） ### 2. 网页内容读取 (`metaso_web_reader`) 读取指定 URL 的网页内容。 **参数：** - `url`: 要读取的 URL 地址（必填） - `format`: 输出格式（必填，json 或 markdown） ### 3. 智能问答 (`metaso_chat`) 基于 RAG 的智能问答服务。 **参数：** - `messages`: 用户消息数组（必填） - `model`: 使用的模型（必填） - `scope`: 搜索范围（必填） - `format`: 输出格式（必填） - `stream`: 是否开启流式输出（必填） - `conciseSnippet`: 是否返回精简的原文匹配信息（必填） ## 配置 ### 环境变量配置 **必要配置：** ```bash # Linux/macOS export METASO_API_KEY="您的API密钥" # Windows (PowerShell) $env:METASO_API_KEY="您的API密钥" ``` **OpenClaw 配置：** 在 OpenClaw 配置文件 `openclaw.json` 中添加： ```json { "env": { "METASO_API_KEY": "您的API密钥" } } ``` ## 使用方法 ```javascript import { metasoSearch, metasoReadPage, metasoChat } from './skills/metaso-search/metaso-api.js'; // 搜索 const searchResult = await metasoSearch('人工智能', 2, 'document', true, false, true); // 读取网页内容 const contentResult = await metasoReadPage('https://example.com', 'json'); // 智能问答 const chatResult = await metasoChat( [{ role: 'user', content: '什么是机器学习？' }], 'fast', 'webpage', 'simple', false, true ); ``` ## 文件说明 ### `metaso-api.js` - API 核心模块 - 提供所有 API 方法的实现 - 包含自动配置加载 - 错误处理和重试机制 - 使用 Metaso HTTP API 端点 ### `quick-test.js` - 快速测试脚本 - 用于快速验证 API 基本功能的简化测试 - 包含搜索和聊天功能的基本测试 ### `simple-test.js` - 简单功能测试 - 测试所有 API 功能的详细测试脚本 - 包含搜索、内容读取和聊天功能的测试 ## 运行测试 ```bash cd skills/metaso-search node quick-test.js ``` ## 故障排除 ### API 密钥未配置 ``` Error: METASO_API_KEY 环境变量未设置，请在使用前配置 ``` **解决方法**：设置 METASO_API_KEY 环境变量 ### 认证失败 ``` Error: API密钥无效 ``` **解决方法**：检查 API 密钥是否正确 ## 版本信息 - 当前版本: 2.1.0 - 移除了硬编码的默认 API 密钥 - 优化了配置管理 - 简化了文档结构

path: metaso-api.js
content: // Metaso 搜索 API 模块 - 直接 HTTP API 访问

// 支持的模型和搜索范围
const SUPPORTED_MODELS = ['fast', 'fast_thinking', 'ds-r1'];
const SUPPORTED_SCOPES = ['webpage', 'document', 'paper', 'image', 'video', 'podcast'];

// Metaso API 基础类
class MetasoAPI {
  constructor() {
    // 必须从环境变量获取 API 密钥
    this.[REDACTED_SECRET];
    
    if (!this.apiKey) {
      throw new Error('METASO_API_KEY 环境变量未设置，请在使用前配置');
    }
    
    this.searchUrl = 'https://metaso.cn/api/v1/search';
    this.readerUrl = 'https://metaso.cn/api/v1/reader';
    this.chatUrl = 'https://metaso.cn/api/v1/chat/completions';
  }

  // 通用请求方法
  async _request(url, options = {}) {
    const defaultOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      ...options
    };

    try {
      const response = await fetch(url, defaultOptions);

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`HTTP Error ${response.status}: ${response.statusText} - ${errorText}`);
      }

      const data = await response.json();
      return data;
    } catch (error) {
      console.error(`请求失败 [${url}]:`, error.message);
      throw error;
    }
  }

  // 网页搜索 - 使用正确的搜索 URL
  async search(q, size, scope, includeSummary, includeRawContent, conciseSnippet) {
    // 验证参数
    if (!q || typeof q !== 'string') {
      throw new Error('搜索关键词 (q) 必须是有效的字符串');
    }
    if (typeof size !== 'number' || size < 1 || size > 100) {
      throw new Error('搜索结果数量 (size) 必须是 1-100 之间的整数');
    }
    if (!SUPPORTED_SCOPES.includes(scope)) {
      throw new Error(`不支持的搜索范围 (scope): ${scope}。支持的范围: ${SUPPORTED_SCOPES.join(', ')}`);
    }
    if (typeof includeSummary !== 'boolean') {
      throw new Error('是否包含精简的原文匹配信息 (includeSummary) 必须是布尔值');
    }
    if (typeof includeRawContent !== 'boolean') {
      throw new Error('是否通过网页的摘要信息进行召回增强 (includeRawContent) 必须是布尔值');
    }
    if (typeof conciseSnippet !== 'boolean') {
      throw new Error('是否抓取所有来源网页原文 (conciseSnippet) 必须是布尔值');
    }

    const body = {
      q: q,
      size: size,
      scope: scope,
      includeSummary: includeSummary,
      includeRawContent: includeRawContent,
      conciseSnippet: conciseSnippet
    };

    return this._request(this.searchUrl, {
      body: JSON.stringify(body)
    });
  }

  // 网页内容读取 - 使用正确的阅读器 URL
  async readWebPage(url, format) {
    // 验证参数
    if (!url || typeof url !== 'string') {
      throw new Error('URL 地址 (url) 必须是有效的字符串');
    }
    if (!['json', 'markdown'].includes(format)) {
      throw new Error(`不支持的输出格式 (format): ${format}。支持的格式: json, markdown`);
    }

    const headers = {
      'Authorization': `Bearer ${this.apiKey}`,
      'Content-Type': 'application/json'
    };

    // 根据格式设置 Accept 头部
    if (format === 'json') {
      headers['Accept'] = 'application/json';
    } else if (format === 'markdown') {
      headers['Accept'] = 'text/plain';
    }

    const body = {
      url: url
    };

    try {
      const response = await fetch(this.readerUrl, {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(body)
      });

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`HTTP Error ${response.status}: ${response.statusText} - ${errorText}`);
      }

      // 根据格式解析响应
      if (format === 'json') {
        return await response.json();
      } else {
        return {
          title: url,
          url: url,
          content: await response.text()
        };
      }
    } catch (error) {
      console.error(`网页读取失败 [${url}]:`, error.message);
      throw error;
    }
  }

  // 智能问答 - 使用正确的聊天 URL
  async chat(messages, model, scope, format, stream, conciseSnippet) {
    // 验证参数
    if (!SUPPORTED_MODELS.includes(model)) {
      throw new Error(`不支持的模型 (model): ${model}。支持的模型: ${SUPPORTED_MODELS.join(', ')}`);
    }
    if (!SUPPORTED_SCOPES.includes(scope)) {
      throw new Error(`不支持的搜索范围 (scope): ${scope}。支持的范围: ${SUPPORTED_SCOPES.join(', ')}`);
    }
    if (!['chat_completions', 'simple'].includes(format)) {
      throw new Error(`不支持的输出格式 (format): ${format}。支持的格式: chat_completions, simple`);
    }
    if (typeof stream !== 'boolean') {
      throw new Error('是否开启流式输出 (stream) 必须是布尔值');
    }
    if (format === 'simple' && stream) {
      throw new Error('流式输出仅在 format = chat_completions 时支持');
    }
    if (!Array.isArray(messages)) {
      throw new Error('消息内容 (messages) 必须是数组类型');
    }

    const body = {
      model: model,
      messages: messages,
      scope: scope,
      format: format,
      stream: stream,
      conciseSnippet: conciseSnippet
    };

    return this._request(this.chatUrl, {
      body: JSON.stringify(body)
    });
  }
}

// 创建单例实例
let metasoAPI = null;

export function getMetasoAPI() {
  if (!metasoAPI) {
    metasoAPI = new MetasoAPI();
  }
  return metasoAPI;
}

// 导出常用方法
export async function metasoSearch(query, size, scope, includeSummary, includeRawContent, conciseSnippet) {
  const api = getMetasoAPI();
  return api.search(query, size, scope, includeSummary, includeRawContent, conciseSnippet);
}

export async function metasoReadPage(url, format) {
  const api = getMetasoAPI();
  return api.readWebPage(url, format);
}

export async function metasoChat(messages, model, scope, format, stream, conciseSnippet) {
  const api = getMetasoAPI();
  return api.chat(messages, model, scope, format, stream, conciseSnippet);
}

// 提供与旧 API 兼容的方法
export async function metasoPing() {
  return { status: 'ok' }; // 模拟 Ping 响应
}

export async function metasoListTools() {
  return {
    tools: [
      {
        name: 'metaso_web_search',
        description: '根据关键词搜索网页、文档、论文、图片、视频、播客等内容',
        inputSchema: {
          type: 'object',
          properties: {
            q: { type: 'string', description: '搜索查询关键词' },
            size: { type: 'integer', description: '返回结果数量，默认10' },
            scope: { 
              type: 'string', 
              description: `搜索范围：${SUPPORTED_SCOPES.join(', ')}, 默认 webpage`,
              enum: SUPPORTED_SCOPES
            },
            includeSummary: { type: 'boolean', description: '是否包含精简的原文匹配信息，默认 true' },
            includeRawContent: { type: 'boolean', description: '是否通过网页的摘要信息进行召回增强，默认 false' },
            conciseSnippet: { type: 'boolean', description: '是否抓取所有来源网页原文，默认 false' }
          },
          required: ['q', 'size', 'scope', 'includeSummary', 'includeRawContent', 'conciseSnippet']
        }
      },
      {
        name: 'metaso_web_reader',
        description: '读取指定 URL 的网页内容',
        inputSchema: {
          type: 'object',
          properties: {
            url: { type: 'string', description: '要读取的 URL 地址' },
            format: { type: 'string', description: '输出格式：json, markdown' }
          },
          required: ['url', 'format']
        }
      },
      {
        name: 'metaso_chat',
        description: '基于 RAG 的智能问答服务',
        inputSchema: {
          type: 'object',
          properties: {
            messages: { type: 'array', description: '用户消息数组' },
            model: { 
              type: 'string', 
              description: `使用的模型：${SUPPORTED_MODELS.join(', ')}, 默认 fast`,
              enum: SUPPORTED_MODELS
            },
            scope: { 
              type: 'string', 
              description: `搜索范围：${SUPPORTED_SCOPES.join(', ')}, 默认 webpage`,
              enum: SUPPORTED_SCOPES
            },
            format: { 
              type: 'string', 
              description: '输出格式：chat_completions, simple',
              enum: ['chat_completions', 'simple']
            },
            stream: { type: 'boolean', description: '是否开启流式输出，默认 false' },
            conciseSnippet: { type: 'boolean', description: '是否返回精简的原文匹配信息，默认 false' }
          },
          required: ['messages', 'model', 'scope', 'format', 'stream', 'conciseSnippet']
        }
      }
    ]
  };
}

// 默认导出
export default MetasoAPI;


path: package.json
content: {
  "name": "metaso-search",
  "version": "2.1.0",
  "description": "Metaso Search API 访问技能 - 支持网页搜索、内容读取和智能问答功能",
  "main": "metaso-api.js",
  "type": "module",
  "keywords": [
    "openclaw",
    "skill",
    "search",
    "metaso",
    "api"
  ],
  "author": "",
  "license": "MIT",
  "repository": "",
  "bugs": "",
  "homepage": ""
}


path: quick-test.js
content: import { metasoSearch, metasoChat } from './metaso-api.js';

async function test() {
  console.log('=== 快速测试 ===');
  console.log('');
  
  try {
    console.log('1. 测试搜索功能...');
    const searchResult = await metasoSearch('人工智能', 2, 'document', true, false, true);
    console.log('✅ 搜索结果:');
    console.log(`   完整响应: ${JSON.stringify(searchResult, null, 2)}`);
    
    console.log('');
    console.log('2. 测试聊天功能...');
    const chatResult = await metasoChat([{ role: 'user', content: '什么是机器学习？' }], 'fast', 'webpage', 'simple', false, true);
    console.log('✅ 回答:');
    console.log(`   完整响应: ${JSON.stringify(chatResult, null, 2)}`);
    
    console.log('');
    console.log('=== 测试完成 ===');
  } catch (error) {
    console.error('❌ 测试失败:', error.message);
    console.error('详细错误:', error);
  }
}

test();


path: simple-test.js
content: import { metasoSearch, metasoReadPage, metasoChat } from './metaso-api.js';

async function test() {
  console.log('=== 简单功能测试 ===');
  console.log('');
  
  try {
    console.log('1. 测试搜索功能（所有参数明确指定）...');
    const searchResult = await metasoSearch(
      '人工智能', 
      2, 
      'document', 
      true, 
      false, 
      true
    );
    console.log('✅ 搜索结果:');
    console.log(`   找到 ${searchResult.total} 个结果`);
    if (searchResult.data) {
      searchResult.data.slice(0, 2).forEach((item, index) => {
        console.log(`   ${index + 1}. ${item.title}`);
      });
    }
    
    console.log('');
    console.log('2. 测试网页读取功能...');
    const readResult = await metasoReadPage('https://example.com', 'json');
    console.log('✅ 网页读取结果:');
    console.log(`   标题: ${readResult.title}`);
    
    console.log('');
    console.log('3. 测试聊天功能...');
    const chatResult = await metasoChat(
      [{ role: 'user', content: '什么是机器学习？' }], 
      'fast', 
      'webpage', 
      'simple', 
      false, 
      true
    );
    console.log('✅ 聊天功能响应:');
    console.log(`   响应类型: ${typeof chatResult}`);
    console.log(`   完整响应: ${JSON.stringify(chatResult, null, 2)}`);
    
    console.log('');
    console.log('=== 测试完成 ===');
  } catch (error) {
    console.error('❌ 测试失败:', error.message);
    console.error('详细错误:', error);
  }
}

test();


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)