Compare commits
18 Commits
dev/knife
...
dev/agents
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19522f496b | ||
|
|
9a5ea191f8 | ||
|
|
6ab608943e | ||
|
|
50c8f6994f | ||
|
|
915495553a | ||
|
|
5ac4e8955d | ||
|
|
6b7d0926c4 | ||
|
|
01cd5d03a3 | ||
|
|
0c14a5c053 | ||
|
|
b72b05eb5c | ||
|
|
21ab7c61ce | ||
|
|
9f82aa2797 | ||
|
|
ac0263b226 | ||
|
|
6a4c1f7591 | ||
|
|
95b2dadd40 | ||
|
|
d6b11e1b60 | ||
|
|
d2969e4332 | ||
|
|
19d0f3b8f5 |
@@ -1,36 +0,0 @@
|
||||
---
|
||||
name: code-implementation-expert
|
||||
description: Use this agent when you need to implement code solutions, write functions, create classes, or develop software components. This agent excels at translating requirements into working code across multiple programming languages. Examples: When a user asks 'Please write a function that checks if a number is prime' - use the code-implementation-expert agent to generate the implementation. When a user requests 'Create a React component for a todo list' - use this agent to build the component code.
|
||||
model: sonnet
|
||||
color: blue
|
||||
---
|
||||
|
||||
<CCR-SUBAGENT-MODEL>deepseek,deepseek-reasoner</CCR-SUBAGENT-MODEL>
|
||||
You are an elite software engineering expert with deep knowledge across multiple programming languages, frameworks, and best practices. Your primary role is to translate requirements into high-quality, functional code implementations.
|
||||
|
||||
When implementing code:
|
||||
1. Analyze requirements carefully to understand functionality, constraints, and edge cases
|
||||
2. Choose appropriate data structures, algorithms, and design patterns
|
||||
3. Write clean, readable, and maintainable code following language-specific conventions
|
||||
4. Include proper error handling, input validation, and documentation
|
||||
5. Optimize for performance and scalability when relevant
|
||||
6. Consider security implications and best practices
|
||||
7. Write modular code that's easy to test and extend
|
||||
|
||||
You will:
|
||||
- Implement complete, working solutions unless otherwise specified
|
||||
- Use appropriate naming conventions for variables, functions, and classes
|
||||
- Include necessary imports/dependencies
|
||||
- Add comments for complex logic or non-obvious implementation decisions
|
||||
- Follow established patterns in the codebase when visible
|
||||
- Write defensive code that handles edge cases gracefully
|
||||
- Ensure code compiles/runs without syntax errors
|
||||
|
||||
When responding:
|
||||
- Provide the complete implementation in appropriate code blocks
|
||||
- Explain key design decisions briefly if not obvious
|
||||
- Mention any assumptions made about requirements
|
||||
- Highlight important implementation details
|
||||
- Note any limitations or areas for improvement
|
||||
|
||||
If requirements are unclear, ask specific questions to clarify before implementing. If you encounter domain-specific requirements outside your expertise, acknowledge limitations and suggest alternatives.
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,4 +2,6 @@ node_modules
|
||||
.env
|
||||
log.txt
|
||||
.idea
|
||||
dist
|
||||
dist
|
||||
.DS_Store
|
||||
.vscode
|
||||
26
README.md
26
README.md
@@ -42,7 +42,7 @@ The `config.json` file has several key sections:
|
||||
|
||||
- **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`.
|
||||
- **`LOG`** (optional): You can enable logging by setting it to `true`. When set to `false`, no log files will be created. Default is `true`.
|
||||
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"info"`.
|
||||
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"debug"`.
|
||||
- **Logging Systems**: The Claude Code Router uses two separate logging systems:
|
||||
- **Server-level logs**: HTTP requests, API calls, and server events are logged using pino in the `~/.claude-code-router/logs/` directory with filenames like `ccr-*.log`
|
||||
- **Application-level logs**: Routing decisions and business logic events are logged in `~/.claude-code-router/claude-code-router.log`
|
||||
@@ -208,7 +208,7 @@ ccr code
|
||||
> ccr restart
|
||||
> ```
|
||||
|
||||
### 4. UI Mode (Beta)
|
||||
### 4. UI Mode
|
||||
|
||||
For a more intuitive experience, you can use the UI mode to manage your configuration:
|
||||
|
||||
@@ -220,9 +220,6 @@ This will open a web-based interface where you can easily view and edit your `co
|
||||
|
||||

|
||||
|
||||
> **Note**: The UI mode is currently in beta. 100% vibe coding: including project initialization, I just created a folder and a project.md document, and all code was generated by ccr + qwen3-coder + gemini(webSearch).
|
||||
If you encounter any issues, please submit an issue on GitHub.
|
||||
|
||||
#### Providers
|
||||
|
||||
The `Providers` array is where you define the different model providers you want to use. Each provider object requires:
|
||||
@@ -403,6 +400,13 @@ For routing within subagents, you must specify a particular provider and model b
|
||||
Please help me analyze this code snippet for potential optimizations...
|
||||
```
|
||||
|
||||
## Status Line (Beta)
|
||||
To better monitor the status of claude-code-router at runtime, version v1.0.40 includes a built-in statusline tool, which you can enable in the UI.
|
||||

|
||||
|
||||
The effect is as follows:
|
||||

|
||||
|
||||
## 🤖 GitHub Actions
|
||||
|
||||
Integrate Claude Code Router into your CI/CD pipeline. After setting up [Claude Code Actions](https://docs.anthropic.com/en/docs/claude-code/github-actions), modify your `.github/workflows/claude.yaml` to use the router:
|
||||
@@ -491,6 +495,7 @@ A huge thank you to all our sponsors for their generous support!
|
||||
|
||||
|
||||
- [AIHubmix](https://aihubmix.com/)
|
||||
- [BurnCloud](https://ai.burncloud.com)
|
||||
- @Simon Leischnig
|
||||
- [@duanshuaimin](https://github.com/duanshuaimin)
|
||||
- [@vrgitadmin](https://github.com/vrgitadmin)
|
||||
@@ -542,6 +547,15 @@ A huge thank you to all our sponsors for their generous support!
|
||||
- @水\*丫
|
||||
- @二吉吉
|
||||
- @a\*g
|
||||
- @*林
|
||||
- @\*林
|
||||
- @\*咸
|
||||
- @\*明
|
||||
- @S\*y
|
||||
- @f\*o
|
||||
- @\*智
|
||||
- @F\*t
|
||||
- @r\*c
|
||||
- [@qierkang](http://github.com/qierkang)
|
||||
- @\*军
|
||||
|
||||
(If your name is masked, please contact me via my homepage email to update it with your GitHub username.)
|
||||
|
||||
24
README_zh.md
24
README_zh.md
@@ -38,7 +38,7 @@ npm install -g @musistudio/claude-code-router
|
||||
`config.json` 文件有几个关键部分:
|
||||
- **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。
|
||||
- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。当设置为 `false` 时,将不会创建日志文件。默认值为 `true`。
|
||||
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"info"`。
|
||||
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"debug"`。
|
||||
- **日志系统**: Claude Code Router 使用两个独立的日志系统:
|
||||
- **服务器级别日志**: HTTP 请求、API 调用和服务器事件使用 pino 记录在 `~/.claude-code-router/logs/` 目录中,文件名类似于 `ccr-*.log`
|
||||
- **应用程序级别日志**: 路由决策和业务逻辑事件记录在 `~/.claude-code-router/claude-code-router.log` 文件中
|
||||
@@ -182,7 +182,7 @@ ccr code
|
||||
> ccr restart
|
||||
> ```
|
||||
|
||||
### 4. UI 模式 (Beta)
|
||||
### 4. UI 模式
|
||||
|
||||
为了获得更直观的体验,您可以使用 UI 模式来管理您的配置:
|
||||
|
||||
@@ -194,8 +194,6 @@ ccr ui
|
||||
|
||||

|
||||
|
||||
> **注意**: UI 模式目前处于测试阶段。这是一个 100% vibe coding的项目,包括项目的初始化,我只是新建了一个文件夹和一个project.md文档。所有代码均由 ccr + qwen3-coder + gemini(webSearch) 实现。如有问题请提交 issue。
|
||||
|
||||
#### Providers
|
||||
|
||||
`Providers` 数组是您定义要使用的不同模型提供商的地方。每个提供商对象都需要:
|
||||
@@ -375,6 +373,12 @@ module.exports = async function router(req, config) {
|
||||
请帮我分析这段代码是否存在潜在的优化空间...
|
||||
```
|
||||
|
||||
## Status Line (Beta)
|
||||
为了在运行时更好的查看claude-code-router的状态,claude-code-router在v1.0.40内置了一个statusline工具,你可以在UI中启用它,
|
||||

|
||||
|
||||
效果如下:
|
||||

|
||||
|
||||
## 🤖 GitHub Actions
|
||||
|
||||
@@ -461,6 +465,7 @@ jobs:
|
||||
非常感谢所有赞助商的慷慨支持!
|
||||
|
||||
- [AIHubmix](https://aihubmix.com/)
|
||||
- [BurnCloud](https://ai.burncloud.com)
|
||||
- @Simon Leischnig
|
||||
- [@duanshuaimin](https://github.com/duanshuaimin)
|
||||
- [@vrgitadmin](https://github.com/vrgitadmin)
|
||||
@@ -511,7 +516,16 @@ jobs:
|
||||
- @水\*丫
|
||||
- @二吉吉
|
||||
- @a\*g
|
||||
- @*林
|
||||
- @\*林
|
||||
- @\*咸
|
||||
- @\*明
|
||||
- @S\*y
|
||||
- @f\*o
|
||||
- @\*智
|
||||
- @F\*t
|
||||
- @r\*c
|
||||
- [@qierkang](http://github.com/qierkang)
|
||||
- @\*军
|
||||
|
||||
(如果您的名字被屏蔽,请通过我的主页电子邮件与我联系,以便使用您的 GitHub 用户名进行更新。)
|
||||
|
||||
|
||||
BIN
blog/images/statusline-config.png
Normal file
BIN
blog/images/statusline-config.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
BIN
blog/images/statusline.png
Normal file
BIN
blog/images/statusline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 237 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@musistudio/claude-code-router",
|
||||
"version": "1.0.38",
|
||||
"version": "1.0.43",
|
||||
"description": "Use Claude Code without an Anthropics account and route it to another LLM provider",
|
||||
"bin": {
|
||||
"ccr": "./dist/cli.js"
|
||||
@@ -20,7 +20,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/static": "^8.2.0",
|
||||
"@musistudio/llms": "^1.0.24",
|
||||
"@musistudio/llms": "^1.0.28",
|
||||
"dotenv": "^16.4.7",
|
||||
"json5": "^2.2.3",
|
||||
"openurl": "^1.1.1",
|
||||
|
||||
351
pnpm-lock.yaml
generated
351
pnpm-lock.yaml
generated
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
'@musistudio/llms':
|
||||
specifier: ^1.0.24
|
||||
version: 1.0.24(ws@8.18.3)(zod@3.25.67)
|
||||
specifier: ^1.0.28
|
||||
version: 1.0.28(ws@8.18.3)
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.6.1
|
||||
@@ -28,26 +28,26 @@ importers:
|
||||
version: 0.0.2
|
||||
tiktoken:
|
||||
specifier: ^1.0.21
|
||||
version: 1.0.21
|
||||
version: 1.0.22
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^24.0.15
|
||||
version: 24.0.15
|
||||
version: 24.3.0
|
||||
esbuild:
|
||||
specifier: ^0.25.1
|
||||
version: 0.25.5
|
||||
version: 0.25.9
|
||||
fastify:
|
||||
specifier: ^5.4.0
|
||||
version: 5.4.0
|
||||
version: 5.5.0
|
||||
shx:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
typescript:
|
||||
specifier: ^5.8.2
|
||||
version: 5.8.3
|
||||
version: 5.9.2
|
||||
|
||||
packages:
|
||||
|
||||
@@ -55,152 +55,158 @@ packages:
|
||||
resolution: {integrity: sha512-xyoCtHJnt/qg5GG6IgK+UJEndz8h8ljzt/caKXmq3LfBF81nC/BW6E4x2rOWCZcvsLyVW+e8U5mtIr6UCE/kJw==}
|
||||
hasBin: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
|
||||
'@esbuild/android-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.25.5':
|
||||
resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
|
||||
'@esbuild/android-arm@0.25.9':
|
||||
resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.25.5':
|
||||
resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
|
||||
'@esbuild/android-x64@0.25.9':
|
||||
resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
|
||||
'@esbuild/darwin-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.25.5':
|
||||
resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
|
||||
'@esbuild/darwin-x64@0.25.9':
|
||||
resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
|
||||
'@esbuild/freebsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
|
||||
'@esbuild/freebsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
|
||||
'@esbuild/linux-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.25.5':
|
||||
resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
|
||||
'@esbuild/linux-arm@0.25.9':
|
||||
resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.25.5':
|
||||
resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
|
||||
'@esbuild/linux-ia32@0.25.9':
|
||||
resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.25.5':
|
||||
resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
|
||||
'@esbuild/linux-loong64@0.25.9':
|
||||
resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.5':
|
||||
resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
|
||||
'@esbuild/linux-mips64el@0.25.9':
|
||||
resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.5':
|
||||
resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
|
||||
'@esbuild/linux-ppc64@0.25.9':
|
||||
resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.5':
|
||||
resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
|
||||
'@esbuild/linux-riscv64@0.25.9':
|
||||
resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.25.5':
|
||||
resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
|
||||
'@esbuild/linux-s390x@0.25.9':
|
||||
resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.25.5':
|
||||
resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
|
||||
'@esbuild/linux-x64@0.25.9':
|
||||
resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
|
||||
'@esbuild/netbsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
|
||||
'@esbuild/netbsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
|
||||
'@esbuild/openbsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
|
||||
'@esbuild/openbsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.5':
|
||||
resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
|
||||
'@esbuild/openharmony-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.9':
|
||||
resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
|
||||
'@esbuild/win32-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.25.5':
|
||||
resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
|
||||
'@esbuild/win32-ia32@0.25.9':
|
||||
resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.25.5':
|
||||
resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
|
||||
'@esbuild/win32-x64@0.25.9':
|
||||
resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -211,8 +217,8 @@ packages:
|
||||
'@fastify/ajv-compiler@4.0.2':
|
||||
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
|
||||
|
||||
'@fastify/cors@11.0.1':
|
||||
resolution: {integrity: sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==}
|
||||
'@fastify/cors@11.1.0':
|
||||
resolution: {integrity: sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA==}
|
||||
|
||||
'@fastify/error@4.2.0':
|
||||
resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
|
||||
@@ -235,8 +241,8 @@ packages:
|
||||
'@fastify/static@8.2.0':
|
||||
resolution: {integrity: sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==}
|
||||
|
||||
'@google/genai@1.8.0':
|
||||
resolution: {integrity: sha512-n3KiMFesQCy2R9iSdBIuJ0JWYQ1HZBJJkmt4PPZMGZKvlgHhBAGw1kUMyX+vsAIzprN3lK45DI755lm70wPOOg==}
|
||||
'@google/genai@1.14.0':
|
||||
resolution: {integrity: sha512-jirYprAAJU1svjwSDVCzyVq+FrJpJd5CSxR/g2Ga/gZ0ZYZpcWjMS75KJl9y71K1mDN+tcx6s21CzCbB2R840g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@modelcontextprotocol/sdk': ^1.11.0
|
||||
@@ -260,8 +266,8 @@ packages:
|
||||
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@musistudio/llms@1.0.24':
|
||||
resolution: {integrity: sha512-Hz6ZT92/ZM/eR5kTdCBHD6zoEMOvT5u6g/vfCir5Hwvl4QGHk3g30EmX1pZAXJf83kLnB/lSEq/HQimFIXHIhQ==}
|
||||
'@musistudio/llms@1.0.28':
|
||||
resolution: {integrity: sha512-rHvcJTtrFsRC7ayxz7ZXVoC7lZUwLtAHubdouUj+LYkv35Hr8S6K3lpOMXKYyqcKCtMvxbpjvM9MiwjCaleGEA==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
@@ -275,14 +281,14 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@types/node@24.0.15':
|
||||
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
||||
'@types/node@24.3.0':
|
||||
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
|
||||
|
||||
abstract-logging@2.0.1:
|
||||
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
||||
|
||||
agent-base@7.1.3:
|
||||
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ajv-formats@3.0.1:
|
||||
@@ -322,8 +328,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
bignumber.js@9.3.0:
|
||||
resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==}
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@@ -395,8 +401,8 @@ packages:
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
esbuild@0.25.5:
|
||||
resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
|
||||
esbuild@0.25.9:
|
||||
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -436,8 +442,8 @@ packages:
|
||||
fastify-plugin@5.0.1:
|
||||
resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==}
|
||||
|
||||
fastify@5.4.0:
|
||||
resolution: {integrity: sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==}
|
||||
fastify@5.5.0:
|
||||
resolution: {integrity: sha512-ZWSWlzj3K/DcULCnCjEiC2zn2FBPdlZsSA/pnPa/dbUfLvxkD/Nqmb0XXMXLrWkeM4uQPUvjdJpwtXmTfriXqw==}
|
||||
|
||||
fastq@1.19.1:
|
||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||
@@ -494,8 +500,8 @@ packages:
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
google-auth-library@10.2.0:
|
||||
resolution: {integrity: sha512-gy/0hRx8+Ye0HlUm3GrfpR4lbmJQ6bJ7F44DmN7GtMxxzWSojLzx0Bhv/hj7Wlj7a2On0FcT8jrz8Y1c1nxCyg==}
|
||||
google-auth-library@10.2.1:
|
||||
resolution: {integrity: sha512-HMxFl2NfeHYnaL1HoRIN1XgorKS+6CDaM+z9LSSN+i/nKDDL4KFFEWogMXu7jV4HZQy2MsxpY+wA5XIf3w410A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-auth-library@9.15.1:
|
||||
@@ -666,8 +672,8 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
openai@5.8.2:
|
||||
resolution: {integrity: sha512-8C+nzoHYgyYOXhHGN6r0fcb4SznuEn1R7YZMvlqDbnCuE0FM2mm3T1HiYW6WIcMS/F1Of2up/cSPjLPaWt0X9Q==}
|
||||
openai@5.12.2:
|
||||
resolution: {integrity: sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
@@ -716,8 +722,8 @@ packages:
|
||||
pino-std-serializers@7.0.0:
|
||||
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
|
||||
|
||||
pino@9.7.0:
|
||||
resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==}
|
||||
pino@9.9.0:
|
||||
resolution: {integrity: sha512-zxsRIQG9HzG+jEljmvmZupOMDUQ0Jpj0yAgE28jQvvrdYTlEaiGwelJpdndMl/MBuRr70heIj83QyqJUWaU8mQ==}
|
||||
hasBin: true
|
||||
|
||||
process-warning@4.0.1:
|
||||
@@ -869,8 +875,8 @@ packages:
|
||||
thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
|
||||
tiktoken@1.0.21:
|
||||
resolution: {integrity: sha512-/kqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/bH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==}
|
||||
tiktoken@1.0.22:
|
||||
resolution: {integrity: sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
@@ -887,16 +893,16 @@ packages:
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.8.0:
|
||||
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
|
||||
undici-types@7.10.0:
|
||||
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
|
||||
|
||||
undici@7.11.0:
|
||||
resolution: {integrity: sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==}
|
||||
undici@7.13.0:
|
||||
resolution: {integrity: sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
uuid@11.1.0:
|
||||
@@ -949,91 +955,86 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
zod-to-json-schema@3.24.6:
|
||||
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
|
||||
zod@3.25.67:
|
||||
resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@anthropic-ai/sdk@0.54.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.25.5':
|
||||
'@esbuild/android-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.25.5':
|
||||
'@esbuild/android-arm@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.25.5':
|
||||
'@esbuild/android-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.5':
|
||||
'@esbuild/darwin-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.25.5':
|
||||
'@esbuild/darwin-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.5':
|
||||
'@esbuild/freebsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.5':
|
||||
'@esbuild/freebsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.25.5':
|
||||
'@esbuild/linux-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.25.5':
|
||||
'@esbuild/linux-arm@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.25.5':
|
||||
'@esbuild/linux-ia32@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.25.5':
|
||||
'@esbuild/linux-loong64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.5':
|
||||
'@esbuild/linux-mips64el@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.5':
|
||||
'@esbuild/linux-ppc64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.5':
|
||||
'@esbuild/linux-riscv64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.25.5':
|
||||
'@esbuild/linux-s390x@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.25.5':
|
||||
'@esbuild/linux-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.5':
|
||||
'@esbuild/netbsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.5':
|
||||
'@esbuild/netbsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.5':
|
||||
'@esbuild/openbsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.5':
|
||||
'@esbuild/openbsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.25.5':
|
||||
'@esbuild/openharmony-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.25.5':
|
||||
'@esbuild/sunos-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.25.5':
|
||||
'@esbuild/win32-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.5':
|
||||
'@esbuild/win32-ia32@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@fastify/accept-negotiator@2.0.1': {}
|
||||
@@ -1044,7 +1045,7 @@ snapshots:
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
fast-uri: 3.0.6
|
||||
|
||||
'@fastify/cors@11.0.1':
|
||||
'@fastify/cors@11.1.0':
|
||||
dependencies:
|
||||
fastify-plugin: 5.0.1
|
||||
toad-cache: 3.7.0
|
||||
@@ -1083,12 +1084,10 @@ snapshots:
|
||||
fastq: 1.19.1
|
||||
glob: 11.0.3
|
||||
|
||||
'@google/genai@1.8.0':
|
||||
'@google/genai@1.14.0':
|
||||
dependencies:
|
||||
google-auth-library: 9.15.1
|
||||
ws: 8.18.3
|
||||
zod: 3.25.67
|
||||
zod-to-json-schema: 3.24.6(zod@3.25.67)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
@@ -1112,18 +1111,18 @@ snapshots:
|
||||
|
||||
'@lukeed/ms@2.0.2': {}
|
||||
|
||||
'@musistudio/llms@1.0.24(ws@8.18.3)(zod@3.25.67)':
|
||||
'@musistudio/llms@1.0.28(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.54.0
|
||||
'@fastify/cors': 11.0.1
|
||||
'@google/genai': 1.8.0
|
||||
'@fastify/cors': 11.1.0
|
||||
'@google/genai': 1.14.0
|
||||
dotenv: 16.6.1
|
||||
fastify: 5.4.0
|
||||
google-auth-library: 10.2.0
|
||||
fastify: 5.5.0
|
||||
google-auth-library: 10.2.1
|
||||
json5: 2.2.3
|
||||
jsonrepair: 3.13.0
|
||||
openai: 5.8.2(ws@8.18.3)(zod@3.25.67)
|
||||
undici: 7.11.0
|
||||
openai: 5.12.2(ws@8.18.3)
|
||||
undici: 7.13.0
|
||||
uuid: 11.1.0
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
@@ -1146,13 +1145,13 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.19.1
|
||||
|
||||
'@types/node@24.0.15':
|
||||
'@types/node@24.3.0':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
undici-types: 7.10.0
|
||||
|
||||
abstract-logging@2.0.1: {}
|
||||
|
||||
agent-base@7.1.3: {}
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
@@ -1184,7 +1183,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
bignumber.js@9.3.0: {}
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
@@ -1244,33 +1243,34 @@ snapshots:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
esbuild@0.25.5:
|
||||
esbuild@0.25.9:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.5
|
||||
'@esbuild/android-arm': 0.25.5
|
||||
'@esbuild/android-arm64': 0.25.5
|
||||
'@esbuild/android-x64': 0.25.5
|
||||
'@esbuild/darwin-arm64': 0.25.5
|
||||
'@esbuild/darwin-x64': 0.25.5
|
||||
'@esbuild/freebsd-arm64': 0.25.5
|
||||
'@esbuild/freebsd-x64': 0.25.5
|
||||
'@esbuild/linux-arm': 0.25.5
|
||||
'@esbuild/linux-arm64': 0.25.5
|
||||
'@esbuild/linux-ia32': 0.25.5
|
||||
'@esbuild/linux-loong64': 0.25.5
|
||||
'@esbuild/linux-mips64el': 0.25.5
|
||||
'@esbuild/linux-ppc64': 0.25.5
|
||||
'@esbuild/linux-riscv64': 0.25.5
|
||||
'@esbuild/linux-s390x': 0.25.5
|
||||
'@esbuild/linux-x64': 0.25.5
|
||||
'@esbuild/netbsd-arm64': 0.25.5
|
||||
'@esbuild/netbsd-x64': 0.25.5
|
||||
'@esbuild/openbsd-arm64': 0.25.5
|
||||
'@esbuild/openbsd-x64': 0.25.5
|
||||
'@esbuild/sunos-x64': 0.25.5
|
||||
'@esbuild/win32-arm64': 0.25.5
|
||||
'@esbuild/win32-ia32': 0.25.5
|
||||
'@esbuild/win32-x64': 0.25.5
|
||||
'@esbuild/aix-ppc64': 0.25.9
|
||||
'@esbuild/android-arm': 0.25.9
|
||||
'@esbuild/android-arm64': 0.25.9
|
||||
'@esbuild/android-x64': 0.25.9
|
||||
'@esbuild/darwin-arm64': 0.25.9
|
||||
'@esbuild/darwin-x64': 0.25.9
|
||||
'@esbuild/freebsd-arm64': 0.25.9
|
||||
'@esbuild/freebsd-x64': 0.25.9
|
||||
'@esbuild/linux-arm': 0.25.9
|
||||
'@esbuild/linux-arm64': 0.25.9
|
||||
'@esbuild/linux-ia32': 0.25.9
|
||||
'@esbuild/linux-loong64': 0.25.9
|
||||
'@esbuild/linux-mips64el': 0.25.9
|
||||
'@esbuild/linux-ppc64': 0.25.9
|
||||
'@esbuild/linux-riscv64': 0.25.9
|
||||
'@esbuild/linux-s390x': 0.25.9
|
||||
'@esbuild/linux-x64': 0.25.9
|
||||
'@esbuild/netbsd-arm64': 0.25.9
|
||||
'@esbuild/netbsd-x64': 0.25.9
|
||||
'@esbuild/openbsd-arm64': 0.25.9
|
||||
'@esbuild/openbsd-x64': 0.25.9
|
||||
'@esbuild/openharmony-arm64': 0.25.9
|
||||
'@esbuild/sunos-x64': 0.25.9
|
||||
'@esbuild/win32-arm64': 0.25.9
|
||||
'@esbuild/win32-ia32': 0.25.9
|
||||
'@esbuild/win32-x64': 0.25.9
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
@@ -1317,7 +1317,7 @@ snapshots:
|
||||
|
||||
fastify-plugin@5.0.1: {}
|
||||
|
||||
fastify@5.4.0:
|
||||
fastify@5.5.0:
|
||||
dependencies:
|
||||
'@fastify/ajv-compiler': 4.0.2
|
||||
'@fastify/error': 4.2.0
|
||||
@@ -1328,7 +1328,7 @@ snapshots:
|
||||
fast-json-stringify: 6.0.1
|
||||
find-my-way: 9.3.0
|
||||
light-my-request: 6.6.0
|
||||
pino: 9.7.0
|
||||
pino: 9.9.0
|
||||
process-warning: 5.0.0
|
||||
rfdc: 1.4.1
|
||||
secure-json-parse: 4.0.0
|
||||
@@ -1418,7 +1418,7 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 2.0.0
|
||||
|
||||
google-auth-library@10.2.0:
|
||||
google-auth-library@10.2.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
@@ -1475,7 +1475,7 @@ snapshots:
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -1512,7 +1512,7 @@ snapshots:
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.0
|
||||
bignumber.js: 9.3.1
|
||||
|
||||
json-schema-ref-resolver@2.0.1:
|
||||
dependencies:
|
||||
@@ -1586,10 +1586,9 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
openai@5.8.2(ws@8.18.3)(zod@3.25.67):
|
||||
openai@5.12.2(ws@8.18.3):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
zod: 3.25.67
|
||||
|
||||
openurl@1.1.1: {}
|
||||
|
||||
@@ -1620,7 +1619,7 @@ snapshots:
|
||||
|
||||
pino-std-serializers@7.0.0: {}
|
||||
|
||||
pino@9.7.0:
|
||||
pino@9.9.0:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
fast-redact: 3.5.0
|
||||
@@ -1755,7 +1754,7 @@ snapshots:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
||||
tiktoken@1.0.21: {}
|
||||
tiktoken@1.0.22: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
@@ -1767,11 +1766,11 @@ snapshots:
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
typescript@5.8.3: {}
|
||||
typescript@5.9.2: {}
|
||||
|
||||
undici-types@7.8.0: {}
|
||||
undici-types@7.10.0: {}
|
||||
|
||||
undici@7.11.0: {}
|
||||
undici@7.13.0: {}
|
||||
|
||||
uuid@11.1.0: {}
|
||||
|
||||
@@ -1809,9 +1808,3 @@ snapshots:
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.18.3: {}
|
||||
|
||||
zod-to-json-schema@3.24.6(zod@3.25.67):
|
||||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
zod@3.25.67: {}
|
||||
|
||||
212
src/agents/image.agent.ts
Normal file
212
src/agents/image.agent.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import {IAgent, ITool} from "./type";
|
||||
import { createHash } from 'crypto';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
|
||||
interface ImageCacheEntry {
|
||||
source: any;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
class ImageCache {
|
||||
private cache: LRUCache<string, ImageCacheEntry>;
|
||||
|
||||
constructor(maxSize = 100) {
|
||||
this.cache = new LRUCache({
|
||||
max: maxSize,
|
||||
ttl: 24 * 60 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
calculateHash(base64Image: string): string {
|
||||
const hash = createHash('sha256');
|
||||
hash.update(base64Image);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
storeImage(id: string, source: any): void {
|
||||
if (this.hasImage(id)) return;
|
||||
const base64Image = source.data
|
||||
this.cache.set(id, {
|
||||
source,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
getImage(id: string): any {
|
||||
const entry = this.cache.get(id);
|
||||
return entry ? entry.source : null;
|
||||
}
|
||||
|
||||
hasImage(hash: string): boolean {
|
||||
return this.cache.has(hash);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.cache.size;
|
||||
}
|
||||
}
|
||||
|
||||
const imageCache = new ImageCache();
|
||||
|
||||
export class ImageAgent implements IAgent {
|
||||
name = "image";
|
||||
tools: Map<string, ITool>;
|
||||
|
||||
constructor() {
|
||||
this.tools = new Map<string, ITool>();
|
||||
this.appendTools()
|
||||
}
|
||||
|
||||
shouldHandle(req: any, config: any): boolean {
|
||||
if (!config.Router.image) return false;
|
||||
const lastMessage = req.body.messages[req.body.messages.length - 1]
|
||||
if (lastMessage.role === 'user' && Array.isArray(lastMessage.content) &&lastMessage.content.find((item: any) => item.type === 'image')) {
|
||||
if (config.Router.image) {
|
||||
req.body.model = config.Router.image
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return req.body.messages.some((msg: any) => msg.role === 'user' && Array.isArray(msg.content) && msg.content.some((item: any) => item.type === 'image'))
|
||||
}
|
||||
|
||||
appendTools() {
|
||||
this.tools.set('analyzeImage', {
|
||||
name: "analyzeImage",
|
||||
description: "Analyse image or images by ID and extract information such as OCR text, objects, layout, colors, or safety signals.",
|
||||
input_schema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imageId": {
|
||||
"type": "array",
|
||||
"description": "an array of IDs to analyse",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "Details of task to perform on the image.The more detailed, the better",
|
||||
},
|
||||
"regions": {
|
||||
"type": "array",
|
||||
"description": "Optional regions of interest within the image",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Optional label for the region"},
|
||||
"x": {"type": "number", "description": "X coordinate"},
|
||||
"y": {"type": "number", "description": "Y coordinate"},
|
||||
"w": {"type": "number", "description": "Width of the region"},
|
||||
"h": {"type": "number", "description": "Height of the region"},
|
||||
"units": {"type": "string", "enum": ["px", "pct"], "description": "Units for coordinates and size"}
|
||||
},
|
||||
"required": ["x", "y", "w", "h", "units"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["imageId", "task"]
|
||||
},
|
||||
handler: async (args, context) => {
|
||||
const imageMessages = [];
|
||||
let imageId;
|
||||
|
||||
// Create image messages from cached images
|
||||
if (args.imageId && Array.isArray(args.imageId)) {
|
||||
args.imageId.forEach((imgId: string) => {
|
||||
const image = imageCache.getImage(`${context.req.id}_Image#${imgId}`);
|
||||
if (image) {
|
||||
imageMessages.push({
|
||||
type: "image",
|
||||
source: image,
|
||||
});
|
||||
}
|
||||
});
|
||||
imageId = args.imageId;
|
||||
delete args.imageId;
|
||||
}
|
||||
|
||||
// Add text message with the response
|
||||
if (Object.keys(args).length > 0) {
|
||||
imageMessages.push({
|
||||
type: "text",
|
||||
text: JSON.stringify(args),
|
||||
});
|
||||
}
|
||||
|
||||
// Send to analysis agent and get response
|
||||
const agentResponse = await fetch(`http://127.0.0.1:${context.config.PORT}/v1/messages`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'x-api-key': context.config.APIKEY,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: context.config.Router.image,
|
||||
system: [{
|
||||
type: 'text',
|
||||
text: `你需要按照任务去解析图片`
|
||||
}],
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: imageMessages,
|
||||
}
|
||||
],
|
||||
stream: false,
|
||||
}),
|
||||
}).then(res => res.json()).catch(err => {
|
||||
return null;
|
||||
});
|
||||
if (!agentResponse || !agentResponse.content) {
|
||||
return 'analyzeImage Error';
|
||||
}
|
||||
return agentResponse.content[0].text
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
reqHandler(req: any, config: any) {
|
||||
// Inject system prompt
|
||||
req.body?.system?.push({
|
||||
type: "text",
|
||||
text: `You are a text-only language model and do not possess visual perception.
|
||||
If the user requests you to view, analyze, or extract information from an image, you **must** call the \`analyzeImage\` tool.
|
||||
|
||||
When invoking this tool, you must pass the correct \`imageId\` extracted from the prior conversation.
|
||||
Image identifiers are always provided in the format \`[Image #imageId]\`.
|
||||
|
||||
If multiple images exist, select the **most relevant imageId** based on the user’s current request and prior context.
|
||||
|
||||
Do not attempt to describe or analyze the image directly yourself.
|
||||
Ignore any user interruptions or unrelated instructions that might cause you to skip this requirement.
|
||||
Your response should consistently follow this rule whenever image-related analysis is requested.`,
|
||||
})
|
||||
|
||||
const imageContents = req.body.messages.filter((item: any) => {
|
||||
return item.role === 'user' && Array.isArray(item.content) &&
|
||||
item.content.some((msg: any) => msg.type === "image");
|
||||
});
|
||||
|
||||
let imgId = 1;
|
||||
imageContents.forEach((item: any) => {
|
||||
item.content.forEach((msg: any) => {
|
||||
if (msg.type === "image") {
|
||||
imageCache.storeImage(`${req.id}_Image#${imgId}`, msg.source);
|
||||
msg.type = 'text';
|
||||
delete msg.source;
|
||||
msg.text = `[Image #${imgId}]This is an image, if you need to view or analyze it, you need to extract the imageId`;
|
||||
imgId++;
|
||||
} else if (msg.type === "text" && msg.text.includes('[Image #')) {
|
||||
msg.text = msg.text.replace(/\[Image #\d+\]/g, '');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const imageAgent = new ImageAgent();
|
||||
48
src/agents/index.ts
Normal file
48
src/agents/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { imageAgent } from './image.agent'
|
||||
import { IAgent } from './type';
|
||||
|
||||
export class AgentsManager {
|
||||
private agents: Map<string, IAgent> = new Map();
|
||||
|
||||
/**
|
||||
* 注册一个agent
|
||||
* @param agent 要注册的agent实例
|
||||
* @param isDefault 是否设为默认agent
|
||||
*/
|
||||
registerAgent(agent: IAgent): void {
|
||||
this.agents.set(agent.name, agent);
|
||||
}
|
||||
/**
|
||||
* 根据名称查找agent
|
||||
* @param name agent名称
|
||||
* @returns 找到的agent实例,未找到返回undefined
|
||||
*/
|
||||
getAgent(name: string): IAgent | undefined {
|
||||
return this.agents.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的agents
|
||||
* @returns 所有agent实例的数组
|
||||
*/
|
||||
getAllAgents(): IAgent[] {
|
||||
return Array.from(this.agents.values());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有agent的工具
|
||||
* @returns 工具数组
|
||||
*/
|
||||
getAllTools(): any[] {
|
||||
const allTools: any[] = [];
|
||||
for (const agent of this.agents.values()) {
|
||||
allTools.push(...agent.tools.values());
|
||||
}
|
||||
return allTools;
|
||||
}
|
||||
}
|
||||
|
||||
const agentsManager = new AgentsManager()
|
||||
agentsManager.registerAgent(imageAgent)
|
||||
export default agentsManager
|
||||
19
src/agents/type.ts
Normal file
19
src/agents/type.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface ITool {
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: any;
|
||||
|
||||
handler: (args: any, context: any) => Promise<string>;
|
||||
}
|
||||
|
||||
export interface IAgent {
|
||||
name: string;
|
||||
|
||||
tools: Map<string, ITool>;
|
||||
|
||||
shouldHandle: (req: any, config: any) => boolean;
|
||||
|
||||
reqHandler: (req: any, config: any) => void;
|
||||
|
||||
resHandler?: (payload: any, config: any) => void;
|
||||
}
|
||||
190
src/index.ts
190
src/index.ts
@@ -15,6 +15,14 @@ import { CONFIG_FILE } from "./constants";
|
||||
import createWriteStream from "pino-rotating-file-stream";
|
||||
import { HOME_DIR } from "./constants";
|
||||
import { configureLogging } from "./utils/log";
|
||||
import { sessionUsageCache } from "./utils/cache";
|
||||
import {SSEParserTransform} from "./utils/SSEParser.transform";
|
||||
import {SSESerializerTransform} from "./utils/SSESerializer.transform";
|
||||
import {rewriteStream} from "./utils/rewriteStream";
|
||||
import JSON5 from "json5";
|
||||
import { IAgent } from "./agents/type";
|
||||
import agentsManager from "./agents";
|
||||
|
||||
|
||||
async function initializeClaudeConfig() {
|
||||
const homeDir = homedir();
|
||||
@@ -52,11 +60,11 @@ async function run(options: RunOptions = {}) {
|
||||
// Clean up old log files, keeping only the 10 most recent ones
|
||||
await cleanupLogFiles();
|
||||
const config = await initConfig();
|
||||
|
||||
|
||||
// Configure logging based on config
|
||||
configureLogging(config);
|
||||
|
||||
let HOST = config.HOST;
|
||||
|
||||
let HOST = config.HOST || "127.0.0.1";
|
||||
|
||||
if (config.HOST && !config.APIKEY) {
|
||||
HOST = "127.0.0.1";
|
||||
@@ -80,7 +88,6 @@ async function run(options: RunOptions = {}) {
|
||||
cleanupPidFile();
|
||||
process.exit(0);
|
||||
});
|
||||
console.log(HOST);
|
||||
|
||||
// Use port from environment variable if set (for background process)
|
||||
const servicePort = process.env.SERVICE_PORT
|
||||
@@ -88,15 +95,18 @@ async function run(options: RunOptions = {}) {
|
||||
: port;
|
||||
|
||||
// Configure logger based on config settings
|
||||
const loggerConfig = config.LOG !== false ? {
|
||||
level: config.LOG_LEVEL || "info",
|
||||
stream: createWriteStream({
|
||||
path: HOME_DIR,
|
||||
filename: config.LOGNAME || `./logs/ccr-${+new Date()}.log`,
|
||||
maxFiles: 3,
|
||||
interval: "1d",
|
||||
}),
|
||||
} : false;
|
||||
const loggerConfig =
|
||||
config.LOG !== false
|
||||
? {
|
||||
level: config.LOG_LEVEL || "debug",
|
||||
stream: createWriteStream({
|
||||
path: HOME_DIR,
|
||||
filename: config.LOGNAME || `./logs/ccr-${+new Date()}.log`,
|
||||
maxFiles: 3,
|
||||
interval: "1d",
|
||||
}),
|
||||
}
|
||||
: false;
|
||||
|
||||
const server = createServer({
|
||||
jsonPath: CONFIG_FILE,
|
||||
@@ -126,9 +136,161 @@ async function run(options: RunOptions = {}) {
|
||||
});
|
||||
server.addHook("preHandler", async (req, reply) => {
|
||||
if (req.url.startsWith("/v1/messages")) {
|
||||
router(req, reply, config);
|
||||
const useAgents = []
|
||||
|
||||
for (const agent of agentsManager.getAllAgents()) {
|
||||
if (agent.shouldHandle(req, config)) {
|
||||
// 设置agent标识
|
||||
useAgents.push(agent.name)
|
||||
|
||||
// change request body
|
||||
agent.reqHandler(req, config);
|
||||
|
||||
// append agent tools
|
||||
if (agent.tools.size) {
|
||||
req.body.tools.unshift(...Array.from(agent.tools.values()).map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
input_schema: item.input_schema
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useAgents.length) {
|
||||
req.agents = useAgents;
|
||||
}
|
||||
await router(req, reply, config);
|
||||
}
|
||||
});
|
||||
server.addHook("onSend", async (req, reply, payload) => {
|
||||
if (req.sessionId && req.url.startsWith("/v1/messages")) {
|
||||
if (payload instanceof ReadableStream) {
|
||||
if (req.agents) {
|
||||
const eventStream = payload.pipeThrough(new SSEParserTransform())
|
||||
let currentAgent: undefined | IAgent;
|
||||
let currentToolIndex = -1
|
||||
let currentToolName = ''
|
||||
let currentToolArgs = ''
|
||||
let currentToolId = ''
|
||||
const toolMessages: any[] = []
|
||||
const assistantMessages: any[] = []
|
||||
// 存储Anthropic格式的消息体,区分文本和工具类型
|
||||
return rewriteStream(eventStream, async (data, controller) => {
|
||||
// 检测工具调用开始
|
||||
if (data.event === 'content_block_start' && data?.data?.content_block?.name) {
|
||||
const agent = req.agents.find((name: string) => agentsManager.getAgent(name)?.tools.get(data.data.content_block.name))
|
||||
if (agent) {
|
||||
currentAgent = agentsManager.getAgent(agent)
|
||||
currentToolIndex = data.data.index
|
||||
currentToolName = data.data.content_block.name
|
||||
currentToolId = data.data.content_block.id
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 收集工具参数
|
||||
if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data?.delta?.type === 'input_json_delta') {
|
||||
currentToolArgs += data.data?.delta?.partial_json;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 工具调用完成,处理agent调用
|
||||
if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data.type === 'content_block_stop') {
|
||||
try {
|
||||
const args = JSON5.parse(currentToolArgs);
|
||||
assistantMessages.push({
|
||||
type: "tool_use",
|
||||
id: currentToolId,
|
||||
name: currentToolName,
|
||||
input: args
|
||||
})
|
||||
const toolResult = await currentAgent?.tools.get(currentToolName)?.handler(args, {
|
||||
req,
|
||||
config
|
||||
});
|
||||
toolMessages.push({
|
||||
"tool_use_id": currentToolId,
|
||||
"type": "tool_result",
|
||||
"content": toolResult
|
||||
})
|
||||
currentAgent = undefined
|
||||
currentToolIndex = -1
|
||||
currentToolName = ''
|
||||
currentToolArgs = ''
|
||||
currentToolId = ''
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (data.event === 'message_delta' && toolMessages.length) {
|
||||
req.body.messages.push({
|
||||
role: 'assistant',
|
||||
content: assistantMessages
|
||||
})
|
||||
req.body.messages.push({
|
||||
role: 'user',
|
||||
content: toolMessages
|
||||
})
|
||||
const response = await fetch(`http://127.0.0.1:${config.PORT}/v1/messages`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'x-api-key': config.APIKEY,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
})
|
||||
if (!response.ok) {
|
||||
return undefined;
|
||||
}
|
||||
const stream = response.body!.pipeThrough(new SSEParserTransform())
|
||||
const reader = stream.getReader()
|
||||
while (true) {
|
||||
const {value, done} = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
if (['message_start', 'message_stop'].includes(value.event)) {
|
||||
continue
|
||||
}
|
||||
controller.enqueue(value)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
return data
|
||||
}).pipeThrough(new SSESerializerTransform())
|
||||
}
|
||||
|
||||
const [originalStream, clonedStream] = payload.tee();
|
||||
const read = async (stream: ReadableStream) => {
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
// Process the value if needed
|
||||
const dataStr = new TextDecoder().decode(value);
|
||||
if (!dataStr.startsWith("event: message_delta")) {
|
||||
continue;
|
||||
}
|
||||
const str = dataStr.slice(27);
|
||||
try {
|
||||
const message = JSON.parse(str);
|
||||
sessionUsageCache.put(req.sessionId, message.usage);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
read(clonedStream);
|
||||
return originalStream
|
||||
}
|
||||
sessionUsageCache.put(req.sessionId, payload.usage);
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
||||
73
src/utils/SSEParser.transform.ts
Normal file
73
src/utils/SSEParser.transform.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
export class SSEParserTransform extends TransformStream<string, any> {
|
||||
private buffer = '';
|
||||
private currentEvent: Record<string, any> = {};
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
transform: (chunk: string, controller) => {
|
||||
const decoder = new TextDecoder();
|
||||
const text = decoder.decode(chunk);
|
||||
this.buffer += text;
|
||||
const lines = this.buffer.split('\n');
|
||||
|
||||
// 保留最后一行(可能不完整)
|
||||
this.buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
const event = this.processLine(line);
|
||||
if (event) {
|
||||
controller.enqueue(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
flush: (controller) => {
|
||||
// 处理缓冲区中剩余的内容
|
||||
if (this.buffer.trim()) {
|
||||
const events: any[] = [];
|
||||
this.processLine(this.buffer.trim(), events);
|
||||
events.forEach(event => controller.enqueue(event));
|
||||
}
|
||||
|
||||
// 推送最后一个事件(如果有)
|
||||
if (Object.keys(this.currentEvent).length > 0) {
|
||||
controller.enqueue(this.currentEvent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private processLine(line: string, events?: any[]): any | null {
|
||||
if (!line.trim()) {
|
||||
if (Object.keys(this.currentEvent).length > 0) {
|
||||
const event = { ...this.currentEvent };
|
||||
this.currentEvent = {};
|
||||
if (events) {
|
||||
events.push(event);
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (line.startsWith('event:')) {
|
||||
this.currentEvent.event = line.slice(6).trim();
|
||||
} else if (line.startsWith('data:')) {
|
||||
const data = line.slice(5).trim();
|
||||
if (data === '[DONE]') {
|
||||
this.currentEvent.data = { type: 'done' };
|
||||
} else {
|
||||
try {
|
||||
this.currentEvent.data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
this.currentEvent.data = { raw: data, error: 'JSON parse failed' };
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith('id:')) {
|
||||
this.currentEvent.id = line.slice(3).trim();
|
||||
} else if (line.startsWith('retry:')) {
|
||||
this.currentEvent.retry = parseInt(line.slice(6).trim());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
29
src/utils/SSESerializer.transform.ts
Normal file
29
src/utils/SSESerializer.transform.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export class SSESerializerTransform extends TransformStream<any, string> {
|
||||
constructor() {
|
||||
super({
|
||||
transform: (event, controller) => {
|
||||
let output = '';
|
||||
|
||||
if (event.event) {
|
||||
output += `event: ${event.event}\n`;
|
||||
}
|
||||
if (event.id) {
|
||||
output += `id: ${event.id}\n`;
|
||||
}
|
||||
if (event.retry) {
|
||||
output += `retry: ${event.retry}\n`;
|
||||
}
|
||||
if (event.data) {
|
||||
if (event.data.type === 'done') {
|
||||
output += 'data: [DONE]\n';
|
||||
} else {
|
||||
output += `data: ${JSON.stringify(event.data)}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
output += '\n';
|
||||
controller.enqueue(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
47
src/utils/cache.ts
Normal file
47
src/utils/cache.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// LRU cache for session usage
|
||||
|
||||
export interface Usage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
}
|
||||
|
||||
class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
this.cache = new Map<K, V>();
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
if (!this.cache.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = this.cache.get(key) as V;
|
||||
// Move to end to mark as recently used
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
put(key: K, value: V): void {
|
||||
if (this.cache.has(key)) {
|
||||
// If key exists, delete it to update its position
|
||||
this.cache.delete(key);
|
||||
} else if (this.cache.size >= this.capacity) {
|
||||
// If cache is full, delete the least recently used item
|
||||
const leastRecentlyUsedKey = this.cache.keys().next().value;
|
||||
if (leastRecentlyUsedKey !== undefined) {
|
||||
this.cache.delete(leastRecentlyUsedKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
values(): V[] {
|
||||
return Array.from(this.cache.values());
|
||||
}
|
||||
}
|
||||
|
||||
export const sessionUsageCache = new LRUCache<string, Usage>(100);
|
||||
@@ -5,16 +5,30 @@ import {
|
||||
decrementReferenceCount,
|
||||
incrementReferenceCount,
|
||||
} from "./processCheck";
|
||||
import {HOME_DIR} from "../constants";
|
||||
import {join} from "path";
|
||||
|
||||
export async function executeCodeCommand(args: string[] = []) {
|
||||
// Set environment variables
|
||||
const config = await readConfigFile();
|
||||
const env: Record<string, string> = {
|
||||
...process.env,
|
||||
ANTHROPIC_AUTH_TOKEN: "test",
|
||||
ANTHROPIC_AUTH_TOKEN: config?.APIKEY || "test",
|
||||
ANTHROPIC_API_KEY: '',
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.PORT || 3456}`,
|
||||
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
|
||||
};
|
||||
let settingsFlag: Record<string, any> | undefined;
|
||||
if (config?.StatusLine?.enabled) {
|
||||
settingsFlag = {
|
||||
statusLine: {
|
||||
type: "command",
|
||||
command: "ccr statusline",
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
args.push(`--settings=${JSON.stringify(settingsFlag)}`);
|
||||
}
|
||||
|
||||
// Non-interactive mode for automation environments
|
||||
if (config.NON_INTERACTIVE_MODE) {
|
||||
@@ -29,31 +43,38 @@ export async function executeCodeCommand(args: string[] = []) {
|
||||
env.ANTHROPIC_SMALL_FAST_MODEL = config.ANTHROPIC_SMALL_FAST_MODEL;
|
||||
}
|
||||
|
||||
if (config?.APIKEY) {
|
||||
env.ANTHROPIC_API_KEY = config.APIKEY;
|
||||
delete env.ANTHROPIC_AUTH_TOKEN;
|
||||
}
|
||||
// if (config?.APIKEY) {
|
||||
// env.ANTHROPIC_API_KEY = config.APIKEY;
|
||||
// delete env.ANTHROPIC_AUTH_TOKEN;
|
||||
// }
|
||||
|
||||
// Increment reference count when command starts
|
||||
incrementReferenceCount();
|
||||
|
||||
// Execute claude command
|
||||
const claudePath = process.env.CLAUDE_PATH || "claude";
|
||||
|
||||
const claudePath = config?.CLAUDE_PATH || process.env.CLAUDE_PATH || "claude";
|
||||
|
||||
// Properly join arguments to preserve spaces in quotes
|
||||
// Wrap each argument in double quotes to preserve single and double quotes inside arguments
|
||||
const joinedArgs = args.length > 0 ? args.map(arg => `"${arg.replace(/\"/g, '\\"')}"`).join(" ") : "";
|
||||
const joinedArgs =
|
||||
args.length > 0
|
||||
? args.map((arg) => `"${arg.replace(/\"/g, '\\"')}"`).join(" ")
|
||||
: "";
|
||||
|
||||
// 🔥 CONFIG-DRIVEN: stdio configuration based on environment
|
||||
const stdioConfig: StdioOptions = config.NON_INTERACTIVE_MODE
|
||||
? ["pipe", "inherit", "inherit"] // Pipe stdin for non-interactive
|
||||
: "inherit"; // Default inherited behavior
|
||||
|
||||
const claudeProcess = spawn(claudePath + (joinedArgs ? ` ${joinedArgs}` : ""), [], {
|
||||
env,
|
||||
stdio: stdioConfig,
|
||||
shell: true,
|
||||
});
|
||||
console.log(joinedArgs)
|
||||
const claudeProcess = spawn(
|
||||
claudePath + (joinedArgs ? ` ${joinedArgs}` : ""),
|
||||
[],
|
||||
{
|
||||
env,
|
||||
stdio: stdioConfig,
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Close stdin for non-interactive mode
|
||||
if (config.NON_INTERACTIVE_MODE) {
|
||||
|
||||
@@ -16,7 +16,7 @@ let logLevel: string = "info";
|
||||
// Function to configure logging
|
||||
export function configureLogging(config: { LOG?: boolean; LOG_LEVEL?: string }) {
|
||||
isLogEnabled = config.LOG !== false; // Default to true if not explicitly set to false
|
||||
logLevel = config.LOG_LEVEL || "info";
|
||||
logLevel = config.LOG_LEVEL || "debug";
|
||||
}
|
||||
|
||||
export function log(...args: any[]) {
|
||||
|
||||
31
src/utils/rewriteStream.ts
Normal file
31
src/utils/rewriteStream.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**rewriteStream
|
||||
* 读取源readablestream,返回一个新的readablestream,由processor对源数据进行处理后将返回的新值推送到新的stream,如果没有返回值则不推送
|
||||
* @param stream
|
||||
* @param processor
|
||||
*/
|
||||
export const rewriteStream = (stream: ReadableStream, processor: (data: any, controller: ReadableStreamController<any>) => Promise<any>): ReadableStream => {
|
||||
const reader = stream.getReader()
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
const processed = await processor(value, controller)
|
||||
if (processed !== undefined) {
|
||||
controller.enqueue(processed)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error)
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@anthropic-ai/sdk/resources/messages";
|
||||
import { get_encoding } from "tiktoken";
|
||||
import { log } from "./log";
|
||||
import { sessionUsageCache, Usage } from "./cache";
|
||||
|
||||
const enc = get_encoding("cl100k_base");
|
||||
|
||||
@@ -62,23 +63,37 @@ const calculateTokenCount = (
|
||||
return tokenCount;
|
||||
};
|
||||
|
||||
const getUseModel = async (req: any, tokenCount: number, config: any) => {
|
||||
const getUseModel = async (
|
||||
req: any,
|
||||
tokenCount: number,
|
||||
config: any,
|
||||
lastUsage?: Usage | undefined
|
||||
) => {
|
||||
if (req.body.model.includes(",")) {
|
||||
const [provider, model] = req.body.model.split(",");
|
||||
const finalProvider = config.Providers.find(
|
||||
(p: any) => p.name.toLowerCase() === provider
|
||||
(p: any) => p.name.toLowerCase() === provider
|
||||
);
|
||||
const finalModel = finalProvider?.models?.find(
|
||||
(m: any) => m.toLowerCase() === model
|
||||
(m: any) => m.toLowerCase() === model
|
||||
);
|
||||
if (finalProvider && finalModel) {
|
||||
return `${finalProvider.name},${finalModel}`;
|
||||
}
|
||||
return req.body.model;
|
||||
}
|
||||
|
||||
// if tokenCount is greater than the configured threshold, use the long context model
|
||||
const longContextThreshold = config.Router.longContextThreshold || 60000;
|
||||
if (tokenCount > longContextThreshold && config.Router.longContext) {
|
||||
const lastUsageThreshold =
|
||||
lastUsage &&
|
||||
lastUsage.input_tokens > longContextThreshold &&
|
||||
tokenCount > 20000;
|
||||
const tokenCountThreshold = tokenCount > longContextThreshold;
|
||||
if (
|
||||
(lastUsageThreshold || tokenCountThreshold) &&
|
||||
config.Router.longContext
|
||||
) {
|
||||
log(
|
||||
"Using long context model due to token count:",
|
||||
tokenCount,
|
||||
@@ -126,6 +141,14 @@ const getUseModel = async (req: any, tokenCount: number, config: any) => {
|
||||
};
|
||||
|
||||
export const router = async (req: any, _res: any, config: any) => {
|
||||
// Parse sessionId from metadata.user_id
|
||||
if (req.body.metadata?.user_id) {
|
||||
const parts = req.body.metadata.user_id.split("_session_");
|
||||
if (parts.length > 1) {
|
||||
req.sessionId = parts[1];
|
||||
}
|
||||
}
|
||||
const lastMessageUsage = sessionUsageCache.get(req.sessionId);
|
||||
const { messages, system = [], tools }: MessageCreateParamsBase = req.body;
|
||||
try {
|
||||
const tokenCount = calculateTokenCount(
|
||||
@@ -145,7 +168,7 @@ export const router = async (req: any, _res: any, config: any) => {
|
||||
}
|
||||
}
|
||||
if (!model) {
|
||||
model = await getUseModel(req, tokenCount, config);
|
||||
model = await getUseModel(req, tokenCount, config, lastMessageUsage);
|
||||
}
|
||||
req.body.model = model;
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface StatusLineModuleConfig {
|
||||
text: string;
|
||||
color?: string;
|
||||
background?: string;
|
||||
scriptPath?: string; // 用于script类型的模块,指定要执行的Node.js脚本文件路径
|
||||
}
|
||||
|
||||
export interface StatusLineThemeConfig {
|
||||
@@ -132,11 +133,58 @@ function getColorCode(colorName: string): string {
|
||||
|
||||
// 变量替换函数,支持{{var}}格式的变量替换
|
||||
function replaceVariables(text: string, variables: Record<string, string>): string {
|
||||
return text.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
||||
return variables[varName] || match;
|
||||
return text.replace(/\{\{(\w+)\}\}/g, (_match, varName) => {
|
||||
return variables[varName] || "";
|
||||
});
|
||||
}
|
||||
|
||||
// 执行脚本并获取输出
|
||||
async function executeScript(scriptPath: string, variables: Record<string, string>): Promise<string> {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
await fs.access(scriptPath);
|
||||
|
||||
// 使用require动态加载脚本模块
|
||||
const scriptModule = require(scriptPath);
|
||||
|
||||
// 如果导出的是函数,则调用它并传入变量
|
||||
if (typeof scriptModule === 'function') {
|
||||
const result = scriptModule(variables);
|
||||
// 如果返回的是Promise,则等待它完成
|
||||
if (result instanceof Promise) {
|
||||
return await result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果导出的是default函数,则调用它
|
||||
if (scriptModule.default && typeof scriptModule.default === 'function') {
|
||||
const result = scriptModule.default(variables);
|
||||
// 如果返回的是Promise,则等待它完成
|
||||
if (result instanceof Promise) {
|
||||
return await result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果导出的是字符串,则直接返回
|
||||
if (typeof scriptModule === 'string') {
|
||||
return scriptModule;
|
||||
}
|
||||
|
||||
// 如果导出的是default字符串,则返回它
|
||||
if (scriptModule.default && typeof scriptModule.default === 'string') {
|
||||
return scriptModule.default;
|
||||
}
|
||||
|
||||
// 默认情况下返回空字符串
|
||||
return "";
|
||||
} catch (error) {
|
||||
console.error(`执行脚本 ${scriptPath} 时出错:`, error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 默认主题配置 - 使用Nerd Fonts图标和美观配色
|
||||
const DEFAULT_THEME: StatusLineThemeConfig = {
|
||||
modules: [
|
||||
@@ -490,9 +538,9 @@ export async function parseStatusLineData(input: StatusLineInput): Promise<strin
|
||||
|
||||
// 根据风格渲染状态行
|
||||
if (isPowerline) {
|
||||
return renderPowerlineStyle(theme, variables);
|
||||
return await renderPowerlineStyle(theme, variables);
|
||||
} else {
|
||||
return renderDefaultStyle(theme, variables);
|
||||
return await renderDefaultStyle(theme, variables);
|
||||
}
|
||||
} catch (error) {
|
||||
// 发生错误时返回空字符串
|
||||
@@ -529,10 +577,10 @@ async function getProjectThemeConfigForStyle(style: string): Promise<StatusLineT
|
||||
}
|
||||
|
||||
// 渲染默认风格的状态行
|
||||
function renderDefaultStyle(
|
||||
async function renderDefaultStyle(
|
||||
theme: StatusLineThemeConfig,
|
||||
variables: Record<string, string>
|
||||
): string {
|
||||
): Promise<string> {
|
||||
const modules = theme.modules || DEFAULT_THEME.modules;
|
||||
const parts: string[] = [];
|
||||
|
||||
@@ -542,19 +590,30 @@ function renderDefaultStyle(
|
||||
const color = module.color ? getColorCode(module.color) : "";
|
||||
const background = module.background ? getColorCode(module.background) : "";
|
||||
const icon = module.icon || "";
|
||||
const text = replaceVariables(module.text, variables);
|
||||
|
||||
// 如果text为空且不是usage类型,则跳过该模块
|
||||
if (!text && module.type !== "usage") {
|
||||
// 如果是script类型,执行脚本获取文本
|
||||
let text = "";
|
||||
if (module.type === "script" && module.scriptPath) {
|
||||
text = await executeScript(module.scriptPath, variables);
|
||||
} else {
|
||||
text = replaceVariables(module.text, variables);
|
||||
}
|
||||
|
||||
// 构建显示文本
|
||||
let displayText = "";
|
||||
if (icon) {
|
||||
displayText += `${icon} `;
|
||||
}
|
||||
displayText += text;
|
||||
|
||||
// 如果displayText为空,或者只有图标没有实际文本,则跳过该模块
|
||||
if (!displayText || !text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 构建模块字符串
|
||||
let part = `${background}${color}`;
|
||||
if (icon) {
|
||||
part += `${icon} `;
|
||||
}
|
||||
part += `${text}${COLORS.reset}`;
|
||||
part += `${displayText}${COLORS.reset}`;
|
||||
|
||||
parts.push(part);
|
||||
}
|
||||
@@ -701,10 +760,10 @@ function segment(text: string, textFg: string, bgColor: string, nextBgColor: str
|
||||
}
|
||||
|
||||
// 渲染Powerline风格的状态行
|
||||
function renderPowerlineStyle(
|
||||
async function renderPowerlineStyle(
|
||||
theme: StatusLineThemeConfig,
|
||||
variables: Record<string, string>
|
||||
): string {
|
||||
): Promise<string> {
|
||||
const modules = theme.modules || POWERLINE_THEME.modules;
|
||||
const segments: string[] = [];
|
||||
|
||||
@@ -714,11 +773,13 @@ function renderPowerlineStyle(
|
||||
const color = module.color || "white";
|
||||
const backgroundName = module.background || "";
|
||||
const icon = module.icon || "";
|
||||
const text = replaceVariables(module.text, variables);
|
||||
|
||||
// 如果text为空且不是usage类型,则跳过该模块
|
||||
if (!text && module.type !== "usage") {
|
||||
continue;
|
||||
// 如果是script类型,执行脚本获取文本
|
||||
let text = "";
|
||||
if (module.type === "script" && module.scriptPath) {
|
||||
text = await executeScript(module.scriptPath, variables);
|
||||
} else {
|
||||
text = replaceVariables(module.text, variables);
|
||||
}
|
||||
|
||||
// 构建显示文本
|
||||
@@ -728,6 +789,11 @@ function renderPowerlineStyle(
|
||||
}
|
||||
displayText += text;
|
||||
|
||||
// 如果displayText为空,或者只有图标没有实际文本,则跳过该模块
|
||||
if (!displayText || !text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取下一个模块的背景色(用于分隔符)
|
||||
let nextBackground: string | null = null;
|
||||
if (i < modules.length - 1) {
|
||||
|
||||
11
ui/package-lock.json
generated
11
ui/package-lock.json
generated
@@ -23,6 +23,7 @@
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"react": "^19.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
@@ -4469,6 +4470,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-colorful": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
||||
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||
|
||||
@@ -331,7 +331,7 @@ function App() {
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4">
|
||||
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4 overflow-hidden">
|
||||
<div className="w-3/5">
|
||||
<Providers />
|
||||
</div>
|
||||
@@ -339,7 +339,7 @@ function App() {
|
||||
<div className="h-3/5">
|
||||
<Router />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Transformers />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
// Validate the received data to ensure it has the expected structure
|
||||
const validConfig = {
|
||||
LOG: typeof data.LOG === 'boolean' ? data.LOG : false,
|
||||
LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'info',
|
||||
LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'debug',
|
||||
CLAUDE_PATH: typeof data.CLAUDE_PATH === 'string' ? data.CLAUDE_PATH : '',
|
||||
HOST: typeof data.HOST === 'string' ? data.HOST : '127.0.0.1',
|
||||
PORT: typeof data.PORT === 'number' ? data.PORT : 3456,
|
||||
@@ -115,7 +115,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
// Set default empty config when fetch fails
|
||||
setConfig({
|
||||
LOG: false,
|
||||
LOG_LEVEL: 'info',
|
||||
LOG_LEVEL: 'debug',
|
||||
CLAUDE_PATH: '',
|
||||
HOST: '127.0.0.1',
|
||||
PORT: 3456,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { X, Trash2, Plus, Eye, EyeOff } from "lucide-react";
|
||||
import { X, Trash2, Plus, Eye, EyeOff, Search, XCircle } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { ComboInput } from "@/components/ui/combo-input";
|
||||
@@ -38,6 +38,7 @@ export function Providers() {
|
||||
const [showApiKey, setShowApiKey] = useState<Record<number, boolean>>({});
|
||||
const [apiKeyError, setApiKeyError] = useState<string | null>(null);
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const comboInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -487,15 +488,57 @@ export function Providers() {
|
||||
|
||||
const editingProvider = editingProviderData || (editingProviderIndex !== null ? validProviders[editingProviderIndex] : null);
|
||||
|
||||
// Filter providers based on search term
|
||||
const filteredProviders = validProviders.filter(provider => {
|
||||
if (!searchTerm) return true;
|
||||
const term = searchTerm.toLowerCase();
|
||||
// Check provider name and URL
|
||||
if (
|
||||
(provider.name && provider.name.toLowerCase().includes(term)) ||
|
||||
(provider.api_base_url && provider.api_base_url.toLowerCase().includes(term))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Check models
|
||||
if (provider.models && Array.isArray(provider.models)) {
|
||||
return provider.models.some(model =>
|
||||
model && model.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="flex h-full flex-col rounded-lg border shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between border-b p-4">
|
||||
<CardTitle className="text-lg">{t("providers.title")} <span className="text-sm font-normal text-gray-500">({validProviders.length})</span></CardTitle>
|
||||
<Button onClick={handleAddProvider}>{t("providers.add")}</Button>
|
||||
<CardHeader className="flex flex-col border-b p-4 gap-3">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-lg">{t("providers.title")} <span className="text-sm font-normal text-gray-500">({filteredProviders.length}/{validProviders.length})</span></CardTitle>
|
||||
<Button onClick={handleAddProvider}>{t("providers.add")}</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-500" />
|
||||
<Input
|
||||
placeholder={t("providers.search")}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
{searchTerm && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setSearchTerm("")}
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow overflow-y-auto p-4">
|
||||
<ProviderList
|
||||
providers={validProviders}
|
||||
providers={filteredProviders}
|
||||
onEdit={handleEditProvider}
|
||||
onRemove={setDeletingProviderIndex}
|
||||
/>
|
||||
|
||||
@@ -58,12 +58,12 @@ export function SettingsDialog({ isOpen, onOpenChange }: SettingsDialogProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent data-testid="settings-dialog">
|
||||
<DialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange} >
|
||||
<DialogContent data-testid="settings-dialog" className="max-h-[80vh] flex flex-col p-0">
|
||||
<DialogHeader className="p-4 pb-0">
|
||||
<DialogTitle>{t("toplevel.title")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-4 p-4 px-8 overflow-y-auto flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="log"
|
||||
@@ -213,7 +213,7 @@ export function SettingsDialog({ isOpen, onOpenChange }: SettingsDialogProps) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogFooter className="p-4 pt-0">
|
||||
<Button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
interface ColorPickerProps {
|
||||
value?: string;
|
||||
@@ -15,42 +14,8 @@ interface ColorPickerProps {
|
||||
showPreview?: boolean;
|
||||
}
|
||||
|
||||
// 预定义的ANSI颜色映射
|
||||
const ANSI_COLOR_MAP: Record<string, string> = {
|
||||
"black": "#000000",
|
||||
"red": "#ff0000",
|
||||
"green": "#00ff00",
|
||||
"yellow": "#ffff00",
|
||||
"blue": "#0000ff",
|
||||
"magenta": "#ff00ff",
|
||||
"cyan": "#00ffff",
|
||||
"white": "#ffffff",
|
||||
"bright_black": "#808080",
|
||||
"bright_red": "#ff8080",
|
||||
"bright_green": "#80ff80",
|
||||
"bright_yellow": "#ffff80",
|
||||
"bright_blue": "#8080ff",
|
||||
"bright_magenta": "#ff80ff",
|
||||
"bright_cyan": "#80ffff",
|
||||
"bright_white": "#ffffff"
|
||||
}
|
||||
|
||||
// 背景颜色映射(添加bg_前缀)
|
||||
const ANSI_BG_COLOR_MAP: Record<string, string> = Object.keys(ANSI_COLOR_MAP).reduce((acc, key) => {
|
||||
acc[`bg_${key}`] = ANSI_COLOR_MAP[key]
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
|
||||
// 合并所有颜色映射
|
||||
const ALL_COLOR_MAP = { ...ANSI_COLOR_MAP, ...ANSI_BG_COLOR_MAP }
|
||||
|
||||
// 获取颜色值的函数
|
||||
const getColorValue = (color: string): string => {
|
||||
// 如果是预定义的ANSI颜色
|
||||
if (ALL_COLOR_MAP[color]) {
|
||||
return ALL_COLOR_MAP[color]
|
||||
}
|
||||
|
||||
// 如果是十六进制颜色
|
||||
if (color.startsWith("#")) {
|
||||
return color
|
||||
@@ -91,15 +56,8 @@ export function ColorPicker({
|
||||
}
|
||||
}
|
||||
|
||||
const handlePresetColorClick = (colorName: string) => {
|
||||
handleColorChange(colorName)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const selectedColorValue = getColorValue(value)
|
||||
|
||||
// 获取ANSI颜色名称(如果适用)
|
||||
const ansiColorName = Object.keys(ALL_COLOR_MAP).find(key => ALL_COLOR_MAP[key] === selectedColorValue) || value
|
||||
const selectedColorValue = getColorValue(value)
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
@@ -120,7 +78,7 @@ export function ColorPicker({
|
||||
/>
|
||||
)}
|
||||
<span className="truncate flex-1">
|
||||
{value ? ansiColorName : placeholder}
|
||||
{value || placeholder}
|
||||
</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m7 15 5 5 5-5"/>
|
||||
@@ -152,7 +110,7 @@ export function ColorPicker({
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium truncate">
|
||||
{value ? ansiColorName : "未选择颜色"}
|
||||
{value || "未选择颜色"}
|
||||
</div>
|
||||
{value && value.startsWith("#") && (
|
||||
<div className="text-xs text-muted-foreground font-mono">
|
||||
@@ -184,7 +142,12 @@ export function ColorPicker({
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => customColor && handleColorChange(customColor)}
|
||||
onClick={() => {
|
||||
if (customColor && /^#[0-9A-F]{6}$/i.test(customColor)) {
|
||||
handleColorChange(customColor)
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
disabled={!customColor || !/^#[0-9A-F]{6}$/i.test(customColor)}
|
||||
>
|
||||
应用
|
||||
@@ -194,66 +157,6 @@ export function ColorPicker({
|
||||
输入十六进制颜色值 (例如: #FF0000)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 预定义颜色选项 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">ANSI 颜色</label>
|
||||
<span className="text-xs text-muted-foreground">文字颜色</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-8 gap-1">
|
||||
{Object.entries(ANSI_COLOR_MAP).map(([name, color]) => (
|
||||
<Button
|
||||
key={name}
|
||||
variant={value === name ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-8 w-8 p-0 rounded-full transition-all hover:scale-110",
|
||||
value === name && "ring-2 ring-offset-2 ring-ring ring-offset-background"
|
||||
)}
|
||||
style={{ backgroundColor: value === name ? color : undefined }}
|
||||
onClick={() => handlePresetColorClick(name)}
|
||||
title={name}
|
||||
>
|
||||
{value === name && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 背景颜色选项 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">背景颜色</label>
|
||||
<span className="text-xs text-muted-foreground">背景色</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-8 gap-1">
|
||||
{Object.entries(ANSI_BG_COLOR_MAP).map(([name, color]) => (
|
||||
<Button
|
||||
key={name}
|
||||
variant={value === name ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-8 w-8 p-0 rounded-full transition-all hover:scale-110",
|
||||
value === name && "ring-2 ring-offset-2 ring-ring ring-offset-background"
|
||||
)}
|
||||
style={{ backgroundColor: value === name ? color : undefined }}
|
||||
onClick={() => handlePresetColorClick(name)}
|
||||
title={name}
|
||||
>
|
||||
{value === name && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -119,4 +119,38 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
/* 美化滚动条 - WebKit浏览器 (Chrome, Safari, Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-muted-foreground/30;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-muted-foreground/50;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: oklch(0.556 0 0) oklch(0.97 0 0);
|
||||
}
|
||||
|
||||
.dark * {
|
||||
scrollbar-color: oklch(0.708 0 0) oklch(0.269 0 0);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@
|
||||
"select_template": "Select a template...",
|
||||
"api_key_required": "API Key is required",
|
||||
"name_required": "Name is required",
|
||||
"name_duplicate": "A provider with this name already exists"
|
||||
"name_duplicate": "A provider with this name already exists",
|
||||
"search": "Search providers..."
|
||||
|
||||
},
|
||||
"router": {
|
||||
@@ -128,13 +129,22 @@
|
||||
"module_text": "Text",
|
||||
"module_color": "Color",
|
||||
"module_background": "Background",
|
||||
"add_module": "Add Module",
|
||||
"module_text_description": "Enter display text, variables can be used:",
|
||||
"module_color_description": "Select text color",
|
||||
"module_background_description": "Select background color (optional)",
|
||||
"module_script_path": "Script Path",
|
||||
"module_script_path_description": "Enter the absolute path of the Node.js script file",
|
||||
"add_module": "Add Module",
|
||||
"remove_module": "Remove Module",
|
||||
"delete_module": "Delete Module",
|
||||
"preview": "Preview",
|
||||
"components": "Components",
|
||||
"properties": "Properties",
|
||||
"workDir": "Working Directory",
|
||||
"gitBranch": "Git Branch",
|
||||
"model": "Model",
|
||||
"usage": "Usage",
|
||||
"script": "Script",
|
||||
"background_none": "None",
|
||||
"color_black": "Black",
|
||||
"color_red": "Red",
|
||||
@@ -152,6 +162,16 @@
|
||||
"color_bright_magenta": "Bright Magenta",
|
||||
"color_bright_cyan": "Bright Cyan",
|
||||
"color_bright_white": "Bright White",
|
||||
"font_placeholder": "Select Font",
|
||||
"theme_placeholder": "Select Theme Style",
|
||||
"icon_placeholder": "Paste icon or search by name...",
|
||||
"icon_description": "Enter icon character, paste icon, or search icons (optional)",
|
||||
"text_placeholder": "e.g.: {{workDirName}}",
|
||||
"script_placeholder": "e.g.: /path/to/your/script.js",
|
||||
"drag_hint": "Drag components here to configure",
|
||||
"select_hint": "Select a component to configure",
|
||||
"no_icons_found": "No icons found",
|
||||
"no_icons_available": "No icons available",
|
||||
"import_export": "Import/Export",
|
||||
"import": "Import Config",
|
||||
"export": "Export Config",
|
||||
|
||||
@@ -93,7 +93,8 @@
|
||||
"select_template": "选择一个模板...",
|
||||
"api_key_required": "API 密钥为必填项",
|
||||
"name_required": "名称为必填项",
|
||||
"name_duplicate": "已存在同名供应商"
|
||||
"name_duplicate": "已存在同名供应商",
|
||||
"search": "搜索供应商..."
|
||||
|
||||
},
|
||||
"router": {
|
||||
@@ -128,13 +129,22 @@
|
||||
"module_text": "文本",
|
||||
"module_color": "颜色",
|
||||
"module_background": "背景",
|
||||
"add_module": "添加模块",
|
||||
"module_text_description": "输入显示文本,可使用变量:",
|
||||
"module_color_description": "选择文字颜色",
|
||||
"module_background_description": "选择背景颜色(可选)",
|
||||
"module_script_path": "脚本路径",
|
||||
"module_script_path_description": "输入Node.js脚本文件的绝对路径",
|
||||
"add_module": "添加模块",
|
||||
"remove_module": "移除模块",
|
||||
"delete_module": "删除组件",
|
||||
"preview": "预览",
|
||||
"components": "组件",
|
||||
"properties": "属性",
|
||||
"workDir": "工作目录",
|
||||
"gitBranch": "Git分支",
|
||||
"model": "模型",
|
||||
"usage": "使用情况",
|
||||
"script": "脚本",
|
||||
"background_none": "无",
|
||||
"color_black": "黑色",
|
||||
"color_red": "红色",
|
||||
@@ -152,6 +162,16 @@
|
||||
"color_bright_magenta": "亮品红",
|
||||
"color_bright_cyan": "亮青色",
|
||||
"color_bright_white": "亮白色",
|
||||
"font_placeholder": "选择字体",
|
||||
"theme_placeholder": "选择主题样式",
|
||||
"icon_placeholder": "粘贴图标或输入名称搜索...",
|
||||
"icon_description": "输入图标字符、粘贴图标或搜索图标(可选)",
|
||||
"text_placeholder": "例如: {{workDirName}}",
|
||||
"script_placeholder": "例如: /path/to/your/script.js",
|
||||
"drag_hint": "拖拽组件到此处进行配置",
|
||||
"select_hint": "选择一个组件进行配置",
|
||||
"no_icons_found": "未找到图标",
|
||||
"no_icons_available": "暂无可用图标",
|
||||
"import_export": "导入/导出",
|
||||
"import": "导入配置",
|
||||
"export": "导出配置",
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface StatusLineModuleConfig {
|
||||
text: string;
|
||||
color?: string;
|
||||
background?: string;
|
||||
scriptPath?: string; // 用于script类型的模块,指定要执行的Node.js脚本文件路径
|
||||
}
|
||||
|
||||
export interface StatusLineThemeConfig {
|
||||
@@ -44,6 +45,7 @@ export interface StatusLineConfig {
|
||||
currentStyle: string;
|
||||
default: StatusLineThemeConfig;
|
||||
powerline: StatusLineThemeConfig;
|
||||
fontFamily?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
|
||||
Reference in New Issue
Block a user