Skip to content

Commit aa6a681

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/copilot
2 parents f44aff4 + 21387f5 commit aa6a681

256 files changed

Lines changed: 32753 additions & 438 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yaml

Lines changed: 592 additions & 16 deletions
Large diffs are not rendered by default.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ logs/*
1212
conv/*
1313
temp/*
1414
refs/*
15+
plugins/*
16+
examples/plugin/bin/*
1517

1618
# Storage backends
1719
pgstore/*

.goreleaser.yml

Lines changed: 0 additions & 44 deletions
This file was deleted.

Dockerfile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
FROM golang:1.26-alpine AS builder
1+
FROM golang:1.26-bookworm AS builder
22

33
WORKDIR /app
44

5+
RUN apt-get update && apt-get install -y --no-install-recommends build-essential git && rm -rf /var/lib/apt/lists/*
6+
57
COPY go.mod go.sum ./
68

79
RUN go mod download
@@ -12,11 +14,11 @@ ARG VERSION=dev
1214
ARG COMMIT=none
1315
ARG BUILD_DATE=unknown
1416

15-
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.Commit=${COMMIT}' -X 'main.BuildDate=${BUILD_DATE}'" -o ./CLIProxyAPI ./cmd/server/
17+
RUN CGO_ENABLED=1 GOOS=linux go build -buildvcs=false -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.Commit=${COMMIT}' -X 'main.BuildDate=${BUILD_DATE}'" -o ./CLIProxyAPI ./cmd/server/
1618

17-
FROM alpine:3.23
19+
FROM debian:bookworm
1820

19-
RUN apk add --no-cache tzdata
21+
RUN apt-get update && apt-get install -y --no-install-recommends tzdata ca-certificates && rm -rf /var/lib/apt/lists/*
2022

2123
RUN mkdir /CLIProxyAPI
2224

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ VisionCoder is also offering our users a limited-time <a href="https://coder.vis
4040
<td width="180"><a href="https://apikey.fun/register?aff=CLIProxyAPI"><img src="./assets/apikey.png" alt="APIKEY.FUN" width="150"></a></td>
4141
<td>Thanks to APIKEY.FUN for sponsoring this project! APIKEY.FUN is a professional enterprise-grade AI relay platform dedicated to providing stable, efficient, and low-cost AI model API access for enterprises and individual developers. The platform supports popular mainstream models such as Claude, OpenAI, and Gemini, with prices as low as 7% of the official price. Register through this project's <a href="https://apikey.fun/register?aff=CLIProxyAPI">exclusive link</a> to enjoy a special <b>permanent 5% top-up discount</b>.</td>
4242
</tr>
43+
<tr>
44+
<td width="180"><a href="https://runapi.co/register?aff=FivD"><img src="./assets/runapi.png" alt="RunAPI" width="150"></a></td>
45+
<td>RunAPI is an efficient and stable API platform—an alternative to OpenRouter. A single API Key gives you access to 150+ leading models, including OpenAI, Claude, Gemini, DeepSeek, Grok, and more, at prices as low as 10% of the original (up to 90% off), with exceptional stability. It's seamlessly compatible with tools like Claude Code, OpenClaw, and others. RunAPI offers an exclusive perk for CPA users: <a href="https://runapi.co/register?aff=FivD">register</a> and contact an administrator to claim ¥7 in free credit.</td>
46+
</tr>
47+
<tr>
48+
<td width="180"><a href="https://unity2.ai/register?source=cliproxyapi"><img src="./assets/unity2.jpg" alt="Unity2" width="150"></a></td>
49+
<td>Thanks to Unity2.ai for sponsoring this project! Unity2.ai is a high-performance AI model API relay platform for individual developers, teams, and enterprises. It has long served leading domestic enterprises, handles more than 30 billion token calls per day, and supports high concurrency at the 5000 RPM level. It supports balance billing, first top-up bonuses, bundled subscriptions, enterprise invoicing, and dedicated integration support. Register through <a href="https://unity2.ai/register?source=cliproxyapi">this link</a> to receive a $2 balance, then join the official group to get another $10 balance, for up to $12 in free credit.</td>
50+
</tr>
4351
</tbody>
4452
</table>
4553

README_CN.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ VisionCoder 还为我们的用户提供 <a href="https://coder.visioncoder.cn" t
4040
<td width="180"><a href="https://apikey.fun/register?aff=CLIProxyAPI"><img src="./assets/apikey.png" alt="APIKEY.FUN" width="150"></a></td>
4141
<td>感谢 APIKEY.FUN 赞助本项目!APIKEY.FUN 是一家专业的企业级 AI 中转站,致力于为企业和个人开发者提供稳定、高效、低成本的 AI 模型 API 接入服务。平台支持 Claude、OpenAI、Gemini 等主流热门模型,价格低至官方原价的 7%。通过本项目<a href="https://apikey.fun/register?aff=CLIProxyAPI">专属链接</a>注册,还可享受最高 <b>充值永久 95 折</b> 专属优惠。</td>
4242
</tr>
43+
<tr>
44+
<td width="180"><a href="https://runapi.co/register?aff=FivD"><img src="./assets/runapi.png" alt="RunAPI" width="150"></a></td>
45+
<td>RunAPI 是高效稳定的API OpenRouter平替平台,一个 API Key 即可访问 OpenAI、Claude、Gemini、DeepSeek、Grok 等 150+ 主流模型,低至 1 折,极其稳定,可以无缝兼容 Claude Code、OpenClaw 等工具。RunAPI 为 CPA的用户提供专属福利:<a href="https://runapi.co/register?aff=FivD">注册</a>联系管理员即可领取¥7的免费额度</td>
46+
</tr>
47+
<tr>
48+
<td width="180"><a href="https://unity2.ai/register?source=cliproxyapi"><img src="./assets/unity2.jpg" alt="Unity2" width="150"></a></td>
49+
<td>感谢 Unity2.ai 赞助了本项目!Unity2.ai 是面向个人开发者、团队和企业的高性能 AI 模型 API 中转平台,长期服务国内头部企业,日均承载超 300 亿 token 调用,支持 5000 RPM 级高并发。支持余额计费、首充赠额、组合订阅、企业开票和专属对接。通过<a href="https://unity2.ai/register?source=cliproxyapi">此链接</a>注册可领取 $2 余额,加入官方群再送 $10 余额,最高可领 $12 免费额度。</td>
50+
</tr>
4351
</tbody>
4452
</table>
4553

README_JA.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ PackyCodeは当ソフトウェアのユーザーに特別割引を提供して
3838
<td width="180"><a href="https://apikey.fun/register?aff=CLIProxyAPI"><img src="./assets/apikey.png" alt="APIKEY.FUN" width="150"></a></td>
3939
<td>APIKEY.FUNのスポンサーシップに感謝します!APIKEY.FUNはプロフェッショナルなエンタープライズ向けAIリレーサービスで、企業および個人開発者に安定・高効率・低コストなAIモデルAPI接続サービスを提供しています。Claude、OpenAI、Geminiなどの主要人気モデルに対応し、価格は公式価格の7%から利用できます。本プロジェクトの<a href="https://apikey.fun/register?aff=CLIProxyAPI">専用リンク</a>から登録すると、さらに<b>チャージが永続的に5%割引</b>となる特別優待を受けられます。</td>
4040
</tr>
41+
<tr>
42+
<td width="180"><a href="https://runapi.co/register?aff=FivD"><img src="./assets/runapi.png" alt="RunAPI" width="150"></a></td>
43+
<td>RunAPIは高効率で安定したAPIプラットフォームで、OpenRouterの代替として利用できます。1つのAPI KeyでOpenAI、Claude、Gemini、DeepSeek、Grokなど150以上の主要モデルにアクセスでき、価格は公式価格の10%から、非常に安定しており、Claude Code、OpenClawなどのツールとシームレスに互換性があります。RunAPIはCPAユーザー向けに特別特典を提供しています:<a href="https://runapi.co/register?aff=FivD">登録</a>後に管理者へ連絡すると、7元分の無料クレジットを受け取れます。</td>
44+
</tr>
45+
<tr>
46+
<td width="180"><a href="https://unity2.ai/register?source=cliproxyapi"><img src="./assets/unity2.jpg" alt="Unity2" width="150"></a></td>
47+
<td>Unity2.aiのスポンサーシップに感謝します!Unity2.aiは、個人開発者、チーム、企業向けの高性能AIモデルAPIリレープラットフォームです。国内の大手企業に長期的にサービスを提供し、1日あたり300億tokenを超える呼び出しを処理し、5000 RPM級の高同時実行に対応しています。残高課金、初回チャージ特典、組み合わせサブスクリプション、企業向け請求書発行、専任サポートに対応しています。<a href="https://unity2.ai/register?source=cliproxyapi">こちらのリンク</a>から登録すると$2の残高を受け取れ、公式グループに参加するとさらに$10の残高が付与され、最大$12の無料クレジットを受け取れます。</td>
48+
</tr>
4149
</tbody>
4250
</table>
4351

assets/runapi.png

12.8 KB
Loading

assets/unity2.jpg

54.4 KB
Loading

cmd/server/main.go

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import (
2525
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
2626
"github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset"
2727
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
28+
"github.com/router-for-me/CLIProxyAPI/v7/internal/pluginhost"
2829
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
2930
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
31+
"github.com/router-for-me/CLIProxyAPI/v7/internal/safemode"
3032
"github.com/router-for-me/CLIProxyAPI/v7/internal/store"
3133
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
3234
"github.com/router-for-me/CLIProxyAPI/v7/internal/tui"
@@ -51,6 +53,16 @@ func init() {
5153
buildinfo.BuildDate = BuildDate
5254
}
5355

56+
func shouldStartExampleAPIKeyWarningServer(cfg *config.Config, commandMode, tuiMode, standalone, cloudConfigMissing, homeMode bool) bool {
57+
if cfg == nil || commandMode || homeMode || cloudConfigMissing {
58+
return false
59+
}
60+
if tuiMode && !standalone {
61+
return false
62+
}
63+
return safemode.HasExampleAPIKeys(cfg.APIKeys)
64+
}
65+
5466
// main is the entry point of the application.
5567
// It parses command-line flags, loads configuration, and starts the appropriate
5668
// service based on the provided flags (login, codex-login, or server mode).
@@ -128,6 +140,12 @@ func main() {
128140
})
129141
}
130142

143+
pluginHost := pluginhost.New()
144+
if bootstrapCfg := loadPluginBootstrapConfig(pluginBootstrapConfigPath(os.Args[1:], DefaultConfigPath)); bootstrapCfg != nil {
145+
pluginHost.ApplyConfig(context.Background(), bootstrapCfg)
146+
pluginHost.RegisterCommandLineFlags(context.Background(), flag.CommandLine)
147+
}
148+
131149
// Parse the command-line flags.
132150
flag.Parse()
133151

@@ -514,6 +532,16 @@ func main() {
514532
CallbackPort: oauthCallbackPort,
515533
}
516534

535+
commandMode := vertexImport != "" || login || antigravityLogin || codexLogin || codexDeviceLogin || claudeLogin || kimiLogin || xaiLogin
536+
cloudConfigMissing := isCloudDeploy && !configFileExists
537+
homeMode := configLoadedFromHome || (cfg != nil && cfg.Home.Enabled)
538+
if shouldStartExampleAPIKeyWarningServer(cfg, commandMode, tuiMode, standalone, cloudConfigMissing, homeMode) {
539+
matches := safemode.ExampleAPIKeys(cfg.APIKeys)
540+
log.WithField("api_keys", strings.Join(matches, ",")).Error("unsafe example API key configured; starting warning-only server")
541+
cmd.StartExampleAPIKeyWarningServer(cfg, configFilePath, matches)
542+
return
543+
}
544+
517545
// Register the shared token store once so all components use the same persistence backend.
518546
if usePostgresStore {
519547
sdkAuth.RegisterTokenStore(pgStoreInst)
@@ -527,6 +555,15 @@ func main() {
527555

528556
// Register built-in access providers before constructing services.
529557
configaccess.Register(&cfg.SDKConfig)
558+
pluginHost.ApplyConfig(context.Background(), cfg)
559+
if pluginHost.HasTriggeredCommandLineFlags() {
560+
if exitCode, handled := pluginHost.ExecuteCommandLine(context.Background(), os.Args[0], os.Args[1:], configFilePath, flag.CommandLine); handled {
561+
if exitCode != 0 {
562+
os.Exit(exitCode)
563+
}
564+
return
565+
}
566+
}
530567

531568
// Handle different command modes based on the provided flags.
532569

@@ -604,7 +641,7 @@ func main() {
604641
password = localMgmtPassword
605642
}
606643

607-
cancel, done := cmd.StartServiceBackground(cfg, configFilePath, password)
644+
cancel, done := cmd.StartServiceBackgroundWithPluginHost(cfg, configFilePath, password, pluginHost)
608645

609646
client := tui.NewClient(cfg.Port, password)
610647
ready := false
@@ -653,7 +690,63 @@ func main() {
653690
} else if cfg.Home.Enabled {
654691
log.Info("Home mode: remote model updates disabled")
655692
}
656-
cmd.StartService(cfg, configFilePath, password)
693+
cmd.StartServiceWithPluginHost(cfg, configFilePath, password, pluginHost)
657694
}
658695
}
659696
}
697+
698+
func pluginBootstrapConfigPath(args []string, defaultPath string) string {
699+
for i := 0; i < len(args); i++ {
700+
arg := args[i]
701+
switch {
702+
case arg == "--":
703+
return defaultPluginBootstrapConfigPath(defaultPath)
704+
case arg == "-config" || arg == "--config":
705+
if i+1 < len(args) {
706+
return args[i+1]
707+
}
708+
return defaultPluginBootstrapConfigPath(defaultPath)
709+
case strings.HasPrefix(arg, "-config="):
710+
return strings.TrimPrefix(arg, "-config=")
711+
case strings.HasPrefix(arg, "--config="):
712+
return strings.TrimPrefix(arg, "--config=")
713+
}
714+
}
715+
return defaultPluginBootstrapConfigPath(defaultPath)
716+
}
717+
718+
func defaultPluginBootstrapConfigPath(defaultPath string) string {
719+
if strings.TrimSpace(defaultPath) != "" {
720+
return defaultPath
721+
}
722+
wd, errGetwd := os.Getwd()
723+
if errGetwd != nil {
724+
return "config.yaml"
725+
}
726+
return filepath.Join(wd, "config.yaml")
727+
}
728+
729+
func loadPluginBootstrapConfig(path string) *config.Config {
730+
raw, errReadFile := os.ReadFile(path)
731+
if errReadFile != nil {
732+
if !errors.Is(errReadFile, os.ErrNotExist) {
733+
log.Warnf("failed to read plugin bootstrap config: %v", errReadFile)
734+
}
735+
cfg := &config.Config{}
736+
cfg.NormalizePluginsConfig()
737+
return cfg
738+
}
739+
if len(strings.TrimSpace(string(raw))) == 0 {
740+
cfg := &config.Config{}
741+
cfg.NormalizePluginsConfig()
742+
return cfg
743+
}
744+
cfg, errParseConfig := config.ParseConfigBytes(raw)
745+
if errParseConfig != nil {
746+
log.Warnf("failed to parse plugin bootstrap config: %v", errParseConfig)
747+
cfg = &config.Config{}
748+
cfg.NormalizePluginsConfig()
749+
return cfg
750+
}
751+
return cfg
752+
}

0 commit comments

Comments
 (0)