diff --git a/README.md b/README.md index 83c598a74..049374cd7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # ProxyCast 🚀 -**把你的 AI 客户端额度用到任何地方** +**AI Agent 创作工具平台** [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Tauri](https://img.shields.io/badge/Tauri-2.0-blue.svg)](https://tauri.app/) @@ -13,219 +13,53 @@ --- -## 🤔 这个工具能帮你做什么? - -**场景一:换个更好用的 IDE** -> 我有 Kiro 账号,可以用 Claude 系列模型,但 Kiro IDE 不太顺手。我想用 Claude Code 或 Cursor 来写代码,但又不想额外付费买 API。 - -**场景二:把额度分享给其他工具** -> Claude Code 这个月额度还剩很多,与其浪费不如转给 Cherry Studio 聊天用,或者给我的 AI Agent 项目提供 API 接口。 - -**场景三:统一管理多个 AI 账号** -> 我有 Kiro、Gemini CLI、通义千问好几个账号,想统一管理,哪个有额度就用哪个。 - -**ProxyCast 就是解决这些问题的工具** —— 它把你已有的 AI 客户端凭证转换成标准 OpenAI API,让任何支持 OpenAI 接口的工具都能使用。 - ---- - -## 💡 工作原理 - -``` -你的 AI 客户端凭证 ProxyCast 任意 OpenAI 兼容工具 -┌─────────────────┐ ┌─────────────┐ ┌─────────────────────┐ -│ Kiro OAuth │ │ │ │ Claude Code │ -│ Gemini OAuth │ ───▶ │ 本地 API │ ───▶ │ Cherry Studio │ -│ 通义千问 OAuth │ │ 代理服务 │ │ Cursor / Cline │ -│ ... │ │ │ │ 你的 AI Agent │ -└─────────────────┘ └─────────────┘ └─────────────────────┘ -``` - -> **💡 与 AIClient-2-API 的区别** -> -> ProxyCast 是 [AIClient-2-API](https://github.com/justlovemaki/AIClient-2-API) 的桌面版本,提供更友好的图形界面和一键操作体验,无需命令行配置。 - ---- - ## ✨ 核心特性 -### 🎯 多 Provider 统一管理 -- **Kiro** - 通过 OAuth 使用 Claude 系列模型(Opus 4.5、Sonnet 4.5、Sonnet 4、Haiku 4.5) -- **Gemini CLI** - 通过 OAuth 使用 Gemini 模型 -- **Gemini API Key** - 多账号负载均衡,支持模型排除 -- **通义千问** - 通过 OAuth 使用 Qwen3 Coder Plus -- **Antigravity** - 通过 OAuth 使用 Claude 模型 -- **Vertex AI** - Google Cloud AI 平台,支持模型别名 -- **OpenAI 自定义** - 配置自定义 OpenAI 兼容 API -- **Claude 自定义** - 配置自定义 Claude API - -### 🖥️ 友好的图形界面 -- **Dashboard** - 服务状态监控、API 测试面板 -- **Provider 管理** - 一键加载凭证、Token 刷新、默认 Provider 切换 -- **设置页面** - 服务器配置、端口设置、API Key 管理 -- **日志查看** - 实时日志记录、操作追踪 - -### 🔄 智能凭证管理 -- 自动检测凭证文件变化(每 5 秒) -- 一键读取本地 OAuth 凭证 -- Token 过期自动刷新 -- 环境变量导出(.env 格式) -- **配额超限自动切换** - 自动切换到下一个可用凭证 -- **预览模型回退** - 主模型配额用尽时尝试预览版本 -- **Per-Key 代理** - 为每个凭证单独配置代理 - -### 🔐 安全与管理 -- **HTTPS 部署** - 当前版本不内置 TLS,请使用反向代理进行 HTTPS 终止 -- **远程管理 API** - 通过 API 远程管理配置和凭证 -- **访问控制** - 支持 localhost 限制和密钥认证 - -### 🔌 多路由支持 -- 支持 `/api/provider/{provider}/v1/*` 路由模式 -- 模型映射 - 将请求模型映射到 Provider 支持的模型 -- 管理端点代理 - 代理认证和账户功能 - -### 🌐 完整 API 兼容 -- `/v1/chat/completions` - OpenAI Chat API -- `/v1/models` - 模型列表 -- `/v1/messages` - Anthropic Messages API -- `/v1/messages/count_tokens` - Token 计数 -- `/health` - 健康检查 -- `/ready` - 就绪检查 -- `/api/provider/{provider}/v1/*` - Provider 路由 -- `/v0/management/*` - 远程管理 API - - `/v0/management/backup` - 触发数据库备份 - - `/v0/management/restore` - 从备份恢复 - ---- - -## 📸 界面截图 - -### 仪表盘 - 系统状态与监控 -![Dashboard](docs/images/943663ed-b17c-4b32-a74c-c0243ffb3dea.png) - -### 凭证池 - 多凭证管理与配额查询 -![Provider Pool](docs/images/aee62eb5-3aeb-4454-b14d-24b1d5f9a0fe.png) - -### 路由管理 - 智能路由规则和容错策略 -![Router](docs/images/067c7d64-e116-4a30-b533-748873166f37.png) - -### 配置管理 - 客户端配置切换 -![Config](docs/images/25eb018a-5be2-4f82-ba22-e68f39160cac.png) - -### 扩展 - MCP/Prompts/Skills 管理 -![Extensions](docs/images/ffc70018-aa5f-4738-883d-045614488608.png) - -### API Server - 服务控制与 API 测试 -![API Server](docs/images/151b4355-821c-4bda-a731-c4367b6b8716.png) - -### 设置 - 应用参数和偏好 -![Settings](docs/images/c7d8236b-ea6c-4496-ada5-288cd0a01738.png) +- **多 Provider 统一管理** - 支持 Kiro、Gemini、通义千问、Antigravity、Vertex AI 等多种 AI 服务 +- **智能凭证管理** - 自动检测凭证变化、Token 自动刷新、配额超限自动切换 +- **完整 API 兼容** - 支持 OpenAI Chat API 和 Anthropic Messages API +- **友好图形界面** - Dashboard 监控、Provider 管理、日志查看 --- ## 🚀 快速开始 -### 下载安装 +### 安装 -#### macOS (推荐使用 Homebrew) +#### macOS (Homebrew) ```bash brew tap aiclientproxy/tap brew install --cask proxycast ``` -更新版本: -```bash -brew upgrade --cask proxycast -``` - #### 手动下载 -从 [Releases](https://github.com/aiclientproxy/proxycast/releases) 页面下载对应平台的安装包: +从 [Releases](https://github.com/aiclientproxy/proxycast/releases) 下载对应平台安装包。 -- **macOS (Apple Silicon)**: `proxycast_x.x.x_aarch64.dmg` -- **macOS (Intel)**: `proxycast_x.x.x_x64.dmg` -- **Windows (x64)**: `proxycast_x.x.x_x64-setup.exe` -- **Ubuntu/Debian (x64)**: `proxycast_x.x.x_amd64.deb` +### 使用 -### 使用步骤 - -1. **启动应用** - 打开 ProxyCast -2. **加载凭证** - 进入 Provider 管理页面,点击"一键读取凭证" -3. **启动服务** - 在 Dashboard 点击"启动服务器" -4. **配置客户端** - 在 Cherry-Studio、Cline 等工具中配置: +1. 启动 ProxyCast +2. 加载凭证 - Provider 管理页面点击"一键读取凭证" +3. 启动服务 - Dashboard 点击"启动服务器" +4. 配置客户端: ``` API Base URL: http://localhost:8999/v1 - API Key: 启动时自动生成的密钥(可在设置页查看/修改) + API Key: 启动时自动生成(设置页查看) ``` --- -## 🧰 运维提示 - -- **自动备份**:数据库默认每天自动备份到 `~/.proxycast/backups/`,保留 7 天。 -- **配置备份**:每次写入配置会生成 `config.yaml.backup` 以便回滚。 -- **日志归档**:7 天游离线日志自动压缩,30 天前压缩日志自动清理。 -- **生产 HTTPS**:当前版本不内置 TLS,生产环境需反向代理终止 HTTPS。 - ---- - -## 🔧 API 使用示例 - -### OpenAI Chat Completions - -```bash -curl http://localhost:8999/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-api-key" \ - -d '{ - "model": "claude-sonnet-4-5-20250514", - "messages": [ - {"role": "user", "content": "Hello!"} - ], - "stream": true - }' -``` - -### Anthropic Messages API - -```bash -curl http://localhost:8999/v1/messages \ - -H "Content-Type: application/json" \ - -H "x-api-key: your-api-key" \ - -H "anthropic-version: 2023-06-01" \ - -d '{ - "model": "claude-sonnet-4-5-20250514", - "max_tokens": 1024, - "messages": [ - {"role": "user", "content": "Hello!"} - ] - }' -``` - ---- - ## 🛠️ 开发构建 -### 环境要求 - -- Node.js >= 20.0.0 -- Rust >= 1.70 -- pnpm 或 npm - -### 本地开发 - ```bash # 安装依赖 npm install -# 启动开发服务器 +# 开发模式 npm run tauri dev -``` -### 构建发布 - -```bash -# 构建生产版本 +# 构建发布 npm run tauri build ``` @@ -233,23 +67,8 @@ npm run tauri build ## 📄 开源协议 -本项目采用 [GNU General Public License v3 (GPLv3)](https://www.gnu.org/licenses/gpl-3.0) 协议开源。 - -## 🙏 致谢 - -- [AIClient-2-API](https://github.com/justlovemaki/AIClient-2-API) - 核心逻辑参考 -- [Tauri](https://tauri.app/) - 跨平台桌面框架 -- [shadcn/ui](https://ui.shadcn.com/) - UI 组件库 - ---- +[GNU General Public License v3 (GPLv3)](https://www.gnu.org/licenses/gpl-3.0) ## ⚠️ 免责声明 -### 使用风险提示 -本项目(ProxyCast)仅供学习和研究使用。用户在使用本项目时需自行承担所有风险。作者不对因使用本项目而导致的任何直接、间接或后果性损失负责。 - -### 第三方服务责任声明 -本项目是一个 API 代理工具,不提供任何 AI 模型服务。所有 AI 模型服务均由各自的第三方提供商(如 Google、Anthropic、阿里云等)提供。用户在通过本项目访问这些服务时,应遵守各第三方服务的使用条款和政策。 - -### 数据隐私声明 -本项目在本地运行,不收集或上传任何用户数据。但用户在使用本项目时应保护好自己的 API 密钥和其他敏感信息。 +本项目仅供学习研究使用,用户需自行承担使用风险。本项目不提供 AI 模型服务,所有服务由第三方提供商提供。 diff --git a/package.json b/package.json index 4c70a5a28..536457c98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "proxycast", "private": true, - "version": "0.45.2", + "version": "0.46.0", "type": "module", "repository": { "type": "git", diff --git a/scripts/playwright/browser-tool.mjs b/scripts/playwright/browser-tool.mjs new file mode 100644 index 000000000..e651a492c --- /dev/null +++ b/scripts/playwright/browser-tool.mjs @@ -0,0 +1,351 @@ +#!/usr/bin/env node +/** + * Playwright Browser Tool for AI Agent + * + * 提供浏览器自动化功能,专为 AI Agent 设计 + * 输出结构化的可访问性树,便于 AI 理解页面结构 + */ + +import { chromium } from 'playwright'; + +// 全局浏览器实例(保持会话) +let browser = null; +let context = null; +let page = null; + +// 元素引用映射 +let elementRefs = new Map(); +let refCounter = 0; + +/** + * 初始化浏览器 + */ +async function initBrowser(headless = true) { + if (!browser) { + browser = await chromium.launch({ + headless, + args: ['--no-sandbox', '--disable-setuid-sandbox'] + }); + context = await browser.newContext({ + viewport: { width: 1280, height: 720 }, + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + }); + page = await context.newPage(); + } + return { browser, context, page }; +} + +/** + * 获取页面可访问性树快照 + * 为每个元素生成唯一引用 ID(如 @e1, @e2) + */ +async function getSnapshot(interactiveOnly = false) { + if (!page) throw new Error('浏览器未初始化,请先执行 open 操作'); + + // 清空之前的引用 + elementRefs.clear(); + refCounter = 0; + + // 获取可访问性树 + const snapshot = await page.accessibility.snapshot({ interestingOnly: interactiveOnly }); + + if (!snapshot) { + return { tree: '(empty page)', refs: {} }; + } + + // 递归处理节点,添加引用 ID + function processNode(node, depth = 0) { + const indent = ' '.repeat(depth); + const ref = `@e${++refCounter}`; + + // 存储元素引用(通过角色和名称定位) + elementRefs.set(ref, { + role: node.role, + name: node.name, + // 构建选择器 + selector: buildSelector(node) + }); + + let line = `${indent}${ref} [${node.role}]`; + + if (node.name) { + line += ` "${node.name}"`; + } + if (node.value) { + line += ` value="${node.value}"`; + } + if (node.checked !== undefined) { + line += ` checked=${node.checked}`; + } + if (node.pressed !== undefined) { + line += ` pressed=${node.pressed}`; + } + if (node.selected !== undefined) { + line += ` selected=${node.selected}`; + } + if (node.disabled) { + line += ` (disabled)`; + } + + let result = line + '\n'; + + if (node.children) { + for (const child of node.children) { + result += processNode(child, depth + 1); + } + } + + return result; + } + + function buildSelector(node) { + // 优先使用 aria-label 或 role + name 组合 + if (node.name) { + const role = node.role.toLowerCase(); + // 尝试多种选择器策略 + return `role=${role}[name="${node.name}"]`; + } + return null; + } + + const tree = processNode(snapshot); + + // 转换 Map 为普通对象 + const refs = {}; + for (const [key, value] of elementRefs) { + refs[key] = value; + } + + return { tree, refs }; +} + +/** + * 解析选择器(支持 @e1 格式的引用) + */ +function resolveSelector(selector) { + if (selector.startsWith('@e')) { + const ref = elementRefs.get(selector); + if (!ref) { + throw new Error(`未找到元素引用: ${selector},请先执行 snapshot 获取最新的元素引用`); + } + return ref.selector || `text="${ref.name}"`; + } + return selector; +} + +/** + * 执行浏览器操作 + */ +async function executeAction(action, headless = true) { + const result = { + success: true, + output: '', + url: null, + title: null, + screenshot: null, + error: null + }; + + try { + switch (action.open?.url ? 'open' : Object.keys(action)[0]) { + case 'open': { + const url = action.open?.url || action.url; + await initBrowser(headless); + await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); + result.url = page.url(); + result.title = await page.title(); + result.output = `已打开页面: ${result.title}`; + break; + } + + case 'snapshot': { + if (!page) throw new Error('浏览器未初始化'); + const interactiveOnly = action.snapshot?.interactive_only || action.interactive_only || false; + const { tree, refs } = await getSnapshot(interactiveOnly); + result.url = page.url(); + result.title = await page.title(); + result.output = `页面可访问性树:\n\n${tree}\n\n共 ${Object.keys(refs).length} 个元素`; + break; + } + + case 'click': { + if (!page) throw new Error('浏览器未初始化'); + const selector = resolveSelector(action.click?.selector || action.selector); + await page.click(selector, { timeout: 5000 }); + result.output = `已点击元素: ${action.click?.selector || action.selector}`; + break; + } + + case 'fill': { + if (!page) throw new Error('浏览器未初始化'); + const selector = resolveSelector(action.fill?.selector || action.selector); + const value = action.fill?.value || action.value; + await page.fill(selector, value, { timeout: 5000 }); + result.output = `已填充表单: ${action.fill?.selector || action.selector} = "${value}"`; + break; + } + + case 'type': { + if (!page) throw new Error('浏览器未初始化'); + const selector = resolveSelector(action.type?.selector || action.selector); + const text = action.type?.text || action.text; + await page.type(selector, text, { delay: 50 }); + result.output = `已输入文本: "${text}"`; + break; + } + + case 'press': { + if (!page) throw new Error('浏览器未初始化'); + const key = action.press?.key || action.key; + await page.keyboard.press(key); + result.output = `已按键: ${key}`; + break; + } + + case 'scroll': { + if (!page) throw new Error('浏览器未初始化'); + const direction = action.scroll?.direction || action.direction || 'down'; + const amount = action.scroll?.amount || action.amount || 500; + + let deltaX = 0, deltaY = 0; + switch (direction) { + case 'up': deltaY = -amount; break; + case 'down': deltaY = amount; break; + case 'left': deltaX = -amount; break; + case 'right': deltaX = amount; break; + } + + await page.mouse.wheel(deltaX, deltaY); + result.output = `已滚动: ${direction} ${amount}px`; + break; + } + + case 'wait_for': { + if (!page) throw new Error('浏览器未初始化'); + const selector = resolveSelector(action.wait_for?.selector || action.selector); + const timeoutMs = action.wait_for?.timeout_ms || action.timeout_ms || 5000; + await page.waitForSelector(selector, { timeout: timeoutMs }); + result.output = `元素已出现: ${action.wait_for?.selector || action.selector}`; + break; + } + + case 'screenshot': { + if (!page) throw new Error('浏览器未初始化'); + const fullPage = action.screenshot?.full_page || action.full_page || false; + const path = action.screenshot?.path || action.path; + + const options = { fullPage }; + if (path) { + options.path = path; + await page.screenshot(options); + result.output = `截图已保存: ${path}`; + } else { + const buffer = await page.screenshot(options); + result.screenshot = buffer.toString('base64'); + result.output = '截图已生成(base64)'; + } + break; + } + + case 'get_text': { + if (!page) throw new Error('浏览器未初始化'); + const selector = action.get_text?.selector || action.selector; + + let text; + if (selector) { + const resolved = resolveSelector(selector); + text = await page.textContent(resolved); + } else { + text = await page.evaluate(() => document.body.innerText); + } + + result.output = text || '(empty)'; + break; + } + + case 'evaluate': { + if (!page) throw new Error('浏览器未初始化'); + const script = action.evaluate?.script || action.script; + const evalResult = await page.evaluate(script); + result.output = JSON.stringify(evalResult, null, 2); + break; + } + + case 'close': { + if (browser) { + await browser.close(); + browser = null; + context = null; + page = null; + elementRefs.clear(); + } + result.output = '浏览器已关闭'; + break; + } + + default: + throw new Error(`未知操作: ${JSON.stringify(action)}`); + } + } catch (error) { + result.success = false; + result.error = error.message; + result.output = `操作失败: ${error.message}`; + } + + return result; +} + +/** + * 主函数 + */ +async function main() { + const args = process.argv.slice(2); + + let actionJson = null; + let headless = true; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--action' && args[i + 1]) { + actionJson = args[i + 1]; + i++; + } else if (args[i] === '--headless') { + headless = true; + } else if (args[i] === '--no-headless') { + headless = false; + } + } + + if (!actionJson) { + console.error('Usage: browser-tool.mjs --action [--headless|--no-headless]'); + process.exit(1); + } + + try { + const action = JSON.parse(actionJson); + const result = await executeAction(action, headless); + console.log(JSON.stringify(result)); + } catch (error) { + console.log(JSON.stringify({ + success: false, + output: '', + error: error.message + })); + process.exit(1); + } +} + +// 处理进程退出 +process.on('exit', async () => { + if (browser) { + await browser.close(); + } +}); + +process.on('SIGINT', async () => { + if (browser) { + await browser.close(); + } + process.exit(0); +}); + +main(); diff --git a/scripts/playwright/package.json b/scripts/playwright/package.json new file mode 100644 index 000000000..eb24182b0 --- /dev/null +++ b/scripts/playwright/package.json @@ -0,0 +1,13 @@ +{ + "name": "proxycast-playwright-tools", + "version": "1.0.0", + "description": "Playwright browser automation tools for ProxyCast AI Agent", + "type": "module", + "scripts": { + "test": "node browser-tool.mjs --action '{\"open\":{\"url\":\"https://example.com\"}}' --no-headless", + "install-browsers": "npx playwright install chromium" + }, + "dependencies": { + "playwright": "^1.40.0" + } +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 21de5283e..8f95a8631 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -26,7 +26,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -40,6 +42,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -55,6 +66,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -64,6 +81,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -100,24 +167,102 @@ dependencies = [ ] [[package]] -name = "ashpd" -version = "0.11.0" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "aster" +version = "0.3.0" dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.2", - "raw-window-handle", + "ahash", + "anyhow", + "async-stream", + "async-trait", + "aws-config", + "aws-sdk-bedrockruntime", + "aws-sdk-sagemakerruntime", + "aws-smithy-types", + "axum 0.8.8", + "base64 0.21.7", + "blake3", + "boa_engine", + "boa_gc", + "chrono", + "clap", + "dashmap 6.1.0", + "dirs 5.0.1", + "etcetera 0.11.0", + "fs2", + "futures", + "glob", + "hex", + "hostname", + "ignore", + "include_dir", + "indexmap 2.13.0", + "indoc", + "insta", + "jsonschema", + "jsonwebtoken", + "keyring", + "lazy_static", + "lru", + "minijinja", + "nanoid", + "notify 8.2.0", + "oauth2", + "once_cell", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry_sdk", + "parking_lot", + "paste", + "posthog-rs", + "rand 0.8.5", + "regex", + "reqwest 0.12.28", + "rmcp", + "schemars 1.2.0", "serde", - "serde_repr", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "sha2", + "shellexpand", + "sqlx", + "sys-info", + "tempfile", + "thiserror 1.0.69", + "tiktoken-rs", "tokio", + "tokio-cron-scheduler", + "tokio-stream", + "tokio-tungstenite 0.28.0", + "tokio-util", + "tonic", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "unbinder", + "unicode-normalization", "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", + "urlencoding", + "utoipa", + "uuid", + "webbrowser", + "which", + "winapi", + "winreg 0.55.0", + "zip", ] [[package]] @@ -225,7 +370,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -265,7 +410,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -282,7 +427,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -308,6 +453,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -331,6 +485,48 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.15.2" @@ -353,6 +549,361 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "aws-runtime" +version = "1.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81b5b2898f6798ad58f484856768bca817e3cd9de0974c24ae0f1113fe88f1b" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-bedrockruntime" +version = "1.120.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b8dcf42378ab2d5accac1652cdd059114fb071baf53250ceafb76fcdde347f" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "hyper 0.14.32", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sagemakerruntime" +version = "1.93.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a09d698fec856412ca584d70416ebf9575072102d44ab71321792c48a585462" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.91.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee6402a36f27b52fe67661c6732d684b2635152b676aa2babbfb5204f99115d" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.93.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45a7f750bbd170ee3677671ad782d90b894548f4e4ae168302c57ec9de5cb3e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.95.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55542378e419558e6b1f398ca70adb0b2088077e79ad9f14eb09441f2f7b2164" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12f8b310e38cad85cf3bef45ad236f470717393c613266ce0a89512286b650" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f616c3f2260612fe44cede278bafa18e73e6479c4e393e2c4518cf2a9a228a" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a392db6c583ea4a912538afb86b7be7c5d8887d91604f50eb55c262ee1b4a5f5" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0d43d899f9e508300e587bf582ba54c27a452dd0a9ea294690669138ae14a2" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905cb13a9895626d49cf2ced759b062d913834c7482c38e49557eac4e6193f01" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "axum" version = "0.7.9" @@ -360,17 +911,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.4.5", "base64 0.22.1", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -381,10 +932,43 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", - "tokio-tungstenite", - "tower 0.5.2", + "tokio-tungstenite 0.24.0", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -399,13 +983,32 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -420,16 +1023,16 @@ dependencies = [ "arc-swap", "bytes", "fs-err", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", "hyper-util", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.23.36", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", ] @@ -446,10 +1049,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.8.2" +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bit-set" @@ -496,6 +1109,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq 0.4.2", + "cpufeatures", +] + [[package]] name = "block" version = "0.1.6" @@ -533,6 +1160,153 @@ dependencies = [ "piper", ] +[[package]] +name = "boa_ast" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc119a5ad34c3f459062a96907f53358989b173d104258891bb74f95d93747e8" +dependencies = [ + "bitflags 2.10.0", + "boa_interner", + "boa_macros", + "boa_string", + "indexmap 2.13.0", + "num-bigint", + "rustc-hash 2.1.1", +] + +[[package]] +name = "boa_engine" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e637ec52ea66d76b0ca86180c259d6c7bb6e6a6e14b2f36b85099306d8b00cc3" +dependencies = [ + "aligned-vec", + "arrayvec", + "bitflags 2.10.0", + "boa_ast", + "boa_gc", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_string", + "bytemuck", + "cfg-if", + "cow-utils", + "dashmap 6.1.0", + "dynify", + "fast-float2", + "float16", + "futures-channel", + "futures-concurrency", + "futures-lite", + "hashbrown 0.16.1", + "icu_normalizer", + "indexmap 2.13.0", + "intrusive-collections", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "paste", + "portable-atomic", + "rand 0.9.2", + "regress", + "rustc-hash 2.1.1", + "ryu-js", + "serde", + "serde_json", + "small_btree", + "static_assertions", + "tag_ptr", + "tap", + "thin-vec", + "thiserror 2.0.17", + "time", + "xsum", +] + +[[package]] +name = "boa_gc" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1179f690cbfcbe5364cceee5f1cb577265bb6f07b0be6f210aabe270adcf9da" +dependencies = [ + "boa_macros", + "boa_string", + "hashbrown 0.16.1", + "thin-vec", +] + +[[package]] +name = "boa_interner" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9626505d33dc63d349662437297df1d3afd9d5fc4a2b3ad34e5e1ce879a78848" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "once_cell", + "phf 0.13.1", + "rustc-hash 2.1.1", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f36418a46544b152632c141b0a0b7a453cd69ca150caeef83aee9e2f4b48b7d" +dependencies = [ + "cfg-if", + "cow-utils", + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "boa_parser" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f99bf5b684f0de946378fcfe5f38c3a0fbd51cbf83a0f39ff773a0e218541f" +dependencies = [ + "bitflags 2.10.0", + "boa_ast", + "boa_interner", + "boa_macros", + "fast-float2", + "icu_properties", + "num-bigint", + "num-traits", + "regress", + "rustc-hash 2.1.1", +] + +[[package]] +name = "boa_string" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ce9d7aa5563a2e14eab111e2ae1a06a69a812f6c0c3d843196c9d03fbef440" +dependencies = [ + "fast-float2", + "itoa", + "paste", + "rustc-hash 2.1.1", + "ryu-js", + "static_assertions", +] + +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "brotli" version = "8.0.2" @@ -571,11 +1345,31 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] name = "byteorder" @@ -598,6 +1392,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -669,7 +1473,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.27", "serde", "serde_json", "thiserror 2.0.17", @@ -682,14 +1486,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] name = "cc" -version = "1.2.51" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -760,6 +1564,46 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + [[package]] name = "clipboard-win" version = "5.4.1" @@ -807,6 +1651,12 @@ dependencies = [ "objc", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -827,6 +1677,8 @@ dependencies = [ "compression-core", "flate2", "memchr", + "zstd 0.13.3", + "zstd-safe 7.2.4", ] [[package]] @@ -844,6 +1696,24 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-random" version = "0.1.18" @@ -859,7 +1729,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] @@ -870,6 +1740,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.4.0" @@ -882,10 +1758,29 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -960,6 +1855,12 @@ dependencies = [ "libc", ] +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -969,6 +1870,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -978,6 +1894,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "croner" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c344b0690c1ad1c7176fe18eb173e0c927008fdaaa256e40dfd43ddd149c0843" +dependencies = [ + "chrono", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1006,6 +1931,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1052,7 +1986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -1062,7 +1996,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -1071,8 +2015,32 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] @@ -1086,7 +2054,31 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.114", ] [[package]] @@ -1095,9 +2087,20 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.114", ] [[package]] @@ -1114,10 +2117,57 @@ dependencies = [ ] [[package]] -name = "data-encoding" -version = "2.9.0" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "dbus" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "dbus", + "openssl", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] [[package]] name = "deranged" @@ -1129,6 +2179,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.114", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1138,8 +2219,8 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.113", + "rustc_version 0.4.1", + "syn 2.0.114", ] [[package]] @@ -1149,6 +2230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1241,16 +2323,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.9", + "syn 2.0.114", ] [[package]] @@ -1273,7 +2346,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -1285,6 +2358,21 @@ dependencies = [ "const-random", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1327,11 +2415,43 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dynify" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81acb15628a3e22358bf73de5e7e62360b8a777dbcb5fc9ac7dfa9ae73723747" +dependencies = [ + "dynify-macros", +] + +[[package]] +name = "dynify-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] [[package]] name = "embed-resource" @@ -1341,8 +2461,8 @@ checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" dependencies = [ "cc", "memchr", - "rustc_version", - "toml 0.9.10+spec-1.1.0", + "rustc_version 0.4.1", + "toml 0.9.11+spec-1.1.0", "vswhom", "winreg 0.55.0", ] @@ -1353,6 +2473,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1386,7 +2512,33 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -1422,6 +2574,27 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -1466,6 +2639,23 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set 0.8.0", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + [[package]] name = "fastrand" version = "2.3.0" @@ -1489,7 +2679,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -1508,7 +2698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset 0.9.1", - "rustc_version", + "rustc_version 0.4.1", ] [[package]] @@ -1536,26 +2726,76 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "float16" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bffafbd079d520191c7c2779ae9cf757601266cf4167d3f659ff09617ff8483" +dependencies = [ + "cfg-if", + "rustc_version 0.2.3", +] + +[[package]] +name = "fluent-uri" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1583,7 +2823,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -1607,6 +2847,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + [[package]] name = "fs-err" version = "3.2.2" @@ -1677,6 +2927,19 @@ dependencies = [ "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a9561702beff46b705a8ac9c0803ec4c7fc5d01330a99b1feaf86e206e92ba" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -1694,6 +2957,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1721,7 +2995,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -1895,13 +3169,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1911,9 +3187,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1982,7 +3260,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -2019,6 +3297,19 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -2079,7 +3370,26 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -2093,8 +3403,8 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", - "indexmap 2.12.1", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -2125,6 +3435,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", ] [[package]] @@ -2132,6 +3454,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "hashlink" @@ -2166,6 +3493,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2175,6 +3511,26 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -2187,6 +3543,17 @@ dependencies = [ "match_token", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -2197,6 +3564,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -2204,7 +3582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -2215,8 +3593,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -2232,6 +3610,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.8.1" @@ -2242,9 +3644,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -2255,19 +3657,49 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.36", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.5", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", "tower-service", ] @@ -2279,7 +3711,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -2298,15 +3730,15 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", - "system-configuration", + "socket2 0.6.1", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", @@ -2349,9 +3781,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", @@ -2368,6 +3800,7 @@ checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", @@ -2375,43 +3808,48 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "8b24a59706036ba941c9476a55cd57b82b77f38a3c667d637ee7cabbc85eaedc" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "f5a97b8ac6235e69506e8dacfb2adf38461d2ce6d3e9bd9c94c4cbc3cd4400a4" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", + "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -2421,6 +3859,8 @@ checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -2465,6 +3905,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.9" @@ -2479,6 +3935,25 @@ dependencies = [ "tiff", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2492,9 +3967,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2502,6 +3977,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "infer" version = "0.19.0" @@ -2522,6 +4006,17 @@ dependencies = [ "libc", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.10.0", + "inotify-sys", + "libc", +] + [[package]] name = "inotify-sys" version = "0.1.5" @@ -2540,6 +4035,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "insta" +version = "1.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset 0.9.1", +] + [[package]] name = "ioctl-rs" version = "0.1.6" @@ -2584,6 +4100,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -2677,6 +4208,48 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonschema" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d" +dependencies = [ + "ahash", + "base64 0.22.1", + "bytecount", + "email_address", + "fancy-regex 0.14.0", + "fraction", + "idna", + "itoa", + "num-cmp", + "num-traits", + "once_cell", + "percent-encoding", + "referencing", + "regex", + "regex-syntax", + "reqwest 0.12.28", + "serde", + "serde_json", + "uuid-simd", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "keyboard-types" version = "0.7.0" @@ -2688,6 +4261,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "log", + "openssl", + "security-framework 2.11.1", + "security-framework 3.5.1", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -2716,7 +4305,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 2.12.1", + "indexmap 2.13.0", "selectors", ] @@ -2725,6 +4314,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libappindicator" @@ -2746,15 +4338,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading 0.7.4", + "libloading", "once_cell", ] [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "cc", + "pkg-config", +] [[package]] name = "libloading" @@ -2767,14 +4369,10 @@ dependencies = [ ] [[package]] -name = "libloading" -version = "0.8.9" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -2836,6 +4434,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2851,6 +4455,21 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -2888,7 +4507,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -2903,6 +4522,22 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "md5" version = "0.7.0" @@ -2915,6 +4550,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + [[package]] name = "memoffset" version = "0.6.5" @@ -2939,6 +4580,23 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minijinja" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" +dependencies = [ + "memo-map", + "self_cell", + "serde", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2968,6 +4626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -3014,6 +4673,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -3023,10 +4691,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3091,7 +4759,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset 0.9.1", ] [[package]] @@ -3100,6 +4767,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "6.1.1" @@ -3109,7 +4786,7 @@ dependencies = [ "bitflags 2.10.0", "filetime", "fsevent-sys", - "inotify", + "inotify 0.9.6", "kqueue", "libc", "log", @@ -3118,6 +4795,30 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.10.0", + "fsevent-sys", + "inotify 0.11.0", + "kqueue", + "libc", + "log", + "mio 1.1.1", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "ntapi" version = "0.4.2" @@ -3136,12 +4837,110 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3149,6 +4948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3170,7 +4970,36 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.17", + "http 1.4.0", + "rand 0.8.5", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", ] [[package]] @@ -3400,6 +5229,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "open" version = "5.3.3" @@ -3435,7 +5270,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -3444,6 +5279,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-src" version = "300.5.4+3.5.4" @@ -3466,6 +5307,99 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5feffc321035ad94088a7e5333abb4d84a8726e54a802e736ce9dd7237e85b" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.0", + "opentelemetry", + "reqwest 0.12.28", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" +dependencies = [ + "async-trait", + "futures-core", + "http 1.4.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.12.28", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3502,6 +5436,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "pango" version = "0.18.3" @@ -3567,6 +5507,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -3585,6 +5537,25 @@ dependencies = [ "sha2", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3621,6 +5592,16 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", +] + [[package]] name = "phf_codegen" version = "0.8.0" @@ -3671,6 +5652,16 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + [[package]] name = "phf_macros" version = "0.10.0" @@ -3695,7 +5686,20 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -3725,6 +5729,35 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -3748,6 +5781,27 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -3761,7 +5815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.12.1", + "indexmap 2.13.0", "quick-xml", "serde", "time", @@ -3807,6 +5861,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + [[package]] name = "portable-pty" version = "0.8.1" @@ -3828,6 +5888,21 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "posthog-rs" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610d236077c94c96194b0d2b40989bda4bf8faf60e15a0da6ef8ffd4c78f6c1c" +dependencies = [ + "chrono", + "derive_builder", + "reqwest 0.11.27", + "semver 1.0.27", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -3926,6 +6001,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "process-wrap" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5fd83ab7fa55fd06f5e665e3fc52b8bca451c0486b8ea60ad649cd1c10a5da" +dependencies = [ + "futures", + "indexmap 2.13.0", + "nix 0.30.1", + "tokio", + "tracing", + "windows 0.61.3", +] + [[package]] name = "proptest" version = "1.9.0" @@ -3945,21 +6034,45 @@ dependencies = [ "unarray", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "proxycast" -version = "0.45.2" +version = "0.46.0" dependencies = [ "anyhow", "arboard", + "aster", "async-stream", "async-trait", - "axum", + "axum 0.7.9", "axum-server", "base64 0.22.1", "bytes", "chrono", "cocoa", - "dashmap", + "dashmap 5.5.3", "dirs 5.0.1", "flate2", "fs2", @@ -3967,10 +6080,10 @@ dependencies = [ "glob", "hex", "if-addrs", - "indexmap 2.12.1", + "indexmap 2.13.0", "md5", "mouse_position", - "notify", + "notify 6.1.1", "objc", "once_cell", "open", @@ -3980,9 +6093,9 @@ dependencies = [ "proptest", "rand 0.8.5", "regex", - "reqwest", + "reqwest 0.12.28", "rusqlite", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "scopeguard", "serde", "serde_json", @@ -4021,6 +6134,22 @@ dependencies = [ "zip", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "pxfm" version = "0.1.27" @@ -4051,6 +6180,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.36", + "socket2 0.6.1", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.36", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.1", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.43" @@ -4098,7 +6282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4128,7 +6312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4146,14 +6330,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -4182,7 +6366,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4235,7 +6419,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] @@ -4246,7 +6430,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -4268,7 +6452,21 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "referencing" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e" +dependencies = [ + "ahash", + "fluent-uri", + "once_cell", + "parking_lot", + "percent-encoding", + "serde_json", ] [[package]] @@ -4294,12 +6492,69 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "regress" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" +dependencies = [ + "hashbrown 0.16.1", + "memchr", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.28" @@ -4308,15 +6563,18 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "cookie", + "cookie_store", "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.8.1", + "hyper-rustls 0.27.7", "hyper-tls", "hyper-util", "js-sys", @@ -4325,15 +6583,19 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.36", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.4", "tokio-util", - "tower 0.5.2", + "tower 0.5.3", "tower-http 0.6.8", "tower-service", "url", @@ -4341,15 +6603,15 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 1.0.5", ] [[package]] name = "rfd" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" dependencies = [ - "ashpd", "block2", "dispatch2", "glib-sys", @@ -4365,7 +6627,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4376,12 +6638,74 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] +[[package]] +name = "rmcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa" +dependencies = [ + "async-trait", + "base64 0.22.1", + "chrono", + "futures", + "http 1.4.0", + "oauth2", + "pastey", + "pin-project-lite", + "process-wrap", + "reqwest 0.12.28", + "rmcp-macros", + "schemars 1.2.0", + "serde", + "serde_json", + "sse-stream", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "rmcp-macros" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.114", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rusqlite" version = "0.31.0" @@ -4412,13 +6736,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", ] [[package]] @@ -4434,6 +6773,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.36" @@ -4442,12 +6793,34 @@ checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "once_cell", + "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.0", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -4463,9 +6836,20 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.8" @@ -4502,6 +6886,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + [[package]] name = "same-file" version = "1.0.6" @@ -4528,7 +6918,7 @@ checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap 1.9.3", - "schemars_derive", + "schemars_derive 0.8.22", "serde", "serde_json", "url", @@ -4553,8 +6943,10 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ + "chrono", "dyn-clone", "ref-cast", + "schemars_derive 1.2.0", "serde", "serde_json", ] @@ -4568,14 +6960,20 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] -name = "scoped-tls" -version = "1.0.1" +name = "schemars_derive" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.114", +] [[package]] name = "scopeguard" @@ -4583,6 +6981,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -4596,6 +7004,19 @@ dependencies = [ "security-framework-sys", ] +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" version = "2.15.0" @@ -4624,6 +7045,21 @@ dependencies = [ "smallvec", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" @@ -4634,6 +7070,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.228" @@ -4673,7 +7115,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -4684,14 +7126,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -4719,7 +7161,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -4762,7 +7204,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.13.0", "schemars 0.9.0", "schemars 1.2.0", "serde_core", @@ -4777,10 +7219,10 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -4789,7 +7231,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -4857,7 +7299,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -4928,6 +7370,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +[[package]] +name = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "dirs 6.0.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -4965,12 +7416,40 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.17", + "time", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4989,11 +7468,33 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "small_btree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba60d2df92ba73864714808ca68c059734853e6ab722b40e1cf543ebb3a057a" +dependencies = [ + "arrayvec", +] + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "socket2" @@ -5053,6 +7554,249 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap 2.13.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.25.4", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.114", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.114", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera 0.8.0", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "sse-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" +dependencies = [ + "bytes", + "futures-util", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "ssh2" version = "0.9.5" @@ -5102,6 +7846,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -5138,15 +7893,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.113" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -5164,7 +7925,17 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "sys-info" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +dependencies = [ + "cc", + "libc", ] [[package]] @@ -5178,7 +7949,18 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows 0.56.0", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", ] [[package]] @@ -5189,7 +7971,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.10.0", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -5215,6 +8007,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tag_ptr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05" + [[package]] name = "tao" version = "0.34.5" @@ -5263,9 +8061,15 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.44" @@ -5299,7 +8103,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http", + "http 1.4.0", "image", "jni", "libc", @@ -5314,7 +8118,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "serde_repr", @@ -5348,12 +8152,12 @@ dependencies = [ "heck 0.5.0", "json-patch", "schemars 0.8.22", - "semver", + "semver 1.0.27", "serde", "serde_json", "tauri-utils", "tauri-winres", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "walkdir", ] @@ -5371,11 +8175,11 @@ dependencies = [ "png 0.17.16", "proc-macro2", "quote", - "semver", + "semver 1.0.27", "serde", "serde_json", "sha2", - "syn 2.0.113", + "syn 2.0.114", "tauri-utils", "thiserror 2.0.17", "time", @@ -5393,7 +8197,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "tauri-codegen", "tauri-utils", ] @@ -5411,7 +8215,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "walkdir", ] @@ -5431,9 +8235,9 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.4.5" +version = "2.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73" +checksum = "444b091f24f2f6bdb4a305b54d3961f629c11861c685aceeea9a1972f89e43d5" dependencies = [ "dunce", "plist", @@ -5452,9 +8256,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" +checksum = "05416b57601eca8666b5ec4186f5b1dc826ed35263b4797ad6641e58da6bc6c3" dependencies = [ "log", "raw-window-handle", @@ -5470,9 +8274,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.4" +version = "2.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" dependencies = [ "anyhow", "dunce", @@ -5486,7 +8290,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.17", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "url", ] @@ -5507,9 +8311,9 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c" +checksum = "39b76f884a3937e04b631ffdc3be506088fa979369d25147361352f2f352e5ed" dependencies = [ "encoding_rs", "log", @@ -5528,9 +8332,9 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710" +checksum = "acba6b5ca527a96cdfcc96ae09b09ccb91ddff5e33978ca6873b96ea16bb404c" dependencies = [ "serde", "serde_json", @@ -5550,7 +8354,7 @@ dependencies = [ "cookie", "dpi", "gtk", - "http", + "http 1.4.0", "jni", "objc2", "objc2-ui-kit", @@ -5573,7 +8377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" dependencies = [ "gtk", - "http", + "http 1.4.0", "jni", "log", "objc2", @@ -5606,7 +8410,7 @@ dependencies = [ "dunce", "glob", "html5ever", - "http", + "http 1.4.0", "infer", "json-patch", "kuchikiki", @@ -5617,14 +8421,14 @@ dependencies = [ "quote", "regex", "schemars 0.8.22", - "semver", + "semver 1.0.27", "serde", "serde-untagged", "serde_json", "serde_with", "swift-rs", "thiserror 2.0.17", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "url", "urlpattern", "uuid", @@ -5639,7 +8443,7 @@ checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" dependencies = [ "dunce", "embed-resource", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -5675,6 +8479,12 @@ dependencies = [ "libc", ] +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + [[package]] name = "thiserror" version = "1.0.69" @@ -5701,7 +8511,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -5712,7 +8522,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -5747,39 +8557,42 @@ dependencies = [ "anyhow", "base64 0.21.7", "bstr", - "fancy-regex", + "fancy-regex 0.13.0", "lazy_static", "parking_lot", "regex", - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", + "js-sys", + "libc", "num-conv", + "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -5801,9 +8614,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -5816,12 +8645,26 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.1", "tokio-macros", - "tracing", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-cron-scheduler" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71ce8f810abc9fabebccc30302a952f9e89c6cf246fafaf170fef164063141" +dependencies = [ + "chrono", + "croner", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + [[package]] name = "tokio-macros" version = "2.6.0" @@ -5830,7 +8673,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -5843,13 +8686,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.36", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -5862,7 +8726,23 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.24.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tungstenite 0.28.0", ] [[package]] @@ -5892,11 +8772,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", "toml_datetime 0.7.5+spec-1.1.0", @@ -5929,7 +8809,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -5940,7 +8820,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -5953,7 +8833,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow 0.7.14", @@ -5975,11 +8855,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] -name = "tower" -version = "0.4.13" +name = "tonic" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ + "async-stream", + "async-trait", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5987,14 +8886,34 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -6009,8 +8928,8 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.10.0", "bytes", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "pin-project-lite", "tower-layer", @@ -6028,14 +8947,14 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "iri-string", "pin-project-lite", "tokio", "tokio-util", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", ] @@ -6072,7 +8991,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -6096,6 +9015,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.22" @@ -6147,7 +9084,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.4.0", "httparse", "log", "rand 0.8.5", @@ -6156,6 +9093,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.2", + "rustls 0.23.36", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -6185,6 +9141,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unbinder" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911624cb458604f41ac58db43ab8c2ccc9ad530791c1cd5d0ff8d824a38eeaa7" +dependencies = [ + "cfg-if", + "rustc-hash 2.1.1", + "serde", + "serde_json", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -6226,18 +9194,45 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6287,12 +9282,48 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "uuid" version = "1.19.0" @@ -6305,6 +9336,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "uuid", + "vsimd", +] + [[package]] name = "valuable" version = "0.1.1" @@ -6329,6 +9371,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "vswhom" version = "0.1.0" @@ -6449,7 +9497,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -6475,66 +9523,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wayland-backend" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" -dependencies = [ - "cc", - "downcast-rs", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" -dependencies = [ - "bitflags 2.10.0", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" -dependencies = [ - "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" -dependencies = [ - "dlib", - "log", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.83" @@ -6545,6 +9533,32 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + [[package]] name = "webkit2gtk" version = "2.0.1" @@ -6590,10 +9604,25 @@ dependencies = [ ] [[package]] -name = "webview2-com" -version = "0.38.0" +name = "webpki-roots" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", @@ -6605,20 +9634,20 @@ dependencies = [ [[package]] name = "webview2-com-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] name = "webview2-com-sys" -version = "0.38.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.17", "windows 0.61.3", @@ -6631,6 +9660,17 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "whoami" version = "1.6.1" @@ -6713,6 +9753,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -6747,6 +9797,18 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -6792,7 +9854,18 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -6803,7 +9876,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -6814,7 +9887,18 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -6825,7 +9909,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -7259,6 +10343,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -7279,12 +10373,24 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" version = "0.6.2" @@ -7307,7 +10413,7 @@ dependencies = [ "gdkx11", "gtk", "html5ever", - "http", + "http 1.4.0", "javascriptcore-rs", "jni", "kuchikiki", @@ -7390,6 +10496,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xsum" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" + [[package]] name = "yoke" version = "0.8.1" @@ -7409,15 +10527,15 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "synstructure", ] [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" dependencies = [ "async-broadcast", "async-executor", @@ -7433,11 +10551,11 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix", "serde", "serde_repr", - "tokio", "tracing", "uds_windows", "uuid", @@ -7450,14 +10568,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "zbus_names", "zvariant", "zvariant_utils", @@ -7465,34 +10583,33 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow 0.7.14", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -7512,7 +10629,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "synstructure", ] @@ -7521,6 +10638,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] name = "zerotrie" @@ -7539,6 +10670,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -7552,7 +10684,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", ] [[package]] @@ -7564,7 +10696,7 @@ dependencies = [ "aes", "byteorder", "bzip2", - "constant_time_eq", + "constant_time_eq 0.1.5", "crc32fast", "crossbeam-utils", "flate2", @@ -7572,14 +10704,14 @@ dependencies = [ "pbkdf2", "sha1", "time", - "zstd", + "zstd 0.11.2+zstd.1.5.2", ] [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" [[package]] name = "zstd" @@ -7587,7 +10719,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe 7.2.4", ] [[package]] @@ -7600,6 +10741,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.16+zstd.1.5.7" @@ -7627,14 +10777,13 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" dependencies = [ "endi", "enumflags2", "serde", - "url", "winnow 0.7.14", "zvariant_derive", "zvariant_utils", @@ -7642,26 +10791,26 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.113", + "syn 2.0.114", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.113", + "syn 2.0.114", "winnow 0.7.14", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a487daf0b..482e80066 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proxycast" -version = "0.45.2" +version = "0.46.0" description = "AI API Proxy Desktop App" authors = ["you"] edition = "2021" @@ -77,6 +77,10 @@ mouse_position = "0.1.4" window-vibrancy = "0.7.1" if-addrs = "0.13" +# Aster Agent Framework +# 使用本地路径进行开发,后续切换到 git +aster = { path = "../../../astercloud/aster-rust/crates/aster" } + # Platform specific dependencies for browser interceptor # Windows specific dependencies for browser interceptor and machine ID management diff --git a/src-tauri/src/agent/aster_agent.rs b/src-tauri/src/agent/aster_agent.rs new file mode 100644 index 000000000..9f01938ee --- /dev/null +++ b/src-tauri/src/agent/aster_agent.rs @@ -0,0 +1,205 @@ +//! Aster Agent 包装器 +//! +//! 提供简化的接口来使用 Aster Agent +//! 处理消息发送、事件流转换和会话管理 + +use crate::agent::aster_state::{AsterAgentState, SessionConfigBuilder}; +use crate::agent::event_converter::TauriAgentEvent; +use aster::agents::SessionConfig; +use aster::conversation::message::Message; +use aster::session::SessionManager; +use std::path::PathBuf; +use tauri::{AppHandle, Emitter}; +use tokio_util::sync::CancellationToken; + +/// Aster Agent 包装器 +/// +/// 提供与 Tauri 集成的简化接口 +pub struct AsterAgentWrapper; + +impl AsterAgentWrapper { + /// 发送消息并获取流式响应 + /// + /// # Arguments + /// * `state` - Aster Agent 状态 + /// * `app` - Tauri AppHandle,用于发送事件 + /// * `message` - 用户消息文本 + /// * `session_id` - 会话 ID + /// * `event_name` - 前端监听的事件名称 + /// + /// # Returns + /// 成功时返回 Ok(()),失败时返回错误信息 + pub async fn send_message( + state: &AsterAgentState, + app: &AppHandle, + message: String, + session_id: String, + event_name: String, + ) -> Result<(), String> { + // 确保 Agent 已初始化 + if !state.is_initialized().await { + state.init_agent().await?; + } + + // 创建取消令牌 + let cancel_token = state.create_cancel_token(&session_id).await; + + // 创建用户消息 + let user_message = Message::user().with_text(&message); + + // 创建会话配置 + let session_config = SessionConfigBuilder::new(&session_id).build(); + + // 使用 with_agent 方法获取 Agent 并处理 + let app_clone = app.clone(); + let event_name_clone = event_name.clone(); + let cancel_token_clone = cancel_token.clone(); + + let result = state + .with_agent(|agent| { + // 注意:这里我们需要异步处理,但 with_agent 是同步的 + // 我们需要重新设计这个接口 + }) + .await; + + // 由于 with_agent 的限制,我们需要使用不同的方法 + // 直接在这里处理流 + Self::process_reply_internal( + state, + &app_clone, + user_message, + session_config, + cancel_token_clone, + event_name_clone, + ) + .await?; + + // 清理取消令牌 + state.remove_cancel_token(&session_id).await; + + Ok(()) + } + + /// 内部处理回复的方法 + async fn process_reply_internal( + state: &AsterAgentState, + app: &AppHandle, + user_message: Message, + session_config: SessionConfig, + cancel_token: CancellationToken, + event_name: String, + ) -> Result<(), String> { + // 这里我们需要一个更好的方式来访问 Agent + // 暂时使用一个简化的实现 + + // 发送开始事件 + let start_event = TauriAgentEvent::TextDelta { + text: String::new(), + }; + let _ = app.emit(&event_name, &start_event); + + // TODO: 实现完整的 Agent 调用 + // 由于 Agent.reply() 需要 &self,而我们的 with_agent 方法不支持异步 + // 我们需要重新设计 AsterAgentState 的接口 + + // 发送完成事件 + let done_event = TauriAgentEvent::FinalDone { usage: None }; + if let Err(e) = app.emit(&event_name, &done_event) { + tracing::error!("Failed to emit final done event: {}", e); + } + + Ok(()) + } + + /// 停止当前会话 + pub async fn stop_session(state: &AsterAgentState, session_id: &str) -> bool { + state.cancel_session(session_id).await + } + + /// 创建新会话 + pub async fn create_session( + working_dir: Option, + name: Option, + ) -> Result { + let dir = working_dir + .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); + let session_name = name.unwrap_or_else(|| "New Session".to_string()); + + let session = + SessionManager::create_session(dir, session_name, aster::session::SessionType::User) + .await + .map_err(|e| format!("Failed to create session: {}", e))?; + + Ok(session.id) + } + + /// 列出所有会话 + pub async fn list_sessions() -> Result, String> { + let sessions = SessionManager::list_sessions() + .await + .map_err(|e| format!("Failed to list sessions: {}", e))?; + + Ok(sessions + .into_iter() + .map(|s| SessionInfo { + id: s.id, + name: s.name, + created_at: s.created_at.timestamp(), + updated_at: s.updated_at.timestamp(), + }) + .collect()) + } + + /// 获取会话详情 + pub async fn get_session(session_id: &str) -> Result { + let session = SessionManager::get_session(session_id, true) + .await + .map_err(|e| format!("Failed to get session: {}", e))?; + + Ok(SessionDetail { + id: session.id, + name: session.name, + created_at: session.created_at.timestamp(), + updated_at: session.updated_at.timestamp(), + messages: session + .conversation + .map(|c| { + c.messages() + .iter() + .map(|m| crate::agent::event_converter::convert_to_tauri_message(m)) + .collect() + }) + .unwrap_or_default(), + }) + } +} + +/// 会话信息(简化版) +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SessionInfo { + pub id: String, + pub name: String, + pub created_at: i64, + pub updated_at: i64, +} + +/// 会话详情(包含消息) +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SessionDetail { + pub id: String, + pub name: String, + pub created_at: i64, + pub updated_at: i64, + pub messages: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_session_config_builder() { + let config = SessionConfigBuilder::new("test-session").build(); + assert_eq!(config.id, "test-session"); + } +} diff --git a/src-tauri/src/agent/aster_state.rs b/src-tauri/src/agent/aster_state.rs new file mode 100644 index 000000000..e876acd50 --- /dev/null +++ b/src-tauri/src/agent/aster_state.rs @@ -0,0 +1,293 @@ +//! Aster Agent 状态管理 +//! +//! 管理 Aster Agent 实例和相关状态 +//! 提供 Tauri 应用与 Aster 框架的桥接 + +use aster::agents::{Agent, SessionConfig}; +use aster::model::ModelConfig; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; + +/// Provider 配置信息 +#[derive(Debug, Clone)] +pub struct ProviderConfig { + /// Provider 名称 (openai, anthropic, google, ollama 等) + pub provider_name: String, + /// 模型名称 + pub model_name: String, + /// API Key (可选,某些 provider 从环境变量读取) + pub api_key: Option, + /// Base URL (可选,用于自定义端点) + pub base_url: Option, +} + +/// Aster Agent 全局状态 +/// +/// 在 Tauri 应用中作为 managed state 使用 +pub struct AsterAgentState { + /// Aster Agent 实例 + agent: Arc>>, + /// 当前活跃的取消令牌(用于中止正在进行的对话) + cancel_tokens: Arc>>, + /// 当前 Provider 配置 + current_provider_config: Arc>>, +} + +impl Default for AsterAgentState { + fn default() -> Self { + Self::new() + } +} + +impl AsterAgentState { + /// 创建新的 Aster Agent 状态 + pub fn new() -> Self { + Self { + agent: Arc::new(RwLock::new(None)), + cancel_tokens: Arc::new(RwLock::new(std::collections::HashMap::new())), + current_provider_config: Arc::new(RwLock::new(None)), + } + } + + /// 初始化 Agent + /// + /// 如果 Agent 尚未初始化,则创建新的 Agent 实例 + pub async fn init_agent(&self) -> Result<(), String> { + let mut agent_guard = self.agent.write().await; + if agent_guard.is_none() { + let agent = Agent::new(); + *agent_guard = Some(agent); + tracing::info!("Aster Agent initialized"); + } + Ok(()) + } + + /// 配置 Provider + /// + /// 根据配置创建并设置 Provider + pub async fn configure_provider( + &self, + config: ProviderConfig, + session_id: &str, + ) -> Result<(), String> { + // 确保 Agent 已初始化 + self.init_agent().await?; + + // 设置环境变量(Aster 的 provider 从环境变量读取配置) + self.set_provider_env_vars(&config); + + // 创建 ModelConfig + let model_config = ModelConfig::new(&config.model_name) + .map_err(|e| format!("创建 ModelConfig 失败: {}", e))?; + + // 创建 Provider + let provider = aster::providers::create(&config.provider_name, model_config) + .await + .map_err(|e| format!("创建 Provider 失败: {}", e))?; + + // 更新 Agent 的 Provider + let agent_guard = self.agent.read().await; + if let Some(agent) = agent_guard.as_ref() { + agent + .update_provider(provider, session_id) + .await + .map_err(|e| format!("更新 Provider 失败: {}", e))?; + } + + // 保存当前配置 + let mut config_guard = self.current_provider_config.write().await; + *config_guard = Some(config.clone()); + + tracing::info!( + "[AsterAgent] Provider 配置成功: {} / {}", + config.provider_name, + config.model_name + ); + + Ok(()) + } + + /// 设置 Provider 相关的环境变量 + fn set_provider_env_vars(&self, config: &ProviderConfig) { + // 根据 provider 类型设置对应的环境变量 + let env_key = match config.provider_name.as_str() { + "openai" => "OPENAI_API_KEY", + "anthropic" => "ANTHROPIC_API_KEY", + "google" => "GOOGLE_API_KEY", + "deepseek" | "custom_deepseek" => "DEEPSEEK_API_KEY", + "groq" => "GROQ_API_KEY", + "mistral" => "MISTRAL_API_KEY", + "openrouter" => "OPENROUTER_API_KEY", + "ollama" => return, // Ollama 不需要 API Key + _ => { + // 通用 OpenAI 兼容格式 + if let Some(api_key) = &config.api_key { + std::env::set_var("OPENAI_API_KEY", api_key); + } + if let Some(base_url) = &config.base_url { + std::env::set_var("OPENAI_BASE_URL", base_url); + } + return; + } + }; + + if let Some(api_key) = &config.api_key { + std::env::set_var(env_key, api_key); + } + + if let Some(base_url) = &config.base_url { + let base_url_key = format!( + "{}_BASE_URL", + config.provider_name.to_uppercase().replace("_", "") + ); + std::env::set_var(base_url_key, base_url); + } + } + + /// 获取当前 Provider 配置 + pub async fn get_provider_config(&self) -> Option { + self.current_provider_config.read().await.clone() + } + + /// 检查 Provider 是否已配置 + pub async fn is_provider_configured(&self) -> bool { + self.current_provider_config.read().await.is_some() + } + + /// 获取 Agent 的只读引用并执行同步操作 + pub async fn with_agent(&self, f: F) -> Result + where + F: FnOnce(&Agent) -> R, + { + let guard = self.agent.read().await; + match guard.as_ref() { + Some(agent) => Ok(f(agent)), + None => Err("Agent not initialized".to_string()), + } + } + + /// 获取 Agent 的可变引用并执行同步操作 + pub async fn with_agent_mut(&self, f: F) -> Result + where + F: FnOnce(&mut Agent) -> R, + { + let mut guard = self.agent.write().await; + match guard.as_mut() { + Some(agent) => Ok(f(agent)), + None => Err("Agent not initialized".to_string()), + } + } + + /// 获取 Agent 的 Arc 引用 + /// + /// 用于需要长期持有 Agent 引用的场景 + pub fn get_agent_arc(&self) -> Arc>> { + self.agent.clone() + } + + /// 创建新的取消令牌 + pub async fn create_cancel_token(&self, session_id: &str) -> CancellationToken { + let token = CancellationToken::new(); + let mut tokens = self.cancel_tokens.write().await; + tokens.insert(session_id.to_string(), token.clone()); + token + } + + /// 取消指定会话的操作 + pub async fn cancel_session(&self, session_id: &str) -> bool { + let tokens = self.cancel_tokens.read().await; + if let Some(token) = tokens.get(session_id) { + token.cancel(); + true + } else { + false + } + } + + /// 移除取消令牌 + pub async fn remove_cancel_token(&self, session_id: &str) { + let mut tokens = self.cancel_tokens.write().await; + tokens.remove(session_id); + } + + /// 检查 Agent 是否已初始化 + pub async fn is_initialized(&self) -> bool { + self.agent.read().await.is_some() + } +} + +/// 会话配置构建器 +/// +/// 用于构建 Aster SessionConfig +pub struct SessionConfigBuilder { + id: String, + max_turns: Option, +} + +impl SessionConfigBuilder { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + max_turns: None, + } + } + + pub fn max_turns(mut self, turns: u32) -> Self { + self.max_turns = Some(turns); + self + } + + pub fn build(self) -> SessionConfig { + SessionConfig { + id: self.id, + schedule_id: None, + max_turns: self.max_turns, + retry_config: None, + } + } +} + +/// 消息构建辅助函数 +pub mod message_helpers { + use aster::conversation::message::Message; + + /// 创建用户文本消息 + pub fn user_text(text: impl Into) -> Message { + Message::user().with_text(text) + } + + /// 创建助手文本消息 + pub fn assistant_text(text: impl Into) -> Message { + Message::assistant().with_text(text) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_aster_state_init() { + let state = AsterAgentState::new(); + assert!(!state.is_initialized().await); + + state.init_agent().await.unwrap(); + assert!(state.is_initialized().await); + } + + #[tokio::test] + async fn test_cancel_token() { + let state = AsterAgentState::new(); + let session_id = "test-session"; + + let token = state.create_cancel_token(session_id).await; + assert!(!token.is_cancelled()); + + assert!(state.cancel_session(session_id).await); + assert!(token.is_cancelled()); + + state.remove_cancel_token(session_id).await; + assert!(!state.cancel_session(session_id).await); + } +} diff --git a/src-tauri/src/agent/event_converter.rs b/src-tauri/src/agent/event_converter.rs new file mode 100644 index 000000000..86c6215ee --- /dev/null +++ b/src-tauri/src/agent/event_converter.rs @@ -0,0 +1,451 @@ +//! Aster 事件转换器 +//! +//! 将 Aster AgentEvent 转换为 Tauri 可用的事件格式 +//! 用于前端实时显示流式响应 + +use aster::agents::AgentEvent; +use aster::conversation::message::{ActionRequiredData, Message, MessageContent}; +use serde::{Deserialize, Serialize}; + +/// 从工具结果中提取文本内容 +/// +/// 使用 serde_json 来处理,避免直接依赖 rmcp 类型 +fn extract_tool_result_text(result: &T) -> String { + if let Ok(json) = serde_json::to_value(result) { + if let Some(content) = json.get("content").and_then(|c| c.as_array()) { + return content + .iter() + .filter_map(|item| { + if item.get("type").and_then(|t| t.as_str()) == Some("text") { + item.get("text").and_then(|t| t.as_str()).map(String::from) + } else { + None + } + }) + .collect::>() + .join("\n"); + } + } + String::new() +} + +/// Tauri Agent 事件 +/// +/// 用于前端消费的事件格式,与现有的 StreamEvent 兼容 +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TauriAgentEvent { + /// 文本增量 + #[serde(rename = "text_delta")] + TextDelta { text: String }, + + /// 思考内容增量 + #[serde(rename = "thinking_delta")] + ThinkingDelta { text: String }, + + /// 工具调用开始 + #[serde(rename = "tool_start")] + ToolStart { + tool_name: String, + tool_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + arguments: Option, + }, + + /// 工具调用结束 + #[serde(rename = "tool_end")] + ToolEnd { + tool_id: String, + result: TauriToolResult, + }, + + /// 需要用户操作(权限确认、用户输入等) + #[serde(rename = "action_required")] + ActionRequired { + request_id: String, + action_type: String, + data: serde_json::Value, + }, + + /// 模型变更 + #[serde(rename = "model_change")] + ModelChange { model: String, mode: String }, + + /// 完成(单次响应完成) + #[serde(rename = "done")] + Done { + #[serde(skip_serializing_if = "Option::is_none")] + usage: Option, + }, + + /// 最终完成(整个对话完成) + #[serde(rename = "final_done")] + FinalDone { + #[serde(skip_serializing_if = "Option::is_none")] + usage: Option, + }, + + /// 错误 + #[serde(rename = "error")] + Error { message: String }, + + /// 完整消息(用于历史记录) + #[serde(rename = "message")] + Message { message: TauriMessage }, +} + +/// 工具执行结果 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TauriToolResult { + pub success: bool, + pub output: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Token 使用量 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TauriTokenUsage { + pub input_tokens: u32, + pub output_tokens: u32, +} + +/// 简化的消息结构 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TauriMessage { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + pub role: String, + pub content: Vec, + pub timestamp: i64, +} + +/// 简化的消息内容 +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TauriMessageContent { + #[serde(rename = "text")] + Text { text: String }, + + #[serde(rename = "thinking")] + Thinking { text: String }, + + #[serde(rename = "tool_request")] + ToolRequest { + id: String, + tool_name: String, + arguments: serde_json::Value, + }, + + #[serde(rename = "tool_response")] + ToolResponse { + id: String, + success: bool, + output: String, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, + }, + + #[serde(rename = "action_required")] + ActionRequired { + id: String, + action_type: String, + data: serde_json::Value, + }, + + #[serde(rename = "image")] + Image { mime_type: String, data: String }, +} + +/// 将 Aster AgentEvent 转换为 TauriAgentEvent 列表 +/// +/// 一个 AgentEvent 可能产生多个 TauriAgentEvent +pub fn convert_agent_event(event: AgentEvent) -> Vec { + match event { + AgentEvent::Message(message) => convert_message(message), + AgentEvent::McpNotification((server_name, notification)) => { + // MCP 通知暂时忽略或转换为日志 + tracing::debug!("MCP notification from {}: {:?}", server_name, notification); + vec![] + } + AgentEvent::ModelChange { model, mode } => { + vec![TauriAgentEvent::ModelChange { model, mode }] + } + AgentEvent::HistoryReplaced(_conversation) => { + // 历史替换事件,可能需要特殊处理 + tracing::debug!("History replaced"); + vec![] + } + } +} + +/// 将 Aster Message 转换为 TauriAgentEvent 列表 +fn convert_message(message: Message) -> Vec { + let mut events = Vec::new(); + + for content in &message.content { + match content { + MessageContent::Text(text_content) => { + events.push(TauriAgentEvent::TextDelta { + text: text_content.text.clone(), + }); + } + MessageContent::Thinking(thinking) => { + events.push(TauriAgentEvent::ThinkingDelta { + text: thinking.thinking.clone(), + }); + } + MessageContent::ToolRequest(tool_request) => match &tool_request.tool_call { + Ok(call) => { + events.push(TauriAgentEvent::ToolStart { + tool_name: call.name.to_string(), + tool_id: tool_request.id.clone(), + arguments: serde_json::to_string(&call.arguments).ok(), + }); + } + Err(e) => { + events.push(TauriAgentEvent::Error { + message: format!("Invalid tool call: {}", e), + }); + } + }, + MessageContent::ToolResponse(tool_response) => { + let (success, output, error) = match &tool_response.tool_result { + Ok(result) => { + // 从 CallToolResult 中提取文本内容 + let output = extract_tool_result_text(result); + (true, output, None) + } + Err(e) => (false, String::new(), Some(e.to_string())), + }; + + events.push(TauriAgentEvent::ToolEnd { + tool_id: tool_response.id.clone(), + result: TauriToolResult { + success, + output, + error, + }, + }); + } + MessageContent::ActionRequired(action_required) => { + let (request_id, action_type, data) = match &action_required.data { + ActionRequiredData::ToolConfirmation { + id, + tool_name, + arguments, + prompt, + } => ( + id.clone(), + "tool_confirmation".to_string(), + serde_json::json!({ + "tool_name": tool_name, + "arguments": arguments, + "prompt": prompt, + }), + ), + ActionRequiredData::Elicitation { + id, + message, + requested_schema, + } => ( + id.clone(), + "elicitation".to_string(), + serde_json::json!({ + "message": message, + "requested_schema": requested_schema, + }), + ), + ActionRequiredData::ElicitationResponse { id, user_data } => ( + id.clone(), + "elicitation_response".to_string(), + serde_json::json!({ + "user_data": user_data, + }), + ), + }; + + events.push(TauriAgentEvent::ActionRequired { + request_id, + action_type, + data, + }); + } + MessageContent::SystemNotification(notification) => { + // 系统通知转换为文本 + events.push(TauriAgentEvent::TextDelta { + text: notification.msg.clone(), + }); + } + MessageContent::Image(image) => { + // 图片内容暂时忽略 + tracing::debug!("Image content: {}", image.mime_type); + } + MessageContent::ToolConfirmationRequest(req) => { + events.push(TauriAgentEvent::ActionRequired { + request_id: req.id.clone(), + action_type: "tool_confirmation".to_string(), + data: serde_json::json!({ + "tool_name": req.tool_name, + "arguments": req.arguments, + "prompt": req.prompt, + }), + }); + } + MessageContent::FrontendToolRequest(req) => match &req.tool_call { + Ok(call) => { + events.push(TauriAgentEvent::ToolStart { + tool_name: call.name.to_string(), + tool_id: req.id.clone(), + arguments: serde_json::to_string(&call.arguments).ok(), + }); + } + Err(e) => { + events.push(TauriAgentEvent::Error { + message: format!("Invalid frontend tool call: {}", e), + }); + } + }, + MessageContent::RedactedThinking(_) => { + // 隐藏的思考内容,忽略 + } + } + } + + events +} + +/// 将 Aster Message 转换为 TauriMessage +pub fn convert_to_tauri_message(message: &Message) -> TauriMessage { + let content = message + .content + .iter() + .filter_map(|c| convert_message_content(c)) + .collect(); + + TauriMessage { + id: message.id.clone(), + role: format!("{:?}", message.role).to_lowercase(), + content, + timestamp: message.created, + } +} + +/// 将 MessageContent 转换为 TauriMessageContent +fn convert_message_content(content: &MessageContent) -> Option { + match content { + MessageContent::Text(text) => Some(TauriMessageContent::Text { + text: text.text.clone(), + }), + MessageContent::Thinking(thinking) => Some(TauriMessageContent::Thinking { + text: thinking.thinking.clone(), + }), + MessageContent::ToolRequest(req) => { + req.tool_call + .as_ref() + .ok() + .map(|call| TauriMessageContent::ToolRequest { + id: req.id.clone(), + tool_name: call.name.to_string(), + arguments: serde_json::to_value(&call.arguments).unwrap_or_default(), + }) + } + MessageContent::ToolResponse(resp) => { + let (success, output, error) = match &resp.tool_result { + Ok(result) => { + let output = extract_tool_result_text(result); + (true, output, None) + } + Err(e) => (false, String::new(), Some(e.to_string())), + }; + Some(TauriMessageContent::ToolResponse { + id: resp.id.clone(), + success, + output, + error, + }) + } + MessageContent::ActionRequired(action) => { + let (id, action_type, data) = match &action.data { + ActionRequiredData::ToolConfirmation { + id, + tool_name, + arguments, + prompt, + } => ( + id.clone(), + "tool_confirmation".to_string(), + serde_json::json!({ + "tool_name": tool_name, + "arguments": arguments, + "prompt": prompt, + }), + ), + ActionRequiredData::Elicitation { + id, + message, + requested_schema, + } => ( + id.clone(), + "elicitation".to_string(), + serde_json::json!({ + "message": message, + "requested_schema": requested_schema, + }), + ), + ActionRequiredData::ElicitationResponse { id, user_data } => ( + id.clone(), + "elicitation_response".to_string(), + user_data.clone(), + ), + }; + Some(TauriMessageContent::ActionRequired { + id, + action_type, + data, + }) + } + MessageContent::Image(image) => Some(TauriMessageContent::Image { + mime_type: image.mime_type.clone(), + data: image.data.clone(), + }), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_convert_text_delta() { + let message = Message::assistant().with_text("Hello, world!"); + let events = convert_message(message); + + assert_eq!(events.len(), 1); + match &events[0] { + TauriAgentEvent::TextDelta { text } => { + assert_eq!(text, "Hello, world!"); + } + _ => panic!("Expected TextDelta event"), + } + } + + #[test] + fn test_convert_model_change() { + let event = AgentEvent::ModelChange { + model: "claude-3".to_string(), + mode: "chat".to_string(), + }; + let events = convert_agent_event(event); + + assert_eq!(events.len(), 1); + match &events[0] { + TauriAgentEvent::ModelChange { model, mode } => { + assert_eq!(model, "claude-3"); + assert_eq!(mode, "chat"); + } + _ => panic!("Expected ModelChange event"), + } + } +} diff --git a/src-tauri/src/agent/mod.rs b/src-tauri/src/agent/mod.rs index 0259bfbf0..0df850afe 100644 --- a/src-tauri/src/agent/mod.rs +++ b/src-tauri/src/agent/mod.rs @@ -9,7 +9,13 @@ //! - native_agent - 核心 Agent 逻辑 //! - tool_loop - 工具调用循环 //! - tools/ - 工具实现 +//! - aster_state - Aster Agent 状态管理(新) +//! - aster_agent - Aster Agent 包装器(新) +//! - event_converter - Aster 事件转换器(新) +pub mod aster_agent; +pub mod aster_state; +pub mod event_converter; pub mod native_agent; pub mod parsers; pub mod protocols; @@ -17,6 +23,9 @@ pub mod tool_loop; pub mod tools; pub mod types; +pub use aster_agent::{AsterAgentWrapper, SessionDetail, SessionInfo}; +pub use aster_state::AsterAgentState; +pub use event_converter::{convert_agent_event, TauriAgentEvent}; pub use native_agent::{NativeAgent, NativeAgentState}; pub use parsers::{AnthropicSSEParser, OpenAISSEParser}; pub use protocols::{create_protocol, AnthropicProtocol, OpenAIProtocol, Protocol}; diff --git a/src-tauri/src/agent/native_agent.rs b/src-tauri/src/agent/native_agent.rs index 2158363a1..07be91d16 100644 --- a/src-tauri/src/agent/native_agent.rs +++ b/src-tauri/src/agent/native_agent.rs @@ -524,6 +524,7 @@ impl NativeAgent { content: Some(OpenAIMessageContent::Text(prompt.clone())), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } @@ -552,6 +553,7 @@ impl NativeAgent { content: Some(OpenAIMessageContent::Parts(parts)), tool_calls: None, tool_call_id: None, + reasoning_content: None, } } else { ChatMessage { @@ -559,6 +561,7 @@ impl NativeAgent { content: Some(OpenAIMessageContent::Text(user_message.to_string())), tool_calls: None, tool_call_id: None, + reasoning_content: None, } }; @@ -606,6 +609,7 @@ impl NativeAgent { .collect() }), tool_call_id: msg.tool_call_id.clone(), + reasoning_content: msg.reasoning_content.clone(), } } @@ -642,6 +646,7 @@ impl NativeAgent { timestamp: chrono::Utc::now().to_rfc3339(), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); session.updated_at = chrono::Utc::now().to_rfc3339(); } @@ -662,6 +667,7 @@ impl NativeAgent { timestamp: chrono::Utc::now().to_rfc3339(), tool_calls, tool_call_id: None, + reasoning_content: None, }); session.updated_at = chrono::Utc::now().to_rfc3339(); } diff --git a/src-tauri/src/agent/parsers/openai_sse.rs b/src-tauri/src/agent/parsers/openai_sse.rs index cd1891401..b84489526 100644 --- a/src-tauri/src/agent/parsers/openai_sse.rs +++ b/src-tauri/src/agent/parsers/openai_sse.rs @@ -31,6 +31,8 @@ struct ToolCallDelta { pub struct OpenAISSEParser { /// 累积的完整内容 full_content: String, + /// 累积的推理内容(DeepSeek R1 等模型) + reasoning_content: String, /// 当前正在构建的工具调用索引 current_tool_indices: HashMap, } @@ -103,6 +105,15 @@ impl OpenAISSEParser { s.to_string() }); + // 提取推理内容(DeepSeek R1 等模型) + if let Some(reasoning) = delta + .get("reasoning_content") + .and_then(|c| c.as_str()) + .filter(|s| !s.is_empty()) + { + self.reasoning_content.push_str(reasoning); + } + // 提取工具调用 if let Some(tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) { for tc in tool_calls { @@ -181,6 +192,15 @@ impl OpenAISSEParser { self.full_content.clone() } + /// 获取推理内容(DeepSeek R1 等模型) + pub fn get_reasoning_content(&self) -> Option { + if self.reasoning_content.is_empty() { + None + } else { + Some(self.reasoning_content.clone()) + } + } + /// 是否有工具调用 pub fn has_tool_calls(&self) -> bool { !self.current_tool_indices.is_empty() diff --git a/src-tauri/src/agent/protocols/anthropic.rs b/src-tauri/src/agent/protocols/anthropic.rs index e3cf90550..f56ee2aa8 100644 --- a/src-tauri/src/agent/protocols/anthropic.rs +++ b/src-tauri/src/agent/protocols/anthropic.rs @@ -359,6 +359,7 @@ impl AnthropicProtocol { content: full_content, tool_calls, usage, + reasoning_content: None, }); } } @@ -396,6 +397,7 @@ impl AnthropicProtocol { content: full_content, tool_calls, usage, + reasoning_content: None, }) } } diff --git a/src-tauri/src/agent/protocols/openai.rs b/src-tauri/src/agent/protocols/openai.rs index 85bd2d336..29f7675d6 100644 --- a/src-tauri/src/agent/protocols/openai.rs +++ b/src-tauri/src/agent/protocols/openai.rs @@ -62,6 +62,7 @@ impl OpenAIProtocol { .collect() }), tool_call_id: msg.tool_call_id.clone(), + reasoning_content: msg.reasoning_content.clone(), } } @@ -81,6 +82,7 @@ impl OpenAIProtocol { content: Some(OpenAIMessageContent::Text(prompt.clone())), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } @@ -109,6 +111,7 @@ impl OpenAIProtocol { content: Some(OpenAIMessageContent::Parts(parts)), tool_calls: None, tool_call_id: None, + reasoning_content: None, } } else { ChatMessage { @@ -116,6 +119,7 @@ impl OpenAIProtocol { content: Some(OpenAIMessageContent::Text(user_message.to_string())), tool_calls: None, tool_call_id: None, + reasoning_content: None, } }; @@ -137,6 +141,7 @@ impl OpenAIProtocol { content: Some(OpenAIMessageContent::Text(prompt.clone())), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } @@ -218,6 +223,7 @@ impl OpenAIProtocol { content, tool_calls: None, usage, + reasoning_content: None, }); } } @@ -260,6 +266,7 @@ impl OpenAIProtocol { content: full_content, tool_calls, usage: final_usage, + reasoning_content: None, }); } } @@ -317,11 +324,13 @@ impl OpenAIProtocol { content, tool_calls: None, usage, + reasoning_content: None, }); } } let full_content = parser.get_full_content(); + let reasoning_content = parser.get_reasoning_content(); let tool_calls = if parser.has_tool_calls() { Some(parser.finalize_tool_calls()) } else { @@ -340,6 +349,7 @@ impl OpenAIProtocol { content: full_content, tool_calls, usage: final_usage, + reasoning_content, }) } } diff --git a/src-tauri/src/agent/tool_loop.rs b/src-tauri/src/agent/tool_loop.rs index 841133d91..4d075a710 100644 --- a/src-tauri/src/agent/tool_loop.rs +++ b/src-tauri/src/agent/tool_loop.rs @@ -84,6 +84,7 @@ impl ToolCallResult { timestamp: chrono::Utc::now().to_rfc3339(), tool_calls: None, tool_call_id: Some(self.tool_call_id.clone()), + reasoning_content: None, } } @@ -301,6 +302,7 @@ impl ToolLoopEngine { .collect() }), tool_call_id: None, + reasoning_content: None, } } } @@ -458,6 +460,7 @@ mod tests { content: "".to_string(), tool_calls: Some(vec![]), usage: None, + reasoning_content: None, }; assert!(!ToolLoopEngine::has_tool_calls(&result_empty_tools)); } @@ -1045,6 +1048,7 @@ mod proptests { content: content.clone(), tool_calls: Some(vec![]), usage: None, + reasoning_content: None, }; // 验证:should_continue 返回 false diff --git a/src-tauri/src/agent/tools/browser.rs b/src-tauri/src/agent/tools/browser.rs new file mode 100644 index 000000000..b61d0a48a --- /dev/null +++ b/src-tauri/src/agent/tools/browser.rs @@ -0,0 +1,468 @@ +//! Browser 工具模块 +//! +//! 提供浏览器自动化功能,基于 Playwright +//! 专为 AI Agent 设计,提供结构化的页面快照 + +#![allow(dead_code)] + +use super::registry::Tool; +use super::types::{JsonSchema, PropertySchema, ToolDefinition, ToolError, ToolResult}; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::process::Stdio; +use std::time::Duration; +use tokio::process::Command; +use tokio::time::timeout; +use tracing::info; + +/// 默认超时时间(秒) +const DEFAULT_TIMEOUT_SECS: u64 = 30; + +/// Playwright 脚本目录 +const PLAYWRIGHT_SCRIPTS_DIR: &str = "scripts/playwright"; + +/// 浏览器操作类型 +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum BrowserAction { + /// 打开页面 + Open { url: String }, + /// 获取页面快照(AI 友好的可访问性树) + Snapshot { + #[serde(default)] + interactive_only: bool, + }, + /// 点击元素 + Click { selector: String }, + /// 填充表单 + Fill { selector: String, value: String }, + /// 输入文本(逐字符) + Type { selector: String, text: String }, + /// 按键 + Press { key: String }, + /// 滚动页面 + Scroll { + direction: ScrollDirection, + #[serde(default = "default_scroll_amount")] + amount: i32, + }, + /// 等待元素 + WaitFor { + selector: String, + #[serde(default = "default_wait_timeout")] + timeout_ms: u64, + }, + /// 截图 + Screenshot { + #[serde(default)] + full_page: bool, + path: Option, + }, + /// 获取页面文本内容 + GetText { selector: Option }, + /// 执行 JavaScript + Evaluate { script: String }, + /// 关闭浏览器 + Close, +} + +fn default_scroll_amount() -> i32 { + 500 +} + +fn default_wait_timeout() -> u64 { + 5000 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ScrollDirection { + Up, + Down, + Left, + Right, +} + +/// 浏览器操作结果 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BrowserResult { + /// 是否成功 + pub success: bool, + /// 输出内容 + pub output: String, + /// 页面 URL + pub url: Option, + /// 页面标题 + pub title: Option, + /// 截图 base64(如果有) + pub screenshot: Option, + /// 错误信息 + pub error: Option, +} + +/// Browser 工具 +/// +/// 提供浏览器自动化功能,专为 AI Agent 设计 +pub struct BrowserTool { + /// Playwright 脚本路径 + script_path: PathBuf, + /// 超时时间(秒) + timeout_secs: u64, + /// 是否使用 headless 模式 + headless: bool, +} + +impl BrowserTool { + /// 创建新的 Browser 工具 + pub fn new() -> Self { + // 获取脚本路径(相对于项目根目录) + let script_path = std::env::current_dir() + .unwrap_or_default() + .join(PLAYWRIGHT_SCRIPTS_DIR) + .join("browser-tool.mjs"); + + Self { + script_path, + timeout_secs: DEFAULT_TIMEOUT_SECS, + headless: true, + } + } + + /// 设置脚本路径 + pub fn with_script_path(mut self, path: PathBuf) -> Self { + self.script_path = path; + self + } + + /// 设置超时时间 + pub fn with_timeout(mut self, timeout_secs: u64) -> Self { + self.timeout_secs = timeout_secs; + self + } + + /// 设置是否 headless + pub fn with_headless(mut self, headless: bool) -> Self { + self.headless = headless; + self + } + + /// 执行浏览器操作 + async fn execute_action(&self, action: &BrowserAction) -> Result { + let action_json = serde_json::to_string(action) + .map_err(|e| ToolError::ExecutionFailed(format!("序列化操作失败: {}", e)))?; + + info!("[BrowserTool] 执行操作: {:?}", action); + + // 构建命令 + let mut cmd = Command::new("node"); + cmd.arg(&self.script_path); + cmd.arg("--action"); + cmd.arg(&action_json); + + if self.headless { + cmd.arg("--headless"); + } + + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + // 执行命令 + let timeout_duration = Duration::from_secs(self.timeout_secs); + let result = timeout(timeout_duration, cmd.output()).await; + + match result { + Ok(Ok(output)) => { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if output.status.success() { + // 解析 JSON 输出 + serde_json::from_str(&stdout).map_err(|e| { + ToolError::ExecutionFailed(format!( + "解析输出失败: {}\nstdout: {}\nstderr: {}", + e, stdout, stderr + )) + }) + } else { + Err(ToolError::ExecutionFailed(format!( + "浏览器操作失败: {}", + stderr + ))) + } + } + Ok(Err(e)) => Err(ToolError::ExecutionFailed(format!("执行命令失败: {}", e))), + Err(_) => Err(ToolError::Timeout), + } + } +} + +impl Default for BrowserTool { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl Tool for BrowserTool { + fn definition(&self) -> ToolDefinition { + ToolDefinition::new( + "browser", + "Control a web browser for automation tasks. Use this to navigate websites, \ + interact with elements, fill forms, and extract information. The 'snapshot' \ + action returns an accessibility tree with element references (like @e1, @e2) \ + that can be used in subsequent actions.", + ) + .with_parameters( + JsonSchema::new() + .add_property( + "action", + PropertySchema::string( + "The browser action to perform. One of: open, snapshot, click, fill, \ + type, press, scroll, wait_for, screenshot, get_text, evaluate, close", + ), + true, + ) + .add_property( + "url", + PropertySchema::string("URL to open (for 'open' action)"), + false, + ) + .add_property( + "selector", + PropertySchema::string( + "Element selector. Can be CSS selector, XPath, or element reference \ + like @e1 from snapshot output", + ), + false, + ) + .add_property( + "value", + PropertySchema::string("Value to fill (for 'fill' action)"), + false, + ) + .add_property( + "text", + PropertySchema::string("Text to type (for 'type' action)"), + false, + ) + .add_property( + "key", + PropertySchema::string( + "Key to press (for 'press' action), e.g., 'Enter', 'Tab', 'Escape'", + ), + false, + ) + .add_property( + "direction", + PropertySchema::string("Scroll direction: up, down, left, right"), + false, + ) + .add_property( + "script", + PropertySchema::string("JavaScript code to evaluate (for 'evaluate' action)"), + false, + ) + .add_property( + "interactive_only", + PropertySchema::boolean( + "Only include interactive elements in snapshot (default: false)", + ), + false, + ) + .add_property( + "full_page", + PropertySchema::boolean("Capture full page screenshot (default: false)"), + false, + ), + ) + } + + async fn execute(&self, args: serde_json::Value) -> Result { + let action_str = args + .get("action") + .and_then(|v| v.as_str()) + .ok_or_else(|| ToolError::InvalidArguments("缺少 action 参数".to_string()))?; + + // 解析操作 + let action = match action_str { + "open" => { + let url = args.get("url").and_then(|v| v.as_str()).ok_or_else(|| { + ToolError::InvalidArguments("open 操作需要 url 参数".to_string()) + })?; + BrowserAction::Open { + url: url.to_string(), + } + } + "snapshot" => { + let interactive_only = args + .get("interactive_only") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + BrowserAction::Snapshot { interactive_only } + } + "click" => { + let selector = args + .get("selector") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + ToolError::InvalidArguments("click 操作需要 selector 参数".to_string()) + })?; + BrowserAction::Click { + selector: selector.to_string(), + } + } + "fill" => { + let selector = args + .get("selector") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + ToolError::InvalidArguments("fill 操作需要 selector 参数".to_string()) + })?; + let value = args.get("value").and_then(|v| v.as_str()).ok_or_else(|| { + ToolError::InvalidArguments("fill 操作需要 value 参数".to_string()) + })?; + BrowserAction::Fill { + selector: selector.to_string(), + value: value.to_string(), + } + } + "type" => { + let selector = args + .get("selector") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + ToolError::InvalidArguments("type 操作需要 selector 参数".to_string()) + })?; + let text = args.get("text").and_then(|v| v.as_str()).ok_or_else(|| { + ToolError::InvalidArguments("type 操作需要 text 参数".to_string()) + })?; + BrowserAction::Type { + selector: selector.to_string(), + text: text.to_string(), + } + } + "press" => { + let key = args.get("key").and_then(|v| v.as_str()).ok_or_else(|| { + ToolError::InvalidArguments("press 操作需要 key 参数".to_string()) + })?; + BrowserAction::Press { + key: key.to_string(), + } + } + "scroll" => { + let direction = args + .get("direction") + .and_then(|v| v.as_str()) + .unwrap_or("down"); + let direction = match direction { + "up" => ScrollDirection::Up, + "down" => ScrollDirection::Down, + "left" => ScrollDirection::Left, + "right" => ScrollDirection::Right, + _ => ScrollDirection::Down, + }; + let amount = args + .get("amount") + .and_then(|v| v.as_i64()) + .map(|v| v as i32) + .unwrap_or(500); + BrowserAction::Scroll { direction, amount } + } + "wait_for" => { + let selector = args + .get("selector") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + ToolError::InvalidArguments("wait_for 操作需要 selector 参数".to_string()) + })?; + let timeout_ms = args + .get("timeout_ms") + .and_then(|v| v.as_u64()) + .unwrap_or(5000); + BrowserAction::WaitFor { + selector: selector.to_string(), + timeout_ms, + } + } + "screenshot" => { + let full_page = args + .get("full_page") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let path = args.get("path").and_then(|v| v.as_str()).map(String::from); + BrowserAction::Screenshot { full_page, path } + } + "get_text" => { + let selector = args + .get("selector") + .and_then(|v| v.as_str()) + .map(String::from); + BrowserAction::GetText { selector } + } + "evaluate" => { + let script = args.get("script").and_then(|v| v.as_str()).ok_or_else(|| { + ToolError::InvalidArguments("evaluate 操作需要 script 参数".to_string()) + })?; + BrowserAction::Evaluate { + script: script.to_string(), + } + } + "close" => BrowserAction::Close, + _ => { + return Err(ToolError::InvalidArguments(format!( + "未知的操作: {}", + action_str + ))); + } + }; + + // 执行操作 + let result = self.execute_action(&action).await?; + + // 构建输出 + let mut output = result.output; + + if let Some(url) = &result.url { + output = format!("URL: {}\n{}", url, output); + } + if let Some(title) = &result.title { + output = format!("Title: {}\n{}", title, output); + } + + if result.success { + Ok(ToolResult::success(output)) + } else { + Ok(ToolResult::failure_with_output( + output, + result.error.unwrap_or_else(|| "未知错误".to_string()), + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tool_definition() { + let tool = BrowserTool::new(); + let def = tool.definition(); + + assert_eq!(def.name, "browser"); + assert!(!def.description.is_empty()); + assert!(def.parameters.required.contains(&"action".to_string())); + } + + #[test] + fn test_action_serialization() { + let action = BrowserAction::Open { + url: "https://example.com".to_string(), + }; + let json = serde_json::to_string(&action).unwrap(); + assert!(json.contains("open")); + assert!(json.contains("https://example.com")); + } +} diff --git a/src-tauri/src/agent/tools/mod.rs b/src-tauri/src/agent/tools/mod.rs index d3b8e48a6..3259f29f5 100644 --- a/src-tauri/src/agent/tools/mod.rs +++ b/src-tauri/src/agent/tools/mod.rs @@ -14,6 +14,7 @@ //! - `prompt`: 工具 Prompt 生成器(System Prompt 工具注入) pub mod bash; +pub mod browser; pub mod edit_file; pub mod prompt; pub mod read_file; @@ -25,6 +26,7 @@ pub mod types; pub mod write_file; pub use bash::{BashExecutionResult, BashTool, ShellType}; +pub use browser::{BrowserAction, BrowserResult, BrowserTool}; pub use edit_file::{EditFileResult, EditFileTool, UndoResult}; pub use prompt::{generate_tools_prompt, PromptFormat, ToolPromptGenerator}; pub use read_file::{ReadFileResult, ReadFileTool}; diff --git a/src-tauri/src/agent/types.rs b/src-tauri/src/agent/types.rs index c55887304..f682502c9 100644 --- a/src-tauri/src/agent/types.rs +++ b/src-tauri/src/agent/types.rs @@ -116,6 +116,10 @@ pub struct AgentMessage { /// 工具调用 ID(tool 角色消息需要) #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, + /// 推理内容(DeepSeek R1 等模型的思维链内容) + /// DeepSeek Reasoner 在 Tool Calls 场景下要求此字段 + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_content: Option, } /// 消息内容类型 @@ -468,6 +472,9 @@ pub struct StreamResult { /// Token 使用量 #[serde(skip_serializing_if = "Option::is_none")] pub usage: Option, + /// 推理内容(DeepSeek R1 等模型的思维链内容) + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_content: Option, } impl StreamResult { @@ -477,6 +484,7 @@ impl StreamResult { content, tool_calls: None, usage: None, + reasoning_content: None, } } @@ -492,6 +500,12 @@ impl StreamResult { self } + /// 设置推理内容 + pub fn with_reasoning_content(mut self, reasoning_content: String) -> Self { + self.reasoning_content = Some(reasoning_content); + self + } + /// 是否有工具调用 pub fn has_tool_calls(&self) -> bool { self.tool_calls diff --git a/src-tauri/src/app/bootstrap.rs b/src-tauri/src/app/bootstrap.rs index e9b6575f7..e16871515 100644 --- a/src-tauri/src/app/bootstrap.rs +++ b/src-tauri/src/app/bootstrap.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use tokio::sync::RwLock; +use crate::agent::AsterAgentState; use crate::agent::NativeAgentState; use crate::commands::api_key_provider_cmd::ApiKeyProviderServiceState; use crate::commands::connect_cmd::ConnectStateWrapper; @@ -134,6 +135,7 @@ pub struct AppStates { pub enhanced_stats_service: EnhancedStatsServiceState, pub batch_operations: BatchOperationsState, pub native_agent: NativeAgentState, + pub aster_agent: AsterAgentState, pub oauth_plugin_manager: crate::commands::oauth_plugin_cmd::OAuthPluginManagerState, pub orchestrator: OrchestratorState, pub connect_state: ConnectStateWrapper, @@ -213,6 +215,7 @@ pub fn init_states(config: &Config) -> Result { // 其他状态 let native_agent_state = NativeAgentState::new(); + let aster_agent_state = AsterAgentState::new(); let oauth_plugin_manager_state = crate::commands::oauth_plugin_cmd::OAuthPluginManagerState::with_defaults(); let orchestrator_state = OrchestratorState::new(); @@ -275,6 +278,7 @@ pub fn init_states(config: &Config) -> Result { enhanced_stats_service: enhanced_stats_service_state, batch_operations: batch_operations_state, native_agent: native_agent_state, + aster_agent: aster_agent_state, oauth_plugin_manager: oauth_plugin_manager_state, orchestrator: orchestrator_state, connect_state, diff --git a/src-tauri/src/app/commands/api_test.rs b/src-tauri/src/app/commands/api_test.rs index be2f39352..1119ec1b3 100644 --- a/src-tauri/src/app/commands/api_test.rs +++ b/src-tauri/src/app/commands/api_test.rs @@ -119,6 +119,7 @@ pub async fn check_api_compatibility( )), tool_calls: None, tool_call_id: None, + reasoning_content: None, }], temperature: None, max_tokens: Some(100), @@ -155,6 +156,7 @@ pub async fn check_api_compatibility( )), tool_calls: None, tool_call_id: None, + reasoning_content: None, }], temperature: None, max_tokens: Some(10), diff --git a/src-tauri/src/app/commands/config.rs b/src-tauri/src/app/commands/config.rs index 853924423..a7166b3da 100644 --- a/src-tauri/src/app/commands/config.rs +++ b/src-tauri/src/app/commands/config.rs @@ -25,13 +25,18 @@ pub async fn save_config( ) -> Result<(), String> { let host = config.server.host.to_lowercase(); - tracing::info!("[CONFIG] 保存配置请求: host={}, port={}", host, config.server.port); + tracing::info!( + "[CONFIG] 保存配置请求: host={}, port={}", + host, + config.server.port + ); // 验证绑定地址 if !is_valid_bind_host(&host) { tracing::warn!("[CONFIG] 无效的监听地址: {}", host); return Err( - "无效的监听地址。允许的地址:127.0.0.1、localhost、::1、0.0.0.0、:: 或局域网 IP".to_string(), + "无效的监听地址。允许的地址:127.0.0.1、localhost、::1、0.0.0.0、:: 或局域网 IP" + .to_string(), ); } @@ -43,7 +48,7 @@ pub async fn save_config( let mut s = state.write().await; s.config = config.clone(); - + match config::save_config(&config) { Ok(()) => { tracing::info!("[CONFIG] 配置保存成功: host={}", config.server.host); diff --git a/src-tauri/src/app/runner.rs b/src-tauri/src/app/runner.rs index d8fc1fa82..bfb0ecda6 100644 --- a/src-tauri/src/app/runner.rs +++ b/src-tauri/src/app/runner.rs @@ -71,6 +71,7 @@ pub fn run() { enhanced_stats_service: enhanced_stats_service_state, batch_operations: batch_operations_state, native_agent: native_agent_state, + aster_agent: aster_agent_state, oauth_plugin_manager: oauth_plugin_manager_state, orchestrator: orchestrator_state, connect_state, @@ -153,6 +154,7 @@ pub fn run() { .manage(enhanced_stats_service_state) .manage(batch_operations_state) .manage(native_agent_state) + .manage(aster_agent_state) .manage(oauth_plugin_manager_state) .manage(orchestrator_state) .manage(connect_state) @@ -883,6 +885,7 @@ pub fn run() { commands::plugin_cmd::get_plugins_with_ui, commands::plugin_cmd::read_plugin_manifest_cmd, commands::plugin_cmd::launch_plugin_ui, + commands::plugin_cmd::frontend_debug_log, // Plugin RPC commands commands::plugin_rpc_cmd::plugin_rpc_connect, commands::plugin_rpc_cmd::plugin_rpc_disconnect, @@ -1072,6 +1075,16 @@ pub fn run() { commands::native_agent_cmd::native_agent_get_session, commands::native_agent_cmd::native_agent_delete_session, commands::native_agent_cmd::native_agent_list_sessions, + // Aster Agent commands + commands::aster_agent_cmd::aster_agent_init, + commands::aster_agent_cmd::aster_agent_status, + commands::aster_agent_cmd::aster_agent_configure_provider, + commands::aster_agent_cmd::aster_agent_chat_stream, + commands::aster_agent_cmd::aster_agent_stop, + commands::aster_agent_cmd::aster_session_create, + commands::aster_agent_cmd::aster_session_list, + commands::aster_agent_cmd::aster_session_get, + commands::aster_agent_cmd::aster_agent_confirm, // Models config commands commands::models_cmd::get_models_config, commands::models_cmd::save_models_config, diff --git a/src-tauri/src/commands/aster_agent_cmd.rs b/src-tauri/src/commands/aster_agent_cmd.rs new file mode 100644 index 000000000..277569a74 --- /dev/null +++ b/src-tauri/src/commands/aster_agent_cmd.rs @@ -0,0 +1,312 @@ +//! Aster Agent 命令模块 +//! +//! 提供基于 Aster 框架的 Tauri 命令 +//! 这是新的对话系统实现,与 native_agent_cmd.rs 并行存在 + +use crate::agent::aster_state::{ProviderConfig, SessionConfigBuilder}; +use crate::agent::event_converter::convert_agent_event; +use crate::agent::{ + AsterAgentState, AsterAgentWrapper, SessionDetail, SessionInfo, TauriAgentEvent, +}; +use aster::conversation::message::Message; +use futures::StreamExt; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use tauri::{AppHandle, Emitter, State}; + +/// Aster Agent 状态信息 +#[derive(Debug, Serialize)] +pub struct AsterAgentStatus { + pub initialized: bool, + pub provider_configured: bool, + pub provider_name: Option, + pub model_name: Option, +} + +/// Provider 配置请求 +#[derive(Debug, Deserialize)] +pub struct ConfigureProviderRequest { + pub provider_name: String, + pub model_name: String, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub base_url: Option, +} + +/// 初始化 Aster Agent +#[tauri::command] +pub async fn aster_agent_init( + state: State<'_, AsterAgentState>, +) -> Result { + tracing::info!("[AsterAgent] 初始化 Agent"); + + state.init_agent().await?; + + let provider_config = state.get_provider_config().await; + + tracing::info!("[AsterAgent] Agent 初始化成功"); + + Ok(AsterAgentStatus { + initialized: true, + provider_configured: provider_config.is_some(), + provider_name: provider_config.as_ref().map(|c| c.provider_name.clone()), + model_name: provider_config.as_ref().map(|c| c.model_name.clone()), + }) +} + +/// 配置 Aster Agent 的 Provider +#[tauri::command] +pub async fn aster_agent_configure_provider( + state: State<'_, AsterAgentState>, + request: ConfigureProviderRequest, + session_id: String, +) -> Result { + tracing::info!( + "[AsterAgent] 配置 Provider: {} / {}", + request.provider_name, + request.model_name + ); + + let config = ProviderConfig { + provider_name: request.provider_name, + model_name: request.model_name, + api_key: request.api_key, + base_url: request.base_url, + }; + + state + .configure_provider(config.clone(), &session_id) + .await?; + + Ok(AsterAgentStatus { + initialized: true, + provider_configured: true, + provider_name: Some(config.provider_name), + model_name: Some(config.model_name), + }) +} + +/// 获取 Aster Agent 状态 +#[tauri::command] +pub async fn aster_agent_status( + state: State<'_, AsterAgentState>, +) -> Result { + let provider_config = state.get_provider_config().await; + Ok(AsterAgentStatus { + initialized: state.is_initialized().await, + provider_configured: provider_config.is_some(), + provider_name: provider_config.as_ref().map(|c| c.provider_name.clone()), + model_name: provider_config.as_ref().map(|c| c.model_name.clone()), + }) +} + +/// 发送消息请求参数 +#[derive(Debug, Deserialize)] +pub struct AsterChatRequest { + pub message: String, + pub session_id: String, + pub event_name: String, + #[serde(default)] + pub images: Option>, + /// Provider 配置(可选,如果未配置则使用当前配置) + #[serde(default)] + pub provider_config: Option, +} + +/// 图片输入 +#[derive(Debug, Deserialize)] +pub struct ImageInput { + pub data: String, + pub media_type: String, +} + +/// 发送消息并获取流式响应 +#[tauri::command] +pub async fn aster_agent_chat_stream( + app: AppHandle, + state: State<'_, AsterAgentState>, + request: AsterChatRequest, +) -> Result<(), String> { + tracing::info!( + "[AsterAgent] 发送流式消息: session={}, event={}", + request.session_id, + request.event_name + ); + + // 确保 Agent 已初始化 + if !state.is_initialized().await { + state.init_agent().await?; + } + + // 如果提供了 Provider 配置,则配置 Provider + if let Some(provider_config) = &request.provider_config { + let config = ProviderConfig { + provider_name: provider_config.provider_name.clone(), + model_name: provider_config.model_name.clone(), + api_key: provider_config.api_key.clone(), + base_url: provider_config.base_url.clone(), + }; + state + .configure_provider(config, &request.session_id) + .await?; + } + + // 检查 Provider 是否已配置 + if !state.is_provider_configured().await { + return Err("Provider 未配置,请先调用 aster_agent_configure_provider".to_string()); + } + + // 创建取消令牌 + let cancel_token = state.create_cancel_token(&request.session_id).await; + + // 创建用户消息 + let user_message = Message::user().with_text(&request.message); + + // 创建会话配置 + let session_config = SessionConfigBuilder::new(&request.session_id).build(); + + // 获取 Agent Arc 并保持 guard 在整个流处理期间存活 + let agent_arc = state.get_agent_arc(); + let guard = agent_arc.read().await; + let agent = guard.as_ref().ok_or("Agent not initialized")?; + + // 获取事件流 + let stream_result = agent + .reply(user_message, session_config, Some(cancel_token.clone())) + .await; + + match stream_result { + Ok(mut stream) => { + // 处理事件流 + while let Some(event_result) = stream.next().await { + match event_result { + Ok(agent_event) => { + // 转换 Aster 事件为 Tauri 事件 + let tauri_events = convert_agent_event(agent_event); + + // 发送每个事件到前端 + for tauri_event in tauri_events { + if let Err(e) = app.emit(&request.event_name, &tauri_event) { + tracing::error!("[AsterAgent] 发送事件失败: {}", e); + } + } + } + Err(e) => { + // 发送错误事件 + let error_event = TauriAgentEvent::Error { + message: format!("Stream error: {}", e), + }; + if let Err(emit_err) = app.emit(&request.event_name, &error_event) { + tracing::error!("[AsterAgent] 发送错误事件失败: {}", emit_err); + } + } + } + } + + // 发送完成事件 + let done_event = TauriAgentEvent::FinalDone { usage: None }; + if let Err(e) = app.emit(&request.event_name, &done_event) { + tracing::error!("[AsterAgent] 发送完成事件失败: {}", e); + } + } + Err(e) => { + // 发送错误事件 + let error_event = TauriAgentEvent::Error { + message: format!("Agent error: {}", e), + }; + if let Err(emit_err) = app.emit(&request.event_name, &error_event) { + tracing::error!("[AsterAgent] 发送错误事件失败: {}", emit_err); + } + return Err(format!("Agent error: {}", e)); + } + } + + // guard 会在函数结束时自动释放(stream_result 先释放) + + // 清理取消令牌 + state.remove_cancel_token(&request.session_id).await; + + Ok(()) +} + +/// 停止当前会话 +#[tauri::command] +pub async fn aster_agent_stop( + state: State<'_, AsterAgentState>, + session_id: String, +) -> Result { + tracing::info!("[AsterAgent] 停止会话: {}", session_id); + Ok(state.cancel_session(&session_id).await) +} + +/// 创建新会话 +#[tauri::command] +pub async fn aster_session_create( + working_dir: Option, + name: Option, +) -> Result { + tracing::info!("[AsterAgent] 创建会话: name={:?}", name); + let dir = working_dir.map(PathBuf::from); + AsterAgentWrapper::create_session(dir, name).await +} + +/// 列出所有会话 +#[tauri::command] +pub async fn aster_session_list() -> Result, String> { + tracing::info!("[AsterAgent] 列出会话"); + AsterAgentWrapper::list_sessions().await +} + +/// 获取会话详情 +#[tauri::command] +pub async fn aster_session_get(session_id: String) -> Result { + tracing::info!("[AsterAgent] 获取会话: {}", session_id); + AsterAgentWrapper::get_session(&session_id).await +} + +/// 确认权限请求 +#[derive(Debug, Deserialize)] +pub struct ConfirmRequest { + pub request_id: String, + pub confirmed: bool, + pub response: Option, +} + +/// 确认权限请求(用于工具调用确认等) +#[tauri::command] +pub async fn aster_agent_confirm( + state: State<'_, AsterAgentState>, + request: ConfirmRequest, +) -> Result<(), String> { + tracing::info!( + "[AsterAgent] 确认请求: id={}, confirmed={}", + request.request_id, + request.confirmed + ); + + // TODO: 实现权限确认逻辑 + // 这需要 Aster 框架支持 confirmation_tx 通道 + // 目前先返回成功 + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_aster_chat_request_deserialize() { + let json = r#"{ + "message": "Hello", + "session_id": "test-session", + "event_name": "agent_stream" + }"#; + + let request: AsterChatRequest = serde_json::from_str(json).unwrap(); + assert_eq!(request.message, "Hello"); + assert_eq!(request.session_id, "test-session"); + assert_eq!(request.event_name, "agent_stream"); + } +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 519b9f71f..d2896cd47 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,5 +1,6 @@ pub mod agent_cmd; pub mod api_key_provider_cmd; +pub mod aster_agent_cmd; pub mod auto_fix_cmd; pub mod browser_interceptor_cmd; pub mod config_cmd; diff --git a/src-tauri/src/commands/native_agent_cmd.rs b/src-tauri/src/commands/native_agent_cmd.rs index 39e00561b..35ffb63e1 100644 --- a/src-tauri/src/commands/native_agent_cmd.rs +++ b/src-tauri/src/commands/native_agent_cmd.rs @@ -267,6 +267,7 @@ pub async fn native_agent_chat_stream( timestamp: chrono::Utc::now().to_rfc3339(), tool_calls: None, tool_call_id: None, + reasoning_content: None, }; if let Err(e) = AgentDao::add_message(&conn, sid, &user_message) { tracing::warn!("[NativeAgent] 保存用户消息到数据库失败: {}", e); @@ -368,6 +369,7 @@ pub async fn native_agent_chat_stream( timestamp: chrono::Utc::now().to_rfc3339(), tool_calls: None, tool_call_id: None, + reasoning_content: None, }; if let Err(e) = AgentDao::add_message(&conn, sid, &assistant_message) { tracing::warn!("[NativeAgent] 保存助手消息到数据库失败: {}", e); diff --git a/src-tauri/src/commands/network_cmd.rs b/src-tauri/src/commands/network_cmd.rs index b974bb514..a20a182e5 100644 --- a/src-tauri/src/commands/network_cmd.rs +++ b/src-tauri/src/commands/network_cmd.rs @@ -42,7 +42,7 @@ fn get_local_ip() -> Option { socket.connect("8.8.8.8:80").ok()?; let local_addr = socket.local_addr().ok()?; let ip_str = local_addr.ip().to_string(); - + // 检查是否是 VPN 地址 (198.18.x.x) if let IpAddr::V4(ipv4) = local_addr.ip() { if ipv4.octets()[0] == 198 && (ipv4.octets()[1] == 18 || ipv4.octets()[1] == 19) { @@ -60,7 +60,7 @@ fn get_local_ip() -> Option { return Some("127.0.0.1".to_string()); } } - + Some(ip_str) } @@ -108,7 +108,6 @@ fn get_all_local_ips() -> Vec { ips } - /// 根据监听地址生成可访问的 URL /// /// 用于生成客户端配置中的 API URL。 diff --git a/src-tauri/src/commands/plugin_cmd.rs b/src-tauri/src/commands/plugin_cmd.rs index f2539bbe3..2211be81c 100644 --- a/src-tauri/src/commands/plugin_cmd.rs +++ b/src-tauri/src/commands/plugin_cmd.rs @@ -19,6 +19,12 @@ use tokio::sync::RwLock; use super::plugin_install_cmd::PluginInstallerState; +/// 前端调试日志命令 +#[tauri::command] +pub fn frontend_debug_log(message: String) { + println!("[Frontend] {}", message); +} + /// 插件管理器状态 pub struct PluginManagerState(pub Arc>); @@ -144,7 +150,9 @@ pub async fn get_plugins_dir( state: tauri::State<'_, PluginManagerState>, ) -> Result { let manager = state.0.read().await; - Ok(manager.plugins_dir().to_string_lossy().to_string()) + let dir = manager.plugins_dir().to_string_lossy().to_string(); + println!("[get_plugins_dir] 返回: {}", dir); + Ok(dir) } // ============================================================================ @@ -226,6 +234,18 @@ pub async fn get_plugins_with_ui( // 获取所有已安装插件(从数据库) let installed_plugins = installer.list_installed().map_err(|e| e.to_string())?; + tracing::info!( + "[get_plugins_with_ui] 从数据库获取到 {} 个已安装插件", + installed_plugins.len() + ); + for p in &installed_plugins { + tracing::info!( + "[get_plugins_with_ui] 数据库插件: id={}, install_path={:?}", + p.id, + p.install_path + ); + } + let mut registered_ids: std::collections::HashSet = installed_plugins.iter().map(|p| p.id.clone()).collect(); @@ -254,11 +274,23 @@ pub async fn get_plugins_with_ui( }) .collect(); + tracing::info!( + "[get_plugins_with_ui] 从数据库筛选出 {} 个带 UI 的插件", + ui_plugins.len() + ); + // 扫描插件目录中未注册的插件 let plugins_dir = manager.plugins_dir(); + tracing::info!( + "[get_plugins_with_ui] PluginManager plugins_dir={:?}, exists={}", + plugins_dir, + plugins_dir.exists() + ); + if let Ok(entries) = std::fs::read_dir(plugins_dir) { for entry in entries.flatten() { let path = entry.path(); + tracing::debug!("[get_plugins_with_ui] 扫描目录: {:?}", path); if path.is_dir() { let plugin_id = path .file_name() @@ -266,14 +298,36 @@ pub async fn get_plugins_with_ui( .map(|s| s.to_string()); if let Some(id) = plugin_id { + tracing::info!( + "[get_plugins_with_ui] 发现目录: id={}, already_registered={}", + id, + registered_ids.contains(&id) + ); + // 跳过已注册的插件 if registered_ids.contains(&id) { continue; } // 尝试读取 manifest - if let Some(manifest) = read_plugin_manifest(&path) { + let manifest_result = read_plugin_manifest(&path); + tracing::info!( + "[get_plugins_with_ui] 读取 manifest: id={}, success={}", + id, + manifest_result.is_some() + ); + + if let Some(manifest) = manifest_result { + tracing::info!( + "[get_plugins_with_ui] manifest: name={}, has_ui={}", + manifest.name, + manifest.ui.is_some() + ); if let Some(ui_config) = manifest.ui { + tracing::info!( + "[get_plugins_with_ui] ui_config: surfaces={:?}", + ui_config.surfaces + ); if !ui_config.surfaces.is_empty() { ui_plugins.push(PluginUIInfo { plugin_id: id.clone(), @@ -289,8 +343,16 @@ pub async fn get_plugins_with_ui( } } } + } else { + tracing::error!("[get_plugins_with_ui] 无法读取插件目录: {:?}", plugins_dir); } + tracing::info!( + "[get_plugins_with_ui] 最终返回 {} 个带 UI 的插件: {:?}", + ui_plugins.len(), + ui_plugins.iter().map(|p| &p.plugin_id).collect::>() + ); + Ok(ui_plugins) } @@ -341,26 +403,58 @@ pub async fn handle_plugin_action( /// /// 从插件目录读取 plugin.json 文件 /// 用于检查插件是否存在于文件系统中(即使未在数据库中注册) +/// 首先尝试从 PluginManager 的 plugins_dir 读取,如果失败则尝试从数据库中的 install_path 读取 #[tauri::command] pub async fn read_plugin_manifest_cmd( state: tauri::State<'_, PluginManagerState>, + installer_state: tauri::State<'_, PluginInstallerState>, plugin_id: String, ) -> Result, String> { let manager = state.0.read().await; let plugins_dir = manager.plugins_dir(); let plugin_path = plugins_dir.join(&plugin_id); - tracing::debug!( - "read_plugin_manifest_cmd: plugin_id={}, plugins_dir={:?}, plugin_path={:?}", + // 使用 println! 确保在 release 版本中也能看到日志 + println!( + "[read_plugin_manifest_cmd] plugin_id={}, plugins_dir={:?}, plugin_path={:?}, exists={}", plugin_id, plugins_dir, - plugin_path + plugin_path, + plugin_path.exists() ); - let result = read_plugin_manifest(&plugin_path); - tracing::debug!("read_plugin_manifest_cmd: result={:?}", result.is_some()); + // 首先尝试从 PluginManager 的 plugins_dir 读取 + if let Some(manifest) = read_plugin_manifest(&plugin_path) { + println!("[read_plugin_manifest_cmd] found in plugins_dir"); + println!( + "[read_plugin_manifest_cmd] manifest: name={}, version={}, plugin_type={:?}, has_ui={}", + manifest.name, + manifest.version, + manifest.plugin_type, + manifest.ui.is_some() + ); + // 输出序列化后的 JSON + if let Ok(json) = serde_json::to_string(&manifest) { + println!("[read_plugin_manifest_cmd] JSON: {}", json); + } + return Ok(Some(manifest)); + } - Ok(result) + // 如果失败,尝试从数据库中的 install_path 读取 + let installer = installer_state.0.read().await; + if let Ok(Some(installed)) = installer.get_plugin(&plugin_id) { + println!( + "[read_plugin_manifest_cmd] trying install_path={:?}", + installed.install_path + ); + if let Some(manifest) = read_plugin_manifest(&installed.install_path) { + println!("[read_plugin_manifest_cmd] found in install_path"); + return Ok(Some(manifest)); + } + } + + println!("[read_plugin_manifest_cmd] not found"); + Ok(None) } /// 启动插件 UI(用于 binary 类型插件) @@ -369,15 +463,29 @@ pub async fn read_plugin_manifest_cmd( #[tauri::command] pub async fn launch_plugin_ui( state: tauri::State<'_, PluginManagerState>, + installer_state: tauri::State<'_, PluginInstallerState>, plugin_id: String, ) -> Result<(), String> { let manager = state.0.read().await; let plugins_dir = manager.plugins_dir(); let plugin_path = plugins_dir.join(&plugin_id); - // 读取插件清单 - let manifest = - read_plugin_manifest(&plugin_path).ok_or_else(|| format!("插件 {} 不存在", plugin_id))?; + // 首先尝试从 PluginManager 的 plugins_dir 读取 + let (manifest, actual_plugin_path) = if let Some(m) = read_plugin_manifest(&plugin_path) { + (m, plugin_path) + } else { + // 如果失败,尝试从数据库中的 install_path 读取 + let installer = installer_state.0.read().await; + if let Ok(Some(installed)) = installer.get_plugin(&plugin_id) { + if let Some(m) = read_plugin_manifest(&installed.install_path) { + (m, installed.install_path.clone()) + } else { + return Err(format!("插件 {} 不存在", plugin_id)); + } + } else { + return Err(format!("插件 {} 不存在", plugin_id)); + } + }; // 检查是否是 binary 类型 if manifest.plugin_type != PluginType::Binary { @@ -390,7 +498,7 @@ pub async fn launch_plugin_ui( .ok_or_else(|| "插件缺少 binary 配置".to_string())?; let binary_name = &binary_config.binary_name; - let binary_path = plugin_path.join(binary_name); + let binary_path = actual_plugin_path.join(binary_name); if !binary_path.exists() { return Err(format!("插件二进制文件不存在: {}", binary_path.display())); diff --git a/src-tauri/src/converter/anthropic_to_openai.rs b/src-tauri/src/converter/anthropic_to_openai.rs index 7e905f599..230c33e9b 100644 --- a/src-tauri/src/converter/anthropic_to_openai.rs +++ b/src-tauri/src/converter/anthropic_to_openai.rs @@ -16,6 +16,7 @@ pub fn convert_anthropic_to_openai(request: &AnthropicMessagesRequest) -> ChatCo content: Some(MessageContent::Text(system_text)), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } } @@ -83,6 +84,7 @@ fn convert_anthropic_message(msg: &AnthropicMessage) -> Vec { content: Some(MessageContent::Text(s.clone())), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } serde_json::Value::Array(parts) => { @@ -147,6 +149,7 @@ fn convert_anthropic_message(msg: &AnthropicMessage) -> Vec { content, tool_calls: tc, tool_call_id: None, + reasoning_content: None, }); } // 处理 user 消息 @@ -158,6 +161,7 @@ fn convert_anthropic_message(msg: &AnthropicMessage) -> Vec { content: Some(MessageContent::Text(content)), tool_calls: None, tool_call_id: Some(tool_use_id), + reasoning_content: None, }); } @@ -168,6 +172,7 @@ fn convert_anthropic_message(msg: &AnthropicMessage) -> Vec { content: Some(MessageContent::Text(text_parts.join(""))), tool_calls: None, tool_call_id: None, + reasoning_content: None, }); } } diff --git a/src-tauri/src/database/dao/agent.rs b/src-tauri/src/database/dao/agent.rs index b089fd4b0..de5bc1760 100644 --- a/src-tauri/src/database/dao/agent.rs +++ b/src-tauri/src/database/dao/agent.rs @@ -201,6 +201,7 @@ impl AgentDao { timestamp, tool_calls, tool_call_id, + reasoning_content: None, }) })?; diff --git a/src-tauri/src/models/openai.rs b/src-tauri/src/models/openai.rs index 4ea0b8af4..2119cb667 100644 --- a/src-tauri/src/models/openai.rs +++ b/src-tauri/src/models/openai.rs @@ -58,6 +58,10 @@ pub struct ChatMessage { pub tool_calls: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, + /// 推理内容(DeepSeek R1 等模型的思维链内容) + /// DeepSeek Reasoner 在 Tool Calls 场景下要求此字段 + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_content: Option, } impl ChatMessage { diff --git a/src-tauri/src/plugin/installer/mod.rs b/src-tauri/src/plugin/installer/mod.rs index 41fae142e..67e9db49f 100644 --- a/src-tauri/src/plugin/installer/mod.rs +++ b/src-tauri/src/plugin/installer/mod.rs @@ -21,3 +21,6 @@ pub use types::{ NoopProgressCallback, PackageFormat, ProgressCallback, }; pub use validator::PackageValidator; + +#[cfg(test)] +mod tests; diff --git a/src-tauri/src/plugin/installer/tests.rs b/src-tauri/src/plugin/installer/tests.rs new file mode 100644 index 000000000..bd7e8c48b --- /dev/null +++ b/src-tauri/src/plugin/installer/tests.rs @@ -0,0 +1,554 @@ +//! 插件安装器测试 +//! +//! 测试插件安装、注册、加载的完整流程 + +use super::*; +use std::fs; +use std::io::Write; +use tempfile::TempDir; +use zip::write::FileOptions; +use zip::ZipWriter; + +/// 创建测试用的插件包 +fn create_test_plugin_zip(dir: &TempDir, plugin_id: &str, version: &str) -> std::path::PathBuf { + let zip_path = dir.path().join(format!("{}.zip", plugin_id)); + let file = fs::File::create(&zip_path).unwrap(); + let mut zip = ZipWriter::new(file); + + let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored); + + // 创建 plugin.json + let manifest = serde_json::json!({ + "name": plugin_id, + "version": version, + "description": format!("Test plugin {}", plugin_id), + "author": "Test Author", + "plugin_type": "script", + "entry": "config.json", + "ui": { + "surfaces": ["tools"], + "entry": "dist/index.js", + "icon": "Package" + } + }); + + zip.start_file("plugin.json", options).unwrap(); + zip.write_all(manifest.to_string().as_bytes()).unwrap(); + + // 创建 config.json + let config = serde_json::json!({ + "enabled": true + }); + zip.start_file("config.json", options).unwrap(); + zip.write_all(config.to_string().as_bytes()).unwrap(); + + // 创建 dist/index.js + zip.start_file("dist/index.js", options).unwrap(); + zip.write_all(b"// Plugin UI code").unwrap(); + + zip.finish().unwrap(); + zip_path +} + +/// 创建测试注册表 +fn create_test_registry(dir: &TempDir) -> PluginRegistry { + let db_path = dir.path().join("test.db"); + let registry = PluginRegistry::from_path(&db_path).unwrap(); + registry.init_tables().unwrap(); + registry +} + +#[cfg(test)] +mod registry_tests { + use super::*; + + #[test] + fn test_registry_create_and_register() { + let temp_dir = TempDir::new().unwrap(); + let registry = create_test_registry(&temp_dir); + + let plugin = InstalledPlugin { + id: "test-plugin".to_string(), + name: "Test Plugin".to_string(), + version: "1.0.0".to_string(), + description: "A test plugin".to_string(), + author: Some("Test Author".to_string()), + install_path: temp_dir.path().join("test-plugin"), + installed_at: chrono::Utc::now(), + source: InstallSource::Local { + path: "/tmp/test.zip".to_string(), + }, + enabled: true, + }; + + // 注册插件 + registry.register(&plugin).unwrap(); + + // 验证插件存在 + assert!(registry.exists("test-plugin").unwrap()); + assert!(!registry.exists("non-existent").unwrap()); + } + + #[test] + fn test_registry_get_plugin() { + let temp_dir = TempDir::new().unwrap(); + let registry = create_test_registry(&temp_dir); + + let plugin = InstalledPlugin { + id: "get-test".to_string(), + name: "Get Test Plugin".to_string(), + version: "2.0.0".to_string(), + description: "Plugin for get test".to_string(), + author: None, + install_path: temp_dir.path().join("get-test"), + installed_at: chrono::Utc::now(), + source: InstallSource::Url { + url: "https://example.com/plugin.zip".to_string(), + }, + enabled: false, + }; + + registry.register(&plugin).unwrap(); + + // 获取插件 + let retrieved = registry.get("get-test").unwrap().unwrap(); + assert_eq!(retrieved.id, "get-test"); + assert_eq!(retrieved.name, "Get Test Plugin"); + assert_eq!(retrieved.version, "2.0.0"); + assert!(!retrieved.enabled); + + // 获取不存在的插件 + let not_found = registry.get("non-existent").unwrap(); + assert!(not_found.is_none()); + } + + #[test] + fn test_registry_list_plugins() { + let temp_dir = TempDir::new().unwrap(); + let registry = create_test_registry(&temp_dir); + + // 注册多个插件 + for i in 1..=3 { + let plugin = InstalledPlugin { + id: format!("plugin-{}", i), + name: format!("Plugin {}", i), + version: "1.0.0".to_string(), + description: format!("Plugin {} description", i), + author: Some("Author".to_string()), + install_path: temp_dir.path().join(format!("plugin-{}", i)), + installed_at: chrono::Utc::now(), + source: InstallSource::Local { + path: format!("/tmp/plugin-{}.zip", i), + }, + enabled: true, + }; + registry.register(&plugin).unwrap(); + } + + // 列出所有插件 + let plugins = registry.list().unwrap(); + assert_eq!(plugins.len(), 3); + } + + #[test] + fn test_registry_unregister_plugin() { + let temp_dir = TempDir::new().unwrap(); + let registry = create_test_registry(&temp_dir); + + let plugin = InstalledPlugin { + id: "unregister-test".to_string(), + name: "Unregister Test".to_string(), + version: "1.0.0".to_string(), + description: "".to_string(), + author: None, + install_path: temp_dir.path().join("unregister-test"), + installed_at: chrono::Utc::now(), + source: InstallSource::Local { + path: "/tmp/test.zip".to_string(), + }, + enabled: true, + }; + + registry.register(&plugin).unwrap(); + assert!(registry.exists("unregister-test").unwrap()); + + // 注销插件 + registry.unregister("unregister-test").unwrap(); + assert!(!registry.exists("unregister-test").unwrap()); + + // 注销不存在的插件应该返回错误 + let result = registry.unregister("non-existent"); + assert!(result.is_err()); + } + + #[test] + fn test_registry_set_enabled() { + let temp_dir = TempDir::new().unwrap(); + let registry = create_test_registry(&temp_dir); + + let plugin = InstalledPlugin { + id: "enabled-test".to_string(), + name: "Enabled Test".to_string(), + version: "1.0.0".to_string(), + description: "".to_string(), + author: None, + install_path: temp_dir.path().join("enabled-test"), + installed_at: chrono::Utc::now(), + source: InstallSource::Local { + path: "/tmp/test.zip".to_string(), + }, + enabled: true, + }; + + registry.register(&plugin).unwrap(); + + // 禁用插件 + registry.set_enabled("enabled-test", false).unwrap(); + let retrieved = registry.get("enabled-test").unwrap().unwrap(); + assert!(!retrieved.enabled); + + // 启用插件 + registry.set_enabled("enabled-test", true).unwrap(); + let retrieved = registry.get("enabled-test").unwrap().unwrap(); + assert!(retrieved.enabled); + } +} + +#[cfg(test)] +mod installer_tests { + use super::*; + + #[test] + fn test_installer_from_paths() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + let installer = + PluginInstaller::from_paths(plugins_dir.clone(), temp_install_dir.clone(), &db_path) + .unwrap(); + + assert_eq!(installer.plugins_dir(), plugins_dir); + assert_eq!(installer.temp_dir(), temp_install_dir); + } + + #[test] + fn test_installer_list_installed_empty() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + let installer = + PluginInstaller::from_paths(plugins_dir, temp_install_dir, &db_path).unwrap(); + + let plugins = installer.list_installed().unwrap(); + assert!(plugins.is_empty()); + } + + #[test] + fn test_installer_is_installed() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + let installer = + PluginInstaller::from_paths(plugins_dir, temp_install_dir, &db_path).unwrap(); + + // 未安装的插件 + assert!(!installer.is_installed("non-existent").unwrap()); + } + + #[tokio::test] + async fn test_installer_install_from_file() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + // 创建测试插件包 + let zip_path = create_test_plugin_zip(&temp_dir, "local-test-plugin", "1.0.0"); + + let installer = + PluginInstaller::from_paths(plugins_dir.clone(), temp_install_dir, &db_path).unwrap(); + + // 安装插件 + let progress = NoopProgressCallback; + let result = installer.install_from_file(&zip_path, &progress).await; + + assert!(result.is_ok(), "Install failed: {:?}", result.err()); + + let installed = result.unwrap(); + assert_eq!(installed.id, "local-test-plugin"); + assert_eq!(installed.version, "1.0.0"); + assert!(installed.enabled); + + // 验证插件已注册 + assert!(installer.is_installed("local-test-plugin").unwrap()); + + // 验证插件文件已复制 + let plugin_dir = plugins_dir.join("local-test-plugin"); + assert!(plugin_dir.exists()); + assert!(plugin_dir.join("plugin.json").exists()); + assert!(plugin_dir.join("config.json").exists()); + assert!(plugin_dir.join("dist/index.js").exists()); + } + + #[tokio::test] + async fn test_installer_uninstall() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + // 创建并安装测试插件 + let zip_path = create_test_plugin_zip(&temp_dir, "uninstall-test", "1.0.0"); + + let installer = + PluginInstaller::from_paths(plugins_dir.clone(), temp_install_dir, &db_path).unwrap(); + + let progress = NoopProgressCallback; + installer + .install_from_file(&zip_path, &progress) + .await + .unwrap(); + + // 验证已安装 + assert!(installer.is_installed("uninstall-test").unwrap()); + assert!(plugins_dir.join("uninstall-test").exists()); + + // 卸载插件 + installer.uninstall("uninstall-test").await.unwrap(); + + // 验证已卸载 + assert!(!installer.is_installed("uninstall-test").unwrap()); + assert!(!plugins_dir.join("uninstall-test").exists()); + } + + #[tokio::test] + async fn test_installer_reinstall_updates_version() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + let installer = + PluginInstaller::from_paths(plugins_dir, temp_install_dir, &db_path).unwrap(); + + let progress = NoopProgressCallback; + + // 安装 v1.0.0 + let zip_v1 = create_test_plugin_zip(&temp_dir, "version-test", "1.0.0"); + let installed_v1 = installer + .install_from_file(&zip_v1, &progress) + .await + .unwrap(); + assert_eq!(installed_v1.version, "1.0.0"); + + // 安装 v2.0.0(覆盖安装) + let zip_v2 = create_test_plugin_zip(&temp_dir, "version-test", "2.0.0"); + let installed_v2 = installer + .install_from_file(&zip_v2, &progress) + .await + .unwrap(); + assert_eq!(installed_v2.version, "2.0.0"); + + // 验证数据库中的版本已更新 + let plugin = installer.get_plugin("version-test").unwrap().unwrap(); + assert_eq!(plugin.version, "2.0.0"); + } +} + +#[cfg(test)] +mod validator_tests { + use super::*; + + #[test] + fn test_validator_valid_package() { + let temp_dir = TempDir::new().unwrap(); + let zip_path = create_test_plugin_zip(&temp_dir, "valid-plugin", "1.0.0"); + + let validator = PackageValidator::new(); + + // 验证格式 + let format = validator.validate_format(&zip_path); + assert!( + format.is_ok(), + "Format validation failed: {:?}", + format.err() + ); + + // 提取并验证 manifest + let manifest = validator.extract_and_validate_manifest(&zip_path, format.unwrap()); + assert!( + manifest.is_ok(), + "Manifest validation failed: {:?}", + manifest.err() + ); + + let manifest = manifest.unwrap(); + assert_eq!(manifest.name, "valid-plugin"); + assert_eq!(manifest.version, "1.0.0"); + } + + #[test] + fn test_validator_missing_manifest() { + let temp_dir = TempDir::new().unwrap(); + let zip_path = temp_dir.path().join("no-manifest.zip"); + + // 创建没有 plugin.json 的 zip + let file = fs::File::create(&zip_path).unwrap(); + let mut zip = ZipWriter::new(file); + let options = FileOptions::default(); + zip.start_file("config.json", options).unwrap(); + zip.write_all(b"{}").unwrap(); + zip.finish().unwrap(); + + let validator = PackageValidator::new(); + let format = validator.validate_format(&zip_path).unwrap(); + let result = validator.extract_and_validate_manifest(&zip_path, format); + + assert!(result.is_err()); + } + + #[test] + fn test_validator_invalid_manifest_json() { + let temp_dir = TempDir::new().unwrap(); + let zip_path = temp_dir.path().join("invalid-json.zip"); + + // 创建有无效 JSON 的 zip + let file = fs::File::create(&zip_path).unwrap(); + let mut zip = ZipWriter::new(file); + let options = FileOptions::default(); + zip.start_file("plugin.json", options).unwrap(); + zip.write_all(b"{ invalid json }").unwrap(); + zip.finish().unwrap(); + + let validator = PackageValidator::new(); + let format = validator.validate_format(&zip_path).unwrap(); + let result = validator.extract_and_validate_manifest(&zip_path, format); + + assert!(result.is_err()); + } + + #[test] + fn test_validator_missing_required_fields() { + let temp_dir = TempDir::new().unwrap(); + let zip_path = temp_dir.path().join("missing-fields.zip"); + + // 创建缺少必填字段的 manifest + let file = fs::File::create(&zip_path).unwrap(); + let mut zip = ZipWriter::new(file); + let options = FileOptions::default(); + zip.start_file("plugin.json", options).unwrap(); + // 缺少 name 和 version + zip.write_all(b"{\"description\": \"test\"}").unwrap(); + zip.finish().unwrap(); + + let validator = PackageValidator::new(); + let format = validator.validate_format(&zip_path).unwrap(); + let result = validator.extract_and_validate_manifest(&zip_path, format); + + assert!(result.is_err()); + } +} + +#[cfg(test)] +mod integration_tests { + use super::*; + + /// 测试完整的安装-加载-卸载流程 + #[tokio::test] + async fn test_full_plugin_lifecycle() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + // 创建测试插件包 + let zip_path = create_test_plugin_zip(&temp_dir, "lifecycle-test", "1.0.0"); + + let installer = + PluginInstaller::from_paths(plugins_dir.clone(), temp_install_dir, &db_path).unwrap(); + + let progress = NoopProgressCallback; + + // 1. 安装插件 + let installed = installer + .install_from_file(&zip_path, &progress) + .await + .unwrap(); + assert_eq!(installed.id, "lifecycle-test"); + + // 2. 验证插件已注册 + assert!(installer.is_installed("lifecycle-test").unwrap()); + + // 3. 获取插件信息 + let plugin_info = installer.get_plugin("lifecycle-test").unwrap().unwrap(); + assert_eq!(plugin_info.version, "1.0.0"); + assert!(plugin_info.enabled); + + // 4. 验证插件文件存在 + let plugin_dir = plugins_dir.join("lifecycle-test"); + assert!(plugin_dir.join("plugin.json").exists()); + assert!(plugin_dir.join("dist/index.js").exists()); + + // 5. 列出所有插件 + let all_plugins = installer.list_installed().unwrap(); + assert_eq!(all_plugins.len(), 1); + assert_eq!(all_plugins[0].id, "lifecycle-test"); + + // 6. 卸载插件 + installer.uninstall("lifecycle-test").await.unwrap(); + + // 7. 验证插件已移除 + assert!(!installer.is_installed("lifecycle-test").unwrap()); + assert!(!plugin_dir.exists()); + + // 8. 列表应为空 + let all_plugins = installer.list_installed().unwrap(); + assert!(all_plugins.is_empty()); + } + + /// 测试多个插件同时安装 + #[tokio::test] + async fn test_multiple_plugins() { + let temp_dir = TempDir::new().unwrap(); + let plugins_dir = temp_dir.path().join("plugins"); + let temp_install_dir = temp_dir.path().join("temp"); + let db_path = temp_dir.path().join("test.db"); + + let installer = + PluginInstaller::from_paths(plugins_dir, temp_install_dir, &db_path).unwrap(); + + let progress = NoopProgressCallback; + + // 安装多个插件 + let plugin_ids = ["plugin-a", "plugin-b", "plugin-c"]; + for id in &plugin_ids { + let zip_path = create_test_plugin_zip(&temp_dir, id, "1.0.0"); + installer + .install_from_file(&zip_path, &progress) + .await + .unwrap(); + } + + // 验证所有插件都已安装 + let all_plugins = installer.list_installed().unwrap(); + assert_eq!(all_plugins.len(), 3); + + for id in &plugin_ids { + assert!(installer.is_installed(id).unwrap()); + } + + // 卸载一个插件 + installer.uninstall("plugin-b").await.unwrap(); + + // 验证只剩两个 + let remaining = installer.list_installed().unwrap(); + assert_eq!(remaining.len(), 2); + assert!(!installer.is_installed("plugin-b").unwrap()); + } +} diff --git a/src-tauri/src/plugin/loader.rs b/src-tauri/src/plugin/loader.rs index 0780bd351..f93fb72ff 100644 --- a/src-tauri/src/plugin/loader.rs +++ b/src-tauri/src/plugin/loader.rs @@ -19,7 +19,9 @@ impl PluginLoader { } pub fn default_plugins_dir() -> PathBuf { - dirs::config_dir() + // 使用 data_dir 而不是 config_dir,与 PluginInstaller 保持一致 + // 在 macOS 上两者相同,但在其他平台上可能不同 + dirs::data_dir() .unwrap_or_else(|| PathBuf::from(".")) .join("proxycast") .join("plugins") diff --git a/src-tauri/src/plugin/tests.rs b/src-tauri/src/plugin/tests.rs index 2fac07b2a..d5e042997 100644 --- a/src-tauri/src/plugin/tests.rs +++ b/src-tauri/src/plugin/tests.rs @@ -171,6 +171,40 @@ fn test_plugin_manifest_serde() { assert_eq!(parsed.hooks.len(), 2); } +#[test] +fn test_machine_id_tool_manifest_parsing() { + // 测试 machine-id-tool 的 plugin.json 格式 + let json = r#"{ + "name": "machine-id-tool", + "version": "0.4.0", + "description": "Machine ID 管理工具", + "author": "ProxyCast Team", + "homepage": "https://github.com/aiclientproxy/MachineIdTool", + "license": "MIT", + "plugin_type": "script", + "entry": "config.json", + "min_proxycast_version": "1.0.0", + "ui": { + "surfaces": ["tools", "sidebar"], + "entry": "dist/index.js", + "icon": "Cpu", + "title": "机器码管理工具", + "description": "查看、修改和管理系统机器码" + } + }"#; + + let manifest: PluginManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.name, "machine-id-tool"); + assert_eq!(manifest.version, "0.4.0"); + assert_eq!(manifest.plugin_type, PluginType::Script); + assert!(manifest.ui.is_some()); + + let ui = manifest.ui.unwrap(); + assert_eq!(ui.surfaces, vec!["tools", "sidebar"]); + assert_eq!(ui.entry, Some("dist/index.js".to_string())); + assert_eq!(ui.icon, Some("Cpu".to_string())); +} + // Property-based tests use proptest::prelude::*; diff --git a/src-tauri/src/server/mod.rs b/src-tauri/src/server/mod.rs index 4d826a1ca..97c423a7c 100644 --- a/src-tauri/src/server/mod.rs +++ b/src-tauri/src/server/mod.rs @@ -223,7 +223,7 @@ impl ServerState { } /// 解析绑定地址 - /// + /// /// 直接返回用户配置的地址,不做任何自动替换。 /// 如果地址无效,绑定时会失败并返回错误。 fn resolve_bind_host(&self, configured_host: &str) -> String { @@ -299,7 +299,7 @@ impl ServerState { // - 局域网 IP:检查是否在当前网卡列表中,如果不在则自动切换到当前局域网 IP let configured_host = self.config.server.host.clone(); let host = self.resolve_bind_host(&configured_host); - + // 如果地址发生了变化,记录日志 if host != configured_host { tracing::warn!( @@ -308,7 +308,7 @@ impl ServerState { host ); } - + let port = self.config.server.port; let api_key = self.config.server.api_key.clone(); let api_key_for_state = api_key.clone(); // 用于保存到 running_api_key @@ -1051,15 +1051,13 @@ async fn run_server( let addr: std::net::SocketAddr = format!("{host}:{port}") .parse() .map_err(|e| format!("无效的监听地址 {}:{} - {}", host, port, e))?; - - let listener = tokio::net::TcpListener::bind(addr) - .await - .map_err(|e| { - format!( - "无法绑定到 {}:{},错误: {}。请检查地址是否有效或端口是否被占用。", - host, port, e - ) - })?; + + let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| { + format!( + "无法绑定到 {}:{},错误: {}。请检查地址是否有效或端口是否被占用。", + host, port, e + ) + })?; tracing::info!("Server listening on {}", addr); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e08c8761c..7e44ebf18 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ProxyCast", - "version": "0.45.2", + "version": "0.46.0", "identifier": "com.proxycast.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.tsx b/src/App.tsx index 1d1fbe229..aab600d36 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,6 +28,7 @@ import { } from "./components/terminal"; import { flowEventManager } from "./lib/flowEventManager"; import { OnboardingWizard, useOnboardingState } from "./components/onboarding"; +import { STORAGE_KEYS } from "./components/onboarding/constants"; import { ConnectConfirmDialog } from "./components/connect"; import { showRegistryLoadError } from "./lib/utils/connectError"; import { useDeepLink } from "./hooks/useDeepLink"; @@ -36,6 +37,7 @@ import { ComponentDebugProvider } from "./contexts/ComponentDebugContext"; import { SoundProvider } from "./contexts/SoundProvider"; import { ComponentDebugOverlay } from "./components/dev"; import { Page } from "./types/page"; +import { windowApi } from "./lib/api/window"; const AppContainer = styled.div` display: flex; @@ -103,6 +105,31 @@ function AppContent() { flowEventManager.subscribe(); }, []); + // 应用启动时应用保存的窗口尺寸偏好 + useEffect(() => { + const applyWindowSizePreference = async () => { + const savedPreference = localStorage.getItem( + STORAGE_KEYS.WINDOW_SIZE_PREFERENCE, + ); + if (savedPreference) { + try { + if (savedPreference === "fullscreen") { + const isCurrentlyFullscreen = await windowApi.isFullscreen(); + if (!isCurrentlyFullscreen) { + await windowApi.toggleFullscreen(); + } + } else { + await windowApi.setWindowSizeByOption(savedPreference); + } + } catch (error) { + console.error("应用窗口尺寸偏好失败:", error); + } + } + }; + + applyWindowSizePreference(); + }, []); + // 处理 Registry 加载失败 // _Requirements: 7.2, 7.3_ useEffect(() => { diff --git a/src/components/agent/chat/components/ChatSettings.tsx b/src/components/agent/chat/components/ChatSettings.tsx index 020798397..d0970519a 100644 --- a/src/components/agent/chat/components/ChatSettings.tsx +++ b/src/components/agent/chat/components/ChatSettings.tsx @@ -24,6 +24,7 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; +import { getAgentBackend, setAgentBackend, type AgentBackend } from "../config"; // --- Styled Components --- @@ -142,6 +143,14 @@ interface ChatSettingsProps { export const ChatSettings: React.FC = ({ onClose }) => { // Local state for UI toggles (Mocking functional settings) const [fontSize, setFontSize] = useState([14]); + const [agentBackend, setBackend] = useState(getAgentBackend()); + + const handleBackendChange = (value: AgentBackend) => { + setBackend(value); + setAgentBackend(value); + // 提示用户需要刷新页面 + window.location.reload(); + }; return ( @@ -161,6 +170,30 @@ export const ChatSettings: React.FC = ({ onClose }) => { + {/* Agent Backend Settings */} + + +
+
Agent 引擎
+
切换后需刷新页面
+
+ +
+
+ + + {/* Message Settings */} diff --git a/src/components/agent/chat/components/DecisionPanel.tsx b/src/components/agent/chat/components/DecisionPanel.tsx new file mode 100644 index 000000000..4290cb656 --- /dev/null +++ b/src/components/agent/chat/components/DecisionPanel.tsx @@ -0,0 +1,346 @@ +/** + * DecisionPanel - 权限确认面板 + * + * 用于显示需要用户确认的操作,如: + * - 工具调用确认 + * - 用户问题(AskUserQuestion) + * - 权限请求 + * + * 参考 Claude-Cowork 的设计 + */ + +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { cn } from "@/lib/utils"; +import { + CheckCircle, + XCircle, + AlertTriangle, + HelpCircle, + Terminal, + FileEdit, + Globe, +} from "lucide-react"; +import type { ActionRequired, ConfirmResponse } from "@/stores/agentStore"; + +/** 问题选项 */ +interface QuestionOption { + label: string; + description?: string; +} + +/** 问题数据 */ +interface Question { + question: string; + header?: string; + options?: QuestionOption[]; + multiSelect?: boolean; +} + +interface DecisionPanelProps { + request: ActionRequired; + onSubmit: (response: ConfirmResponse) => void; +} + +/** 获取工具图标 */ +function getToolIcon(toolName?: string) { + if (!toolName) return ; + + const name = toolName.toLowerCase(); + if ( + name.includes("bash") || + name.includes("terminal") || + name.includes("exec") + ) { + return ; + } + if ( + name.includes("write") || + name.includes("edit") || + name.includes("file") + ) { + return ; + } + if (name.includes("web") || name.includes("fetch") || name.includes("http")) { + return ; + } + return ; +} + +/** 格式化工具参数 */ +function formatArguments(args?: Record): string { + if (!args) return ""; + try { + return JSON.stringify(args, null, 2); + } catch { + return String(args); + } +} + +export function DecisionPanel({ request, onSubmit }: DecisionPanelProps) { + // 解析问题数据(用于 ask_user 类型) + const questions: Question[] = (request as any).questions ?? []; + const [selectedOptions, setSelectedOptions] = useState< + Record + >({}); + const [otherInputs, setOtherInputs] = useState>({}); + + // 重置状态当请求变化时 + useEffect(() => { + setSelectedOptions({}); + setOtherInputs({}); + }, [request.requestId]); + + // 切换选项 + const toggleOption = ( + qIndex: number, + optionLabel: string, + multiSelect?: boolean, + ) => { + setSelectedOptions((prev) => { + const current = prev[qIndex] ?? []; + if (multiSelect) { + const next = current.includes(optionLabel) + ? current.filter((label) => label !== optionLabel) + : [...current, optionLabel]; + return { ...prev, [qIndex]: next }; + } + return { ...prev, [qIndex]: [optionLabel] }; + }); + }; + + // 构建答案 + const buildAnswers = () => { + const answers: Record = {}; + questions.forEach((q, qIndex) => { + const selected = selectedOptions[qIndex] ?? []; + const otherText = otherInputs[qIndex]?.trim() ?? ""; + let value = ""; + if (q.multiSelect) { + const combined = [...selected]; + if (otherText) combined.push(otherText); + value = combined.join(", "); + } else { + value = otherText || selected[0] || ""; + } + if (value) answers[q.question] = value; + }); + return answers; + }; + + // 检查是否���以提交 + const canSubmit = + questions.length === 0 || + questions.every((_, qIndex) => { + const selected = selectedOptions[qIndex] ?? []; + const otherText = otherInputs[qIndex]?.trim() ?? ""; + return selected.length > 0 || otherText.length > 0; + }); + + // 处理允许 + const handleAllow = () => { + const response = + questions.length > 0 ? JSON.stringify(buildAnswers()) : undefined; + onSubmit({ + requestId: request.requestId, + confirmed: true, + response, + }); + }; + + // 处理拒绝 + const handleDeny = () => { + onSubmit({ + requestId: request.requestId, + confirmed: false, + response: "用户拒绝了请求", + }); + }; + + // 渲染用户问题面板 + if (request.actionType === "ask_user" && questions.length > 0) { + return ( + + + + + Claude 的问题 + + + + {questions.map((q, qIndex) => ( +
+

{q.question}

+ + {q.header && ( + + {q.header} + + )} + + {/* 选项列表 */} + {q.options && q.options.length > 0 && ( +
+ {q.options.map((option, optIndex) => { + const isSelected = (selectedOptions[qIndex] ?? []).includes( + option.label, + ); + const shouldAutoSubmit = + questions.length === 1 && !q.multiSelect; + + return ( + + ); + })} +
+ )} + + {/* 其他输入 */} +
+ + + setOtherInputs((prev) => ({ + ...prev, + [qIndex]: e.target.value, + })) + } + /> +
+ + {q.multiSelect && ( +

+ 可以选择多个选项 +

+ )} +
+ ))} + + {/* 操作按钮 */} +
+ + +
+
+
+ ); + } + + // 渲染工具确认面板 + return ( + + + + + 权限请求 + + + + {/* 工具信息 */} +
+ {getToolIcon(request.toolName)} + + Claude 想要使用: + + {request.toolName || "未知工具"} + + +
+ + {/* 参数预览 */} + {request.arguments && ( +
+
+              {formatArguments(request.arguments)}
+            
+
+ )} + + {/* 操作按钮 */} +
+ + +
+
+
+ ); +} + +/** 权限确认列表组件 */ +export function DecisionPanelList({ + requests, + onSubmit, +}: { + requests: ActionRequired[]; + onSubmit: (response: ConfirmResponse) => void; +}) { + if (requests.length === 0) return null; + + return ( +
+ {requests.map((request) => ( + + ))} +
+ ); +} + +export default DecisionPanel; diff --git a/src/components/agent/chat/config.ts b/src/components/agent/chat/config.ts new file mode 100644 index 000000000..070278ed3 --- /dev/null +++ b/src/components/agent/chat/config.ts @@ -0,0 +1,35 @@ +/** + * Agent 后端配置 + * + * 用于切换 Native 和 Aster 后端 + */ + +export type AgentBackend = "native" | "aster"; + +// 默认使用 Native 后端,可以通过 localStorage 切换 +const STORAGE_KEY = "proxycast_agent_backend"; + +/** + * 获取当前 Agent 后端 + */ +export function getAgentBackend(): AgentBackend { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "aster" || stored === "native") { + return stored; + } + return "native"; // 默认 +} + +/** + * 设置 Agent 后端 + */ +export function setAgentBackend(backend: AgentBackend): void { + localStorage.setItem(STORAGE_KEY, backend); +} + +/** + * 是否使用 Aster 后端 + */ +export function useAsterBackend(): boolean { + return getAgentBackend() === "aster"; +} diff --git a/src/components/agent/chat/hooks/index.ts b/src/components/agent/chat/hooks/index.ts new file mode 100644 index 000000000..1b6e67246 --- /dev/null +++ b/src/components/agent/chat/hooks/index.ts @@ -0,0 +1,49 @@ +/** + * Agent Chat Hook 统一导出 + * + * 根据配置自动选择 Native 或 Aster 后端 + */ + +import { useAgentChat } from "./useAgentChat"; +import { useAsterAgentChat } from "./useAsterAgentChat"; +import { getAgentBackend } from "../config"; + +export type { Topic } from "./useAgentChat"; + +/** Hook 配置选项 */ +interface UseAgentChatUnifiedOptions { + systemPrompt?: string; + onWriteFile?: (content: string, fileName: string) => void; +} + +/** + * 统一的 Agent Chat Hook + * + * 根据 localStorage 配置自动选择后端: + * - "native": 使用原有的 Native Agent 后端 + * - "aster": 使用新的 Aster Agent 后端 + * + * 切换方式: + * localStorage.setItem("proxycast_agent_backend", "aster") + */ +export function useAgentChatUnified(options: UseAgentChatUnifiedOptions = {}) { + const backend = getAgentBackend(); + + // 根据配置选择 hook + // 注意:React hooks 规则要求 hooks 调用顺序一致 + // 这里我们总是调用两个 hook,但只使用其中一个的结果 + const nativeResult = useAgentChat(options); + const asterResult = useAsterAgentChat(options); + + if (backend === "aster") { + console.log("[AgentChat] 使用 Aster 后端"); + return asterResult; + } + + console.log("[AgentChat] 使用 Native 后端"); + return nativeResult; +} + +// 重新导出原有 hooks,便于直接使用 +export { useAgentChat } from "./useAgentChat"; +export { useAsterAgentChat } from "./useAsterAgentChat"; diff --git a/src/components/agent/chat/hooks/useAsterAgentChat.ts b/src/components/agent/chat/hooks/useAsterAgentChat.ts new file mode 100644 index 000000000..d0c7b31b9 --- /dev/null +++ b/src/components/agent/chat/hooks/useAsterAgentChat.ts @@ -0,0 +1,649 @@ +/** + * Aster Agent Chat Hook + * + * 基于 Aster 框架的聊天 hook + * 接口与 useAgentChat 保持一致,便于切换 + */ + +import { useState, useEffect, useRef, useCallback } from "react"; +import { toast } from "sonner"; +import { safeListen } from "@/lib/dev-bridge"; +import type { UnlistenFn } from "@tauri-apps/api/event"; +import { + initAsterAgent, + sendAsterMessageStream, + createAsterSession, + listAsterSessions, + getAsterSession, + stopAsterSession, + confirmAsterAction, + parseStreamEvent, + type StreamEvent, + type AsterSessionInfo, +} from "@/lib/api/agent"; +import { Message, MessageImage, ContentPart } from "../types"; + +/** 话题信息 */ +export interface Topic { + id: string; + title: string; + createdAt: Date; + messagesCount: number; +} + +/** 权限确认请求 */ +export interface ActionRequired { + requestId: string; + actionType: string; + toolName?: string; + arguments?: Record; + question?: string; + timestamp: Date; +} + +/** 确认响应 */ +export interface ConfirmResponse { + requestId: string; + confirmed: boolean; + response?: string; +} + +/** Hook 配置选项 */ +interface UseAsterAgentChatOptions { + systemPrompt?: string; + onWriteFile?: (content: string, fileName: string) => void; +} + +// 音效相关(复用) +let toolcallAudio: HTMLAudioElement | null = null; +let typewriterAudio: HTMLAudioElement | null = null; +let lastTypewriterTime = 0; +const TYPEWRITER_INTERVAL = 120; + +const initAudio = () => { + if (!toolcallAudio) { + toolcallAudio = new Audio("/sounds/tool-call.mp3"); + toolcallAudio.volume = 1; + toolcallAudio.load(); + } + if (!typewriterAudio) { + typewriterAudio = new Audio("/sounds/typing.mp3"); + typewriterAudio.volume = 0.6; + typewriterAudio.load(); + } +}; + +const getSoundEnabled = (): boolean => { + return localStorage.getItem("proxycast_sound_enabled") === "true"; +}; + +const playToolcallSound = () => { + if (!getSoundEnabled()) return; + initAudio(); + if (toolcallAudio) { + toolcallAudio.currentTime = 0; + toolcallAudio.play().catch(console.error); + } +}; + +const playTypewriterSound = () => { + if (!getSoundEnabled()) return; + const now = Date.now(); + if (now - lastTypewriterTime < TYPEWRITER_INTERVAL) return; + initAudio(); + if (typewriterAudio) { + typewriterAudio.currentTime = 0; + typewriterAudio.play().catch(console.error); + lastTypewriterTime = now; + } +}; + +/** + * 将前端 Provider 类型映射到 Aster Provider 名称 + */ +const mapProviderName = (providerType: string): string => { + const mapping: Record = { + // OpenAI 兼容 + openai: "openai", + "gpt-4": "openai", + "gpt-4o": "openai", + // Anthropic + claude: "anthropic", + anthropic: "anthropic", + // Google + google: "google", + gemini: "google", + // DeepSeek + deepseek: "custom_deepseek", + "deepseek-reasoner": "custom_deepseek", + // Ollama + ollama: "ollama", + // OpenRouter + openrouter: "openrouter", + // 其他 + groq: "groq", + mistral: "mistral", + }; + return mapping[providerType.toLowerCase()] || providerType; +}; + +export function useAsterAgentChat(options: UseAsterAgentChatOptions = {}) { + const { onWriteFile } = options; + + // 状态 + const [isInitialized, setIsInitialized] = useState(false); + const [sessionId, setSessionId] = useState(null); + const [messages, setMessages] = useState([]); + const [topics, setTopics] = useState([]); + const [isSending, setIsSending] = useState(false); + const [pendingActions, setPendingActions] = useState([]); + + // Provider/Model(本地状态) + const [providerType, setProviderType] = useState( + () => localStorage.getItem("agent_pref_provider") || "claude", + ); + const [model, setModel] = useState( + () => localStorage.getItem("agent_pref_model") || "claude-sonnet-4-5", + ); + + // Refs + const unlistenRef = useRef(null); + const currentAssistantMsgIdRef = useRef(null); + + // 持久化 provider/model + useEffect(() => { + localStorage.setItem("agent_pref_provider", providerType); + }, [providerType]); + + useEffect(() => { + localStorage.setItem("agent_pref_model", model); + }, [model]); + + // 初始化 Aster Agent + useEffect(() => { + const init = async () => { + try { + await initAsterAgent(); + setIsInitialized(true); + console.log("[AsterChat] Agent 初始化成功"); + // 初始化后加载话题列表 + const sessions = await listAsterSessions(); + const topicList: Topic[] = sessions.map((s: AsterSessionInfo) => ({ + id: s.id, + title: + s.name || + `话题 ${new Date(s.created_at).toLocaleDateString("zh-CN")}`, + createdAt: new Date(s.created_at), + messagesCount: s.messages_count, + })); + setTopics(topicList); + } catch (err) { + console.error("[AsterChat] 初始化失败:", err); + } + }; + init(); + }, []); + + // 加载话题列表 + const loadTopics = useCallback(async () => { + try { + const sessions = await listAsterSessions(); + const topicList: Topic[] = sessions.map((s: AsterSessionInfo) => ({ + id: s.id, + title: + s.name || + `话题 ${new Date(s.created_at).toLocaleDateString("zh-CN")}`, + createdAt: new Date(s.created_at), + messagesCount: s.messages_count, + })); + setTopics(topicList); + } catch (error) { + console.error("[AsterChat] 加载话题失败:", error); + } + }, []); + + // 确保有会话 + const ensureSession = useCallback(async (): Promise => { + if (sessionId) return sessionId; + + try { + const newSessionId = await createAsterSession(); + setSessionId(newSessionId); + return newSessionId; + } catch (error) { + console.error("[AsterChat] 创建会话失败:", error); + toast.error("创建会话失败"); + return null; + } + }, [sessionId]); + + // 辅助函数:追加文本到 contentParts + const appendTextToParts = ( + parts: ContentPart[], + text: string, + ): ContentPart[] => { + const newParts = [...parts]; + const lastPart = newParts[newParts.length - 1]; + + if (lastPart && lastPart.type === "text") { + newParts[newParts.length - 1] = { + type: "text", + text: lastPart.text + text, + }; + } else { + newParts.push({ type: "text", text }); + } + return newParts; + }; + + // 发送消息 + const sendMessage = useCallback( + async ( + content: string, + images: MessageImage[], + _webSearch?: boolean, + _thinking?: boolean, + ) => { + // 用户消息 + const userMsg: Message = { + id: crypto.randomUUID(), + role: "user", + content, + images: images.length > 0 ? images : undefined, + timestamp: new Date(), + }; + + // 助手消息占位符 + const assistantMsgId = crypto.randomUUID(); + const assistantMsg: Message = { + id: assistantMsgId, + role: "assistant", + content: "", + timestamp: new Date(), + isThinking: true, + thinkingContent: "思考中...", + contentParts: [], + }; + + setMessages((prev) => [...prev, userMsg, assistantMsg]); + setIsSending(true); + currentAssistantMsgIdRef.current = assistantMsgId; + + let accumulatedContent = ""; + let unlisten: UnlistenFn | null = null; + + try { + const activeSessionId = await ensureSession(); + if (!activeSessionId) throw new Error("无法创建会话"); + + const eventName = `aster_stream_${assistantMsgId}`; + + // 设置事件监听 + unlisten = await safeListen(eventName, (event) => { + const data = parseStreamEvent(event.payload); + if (!data) return; + + switch (data.type) { + case "text_delta": + accumulatedContent += data.text; + playTypewriterSound(); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + content: accumulatedContent, + thinkingContent: undefined, + contentParts: appendTextToParts( + msg.contentParts || [], + data.text, + ), + } + : msg, + ), + ); + break; + + case "tool_start": { + playToolcallSound(); + const newToolCall = { + id: data.tool_id, + name: data.tool_name, + arguments: data.arguments, + status: "running" as const, + startTime: new Date(), + }; + + // 检查是否是写入文件工具 + const toolName = data.tool_name.toLowerCase(); + if (toolName.includes("write") || toolName.includes("create")) { + try { + const args = JSON.parse(data.arguments || "{}"); + const filePath = args.path || args.file_path || args.filePath; + const fileContent = args.content || args.text || ""; + if (filePath && fileContent && onWriteFile) { + onWriteFile(fileContent, filePath); + } + } catch (e) { + console.warn("[AsterChat] 解析工具参数失败:", e); + } + } + + setMessages((prev) => + prev.map((msg) => { + if (msg.id !== assistantMsgId) return msg; + if (msg.toolCalls?.find((tc) => tc.id === data.tool_id)) + return msg; + return { + ...msg, + toolCalls: [...(msg.toolCalls || []), newToolCall], + contentParts: [ + ...(msg.contentParts || []), + { type: "tool_use" as const, toolCall: newToolCall }, + ], + }; + }), + ); + break; + } + + case "tool_end": + setMessages((prev) => + prev.map((msg) => { + if (msg.id !== assistantMsgId) return msg; + const updatedToolCalls = (msg.toolCalls || []).map((tc) => + tc.id === data.tool_id + ? { + ...tc, + status: data.result.success + ? ("completed" as const) + : ("failed" as const), + result: data.result, + endTime: new Date(), + } + : tc, + ); + const updatedContentParts = (msg.contentParts || []).map( + (part) => { + if ( + part.type === "tool_use" && + part.toolCall.id === data.tool_id + ) { + return { + ...part, + toolCall: { + ...part.toolCall, + status: data.result.success + ? ("completed" as const) + : ("failed" as const), + result: data.result, + endTime: new Date(), + }, + }; + } + return part; + }, + ); + return { + ...msg, + toolCalls: updatedToolCalls, + contentParts: updatedContentParts, + }; + }), + ); + break; + + case "final_done": + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + isThinking: false, + content: accumulatedContent || "(无响应)", + } + : msg, + ), + ); + setIsSending(false); + unlistenRef.current = null; + currentAssistantMsgIdRef.current = null; + if (unlisten) { + unlisten(); + unlisten = null; + } + break; + + case "error": + toast.error(`响应错误: ${data.message}`); + setMessages((prev) => + prev.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + isThinking: false, + content: accumulatedContent || `错误: ${data.message}`, + } + : msg, + ), + ); + setIsSending(false); + if (unlisten) { + unlisten(); + unlisten = null; + } + break; + + // 处理权限确认请求 + default: { + // 检查是否是 action_required 事件(通过原始 payload) + const rawEvent = event.payload as unknown as Record< + string, + unknown + >; + if (rawEvent.type === "action_required") { + const actionData: ActionRequired = { + requestId: rawEvent.request_id as string, + actionType: rawEvent.action_type as string, + toolName: (rawEvent.data as Record) + ?.tool_name as string, + arguments: (rawEvent.data as Record) + ?.arguments as Record, + question: (rawEvent.data as Record) + ?.question as string, + timestamp: new Date(), + }; + setPendingActions((prev) => [...prev, actionData]); + } + break; + } + } + }); + + unlistenRef.current = unlisten; + + // 发送请求 + const imagesToSend = + images.length > 0 + ? images.map((img) => ({ + data: img.data, + media_type: img.mediaType, + })) + : undefined; + + // 构建 Provider 配置 + const providerConfig = { + provider_name: mapProviderName(providerType), + model_name: model, + }; + + await sendAsterMessageStream( + content, + activeSessionId, + eventName, + imagesToSend, + providerConfig, + ); + } catch (error) { + console.error("[AsterChat] 发送失败:", error); + toast.error(`发送失败: ${error}`); + setMessages((prev) => prev.filter((msg) => msg.id !== assistantMsgId)); + setIsSending(false); + if (unlisten) unlisten(); + } + }, + [ensureSession, onWriteFile, providerType, model], + ); + + // 停止发送 + const stopSending = useCallback(async () => { + if (unlistenRef.current) { + unlistenRef.current(); + unlistenRef.current = null; + } + + if (sessionId) { + try { + await stopAsterSession(sessionId); + } catch (e) { + console.error("[AsterChat] 停止失败:", e); + } + } + + if (currentAssistantMsgIdRef.current) { + setMessages((prev) => + prev.map((msg) => + msg.id === currentAssistantMsgIdRef.current + ? { ...msg, isThinking: false, content: msg.content || "(已停止)" } + : msg, + ), + ); + currentAssistantMsgIdRef.current = null; + } + + setIsSending(false); + toast.info("已停止生成"); + }, [sessionId]); + + // 确认权限请求 + const confirmAction = useCallback(async (response: ConfirmResponse) => { + try { + await confirmAsterAction( + response.requestId, + response.confirmed, + response.response, + ); + // 移除已处理的请求 + setPendingActions((prev) => + prev.filter((a) => a.requestId !== response.requestId), + ); + } catch (error) { + console.error("[AsterChat] 确认失败:", error); + toast.error("确认操作失败"); + } + }, []); + + // 清空消息 + const clearMessages = useCallback(() => { + setMessages([]); + setSessionId(null); + toast.success("新话题已创建"); + }, []); + + // 删除消息 + const deleteMessage = useCallback((id: string) => { + setMessages((prev) => prev.filter((msg) => msg.id !== id)); + }, []); + + // 编辑消息 + const editMessage = useCallback((id: string, newContent: string) => { + setMessages((prev) => + prev.map((msg) => + msg.id === id ? { ...msg, content: newContent } : msg, + ), + ); + }, []); + + // 切换话题 + const switchTopic = useCallback( + async (topicId: string) => { + if (topicId === sessionId) return; + + try { + const detail = await getAsterSession(topicId); + const loadedMessages: Message[] = detail.messages.map((msg, index) => ({ + id: `${topicId}-${index}`, + role: msg.role as "user" | "assistant", + content: msg.content, + timestamp: new Date(msg.timestamp), + isThinking: false, + })); + + setMessages(loadedMessages); + setSessionId(topicId); + toast.info("已切换话题"); + } catch (error) { + console.error("[AsterChat] 切换话题失败:", error); + setMessages([]); + setSessionId(topicId); + toast.error("加载对话历史失败"); + } + }, + [sessionId], + ); + + // 删除话题 + const deleteTopic = useCallback( + async (topicId: string) => { + // TODO: 实现后端删除 + setTopics((prev) => prev.filter((t) => t.id !== topicId)); + if (topicId === sessionId) { + setSessionId(null); + setMessages([]); + } + toast.success("话题已删除"); + }, + [sessionId], + ); + + // 兼容接口 + const handleStartProcess = useCallback(async () => { + // Aster 不需要单独启动进程 + }, []); + + const handleStopProcess = useCallback(async () => { + setSessionId(null); + }, []); + + return { + // 兼容 useAgentChat 接口 + processStatus: { running: isInitialized }, + handleStartProcess, + handleStopProcess, + + providerType, + setProviderType, + model, + setModel, + providerConfig: {}, // 简化版本 + isConfigLoading: false, + + messages, + isSending, + sendMessage, + stopSending, + clearMessages, + deleteMessage, + editMessage, + + topics, + sessionId, + switchTopic, + deleteTopic, + loadTopics, + + // Aster 特有功能 + pendingActions, + confirmAction, + }; +} diff --git a/src/components/onboarding/OnboardingWizard.tsx b/src/components/onboarding/OnboardingWizard.tsx index 418266194..dc0b04923 100644 --- a/src/components/onboarding/OnboardingWizard.tsx +++ b/src/components/onboarding/OnboardingWizard.tsx @@ -8,13 +8,20 @@ import { Button } from "@/components/ui/button"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { WelcomeStep } from "./steps/WelcomeStep"; import { UserProfileStep } from "./steps/UserProfileStep"; +import { WindowSizeStep } from "./steps/WindowSizeStep"; import { PluginSelectStep } from "./steps/PluginSelectStep"; import { InstallProgressStep, type PluginInstallState, } from "./steps/InstallProgressStep"; import { CompleteStep } from "./steps/CompleteStep"; -import { userProfiles, type UserProfile } from "./constants"; +import { + userProfiles, + type UserProfile, + type WindowSizePreference, + STORAGE_KEYS, +} from "./constants"; +import { windowApi } from "@/lib/api/window"; const Overlay = styled.div` position: fixed; @@ -83,7 +90,7 @@ const FooterRight = styled.div` gap: 12px; `; -const TOTAL_STEPS = 5; +const TOTAL_STEPS = 6; interface OnboardingWizardProps { onComplete: () => void; @@ -92,6 +99,8 @@ interface OnboardingWizardProps { export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { const [currentStep, setCurrentStep] = useState(1); const [userProfile, setUserProfile] = useState(null); + const [windowSizePreference, setWindowSizePreference] = + useState("default"); const [selectedPlugins, setSelectedPlugins] = useState([]); const [installResults, setInstallResults] = useState( [], @@ -110,8 +119,8 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { const handleNext = useCallback(() => { if (currentStep < TOTAL_STEPS) { // 如果没有选择插件,跳过安装步骤 - if (currentStep === 3 && selectedPlugins.length === 0) { - setCurrentStep(5); // 直接跳到完成页 + if (currentStep === 4 && selectedPlugins.length === 0) { + setCurrentStep(6); // 直接跳到完成页 } else { setCurrentStep((prev) => prev + 1); } @@ -121,9 +130,9 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { const handleBack = useCallback(() => { if (currentStep > 1) { // 如果从完成页返回且没有安装结果,返回到插件选择 - if (currentStep === 5 && installResults.length === 0) { - setCurrentStep(3); - } else if (currentStep === 5) { + if (currentStep === 6 && installResults.length === 0) { + setCurrentStep(4); + } else if (currentStep === 6) { // 已经安装过,不能返回 return; } else { @@ -138,14 +147,33 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { const handleInstallComplete = useCallback((results: PluginInstallState[]) => { setInstallResults(results); - setCurrentStep(5); + setCurrentStep(6); }, []); - const handleFinish = useCallback(() => { + const handleFinish = useCallback(async () => { + // 保存窗口尺寸偏好 + if (windowSizePreference) { + localStorage.setItem( + STORAGE_KEYS.WINDOW_SIZE_PREFERENCE, + windowSizePreference, + ); + + // 应用窗口尺寸 + try { + if (windowSizePreference === "fullscreen") { + await windowApi.toggleFullscreen(); + } else { + await windowApi.setWindowSizeByOption(windowSizePreference); + } + } catch (error) { + console.error("应用窗口尺寸失败:", error); + } + } + // 触发插件变化事件,刷新侧边栏 window.dispatchEvent(new CustomEvent("plugin-changed")); onComplete(); - }, [onComplete]); + }, [onComplete, windowSizePreference]); // 渲染当前步骤 const renderStep = () => { @@ -160,6 +188,13 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { /> ); case 3: + return ( + + ); + case 4: return ( ); - case 4: + case 5: return ( ); - case 5: + case 6: return ( @@ -230,7 +267,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { 跳过 + ))} + + + )} + + {/* 语言 */}
diff --git a/src/lib/api/agent.ts b/src/lib/api/agent.ts index 225d6a3e8..86b2ab6d9 100644 --- a/src/lib/api/agent.ts +++ b/src/lib/api/agent.ts @@ -497,6 +497,154 @@ export async function listGooseProviders(): Promise { return await safeInvoke("goose_agent_list_providers"); } +// ============================================================ +// Aster Agent API (基于 Aster 框架的 Agent 实现) +// ============================================================ + +/** + * Aster Agent 状态 + */ +export interface AsterAgentStatus { + initialized: boolean; + provider_configured: boolean; + provider_name?: string; + model_name?: string; +} + +/** + * Aster Provider 配置 + */ +export interface AsterProviderConfig { + provider_name: string; + model_name: string; + api_key?: string; + base_url?: string; +} + +/** + * Aster 会话信息 + */ +export interface AsterSessionInfo { + id: string; + name?: string; + created_at: string; + updated_at: string; + messages_count: number; +} + +/** + * Aster 会话详情 + */ +export interface AsterSessionDetail { + id: string; + name?: string; + messages: Array<{ + role: string; + content: string; + timestamp: string; + }>; +} + +/** + * 初始化 Aster Agent + */ +export async function initAsterAgent(): Promise { + return await safeInvoke("aster_agent_init"); +} + +/** + * 获取 Aster Agent 状态 + */ +export async function getAsterAgentStatus(): Promise { + return await safeInvoke("aster_agent_status"); +} + +/** + * 配置 Aster Agent 的 Provider + */ +export async function configureAsterProvider( + config: AsterProviderConfig, + sessionId: string, +): Promise { + return await safeInvoke("aster_agent_configure_provider", { + request: config, + session_id: sessionId, + }); +} + +/** + * 发送消息到 Aster Agent (流式响应) + * + * 通过 Tauri 事件接收响应流 + */ +export async function sendAsterMessageStream( + message: string, + sessionId: string, + eventName: string, + images?: ImageInput[], + providerConfig?: AsterProviderConfig, +): Promise { + return await safeInvoke("aster_agent_chat_stream", { + request: { + message, + session_id: sessionId, + event_name: eventName, + images, + provider_config: providerConfig, + }, + }); +} + +/** + * 停止 Aster Agent 会话 + */ +export async function stopAsterSession(sessionId: string): Promise { + return await safeInvoke("aster_agent_stop", { sessionId }); +} + +/** + * 创建 Aster 会话 + */ +export async function createAsterSession( + workingDir?: string, + name?: string, +): Promise { + return await safeInvoke("aster_session_create", { workingDir, name }); +} + +/** + * 获取 Aster 会话列表 + */ +export async function listAsterSessions(): Promise { + return await safeInvoke("aster_session_list"); +} + +/** + * 获取 Aster 会话详情 + */ +export async function getAsterSession( + sessionId: string, +): Promise { + return await safeInvoke("aster_session_get", { sessionId }); +} + +/** + * 确认 Aster Agent 权限请求 + */ +export async function confirmAsterAction( + requestId: string, + confirmed: boolean, + response?: string, +): Promise { + return await safeInvoke("aster_agent_confirm", { + request: { + request_id: requestId, + confirmed, + response, + }, + }); +} + // ============================================================ // Terminal Tool API (终端命令执行) // ============================================================ diff --git a/src/stores/agentStore.ts b/src/stores/agentStore.ts new file mode 100644 index 000000000..08dd28407 --- /dev/null +++ b/src/stores/agentStore.ts @@ -0,0 +1,644 @@ +/** + * Aster Agent Zustand Store + * + * 基于 Aster 框架的 Agent 状态管理 + * 参考 Claude-Cowork 的设计模式 + */ + +import { create } from "zustand"; +import { invoke } from "@tauri-apps/api/core"; +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; + +// ============ 类型定义 ============ + +/** 消息图片 */ +export interface MessageImage { + data: string; + mediaType: string; +} + +/** 工具调用结果 */ +export interface ToolResult { + success: boolean; + output?: string; + error?: string; +} + +/** 工具调用状态 */ +export interface ToolCallState { + id: string; + name: string; + arguments?: string; + status: "pending" | "running" | "completed" | "failed"; + result?: ToolResult; + startTime?: Date; + endTime?: Date; +} + +/** Token 使用量 */ +export interface TokenUsage { + input_tokens: number; + output_tokens: number; + total_tokens: number; +} + +/** 内容片段类型 */ +export type ContentPart = + | { type: "text"; text: string } + | { type: "thinking"; text: string } + | { type: "tool_use"; toolCall: ToolCallState }; + +/** 消息 */ +export interface Message { + id: string; + role: "user" | "assistant"; + content: string; + images?: MessageImage[]; + timestamp: Date; + isThinking?: boolean; + thinkingContent?: string; + toolCalls?: ToolCallState[]; + usage?: TokenUsage; + contentParts?: ContentPart[]; +} + +/** 会话信息 */ +export interface SessionInfo { + id: string; + name?: string; + createdAt: Date; + updatedAt: Date; + messagesCount: number; +} + +/** 权限确认请求 */ +export interface ActionRequired { + requestId: string; + actionType: "tool_confirmation" | "ask_user" | "permission_request"; + toolName?: string; + arguments?: Record; + question?: string; + options?: Array<{ + label: string; + description?: string; + }>; + timestamp: Date; +} + +/** 确认响应 */ +export interface ConfirmResponse { + requestId: string; + confirmed: boolean; + response?: string; +} + +// ============ Tauri 事件类型 ============ + +/** Tauri Agent 事件 */ +export type TauriAgentEvent = + | { type: "text_delta"; text: string } + | { type: "thinking_delta"; text: string } + | { + type: "tool_start"; + tool_name: string; + tool_id: string; + arguments?: string; + } + | { type: "tool_end"; tool_id: string; result: ToolResult } + | { + type: "action_required"; + request_id: string; + action_type: string; + data: Record; + } + | { type: "model_change"; model: string; mode: string } + | { type: "done"; usage?: TokenUsage } + | { type: "final_done"; usage?: TokenUsage } + | { type: "error"; message: string } + | { type: "message"; message: unknown }; + +// ============ Store 状态类型 ============ + +interface AgentState { + // 会话状态 + currentSessionId: string | null; + sessions: SessionInfo[]; + messages: Message[]; + + // 流式状态 + isStreaming: boolean; + currentAssistantMsgId: string | null; + + // 权限确认 + pendingActions: ActionRequired[]; + + // 配置 + isInitialized: boolean; + + // Actions + initialize: () => Promise; + sendMessage: (content: string, images?: MessageImage[]) => Promise; + stopStreaming: () => Promise; + confirmAction: (response: ConfirmResponse) => Promise; + switchSession: (sessionId: string) => Promise; + createSession: (name?: string) => Promise; + deleteSession: (sessionId: string) => Promise; + clearMessages: () => void; + loadSessions: () => Promise; + + // 内部方法 + _handleEvent: (event: TauriAgentEvent) => void; + _cleanup: () => void; +} + +// ============ Store 实现 ============ + +// 事件监听器引用 +let eventUnlisten: UnlistenFn | null = null; + +export const useAgentStore = create((set, get) => ({ + // 初始状态 + currentSessionId: null, + sessions: [], + messages: [], + isStreaming: false, + currentAssistantMsgId: null, + pendingActions: [], + isInitialized: false, + + // 初始化 Agent + initialize: async () => { + try { + await invoke("aster_agent_init"); + set({ isInitialized: true }); + console.log("[AgentStore] Agent 初始化成功"); + + // 加载会话列表 + await get().loadSessions(); + } catch (error) { + console.error("[AgentStore] Agent 初始化失败:", error); + throw error; + } + }, + + // 发送消息 + sendMessage: async (content: string, images?: MessageImage[]) => { + const state = get(); + + // 确保已初始化 + if (!state.isInitialized) { + await state.initialize(); + } + + // 确保有会话 + let sessionId = state.currentSessionId; + if (!sessionId) { + sessionId = await state.createSession(); + } + + // 创建用户消息 + const userMsg: Message = { + id: crypto.randomUUID(), + role: "user", + content, + images, + timestamp: new Date(), + }; + + // 创建助手消息占位符 + const assistantMsgId = crypto.randomUUID(); + const assistantMsg: Message = { + id: assistantMsgId, + role: "assistant", + content: "", + timestamp: new Date(), + isThinking: true, + thinkingContent: "思考中...", + contentParts: [], + }; + + // 更新状态 + set((s) => ({ + messages: [...s.messages, userMsg, assistantMsg], + isStreaming: true, + currentAssistantMsgId: assistantMsgId, + })); + + // 创建唯一事件名称 + const eventName = `aster_stream_${assistantMsgId}`; + + try { + // 设置事件监听器 + eventUnlisten = await listen(eventName, (event) => { + get()._handleEvent(event.payload); + }); + + // 发送请求 + await invoke("aster_agent_chat_stream", { + request: { + message: content, + session_id: sessionId, + event_name: eventName, + images: images?.map((img) => ({ + data: img.data, + media_type: img.mediaType, + })), + }, + }); + } catch (error) { + console.error("[AgentStore] 发送消息失败:", error); + + // 更新消息状态为错误 + set((s) => ({ + messages: s.messages.map((msg) => + msg.id === assistantMsgId + ? { + ...msg, + isThinking: false, + content: `错误: ${error}`, + } + : msg, + ), + isStreaming: false, + currentAssistantMsgId: null, + })); + + get()._cleanup(); + throw error; + } + }, + + // 停止流式响应 + stopStreaming: async () => { + const state = get(); + if (!state.currentSessionId) return; + + try { + await invoke("aster_agent_stop", { + sessionId: state.currentSessionId, + }); + } catch (error) { + console.error("[AgentStore] 停止失败:", error); + } + + // 更新消息状态 + set((s) => ({ + messages: s.messages.map((msg) => + msg.id === s.currentAssistantMsgId + ? { + ...msg, + isThinking: false, + content: msg.content || "(已停止生成)", + } + : msg, + ), + isStreaming: false, + currentAssistantMsgId: null, + })); + + get()._cleanup(); + }, + + // 确认权限请求 + confirmAction: async (response: ConfirmResponse) => { + try { + await invoke("aster_agent_confirm", { + request: { + request_id: response.requestId, + confirmed: response.confirmed, + response: response.response, + }, + }); + + // 移除已处理的请求 + set((s) => ({ + pendingActions: s.pendingActions.filter( + (a) => a.requestId !== response.requestId, + ), + })); + } catch (error) { + console.error("[AgentStore] 确认失败:", error); + throw error; + } + }, + + // 切换会话 + switchSession: async (sessionId: string) => { + try { + const detail = await invoke<{ + id: string; + name?: string; + messages: Array<{ + role: string; + content: string; + timestamp: string; + }>; + }>("aster_session_get", { sessionId }); + + // 转换消息格式 + const messages: Message[] = detail.messages.map((msg, index) => ({ + id: `${sessionId}-${index}`, + role: msg.role as "user" | "assistant", + content: msg.content, + timestamp: new Date(msg.timestamp), + })); + + set({ + currentSessionId: sessionId, + messages, + pendingActions: [], + }); + } catch (error) { + console.error("[AgentStore] 切换会话失败:", error); + throw error; + } + }, + + // 创建会话 + createSession: async (name?: string) => { + try { + const sessionId = await invoke("aster_session_create", { + workingDir: null, + name, + }); + + set((s) => ({ + currentSessionId: sessionId, + messages: [], + sessions: [ + { + id: sessionId, + name, + createdAt: new Date(), + updatedAt: new Date(), + messagesCount: 0, + }, + ...s.sessions, + ], + })); + + return sessionId; + } catch (error) { + console.error("[AgentStore] 创建会话失败:", error); + throw error; + } + }, + + // 删除会话 + deleteSession: async (sessionId: string) => { + // TODO: 实现后端删除接口 + set((s) => ({ + sessions: s.sessions.filter((sess) => sess.id !== sessionId), + ...(s.currentSessionId === sessionId + ? { currentSessionId: null, messages: [] } + : {}), + })); + }, + + // 清空消息 + clearMessages: () => { + set({ + messages: [], + currentSessionId: null, + pendingActions: [], + }); + }, + + // 加载会话列表 + loadSessions: async () => { + try { + const sessions = await invoke< + Array<{ + id: string; + name?: string; + created_at: string; + updated_at: string; + messages_count: number; + }> + >("aster_session_list"); + + set({ + sessions: sessions.map((s) => ({ + id: s.id, + name: s.name, + createdAt: new Date(s.created_at), + updatedAt: new Date(s.updated_at), + messagesCount: s.messages_count, + })), + }); + } catch (error) { + console.error("[AgentStore] 加载会话列表失败:", error); + } + }, + + // 处理事件 + _handleEvent: (event: TauriAgentEvent) => { + const state = get(); + const msgId = state.currentAssistantMsgId; + if (!msgId) return; + + console.log("[AgentStore] 收到事件:", event.type, event); + + switch (event.type) { + case "text_delta": + set((s) => ({ + messages: s.messages.map((msg) => { + if (msg.id !== msgId) return msg; + + const newContent = msg.content + event.text; + const newParts = [...(msg.contentParts || [])]; + + // 追加到最后一个 text 类型,或创建新的 + const lastPart = newParts[newParts.length - 1]; + if (lastPart && lastPart.type === "text") { + newParts[newParts.length - 1] = { + type: "text", + text: lastPart.text + event.text, + }; + } else { + newParts.push({ type: "text", text: event.text }); + } + + return { + ...msg, + content: newContent, + isThinking: false, + thinkingContent: undefined, + contentParts: newParts, + }; + }), + })); + break; + + case "thinking_delta": + set((s) => ({ + messages: s.messages.map((msg) => { + if (msg.id !== msgId) return msg; + + const newParts = [...(msg.contentParts || [])]; + const lastPart = newParts[newParts.length - 1]; + if (lastPart && lastPart.type === "thinking") { + newParts[newParts.length - 1] = { + type: "thinking", + text: lastPart.text + event.text, + }; + } else { + newParts.push({ type: "thinking", text: event.text }); + } + + return { + ...msg, + thinkingContent: (msg.thinkingContent || "") + event.text, + contentParts: newParts, + }; + }), + })); + break; + + case "tool_start": { + const newToolCall: ToolCallState = { + id: event.tool_id, + name: event.tool_name, + arguments: event.arguments, + status: "running", + startTime: new Date(), + }; + + set((s) => ({ + messages: s.messages.map((msg) => { + if (msg.id !== msgId) return msg; + + // 检查是否已存在 + if (msg.toolCalls?.find((tc) => tc.id === event.tool_id)) { + return msg; + } + + return { + ...msg, + toolCalls: [...(msg.toolCalls || []), newToolCall], + contentParts: [ + ...(msg.contentParts || []), + { type: "tool_use" as const, toolCall: newToolCall }, + ], + }; + }), + })); + break; + } + + case "tool_end": + set((s) => ({ + messages: s.messages.map((msg) => { + if (msg.id !== msgId) return msg; + + const updatedToolCalls = (msg.toolCalls || []).map((tc) => + tc.id === event.tool_id + ? { + ...tc, + status: event.result.success + ? ("completed" as const) + : ("failed" as const), + result: event.result, + endTime: new Date(), + } + : tc, + ); + + const updatedContentParts = (msg.contentParts || []).map((part) => { + if ( + part.type === "tool_use" && + part.toolCall.id === event.tool_id + ) { + return { + ...part, + toolCall: { + ...part.toolCall, + status: event.result.success + ? ("completed" as const) + : ("failed" as const), + result: event.result, + endTime: new Date(), + }, + }; + } + return part; + }); + + return { + ...msg, + toolCalls: updatedToolCalls, + contentParts: updatedContentParts, + }; + }), + })); + break; + + case "action_required": + set((s) => ({ + pendingActions: [ + ...s.pendingActions, + { + requestId: event.request_id, + actionType: event.action_type as ActionRequired["actionType"], + ...event.data, + timestamp: new Date(), + }, + ], + })); + break; + + case "done": + // 单次响应完成,但工具循环可能继续 + console.log("[AgentStore] done 事件,等待 final_done..."); + break; + + case "final_done": + set((s) => ({ + messages: s.messages.map((msg) => + msg.id === msgId + ? { + ...msg, + isThinking: false, + usage: event.usage, + } + : msg, + ), + isStreaming: false, + currentAssistantMsgId: null, + })); + get()._cleanup(); + break; + + case "error": + set((s) => ({ + messages: s.messages.map((msg) => + msg.id === msgId + ? { + ...msg, + isThinking: false, + content: msg.content || `错误: ${event.message}`, + } + : msg, + ), + isStreaming: false, + currentAssistantMsgId: null, + })); + get()._cleanup(); + break; + } + }, + + // 清理资源 + _cleanup: () => { + if (eventUnlisten) { + eventUnlisten(); + eventUnlisten = null; + } + }, +})); + +// 导出便捷 hooks +export const useAgentMessages = () => useAgentStore((s) => s.messages); +export const useAgentStreaming = () => useAgentStore((s) => s.isStreaming); +export const useAgentSessions = () => useAgentStore((s) => s.sessions); +export const usePendingActions = () => useAgentStore((s) => s.pendingActions); diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 000000000..e3a276c51 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,22 @@ +/** + * Stores 导出 + */ + +// Aster Agent Store +export { + useAgentStore, + useAgentMessages, + useAgentStreaming, + useAgentSessions, + usePendingActions, + type Message, + type MessageImage, + type ToolResult, + type ToolCallState, + type TokenUsage, + type ContentPart, + type SessionInfo, + type ActionRequired, + type ConfirmResponse, + type TauriAgentEvent, +} from "./agentStore";