You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/09/04 15:12:20 UTC
(casbin-gateway) branch master updated (1c61862 -> 4f2be9a)
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
from 1c61862 feat(permissions): extend hook enforcement to hermes-agent and windsurf
new bf5572f feat(providers): import a New API channel by pasting its connection info
new 1b1bcb7 fix(web): expand every settings section by default
new 4f2be9a fix(providers): name this gateway in the User-Agent, not Go's default
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
controllers/proxy.go | 4 +--
object/import_link.go | 9 +++++
object/provider_link.go | 55 +++++++++++++++++++++++++++++
proxy/proxy.go | 32 ++++++++++++++---
web/src/components/ProviderSourcePicker.tsx | 10 +++---
web/src/locales/en/data.json | 6 ++--
web/src/locales/zh/data.json | 6 ++--
web/src/pages/SettingPage.tsx | 4 +--
8 files changed, 107 insertions(+), 19 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org
(casbin-gateway) 03/03: fix(providers): name this gateway in the User-Agent, not Go's default
Posted by hs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit 4f2be9ac4618d3b9675e4707f9c6cf07f5f8f25c
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Fri Sep 4 23:12:09 2026 +0800
fix(providers): name this gateway in the User-Agent, not Go's default
---
controllers/proxy.go | 4 ++--
proxy/proxy.go | 32 ++++++++++++++++++++++++++++----
2 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/controllers/proxy.go b/controllers/proxy.go
index 127b11b..ab0c21b 100644
--- a/controllers/proxy.go
+++ b/controllers/proxy.go
@@ -182,7 +182,7 @@ type routingFields struct {
// It has no overall Timeout on purpose, see proxyResponseHeaderTimeout.
var proxyClient = &http.Client{
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
- Transport: &http.Transport{
+ Transport: proxy.WithUserAgent(&http.Transport{
Proxy: proxy.Proxy,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
@@ -194,7 +194,7 @@ var proxyClient = &http.Client{
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: proxyResponseHeaderTimeout,
- },
+ }),
}
// ChatCompletions is the OpenAI-compatible chat completions proxy endpoint.
diff --git a/proxy/proxy.go b/proxy/proxy.go
index 27bfab1..ea165ce 100644
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -37,9 +37,14 @@ var (
proxyUrl *url.URL
transportOnce sync.Once
- transport *http.Transport
+ transport http.RoundTripper
)
+// UserAgent identifies this gateway to the upstreams it calls. Go leaves the
+// header at "Go-http-client/1.1", which the bot filters in front of several
+// relays refuse outright with a 403 and an HTML error page.
+const UserAgent = "casbin-gateway"
+
func InitHttpClient() {
// Reading the setting here rather than on first use keeps the "proxy
// enabled" line in the startup log.
@@ -63,14 +68,33 @@ func Proxy(req *http.Request) (*url.URL, error) {
// Transport returns the shared transport for outbound requests. Sharing one
// instance keeps the connection pool shared as well.
-func Transport() *http.Transport {
+func Transport() http.RoundTripper {
transportOnce.Do(func() {
- transport = http.DefaultTransport.(*http.Transport).Clone()
- transport.Proxy = Proxy
+ base := http.DefaultTransport.(*http.Transport).Clone()
+ base.Proxy = Proxy
+ transport = WithUserAgent(base)
})
return transport
}
+// WithUserAgent names this gateway on every request that does not already
+// carry a User-Agent of its own, so a request forwarding the caller's keeps it.
+func WithUserAgent(base http.RoundTripper) http.RoundTripper {
+ return userAgentTransport{base: base}
+}
+
+type userAgentTransport struct {
+ base http.RoundTripper
+}
+
+func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ if _, ok := req.Header["User-Agent"]; !ok {
+ req = req.Clone(req.Context())
+ req.Header.Set("User-Agent", UserAgent)
+ }
+ return t.base.RoundTrip(req)
+}
+
// currentProxyUrl parses the httpProxy setting, re-parsing it only when the
// setting itself has changed.
func currentProxyUrl() *url.URL {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org
(casbin-gateway) 02/03: fix(web): expand every settings section by default
Posted by hs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit 1b1bcb767687293f7e71dab6351020ae664736bb
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Fri Sep 4 23:09:12 2026 +0800
fix(web): expand every settings section by default
---
web/src/pages/SettingPage.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/SettingPage.tsx b/web/src/pages/SettingPage.tsx
index a4fc634..800ade9 100644
--- a/web/src/pages/SettingPage.tsx
+++ b/web/src/pages/SettingPage.tsx
@@ -205,7 +205,7 @@ export default function SettingPage({account}: {account: Account}) {
{numberField("agentMonitorPollSeconds", i18next.t("setting:Agent poll seconds"), undefined, 1)}
</Section>
- <Section id="signin" columns={2} collapsible title={i18next.t("setting:Sign-in")} description={i18next.t("setting:Sign-in description")}>
+ <Section id="signin" columns={2} title={i18next.t("setting:Sign-in")} description={i18next.t("setting:Sign-in description")}>
{textField("casdoorEndpoint", i18next.t("setting:Casdoor endpoint"))}
{textField("clientId", i18next.t("setting:Client ID"))}
{secretField("clientSecret", i18next.t("setting:Client secret"))}
@@ -218,7 +218,7 @@ export default function SettingPage({account}: {account: Account}) {
{textField("relayToken", i18next.t("setting:Relay token"), i18next.t("setting:Relay token hint"))}
</Section>
- <Section id="network" columns={2} collapsible title={i18next.t("setting:Network")}>
+ <Section id="network" columns={2} title={i18next.t("setting:Network")}>
{textField("httpProxy", i18next.t("setting:Outbound SOCKS5 proxy"), i18next.t("setting:Outbound SOCKS5 proxy hint"))}
{textField("allowedHosts", i18next.t("setting:Allowed hosts"), i18next.t("setting:Allowed hosts hint"))}
{textField("allowedOrigins", i18next.t("setting:Allowed origins"), i18next.t("setting:Allowed origins hint"))}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org
(casbin-gateway) 01/03: feat(providers): import a New API channel by pasting its connection info
Posted by hs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit bf5572fa203e5d258d493dda92719836f90a52c1
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Fri Sep 4 22:51:32 2026 +0800
feat(providers): import a New API channel by pasting its connection info
---
object/import_link.go | 9 +++++
object/provider_link.go | 55 +++++++++++++++++++++++++++++
web/src/components/ProviderSourcePicker.tsx | 10 +++---
web/src/locales/en/data.json | 6 ++--
web/src/locales/zh/data.json | 6 ++--
5 files changed, 75 insertions(+), 11 deletions(-)
diff --git a/object/import_link.go b/object/import_link.go
index bd9f3a0..e909ccc 100644
--- a/object/import_link.go
+++ b/object/import_link.go
@@ -110,6 +110,15 @@ type SkillImport struct {
// balance with, and the flags that would activate what was imported — Gateway
// asks the reader that instead.
func ParseImportLink(owner string, raw string) (*ImportLink, error) {
+ // New API sites hand out connection info as JSON rather than a link; it is
+ // pasted into the same box, so it is read here.
+ if provider, isChannel, err := providerFromNewApiChannel(owner, raw); isChannel {
+ if err != nil {
+ return nil, err
+ }
+ return &ImportLink{Resource: ImportResourceProvider, Provider: provider}, nil
+ }
+
query, err := importLinkQuery(raw)
if err != nil {
return nil, err
diff --git a/object/provider_link.go b/object/provider_link.go
index e35093b..2b5a36c 100644
--- a/object/provider_link.go
+++ b/object/provider_link.go
@@ -15,6 +15,7 @@
package object
import (
+ "encoding/json"
"fmt"
"net/url"
"strings"
@@ -108,3 +109,57 @@ func linkModels(query url.Values) []string {
}
return models
}
+
+// NewApiChannelType marks the connection info a New API site copies out of its
+// channel page. It is JSON rather than a link, and carries only these two
+// fields, so it is read where a link is pasted instead of getting a box of its
+// own.
+const NewApiChannelType = "newapi_channel_conn"
+
+type newApiChannel struct {
+ Type string `json:"_type"`
+ Key string `json:"key"`
+ Url string `json:"url"`
+}
+
+// providerFromNewApiChannel reads that connection info. The second value says
+// whether the text was this format at all, so text that is not falls through to
+// being read as a link.
+func providerFromNewApiChannel(owner string, raw string) (*Provider, bool, error) {
+ value := strings.TrimSpace(raw)
+ if !strings.HasPrefix(value, "{") {
+ return nil, false, nil
+ }
+
+ var channel newApiChannel
+ if err := json.Unmarshal([]byte(value), &channel); err != nil {
+ return nil, true, fmt.Errorf("this is neither a link nor JSON: %w", err)
+ }
+ if channel.Type != NewApiChannelType {
+ return nil, true, fmt.Errorf("this JSON is not New API connection info: %q", channel.Type)
+ }
+
+ site := strings.TrimRight(strings.TrimSpace(channel.Url), "/")
+ parsed, err := url.Parse(site)
+ if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" {
+ return nil, true, fmt.Errorf("this connection info does not name a site: %q", channel.Url)
+ }
+ key := strings.TrimSpace(channel.Key)
+ if key == "" {
+ return nil, true, fmt.Errorf("this connection info carries no API key")
+ }
+
+ return &Provider{
+ Owner: owner,
+ DisplayName: strings.TrimPrefix(parsed.Host, "www."),
+ // A New API site resells every vendor behind one OpenAI-compatible
+ // endpoint, so the models are whatever that one site sells.
+ Type: "custom",
+ Status: "enabled",
+ AuthMode: ProviderAuthProvider,
+ BaseUrl: site,
+ ApiKey: key,
+ Models: []string{},
+ Icon: site,
+ }, true, nil
+}
diff --git a/web/src/components/ProviderSourcePicker.tsx b/web/src/components/ProviderSourcePicker.tsx
index 7d55124..a7bbf77 100644
--- a/web/src/components/ProviderSourcePicker.tsx
+++ b/web/src/components/ProviderSourcePicker.tsx
@@ -99,7 +99,7 @@ export function ProviderSourcePicker({
onLink,
}: {
onPick: (source: ProviderSource) => void;
- /** A vendor's own "add this provider" link, pasted rather than typed out. */
+ /** A vendor's "add this provider" link, or New API connection info. */
onLink?: (link: string) => Promise<void>;
}) {
const [query, setQuery] = React.useState("");
@@ -148,7 +148,7 @@ export function ProviderSourcePicker({
<div className="grid gap-1.5 rounded-lg border border-dashed p-3">
<span className="flex items-center gap-2 text-sm font-medium">
<Link2 className="size-4" />
- {i18next.t("provider:Paste a provider link")}
+ {i18next.t("provider:Paste a link or connection info")}
</span>
<div className="flex flex-wrap gap-2">
<Input
@@ -160,15 +160,15 @@ export function ProviderSourcePicker({
importLink();
}
}}
- placeholder="ccswitch://v1/import?resource=provider&..."
+ placeholder={"ccswitch://v1/import?... or {\"_type\":\"newapi_channel_conn\",...}"}
className="min-w-0 flex-1"
/>
<Button type="button" variant="outline" loading={importing} onClick={importLink}>
- {i18next.t("provider:Read the link")}
+ {i18next.t("provider:Read it")}
</Button>
</div>
<span className="text-muted-foreground text-xs">
- {i18next.t("provider:Paste a provider link hint")}
+ {i18next.t("provider:Paste a link or connection info hint")}
</span>
</div>
)}
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index d16564b..18666e2 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -1105,8 +1105,8 @@
"Notes hint": "For whoever reads this later: which account the key belongs to, when it expires, who pays for it.",
"Nothing to test": "There is nothing to test yet",
"Page description": "One endpoint in front of your model vendors. Gateway holds the API key, so the agents never have it — or forwards the agent's own sign-in and holds nothing.",
- "Paste a provider link": "Paste a provider link",
- "Paste a provider link hint": "Vendors publish these as \"add to CC Switch\" buttons. Gateway reads the same link and fills the form in below, and stores nothing until you confirm.",
+ "Paste a link or connection info": "Paste a link or connection info",
+ "Paste a link or connection info hint": "Vendors publish these as \"add to CC Switch\" buttons, and a New API site copies its connection info out as JSON. Gateway reads either one and fills the form in below, and stores nothing until you confirm.",
"Path hint": "A dotted path into the answer, where a number indexes an array: balance_infos.0.total_balance.",
"Plan usage": "Plan usage",
"Preset": "Preset",
@@ -1119,7 +1119,7 @@
"Provider not found": "Provider not found",
"Provider saved": "Provider saved",
"Providers": "Providers",
- "Read the link": "Read the link",
+ "Read it": "Read it",
"Recovering": "Recovering",
"Refresh balances": "Refresh balances",
"Remaining": "Remaining",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index e4cd473..40621d0 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -1177,8 +1177,8 @@
"Notes hint": "写给以后的自己:这个密钥属于哪个账号、什么时候过期、由谁付费。",
"Nothing to test": "暂时没有可探测的内容",
"Page description": "挡在模型厂商前面的统一入口。API Key 由 Gateway 保管,Agent 拿不到;也可以转发 Agent 自己的登录,什么都不保管。",
- "Paste a provider link": "粘贴供应商链接",
- "Paste a provider link hint": "中转站通常把它做成“添加到 CC Switch”的按钮。Gateway 读的是同一种链接,读完只会把下面的表单填好,你确认之前不会保存任何东西。",
+ "Paste a link or connection info": "粘贴链接或连接信息",
+ "Paste a link or connection info hint": "中转站通常把它做成“添加到 CC Switch”的按钮,New API 站点则是把连接信息复制成一段 JSON。两种 Gateway 都认,读完只会把下面的表单填好,你确认之前不会保存任何东西。",
"Path hint": "返回内容里的点分路径,数字表示数组下标:balance_infos.0.total_balance。",
"Plan usage": "套餐用量",
"Preset": "预设",
@@ -1191,7 +1191,7 @@
"Provider not found": "供应商不存在",
"Provider saved": "供应商已保存",
"Providers": "供应商",
- "Read the link": "读取链接",
+ "Read it": "读取",
"Recovering": "恢复中",
"Refresh balances": "刷新余额",
"Remaining": "余额",
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org