tmp
This commit is contained in:
@@ -1,26 +0,0 @@
|
|||||||
// Tool 模块统一导出入口
|
|
||||||
// 提供工具基类、注册表和所有工具类的统一导出
|
|
||||||
|
|
||||||
// Web工具 (LangChain format)
|
|
||||||
export { webFetchTool } from './web/fetch.js';
|
|
||||||
export { webSearchTool } from './web/search.js';
|
|
||||||
export { httpGetTool } from './web/http_get.js';
|
|
||||||
export { httpPostTool } from './web/http_post.js';
|
|
||||||
|
|
||||||
// 日历工具 (LangChain format)
|
|
||||||
export { getCalendarInfoTool } from './calendar/calendar_info.js';
|
|
||||||
export { getLunarCalendarInfoTool } from './calendar/lunar_calendar_info.js';
|
|
||||||
export { getYearHolidaysTool } from './calendar/year_holidays.js';
|
|
||||||
export { getYearTermsTool } from './calendar/year_terms.js';
|
|
||||||
|
|
||||||
// 地理工具 (LangChain format)
|
|
||||||
export { getLatLngTool } from './geo/latlon.js';
|
|
||||||
|
|
||||||
// 系统工具 (LangChain format)
|
|
||||||
export { getEnvTool } from './system/envs.js';
|
|
||||||
|
|
||||||
// db
|
|
||||||
export { escortRecordQueryTool } from './db/escort_record_query.js';
|
|
||||||
export { escortRecordSetTool } from './db/escort_record_set.js';
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
---
|
|
||||||
name: baidu-ai-map
|
|
||||||
description: 为Agent提供地图和位置相关的能力,例如 AI 地点检索(周边信息)、AI 路线规划、地理编码与逆地理编码、天气查询等地图工具。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 百度地图服务 Agent Plan
|
|
||||||
|
|
||||||
提供大模型友好调用的地图工具skills/baidu-ai-map/SKILL.md,支持POI搜索、导航、语义化 AI 搜索、语义化 AI 路线规划、地理编码与逆地理编码、天气查询。
|
|
||||||
|
|
||||||
## 使用准则
|
|
||||||
|
|
||||||
### 准则 1:API 端点
|
|
||||||
|
|
||||||
所有能力统一使用:
|
|
||||||
|
|
||||||
> **Base URL**: `https://api.map.baidu.com/`
|
|
||||||
|
|
||||||
### 准则 2:SK 凭证安全处理
|
|
||||||
|
|
||||||
SK(Service Key)是调用所有 API 的必须凭证:
|
|
||||||
|
|
||||||
1. 优先读取环境变量 `BAIDU_MAP_AUTH_TOKEN`。
|
|
||||||
2. 如果为空,提示用户前往 [百度地图 Agent Plan](https://lbs.baidu.com/apiconsole/agentplan) 申请。
|
|
||||||
3. 设置环境变量:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export BAIDU_MAP_AUTH_TOKEN="你的SK"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 准则 3:统一鉴权方式(Header 传入)
|
|
||||||
|
|
||||||
调用所有 API 时,统一通过 Header 传入:
|
|
||||||
|
|
||||||
- `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=帮我找北京可带宠物的咖啡馆" \
|
|
||||||
--data-urlencode "region=北京市"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 准则 4:优先使用http_get工具,避免直接调用curl,curl在window下支持不好
|
|
||||||
|
|
||||||
## 全局参数与行为约束
|
|
||||||
|
|
||||||
1. `user_raw_request` 必须是完整的用户需求,不可压缩为关键词。
|
|
||||||
2. `user_raw_request` 出现“我附近”等非明确地点时,可根据上下文为用户推理为具体地点名称,但不可对坐标进行推理。
|
|
||||||
3. `user_raw_request` 需保留定语/约束词,例如“评分最高”“最近”“最便宜”“3公里内”。
|
|
||||||
4. 不得编造坐标;`center` / `location` / `refer_pois` 仅可来自用户明确提供或可信来源,当无可靠坐标时,必须先向用户澄清或先调用 `geocoding` / `place` 等工具获取。
|
|
||||||
5. 统一使用 Header 鉴权:`Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`。
|
|
||||||
6. 经纬度至少保留小数点后 6 位。
|
|
||||||
7. 所有工具返回坐标类型统一为 `gcj02`。
|
|
||||||
8. 禁止使用 grep/sed/awk/jq/python脚本 正则裁剪响应后再推断字段,这会造成百度地图 Agent Plan 巨额的token消耗;短时间内遇到相同或高度相似请求时,应优先基于结果直接推理,避免重复发起请求。
|
|
||||||
9. `direction` 可能返回两类结果:路线结果,或起终点澄清结果。当返回起终点澄清结果时,应先根据用户需求推理最合理候选;若仍无法唯一确定,应引导用户在候选中选择后再继续规划。
|
|
||||||
|
|
||||||
## 工具详解
|
|
||||||
|
|
||||||
### 1. Place(语义化AI地点检索)
|
|
||||||
|
|
||||||
#### API
|
|
||||||
|
|
||||||
`GET /agent_plan/v1/place`
|
|
||||||
|
|
||||||
#### 参数输入(给模型)
|
|
||||||
|
|
||||||
Required:
|
|
||||||
- `user_raw_request`: 用户原始需求,原样完整传入,不可压缩为关键词;保留约束词(如“评分最高”“最近”“3公里内”)
|
|
||||||
- `region`: 城市或区域限制
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
- `center`: 检索中心点和排序参考点(`lat,lng`,gcj02)
|
|
||||||
- `sort`: `distance` 或 `relevance`(默认 `relevance`)
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- `sort=distance` 时,`center` 必传
|
|
||||||
- `center` 只能来自用户明确提供或可信来源,禁止推测/编造,可通过 `geocoding` / `place` 等工具获取
|
|
||||||
- 经纬度至少保留小数点后 6 位
|
|
||||||
|
|
||||||
#### 鉴权
|
|
||||||
|
|
||||||
- GET:Header `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) 帮我查一下八达岭长城附近的五星级酒店
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=帮我查一下八达岭长城附近的五星级酒店" \
|
|
||||||
--data-urlencode "region=延庆区" \
|
|
||||||
--data-urlencode "sort=relevance"
|
|
||||||
|
|
||||||
# 2) 离我最近的火锅店(distance 排序)
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=离我最近的火锅店" \
|
|
||||||
--data-urlencode "region=北京市" \
|
|
||||||
--data-urlencode "center=40.056800,116.308300" \
|
|
||||||
--data-urlencode "sort=distance"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Direction(语义化AI路线规划)
|
|
||||||
|
|
||||||
#### API
|
|
||||||
|
|
||||||
`GET /agent_plan/v1/direction`
|
|
||||||
|
|
||||||
#### 参数输入
|
|
||||||
|
|
||||||
Required:
|
|
||||||
- `user_raw_request`: 用户原始需求,包含起点和终点;保留路线约束词,需要推理并改写用户原始的交通方式
|
|
||||||
- `location`: 用户当前位置坐标(`lat,lng`,gcj02)
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
- `refer_pois`: 地点精确映射,格式 `地点名称:uid,纬度,经度;地点名称:uid,纬度,经度`
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- 当前仅支持驾车、步行、骑行、公交路线规划。`user_raw_request` 中出现其他交通方式时,需要改写到这四种交通方式
|
|
||||||
- `location` 当起点有歧义(如同名地标、模糊起点)时,路线规划服务会基于当前位置推理最合理的起点位置
|
|
||||||
- `refer_pois` 默认不传,用于同名地点消歧,仅在 `user_raw_request` 中明确有歧义地点时传入
|
|
||||||
- `refer_pois` / `location` 的经纬度至少保留小数点后 6 位,禁止推测/编造,可通过 `geocoding` / `place` 等工具获取
|
|
||||||
|
|
||||||
#### 鉴权
|
|
||||||
|
|
||||||
- GET:Header `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) 帮我规划从故宫到颐和园的驾车路线
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=帮我规划从故宫到颐和园的驾车路线" \
|
|
||||||
--data-urlencode "location=39.914590,116.403770"
|
|
||||||
|
|
||||||
# 2) “我家”别名映射
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=步行去我家附近最近的中餐厅" \
|
|
||||||
--data-urlencode "location=40.056800,116.308300" \
|
|
||||||
--data-urlencode "refer_pois=我家:fbc88a21464370106e3e1b52,40.092180,116.345310"
|
|
||||||
|
|
||||||
# 2) 交通方式推理改写:从王府井打车去三里屯要多久
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "user_raw_request=从王府井驾车去三里屯要多久" \
|
|
||||||
--data-urlencode "location=39.914590,116.403770"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Geocoding(地理编码)
|
|
||||||
|
|
||||||
#### API
|
|
||||||
|
|
||||||
`GET /agent_plan/v1/geocoding`
|
|
||||||
|
|
||||||
#### 参数输入
|
|
||||||
|
|
||||||
Required:
|
|
||||||
- `address`: 要解析的完整地址
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
- `region`: 城市/区域提示(减少歧义)
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- 地址越完整,解析越稳定
|
|
||||||
|
|
||||||
#### 鉴权
|
|
||||||
|
|
||||||
- GET:Header `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/geocoding" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "address=北京市海淀区上地十街10号百度大厦" \
|
|
||||||
--data-urlencode "region=北京市"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Reverse Geocoding(逆地理编码)
|
|
||||||
|
|
||||||
#### API
|
|
||||||
|
|
||||||
`GET /agent_plan/v1/reverse_geocoding`
|
|
||||||
|
|
||||||
#### 参数输入
|
|
||||||
|
|
||||||
Required:
|
|
||||||
- `location`: `lat,lng` 格式坐标(gcj02)
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- 经纬度至少保留小数点后 6 位
|
|
||||||
|
|
||||||
#### 鉴权
|
|
||||||
|
|
||||||
- GET:Header `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/reverse_geocoding" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "location=40.056800,116.308300"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Weather(天气查询)
|
|
||||||
|
|
||||||
#### API
|
|
||||||
|
|
||||||
`GET /agent_plan/v1/weather`
|
|
||||||
|
|
||||||
#### 参数输入
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
- `region`: 行政区划名称
|
|
||||||
- `location`: `lat,lng` 格式坐标(gcj02)
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- `region` 与 `location` 至少传一个
|
|
||||||
- `location` 传入时,经纬度至少保留小数点后 6 位
|
|
||||||
|
|
||||||
#### 鉴权
|
|
||||||
|
|
||||||
- GET:Header `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
|
|
||||||
|
|
||||||
#### 示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) 按坐标查询天气
|
|
||||||
curl --get "https://api.map.baidu.com/agent_plan/v1/weather" \
|
|
||||||
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
|
|
||||||
--data-urlencode "location=38.766230,116.432130"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
1. 如果缺少 token,提示用户执行:
|
|
||||||
申请地址:https://lbs.baidu.com/apiconsole/agentplan
|
|
||||||
`export BAIDU_MAP_AUTH_TOKEN="<YOUR_BAIDU_MAP_AUTH_TOKEN>"`
|
|
||||||
2. 如果提示参数错误,重新阅读本Skills查阅如何传参
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"ownerId": "kn778b6bf766ym71fdvrc5yvg98319rv",
|
|
||||||
"slug": "baidu-ai-map",
|
|
||||||
"version": "1.0.6",
|
|
||||||
"publishedAt": 1776928968682
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { tool } from "langchain"
|
|
||||||
import lunar from "lunar-javascript"
|
|
||||||
const { Solar, Lunar, HolidayUtil, TermUtil } = lunar
|
|
||||||
import { getAccurateTime } from "./utils.js"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日历工具
|
|
||||||
* 提供准确时间、农历信息、节气、节日和老黄历等信息
|
|
||||||
*/
|
|
||||||
export const getCalendarInfoTool = tool(
|
|
||||||
async () => {
|
|
||||||
try {
|
|
||||||
// 获取准确时间
|
|
||||||
const timeInfo = await getAccurateTime()
|
|
||||||
|
|
||||||
// 获取农历信息
|
|
||||||
const date = new Date(timeInfo.timestamp)
|
|
||||||
const solar = Solar.fromDate(date)
|
|
||||||
const lunar = solar.getLunar()
|
|
||||||
|
|
||||||
// 组合返回结果
|
|
||||||
const result = {
|
|
||||||
time: timeInfo,
|
|
||||||
weekday: solar.getWeek(),
|
|
||||||
lunar: {
|
|
||||||
lunar: lunar.toString(),
|
|
||||||
solar: solar.toString(),
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(result, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in calendar tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_calendar_info",
|
|
||||||
description: "获取当前准确日期时间,以及对应的农历日期"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { tool } from "langchain"
|
|
||||||
import lunarLib from "lunar-javascript"
|
|
||||||
const { Solar } = lunarLib
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 特定日期日历工具
|
|
||||||
* 基于 lunar-javascript 提供指定日期的完整农历信息
|
|
||||||
*/
|
|
||||||
export const getLunarCalendarInfoTool = tool(
|
|
||||||
async ({ year, month, day }) => {
|
|
||||||
try {
|
|
||||||
const solar = Solar.fromYmd(year, month, day)
|
|
||||||
const lunar = solar.getLunar()
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
solar: solar.toString(),
|
|
||||||
weekday: `星期${solar.getWeekInChinese()}`,
|
|
||||||
lunar: lunar.toString(),
|
|
||||||
ganzhiYear: lunar.getYearInGanZhi(),
|
|
||||||
zodiac: lunar.getYearShengXiao(),
|
|
||||||
solarTerms: lunar.getJieQi() || null,
|
|
||||||
festivals: [...solar.getFestivals(), ...lunar.getFestivals()],
|
|
||||||
yi: lunar.getDayYi(),
|
|
||||||
ji: lunar.getDayJi(),
|
|
||||||
xiShen: lunar.getDayPositionXiDesc(),
|
|
||||||
caiShen: lunar.getDayPositionCaiDesc()
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(result, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_lunar_calendar_info",
|
|
||||||
description: "获取指定日期的日历信息,包括农历信息、老黄历、当年生肖、今日宜忌、喜神方位、财神方位、今日节气、今日节日等",
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
year: {
|
|
||||||
type: "number",
|
|
||||||
description: "年份",
|
|
||||||
minimum: 1900,
|
|
||||||
maximum: 2100
|
|
||||||
},
|
|
||||||
month: {
|
|
||||||
type: "number",
|
|
||||||
description: "月份",
|
|
||||||
minimum: 1,
|
|
||||||
maximum: 12
|
|
||||||
},
|
|
||||||
day: {
|
|
||||||
type: "number",
|
|
||||||
description: "日期",
|
|
||||||
minimum: 1,
|
|
||||||
maximum: 31
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["year", "month", "day"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import lunar from "lunar-javascript"
|
|
||||||
const { Solar, Lunar, HolidayUtil, TermUtil } = lunar
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取准确时间工具
|
|
||||||
* 通过国内权威时间源获取当前准确时间
|
|
||||||
* @returns {string} - 返回当前准确时间的JSON字符串
|
|
||||||
*/
|
|
||||||
export async function getAccurateTime() {
|
|
||||||
const timeSources = [
|
|
||||||
{
|
|
||||||
name: 'timeapi.io',
|
|
||||||
url: 'https://timeapi.io/api/Time/current/zone?timeZone=Asia/Shanghai',
|
|
||||||
method: 'json'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '国家授时中心',
|
|
||||||
url: 'http://www.ntsc.ac.cn',
|
|
||||||
method: 'header'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '阿里云',
|
|
||||||
url: 'https://www.aliyun.com',
|
|
||||||
method: 'header'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '腾讯云',
|
|
||||||
url: 'https://cloud.tencent.com',
|
|
||||||
method: 'header'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '华为云',
|
|
||||||
url: 'https://www.huaweicloud.com',
|
|
||||||
method: 'header'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const source of timeSources) {
|
|
||||||
try {
|
|
||||||
const startTime = Date.now()
|
|
||||||
// 每个时间源限时 3 秒,防止单个源 TCP 挂起长时间阻塞
|
|
||||||
const res = await fetch(source.url, { signal: AbortSignal.timeout(3000) })
|
|
||||||
const latency = Date.now() - startTime
|
|
||||||
|
|
||||||
let timestamp = null
|
|
||||||
let beijingTime = null
|
|
||||||
|
|
||||||
if (source.method === 'json') {
|
|
||||||
const data = await res.json()
|
|
||||||
timestamp = new Date(data.dateTime).getTime()
|
|
||||||
beijingTime = data.dateTime
|
|
||||||
} else if (source.method === 'header') {
|
|
||||||
const dateHeader = res.headers.get('Date')
|
|
||||||
if (dateHeader) {
|
|
||||||
timestamp = new Date(dateHeader).getTime()
|
|
||||||
beijingTime = new Date(timestamp).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp) {
|
|
||||||
return {
|
|
||||||
timestamp,
|
|
||||||
date: new Date(timestamp).toISOString(),
|
|
||||||
localTime: beijingTime,
|
|
||||||
source: source.name,
|
|
||||||
latency: latency,
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`从 ${source.name} 获取时间失败: ${error.message}`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date()
|
|
||||||
return {
|
|
||||||
timestamp: date.getTime(),
|
|
||||||
date: date.toISOString(),
|
|
||||||
localTime: date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
|
|
||||||
success: false,
|
|
||||||
message: '使用本地时间作为备份'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { tool } from "langchain"
|
|
||||||
import lunarLib from "lunar-javascript"
|
|
||||||
const { Solar, HolidayUtil } = lunarLib
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 年份节日列表工具
|
|
||||||
* 基于 lunar-javascript 计算全年节日(公历节日 + 农历传统节日)与法定节假日安排
|
|
||||||
*/
|
|
||||||
export const getYearHolidaysTool = tool(
|
|
||||||
async ({ year }) => {
|
|
||||||
try {
|
|
||||||
// 法定节假日安排(含调休补班)
|
|
||||||
const legal = HolidayUtil.getHolidays(year).map(h => ({
|
|
||||||
date: h.getTarget().toString(),
|
|
||||||
name: h.getName(),
|
|
||||||
type: h.isWork() ? "调休上班" : "放假"
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 遍历全年日期,收集公历节日与农历传统节日(春节、中秋、端午等)
|
|
||||||
const festivals = []
|
|
||||||
const start = Solar.fromYmd(year, 1, 1)
|
|
||||||
const days = Solar.fromYmd(year + 1, 1, 1).subtract(start)
|
|
||||||
for (let i = 0; i < days; i++) {
|
|
||||||
const d = start.next(i)
|
|
||||||
const names = [...d.getFestivals(), ...d.getLunar().getFestivals()]
|
|
||||||
if (names.length) {
|
|
||||||
festivals.push({ date: d.toString(), name: names.join("、") })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify({ year, legal, festivals }, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_year_holidays",
|
|
||||||
description: "查询给定年份节日所在日期列表,包括法定节假日安排(含调休)与传统节日",
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
year: {
|
|
||||||
type: "number",
|
|
||||||
description: "年份",
|
|
||||||
minimum: 1900,
|
|
||||||
maximum: 2100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["year"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { tool } from "langchain"
|
|
||||||
import lunarLib from "lunar-javascript"
|
|
||||||
const { Lunar } = lunarLib
|
|
||||||
|
|
||||||
// 当年冬至在节气表中的 key 为英文(上一年冬至占用中文 key)
|
|
||||||
const EN_TERM_NAMES = { DONG_ZHI: "冬至" }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 年份节气列表工具
|
|
||||||
* 基于 lunar-javascript 计算给定年份的 24 节气精确日期
|
|
||||||
*/
|
|
||||||
export const getYearTermsTool = tool(
|
|
||||||
async ({ year }) => {
|
|
||||||
try {
|
|
||||||
const table = Lunar.fromYmd(year, 1, 1).getJieQiTable()
|
|
||||||
const terms = []
|
|
||||||
for (const [key, solar] of Object.entries(table)) {
|
|
||||||
// 只保留属于目标年份的节气(表首含上一年边界节气)
|
|
||||||
if (solar.getYear() !== year) continue
|
|
||||||
const name = EN_TERM_NAMES[key] ?? key
|
|
||||||
terms.push({ date: solar.toString(), name })
|
|
||||||
}
|
|
||||||
terms.sort((a, b) => a.date.localeCompare(b.date))
|
|
||||||
|
|
||||||
return JSON.stringify({ year, terms }, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_year_terms",
|
|
||||||
description: "查询给定年份节气所在日期列表",
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
year: {
|
|
||||||
type: "number",
|
|
||||||
description: "年份",
|
|
||||||
minimum: 1900,
|
|
||||||
maximum: 2100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["year"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import { tool } from "@langchain/core/tools";
|
|
||||||
import z from "zod";
|
|
||||||
import mongoose from "mongoose";
|
|
||||||
import { DBModel } from "../../../../models/index.js";
|
|
||||||
|
|
||||||
const escortRecordSetTool = tool(
|
|
||||||
async ({ orderId, status, notes, payment }) => {
|
|
||||||
try {
|
|
||||||
if (!orderId) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Record ID (_id) is required as the lookup key",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mongoose.Types.ObjectId.isValid(orderId)) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `Invalid record ID: ${orderId}. A valid record _id is required.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const record = await DBModel.EscortRecord.findById(orderId);
|
|
||||||
if (!record) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `No escort record found for order ID: ${orderId}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = {};
|
|
||||||
|
|
||||||
if (status !== undefined) {
|
|
||||||
update.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notes !== undefined) {
|
|
||||||
if (notes.patientNote !== undefined) {
|
|
||||||
update["notes.patientNote"] = notes.patientNote;
|
|
||||||
}
|
|
||||||
if (notes.escortNote !== undefined) {
|
|
||||||
update["notes.escortNote"] = notes.escortNote;
|
|
||||||
}
|
|
||||||
if (notes.medicalSummary !== undefined) {
|
|
||||||
update["notes.medicalSummary"] = notes.medicalSummary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payment !== undefined) {
|
|
||||||
if (payment.totalFee !== undefined) {
|
|
||||||
update["payment.totalFee"] = payment.totalFee;
|
|
||||||
}
|
|
||||||
if (payment.paidFee !== undefined) {
|
|
||||||
update["payment.paidFee"] = payment.paidFee;
|
|
||||||
}
|
|
||||||
if (payment.status !== undefined) {
|
|
||||||
update["payment.status"] = payment.status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update["meta.updatetime"] = Date.now();
|
|
||||||
|
|
||||||
const updatedRecord = await DBModel.EscortRecord.findByIdAndUpdate(
|
|
||||||
record._id,
|
|
||||||
{ $set: update },
|
|
||||||
{ new: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: updatedRecord,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "escort_record_set",
|
|
||||||
description:
|
|
||||||
"Update escort record fields by record ID (_id). Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
|
|
||||||
schema: z.object({
|
|
||||||
orderId: z
|
|
||||||
.string()
|
|
||||||
.describe("Record ID (_id) used as the lookup key"),
|
|
||||||
status: z
|
|
||||||
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"Appointment status: pending (待确认), confirmed (已确认), in_progress (进行中), completed (已完成), cancelled (已取消)"
|
|
||||||
),
|
|
||||||
notes: z
|
|
||||||
.object({
|
|
||||||
patientNote: z.string().optional().describe("Patient remarks / special requirements"),
|
|
||||||
escortNote: z.string().optional().describe("Escort service notes"),
|
|
||||||
medicalSummary: z.string().optional().describe("Medical visit summary"),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe("Notes information to update"),
|
|
||||||
payment: z
|
|
||||||
.object({
|
|
||||||
totalFee: z.number().optional().describe("Total service fee (RMB)"),
|
|
||||||
paidFee: z.number().optional().describe("Amount already paid (RMB)"),
|
|
||||||
status: z
|
|
||||||
.enum(["unpaid", "partial", "paid", "refunded"])
|
|
||||||
.optional()
|
|
||||||
.describe("Payment status: unpaid, partial, paid, refunded"),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe("Payment information to update"),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export { escortRecordSetTool };
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取城市经纬度工具
|
|
||||||
* @param {string} city - 城市名称
|
|
||||||
* @returns {Object|null} - 返回经纬度对象 {lat, lng},如果未找到则返回null
|
|
||||||
*/
|
|
||||||
export const getLatLngTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
const { city } = input
|
|
||||||
try {
|
|
||||||
// 使用open-meteo地理编码API获取经纬度
|
|
||||||
console.log('获取城市经纬度:', city)
|
|
||||||
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&language=zh&count=1`
|
|
||||||
const res = await fetch(url)
|
|
||||||
const data = await res.json()
|
|
||||||
|
|
||||||
if (data.results?.length) {
|
|
||||||
return {
|
|
||||||
lat: data.results[0].latitude,
|
|
||||||
lng: data.results[0].longitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到结果,返回null
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('地理编码API调用失败:', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_lat_lng",
|
|
||||||
description: `根据城市名称获取经纬度信息`,
|
|
||||||
schema: z.object({
|
|
||||||
city: z.string().describe("城市名称,例如:北京市海淀区")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
// LangChain 环境变量工具
|
|
||||||
|
|
||||||
import { tool } from '@langchain/core/tools';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取环境变量工具
|
|
||||||
* @param {Object} input - 输入参数
|
|
||||||
* @param {string} [input.name] - 环境变量名,不指定则返回所有
|
|
||||||
* @returns {string} - 返回环境变量值或所有环境变量的JSON字符串
|
|
||||||
*/
|
|
||||||
export const getEnvTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
const { name } = input || {};
|
|
||||||
|
|
||||||
const sensitiveKeys = ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'CREDENTIAL'];
|
|
||||||
const isSensitive = (key) => sensitiveKeys.some(s => key.toUpperCase().includes(s));
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
const value = process.env[name];
|
|
||||||
if (value === undefined) {
|
|
||||||
return JSON.stringify({ error: `Environment variable '${name}' not found` });
|
|
||||||
}
|
|
||||||
// 按名查询同样脱敏,防止 LLM 绕过读取密钥
|
|
||||||
return JSON.stringify({ name, value: isSensitive(name) ? '***REDACTED***' : value });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回所有环境变量(过滤敏感信息)
|
|
||||||
const env = { ...process.env };
|
|
||||||
for (const key of Object.keys(env)) {
|
|
||||||
if (isSensitive(key)) {
|
|
||||||
env[key] = '***REDACTED***';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify({ env });
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'env_get',
|
|
||||||
description: '获取环境变量的值。如果不指定变量名,则返回所有环境变量。',
|
|
||||||
schema: z.object({
|
|
||||||
name: z.string().optional().describe('环境变量名,不指定则返回所有')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置环境变量工具
|
|
||||||
* @param {Object} input - 输入参数
|
|
||||||
* @param {string} input.name - 环境变量名
|
|
||||||
* @param {string} input.value - 环境变量值
|
|
||||||
* @returns {string} - 返回设置结果的JSON字符串
|
|
||||||
*/
|
|
||||||
export const setEnvTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
const { name, value } = input || {};
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return JSON.stringify({ error: 'Environment variable name is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined) {
|
|
||||||
return JSON.stringify({ error: 'Environment variable value is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.env[name] = value;
|
|
||||||
return JSON.stringify({ success: true, name, value });
|
|
||||||
} catch (error) {
|
|
||||||
return JSON.stringify({ error: `Failed to set environment variable: ${error.message}` });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'env_set',
|
|
||||||
description: '设置环境变量的值。',
|
|
||||||
schema: z.object({
|
|
||||||
name: z.string().describe('环境变量名'),
|
|
||||||
value: z.string().describe('环境变量值')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default getEnvTool;
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统文件信息工具
|
|
||||||
* @returns {string} - 返回系统文件信息的 JSON 字符串,包括分区信息和各分区剩余空间
|
|
||||||
*/
|
|
||||||
export const getSystemFileInfoTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const fileSystemInfo = {
|
|
||||||
platform: os.platform(),
|
|
||||||
partitions: [],
|
|
||||||
volumeInfo: [],
|
|
||||||
diskHealth: [],
|
|
||||||
inodeInfo: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取文件系统信息(Windows)
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object DeviceID,Description,FileSystem,Size,FreeSpace,VolumeName | ConvertTo-Json"')
|
|
||||||
const partitionData = JSON.parse(stdout)
|
|
||||||
const partitionsArray = Array.isArray(partitionData) ? partitionData : [partitionData]
|
|
||||||
|
|
||||||
fileSystemInfo.partitions = partitionsArray.map(disk => {
|
|
||||||
const size = disk.Size ? parseInt(disk.Size) : 0
|
|
||||||
const freeSpace = disk.FreeSpace ? parseInt(disk.FreeSpace) : 0
|
|
||||||
const usedSpace = size - freeSpace
|
|
||||||
|
|
||||||
return {
|
|
||||||
device: disk.DeviceID || 'N/A',
|
|
||||||
description: disk.Description || 'N/A',
|
|
||||||
filesystem: disk.FileSystem || 'N/A',
|
|
||||||
volumeName: disk.VolumeName || 'N/A',
|
|
||||||
size: size > 0 ? `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
|
|
||||||
freeSpace: freeSpace > 0 ? `${(freeSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
|
|
||||||
usedSpace: usedSpace > 0 ? `${(usedSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
|
|
||||||
usePercent: size > 0 ? `${((usedSpace / size) * 100).toFixed(2)}%` : 'N/A'
|
|
||||||
}
|
|
||||||
}).filter(item => item)
|
|
||||||
|
|
||||||
// 获取卷详细信息(Windows)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object VolumeName,VolumeSerialNumber,FileSystem,MaximumComponentLength,DeviceID,DriveType | ConvertTo-Json"')
|
|
||||||
const allVolumeData = JSON.parse(stdout)
|
|
||||||
const volumeArray = Array.isArray(allVolumeData) ? allVolumeData : [allVolumeData]
|
|
||||||
|
|
||||||
fileSystemInfo.volumeInfo = volumeArray.map(vol => ({
|
|
||||||
device: vol.DeviceID || 'N/A',
|
|
||||||
volumeName: vol.VolumeName || 'N/A',
|
|
||||||
serialNumber: vol.VolumeSerialNumber || 'N/A',
|
|
||||||
fileSystem: vol.FileSystem || 'N/A',
|
|
||||||
maxComponentLength: vol.MaximumComponentLength || 'N/A',
|
|
||||||
deviceID: vol.DeviceID || 'N/A',
|
|
||||||
driveType: vol.DriveType === 2 ? 'Removable' : vol.DriveType === 3 ? 'Local' : vol.DriveType === 4 ? 'Network' : vol.DriveType === 5 ? 'CD-ROM' : 'Unknown'
|
|
||||||
})).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting volume info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取磁盘健康状态(Windows SMART)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_DiskDrive | Select-Object Model,Status,SerialNumber,Size,MediaType | ConvertTo-Json"')
|
|
||||||
const diskData = JSON.parse(stdout)
|
|
||||||
const diskArray = Array.isArray(diskData) ? diskData : [diskData]
|
|
||||||
fileSystemInfo.diskHealth = diskArray.map(disk => {
|
|
||||||
return {
|
|
||||||
model: disk.Model || 'N/A',
|
|
||||||
status: disk.Status || 'N/A',
|
|
||||||
serialNumber: disk.SerialNumber || 'N/A',
|
|
||||||
size: disk.Size ? `${(parseInt(disk.Size) / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A'
|
|
||||||
}
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting disk health:', error)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting file system info:', error)
|
|
||||||
fileSystemInfo.error = 'Error getting file system info'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取文件系统信息(Linux)
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('df -h')
|
|
||||||
fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
device: parts[0],
|
|
||||||
size: parts[1],
|
|
||||||
usedSpace: parts[2],
|
|
||||||
freeSpace: parts[3],
|
|
||||||
usePercent: parts[4],
|
|
||||||
mountedOn: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
|
|
||||||
// 获取挂载选项和文件系统类型(Linux)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('mount | column -t')
|
|
||||||
const mountLines = stdout.trim().split('\n')
|
|
||||||
const mountInfo = {}
|
|
||||||
mountLines.forEach(line => {
|
|
||||||
const match = line.match(/^(.+?)\s+on\s+(.+?)\s+type\s+(.+?)\s+\((.*)\)$/)
|
|
||||||
if (match) {
|
|
||||||
mountInfo[match[2]] = {
|
|
||||||
device: match[1],
|
|
||||||
filesystem: match[3],
|
|
||||||
options: match[4]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fileSystemInfo.mountInfo = mountInfo
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting mount info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取inode信息(Linux)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('df -i')
|
|
||||||
fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
device: parts[0],
|
|
||||||
totalInodes: parts[1],
|
|
||||||
usedInodes: parts[2],
|
|
||||||
freeInodes: parts[3],
|
|
||||||
usePercent: parts[4],
|
|
||||||
mountedOn: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting inode info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取磁盘健康状态(Linux SMART)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('smartctl --health --json /dev/sda 2>/dev/null || echo "{}"')
|
|
||||||
if (stdout && stdout.includes('smart_status')) {
|
|
||||||
const healthData = JSON.parse(stdout)
|
|
||||||
fileSystemInfo.diskHealth.push({
|
|
||||||
device: '/dev/sda',
|
|
||||||
status: healthData.smart_status?.passed ? 'OK' : 'Failed',
|
|
||||||
powerOnHours: healthData.power_on_time?.hours || 'N/A',
|
|
||||||
temperature: healthData.temperature?.current || 'N/A'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('SMART info not available or requires root')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting file system info:', error)
|
|
||||||
fileSystemInfo.error = 'Error getting file system info'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取文件系统信息(macOS)
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('df -h')
|
|
||||||
fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
device: parts[0],
|
|
||||||
size: parts[1],
|
|
||||||
usedSpace: parts[2],
|
|
||||||
freeSpace: parts[3],
|
|
||||||
usePercent: parts[4],
|
|
||||||
mountedOn: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
|
|
||||||
// 获取卷详细信息(macOS)
|
|
||||||
try {
|
|
||||||
const diskInfo = await execPromise('diskutil info -all')
|
|
||||||
const diskLines = diskInfo.stdout.trim().split('\n\n')
|
|
||||||
diskLines.forEach(disk => {
|
|
||||||
const volMatch = disk.match(/Volume Name:\s+(.+)/)
|
|
||||||
const devMatch = disk.match(/Device Node:\s+(.+)/)
|
|
||||||
const fsMatch = disk.match(/File System Personality:\s+(.+)/)
|
|
||||||
const serialMatch = disk.match(/Disk UUID:\s+(.+)/)
|
|
||||||
|
|
||||||
if (devMatch) {
|
|
||||||
fileSystemInfo.volumeInfo.push({
|
|
||||||
device: devMatch[1],
|
|
||||||
volumeName: volMatch ? volMatch[1] : 'N/A',
|
|
||||||
filesystem: fsMatch ? fsMatch[1] : 'N/A',
|
|
||||||
serialNumber: serialMatch ? serialMatch[1] : 'N/A'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting volume info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取inode信息(macOS)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('df -i')
|
|
||||||
fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
device: parts[0],
|
|
||||||
totalInodes: parts[1],
|
|
||||||
usedInodes: parts[2],
|
|
||||||
freeInodes: parts[3],
|
|
||||||
usePercent: parts[4],
|
|
||||||
mountedOn: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting inode info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取磁盘健康状态(macOS)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('diskutil smartStatus -all 2>/dev/null || echo "N/A"')
|
|
||||||
if (stdout && stdout !== 'N/A') {
|
|
||||||
fileSystemInfo.diskHealth = stdout.trim()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('SMART status not available')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting file system info:', error)
|
|
||||||
fileSystemInfo.error = 'Error getting file system info'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(fileSystemInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting file system info:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_system_file_info",
|
|
||||||
description: `获取系统硬盘分区和文件系统信息,包括分区信息、卷标、序列号、磁盘健康状态、inode信息等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统硬件配置信息工具
|
|
||||||
* @returns {string} - 返回系统硬件配置信息的 JSON 字符串
|
|
||||||
*/
|
|
||||||
export const getHardwareInfoTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const hardwareInfo = {
|
|
||||||
cpu: {
|
|
||||||
cores: os.cpus().length,
|
|
||||||
model: os.cpus()[0].model,
|
|
||||||
speed: os.cpus()[0].speed
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
total: `${(os.totalmem() / (1024 ** 3)).toFixed(2)} GB`,
|
|
||||||
free: `${(os.freemem() / (1024 ** 3)).toFixed(2)} GB`
|
|
||||||
},
|
|
||||||
disks: [],
|
|
||||||
gpu: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取硬盘信息(Windows)
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_DiskDrive | Select-Object Model,InterfaceType,Size | ConvertTo-Json"')
|
|
||||||
const diskData = JSON.parse(stdout)
|
|
||||||
const diskArray = Array.isArray(diskData) ? diskData : [diskData]
|
|
||||||
hardwareInfo.disks = diskArray.map(disk => ({
|
|
||||||
model: disk.Model || 'N/A',
|
|
||||||
interfaceType: disk.InterfaceType || 'N/A',
|
|
||||||
size: disk.Size ? `${(parseInt(disk.Size) / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A'
|
|
||||||
})).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting disk info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取显卡信息(Windows)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('wmic path win32_videocontroller get name')
|
|
||||||
hardwareInfo.gpu = stdout.trim().split('\n').slice(1).filter(line => line.trim())
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting GPU info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取硬盘和显卡信息(Linux)
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('lsblk -o NAME,MODEL,SIZE,TYPE | grep disk')
|
|
||||||
const diskLines = stdout.trim().split('\n')
|
|
||||||
hardwareInfo.disks = diskLines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 3) {
|
|
||||||
return {
|
|
||||||
name: parts[0],
|
|
||||||
model: parts.slice(1, -1).join(' '),
|
|
||||||
size: parts[parts.length - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting disk info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('lspci | grep VGA')
|
|
||||||
hardwareInfo.gpu = stdout.trim().split('\n').map(line => line.trim())
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting GPU info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取硬盘和显卡信息(macOS)
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('diskutil list')
|
|
||||||
hardwareInfo.disks = [{
|
|
||||||
info: stdout.trim()
|
|
||||||
}]
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting disk info:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('system_profiler SPDisplaysDataType')
|
|
||||||
hardwareInfo.gpu = [{
|
|
||||||
info: stdout.trim()
|
|
||||||
}]
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting GPU info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(hardwareInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting hardware info:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_hardware_info",
|
|
||||||
description: `获取系统硬件配置信息,包括CPU、内存、硬盘、显卡等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已安装软件信息工具
|
|
||||||
* @returns {string} - 返回已安装软件信息的 JSON 字符串
|
|
||||||
*/
|
|
||||||
export const getInstalledSoftwareTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const softwareInfo = {
|
|
||||||
platform: os.platform(),
|
|
||||||
software: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Windows已安装软件
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_Product | Select-Object Name,Version,Vendor,InstallDate | ConvertTo-Json"')
|
|
||||||
const softwareData = JSON.parse(stdout)
|
|
||||||
const softwareArray = Array.isArray(softwareData) ? softwareData : [softwareData]
|
|
||||||
softwareInfo.software = softwareArray.map(soft => ({
|
|
||||||
name: soft.Name || 'N/A',
|
|
||||||
version: soft.Version || 'N/A',
|
|
||||||
vendor: soft.Vendor || 'N/A',
|
|
||||||
installDate: soft.InstallDate || 'N/A'
|
|
||||||
})).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting installed software:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取Linux已安装软件
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
// 尝试使用dpkg(Debian/Ubuntu)
|
|
||||||
const { stdout: dpkgOutput } = await execPromise('dpkg -l 2>/dev/null || echo "N/A"')
|
|
||||||
if (dpkgOutput && dpkgOutput !== 'N/A') {
|
|
||||||
const lines = dpkgOutput.trim().split('\n').slice(5)
|
|
||||||
softwareInfo.software = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 3) {
|
|
||||||
return {
|
|
||||||
name: parts[1],
|
|
||||||
version: parts[2],
|
|
||||||
description: parts.slice(3).join(' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} else {
|
|
||||||
// 尝试使用rpm(RHEL/CentOS)
|
|
||||||
const { stdout: rpmOutput } = await execPromise('rpm -qa 2>/dev/null || echo "N/A"')
|
|
||||||
if (rpmOutput && rpmOutput !== 'N/A') {
|
|
||||||
const lines = rpmOutput.trim().split('\n')
|
|
||||||
softwareInfo.software = lines.map(line => {
|
|
||||||
const match = line.match(/(.+)-(.+)-(.+)/)
|
|
||||||
if (match) {
|
|
||||||
return {
|
|
||||||
name: match[1],
|
|
||||||
version: `${match[2]}-${match[3]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting installed software:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取macOS已安装软件
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('find /Applications -name "*.app" -type d | sort')
|
|
||||||
const apps = stdout.trim().split('\n')
|
|
||||||
softwareInfo.software = apps.map(app => ({
|
|
||||||
name: app.split('/').pop().replace('.app', ''),
|
|
||||||
path: app
|
|
||||||
})).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting installed software:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(softwareInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting installed software:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_installed_software",
|
|
||||||
description: `获取系统已安装的软件信息,包括软件名称、版本、供应商等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取操作系统信息工具
|
|
||||||
* @returns {string} - 返回操作系统信息的 JSON 字符串
|
|
||||||
*/
|
|
||||||
export const getOsInfoTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const osInfo = {
|
|
||||||
platform: os.platform(),
|
|
||||||
arch: os.arch(),
|
|
||||||
release: os.release(),
|
|
||||||
hostname: os.hostname(),
|
|
||||||
uptime: `${Math.round(os.uptime() / 3600)} hours`,
|
|
||||||
nodeVersion: process.version,
|
|
||||||
homeDir: os.homedir(),
|
|
||||||
tempDir: os.tmpdir(),
|
|
||||||
userInfo: os.userInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Windows详细信息
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber,OSArchitecture | ConvertTo-Json"')
|
|
||||||
const winInfo = JSON.parse(stdout)
|
|
||||||
osInfo.windows = {
|
|
||||||
caption: winInfo.Caption || 'N/A',
|
|
||||||
version: winInfo.Version || 'N/A',
|
|
||||||
buildNumber: winInfo.BuildNumber || 'N/A',
|
|
||||||
architecture: winInfo.OSArchitecture || 'N/A'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting Windows info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取Linux详细信息
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('cat /etc/os-release')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
const linuxInfo = {}
|
|
||||||
lines.forEach(line => {
|
|
||||||
const match = line.match(/(.+?)=(.+)/)
|
|
||||||
if (match) {
|
|
||||||
linuxInfo[match[1]] = match[2].replace(/"/g, '')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
osInfo.linux = linuxInfo
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting Linux info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取macOS详细信息
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('sw_vers')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
const macInfo = {}
|
|
||||||
lines.forEach(line => {
|
|
||||||
const match = line.match(/(.+?):\s+(.+)/)
|
|
||||||
if (match) {
|
|
||||||
macInfo[match[1]] = match[2]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
osInfo.macOS = macInfo
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting macOS info:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(osInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting OS info:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_os_info",
|
|
||||||
description: `获取操作系统信息,包括平台、架构、版本、主机名、运行时间等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统网卡和网络连接信息工具
|
|
||||||
* @returns {string} - 返回网络信息的 JSON 字符串,包括网卡信息和网络连接信息
|
|
||||||
*/
|
|
||||||
export const getNetworkInfoTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const networkInfo = {
|
|
||||||
platform: os.platform(),
|
|
||||||
networkInterfaces: [],
|
|
||||||
dnsServers: [],
|
|
||||||
gateway: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取网络接口信息(跨平台)
|
|
||||||
const interfaces = os.networkInterfaces()
|
|
||||||
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
||||||
networkInfo.networkInterfaces.push({
|
|
||||||
name,
|
|
||||||
addresses: addresses.map(addr => ({
|
|
||||||
family: addr.family,
|
|
||||||
address: addr.address,
|
|
||||||
netmask: addr.netmask,
|
|
||||||
mac: addr.mac,
|
|
||||||
internal: addr.internal,
|
|
||||||
cidr: addr.cidr
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详细网络信息(Windows)
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ipconfig /all')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
let currentAdapter = null
|
|
||||||
|
|
||||||
lines.forEach(line => {
|
|
||||||
const adapterMatch = line.match(/适配器\s+(.+?):/)
|
|
||||||
if (adapterMatch) {
|
|
||||||
if (currentAdapter) {
|
|
||||||
networkInfo.networkInterfaces.push(currentAdapter)
|
|
||||||
}
|
|
||||||
currentAdapter = {
|
|
||||||
name: adapterMatch[1],
|
|
||||||
description: '',
|
|
||||||
macAddress: '',
|
|
||||||
dhcpEnabled: false,
|
|
||||||
ipAddress: [],
|
|
||||||
subnetMask: [],
|
|
||||||
defaultGateway: [],
|
|
||||||
dnsServers: []
|
|
||||||
}
|
|
||||||
} else if (currentAdapter) {
|
|
||||||
const descMatch = line.match(/描述\s+(.+)/)
|
|
||||||
if (descMatch) currentAdapter.description = descMatch[1].trim()
|
|
||||||
|
|
||||||
const macMatch = line.match(/物理地址\s+:\s+(.+)/)
|
|
||||||
if (macMatch) currentAdapter.macAddress = macMatch[1].trim()
|
|
||||||
|
|
||||||
const dhcpMatch = line.match(/DHCP 已启用\s+:\s+(.+)/)
|
|
||||||
if (dhcpMatch) currentAdapter.dhcpEnabled = dhcpMatch[1].trim() === '是'
|
|
||||||
|
|
||||||
const ipMatch = line.match(/IPv4 地址\s+:\s+(.+)/)
|
|
||||||
if (ipMatch) currentAdapter.ipAddress.push(ipMatch[1].trim())
|
|
||||||
|
|
||||||
const subnetMatch = line.match(/子网掩码\s+:\s+(.+)/)
|
|
||||||
if (subnetMatch) currentAdapter.subnetMask.push(subnetMatch[1].trim())
|
|
||||||
|
|
||||||
const gatewayMatch = line.match(/默认网关\s+:\s+(.+)/)
|
|
||||||
if (gatewayMatch && gatewayMatch[1].trim()) currentAdapter.defaultGateway.push(gatewayMatch[1].trim())
|
|
||||||
|
|
||||||
const dnsMatch = line.match(/DNS 服务器\s+:\s+(.+)/)
|
|
||||||
if (dnsMatch && dnsMatch[1].trim()) currentAdapter.dnsServers.push(dnsMatch[1].trim())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (currentAdapter) {
|
|
||||||
networkInfo.networkInterfaces.push(currentAdapter)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting network config:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取DNS服务器(Windows)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('nslookup localhost 2>&1')
|
|
||||||
const dnsMatch = stdout.match(/Server:\s+(.+)/)
|
|
||||||
if (dnsMatch) {
|
|
||||||
networkInfo.dnsServers.push(dnsMatch[1].trim())
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting DNS servers:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取详细网络信息(Linux)
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ip addr show')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
let currentInterface = null
|
|
||||||
|
|
||||||
lines.forEach(line => {
|
|
||||||
const interfaceMatch = line.match(/^\d+:\s+(\w+):/)
|
|
||||||
if (interfaceMatch) {
|
|
||||||
if (currentInterface) {
|
|
||||||
networkInfo.networkInterfaces.push(currentInterface)
|
|
||||||
}
|
|
||||||
currentInterface = {
|
|
||||||
name: interfaceMatch[1],
|
|
||||||
state: '',
|
|
||||||
macAddress: '',
|
|
||||||
ipAddress: [],
|
|
||||||
subnetMask: []
|
|
||||||
}
|
|
||||||
} else if (currentInterface) {
|
|
||||||
const stateMatch = line.match(/state\s+(\w+)/)
|
|
||||||
if (stateMatch) currentInterface.state = stateMatch[1]
|
|
||||||
|
|
||||||
const macMatch = line.match(/link\/ether\s+([a-fA-F0-9:]+)/)
|
|
||||||
if (macMatch) currentInterface.macAddress = macMatch[1]
|
|
||||||
|
|
||||||
const ipMatch = line.match(/inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/)
|
|
||||||
if (ipMatch) {
|
|
||||||
currentInterface.ipAddress.push(ipMatch[1])
|
|
||||||
const maskLength = parseInt(ipMatch[2])
|
|
||||||
currentInterface.subnetMask.push(maskLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (currentInterface) {
|
|
||||||
networkInfo.networkInterfaces.push(currentInterface)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting network config:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取DNS服务器(Linux)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('cat /etc/resolv.conf')
|
|
||||||
const dnsMatches = stdout.match(/nameserver\s+(.+)/g)
|
|
||||||
if (dnsMatches) {
|
|
||||||
networkInfo.dnsServers = dnsMatches.map(match => match.replace('nameserver', '').trim())
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting DNS servers:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取网关(Linux)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ip route show default')
|
|
||||||
const gatewayMatch = stdout.match(/default via (\d+\.\d+\.\d+\.\d+)/)
|
|
||||||
if (gatewayMatch) {
|
|
||||||
networkInfo.gateway.push(gatewayMatch[1])
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting gateway:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取详细网络信息(macOS)
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ifconfig')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
let currentInterface = null
|
|
||||||
|
|
||||||
lines.forEach(line => {
|
|
||||||
const interfaceMatch = line.match(/^(\w+):/)
|
|
||||||
if (interfaceMatch) {
|
|
||||||
if (currentInterface) {
|
|
||||||
networkInfo.networkInterfaces.push(currentInterface)
|
|
||||||
}
|
|
||||||
currentInterface = {
|
|
||||||
name: interfaceMatch[1],
|
|
||||||
status: '',
|
|
||||||
macAddress: '',
|
|
||||||
ipAddress: [],
|
|
||||||
subnetMask: []
|
|
||||||
}
|
|
||||||
} else if (currentInterface) {
|
|
||||||
const statusMatch = line.match(/status:\s+(\w+)/)
|
|
||||||
if (statusMatch) currentInterface.status = statusMatch[1]
|
|
||||||
|
|
||||||
const macMatch = line.match(/ether\s+([a-fA-F0-9:]+)/)
|
|
||||||
if (macMatch) currentInterface.macAddress = macMatch[1]
|
|
||||||
|
|
||||||
const ipMatch = line.match(/inet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+(0x[0-9a-fA-F]+)/)
|
|
||||||
if (ipMatch) {
|
|
||||||
currentInterface.ipAddress.push(ipMatch[1])
|
|
||||||
currentInterface.subnetMask.push(ipMatch[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (currentInterface) {
|
|
||||||
networkInfo.networkInterfaces.push(currentInterface)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting network config:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取DNS服务器(macOS)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('scutil --dns | grep nameserver | awk \'{print $3}\'')
|
|
||||||
networkInfo.dnsServers = stdout.trim().split('\n').filter(dns => dns)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting DNS servers:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取网关(macOS)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('netstat -nr | grep default')
|
|
||||||
const gatewayMatch = stdout.match(/default\s+(\d+\.\d+\.\d+\.\d+)/)
|
|
||||||
if (gatewayMatch) {
|
|
||||||
networkInfo.gateway.push(gatewayMatch[1])
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting gateway:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(networkInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting network info:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_network_info",
|
|
||||||
description: `获取系统网卡和网络信息,包括网卡配置、IP地址、MAC地址、DNS服务器、网关等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
import os from 'os'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { exec } from 'child_process'
|
|
||||||
|
|
||||||
const execPromise = promisify(exec)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统活动连接和Socket信息工具
|
|
||||||
* @returns {string} - 返回Socket信息的 JSON 字符串,包括活动连接和监听端口等
|
|
||||||
*/
|
|
||||||
export const getSocketInfoTool = tool(
|
|
||||||
async (input) => {
|
|
||||||
try {
|
|
||||||
const socketInfo = {
|
|
||||||
platform: os.platform(),
|
|
||||||
activeConnections: [],
|
|
||||||
listeningPorts: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取活动连接(Windows)
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('netstat -ano')
|
|
||||||
const lines = stdout.trim().split('\n').slice(4)
|
|
||||||
socketInfo.activeConnections = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 5) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
localAddress: parts[1],
|
|
||||||
foreignAddress: parts[2],
|
|
||||||
state: parts[3],
|
|
||||||
processId: parts[4]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting active connections:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取监听端口(Windows)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('netstat -an | findstr LISTENING')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
socketInfo.listeningPorts = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 4) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
localAddress: parts[1],
|
|
||||||
state: parts[3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting listening ports:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取活动连接(Linux)
|
|
||||||
else if (os.platform() === 'linux') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ss -tuln')
|
|
||||||
const lines = stdout.trim().split('\n').slice(1)
|
|
||||||
socketInfo.activeConnections = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 5) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
state: parts[1],
|
|
||||||
localAddress: parts[4],
|
|
||||||
peerAddress: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting active connections:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取监听端口(Linux)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('ss -tulpn')
|
|
||||||
const lines = stdout.trim().split('\n').slice(1)
|
|
||||||
socketInfo.listeningPorts = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
state: parts[1],
|
|
||||||
localAddress: parts[4],
|
|
||||||
processInfo: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting listening ports:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 获取活动连接(macOS)
|
|
||||||
else if (os.platform() === 'darwin') {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('netstat -an')
|
|
||||||
const lines = stdout.trim().split('\n').slice(2)
|
|
||||||
socketInfo.activeConnections = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
localAddress: parts[3],
|
|
||||||
foreignAddress: parts[4],
|
|
||||||
state: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting active connections:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取监听端口(macOS)
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise('netstat -an | grep LISTEN')
|
|
||||||
const lines = stdout.trim().split('\n')
|
|
||||||
socketInfo.listeningPorts = lines.map(line => {
|
|
||||||
const parts = line.trim().split(/\s+/)
|
|
||||||
if (parts.length >= 6) {
|
|
||||||
return {
|
|
||||||
protocol: parts[0],
|
|
||||||
localAddress: parts[3],
|
|
||||||
foreignAddress: parts[4],
|
|
||||||
state: parts[5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}).filter(item => item)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting listening ports:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(socketInfo, null, 2)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting socket info:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_socket_info",
|
|
||||||
description: `获取系统活动连接和Socket信息,包括TCP/UDP连接、监听端口、进程ID等`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import * as z from "zod"
|
|
||||||
import { tool } from "langchain"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统时间工具
|
|
||||||
* @returns {string} - 返回当前系统时间
|
|
||||||
*/
|
|
||||||
export const getSystemTimeTool = tool(
|
|
||||||
(input) => {
|
|
||||||
return new Date().toLocaleString()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get_system_time",
|
|
||||||
description: `获取当前系统时间`,
|
|
||||||
schema: z.object({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { TavilyExtract } from "@langchain/tavily";
|
|
||||||
import { tool } from "@langchain/core/tools";
|
|
||||||
import * as z from "zod"
|
|
||||||
import config from "../../../../conf.json" with { type: "json" };
|
|
||||||
|
|
||||||
const webFetchTool = tool(
|
|
||||||
async ({
|
|
||||||
extractDepth = "basic",
|
|
||||||
includeImages = false,
|
|
||||||
format = "markdown",
|
|
||||||
urls = [],
|
|
||||||
}) => {
|
|
||||||
const tavilyExtract = new TavilyExtract({
|
|
||||||
tavilyApiKey: config.agent.tavily.apiKey,
|
|
||||||
extractDepth,
|
|
||||||
includeImages,
|
|
||||||
format
|
|
||||||
});
|
|
||||||
return await tavilyExtract._call({ urls });
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "web_fetch",
|
|
||||||
description: "Run a web fetch",
|
|
||||||
schema: z.object({
|
|
||||||
extractDepth: z.enum(["basic", "advanced"]).default("basic"),
|
|
||||||
includeImages: z.boolean().default(false),
|
|
||||||
format: z
|
|
||||||
.enum(["markdown", "json"])
|
|
||||||
.default("markdown"),
|
|
||||||
urls: z.array(z.string().describe("The urls")).default([]),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export { webFetchTool };
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { tool } from 'langchain';
|
|
||||||
import * as z from 'zod';
|
|
||||||
|
|
||||||
export const httpGetTool = tool(
|
|
||||||
async ({ url, headers = {}, timeout = 30000 }) => {
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
} catch (e) {
|
|
||||||
return JSON.stringify({ success: false, error: `Invalid URL: ${url}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'XAgent-Bot/1.0',
|
|
||||||
...headers
|
|
||||||
},
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
const contentType = response.headers.get('content-type') || '';
|
|
||||||
let body;
|
|
||||||
|
|
||||||
if (contentType.includes('application/json')) {
|
|
||||||
body = await response.json();
|
|
||||||
} else if (contentType.includes('text/') || contentType.includes('application/javascript')) {
|
|
||||||
body = await response.text();
|
|
||||||
} else {
|
|
||||||
const buffer = await response.arrayBuffer();
|
|
||||||
body = Buffer.from(buffer).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
|
||||||
body
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name === 'AbortError') {
|
|
||||||
return JSON.stringify({ success: false, error: `Request timed out after ${timeout}ms` });
|
|
||||||
}
|
|
||||||
return JSON.stringify({ success: false, error: error.message });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'http_get',
|
|
||||||
description: 'Send HTTP GET request and return response content. Supports custom headers and timeout settings.',
|
|
||||||
schema: z.object({
|
|
||||||
url: z.string().describe('Request URL'),
|
|
||||||
headers: z.record(z.string(), z.string()).optional().describe('Request headers'),
|
|
||||||
timeout: z.number().optional().describe('Timeout in milliseconds, default 30000')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { tool } from "@langchain/core/tools";
|
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
const httpPostTool = tool(
|
|
||||||
async ({ url, body, headers = {}, contentType = 'json', timeout = 30000 }) => {
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, error: `Invalid URL: ${url}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
let requestBody;
|
|
||||||
const requestHeaders = {
|
|
||||||
'User-Agent': 'XAgent-Bot/1.0',
|
|
||||||
...headers
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (contentType) {
|
|
||||||
case 'json':
|
|
||||||
requestHeaders['Content-Type'] = 'application/json';
|
|
||||||
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
||||||
break;
|
|
||||||
case 'form':
|
|
||||||
requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
||||||
if (typeof body === 'object') {
|
|
||||||
requestBody = new URLSearchParams(body).toString();
|
|
||||||
} else {
|
|
||||||
requestBody = body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
requestHeaders['Content-Type'] = 'text/plain';
|
|
||||||
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: requestHeaders,
|
|
||||||
body: requestBody,
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
const responseContentType = response.headers.get('content-type') || '';
|
|
||||||
let responseBody;
|
|
||||||
|
|
||||||
if (responseContentType.includes('application/json')) {
|
|
||||||
responseBody = await response.json();
|
|
||||||
} else {
|
|
||||||
responseBody = await response.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
|
||||||
body: responseBody
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name === 'AbortError') {
|
|
||||||
return { success: false, error: `Request timed out after ${timeout}ms` };
|
|
||||||
}
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "http_post",
|
|
||||||
description: "Send HTTP POST request. Supports JSON and form data.",
|
|
||||||
schema: z.object({
|
|
||||||
url: z.string().describe("Request URL"),
|
|
||||||
body: z.union([z.string(), z.object({})]).describe("Request body, can be string or JSON object"),
|
|
||||||
headers: z.record(z.string()).optional().describe("Request headers"),
|
|
||||||
contentType: z.enum(['json', 'form', 'text', 'raw']).optional().describe("Content type, default json"),
|
|
||||||
timeout: z.number().optional().describe("Timeout in milliseconds, default 30000")
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export { httpPostTool };
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { TavilySearch } from "@langchain/tavily";
|
|
||||||
import { tool } from "@langchain/core/tools";
|
|
||||||
import * as z from "zod"
|
|
||||||
import config from "../../../../conf.json" with { type: "json" };
|
|
||||||
|
|
||||||
const webSearchTool = tool(
|
|
||||||
async ({
|
|
||||||
query,
|
|
||||||
maxResults = 5,
|
|
||||||
topic = "general",
|
|
||||||
includeRawContent = false,
|
|
||||||
}) => {
|
|
||||||
const tavilySearch = new TavilySearch({
|
|
||||||
maxResults,
|
|
||||||
tavilyApiKey: config.agent.tavily.apiKey,
|
|
||||||
includeRawContent,
|
|
||||||
topic,
|
|
||||||
});
|
|
||||||
return await tavilySearch._call({ query });
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "internet_search",
|
|
||||||
description: "Run a web search",
|
|
||||||
schema: z.object({
|
|
||||||
query: z.string().describe("The search query"),
|
|
||||||
maxResults: z.number().optional().default(5),
|
|
||||||
topic: z
|
|
||||||
.enum(["general", "news", "finance"])
|
|
||||||
.optional()
|
|
||||||
.default("general"),
|
|
||||||
includeRawContent: z.boolean().optional().default(false),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export { webSearchTool };
|
|
||||||
Reference in New Issue
Block a user