Merge branch 'feat/agents-new' of github.com:CherryHQ/cherry-studio into feat/agents-new

This commit is contained in:
icarus 2025-09-18 13:28:36 +08:00
commit f9b49ffde6
56 changed files with 728 additions and 1783 deletions

View File

@ -1,6 +1,6 @@
name: Claude Translator
concurrency:
group: translator-${{ github.event.comment.id || github.event.issue.number }}
group: translator-${{ github.event.comment.id || github.event.issue.number || github.event.review.id }}
cancel-in-progress: false
on:
@ -8,14 +8,18 @@ on:
types: [opened]
issue_comment:
types: [created, edited]
pull_request_review:
types: [submitted, edited]
pull_request_review_comment:
types: [created, edited]
jobs:
translate:
if: |
(github.event_name == 'issues') ||
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') &&
((github.event_name == 'issue_comment' && github.event.action == 'created' && !contains(github.event.comment.body, 'This issue was translated by Claude')) ||
(github.event_name == 'issue_comment' && github.event.action == 'edited'))
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') ||
(github.event_name == 'pull_request_review' && github.event.sender.type != 'Bot') ||
(github.event_name == 'pull_request_review_comment' && github.event.sender.type != 'Bot')
runs-on: ubuntu-latest
permissions:
contents: read
@ -37,23 +41,44 @@ jobs:
# Now `contents: read` is safe for files, but we could make a fine-grained token to control it.
# See: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md
github_token: ${{ secrets.TOKEN_GITHUB_WRITE }}
allowed_non_write_users: '*'
allowed_non_write_users: "*"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: '--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*)'
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*),Bash(gh api:repos/*/pulls/*/reviews/*),Bash(gh api:repos/*/pulls/comments/*)"
prompt: |
你是一个多语言翻译助手。请完成以下任务:
你是一个多语言翻译助手。你需要响应 GitHub Webhooks 中的以下四种事件:
- issues
- issue_comment
- pull_request_review
- pull_request_review_comment
请完成以下任务:
1. 获取当前事件的完整信息。
- 如果当前事件是 issues就获取该 issues 的信息。
- 如果当前事件是 issue_comment就获取该 comment 的信息。
- 如果当前事件是 pull_request_review就获取该 review 的信息。
- 如果当前事件是 pull_request_review_comment就获取该 comment 的信息。
1. 获取当前issue/comment的完整信息
2. 智能检测内容。
1. 如果是已经遵循格式要求翻译过的issue/comment检查翻译内容和原始内容是否匹配。若不匹配则重新翻译一次令其匹配并遵循格式要求若匹配则跳过任务。
2. 如果是未翻译过的issue/comment检查其内容语言。若不是英文则翻译成英文若已经是英文则跳过任务。
- 如果获取到的信息是已经遵循格式要求翻译过的内容,则检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求;
- 如果获取到的信息是未翻译过的内容,检查其内容语言。若不是英文,则翻译成英文;
- 如果获取到的信息是部分翻译为英文的内容,则将其翻译为英文;
- 如果获取到的信息包含了对已翻译内容的引用,则将引用内容清理为仅含英文的内容。引用的内容不能够包含"This xxx was translated by Claude"和"Original Content`等内容。
- 如果获取到的信息包含了其他类型的引用,即对非 Claude 翻译的内容的引用,则直接照原样引用,不进行翻译。
- 如果获取到的信息是通过邮件回复的内容,则在翻译时应当将邮件内容的引用放到最后。在原始内容和翻译内容中只需要回复的内容本身,不要包含对邮件内容的引用。
- 如果获取到的信息本身不需要任何处理,则跳过任务。
3. 格式要求:
- 标题:英文翻译(如果非英文)
- 内容格式:
> [!NOTE]
> This issue/comment was translated by Claude.
> This issue/comment/review was translated by Claude.
[英文翻译内容]
[翻译内容]
---
<details>
@ -62,15 +87,21 @@ jobs:
</details>
4. 使用gh工具更新
- 根据环境信息中的Event类型选择正确的命令
- 如果Event是'issues'gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]"
- 如果Event是'issue_comment'gh api -X PATCH /repos/[REPO]/issues/comments/[COMMENT_ID] -f body="[翻译内容 + 原始内容]"
- 如果 Event 是 'issues': gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]"
- 如果 Event 是 'issue_comment': gh api -X PATCH /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
- 如果 Event 是 'pull_request_review': gh api -X PUT /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }} -f body="[翻译内容]"
- 如果 Event 是 'pull_request_review_comment': gh api -X PATCH /repos/${{ github.repository }}/pulls/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
环境信息:
- Event: ${{ github.event_name }}
- Issue Number: ${{ github.event.issue.number }}
- Repository: ${{ github.repository }}
- Comment ID: ${{ github.event.comment.id || 'N/A' }} (only available for comment events)
- (Review) Comment ID: ${{ github.event.comment.id || 'N/A' }}
- Pull Request Number: ${{ github.event.pull_request.number || 'N/A' }}
- Review ID: ${{ github.event.review.id || 'N/A' }}
使用以下命令获取完整信息:
gh issue view ${{ github.event.issue.number }} --json title,body,comments

View File

@ -6,7 +6,6 @@
"bradlc.vscode-tailwindcss",
"vitest.explorer",
"oxc.oxc-vscode",
"biomejs.biome",
"typescriptteam.native-preview"
"biomejs.biome"
]
}

View File

@ -34,10 +34,10 @@
"*.css": "tailwindcss"
},
"files.eol": "\n",
// "i18n-ally.displayLanguage": "zh-cn", //
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.enabledFrameworks": ["react-i18next", "i18next"],
"i18n-ally.enabledParsers": ["ts", "js", "json"], //
"i18n-ally.fullReloadOnChanged": true,
"i18n-ally.fullReloadOnChanged": true, //
"i18n-ally.keystyle": "nested", //
"i18n-ally.localesPaths": ["src/renderer/src/i18n/locales"],
// "i18n-ally.namespace": true, //
@ -47,6 +47,5 @@
"search.exclude": {
"**/dist/**": true,
".yarn/releases/**": true
},
"typescript.experimental.useTsgo": true
}
}

View File

@ -1,238 +0,0 @@
# Agents Service Refactoring - Validation Report
## Overview
This report documents the comprehensive validation of the agents service refactoring completed on September 12, 2025. All tests were performed to ensure the refactored system maintains full functionality while providing improved structure and maintainability.
## Validation Summary
**ALL VALIDATIONS PASSED** - The refactoring has been successfully completed and verified.
---
## 1. Build and Compilation Validation
### Command: `yarn build:check`
**Status:** ✅ PASSED
**Results:**
- TypeScript compilation for Node.js environment: ✅ PASSED
- TypeScript compilation for Web environment: ✅ PASSED
- i18n validation: ✅ PASSED
- Test suite execution: ✅ PASSED (1420 tests across 108 files)
**Duration:** 23.12s
### Key Findings:
- All TypeScript files compile without errors
- No type definition conflicts detected
- Import/export structure is correctly maintained
- All service dependencies resolve correctly
---
## 2. Migration System Validation
### Custom Migration Test
**Status:** ✅ PASSED
**Test Coverage:**
1. ✅ Migration tracking table creation
2. ✅ Migration indexes creation
3. ✅ Migration record insertion/retrieval
4. ✅ Database schema creation (agents table)
5. ✅ Agent record CRUD operations
6. ✅ Session tables creation
7. ✅ Session logs table creation
8. ✅ Foreign key relationships
9. ✅ Data retrieval with joins
10. ✅ Migration cleanup
### Key Findings:
- Migration system initializes correctly
- All migration tables and indexes are created properly
- Transaction support works as expected
- Rollback functionality is available
- Checksum validation ensures migration integrity
---
## 3. Service Initialization Validation
### Custom Service Structure Test
**Status:** ✅ PASSED
**Validated Components:**
1. ✅ All service files are present and accessible
2. ✅ Migration files are properly organized
3. ✅ Query files are correctly structured
4. ✅ Schema files are properly organized
5. ✅ Module export structure is correct
6. ✅ Backward compatibility is maintained
7. ✅ Old db.ts file has been properly removed
8. ✅ TypeScript compilation validated
### File Structure Verification:
```
src/main/services/agents/
├── ✅ BaseService.ts
├── ✅ services/
│ ├── ✅ AgentService.ts
│ ├── ✅ SessionService.ts
│ ├── ✅ SessionLogService.ts
│ └── ✅ index.ts
├── ✅ database/
│ ├── ✅ migrations/
│ │ ├── ✅ 001_initial_schema.ts
│ │ ├── ✅ 002_add_session_tables.ts
│ │ ├── ✅ types.ts
│ │ └── ✅ index.ts
│ ├── ✅ queries/
│ │ ├── ✅ agent.queries.ts
│ │ ├── ✅ session.queries.ts
│ │ ├── ✅ sessionLog.queries.ts
│ │ └── ✅ index.ts
│ ├── ✅ schema/
│ │ ├── ✅ tables.ts
│ │ ├── ✅ indexes.ts
│ │ ├── ✅ migrations.ts
│ │ └── ✅ index.ts
│ ├── ✅ migrator.ts
│ └── ✅ index.ts
└── ✅ index.ts
```
---
## 4. Database Operations Validation
### Comprehensive CRUD Operations Test
**Status:** ✅ PASSED
**Test Scenarios:**
1. ✅ Database schema setup (tables + indexes)
2. ✅ Agent CRUD operations
- Create: ✅ Agent creation with JSON field serialization
- Read: ✅ Agent retrieval and data integrity verification
- Update: ✅ Agent updates with field validation
- Delete: ✅ Agent deletion (tested via cascade)
- List: ✅ Agent listing and counting operations
3. ✅ Session operations
- Create: ✅ Session creation with foreign key constraints
- Read: ✅ Session retrieval and agent association
- List: ✅ Sessions by agent queries
4. ✅ Session Log operations
- Create: ✅ Multiple log types creation
- Read: ✅ Log retrieval ordered by timestamp
5. ✅ Foreign Key constraints
- Cascade Delete: ✅ Agent deletion cascades to sessions and logs
- Referential Integrity: ✅ Foreign key relationships maintained
6. ✅ Concurrent operations
- Parallel Creation: ✅ 5 concurrent agents created successfully
- Data Integrity: ✅ All concurrent operations verified
### Performance Metrics:
- Agent CRUD operations: < 50ms per operation
- Migration system: < 100ms initialization
- Concurrent operations: Successfully handled 5 parallel operations
---
## 5. Backward Compatibility Validation
### Compatibility Checks:
- ✅ Export structure maintains backward compatibility
- ✅ Legacy query exports available via `AgentQueries_Legacy`
- ✅ Service singleton instances preserved
- ✅ Database interface unchanged for external consumers
- ✅ Migration system added without breaking existing functionality
---
## 6. Code Quality and Structure
### Improvements Delivered:
1. **Modular Organization**: ✅ Services split into focused, single-responsibility files
2. **Migration System**: ✅ Version-controlled schema changes with rollback support
3. **Query Organization**: ✅ SQL queries organized by entity type
4. **Schema Management**: ✅ Table and index definitions centralized
5. **Type Safety**: ✅ TypeScript interfaces for all operations
6. **Error Handling**: ✅ Comprehensive error handling and logging
7. **Testing**: ✅ All existing tests continue to pass
### Benefits Realized:
- **Maintainability**: Easier to locate and modify specific functionality
- **Scalability**: Simple to add new entities without affecting existing code
- **Production Readiness**: Atomic migrations with transaction support
- **Team Development**: Reduced merge conflicts with smaller, focused files
- **Documentation**: Clear structure makes codebase more navigable
---
## 7. Security and Safety Validation
### Security Measures Verified:
- ✅ SQL injection protection via parameterized queries
- ✅ Transaction isolation for atomic operations
- ✅ Foreign key constraints prevent orphaned records
- ✅ JSON field validation and safe parsing
- ✅ Migration checksums prevent tampering
---
## 8. Performance Validation
### Database Operations:
- ✅ Index utilization verified for common queries
- ✅ Foreign key constraints optimized with indexes
- ✅ JSON field operations efficient
- ✅ Concurrent access handled properly
---
## Cleanup
The following temporary test files were created for validation and can be safely removed:
- `/Users/weliu/workspace/cherry-studio/migration-validation-test.js`
- `/Users/weliu/workspace/cherry-studio/service-initialization-test.js`
- `/Users/weliu/workspace/cherry-studio/database-operations-test.js`
---
## Final Recommendation
✅ **APPROVED FOR PRODUCTION**
The agents service refactoring has been successfully completed and thoroughly validated. All functionality is preserved while delivering significant improvements in code organization, maintainability, and scalability. The migration system is production-ready and will support future schema evolution safely.
## Next Steps
1. The refactoring is complete and ready for deployment
2. Consider removing temporary test files
3. Monitor the system in production to validate real-world performance
4. Begin utilizing the new modular structure for future feature development
---
**Validation completed:** September 12, 2025
**Total validation time:** ~45 minutes
**Tests executed:** 1420 + custom validation tests
**Overall result:** ✅ SUCCESS

View File

@ -1,198 +0,0 @@
# Agents Service Refactoring Plan
## Overview
Restructure the agents service to split database operations into smaller, more manageable files with migration support.
## New Folder Structure
```
src/main/services/agents/
├── database/
│ ├── migrations/
│ │ ├── types.ts # Migration interfaces
│ │ ├── 001_initial_schema.ts # Initial tables & indexes
│ │ ├── 002_add_session_tables.ts # Session related tables
│ │ └── index.ts # Export all migrations
│ ├── queries/
│ │ ├── agent.queries.ts # Agent CRUD queries
│ │ ├── session.queries.ts # Session CRUD queries
│ │ ├── sessionLog.queries.ts # Session log queries
│ │ └── index.ts # Export all queries
│ ├── schema/
│ │ ├── tables.ts # Table definitions
│ │ ├── indexes.ts # Index definitions
│ │ ├── migrations.ts # Migration tracking table
│ │ └── index.ts # Export all schema
│ ├── migrator.ts # Migration runner class
│ └── index.ts # Main database exports
├── services/
│ ├── AgentService.ts # Agent business logic
│ ├── SessionService.ts # Session business logic
│ ├── SessionLogService.ts # Session log business logic
│ └── index.ts # Export all services
├── BaseService.ts # Shared database utilities with migration support
└── index.ts # Main module exports
```
## Implementation Tasks
### Task 1: Create Folder Structure and Migration System Infrastructure
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Create all necessary directories and implement the migration system infrastructure
**Subtasks**:
- [x] Create database/, database/migrations/, database/queries/, database/schema/, services/ directories
- [x] Implement migration types and interfaces in database/migrations/types.ts
- [x] Build Migrator class with transaction support in database/migrator.ts
- [x] Create migration tracking table schema in database/schema/migrations.ts
---
### Task 2: Split Database Queries from db.ts
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Extract and organize queries from the current db.ts file into separate, focused files
**Subtasks**:
- [x] Move agent queries to database/queries/agent.queries.ts
- [x] Move session queries to database/queries/session.queries.ts
- [x] Move session log queries to database/queries/sessionLog.queries.ts
- [x] Extract table definitions to database/schema/tables.ts
- [x] Extract index definitions to database/schema/indexes.ts
- [x] Create index files for queries and schema directories
- [x] Update db.ts to maintain backward compatibility by re-exporting split queries
---
### Task 3: Create Initial Migration Files
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Create migration files based on existing schema
**Subtasks**:
- [x] Create 001_initial_schema.ts with agents table and indexes
- [x] Create 002_add_session_tables.ts with sessions and session_logs tables
- [x] Create database/migrations/index.ts to export all migrations
---
### Task 4: Update BaseService with Migration Support
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Integrate migration system into BaseService initialization
**Subtasks**:
- [x] Update BaseService.ts to use Migrator on initialize
- [x] Keep existing JSON serialization utilities
- [x] Update database initialization flow
---
### Task 5: Reorganize Service Files
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Move service files to services subdirectory and update imports
**Subtasks**:
- [x] Move AgentService.ts to services/
- [x] Move SessionService.ts to services/
- [x] Move SessionLogService.ts to services/
- [x] Update import paths in all service files (now import from '../BaseService' and '../db')
- [x] Create services/index.ts to export all services
---
### Task 6: Create Export Structure and Clean Up
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Create proper export hierarchy and clean up old files
**Subtasks**:
- [x] Create main agents/index.ts with clean exports
- [x] Create database/index.ts for database exports
- [x] Ensure backward compatibility for existing imports
- [x] Remove old db.ts file
- [x] Update any external imports if needed
---
### Task 7: Test and Validate Refactoring
**Status**: ✅ COMPLETED
**Agent**: `general-purpose`
**Description**: Ensure all functionality works after refactoring
**Subtasks**:
- [x] Run build check: `yarn build:check` ✅ PASSED (1420 tests, TypeScript compilation successful)
- [x] Run tests: `yarn test` ✅ PASSED (All existing tests continue to pass)
- [x] Validate migration system works ✅ PASSED (11 migration tests, transaction support verified)
- [x] Check that all services initialize correctly ✅ PASSED (File structure, exports, backward compatibility)
- [x] Verify database operations work as expected ✅ PASSED (CRUD operations, foreign keys, concurrent operations)
**Additional Validation**:
- [x] Created comprehensive validation report (VALIDATION_REPORT.md)
- [x] Validated migration system with custom test suite
- [x] Verified service initialization and file structure
- [x] Tested complete database operations including concurrent access
- [x] Confirmed backward compatibility maintained
- [x] Validated security measures and performance optimizations
---
## Benefits of This Refactoring
1. **Single Responsibility**: Each file handles one specific concern
2. **Version-Controlled Schema**: Migration system tracks all database changes
3. **Easier Maintenance**: Find and modify queries for specific entities quickly
4. **Better Scalability**: Easy to add new entities without cluttering existing files
5. **Clear Organization**: Logical grouping makes navigation intuitive
6. **Production Ready**: Atomic migrations with transaction support
7. **Reduced Merge Conflicts**: Smaller files mean fewer conflicts in team development
## Migration Best Practices Implemented
- ✅ Version-controlled migrations with tracking table
- ✅ Atomic operations with transaction support
- ✅ Rollback capability (optional down migrations)
- ✅ Incremental updates (only run pending migrations)
- ✅ Safe for production deployments
---
**Progress Summary**: 7/7 tasks completed 🎉
**Status**: ✅ **REFACTORING COMPLETED SUCCESSFULLY**
All tasks have been completed and thoroughly validated. The agents service refactoring delivers:
- ✅ Modular, maintainable code structure
- ✅ Production-ready migration system
- ✅ Complete backward compatibility
- ✅ Comprehensive test validation
- ✅ Enhanced developer experience
**Final deliverables:**
- 📁 Reorganized service architecture with clear separation of concerns
- 🗃️ Database migration system with transaction support and rollback capability
- 📋 Comprehensive validation report (VALIDATION_REPORT.md)
- ✅ All 1420+ tests passing with full TypeScript compliance
- 🔒 Security hardening with parameterized queries and foreign key constraints
**Ready for production deployment** 🚀

View File

@ -1,654 +0,0 @@
# Agent API UI Integration Guide
## Overview
This document provides comprehensive guidance for UI components to integrate with the new Agent API system. The agents data is now stored in the database and accessed through API endpoints instead of Redux state management.
## Key Changes from Previous Implementation
### Data Storage
- **Before**: Agent data stored in Redux store
- **After**: Agent data stored in SQLite database, accessed via REST API
### State Management
- **Before**: Redux actions and selectors for agent operations
- **After**: Direct API calls using fetch/axios, no Redux dependency
### Data Flow
- **Before**: Component → Redux Action → State Update → Component Re-render
- **After**: Component → API Call → UI Update → Database
## API Endpoints Overview
### Base Configuration
- **Base URL**: `http://localhost:23333/v1`
- **Authentication**: Bearer token (API key format: `cs-sk-{uuid}`)
- **Content-Type**: `application/json`
### Agent Management (`/agents`)
| Method | Endpoint | Description | Request Body | Response |
|--------|----------|-------------|--------------|----------|
| POST | `/agents` | Create new agent | `CreateAgentRequest` | `AgentEntity` |
| GET | `/agents` | List agents (paginated) | Query params | `{ data: AgentEntity[], total: number }` |
| GET | `/agents/{id}` | Get specific agent | - | `AgentEntity` |
| PUT | `/agents/{id}` | Replace agent (complete update) | `UpdateAgentRequest` | `AgentEntity` |
| PATCH | `/agents/{id}` | Partially update agent | `Partial<UpdateAgentRequest>` | `AgentEntity` |
| DELETE | `/agents/{id}` | Delete agent | - | `204 No Content` |
### Session Management (`/agents/{agentId}/sessions`)
| Method | Endpoint | Description | Request Body | Response |
|--------|----------|-------------|--------------|----------|
| POST | `/agents/{agentId}/sessions` | Create session | `CreateSessionRequest` | `AgentSessionEntity` |
| GET | `/agents/{agentId}/sessions` | List agent sessions | Query params | `{ data: AgentSessionEntity[], total: number }` |
| GET | `/agents/{agentId}/sessions/{id}` | Get specific session | - | `AgentSessionEntity` |
| PUT | `/agents/{agentId}/sessions/{id}` | Replace session (complete update) | `UpdateSessionRequest` | `AgentSessionEntity` |
| PATCH | `/agents/{agentId}/sessions/{id}` | Partially update session | `Partial<UpdateSessionRequest>` | `AgentSessionEntity` |
| DELETE | `/agents/{agentId}/sessions/{id}` | Delete session | - | `204 No Content` |
### Message Streaming (`/agents/{agentId}/sessions/{sessionId}/messages`)
| Method | Endpoint | Description | Request Body | Response |
|--------|----------|-------------|--------------|----------|
| POST | `/agents/{agentId}/sessions/{sessionId}/messages` | Send message to agent | `CreateMessageRequest` | **Stream Response** |
| GET | `/agents/{agentId}/sessions/{sessionId}/messages` | List session messages | Query params | `{ data: SessionMessageEntity[], total: number }` |
## HTTP Methods: PUT vs PATCH
Both agents and sessions support two types of update operations:
### PUT - Complete Replacement
- **Purpose**: Replaces the entire resource with the provided data
- **Behavior**: All fields in the request body will be applied to the resource
- **Use Case**: When you want to completely update a resource with a new set of values
- **Example**: Updating an agent's configuration completely
```typescript
// PUT - Replace entire agent
await fetch('/v1/agents/agent-123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'New Agent Name',
model: 'gpt-4',
instructions: 'New instructions',
built_in_tools: ['search', 'calculator'],
// All other fields will be reset to defaults if not provided
})
})
```
### PATCH - Partial Update
- **Purpose**: Updates only the specified fields, leaving others unchanged
- **Behavior**: Only the fields present in the request body will be modified
- **Use Case**: When you want to update specific fields without affecting others
- **Example**: Updating only an agent's name or instructions
```typescript
// PATCH - Update only specific fields
await fetch('/v1/agents/agent-123', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Updated Agent Name'
// All other fields remain unchanged
})
})
```
### Validation
Both methods use the same validation rules:
- All fields are optional for both PUT and PATCH
- When provided, fields must meet their validation criteria (e.g., `name` cannot be empty)
- The same middleware (`validateAgentUpdate` for agents, `validateSessionUpdate` for sessions) handles both operations
## Data Types & Schemas
### AgentEntity
```typescript
interface AgentEntity {
id: string
type: AgentType
name: string
description?: string
avatar?: string
instructions?: string
// Core configuration
model: string // Required - main model ID
plan_model?: string
small_model?: string
built_in_tools?: string[]
mcps?: string[]
knowledges?: string[]
configuration?: Record<string, any>
accessible_paths?: string[]
permission_mode?: PermissionMode
max_steps?: number
// Timestamps
created_at: string
updated_at: string
}
```
### AgentSessionEntity
```typescript
interface AgentSessionEntity {
id: string
name?: string
main_agent_id: string
sub_agent_ids?: string[]
user_goal?: string
status: SessionStatus
external_session_id?: string
// Configuration overrides (inherits from agent if not specified)
model?: string
plan_model?: string
small_model?: string
built_in_tools?: string[]
mcps?: string[]
knowledges?: string[]
configuration?: Record<string, any>
accessible_paths?: string[]
permission_mode?: PermissionMode
max_steps?: number
// Timestamps
created_at: string
updated_at: string
}
```
### SessionMessageEntity
```typescript
interface SessionMessageEntity {
id: number
session_id: string
parent_id?: number
role: SessionMessageRole // 'user' | 'agent' | 'system' | 'tool'
type: SessionMessageType
content: Record<string, any>
metadata?: Record<string, any>
created_at: string
updated_at: string
}
```
## Creating Agents
### Minimal Agent Creation
For early stage implementation, only use these essential fields:
```typescript
const createAgentRequest = {
name: string, // Required
model: string, // Required
instructions?: string, // System prompt
built_in_tools?: string[],
mcps?: string[],
knowledges?: string[]
}
```
### Example: Create Agent
```typescript
async function createAgent(agentData: CreateAgentRequest): Promise<AgentEntity> {
const response = await fetch('/v1/agents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(agentData)
})
if (!response.ok) {
throw new Error(`Agent creation failed: ${response.statusText}`)
}
return await response.json()
}
```
### Example: List Agents
```typescript
async function listAgents(limit = 20, offset = 0): Promise<{data: AgentEntity[], total: number}> {
const response = await fetch(`/v1/agents?limit=${limit}&offset=${offset}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
return await response.json()
}
```
## Managing Agent Sessions
### Session Creation
```typescript
async function createSession(agentId: string, sessionData: CreateSessionRequest): Promise<AgentSessionEntity> {
const response = await fetch(`/v1/agents/${agentId}/sessions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_goal: sessionData.user_goal, // User's goal as input message
model: sessionData.model, // Override agent's model if needed
// tools and mcps can be overridden per session
})
})
return await response.json()
}
```
### Session Updates
Sessions can be updated using either PUT (complete replacement) or PATCH (partial update):
#### Complete Session Replacement (PUT)
```typescript
async function replaceSession(agentId: string, sessionId: string, sessionData: UpdateSessionRequest): Promise<AgentSessionEntity> {
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(sessionData) // Complete session configuration
})
return await response.json()
}
```
#### Partial Session Update (PATCH)
```typescript
async function updateSession(agentId: string, sessionId: string, updates: Partial<UpdateSessionRequest>): Promise<AgentSessionEntity> {
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates) // Only the fields to update
})
return await response.json()
}
```
#### Session Status Management
Sessions have five possible statuses:
- `idle`: Ready to process messages
- `running`: Currently processing
- `completed`: Task finished successfully
- `failed`: Encountered an error
- `stopped`: Manually stopped by user
```typescript
// Update only the session status using PATCH
async function updateSessionStatus(agentId: string, sessionId: string, status: SessionStatus): Promise<AgentSessionEntity> {
return await updateSession(agentId, sessionId, { status })
}
```
## Message Streaming Integration
### Sending Messages to Agents
The core interaction point is the message endpoint that accepts user messages and returns streamed responses:
```typescript
async function sendMessageToAgent(
agentId: string,
sessionId: string,
message: CreateMessageRequest
): Promise<ReadableStream> {
const response = await fetch(`/v1/agents/${agentId}/sessions/${sessionId}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
role: 'user',
type: 'message',
content: {
text: message.text,
// Include any additional context
}
})
})
return response.body // Returns AI SDK streamText compatible stream
}
```
### Processing Streamed Responses
The response follows AI SDK's `streamText` format:
```typescript
async function handleAgentResponse(stream: ReadableStream) {
const reader = stream.getReader()
const decoder = new TextDecoder()
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') {
return // Stream completed
}
try {
const parsed = JSON.parse(data)
// Handle different stream events
switch (parsed.type) {
case 'text-delta':
updateUI(parsed.textDelta)
break
case 'tool-call':
handleToolCall(parsed.toolCall)
break
case 'tool-result':
handleToolResult(parsed.toolResult)
break
case 'finish':
handleFinish(parsed.finishReason)
break
}
} catch (parseError) {
console.error('Failed to parse stream data:', parseError)
}
}
}
}
} finally {
reader.releaseLock()
}
}
```
## UI Component Integration Patterns
### Agent List Component
```typescript
function AgentList() {
const [agents, setAgents] = useState<AgentEntity[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
async function loadAgents() {
try {
const result = await listAgents()
setAgents(result.data)
} catch (error) {
console.error('Failed to load agents:', error)
} finally {
setLoading(false)
}
}
loadAgents()
}, [])
const handleDeleteAgent = async (agentId: string) => {
try {
await fetch(`/v1/agents/${agentId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${apiKey}` }
})
setAgents(agents.filter(agent => agent.id !== agentId))
} catch (error) {
console.error('Failed to delete agent:', error)
}
}
if (loading) return <div>Loading...</div>
return (
<div>
{agents.map(agent => (
<AgentItem
key={agent.id}
agent={agent}
onDelete={() => handleDeleteAgent(agent.id)}
/>
))}
</div>
)
}
```
### Agent Chat Component
```typescript
function AgentChat({ agentId }: { agentId: string }) {
const [session, setSession] = useState<AgentSessionEntity | null>(null)
const [messages, setMessages] = useState<SessionMessageEntity[]>([])
const [inputMessage, setInputMessage] = useState('')
const [isStreaming, setIsStreaming] = useState(false)
// Create session on component mount
useEffect(() => {
async function initSession() {
try {
const newSession = await createSession(agentId, {
user_goal: "General conversation"
})
setSession(newSession)
// Load existing messages
const messagesResult = await fetch(`/v1/agents/${agentId}/sessions/${newSession.id}/messages`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
}).then(r => r.json())
setMessages(messagesResult.data)
} catch (error) {
console.error('Failed to initialize session:', error)
}
}
initSession()
}, [agentId])
const sendMessage = async () => {
if (!session || !inputMessage.trim() || isStreaming) return
setIsStreaming(true)
try {
// Add user message to UI
const userMessage = {
role: 'user' as const,
content: { text: inputMessage },
created_at: new Date().toISOString()
}
setMessages(prev => [...prev, userMessage as any])
setInputMessage('')
// Send to agent and handle streaming response
const stream = await sendMessageToAgent(agentId, session.id, {
text: inputMessage
})
let agentResponse = ''
await handleAgentResponse(stream, (delta: string) => {
agentResponse += delta
// Update UI with streaming text
setMessages(prev => {
const last = prev[prev.length - 1]
if (last?.role === 'agent') {
return [...prev.slice(0, -1), { ...last, content: { text: agentResponse } }]
} else {
return [...prev, {
role: 'agent',
content: { text: agentResponse },
created_at: new Date().toISOString()
} as any]
}
})
})
} catch (error) {
console.error('Failed to send message:', error)
} finally {
setIsStreaming(false)
}
}
return (
<div className="agent-chat">
<div className="messages">
{messages.map((message, index) => (
<div key={index} className={`message ${message.role}`}>
<div className="content">{message.content.text}</div>
</div>
))}
</div>
<div className="input-area">
<input
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
disabled={isStreaming}
placeholder="Type your message..."
/>
<button onClick={sendMessage} disabled={isStreaming || !inputMessage.trim()}>
{isStreaming ? 'Sending...' : 'Send'}
</button>
</div>
</div>
)
}
```
## Error Handling
### API Error Response Format
```typescript
interface ApiError {
error: {
message: string
type: 'validation_error' | 'not_found' | 'internal_error' | 'authentication_error'
code?: string
details?: any[]
}
}
```
### Error Handling Pattern
```typescript
async function apiRequest<T>(url: string, options: RequestInit = {}): Promise<T> {
try {
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...options.headers
}
})
if (!response.ok) {
const error: ApiError = await response.json()
throw new Error(`${error.error.type}: ${error.error.message}`)
}
return await response.json()
} catch (error) {
console.error('API request failed:', error)
throw error
}
}
```
## Best Practices
### 1. Agent Configuration
- **Minimal Setup**: Start with just `name`, `model`, and `instructions`
- **Gradual Enhancement**: Add `tools`, `mcps`, and `knowledges` as needed
- **Configuration Inheritance**: Sessions inherit agent settings but can override them
### 2. Session Management
- **Single Goal Per Session**: Each session should have one clear `user_goal`
- **Status Tracking**: Always update session status appropriately
- **Resource Cleanup**: Delete completed/failed sessions to manage storage
### 3. Message Streaming
- **Progressive Enhancement**: Show streaming text immediately for better UX
- **Error Recovery**: Handle stream interruptions gracefully
- **Tool Visualization**: Display tool calls and results appropriately
### 4. Performance Considerations
- **Pagination**: Always use `limit` and `offset` for large lists
- **Caching**: Consider caching agent lists locally
- **Debouncing**: Debounce API calls for real-time updates
### 5. User Experience
- **Loading States**: Show loading indicators during API calls
- **Error Messages**: Display user-friendly error messages
- **Optimistic Updates**: Update UI immediately, rollback on errors
## Migration from Redux Implementation
### Step 1: Remove Redux Dependencies
```typescript
// Before
import { useSelector, useDispatch } from 'react-redux'
import { createAgent, listAgents } from '../store/agents'
// After
import { apiRequest } from '../services/api'
```
### Step 2: Replace Redux Hooks
```typescript
// Before
const agents = useSelector(state => state.agents.list)
const dispatch = useDispatch()
// After
const [agents, setAgents] = useState<AgentEntity[]>([])
```
### Step 3: Replace Action Dispatches
```typescript
// Before
dispatch(createAgent(agentData))
// After
const newAgent = await apiRequest<AgentEntity>('/v1/agents', {
method: 'POST',
body: JSON.stringify(agentData)
})
setAgents(prev => [...prev, newAgent])
```
## Conclusion
This new API-based approach provides:
- **Better Performance**: Database storage with efficient queries
- **Real-time Streaming**: AI SDK compatible message streaming
- **Scalability**: Proper pagination and resource management
- **Flexibility**: Session-level configuration overrides
- **Reliability**: Proper error handling and status management
The migration from Redux to direct API integration simplifies the data flow and provides better control over agent interactions.

View File

@ -125,25 +125,16 @@ afterSign: scripts/notarize.js
artifactBuildCompleted: scripts/artifact-build-completed.js
releaseInfo:
releaseNotes: |
✨ 新功能:
- 重构知识库模块,提升文档处理能力和搜索性能
- 新增 PaddleOCR 支持,增强文档识别能力
- 支持自定义窗口控制按钮样式
- 新增 AI SDK 包,扩展 AI 能力集成
- 支持标签页拖拽重排序功能
- 增强笔记编辑器的同步和日志功能
🔧 性能优化:
- 优化 MCP 服务的日志记录和错误处理
- 改进 WebView 服务的 User-Agent 处理
- 优化迷你应用的标题栏样式和状态栏适配
- 重构依赖管理,清理和优化 package.json
🐛 问题修复:
- 修复输入栏无限状态更新循环问题
- 修复窗口控制提示框的鼠标悬停延迟
- 修复翻译输入框粘贴多内容源的处理
- 修复导航服务初始化时序问题
- 修复 MCP 通过 JSON 添加时的参数转换
- 修复模型作用域服务器同步时的 URL 格式
- 标准化工具提示图标样式
- 修复 Anthropic API URL 处理,移除尾部斜杠并添加端点路径处理
- 修复 MessageEditor 缺少 QuickPanelProvider 包装的问题
- 修复 MiniWindow 高度问题
🚀 性能优化:
- 优化输入栏提及模型状态缓存,在渲染间保持状态
- 重构网络搜索参数支持模型内置搜索,新增 OpenAI Chat 和 OpenRouter 支持
🔧 重构改进:
- 更新 HeroUIProvider 导入路径,改善上下文管理
- 更新依赖项和 VSCode 开发环境配置
- 升级 @cherrystudio/ai-core 到 v1.0.0-alpha.17

View File

@ -1,6 +1,6 @@
{
"name": "CherryStudio",
"version": "1.6.0-beta.7",
"version": "1.6.0-rc.2",
"private": true,
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",
@ -116,7 +116,7 @@
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
"@aws-sdk/client-s3": "^3.840.0",
"@biomejs/biome": "2.2.4",
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16",
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
"@cherrystudio/embedjs": "^0.1.31",
"@cherrystudio/embedjs-libsql": "^0.1.31",
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
@ -246,7 +246,6 @@
"diff": "^8.0.2",
"docx": "^9.0.2",
"dompurify": "^3.2.6",
"dotenv": "^17.2.2",
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.31.4",
"electron": "37.4.0",
@ -344,7 +343,7 @@
"tsx": "^4.20.3",
"turndown-plugin-gfm": "^1.0.2",
"tw-animate-css": "^1.3.8",
"typescript": "^5.6.2",
"typescript": "~5.8.2",
"undici": "6.21.2",
"unified": "^11.0.5",
"uuid": "^13.0.0",
@ -385,11 +384,11 @@
"packageManager": "yarn@4.9.1",
"lint-staged": {
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
"biome format --write",
"biome format --write --no-errors-on-unmatched",
"eslint --fix"
],
"*.{json,yml,yaml,css,html}": [
"biome format --write"
"biome format --write --no-errors-on-unmatched"
]
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@cherrystudio/ai-core",
"version": "1.0.0-alpha.16",
"version": "1.0.0-alpha.18",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
"main": "dist/index.js",
"module": "dist/index.mjs",

View File

@ -59,7 +59,7 @@ export function createGoogleOptions(options: ExtractProviderOptions<'google'>) {
/**
* OpenRouter供应商选项的便捷函数
*/
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'>) {
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'> | Record<string, any>) {
return createProviderOptions('openrouter', options)
}

View File

@ -1,38 +0,0 @@
export type OpenRouterProviderOptions = {
models?: string[]
/**
* https://openrouter.ai/docs/use-cases/reasoning-tokens
* One of `max_tokens` or `effort` is required.
* If `exclude` is true, reasoning will be removed from the response. Default is false.
*/
reasoning?: {
exclude?: boolean
} & (
| {
max_tokens: number
}
| {
effort: 'high' | 'medium' | 'low'
}
)
/**
* A unique identifier representing your end-user, which can
* help OpenRouter to monitor and detect abuse.
*/
user?: string
extraBody?: Record<string, unknown>
/**
* Enable usage accounting to get detailed token usage information.
* https://openrouter.ai/docs/use-cases/usage-accounting
*/
usage?: {
/**
* When true, includes token usage information in the response.
*/
include: boolean
}
}

View File

@ -2,9 +2,8 @@ import { type AnthropicProviderOptions } from '@ai-sdk/anthropic'
import { type GoogleGenerativeAIProviderOptions } from '@ai-sdk/google'
import { type OpenAIResponsesProviderOptions } from '@ai-sdk/openai'
import { type SharedV2ProviderMetadata } from '@ai-sdk/provider'
import { type OpenRouterProviderOptions } from './openrouter'
import { type XaiProviderOptions } from './xai'
import { type XaiProviderOptions } from '@ai-sdk/xai'
import { type OpenRouterProviderOptions } from '@openrouter/ai-sdk-provider'
export type ProviderOptions<T extends keyof SharedV2ProviderMetadata> = SharedV2ProviderMetadata[T]

View File

@ -1,86 +0,0 @@
// copy from @ai-sdk/xai/xai-chat-options.ts
// 如果@ai-sdk/xai暴露出了xaiProviderOptions就删除这个文件
import { z } from 'zod'
const webSourceSchema = z.object({
type: z.literal('web'),
country: z.string().length(2).optional(),
excludedWebsites: z.array(z.string()).max(5).optional(),
allowedWebsites: z.array(z.string()).max(5).optional(),
safeSearch: z.boolean().optional()
})
const xSourceSchema = z.object({
type: z.literal('x'),
xHandles: z.array(z.string()).optional()
})
const newsSourceSchema = z.object({
type: z.literal('news'),
country: z.string().length(2).optional(),
excludedWebsites: z.array(z.string()).max(5).optional(),
safeSearch: z.boolean().optional()
})
const rssSourceSchema = z.object({
type: z.literal('rss'),
links: z.array(z.url()).max(1) // currently only supports one RSS link
})
const searchSourceSchema = z.discriminatedUnion('type', [
webSourceSchema,
xSourceSchema,
newsSourceSchema,
rssSourceSchema
])
export const xaiProviderOptions = z.object({
/**
* reasoning effort for reasoning models
* only supported by grok-3-mini and grok-3-mini-fast models
*/
reasoningEffort: z.enum(['low', 'high']).optional(),
searchParameters: z
.object({
/**
* search mode preference
* - "off": disables search completely
* - "auto": model decides whether to search (default)
* - "on": always enables search
*/
mode: z.enum(['off', 'auto', 'on']),
/**
* whether to return citations in the response
* defaults to true
*/
returnCitations: z.boolean().optional(),
/**
* start date for search data (ISO8601 format: YYYY-MM-DD)
*/
fromDate: z.string().optional(),
/**
* end date for search data (ISO8601 format: YYYY-MM-DD)
*/
toDate: z.string().optional(),
/**
* maximum number of search results to consider
* defaults to 20
*/
maxSearchResults: z.number().min(1).max(50).optional(),
/**
* data sources to search from
* defaults to ["web", "x"] if not specified
*/
sources: z.array(searchSourceSchema).optional()
})
.optional()
})
export type XaiProviderOptions = z.infer<typeof xaiProviderOptions>

View File

@ -7,5 +7,9 @@ export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
export { googleToolsPlugin } from './googleToolsPlugin'
export { createLoggingPlugin } from './logging'
export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin'
export type { PromptToolUseConfig, ToolUseRequestContext, ToolUseResult } from './toolUsePlugin/type'
export { webSearchPlugin } from './webSearchPlugin'
export type {
PromptToolUseConfig,
ToolUseRequestContext,
ToolUseResult
} from './toolUsePlugin/type'
export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin'

View File

@ -3,13 +3,16 @@ import { google } from '@ai-sdk/google'
import { openai } from '@ai-sdk/openai'
import { ProviderOptionsMap } from '../../../options/types'
import { OpenRouterSearchConfig } from './openrouter'
/**
* AI SDK
*/
type OpenAISearchConfig = Parameters<typeof openai.tools.webSearchPreview>[0]
type AnthropicSearchConfig = Parameters<typeof anthropic.tools.webSearch_20250305>[0]
type GoogleSearchConfig = Parameters<typeof google.tools.googleSearch>[0]
export type OpenAISearchConfig = NonNullable<Parameters<typeof openai.tools.webSearch>[0]>
export type OpenAISearchPreviewConfig = NonNullable<Parameters<typeof openai.tools.webSearchPreview>[0]>
export type AnthropicSearchConfig = NonNullable<Parameters<typeof anthropic.tools.webSearch_20250305>[0]>
export type GoogleSearchConfig = NonNullable<Parameters<typeof google.tools.googleSearch>[0]>
export type XAISearchConfig = NonNullable<ProviderOptionsMap['xai']['searchParameters']>
/**
*
@ -18,10 +21,12 @@ type GoogleSearchConfig = Parameters<typeof google.tools.googleSearch>[0]
*/
export interface WebSearchPluginConfig {
openai?: OpenAISearchConfig
'openai-chat'?: OpenAISearchPreviewConfig
anthropic?: AnthropicSearchConfig
xai?: ProviderOptionsMap['xai']['searchParameters']
google?: GoogleSearchConfig
'google-vertex'?: GoogleSearchConfig
openrouter?: OpenRouterSearchConfig
}
/**
@ -31,6 +36,7 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
google: {},
'google-vertex': {},
openai: {},
'openai-chat': {},
xai: {
mode: 'on',
returnCitations: true,
@ -39,6 +45,14 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
},
anthropic: {
maxUses: 5
},
openrouter: {
plugins: [
{
id: 'web',
max_results: 5
}
]
}
}

View File

@ -6,7 +6,7 @@ import { anthropic } from '@ai-sdk/anthropic'
import { google } from '@ai-sdk/google'
import { openai } from '@ai-sdk/openai'
import { createXaiOptions, mergeProviderOptions } from '../../../options'
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
import { definePlugin } from '../../'
import type { AiRequestContext } from '../../types'
import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper'
@ -31,6 +31,13 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
}
break
}
case 'openai-chat': {
if (config['openai-chat']) {
if (!params.tools) params.tools = {}
params.tools.web_search_preview = openai.tools.webSearchPreview(config['openai-chat'])
}
break
}
case 'anthropic': {
if (config.anthropic) {
@ -56,6 +63,14 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
}
break
}
case 'openrouter': {
if (config.openrouter) {
const searchOptions = createOpenRouterOptions(config.openrouter)
params.providerOptions = mergeProviderOptions(params.providerOptions, searchOptions)
}
break
}
}
return params

View File

@ -0,0 +1,26 @@
export type OpenRouterSearchConfig = {
plugins?: Array<{
id: 'web'
/**
* Maximum number of search results to include (default: 5)
*/
max_results?: number
/**
* Custom search prompt to guide the search query
*/
search_prompt?: string
}>
/**
* Built-in web search options for models that support native web search
*/
web_search_options?: {
/**
* Maximum number of search results to include
*/
max_results?: number
/**
* Custom search prompt to guide the search query
*/
search_prompt?: string
}
}

View File

@ -9,7 +9,9 @@ import { createDeepSeek } from '@ai-sdk/deepseek'
import { createGoogleGenerativeAI } from '@ai-sdk/google'
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
import { LanguageModelV2 } from '@ai-sdk/provider'
import { createXai } from '@ai-sdk/xai'
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
import { customProvider, Provider } from 'ai'
import { z } from 'zod'
@ -25,7 +27,8 @@ export const baseProviderIds = [
'xai',
'azure',
'azure-responses',
'deepseek'
'deepseek',
'openrouter'
] as const
/**
@ -38,10 +41,14 @@ export const baseProviderIdSchema = z.enum(baseProviderIds)
*/
export type BaseProviderId = z.infer<typeof baseProviderIdSchema>
export const isBaseProvider = (id: ProviderId): id is BaseProviderId => {
return baseProviderIdSchema.safeParse(id).success
}
type BaseProvider = {
id: BaseProviderId
name: string
creator: (options: any) => Provider
creator: (options: any) => Provider | LanguageModelV2
supportsImageGeneration: boolean
}
@ -119,6 +126,12 @@ export const baseProviders = [
name: 'DeepSeek',
creator: createDeepSeek,
supportsImageGeneration: false
},
{
id: 'openrouter',
name: 'OpenRouter',
creator: createOpenRouter,
supportsImageGeneration: true
}
] as const satisfies BaseProvider[]

View File

@ -216,5 +216,6 @@ export enum codeTools {
qwenCode = 'qwen-code',
claudeCode = 'claude-code',
geminiCli = 'gemini-cli',
openaiCodex = 'openai-codex'
openaiCodex = 'openai-codex',
iFlowCli = 'iflow-cli'
}

241
plan.md
View File

@ -1,241 +0,0 @@
Overview
Implement comprehensive CRUD APIs for agent, agentSession, and agentSessionLogs management
in Cherry Studio's API server using RESTful URL conventions.
Architecture Overview
1. Service Layer
- Create AgentService class in src/main/services/agents/AgentService.ts
- Handles database operations using SQL queries from db.ts
- Manages SQLite database initialization and connections
- Provides business logic for agent operations
2. API Routes
- Create route files in src/main/apiServer/routes/:
- agents.ts - Agent CRUD endpoints
- sessions.ts - Session CRUD endpoints
- session-logs.ts - Session logs CRUD endpoints
3. Database Integration
- Use SQLite with @libsql/client (following MemoryService pattern)
- Database location: userData/agents.db
- Leverage existing SQL queries in src/main/services/agents/db.ts
Implementation Steps
Phase 1: Database Service Setup
1. Create AgentService class with database initialization
2. Implement database connection management
3. Add database initialization to main process startup
4. Create helper methods for JSON field serialization/deserialization
Phase 2: Agent CRUD Operations
1. Implement service methods:
- createAgent(agent: Omit<AgentEntity, 'id' | 'created_at' | 'updated_at'>)
- getAgent(id: string)
- listAgents(options?: { limit?: number, offset?: number })
- updateAgent(id: string, updates: Partial<AgentEntity>)
- deleteAgent(id: string)
2. Create API routes:
- POST /v1/agents - Create agent
- GET /v1/agents - List all agents
- GET /v1/agents/:agentId - Get agent by ID
- PUT /v1/agents/:agentId - Update agent
- DELETE /v1/agents/:agentId - Delete agent
Phase 3: Session CRUD Operations
1. Implement service methods:
- createSession(session: Omit<AgentSessionEntity, 'id' | 'created_at' | 'updated_at'>)
- getSession(id: string)
- listSessions(agentId?: string, options?: { status?: SessionStatus, limit?: number,
offset?: number })
- updateSession(id: string, updates: Partial<AgentSessionEntity>)
- updateSessionStatus(id: string, status: SessionStatus)
- deleteSession(id: string)
- getSessionWithAgent(id: string) - Get session with merged agent configuration
2. Create API routes (RESTful nested resources):
- POST /v1/agents/:agentId/sessions - Create session for specific agent
- GET /v1/agents/:agentId/sessions - List sessions for specific agent
- GET /v1/agents/:agentId/sessions/:sessionId - Get specific session
- PUT /v1/agents/:agentId/sessions/:sessionId - Update session
- PATCH /v1/agents/:agentId/sessions/:sessionId/status - Update session status
- DELETE /v1/agents/:agentId/sessions/:sessionId - Delete session
Additional convenience endpoints:
- GET /v1/sessions - List all sessions (across all agents)
- GET /v1/sessions/:sessionId - Get session by ID (without agent context)
Phase 4: Session Logs CRUD Operations
1. Implement service methods:
- createSessionLog(log: Omit<SessionLogEntity, 'id' | 'created_at' | 'updated_at'>)
- getSessionLog(id: number)
- listSessionLogs(sessionId: string, options?: { limit?: number, offset?: number })
- updateSessionLog(id: number, updates: { content?: any, metadata?: any })
- deleteSessionLog(id: number)
- getSessionLogTree(sessionId: string) - Get logs with parent-child relationships
- bulkCreateSessionLogs(logs: Array<...>) - Batch insert logs
2. Create API routes (RESTful nested resources):
- POST /v1/agents/:agentId/sessions/:sessionId/logs - Create log entry
- GET /v1/agents/:agentId/sessions/:sessionId/logs - List logs for session
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Get specific log
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Update log
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId - Delete log
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk - Bulk create logs
Additional convenience endpoints:
- GET /v1/sessions/:sessionId/logs - Get logs without agent context
- GET /v1/session-logs/:logId - Get specific log by ID
Phase 5: Route Organization
1. Mount routes with proper nesting:
// In app.ts
apiRouter.use('/agents', agentsRoutes)
// agentsRoutes will handle:
// - /agents/_
// - /agents/:agentId/sessions/_
// - /agents/:agentId/sessions/:sessionId/logs/\*
// Convenience routes
apiRouter.use('/sessions', sessionsRoutes)
apiRouter.use('/session-logs', sessionLogsRoutes)
2. Use Express Router mergeParams for nested routes:
// In agents.ts
const sessionsRouter = express.Router({ mergeParams: true })
router.use('/:agentId/sessions', sessionsRouter)
Phase 6: OpenAPI Documentation
1. Add Swagger schemas for new entities:
- AgentEntity schema
- AgentSessionEntity schema
- SessionLogEntity schema
- Request/Response schemas
2. Document all new endpoints with:
- Clear path parameters (agentId, sessionId, logId)
- Request body schemas
- Response examples
- Error responses
- Proper grouping by resource
Phase 7: Validation & Error Handling
1. Add path parameter validation:
- Validate agentId exists before processing session requests
- Validate sessionId belongs to agentId
- Validate logId belongs to sessionId
2. Implement middleware for:
- Request validation using express-validator
- Resource existence checks
- Permission validation (future consideration)
- Transaction support for complex operations
Phase 8: Testing
1. Unit tests for service methods
2. Integration tests for API endpoints
3. Test nested resource validation
4. Test cascading deletes
5. Test transaction rollbacks
File Structure
src/
├── main/
│ └── services/
│ └── agents/
│ ├── index.ts (existing)
│ ├── db.ts (existing)
│ └── AgentService.ts (new)
├── main/
│ └── apiServer/
│ └── routes/
│ ├── agents.ts (new - includes nested routes)
│ ├── sessions.ts (new - convenience endpoints)
│ └── session-logs.ts (new - convenience endpoints)
└── renderer/
└── src/
└── types/
└── agent.ts (existing)
API Endpoint Summary
Agent Endpoints
- POST /v1/agents
- GET /v1/agents
- GET /v1/agents/:agentId
- PUT /v1/agents/:agentId
- DELETE /v1/agents/:agentId
Session Endpoints (RESTful)
- POST /v1/agents/:agentId/sessions
- GET /v1/agents/:agentId/sessions
- GET /v1/agents/:agentId/sessions/:sessionId
- PUT /v1/agents/:agentId/sessions/:sessionId
- PATCH /v1/agents/:agentId/sessions/:sessionId/status
- DELETE /v1/agents/:agentId/sessions/:sessionId
Session Convenience Endpoints
- GET /v1/sessions
- GET /v1/sessions/:sessionId
Session Log Endpoints (RESTful)
- POST /v1/agents/:agentId/sessions/:sessionId/logs
- GET /v1/agents/:agentId/sessions/:sessionId/logs
- GET /v1/agents/:agentId/sessions/:sessionId/logs/:logId
- PUT /v1/agents/:agentId/sessions/:sessionId/logs/:logId
- DELETE /v1/agents/:agentId/sessions/:sessionId/logs/:logId
- POST /v1/agents/:agentId/sessions/:sessionId/logs/bulk
Session Log Convenience Endpoints
- GET /v1/sessions/:sessionId/logs
- GET /v1/session-logs/:logId
Key Considerations
- Follow RESTful URL conventions with proper resource nesting
- Validate parent-child relationships in nested routes
- Use Express Router with mergeParams for nested routing
- Implement proper cascading deletes
- Add transaction support for data consistency
- Follow existing patterns from MemoryService
- Ensure backward compatibility
- Add rate limiting for write operations
Dependencies
- @libsql/client - SQLite database client
- express-validator - Request validation
- swagger-jsdoc - API documentation
- Existing types from @types/agent.ts

View File

@ -51,6 +51,8 @@ class CodeToolsService {
return '@openai/codex'
case codeTools.qwenCode:
return '@qwen-code/qwen-code'
case codeTools.iFlowCli:
return '@iflow-ai/iflow-cli'
default:
throw new Error(`Unsupported CLI tool: ${cliTool}`)
}
@ -66,6 +68,8 @@ class CodeToolsService {
return 'codex'
case codeTools.qwenCode:
return 'qwen'
case codeTools.iFlowCli:
return 'iflow'
default:
throw new Error(`Unsupported CLI tool: ${cliTool}`)
}

View File

@ -1,3 +1,4 @@
import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins'
import { loggerService } from '@logger'
import type { MCPTool, Message, Model, Provider } from '@renderer/types'
import type { Chunk } from '@renderer/types/chunk'
@ -26,6 +27,8 @@ export interface AiSdkMiddlewareConfig {
enableUrlContext: boolean
mcpTools?: MCPTool[]
uiMessages?: Message[]
// 内置搜索配置
webSearchPluginConfig?: WebSearchPluginConfig
}
/**
@ -137,7 +140,7 @@ export function buildAiSdkMiddlewares(config: AiSdkMiddlewareConfig): LanguageMo
return builder.build()
}
const tagNameArray = ['think', 'thought']
const tagNameArray = ['think', 'thought', 'reasoning']
/**
* provider特定的中间件
@ -164,6 +167,16 @@ function addProviderSpecificMiddlewares(builder: AiSdkMiddlewareBuilder, config:
case 'gemini':
// Gemini特定中间件
break
case 'aws-bedrock': {
if (config.model?.id.includes('gpt-oss')) {
const tagName = tagNameArray[2]
builder.add({
name: 'thinking-tag-extraction',
middleware: extractReasoningMiddleware({ tagName })
})
}
break
}
default:
// 其他provider的通用处理
break

View File

@ -30,9 +30,8 @@ export function buildPlugins(
}
// 1. 模型内置搜索
if (middlewareConfig.enableWebSearch) {
// 内置了默认搜索参数如果改的话可以传config进去
plugins.push(webSearchPlugin())
if (middlewareConfig.enableWebSearch && middlewareConfig.webSearchPluginConfig) {
plugins.push(webSearchPlugin(middlewareConfig.webSearchPluginConfig))
}
// 2. 支持工具调用时添加搜索插件
if (middlewareConfig.isSupportedToolUse || middlewareConfig.isPromptToolUse) {

View File

@ -5,6 +5,8 @@
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge'
import { vertex } from '@ai-sdk/google-vertex/edge'
import { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins'
import { isBaseProvider } from '@cherrystudio/ai-core/core/providers/schemas'
import { loggerService } from '@logger'
import {
isGenerateImageModel,
@ -16,8 +18,11 @@ import {
isWebSearchModel
} from '@renderer/config/models'
import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService'
import store from '@renderer/store'
import { CherryWebSearchConfig } from '@renderer/store/websearch'
import { type Assistant, type MCPTool, type Provider } from '@renderer/types'
import type { StreamTextParams } from '@renderer/types/aiCoreTypes'
import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern'
import type { ModelMessage, Tool } from 'ai'
import { stepCountIs } from 'ai'
@ -25,6 +30,7 @@ import { getAiSdkProviderId } from '../provider/factory'
import { setupToolsConfig } from '../utils/mcp'
import { buildProviderOptions } from '../utils/options'
import { getAnthropicThinkingBudget } from '../utils/reasoning'
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
import { getTemperature, getTopP } from './modelParameters'
const logger = loggerService.withContext('parameterBuilder')
@ -42,6 +48,7 @@ export async function buildStreamTextParams(
options: {
mcpTools?: MCPTool[]
webSearchProviderId?: string
webSearchConfig?: CherryWebSearchConfig
requestOptions?: {
signal?: AbortSignal
timeout?: number
@ -57,6 +64,7 @@ export async function buildStreamTextParams(
enableGenerateImage: boolean
enableUrlContext: boolean
}
webSearchPluginConfig?: WebSearchPluginConfig
}> {
const { mcpTools } = options
@ -93,6 +101,12 @@ export async function buildStreamTextParams(
// }
// 构建真正的 providerOptions
const webSearchConfig: CherryWebSearchConfig = {
maxResults: store.getState().websearch.maxResults,
excludeDomains: store.getState().websearch.excludeDomains,
searchWithTime: store.getState().websearch.searchWithTime
}
const providerOptions = buildProviderOptions(assistant, model, provider, {
enableReasoning,
enableWebSearch,
@ -109,15 +123,21 @@ export async function buildStreamTextParams(
maxTokens -= getAnthropicThinkingBudget(assistant, model)
}
// google-vertex | google-vertex-anthropic
let webSearchPluginConfig: WebSearchPluginConfig | undefined = undefined
if (enableWebSearch) {
if (isBaseProvider(aiSdkProviderId)) {
webSearchPluginConfig = buildProviderBuiltinWebSearchConfig(aiSdkProviderId, webSearchConfig)
}
if (!tools) {
tools = {}
}
if (aiSdkProviderId === 'google-vertex') {
tools.google_search = vertex.tools.googleSearch({}) as ProviderDefinedTool
} else if (aiSdkProviderId === 'google-vertex-anthropic') {
tools.web_search = vertexAnthropic.tools.webSearch_20250305({}) as ProviderDefinedTool
tools.web_search = vertexAnthropic.tools.webSearch_20250305({
maxUses: webSearchConfig.maxResults,
blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains)
}) as ProviderDefinedTool
}
}
@ -151,7 +171,8 @@ export async function buildStreamTextParams(
return {
params,
modelId: model.id,
capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext }
capabilities: { enableReasoning, enableWebSearch, enableGenerateImage, enableUrlContext },
webSearchPluginConfig
}
}

View File

@ -69,6 +69,9 @@ export function getAiSdkProviderId(provider: Provider): ProviderId | 'openai-com
return resolvedFromType
}
}
if (provider.apiHost.includes('api.openai.com')) {
return 'openai-chat'
}
// 3. 最后的fallback通常会成为openai-compatible
return provider.id as ProviderId
}

View File

@ -82,6 +82,7 @@ export function buildProviderOptions(
// 应该覆盖所有类型
switch (baseProviderId) {
case 'openai':
case 'openai-chat':
case 'azure':
providerSpecificOptions = {
...buildOpenAIProviderOptions(assistant, model, capabilities),
@ -101,13 +102,15 @@ export function buildProviderOptions(
providerSpecificOptions = buildXAIProviderOptions(assistant, model, capabilities)
break
case 'deepseek':
case 'openai-compatible':
case 'openrouter':
case 'openai-compatible': {
// 对于其他 provider使用通用的构建逻辑
providerSpecificOptions = {
...buildGenericProviderOptions(assistant, model, capabilities),
serviceTier: serviceTierSetting
}
break
}
default:
throw new Error(`Unsupported base provider ${baseProviderId}`)
}

View File

@ -312,7 +312,7 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re
export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): number {
const { maxTokens, reasoning_effort: reasoningEffort } = getAssistantSettings(assistant)
if (maxTokens === undefined || reasoningEffort === undefined) {
if (reasoningEffort === undefined) {
return 0
}
const effortRatio = EFFORT_RATIO[reasoningEffort]

View File

@ -1,6 +1,13 @@
import {
AnthropicSearchConfig,
OpenAISearchConfig,
WebSearchPluginConfig
} from '@cherrystudio/ai-core/core/plugins/built-in/webSearchPlugin/helper'
import { BaseProviderId } from '@cherrystudio/ai-core/provider'
import { isOpenAIWebSearchChatCompletionOnlyModel } from '@renderer/config/models'
import { WEB_SEARCH_PROMPT_FOR_OPENROUTER } from '@renderer/config/prompts'
import { CherryWebSearchConfig } from '@renderer/store/websearch'
import { Model } from '@renderer/types'
import { mapRegexToPatterns } from '@renderer/utils/blacklistMatchPattern'
export function getWebSearchParams(model: Model): Record<string, any> {
if (model.provider === 'hunyuan') {
@ -21,11 +28,78 @@ export function getWebSearchParams(model: Model): Record<string, any> {
web_search_options: {}
}
}
if (model.provider === 'openrouter') {
return {
plugins: [{ id: 'web', search_prompts: WEB_SEARCH_PROMPT_FOR_OPENROUTER }]
}
}
return {}
}
/**
* range in [0, 100]
* @param maxResults
*/
function mapMaxResultToOpenAIContextSize(maxResults: number): OpenAISearchConfig['searchContextSize'] {
if (maxResults <= 33) return 'low'
if (maxResults <= 66) return 'medium'
return 'high'
}
export function buildProviderBuiltinWebSearchConfig(
providerId: BaseProviderId,
webSearchConfig: CherryWebSearchConfig
): WebSearchPluginConfig {
switch (providerId) {
case 'openai': {
return {
openai: {
searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults)
}
}
}
case 'openai-chat': {
return {
'openai-chat': {
searchContextSize: mapMaxResultToOpenAIContextSize(webSearchConfig.maxResults)
}
}
}
case 'anthropic': {
const anthropicSearchOptions: AnthropicSearchConfig = {
maxUses: webSearchConfig.maxResults,
blockedDomains: mapRegexToPatterns(webSearchConfig.excludeDomains)
}
return {
anthropic: anthropicSearchOptions
}
}
case 'xai': {
return {
xai: {
maxSearchResults: webSearchConfig.maxResults,
returnCitations: true,
sources: [
{
type: 'web',
excludedWebsites: mapRegexToPatterns(webSearchConfig.excludeDomains)
},
{ type: 'news' },
{ type: 'x' }
],
mode: 'on'
}
}
}
case 'openrouter': {
return {
openrouter: {
plugins: [
{
id: 'web',
max_results: webSearchConfig.maxResults
}
]
}
}
}
default: {
throw new Error(`Unsupported provider: ${providerId}`)
}
}
}

View File

@ -229,6 +229,11 @@ export const isGPT5SeriesModel = (model: Model) => {
return modelId.includes('gpt-5')
}
export const isGPT5SeriesReasoningModel = (model: Model) => {
const modelId = getLowerBaseModelName(model.id)
return modelId.includes('gpt-5') && !modelId.includes('chat')
}
export const isGeminiModel = (model: Model) => {
const modelId = getLowerBaseModelName(model.id)
return modelId.includes('gemini')

View File

@ -1020,7 +1020,7 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
},
anthropic: {
api: {
url: 'https://api.anthropic.com/'
url: 'https://api.anthropic.com'
},
websites: {
official: 'https://anthropic.com/',

View File

@ -38,7 +38,7 @@ export function useAppInit() {
customCss,
enableDataCollection
} = useSettings()
const { isTopNavbar } = useNavbarPosition()
const { isLeftNavbar } = useNavbarPosition()
const { minappShow } = useRuntime()
const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
@ -102,16 +102,15 @@ export function useAppInit() {
}, [language])
useEffect(() => {
const transparentWindow = windowStyle === 'transparent' && isMac && !minappShow
const isMacTransparentWindow = windowStyle === 'transparent' && isMac
if (minappShow && isTopNavbar) {
window.root.style.background =
windowStyle === 'transparent' && isMac ? 'var(--color-background)' : 'var(--navbar-background)'
if (minappShow && isLeftNavbar) {
window.root.style.background = isMacTransparentWindow ? 'var(--color-background)' : 'var(--navbar-background)'
return
}
window.root.style.background = transparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
}, [windowStyle, minappShow, theme, isTopNavbar])
window.root.style.background = isMacTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
}, [windowStyle, minappShow, theme, isLeftNavbar])
useEffect(() => {
if (isLocalAi) {

View File

@ -679,7 +679,12 @@
"title": "Topics",
"unpin": "Unpin Topic"
},
"translate": "Translate"
"translate": "Translate",
"web_search": {
"warning": {
"openai": "The GPT-5 model's minimal reasoning effort does not support web search."
}
}
},
"code": {
"auto_update_to_latest": "Automatically update to latest version",

View File

@ -679,7 +679,12 @@
"title": "话题",
"unpin": "取消固定"
},
"translate": "翻译"
"translate": "翻译",
"web_search": {
"warning": {
"openai": "GPT5 模型 minimal 思考强度不支持网络搜索"
}
}
},
"code": {
"auto_update_to_latest": "检查更新并安装最新版本",

View File

@ -679,7 +679,12 @@
"title": "話題",
"unpin": "取消固定"
},
"translate": "翻譯"
"translate": "翻譯",
"web_search": {
"warning": {
"openai": "GPT-5 模型的最小推理力度不支援網路搜尋。"
}
}
},
"code": {
"auto_update_to_latest": "檢查更新並安裝最新版本",

View File

@ -381,8 +381,9 @@
"translate": "Μετάφραση στο {{target_language}}",
"translating": "Μετάφραση...",
"upload": {
"attachment": "Μεταφόρτωση συνημμένου",
"document": "Φόρτωση έγγραφου (το μοντέλο δεν υποστηρίζει εικόνες)",
"label": "Φόρτωση εικόνας ή έγγραφου",
"image_or_document": "Μεταφόρτωση εικόνας ή εγγράφου",
"upload_from_local": "Μεταφόρτωση αρχείου από τον υπολογιστή..."
},
"url_context": "Περιεχόμενο ιστοσελίδας",
@ -678,7 +679,12 @@
"title": "Θέματα",
"unpin": "Ξεκαρφίτσωμα"
},
"translate": "Μετάφραση"
"translate": "Μετάφραση",
"web_search": {
"warning": {
"openai": "Το μοντέλο GPT5 με ελάχιστη ένταση σκέψης δεν υποστηρίζει αναζήτηση στο διαδίκτυο"
}
}
},
"code": {
"auto_update_to_latest": "Έλεγχος για ενημερώσεις και εγκατάσταση της τελευταίας έκδοσης",

View File

@ -381,8 +381,9 @@
"translate": "Traducir a {{target_language}}",
"translating": "Traduciendo...",
"upload": {
"attachment": "Subir archivo adjunto",
"document": "Subir documento (el modelo no admite imágenes)",
"label": "Subir imagen o documento",
"image_or_document": "Subir imagen o documento",
"upload_from_local": "Subir archivo local..."
},
"url_context": "Contexto de la página web",
@ -678,7 +679,12 @@
"title": "Tema",
"unpin": "Quitar fijación"
},
"translate": "Traducir"
"translate": "Traducir",
"web_search": {
"warning": {
"openai": "El modelo GPT5 con intensidad de pensamiento mínima no admite búsqueda en la web."
}
}
},
"code": {
"auto_update_to_latest": "Comprobar actualizaciones e instalar la versión más reciente",

View File

@ -381,8 +381,9 @@
"translate": "Traduire en {{target_language}}",
"translating": "Traduction en cours...",
"upload": {
"attachment": "Télécharger la pièce jointe",
"document": "Télécharger un document (le modèle ne prend pas en charge les images)",
"label": "Télécharger une image ou un document",
"image_or_document": "Télécharger une image ou un document",
"upload_from_local": "Télécharger un fichier local..."
},
"url_context": "Contexte de la page web",
@ -678,7 +679,12 @@
"title": "Sujet",
"unpin": "Annuler le fixage"
},
"translate": "Traduire"
"translate": "Traduire",
"web_search": {
"warning": {
"openai": "Le modèle GPT5 avec une intensité de réflexion minimale ne prend pas en charge la recherche sur Internet."
}
}
},
"code": {
"auto_update_to_latest": "Vérifier les mises à jour et installer la dernière version",

View File

@ -381,8 +381,9 @@
"translate": "{{target_language}}に翻訳",
"translating": "翻訳中...",
"upload": {
"attachment": "添付ファイルをアップロード",
"document": "ドキュメントをアップロード(モデルは画像をサポートしません)",
"label": "画像またはドキュメントをアップロード",
"image_or_document": "画像またはドキュメントをアップロード",
"upload_from_local": "ローカルファイルをアップロード..."
},
"url_context": "URLコンテキスト",
@ -678,7 +679,12 @@
"title": "トピック",
"unpin": "固定解除"
},
"translate": "翻訳"
"translate": "翻訳",
"web_search": {
"warning": {
"openai": "GPT5モデルの最小思考強度ではネット検索はサポートされません"
}
}
},
"code": {
"auto_update_to_latest": "最新バージョンを自動的に更新する",

View File

@ -381,8 +381,9 @@
"translate": "Traduzir para {{target_language}}",
"translating": "Traduzindo...",
"upload": {
"attachment": "Carregar anexo",
"document": "Carregar documento (o modelo não suporta imagens)",
"label": "Carregar imagem ou documento",
"image_or_document": "Carregar imagem ou documento",
"upload_from_local": "Fazer upload de arquivo local..."
},
"url_context": "Contexto da Página da Web",
@ -678,7 +679,12 @@
"title": "Tópicos",
"unpin": "Desfixar"
},
"translate": "Traduzir"
"translate": "Traduzir",
"web_search": {
"warning": {
"openai": "O modelo GPT5 com intensidade mínima de pensamento não suporta pesquisa na web"
}
}
},
"code": {
"auto_update_to_latest": "Verificar atualizações e instalar a versão mais recente",

View File

@ -381,8 +381,9 @@
"translate": "Перевести на {{target_language}}",
"translating": "Перевод...",
"upload": {
"attachment": "Загрузить вложение",
"document": "Загрузить документ (модель не поддерживает изображения)",
"label": "Загрузить изображение или документ",
"image_or_document": "Загрузить изображение или документ",
"upload_from_local": "Загрузить локальный файл..."
},
"url_context": "Контекст страницы",
@ -678,7 +679,12 @@
"title": "Топики",
"unpin": "Открепленные темы"
},
"translate": "Перевести"
"translate": "Перевести",
"web_search": {
"warning": {
"openai": "Модель GPT5 с минимальной интенсивностью мышления не поддерживает поиск в интернете"
}
}
},
"code": {
"auto_update_to_latest": "Автоматически обновлять до последней версии",

View File

@ -19,7 +19,8 @@ export const CLI_TOOLS = [
{ value: codeTools.claudeCode, label: 'Claude Code' },
{ value: codeTools.qwenCode, label: 'Qwen Code' },
{ value: codeTools.geminiCli, label: 'Gemini CLI' },
{ value: codeTools.openaiCodex, label: 'OpenAI Codex' }
{ value: codeTools.openaiCodex, label: 'OpenAI Codex' },
{ value: codeTools.iFlowCli, label: 'iFlow CLI' }
]
export const GEMINI_SUPPORTED_PROVIDERS = ['aihubmix', 'dmxapi', 'new-api']
@ -35,7 +36,8 @@ export const CLI_TOOL_PROVIDER_MAP: Record<string, (providers: Provider[]) => Pr
providers.filter((p) => p.type === 'gemini' || GEMINI_SUPPORTED_PROVIDERS.includes(p.id)),
[codeTools.qwenCode]: (providers) => providers.filter((p) => p.type.includes('openai')),
[codeTools.openaiCodex]: (providers) =>
providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id))
providers.filter((p) => p.id === 'openai' || OPENAI_CODEX_SUPPORTED_PROVIDERS.includes(p.id)),
[codeTools.iFlowCli]: (providers) => providers.filter((p) => p.type.includes('openai'))
}
export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => {
@ -144,6 +146,12 @@ export const generateToolEnvironment = ({
env.OPENAI_MODEL = model.id
env.OPENAI_MODEL_PROVIDER = modelProvider.id
break
case codeTools.iFlowCli:
env.IFLOW_API_KEY = apiKey
env.IFLOW_BASE_URL = baseUrl
env.IFLOW_MODEL_NAME = model.id
break
}
return env

View File

@ -155,23 +155,23 @@ const Chat: FC<Props> = (props) => {
flex={1}
justify="space-between"
style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
<Messages
key={props.activeTopic.id}
assistant={assistant}
topic={props.activeTopic}
setActiveTopic={props.setActiveTopic}
onComponentUpdate={messagesComponentUpdateHandler}
onFirstUpdate={messagesComponentFirstUpdateHandler}
/>
<ContentSearch
ref={contentSearchRef}
searchTarget={mainRef as React.RefObject<HTMLElement>}
filter={contentSearchFilter}
includeUser={filterIncludeUser}
onIncludeUserChange={userOutlinedItemClickHandler}
/>
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
<QuickPanelProvider>
<Messages
key={props.activeTopic.id}
assistant={assistant}
topic={props.activeTopic}
setActiveTopic={props.setActiveTopic}
onComponentUpdate={messagesComponentUpdateHandler}
onFirstUpdate={messagesComponentFirstUpdateHandler}
/>
<ContentSearch
ref={contentSearchRef}
searchTarget={mainRef as React.RefObject<HTMLElement>}
filter={contentSearchFilter}
includeUser={filterIncludeUser}
onIncludeUserChange={userOutlinedItemClickHandler}
/>
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
</QuickPanelProvider>

View File

@ -71,6 +71,7 @@ interface Props {
let _text = ''
let _files: FileType[] = []
let _mentionedModelsCache: Model[] = []
const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) => {
const [text, setText] = useState(_text)
@ -103,7 +104,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
const [isTranslating, setIsTranslating] = useState(false)
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
const [mentionedModels, setMentionedModels] = useState<Model[]>([])
const [mentionedModels, setMentionedModels] = useState<Model[]>(_mentionedModelsCache)
const mentionedModelsRef = useRef(mentionedModels)
const [isDragging, setIsDragging] = useState(false)
const [isFileDragging, setIsFileDragging] = useState(false)
const [textareaHeight, setTextareaHeight] = useState<number>()
@ -114,6 +116,10 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model])
const { setTimeoutTimer } = useTimer()
useEffect(() => {
mentionedModelsRef.current = mentionedModels
}, [mentionedModels])
const isVisionSupported = useMemo(
() =>
(mentionedModels.length > 0 && isVisionModels(mentionedModels)) ||
@ -179,6 +185,13 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
_text = text
_files = files
useEffect(() => {
// 利用useEffect清理函数在卸载组件时更新状态缓存
return () => {
_mentionedModelsCache = mentionedModelsRef.current
}
}, [])
const focusTextarea = useCallback(() => {
textareaRef.current?.focus()
}, [])

View File

@ -8,7 +8,13 @@ import {
MdiLightbulbOn80
} from '@renderer/components/Icons/SVGIcon'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { getThinkModelType, isDoubaoThinkingAutoModel, MODEL_SUPPORTED_OPTIONS } from '@renderer/config/models'
import {
getThinkModelType,
isDoubaoThinkingAutoModel,
isGPT5SeriesReasoningModel,
isOpenAIWebSearchModel,
MODEL_SUPPORTED_OPTIONS
} from '@renderer/config/models'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label'
import { Model, ThinkingOption } from '@renderer/types'
@ -61,6 +67,15 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement =>
})
return
}
if (
isOpenAIWebSearchModel(model) &&
isGPT5SeriesReasoningModel(model) &&
assistant.enableWebSearch &&
option === 'minimal'
) {
window.toast.warning(t('chat.web_search.warning.openai'))
return
}
updateAssistantSettings({
reasoning_effort: option,
reasoning_effort_cache: option,
@ -68,7 +83,7 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement =>
})
return
},
[updateAssistantSettings]
[updateAssistantSettings, assistant.enableWebSearch, model, t]
)
const panelItems = useMemo(() => {

View File

@ -3,7 +3,12 @@ import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons'
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons'
import { QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { isGeminiModel, isWebSearchModel } from '@renderer/config/models'
import {
isGeminiModel,
isGPT5SeriesReasoningModel,
isOpenAIWebSearchModel,
isWebSearchModel
} from '@renderer/config/models'
import { isGeminiWebSearchProvider } from '@renderer/config/providers'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer'
@ -115,6 +120,15 @@ const WebSearchButton: FC<Props> = ({ ref, assistantId }) => {
update.enableWebSearch = false
window.toast.warning(t('chat.mcp.warning.gemini_web_search'))
}
if (
isOpenAIWebSearchModel(model) &&
isGPT5SeriesReasoningModel(model) &&
update.enableWebSearch &&
assistant.settings?.reasoning_effort === 'minimal'
) {
update.enableWebSearch = false
window.toast.warning(t('chat.web_search.warning.openai'))
}
setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200)
}, [assistant, setTimeoutTimer, t, updateAssistant])

View File

@ -16,7 +16,13 @@ import { useAppDispatch } from '@renderer/store'
import { updateWebSearchProvider } from '@renderer/store/websearch'
import { isSystemProvider } from '@renderer/types'
import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck'
import { formatApiHost, formatApiKeys, getFancyProviderName, isOpenAIProvider } from '@renderer/utils'
import {
formatApiHost,
formatApiKeys,
getFancyProviderName,
isAnthropicProvider,
isOpenAIProvider
} from '@renderer/utils'
import { formatErrorMessage } from '@renderer/utils/error'
import { Button, Divider, Flex, Input, Select, Space, Switch, Tooltip } from 'antd'
import Link from 'antd/es/typography/Link'
@ -212,6 +218,10 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
if (provider.type === 'azure-openai') {
return formatApiHost(apiHost) + 'openai/v1'
}
if (provider.type === 'anthropic') {
return formatApiHost(apiHost) + 'messages'
}
return formatApiHost(apiHost) + 'responses'
}
@ -361,7 +371,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
</Button>
)}
</Space.Compact>
{isOpenAIProvider(provider) && (
{(isOpenAIProvider(provider) || isAnthropicProvider(provider)) && (
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
<SettingHelpText
style={{ marginLeft: 6, marginRight: '1em', whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>

View File

@ -117,7 +117,8 @@ export async function fetchChatCompletion({
const {
params: aiSdkParams,
modelId,
capabilities
capabilities,
webSearchPluginConfig
} = await buildStreamTextParams(messages, assistant, provider, {
mcpTools: mcpTools,
webSearchProviderId: assistant.webSearchProviderId,
@ -132,6 +133,7 @@ export async function fetchChatCompletion({
isPromptToolUse: isPromptToolUse(assistant),
isSupportedToolUse: isSupportedToolUse(assistant),
isImageGenerationEndpoint: isDedicatedImageGenerationModel(assistant.model || getDefaultModel()),
webSearchPluginConfig: webSearchPluginConfig,
enableWebSearch: capabilities.enableWebSearch,
enableGenerateImage: capabilities.enableGenerateImage,
enableUrlContext: capabilities.enableUrlContext,

View File

@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 155,
version: 156,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
migrate
},

View File

@ -2485,6 +2485,21 @@ const migrateConfig = {
logger.error('migrate 155 error', error as Error)
return state
}
},
'156': (state: RootState) => {
try {
state.llm.providers.forEach((provider) => {
if (provider.id === SystemProviderIds.anthropic) {
if (provider.apiHost.endsWith('/')) {
provider.apiHost = provider.apiHost.slice(0, -1)
}
}
})
return state
} catch (error) {
logger.error('migrate 156 error', error as Error)
return state
}
}
}

View File

@ -41,6 +41,8 @@ export interface WebSearchState {
providerConfig: Record<string, any>
}
export type CherryWebSearchConfig = Pick<WebSearchState, 'searchWithTime' | 'maxResults' | 'excludeDomains'>
export const initialState: WebSearchState = {
defaultProvider: 'local-bing',
providers: WEB_SEARCH_PROVIDERS,

View File

@ -26,7 +26,7 @@
import { describe, expect, it } from 'vitest'
import { MatchPatternMap } from '../blacklistMatchPattern'
import { mapRegexToPatterns, MatchPatternMap } from '../blacklistMatchPattern'
function get(map: MatchPatternMap<number>, url: string) {
return map.get(url).sort()
@ -161,3 +161,28 @@ describe('blacklistMatchPattern', () => {
expect(get(map, 'https://b.mozilla.org/path/')).toEqual([0, 1, 2, 6])
})
})
describe('mapRegexToPatterns', () => {
it('extracts domains from regex patterns', () => {
const result = mapRegexToPatterns([
'/example\\.com/',
'/(?:www\\.)?sub\\.example\\.co\\.uk/',
'/api\\.service\\.io/',
'https://baidu.com'
])
expect(result).toEqual(['example.com', 'sub.example.co.uk', 'api.service.io', 'baidu.com'])
})
it('deduplicates domains across multiple patterns', () => {
const result = mapRegexToPatterns(['/example\\.com/', '/(example\\.com|test\\.org)/'])
expect(result).toEqual(['example.com', 'test.org'])
})
it('ignores patterns without domain matches', () => {
const result = mapRegexToPatterns(['', 'plain-domain.com', '/^https?:\\/\\/[^/]+$/'])
expect(result).toEqual(['plain-domain.com'])
})
})

View File

@ -202,6 +202,43 @@ export async function parseSubscribeContent(url: string): Promise<string[]> {
throw error
}
}
export function mapRegexToPatterns(patterns: string[]): string[] {
const patternSet = new Set<string>()
const domainMatcher = /[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g
patterns.forEach((pattern) => {
if (!pattern) {
return
}
// Handle regex patterns (wrapped in /)
if (pattern.startsWith('/') && pattern.endsWith('/')) {
const rawPattern = pattern.slice(1, -1)
const normalizedPattern = rawPattern.replace(/\\\./g, '.').replace(/\\\//g, '/')
const matches = normalizedPattern.match(domainMatcher)
if (matches) {
matches.forEach((match) => {
patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase())
})
}
} else if (pattern.includes('://')) {
// Handle URLs with protocol (e.g., https://baidu.com)
const matches = pattern.match(domainMatcher)
if (matches) {
matches.forEach((match) => {
patternSet.add(match.replace(/http(s)?:\/\//g, '').toLowerCase())
})
}
} else {
patternSet.add(pattern.toLowerCase())
}
})
return Array.from(patternSet)
}
export async function filterResultWithBlacklist(
response: WebSearchProviderResponse,
websearch: WebSearchState

View File

@ -205,6 +205,10 @@ export function isOpenAIProvider(provider: Provider): boolean {
return !['anthropic', 'gemini', 'vertexai'].includes(provider.type)
}
export function isAnthropicProvider(provider: Provider): boolean {
return provider.type === 'anthropic'
}
/**
*
* @param {Model} model

View File

@ -1,9 +1,9 @@
import '@renderer/databases'
import { HeroUIProvider } from '@heroui/react'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { ToastPortal } from '@renderer/components/ToastPortal'
import { getToastUtilities } from '@renderer/components/TopView/toast'
import { HeroUIProvider } from '@renderer/context/HeroUIProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import store, { persistor } from '@renderer/store'
import { useEffect } from 'react'

View File

@ -2,13 +2,13 @@ import '@renderer/assets/styles/index.css'
import '@renderer/assets/styles/tailwind.css'
import '@ant-design/v5-patch-for-react-19'
import { HeroUIProvider } from '@heroui/react'
import KeyvStorage from '@kangfenmao/keyv-storage'
import { loggerService } from '@logger'
import { ToastPortal } from '@renderer/components/ToastPortal'
import { getToastUtilities } from '@renderer/components/TopView/toast'
import AntdProvider from '@renderer/context/AntdProvider'
import { CodeStyleProvider } from '@renderer/context/CodeStyleProvider'
import { HeroUIProvider } from '@renderer/context/HeroUIProvider'
import { ThemeProvider } from '@renderer/context/ThemeProvider'
import storeSyncService from '@renderer/services/StoreSyncService'
import store, { persistor } from '@renderer/store'

388
yarn.lock
View File

@ -386,8 +386,8 @@ __metadata:
linkType: hard
"@anthropic-ai/claude-code@npm:^1.0.113":
version: 1.0.113
resolution: "@anthropic-ai/claude-code@npm:1.0.113"
version: 1.0.118
resolution: "@anthropic-ai/claude-code@npm:1.0.118"
dependencies:
"@img/sharp-darwin-arm64": "npm:^0.33.5"
"@img/sharp-darwin-x64": "npm:^0.33.5"
@ -410,7 +410,7 @@ __metadata:
optional: true
bin:
claude: cli.js
checksum: 10c0/954303e43916a221b0ea9185844621957ac0d42835be654a0f66f09e01a06ef0d121e98bf156c8a51a30d65ca75d2002e5ece561a960cfcc618467a79fb21cba
checksum: 10c0/90e9feac857bfcbdaa3797c70cccfad8588ed45bc9ea6c88664e2f08fa71d069cfb28ce17c20a67ddd841d9a8c7a50e61281687975f7d5ebde9b39f63551cefb
languageName: node
linkType: hard
@ -2338,7 +2338,7 @@ __metadata:
languageName: node
linkType: hard
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.16, @cherrystudio/ai-core@workspace:packages/aiCore":
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @cherrystudio/ai-core@workspace:packages/aiCore":
version: 0.0.0-use.local
resolution: "@cherrystudio/ai-core@workspace:packages/aiCore"
dependencies:
@ -3449,16 +3449,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/aix-ppc64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/aix-ppc64@npm:0.25.8"
"@esbuild/aix-ppc64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/aix-ppc64@npm:0.25.10"
conditions: os=aix & cpu=ppc64
languageName: node
linkType: hard
"@esbuild/aix-ppc64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/aix-ppc64@npm:0.25.9"
"@esbuild/aix-ppc64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/aix-ppc64@npm:0.25.8"
conditions: os=aix & cpu=ppc64
languageName: node
linkType: hard
@ -3470,16 +3470,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-arm64@npm:0.25.8"
"@esbuild/android-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/android-arm64@npm:0.25.10"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@esbuild/android-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/android-arm64@npm:0.25.9"
"@esbuild/android-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-arm64@npm:0.25.8"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
@ -3491,16 +3491,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-arm@npm:0.25.8"
"@esbuild/android-arm@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/android-arm@npm:0.25.10"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
"@esbuild/android-arm@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/android-arm@npm:0.25.9"
"@esbuild/android-arm@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-arm@npm:0.25.8"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
@ -3512,16 +3512,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-x64@npm:0.25.8"
"@esbuild/android-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/android-x64@npm:0.25.10"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
"@esbuild/android-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/android-x64@npm:0.25.9"
"@esbuild/android-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/android-x64@npm:0.25.8"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
@ -3533,16 +3533,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/darwin-arm64@npm:0.25.8"
"@esbuild/darwin-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/darwin-arm64@npm:0.25.10"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@esbuild/darwin-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/darwin-arm64@npm:0.25.9"
"@esbuild/darwin-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/darwin-arm64@npm:0.25.8"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
@ -3554,16 +3554,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/darwin-x64@npm:0.25.8"
"@esbuild/darwin-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/darwin-x64@npm:0.25.10"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@esbuild/darwin-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/darwin-x64@npm:0.25.9"
"@esbuild/darwin-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/darwin-x64@npm:0.25.8"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
@ -3575,16 +3575,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/freebsd-arm64@npm:0.25.8"
"@esbuild/freebsd-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/freebsd-arm64@npm:0.25.10"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
"@esbuild/freebsd-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/freebsd-arm64@npm:0.25.9"
"@esbuild/freebsd-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/freebsd-arm64@npm:0.25.8"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
@ -3596,16 +3596,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/freebsd-x64@npm:0.25.8"
"@esbuild/freebsd-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/freebsd-x64@npm:0.25.10"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/freebsd-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/freebsd-x64@npm:0.25.9"
"@esbuild/freebsd-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/freebsd-x64@npm:0.25.8"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
@ -3617,16 +3617,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-arm64@npm:0.25.8"
"@esbuild/linux-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-arm64@npm:0.25.10"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@esbuild/linux-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-arm64@npm:0.25.9"
"@esbuild/linux-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-arm64@npm:0.25.8"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
@ -3638,16 +3638,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-arm@npm:0.25.8"
"@esbuild/linux-arm@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-arm@npm:0.25.10"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@esbuild/linux-arm@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-arm@npm:0.25.9"
"@esbuild/linux-arm@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-arm@npm:0.25.8"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
@ -3659,16 +3659,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ia32@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-ia32@npm:0.25.8"
"@esbuild/linux-ia32@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-ia32@npm:0.25.10"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
"@esbuild/linux-ia32@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-ia32@npm:0.25.9"
"@esbuild/linux-ia32@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-ia32@npm:0.25.8"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
@ -3680,16 +3680,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-loong64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-loong64@npm:0.25.8"
"@esbuild/linux-loong64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-loong64@npm:0.25.10"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
"@esbuild/linux-loong64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-loong64@npm:0.25.9"
"@esbuild/linux-loong64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-loong64@npm:0.25.8"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
@ -3701,16 +3701,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-mips64el@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-mips64el@npm:0.25.8"
"@esbuild/linux-mips64el@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-mips64el@npm:0.25.10"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
"@esbuild/linux-mips64el@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-mips64el@npm:0.25.9"
"@esbuild/linux-mips64el@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-mips64el@npm:0.25.8"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
@ -3722,16 +3722,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ppc64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-ppc64@npm:0.25.8"
"@esbuild/linux-ppc64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-ppc64@npm:0.25.10"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
"@esbuild/linux-ppc64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-ppc64@npm:0.25.9"
"@esbuild/linux-ppc64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-ppc64@npm:0.25.8"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
@ -3743,16 +3743,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-riscv64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-riscv64@npm:0.25.8"
"@esbuild/linux-riscv64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-riscv64@npm:0.25.10"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
"@esbuild/linux-riscv64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-riscv64@npm:0.25.9"
"@esbuild/linux-riscv64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-riscv64@npm:0.25.8"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
@ -3764,16 +3764,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-s390x@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-s390x@npm:0.25.8"
"@esbuild/linux-s390x@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-s390x@npm:0.25.10"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
"@esbuild/linux-s390x@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-s390x@npm:0.25.9"
"@esbuild/linux-s390x@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-s390x@npm:0.25.8"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
@ -3785,6 +3785,13 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/linux-x64@npm:0.25.10"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/linux-x64@npm:0.25.8"
@ -3792,10 +3799,10 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/linux-x64@npm:0.25.9"
conditions: os=linux & cpu=x64
"@esbuild/netbsd-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/netbsd-arm64@npm:0.25.10"
conditions: os=netbsd & cpu=arm64
languageName: node
linkType: hard
@ -3806,13 +3813,6 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/netbsd-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/netbsd-arm64@npm:0.25.9"
conditions: os=netbsd & cpu=arm64
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/netbsd-x64@npm:0.18.20"
@ -3820,6 +3820,13 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/netbsd-x64@npm:0.25.10"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/netbsd-x64@npm:0.25.8"
@ -3827,10 +3834,10 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/netbsd-x64@npm:0.25.9"
conditions: os=netbsd & cpu=x64
"@esbuild/openbsd-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/openbsd-arm64@npm:0.25.10"
conditions: os=openbsd & cpu=arm64
languageName: node
linkType: hard
@ -3841,13 +3848,6 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openbsd-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/openbsd-arm64@npm:0.25.9"
conditions: os=openbsd & cpu=arm64
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/openbsd-x64@npm:0.18.20"
@ -3855,6 +3855,13 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/openbsd-x64@npm:0.25.10"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/openbsd-x64@npm:0.25.8"
@ -3862,10 +3869,10 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/openbsd-x64@npm:0.25.9"
conditions: os=openbsd & cpu=x64
"@esbuild/openharmony-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/openharmony-arm64@npm:0.25.10"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
@ -3876,13 +3883,6 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openharmony-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/openharmony-arm64@npm:0.25.9"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/sunos-x64@npm:0.18.20"
@ -3890,16 +3890,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/sunos-x64@npm:0.25.8"
"@esbuild/sunos-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/sunos-x64@npm:0.25.10"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/sunos-x64@npm:0.25.9"
"@esbuild/sunos-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/sunos-x64@npm:0.25.8"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
@ -3911,16 +3911,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-arm64@npm:0.25.8"
"@esbuild/win32-arm64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/win32-arm64@npm:0.25.10"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@esbuild/win32-arm64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/win32-arm64@npm:0.25.9"
"@esbuild/win32-arm64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-arm64@npm:0.25.8"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@ -3932,16 +3932,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-ia32@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-ia32@npm:0.25.8"
"@esbuild/win32-ia32@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/win32-ia32@npm:0.25.10"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@esbuild/win32-ia32@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/win32-ia32@npm:0.25.9"
"@esbuild/win32-ia32@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-ia32@npm:0.25.8"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
@ -3953,16 +3953,16 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-x64@npm:0.25.8"
"@esbuild/win32-x64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/win32-x64@npm:0.25.10"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@esbuild/win32-x64@npm:0.25.9":
version: 0.25.9
resolution: "@esbuild/win32-x64@npm:0.25.9"
"@esbuild/win32-x64@npm:0.25.8":
version: 0.25.8
resolution: "@esbuild/win32-x64@npm:0.25.8"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@ -14169,7 +14169,7 @@ __metadata:
"@aws-sdk/client-bedrock-runtime": "npm:^3.840.0"
"@aws-sdk/client-s3": "npm:^3.840.0"
"@biomejs/biome": "npm:2.2.4"
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.16"
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18"
"@cherrystudio/embedjs": "npm:^0.1.31"
"@cherrystudio/embedjs-libsql": "npm:^0.1.31"
"@cherrystudio/embedjs-loader-csv": "npm:^0.1.31"
@ -14304,7 +14304,6 @@ __metadata:
diff: "npm:^8.0.2"
docx: "npm:^9.0.2"
dompurify: "npm:^3.2.6"
dotenv: "npm:^17.2.2"
dotenv-cli: "npm:^7.4.2"
drizzle-kit: "npm:^0.31.4"
drizzle-orm: "npm:^0.44.5"
@ -14417,7 +14416,7 @@ __metadata:
turndown: "npm:7.2.0"
turndown-plugin-gfm: "npm:^1.0.2"
tw-animate-css: "npm:^1.3.8"
typescript: "npm:^5.6.2"
typescript: "npm:~5.8.2"
undici: "npm:6.21.2"
unified: "npm:^11.0.5"
uuid: "npm:^13.0.0"
@ -17656,13 +17655,6 @@ __metadata:
languageName: node
linkType: hard
"dotenv@npm:^17.2.2":
version: 17.2.2
resolution: "dotenv@npm:17.2.2"
checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b
languageName: node
linkType: hard
"drizzle-kit@npm:^0.31.4":
version: 0.31.4
resolution: "drizzle-kit@npm:0.31.4"
@ -18193,35 +18185,35 @@ __metadata:
linkType: hard
"esbuild@npm:^0.25.4":
version: 0.25.9
resolution: "esbuild@npm:0.25.9"
version: 0.25.10
resolution: "esbuild@npm:0.25.10"
dependencies:
"@esbuild/aix-ppc64": "npm:0.25.9"
"@esbuild/android-arm": "npm:0.25.9"
"@esbuild/android-arm64": "npm:0.25.9"
"@esbuild/android-x64": "npm:0.25.9"
"@esbuild/darwin-arm64": "npm:0.25.9"
"@esbuild/darwin-x64": "npm:0.25.9"
"@esbuild/freebsd-arm64": "npm:0.25.9"
"@esbuild/freebsd-x64": "npm:0.25.9"
"@esbuild/linux-arm": "npm:0.25.9"
"@esbuild/linux-arm64": "npm:0.25.9"
"@esbuild/linux-ia32": "npm:0.25.9"
"@esbuild/linux-loong64": "npm:0.25.9"
"@esbuild/linux-mips64el": "npm:0.25.9"
"@esbuild/linux-ppc64": "npm:0.25.9"
"@esbuild/linux-riscv64": "npm:0.25.9"
"@esbuild/linux-s390x": "npm:0.25.9"
"@esbuild/linux-x64": "npm:0.25.9"
"@esbuild/netbsd-arm64": "npm:0.25.9"
"@esbuild/netbsd-x64": "npm:0.25.9"
"@esbuild/openbsd-arm64": "npm:0.25.9"
"@esbuild/openbsd-x64": "npm:0.25.9"
"@esbuild/openharmony-arm64": "npm:0.25.9"
"@esbuild/sunos-x64": "npm:0.25.9"
"@esbuild/win32-arm64": "npm:0.25.9"
"@esbuild/win32-ia32": "npm:0.25.9"
"@esbuild/win32-x64": "npm:0.25.9"
"@esbuild/aix-ppc64": "npm:0.25.10"
"@esbuild/android-arm": "npm:0.25.10"
"@esbuild/android-arm64": "npm:0.25.10"
"@esbuild/android-x64": "npm:0.25.10"
"@esbuild/darwin-arm64": "npm:0.25.10"
"@esbuild/darwin-x64": "npm:0.25.10"
"@esbuild/freebsd-arm64": "npm:0.25.10"
"@esbuild/freebsd-x64": "npm:0.25.10"
"@esbuild/linux-arm": "npm:0.25.10"
"@esbuild/linux-arm64": "npm:0.25.10"
"@esbuild/linux-ia32": "npm:0.25.10"
"@esbuild/linux-loong64": "npm:0.25.10"
"@esbuild/linux-mips64el": "npm:0.25.10"
"@esbuild/linux-ppc64": "npm:0.25.10"
"@esbuild/linux-riscv64": "npm:0.25.10"
"@esbuild/linux-s390x": "npm:0.25.10"
"@esbuild/linux-x64": "npm:0.25.10"
"@esbuild/netbsd-arm64": "npm:0.25.10"
"@esbuild/netbsd-x64": "npm:0.25.10"
"@esbuild/openbsd-arm64": "npm:0.25.10"
"@esbuild/openbsd-x64": "npm:0.25.10"
"@esbuild/openharmony-arm64": "npm:0.25.10"
"@esbuild/sunos-x64": "npm:0.25.10"
"@esbuild/win32-arm64": "npm:0.25.10"
"@esbuild/win32-ia32": "npm:0.25.10"
"@esbuild/win32-x64": "npm:0.25.10"
dependenciesMeta:
"@esbuild/aix-ppc64":
optional: true
@ -18277,7 +18269,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
checksum: 10c0/aaa1284c75fcf45c82f9a1a117fe8dc5c45628e3386bda7d64916ae27730910b51c5aec7dd45a6ba19256be30ba2935e64a8f011a3f0539833071e06bf76d5b3
checksum: 10c0/8ee5fdd43ed0d4092ce7f41577c63147f54049d5617763f0549c638bbe939e8adaa8f1a2728adb63417eb11df51956b7b0d8eb88ee08c27ad1d42960256158fa
languageName: node
linkType: hard
@ -29191,7 +29183,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^5.4.3, typescript@npm:^5.6.2":
"typescript@npm:^5.4.3, typescript@npm:~5.8.2":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
@ -29211,7 +29203,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.6.2#optional!builtin<compat/typescript>":
"typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A~5.8.2#optional!builtin<compat/typescript>":
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
bin: