gateway: ClinePass's DeepSeek models can be pinned to DeepSeek's own API (White Immortal on Discord: clinepass 针对deepseek的模型能否提供一键钉住deepseek官方上游) — Cline's AI gateway serves a model from whichever host it picks, so a DeepSeek model's prompt cache was hit or missed by chance. A Cline provider's editor now has Upstream: DeepSeek models only from DeepSeek's own API (provider.PinUpstream, off by default; zh, ja, de strings); on, every Chat request to it for a DeepSeek model (cline-pass/deepseek-…, not Cline's free ones) carries providerOptions.gateway.only ["deepseek"], as ClinePass + ProviderPin sets it (linux.do/t/topic/2918145), added at the body's end so the cached prefix doesn't change, also when built from a Messages or Responses request. A body naming providerOptions itself goes as sent. TestClinePinsDeepSeek fails on the old code (no providerOptions on chat, messages, responses); clinepass-pin.test.cjs (WebKit, en and zh). provider-price-rate and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with a real ClinePass key (none on this machine).

This commit is contained in:
yetone committed 2026-10-05 08:58:50 +08:00
1 parent 3ec6b4da27
commit 1b025b3fad
9 files changed
+256 -2

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
package gateway
import (
"bytes"
"encoding/json"
"github.com/tidwall/gjson"
"github.com/yetone/magpie/internal/provider"
)
// clinePin is body with its DeepSeek model pinned to DeepSeek's own API
// on the Cline API, when the provider asks it (provider.PinUpstream):
// providerOptions.gateway.only, which Cline's AI gateway reads to serve
// the model from that host alone, as ClinePass + ProviderPin set it
// (linux.do/t/topic/2918145). A body naming providerOptions itself is
// left as the client sent it. The field goes at the end, so the prefix a
// cache keys on doesn't change.
func clinePin(p provider.Provider, to provider.Protocol, body []byte) []byte {
if to != provider.Chat {
return body
}
up := p.ClinePin(gjson.GetBytes(body, "model").String())
if up == "" || gjson.GetBytes(body, "providerOptions").Exists() {
return body
}
end := bytes.LastIndexByte(body, '}')
if end < 0 {
return body
}
opts, _ := json.Marshal(map[string]any{"gateway": map[string]any{"only": []string{up}}})
out := make([]byte, 0, len(body)+len(opts)+24)
out = append(out, body[:end]...)
if len(bytes.TrimSpace(body[bytes.IndexByte(body, '{')+1:end])) > 0 {
out = append(out, ',')
}
out = append(out, `"providerOptions":`...)
out = append(out, opts...)
return append(out, body[end:]...)
}
+80
View File
@@ -0,0 +1,80 @@
package gateway
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/tidwall/gjson"
"github.com/yetone/magpie/internal/provider"
)
// ClinePass's DeepSeek models are served by DeepSeek's own API alone when
// the provider pins them (White Immortal on Discord, as ClinePass +
// ProviderPin does): each request for one carries
// providerOptions.gateway.only ["deepseek"], on Chat and when an Anthropic
// or Responses request is built into one. Its other models, Cline's free
// ones, a provider that doesn't pin, and a body naming providerOptions
// itself go as they were.
func TestClinePinsDeepSeek(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())
cline, err := provider.FromPreset("clinepass")
if err != nil {
t.Fatal(err)
}
cline.ID, cline.Key, cline.PinUpstream = "cline", "clp_key", true
cline.Models = []string{"cline-pass/deepseek-v4-pro", "cline-pass/glm-5.3", "cline-free/deepseek-v4.1-flash"}
plain := cline
plain.ID, plain.Name, plain.PinUpstream = "cline2", "ClinePass 2", false
for _, p := range []provider.Provider{cline, plain} {
if err := provider.Save(p); err != nil {
t.Fatal(err)
}
}
var sent []byte
s := New()
s.client = &http.Client{Transport: countTransport(func(r *http.Request) (*http.Response, error) {
sent, _ = io.ReadAll(r.Body)
if gjson.GetBytes(sent, "stream").Bool() {
return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"text/event-stream"}},
Body: io.NopCloser(strings.NewReader("data: {\"id\":\"c\",\"object\":\"chat.completion.chunk\",\"model\":\"m\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"pong\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n"))}, nil
}
return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader(`{"id":"c","object":"chat.completion","model":"m","choices":[{"index":0,"message":{"role":"assistant","content":"pong"},"finish_reason":"stop"}]}`))}, nil
})}
ask := func(path, body string) string {
t.Helper()
sent = nil
req := httptest.NewRequest("POST", path, strings.NewReader(body))
rec := httptest.NewRecorder()
s.Handler().ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("%s %s: %d %s", path, body, rec.Code, rec.Body)
}
return gjson.GetBytes(sent, "providerOptions").Raw
}
chat := func(model, extra string) string {
return ask("/v1/chat/completions", `{"model":"`+model+`","messages":[{"role":"user","content":"Say pong"}]`+extra+`}`)
}
const pinned = `{"gateway":{"only":["deepseek"]}}`
if got := chat("cline/cline-pass/deepseek-v4-pro", ""); got != pinned {
t.Errorf("chat: providerOptions %s, want %s (sent %s)", got, pinned, sent)
}
if got := ask("/v1/messages", `{"model":"cline/cline-pass/deepseek-v4-pro","max_tokens":64,"messages":[{"role":"user","content":"Say pong"}]}`); got != pinned {
t.Errorf("messages: providerOptions %s, want %s", got, pinned)
}
if got := ask("/v1/responses", `{"model":"cline/cline-pass/deepseek-v4-pro","input":"Say pong","stream":false}`); got != pinned {
t.Errorf("responses: providerOptions %s, want %s", got, pinned)
}
for _, m := range []string{"cline/cline-pass/glm-5.3", "cline/cline-free/deepseek-v4.1-flash", "cline2/cline-pass/deepseek-v4-pro"} {
if got := chat(m, ""); got != "" {
t.Errorf("%s: providerOptions %s, want none", m, got)
}
}
if got := chat("cline/cline-pass/deepseek-v4-pro", `,"providerOptions":{"gateway":{"order":["novita"]}}`); got != `{"gateway":{"order":["novita"]}}` {
t.Errorf("the client's own providerOptions became %s", got)
}
}
+1
View File
@@ -2225,6 +2225,7 @@ func (s *Server) forwardOnce(ctx context.Context, p provider.Provider, to provid
ctx = p.Via(ctx)
body = deepseekToolPatterns(p, to, body)
body = toolOneOfAsAnyOf(p, to, body)
body = clinePin(p, to, body)
if to == provider.Anthropic {
body = s.bodyBetas(p, body)
body = s.withoutRefusedShapes(p, body)
+12 -1
View File
@@ -6486,7 +6486,7 @@ function pickedOf(p) {
// draftOf is a saved provider as its editor's form holds it.
function draftOf(p) {
return { id: p.id, name: p.name, preset: p.preset, chat: p.chat, responses: p.responses, anthropic: p.anthropic, catalog: p.catalog, decide: p.decide || "", key: "", api: p.chat ? "openai" : p.anthropic ? "anthropic" : p.responses ? "responses" : p.decide ? "decide" : "openai", chosen: pickedOf(p), extra: [], headers: headerRows(p.headers), icon: p.icon || "", fallback: [...(p.fallback || [])], unlisted: !!p.unlisted, searches: !!p.searches, balanceURL: p.balanceURL || "", balancePath: p.balancePath || "", modelsURL: p.modelsURL || "", contexts: contextsText(p.contexts), outputs: contextsText(p.outputs), keysUrl: p.keysUrl || "", ...proxyDraft(p), ...concurrencyDraft(p) };
return { id: p.id, name: p.name, preset: p.preset, chat: p.chat, responses: p.responses, anthropic: p.anthropic, catalog: p.catalog, decide: p.decide || "", key: "", api: p.chat ? "openai" : p.anthropic ? "anthropic" : p.responses ? "responses" : p.decide ? "decide" : "openai", chosen: pickedOf(p), extra: [], headers: headerRows(p.headers), icon: p.icon || "", fallback: [...(p.fallback || [])], unlisted: !!p.unlisted, searches: !!p.searches, pinUpstream: !!p.pinUpstream, balanceURL: p.balanceURL || "", balancePath: p.balancePath || "", modelsURL: p.modelsURL || "", contexts: contextsText(p.contexts), outputs: contextsText(p.outputs), keysUrl: p.keysUrl || "", ...proxyDraft(p), ...concurrencyDraft(p) };
}
// duplicateProvider opens the Add form on a copy of p (#268): its URLs,
@@ -6804,6 +6804,16 @@ function drawEditor(p, presetID) {
ed.append(...concurrencyField(p));
ed.append(...priceRateField());
// the Cline API serves a model from whichever host its AI gateway picks;
// pinned, its DeepSeek models are served by DeepSeek's own API alone,
// which keeps their prompt cache (White Immortal on Discord)
if (p?.cline || (isNew && pr?.id === "clinepass")) {
const [ptk, pcb] = tick(t("DeepSeek models only from DeepSeek's own API"), !!draft.pinUpstream);
ptk.classList.add("pin-upstream");
pcb.onchange = () => { draft.pinUpstream = pcb.checked; };
ed.append(...field(t("Upstream"), ptk, t("Cline serves a model from any host its gateway picks; pinned, a request for a DeepSeek model goes only to DeepSeek, which keeps its prompt cache, and fails when DeepSeek can't take it")));
}
// a relay in front of Anthropic's or OpenAI's API searches the web as
// they do, which magpie can't tell from its host (#359): a client's web
// search goes to it as sent, rather than through magpie's own. Only its
@@ -7056,6 +7066,7 @@ function drawEditor(p, presetID) {
if (custom) { body.icon = draft.icon || "generic"; body.balanceURL = (draft.balanceURL || "").trim(); body.balancePath = (draft.balancePath || "").trim(); body.modelsURL = (draft.modelsURL || "").trim(); }
if (p) { body.fallback = draft.fallback; body.unlisted = draft.unlisted; body.modelPrefs = modelPrefsOfDraft(); Object.assign(body, routingOfDraft(p)); }
body.searches = !!draft.searches && searchable();
body.pinUpstream = !!draft.pinUpstream;
const cx = parseContexts(draft.contexts || "");
if (cx.error) return editorError(t("Context window: {v} is not a length like 128k or 1m", { v: cx.error }), "warn");
body.contexts = cx.map;
+9
View File
@@ -2682,6 +2682,9 @@ const I18N = {
"Only through routing groups": "只通过路由分组使用",
"Web search": "联网搜索",
"Searches the web by itself": "自己能联网搜索",
"DeepSeek models only from DeepSeek's own API": "DeepSeek 模型只走 DeepSeek 官方上游",
"Upstream": "上游",
"Cline serves a model from any host its gateway picks; pinned, a request for a DeepSeek model goes only to DeepSeek, which keeps its prompt cache, and fails when DeepSeek can't take it": "Cline 的网关会把一个模型发给它挑的任意上游;钉住后,DeepSeek 模型的请求只发给 DeepSeek 官方,缓存能命中;DeepSeek 官方接不了时请求会失败,不会换别家",
"For a relay in front of Anthropic's or OpenAI's own API: Claude Code's WebSearch and Codex's web_search go to it as they were sent, not through magpie's search. magpie doesn't automatically search with it for other models, but you can name it as the searcher.": "适用于转发 Anthropic 或 OpenAI 官方 API 的中转:Claude Code 的 WebSearch、Codex 的 web_search 原样发给它,不经过 magpie 的搜索。magpie 不会自动用它给其他模型搜索,但你可以在代搜供应商里手动选择它。",
"Its models leave the list agents pick from; the routing groups they are in still use them": "它的模型不出现在 Agent 的可选列表里;所在的路由分组仍会使用它们",
"Agents don't see them: only the routing groups they are in use them.": "Agent 看不到这些模型,只有它们所在的路由分组会用到。",
@@ -5730,6 +5733,9 @@ const I18N = {
"Drop the list fetched from the vendor; the models.dev one is used until Refresh": "ベンダーから取得したリストを破棄します。更新するまでは models.dev のリストを使います",
"Only through routing groups": "ルーティンググループ経由のみ",
"Searches the web by itself": "自分でウェブ検索する",
"DeepSeek models only from DeepSeek's own API": "DeepSeek モデルは DeepSeek 公式 API のみ",
"Upstream": "上流",
"Cline serves a model from any host its gateway picks; pinned, a request for a DeepSeek model goes only to DeepSeek, which keeps its prompt cache, and fails when DeepSeek can't take it": "Cline のゲートウェイはモデルを任意の上流に送ります。固定すると DeepSeek モデルへのリクエストは DeepSeek 公式だけに送られ、プロンプトキャッシュが効きます。DeepSeek が受けられないときは失敗します",
"For a relay in front of Anthropic's or OpenAI's own API: Claude Code's WebSearch and Codex's web_search go to it as they were sent, not through magpie's search. magpie doesn't automatically search with it for other models, but you can name it as the searcher.": "Anthropic または OpenAI 公式 API の前段にあるリレー向け:Claude Code の WebSearch と Codex の web_search は magpie の検索を通さず、送られたままこのプロバイダに届きます。magpie は他のモデルのためにこれで自動検索はしませんが、検索役として指定することはできます。",
"Its models leave the list agents pick from; the routing groups they are in still use them": "そのモデルはエージェントが選ぶリストから外れます。所属するルーティンググループでは引き続き使われます",
"Agents don't see them: only the routing groups they are in use them.": "エージェントには見えません。所属するルーティンググループだけが使います。",
@@ -8781,6 +8787,9 @@ const I18N = {
"Drop the list fetched from the vendor; the models.dev one is used until Refresh": "Die vom Anbieter abgerufene Liste verwerfen; bis zum Aktualisieren gilt die von models.dev",
"Only through routing groups": "Nur über Weiterleitungsgruppen",
"Searches the web by itself": "Sucht selbst im Web",
"DeepSeek models only from DeepSeek's own API": "DeepSeek-Modelle nur über DeepSeeks eigene API",
"Upstream": "Upstream",
"Cline serves a model from any host its gateway picks; pinned, a request for a DeepSeek model goes only to DeepSeek, which keeps its prompt cache, and fails when DeepSeek can't take it": "Cline lässt ein Modell von einem beliebigen Anbieter seines Gateways bedienen; angeheftet geht eine Anfrage an ein DeepSeek-Modell nur an DeepSeek, das seinen Prompt-Cache behält, und schlägt fehl, wenn DeepSeek sie nicht annimmt",
"For a relay in front of Anthropic's or OpenAI's own API: Claude Code's WebSearch and Codex's web_search go to it as they were sent, not through magpie's search. magpie doesn't automatically search with it for other models, but you can name it as the searcher.": "Für ein Relay vor der eigenen API von Anthropic oder OpenAI: WebSearch von Claude Code und web_search von Codex gehen unverändert dorthin, nicht über die Suche von magpie. magpie sucht damit nicht automatisch für andere Modelle, aber Sie können es als Suchdienst festlegen.",
"Its models leave the list agents pick from; the routing groups they are in still use them": "Seine Modelle verschwinden aus der Auswahlliste der Agenten; die Weiterleitungsgruppen, in denen sie sind, nutzen sie weiterhin",
"Agents don't see them: only the routing groups they are in use them.": "Agenten sehen sie nicht: Nur die Weiterleitungsgruppen, in denen sie sind, nutzen sie.",
+6 -1
View File
@@ -63,6 +63,10 @@ type providerJSON struct {
Headers map[string]string `json:"headers,omitempty"`
// the vendor searches the web by itself (provider.Searches)
Searches bool `json:"searches"`
// on the Cline API: its DeepSeek models are served by DeepSeek's own
// API alone (provider.PinUpstream), which only it offers
Cline bool `json:"cline,omitempty"`
PinUpstream bool `json:"pinUpstream"`
// the proxy its requests go through: "" the global one, "direct"
// none, or an address (#237)
Proxy string `json:"proxy"`
@@ -372,7 +376,7 @@ func providerInfo(p provider.Provider, agents []agentUse) providerJSON {
ID: p.ID, Name: p.Name, Icon: p.Icon, Preset: p.Preset, Host: p.Host(),
Chat: p.Chat, Responses: p.Responses, Anthropic: p.Anthropic, Decide: p.Decide, ModelTest: p.ModelTest(), DecideTest: p.AsksDecideModels(),
Catalog: p.Catalog, Website: p.Website, KeysURL: p.KeysURL,
Proxy: p.Proxy, AccountProxies: p.AccountProxies, AccountModels: p.AccountModels, AccountCaps: p.AccountCaps, Headers: p.Headers, Searches: p.Searches, BalanceURL: p.BalanceURL, BalancePath: p.BalancePath, ModelsURL: p.ModelsURL,
Proxy: p.Proxy, AccountProxies: p.AccountProxies, AccountModels: p.AccountModels, AccountCaps: p.AccountCaps, Headers: p.Headers, Searches: p.Searches, Cline: p.IsCline(), PinUpstream: p.PinUpstream, BalanceURL: p.BalanceURL, BalancePath: p.BalancePath, ModelsURL: p.ModelsURL,
Ready: p.Ready(), Chosen: p.Models, Models: []modelJSON{}, Agents: []providerAgent{},
Fallback: p.Fallback, Routing: p.Routing, Sink: p.Sink, Affinity: p.Affinity, KeepLogin: p.KeepLogin, KeepLoginAs: p.KeepLoginAs, Unlisted: p.Unlisted, Off: p.Off, Contexts: p.Contexts,
MaxConcurrency: p.MaxConcurrency, PluginConcurrency: p.PluginConcurrency(), PriceRate: p.PriceRate,
@@ -879,6 +883,7 @@ func providerRoutes(mux *http.ServeMux, w Windows) {
pr.Key, pr.Models, pr.Fallback, pr.Headers, pr.BalanceToken, pr.Contexts = in.Key, in.Models, in.Fallback, in.Headers, in.BalanceToken, in.Contexts
pr.ZhipuTeam = in.ZhipuTeam
pr.Searches = in.Searches
pr.PinUpstream = in.PinUpstream
if in.Name != "" {
pr.Name = in.Name
}
+89
View File
@@ -0,0 +1,89 @@
// Run with Node's test runner and Playwright on the module path; see README.md.
// ClinePass's DeepSeek models pinned to DeepSeek's own API (White Immortal
// on Discord): the editor of a Cline provider has the tick, as saved; Save
// posts pinUpstream as ticked. Another provider's editor has no such tick.
// In English and Chinese.
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { test } = require("node:test");
const { chromium, webkit } = require("playwright");
const assets = path.resolve(__dirname, "../assets");
const cline = {
id: "clinepass", name: "ClinePass", icon: "cline", preset: "clinepass", cline: true, pinUpstream: false,
chat: "https://api.cline.bot/api/v1", responses: "", anthropic: "", catalog: "",
models: [{ id: "cline-pass/deepseek-v4-pro", name: "DeepSeek V4 Pro", on: true }], agents: [], fallback: [], headers: {},
key: { set: true, masked: "clp_…one" }, keyList: [], balanceToken: { takes: false, set: false }, proxy: "", maxConcurrency: null,
};
const relay = {
id: "relay", name: "Relay", icon: "generic", chat: "https://relay.example.com/v1", responses: "", anthropic: "", catalog: "",
models: [{ id: "model-a", name: "Model A", on: true }], agents: [], fallback: [], headers: {},
key: { set: true, masked: "sk-…one" }, keyList: [], balanceToken: { takes: false, set: false }, proxy: "", maxConcurrency: null,
};
function serve(lang, posts) {
const providers = { providers: [cline, relay], presets: [], excluded: [], gateway: { running: true, window: true } };
const state = { agents: [], profiles: [], settings: { lang, theme: "light" } };
return async (route) => {
const url = new URL(route.request().url());
const json = (data) => route.fulfill({ json: data });
if (url.pathname === "/boot.js") return route.fulfill({ contentType: "text/javascript", body: `window.bootPrefs = {lang:"${lang}",theme:"light",web:true};` });
if (url.pathname === "/wails/runtime.js") return route.fulfill({ contentType: "text/javascript", body: "export const Window = {};" });
if (url.pathname === "/api/state") return json(state);
if (url.pathname === "/api/providers") return json(providers);
if (url.pathname === "/api/groups") return json({ groups: [] });
if (url.pathname.startsWith("/api/provider/") && route.request().method() === "POST") {
posts.push({ action: url.pathname.slice("/api/provider/".length), body: route.request().postDataJSON() });
return json(providers);
}
if (url.pathname.startsWith("/api/")) return json({});
const file = path.join(assets, url.pathname === "/" ? "index.html" : url.pathname);
const contentType = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png" }[path.extname(file)];
await route.fulfill({ body: await fs.readFile(file), contentType });
};
}
const words = { en: { tick: "DeepSeek models only from DeepSeek's own API", save: "Save" }, zh: { save: "保存" } };
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
for (const lang of ["en", "zh"]) {
const w = words[lang];
test(`${engine} ${lang}: a Cline provider's DeepSeek models are pinned from its editor`, async (t) => {
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
t.after(() => browser.close());
const page = await (await browser.newContext({ viewport: { width: 900, height: 760 }, reducedMotion: "reduce" })).newPage();
page.setDefaultTimeout(5000);
const errors = [];
page.on("pageerror", (e) => errors.push(e.message));
const posts = [];
await page.route("**/*", serve(lang, posts));
await page.goto("http://magpie.test/?view=providers");
const zh = await page.evaluate(() => [
"DeepSeek models only from DeepSeek's own API", "Upstream",
"Cline serves a model from any host its gateway picks; pinned, a request for a DeepSeek model goes only to DeepSeek, which keeps its prompt cache, and fails when DeepSeek can't take it",
].map((k) => I18N.zh[k]));
assert(zh.every(Boolean), "every string has its Chinese");
if (lang === "zh") w.tick = zh[0];
await page.locator(".row.provider", { hasText: "ClinePass" }).click();
const tick = page.locator(".editor label.pin-upstream");
await tick.waitFor();
assert.equal((await tick.textContent()).trim(), w.tick);
assert.equal(await tick.locator("input").isChecked(), false, "not pinned as saved");
await tick.locator("input").check();
const n = posts.length;
await page.locator(".editor .bar").getByRole("button", { name: w.save, exact: true }).click();
for (let i = 0; i < 50 && posts.length === n; i++) await page.waitForTimeout(50);
const saved = posts.at(-1);
assert.equal(saved.action, "save");
assert.equal(saved.body.id, "clinepass");
assert.equal(saved.body.pinUpstream, true);
await page.locator(".row.provider", { hasText: "Relay" }).click();
await page.locator(".editor input.price-rate").waitFor();
assert.equal(await page.locator(".editor label.pin-upstream").count(), 0, "only a Cline provider has it");
assert.deepEqual(errors, []);
});
}
}
+14
View File
@@ -169,3 +169,17 @@ func (p Provider) clineFreeModel(model string) bool {
}
return isClineFree(model)
}
// ClinePin is the upstream a request for model is pinned to on p
// (PinUpstream): "deepseek" for a DeepSeek model of the Cline API, ""
// for anything else. Cline's free models are served where Cline puts
// them, so they never are.
func (p Provider) ClinePin(model string) string {
if !p.PinUpstream || !p.IsCline() || strings.HasPrefix(model, clineFreePrefix) {
return ""
}
if name := model[strings.LastIndex(model, "/")+1:]; strings.HasPrefix(strings.ToLower(name), "deepseek") {
return "deepseek"
}
return ""
}
+6
View File
@@ -145,6 +145,12 @@ type Provider struct {
// it as the client sent it, rather than given magpie's search (#359).
Searches bool `json:"searches,omitempty"`
// PinUpstream, on the Cline API, has its DeepSeek models answered only
// by DeepSeek's own API (White Immortal on Discord): Cline routes a
// model through an AI gateway that may serve it from any host, and
// DeepSeek's own keeps its prompt cache. See ClinePin.
PinUpstream bool `json:"pinUpstream,omitempty"`
// Proxy is the proxy magpie's requests to this provider go through
// (#237: Codex through one, a vendor at home without): "" follows
// the global one (Settings' Proxy, the environment's, the system's),