Compare commits

...

4 Commits

Author SHA1 Message Date
Luis Pater
bdac24bb4e Update PassthroughGeminiResponseStream to handle [DONE] marker
Some checks failed
docker-image / docker (push) Has been cancelled
goreleaser / goreleaser (push) Has been cancelled
- Added logic to return an empty slice when the raw JSON equals the `[DONE]` marker.
- Ensures proper termination of streamed Gemini responses.
2025-09-01 02:00:55 +08:00
Luis Pater
6d30faf9c9 Update Management API CN docs for authentication requirements
- Changed authentication requirements to mandate management keys for all requests, including local access.
- Clarified remote access setup and key provision methods.
- Adjusted the section header.
2025-08-31 15:29:20 +08:00
Luis Pater
c0eaa41c7a Add Gemini-to-Gemini request normalization and passthrough support
Some checks failed
docker-image / docker (push) Has been cancelled
goreleaser / goreleaser (push) Has been cancelled
- Introduced a `ConvertGeminiRequestToGemini` function to normalize Gemini v1beta requests by ensuring valid or default roles.
- Added passthrough response handlers for both streamed and non-streamed Gemini responses.
- Registered translators for Gemini-to-Gemini traffic in the initialization process.
- Updated `gemini-cli` request normalization to align with the new Gemini translator logic.
2025-08-31 15:05:16 +08:00
Luis Pater
8a2285e706 Reorganize and reintroduce Management API section in README files 2025-08-31 14:41:57 +08:00
8 changed files with 159 additions and 38 deletions

View File

@@ -1,4 +1,4 @@
# 管理 API(简体中文)
# 管理 API
基础路径:`http://localhost:8317/v0/management`
@@ -10,12 +10,11 @@
## 认证
- 本地访问(`127.0.0.1``::1`):无需管理密钥
- 远程访问需要同时满足:
- 配置中 `allow-remote-management: true`
- 通过以下任意方式提供管理密钥(明文):
- `Authorization: Bearer <plaintext-key>`
- `X-Management-Key: <plaintext-key>`
- 所有请求(包括本地访问)都必须提供有效的管理密钥.
- 远程访问需要在配置文件中开启远程访问: `allow-remote-management: true`
- 通过以下任意方式提供管理密钥(明文):
- `Authorization: Bearer <plaintext-key>`
- `X-Management-Key: <plaintext-key>`
若在启动时检测到配置中的管理密钥为明文,会自动使用 bcrypt 加密并回写到配置文件中。
@@ -33,7 +32,7 @@
- GET `/debug` — 获取当前 debug 状态
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/debug
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/debug
```
- 响应:
```json
@@ -56,7 +55,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/proxy-url` — 获取代理 URL 字符串
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/proxy-url
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/proxy-url
```
- 响应:
```json
@@ -84,7 +83,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- DELETE `/proxy-url` — 清空代理 URL
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE http://localhost:8317/v0/management/proxy-url
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE http://localhost:8317/v0/management/proxy-url
```
- 响应:
```json
@@ -95,7 +94,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE http://localhost:8317
- GET `/quota-exceeded/switch-project`
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-project
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-project
```
- 响应:
```json
@@ -116,7 +115,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/quota-exceeded/switch-preview-model`
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-preview-model
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-preview-model
```
- 响应:
```json
@@ -139,7 +138,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/api-keys` — 返回完整列表
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/api-keys
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/api-keys
```
- 响应:
```json
@@ -179,11 +178,11 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- DELETE `/api-keys` — 删除其中一个(`?value=` 或 `?index=`
- 请求(按值删除):
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?value=k1'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?value=k1'
```
- 请求(按索引删除):
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?index=0'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?index=0'
```
- 响应:
```json
@@ -194,7 +193,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:831
- GET `/generative-language-api-key`
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/generative-language-api-key
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/generative-language-api-key
```
- 响应:
```json
@@ -227,7 +226,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- DELETE `/generative-language-api-key`
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/generative-language-api-key?value=AIzaSy-2'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/generative-language-api-key?value=AIzaSy-2'
```
- 响应:
```json
@@ -238,7 +237,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:831
- GET `/request-log` — 获取布尔值
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-log
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-log
```
- 响应:
```json
@@ -261,7 +260,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/request-retry` — 获取整数
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-retry
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-retry
```
- 响应:
```json
@@ -284,7 +283,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/allow-localhost-unauthenticated` — 获取布尔值
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/allow-localhost-unauthenticated
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/allow-localhost-unauthenticated
```
- 响应:
```json
@@ -307,7 +306,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/claude-api-key` — 列出全部
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/claude-api-key
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/claude-api-key
```
- 响应:
```json
@@ -347,11 +346,11 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- DELETE `/claude-api-key` — 删除其中一个(`?api-key=` 或 `?index=`
- 请求(按 api-key
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?api-key=sk-b2'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?api-key=sk-b2'
```
- 请求(按索引):
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?index=0'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?index=0'
```
- 响应:
```json
@@ -362,7 +361,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:831
- GET `/openai-compatibility` — 列出全部
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/openai-compatibility
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/openai-compatibility
```
- 响应:
```json
@@ -402,11 +401,11 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- DELETE `/openai-compatibility` — 删除(`?name=` 或 `?index=`
- 请求(按名称):
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?name=openrouter'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?name=openrouter'
```
- 请求(按索引):
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?index=0'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?index=0'
```
- 响应:
```json
@@ -420,7 +419,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:831
- GET `/auth-files` — 列表
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/auth-files
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/auth-files
```
- 响应:
```json
@@ -430,7 +429,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/manage
- GET `/auth-files/download?name=<file.json>` — 下载单个文件
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -OJ 'http://localhost:8317/v0/management/auth-files/download?name=acc1.json'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -OJ 'http://localhost:8317/v0/management/auth-files/download?name=acc1.json'
```
- POST `/auth-files` — 上传
@@ -455,7 +454,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -OJ 'http://localhost:8317/v0/m
- DELETE `/auth-files?name=<file.json>` — 删除单个文件
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?name=acc1.json'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?name=acc1.json'
```
- 响应:
```json
@@ -465,7 +464,7 @@ curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:831
- DELETE `/auth-files?all=true` — 删除 `auth-dir` 下所有 `.json` 文件
- 请求:
```bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?all=true'
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?all=true'
```
- 响应:
```json

View File

@@ -328,10 +328,6 @@ openai-compatibility:
alias: "kimi-k2" # The alias used in the API.
```
## Management API
see [MANAGEMENT_API.md](MANAGEMENT_API.md)
### OpenAI Compatibility Providers
Configure upstream OpenAI-compatible providers (e.g., OpenRouter) via `openai-compatibility`.
@@ -466,6 +462,10 @@ Run the following command to start the server:
docker run --rm -p 8317:8317 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest
```
## Management API
see [MANAGEMENT_API.md](MANAGEMENT_API.md)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.

View File

@@ -96,10 +96,6 @@
默认情况下,服务器在端口 8317 上运行。
## 管理 API 文档
请参见 [MANAGEMENT_API_CN.md](MANAGEMENT_API_CN.md)
### API 端点
#### 列出模型
@@ -462,6 +458,10 @@ docker run -it -rm -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /pat
docker run --rm -p 8317:8317 -v /path/to/your/config.yaml:/CLIProxyAPI/config.yaml -v /path/to/your/auth-dir:/root/.cli-proxy-api eceasy/cli-proxy-api:latest
```
## 管理 API 文档
请参见 [MANAGEMENT_API_CN.md](MANAGEMENT_API_CN.md)
## 贡献
欢迎贡献!请随时提交 Pull Request。

View File

@@ -49,6 +49,33 @@ func ConvertGeminiRequestToGeminiCLI(_ string, rawJSON []byte, _ bool) []byte {
}
rawJSON = []byte(template)
// Normalize roles in request.contents: default to valid values if missing/invalid
contents := gjson.GetBytes(rawJSON, "request.contents")
if contents.Exists() {
prevRole := ""
idx := 0
contents.ForEach(func(_ gjson.Result, value gjson.Result) bool {
role := value.Get("role").String()
valid := role == "user" || role == "model"
if role == "" || !valid {
var newRole string
if prevRole == "" {
newRole = "user"
} else if prevRole == "user" {
newRole = "model"
} else {
newRole = "user"
}
path := fmt.Sprintf("request.contents.%d.role", idx)
rawJSON, _ = sjson.SetBytes(rawJSON, path, newRole)
role = newRole
}
prevRole = role
idx++
return true
})
}
return rawJSON
}

View File

@@ -0,0 +1,54 @@
// Package gemini provides in-provider request normalization for Gemini API.
// It ensures incoming v1beta requests meet minimal schema requirements
// expected by Google's Generative Language API.
package gemini
import (
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// ConvertGeminiRequestToGemini normalizes Gemini v1beta requests.
// - Adds a default role for each content if missing or invalid.
// The first message defaults to "user", then alternates user/model when needed.
//
// It keeps the payload otherwise unchanged.
func ConvertGeminiRequestToGemini(_ string, rawJSON []byte, _ bool) []byte {
// Fast path: if no contents field, return as-is
contents := gjson.GetBytes(rawJSON, "contents")
if !contents.Exists() {
return rawJSON
}
// Walk contents and fix roles
out := rawJSON
prevRole := ""
idx := 0
contents.ForEach(func(_ gjson.Result, value gjson.Result) bool {
role := value.Get("role").String()
// Only user/model are valid for Gemini v1beta requests
valid := role == "user" || role == "model"
if role == "" || !valid {
var newRole string
if prevRole == "" {
newRole = "user"
} else if prevRole == "user" {
newRole = "model"
} else {
newRole = "user"
}
path := fmt.Sprintf("contents.%d.role", idx)
out, _ = sjson.SetBytes(out, path, newRole)
role = newRole
}
prevRole = role
idx++
return true
})
return out
}

View File

@@ -0,0 +1,19 @@
package gemini
import (
"bytes"
"context"
)
// PassthroughGeminiResponseStream forwards Gemini responses unchanged.
func PassthroughGeminiResponseStream(_ context.Context, _ string, rawJSON []byte, _ *any) []string {
if bytes.Equal(rawJSON, []byte("[DONE]")) {
return []string{}
}
return []string{string(rawJSON)}
}
// PassthroughGeminiResponseNonStream forwards Gemini responses unchanged.
func PassthroughGeminiResponseNonStream(_ context.Context, _ string, rawJSON []byte, _ *any) string {
return string(rawJSON)
}

View File

@@ -0,0 +1,21 @@
package gemini
import (
. "github.com/luispater/CLIProxyAPI/internal/constant"
"github.com/luispater/CLIProxyAPI/internal/interfaces"
"github.com/luispater/CLIProxyAPI/internal/translator/translator"
)
// Register a no-op response translator and a request normalizer for Gemini→Gemini.
// The request converter ensures missing or invalid roles are normalized to valid values.
func init() {
translator.Register(
GEMINI,
GEMINI,
ConvertGeminiRequestToGemini,
interfaces.TranslateResponse{
Stream: PassthroughGeminiResponseStream,
NonStream: PassthroughGeminiResponseNonStream,
},
)
}

View File

@@ -12,6 +12,7 @@ import (
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/gemini"
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/openai"
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/claude"
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini"
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini-cli"
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai"
_ "github.com/luispater/CLIProxyAPI/internal/translator/openai/claude"