dsh-web-search-plugin 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +104 -67
- package/cordis.patch.yml +1 -1
- package/lib/client.js +123 -26
- package/lib/index.js +97 -8
- package/lib/providers.js +212 -0
- package/lib/rest.js +329 -0
- package/lib/shared.js +1 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import z from "@deepseek-ai/schemastery";
|
|
20
20
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
21
|
+
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
21
22
|
import { MAX_RESULTS_CAP } from "./shared.js";
|
|
22
23
|
import { TAVILY_DEFAULT_API_KEY_ENV, TAVILY_DEFAULT_BASE_URL, TavilySearchProvider, resolveTavilyOptions } from "./tavily.js";
|
|
23
24
|
import { BRAVE_DEFAULT_API_KEY_ENV, BRAVE_DEFAULT_BASE_URL, BraveSearchProvider, resolveBraveOptions } from "./brave.js";
|
|
24
25
|
import { DEEPSEEK_DEFAULT_API_KEY_ENV, DEEPSEEK_DEFAULT_BASE_URL, DeepSeekSearchProvider, resolveDeepseekOptions } from "./deepseek.js";
|
|
25
26
|
import { TAVILY_USAGE_POLL_MS, UsageTracker } from "./usage.js";
|
|
27
|
+
import { RestSearchProvider, resolveRestProvider } from "./rest.js";
|
|
28
|
+
import { BUILTIN_REST_IDS, REST_PROVIDERS, findProvider } from "./providers.js";
|
|
26
29
|
|
|
27
30
|
/** Stable id this plugin registers under on `ctx.web`. */
|
|
28
31
|
const SEARCH_PROVIDER_ID = "dsh-web-search";
|
|
@@ -34,8 +37,8 @@ const inject = ["web"];
|
|
|
34
37
|
|
|
35
38
|
/** Plugin config (all optional — `apply` fills defaults). */
|
|
36
39
|
const Config = z.object({
|
|
37
|
-
/** Active backend: `deepseek-official
|
|
38
|
-
provider: z.string().default("
|
|
40
|
+
/** Active backend: `deepseek-official` (default), `tavily`, `brave`, `serper`, `serpapi`, `exa`, or `searxng`. */
|
|
41
|
+
provider: z.string().default("deepseek-official"),
|
|
39
42
|
/** Tavily auth mode: `keyless` (default) or `keyed`. */
|
|
40
43
|
mode: z.string().default("keyless"),
|
|
41
44
|
/** Literal Tavily API key; prefer {@link apiKeyEnv}. */
|
|
@@ -66,6 +69,38 @@ const Config = z.object({
|
|
|
66
69
|
freshness: z.string().default(""),
|
|
67
70
|
/** Optional HTTP(S) proxy URL for Brave (falls back to HTTPS_PROXY / HTTP_PROXY). */
|
|
68
71
|
proxy: z.string().default(""),
|
|
72
|
+
/** Literal Serper API key; prefer {@link serperApiKeyEnv}. */
|
|
73
|
+
serperApiKey: z.string().role("secret").default(""),
|
|
74
|
+
/** Credential reference for Serper search. */
|
|
75
|
+
serperApiKeyEnv: z.string().role("credential-ref").default("SERPER_API_KEY"),
|
|
76
|
+
/** Serper endpoint base. `/search` is appended. */
|
|
77
|
+
serperBaseURL: z.string().default("https://google.serper.dev"),
|
|
78
|
+
/** Literal SerpApi API key; prefer {@link serpapiApiKeyEnv}. */
|
|
79
|
+
serpapiApiKey: z.string().role("secret").default(""),
|
|
80
|
+
/** Credential reference for SerpApi search. */
|
|
81
|
+
serpapiApiKeyEnv: z.string().role("credential-ref").default("SERPAPI_API_KEY"),
|
|
82
|
+
/** SerpApi endpoint base. `/search.json` is appended. */
|
|
83
|
+
serpapiBaseURL: z.string().default("https://serpapi.com"),
|
|
84
|
+
/** Literal Exa API key; prefer {@link exaApiKeyEnv}. */
|
|
85
|
+
exaApiKey: z.string().role("secret").default(""),
|
|
86
|
+
/** Credential reference for Exa search. */
|
|
87
|
+
exaApiKeyEnv: z.string().role("credential-ref").default("EXA_API_KEY"),
|
|
88
|
+
/** Exa endpoint base. `/search` is appended. */
|
|
89
|
+
exaBaseURL: z.string().default("https://api.exa.ai"),
|
|
90
|
+
/** Optional SearXNG instance base URL (self-hosted). `/search` is appended. */
|
|
91
|
+
searxngBaseURL: z.string().default("https://searx.be"),
|
|
92
|
+
/** Literal Scavio API key; prefer {@link scavioApiKeyEnv}. */
|
|
93
|
+
scavioApiKey: z.string().role("secret").default(""),
|
|
94
|
+
/** Credential reference for Scavio search. */
|
|
95
|
+
scavioApiKeyEnv: z.string().role("credential-ref").default("SCAVIO_API_KEY"),
|
|
96
|
+
/** Scavio endpoint base. `/api/v2/google` is appended. */
|
|
97
|
+
scavioBaseURL: z.string().default("https://api.scavio.dev"),
|
|
98
|
+
/** Literal Firecrawl API key; prefer {@link firecrawlApiKeyEnv}. */
|
|
99
|
+
firecrawlApiKey: z.string().role("secret").default(""),
|
|
100
|
+
/** Credential reference for Firecrawl search. */
|
|
101
|
+
firecrawlApiKeyEnv: z.string().role("credential-ref").default("FIRECRAWL_API_KEY"),
|
|
102
|
+
/** Firecrawl endpoint base. `/v2/search` is appended. */
|
|
103
|
+
firecrawlBaseURL: z.string().default("https://api.firecrawl.dev"),
|
|
69
104
|
/** Literal DeepSeek API key; prefer {@link deepseekApiKeyEnv}. */
|
|
70
105
|
deepseekApiKey: z.string().role("secret").default(""),
|
|
71
106
|
/** Credential reference for DeepSeek search. */
|
|
@@ -106,6 +141,13 @@ class PluginSearchProvider {
|
|
|
106
141
|
...this.resolveOptions().brave,
|
|
107
142
|
onRateLimit: (headers) => usage?.recordBraveHeaders(headers)
|
|
108
143
|
}));
|
|
144
|
+
this.rest = new Map();
|
|
145
|
+
for (const providerId of BUILTIN_REST_IDS) {
|
|
146
|
+
this.rest.set(providerId, new RestSearchProvider(providerId, () => this.resolveOptions().rest[providerId], {
|
|
147
|
+
onUsage: providerId === "tavily" ? (credits) => usage?.recordTavilyCredits(credits) : void 0,
|
|
148
|
+
onRateLimit: providerId === "brave" ? (headers) => usage?.recordBraveHeaders(headers) : void 0
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
109
151
|
}
|
|
110
152
|
|
|
111
153
|
available() {
|
|
@@ -117,21 +159,63 @@ class PluginSearchProvider {
|
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
backend() {
|
|
120
|
-
const
|
|
121
|
-
|
|
162
|
+
const options = this.resolveOptions();
|
|
163
|
+
const provider = options.provider;
|
|
164
|
+
const restInstance = this.rest.get(provider);
|
|
165
|
+
if (restInstance !== void 0) return restInstance;
|
|
122
166
|
if (provider === "deepseek-official") return this.deepseek;
|
|
123
|
-
|
|
167
|
+
// Fallback: provider points at something unknown → official default.
|
|
168
|
+
return this.deepseek;
|
|
124
169
|
}
|
|
125
170
|
}
|
|
126
171
|
|
|
127
172
|
/** Project one resolved section into options for each backend. */
|
|
128
173
|
function resolveOptions(ctx, config) {
|
|
129
|
-
const
|
|
174
|
+
const isBuiltIn = config.provider === "tavily" || config.provider === "brave" || config.provider === "deepseek-official" || BUILTIN_REST_IDS.includes(config.provider);
|
|
175
|
+
const provider = isBuiltIn ? config.provider : "deepseek-official";
|
|
176
|
+
// Per-provider literal key / optional endpoint override, used when the
|
|
177
|
+
// metadata row's default is not enough (self-hosted SearXNG, proxied
|
|
178
|
+
// providers, custom key refs).
|
|
179
|
+
const keySourceByProvider = {
|
|
180
|
+
brave: { literal: config.braveApiKey, envName: config.braveApiKeyEnv ?? BRAVE_DEFAULT_API_KEY_ENV, baseURL: config.braveBaseURL ?? "", proxy: config.proxy || launchEnvironmentOf(ctx).get("HTTPS_PROXY")?.value || launchEnvironmentOf(ctx).get("HTTP_PROXY")?.value || "" },
|
|
181
|
+
tavily: { literal: config.apiKey, envName: config.apiKeyEnv ?? TAVILY_DEFAULT_API_KEY_ENV, baseURL: config.baseURL ?? "" },
|
|
182
|
+
serper: { literal: config.serperApiKey, envName: config.serperApiKeyEnv ?? "SERPER_API_KEY", baseURL: config.serperBaseURL ?? "" },
|
|
183
|
+
serpapi: { literal: config.serpapiApiKey, envName: config.serpapiApiKeyEnv ?? "SERPAPI_API_KEY", baseURL: config.serpapiBaseURL ?? "" },
|
|
184
|
+
exa: { literal: config.exaApiKey, envName: config.exaApiKeyEnv ?? "EXA_API_KEY", baseURL: config.exaBaseURL ?? "" },
|
|
185
|
+
searxng: { baseURL: config.searxngBaseURL ?? "" },
|
|
186
|
+
scavio: { literal: config.scavioApiKey, envName: config.scavioApiKeyEnv ?? "SCAVIO_API_KEY", baseURL: config.scavioBaseURL ?? "" },
|
|
187
|
+
firecrawl: { literal: config.firecrawlApiKey, envName: config.firecrawlApiKeyEnv ?? "FIRECRAWL_API_KEY", baseURL: config.firecrawlBaseURL ?? "" }
|
|
188
|
+
};
|
|
189
|
+
const settings = {
|
|
190
|
+
country: config.country,
|
|
191
|
+
searchLang: config.searchLang,
|
|
192
|
+
freshness: config.freshness,
|
|
193
|
+
searchDepth: config.searchDepth,
|
|
194
|
+
topic: config.topic,
|
|
195
|
+
includeAnswer: config.includeAnswer
|
|
196
|
+
};
|
|
197
|
+
const rest = {};
|
|
198
|
+
for (const providerId of BUILTIN_REST_IDS) {
|
|
199
|
+
const row = findProvider(providerId);
|
|
200
|
+
if (row === void 0) continue;
|
|
201
|
+
const source = keySourceByProvider[providerId] ?? {};
|
|
202
|
+
const resolved = resolveRestProvider(ctx, row, {
|
|
203
|
+
config,
|
|
204
|
+
settings,
|
|
205
|
+
keyed: providerId === "tavily" ? config.mode === "keyed" : false,
|
|
206
|
+
keySource: source
|
|
207
|
+
});
|
|
208
|
+
// Endpoint override: an explicit config baseURL wins over the row default.
|
|
209
|
+
if (source.baseURL != null && source.baseURL.length > 0) resolved.baseURL = source.baseURL;
|
|
210
|
+
if (providerId === "brave" && (source.proxy ?? "").length > 0) resolved.proxy = source.proxy;
|
|
211
|
+
rest[providerId] = resolved;
|
|
212
|
+
}
|
|
130
213
|
return {
|
|
131
214
|
provider,
|
|
132
215
|
deepseek: resolveDeepseekOptions(ctx, config),
|
|
133
216
|
tavily: resolveTavilyOptions(ctx, config),
|
|
134
|
-
brave: resolveBraveOptions(ctx, config)
|
|
217
|
+
brave: resolveBraveOptions(ctx, config),
|
|
218
|
+
rest
|
|
135
219
|
};
|
|
136
220
|
}
|
|
137
221
|
|
|
@@ -236,11 +320,14 @@ function apply(ctx, config) {
|
|
|
236
320
|
export {
|
|
237
321
|
BRAVE_DEFAULT_API_KEY_ENV,
|
|
238
322
|
BRAVE_DEFAULT_BASE_URL,
|
|
323
|
+
BUILTIN_REST_IDS,
|
|
239
324
|
BraveSearchProvider,
|
|
240
325
|
Config,
|
|
241
326
|
DEEPSEEK_DEFAULT_API_KEY_ENV,
|
|
242
327
|
DEEPSEEK_DEFAULT_BASE_URL,
|
|
243
328
|
DeepSeekSearchProvider,
|
|
329
|
+
REST_PROVIDERS,
|
|
330
|
+
RestSearchProvider,
|
|
244
331
|
SEARCH_PROVIDER_ID,
|
|
245
332
|
TAVILY_DEFAULT_API_KEY_ENV,
|
|
246
333
|
TAVILY_DEFAULT_BASE_URL,
|
|
@@ -250,6 +337,8 @@ export {
|
|
|
250
337
|
WEB_SEARCH_SETTINGS_NAMESPACE,
|
|
251
338
|
WEB_SEARCH_TAVILY_SETTINGS_NAMESPACE,
|
|
252
339
|
apply,
|
|
340
|
+
findProvider,
|
|
253
341
|
inject,
|
|
254
|
-
name
|
|
342
|
+
name,
|
|
343
|
+
resolveRestProvider
|
|
255
344
|
};
|
package/lib/providers.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static metadata table for every built-in REST web-search provider.
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally **data, not code**: adding a new REST provider
|
|
5
|
+
* means adding one row here (plus an optional official "get a key" link in the
|
|
6
|
+
* settings card), with zero per-provider execution code. The generic backend in
|
|
7
|
+
* `lib/rest.js` reads a row and performs `method` / `path` / `queryParam` /
|
|
8
|
+
* `countParam` / `params` / `auth` / `response` / `hooks`.
|
|
9
|
+
*
|
|
10
|
+
* Only **pure REST providers** ("one query → one HTTP request → one result
|
|
11
|
+
* array") belong here. Model-native search tool (DeepSeek's Anthropic Messages
|
|
12
|
+
* `web_search_20250305`, Perplexity sonar tools, OpenAI Responses) is **not**
|
|
13
|
+
* REST and stays on its dedicated backend (`lib/deepseek.js`).
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-web-search-plugin/providers
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Built-in REST providers.
|
|
20
|
+
* `auth.type` ∈ { `bearer`, `header`, `none`, `query` }:
|
|
21
|
+
* - `bearer` — `Authorization: Bearer <key>`
|
|
22
|
+
* - `header` — key in a named header (`auth.header`)
|
|
23
|
+
* - `none` — no key (SearXNG, keyless)
|
|
24
|
+
* - `query` — key as a query-string parameter (`auth.param`, e.g. SerpApi)
|
|
25
|
+
* `params[].when` ∈ { `always`, `nonEmpty`, `keyed`, `keyless` }.
|
|
26
|
+
*/
|
|
27
|
+
export const REST_PROVIDERS = [
|
|
28
|
+
{
|
|
29
|
+
id: "brave",
|
|
30
|
+
name: "Brave Search",
|
|
31
|
+
kind: "rest",
|
|
32
|
+
method: "GET",
|
|
33
|
+
baseURL: "https://api.search.brave.com/res/v1/web/search",
|
|
34
|
+
path: "",
|
|
35
|
+
queryIn: "query",
|
|
36
|
+
queryParam: "q",
|
|
37
|
+
countParam: "count",
|
|
38
|
+
auth: { type: "header", header: "X-Subscription-Token" },
|
|
39
|
+
keyRequired: true,
|
|
40
|
+
apiKeyRef: "BRAVE_API_KEY",
|
|
41
|
+
response: "brave",
|
|
42
|
+
params: [
|
|
43
|
+
{ key: "country", setting: "country", when: "nonEmpty" },
|
|
44
|
+
{ key: "search_lang", setting: "searchLang", when: "nonEmpty" },
|
|
45
|
+
{ key: "freshness", setting: "freshness", when: "nonEmpty" }
|
|
46
|
+
],
|
|
47
|
+
officialUrl: "https://api-dashboard.search.brave.com/",
|
|
48
|
+
hooks: ["rate-limit", "proxy"]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "tavily",
|
|
52
|
+
name: "Tavily",
|
|
53
|
+
kind: "rest",
|
|
54
|
+
method: "POST",
|
|
55
|
+
baseURL: "https://api.tavily.com",
|
|
56
|
+
path: "/search",
|
|
57
|
+
queryIn: "body",
|
|
58
|
+
queryParam: "query",
|
|
59
|
+
countParam: "max_results",
|
|
60
|
+
auth: { type: "bearer" },
|
|
61
|
+
keyRequired: false, // keyless by default; keyed only when a key is configured
|
|
62
|
+
apiKeyRef: "TAVILY_API_KEY",
|
|
63
|
+
response: "tavily",
|
|
64
|
+
params: [
|
|
65
|
+
{ key: "search_depth", setting: "searchDepth", when: "nonEmpty" },
|
|
66
|
+
{ key: "topic", setting: "topic", when: "nonEmpty" },
|
|
67
|
+
{ key: "include_answer", setting: "includeAnswer", when: "nonEmpty" },
|
|
68
|
+
{ key: "include_images", value: false, when: "always" },
|
|
69
|
+
{ key: "include_usage", value: true, when: "keyed" }
|
|
70
|
+
],
|
|
71
|
+
keylessHeaders: { "x-tavily-access-mode": "keyless" },
|
|
72
|
+
keylessHeaderWhen: "keyless",
|
|
73
|
+
officialUrl: "https://app.tavily.com/",
|
|
74
|
+
hooks: ["usage"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "serper",
|
|
78
|
+
name: "Serper",
|
|
79
|
+
kind: "rest",
|
|
80
|
+
method: "POST",
|
|
81
|
+
baseURL: "https://google.serper.dev",
|
|
82
|
+
path: "/search",
|
|
83
|
+
queryIn: "body",
|
|
84
|
+
queryParam: "q",
|
|
85
|
+
countParam: "num",
|
|
86
|
+
auth: { type: "header", header: "X-API-KEY" },
|
|
87
|
+
keyRequired: true,
|
|
88
|
+
apiKeyRef: "SERPER_API_KEY",
|
|
89
|
+
response: "serper",
|
|
90
|
+
params: [],
|
|
91
|
+
officialUrl: "https://serper.dev/",
|
|
92
|
+
hooks: []
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "serpapi",
|
|
96
|
+
name: "SerpApi",
|
|
97
|
+
kind: "rest",
|
|
98
|
+
method: "GET",
|
|
99
|
+
baseURL: "https://serpapi.com",
|
|
100
|
+
path: "/search.json",
|
|
101
|
+
queryIn: "query",
|
|
102
|
+
queryParam: "q",
|
|
103
|
+
countParam: "num",
|
|
104
|
+
auth: { type: "query", param: "api_key" },
|
|
105
|
+
keyRequired: true,
|
|
106
|
+
apiKeyRef: "SERPAPI_API_KEY",
|
|
107
|
+
response: "serper",
|
|
108
|
+
params: [],
|
|
109
|
+
officialUrl: "https://serpapi.com/",
|
|
110
|
+
hooks: []
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "exa",
|
|
114
|
+
name: "Exa",
|
|
115
|
+
kind: "rest",
|
|
116
|
+
method: "POST",
|
|
117
|
+
baseURL: "https://api.exa.ai",
|
|
118
|
+
path: "/search",
|
|
119
|
+
queryIn: "body",
|
|
120
|
+
queryParam: "query",
|
|
121
|
+
countParam: "num_results",
|
|
122
|
+
auth: { type: "bearer" },
|
|
123
|
+
keyRequired: true,
|
|
124
|
+
apiKeyRef: "EXA_API_KEY",
|
|
125
|
+
response: "exa",
|
|
126
|
+
params: [
|
|
127
|
+
{ key: "contents", value: "text", when: "always" }
|
|
128
|
+
],
|
|
129
|
+
officialUrl: "https://dashboard.exa.ai/",
|
|
130
|
+
hooks: []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "searxng",
|
|
134
|
+
name: "SearXNG",
|
|
135
|
+
kind: "rest",
|
|
136
|
+
method: "GET",
|
|
137
|
+
baseURL: "https://searx.be",
|
|
138
|
+
path: "/search",
|
|
139
|
+
queryIn: "query",
|
|
140
|
+
queryParam: "q",
|
|
141
|
+
countParam: "count",
|
|
142
|
+
auth: { type: "none" },
|
|
143
|
+
keyRequired: false,
|
|
144
|
+
apiKeyRef: "",
|
|
145
|
+
response: "searxng",
|
|
146
|
+
params: [
|
|
147
|
+
{ key: "format", value: "json", when: "always" }
|
|
148
|
+
],
|
|
149
|
+
officialUrl: "https://docs.searxng.org/",
|
|
150
|
+
hooks: []
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "scavio",
|
|
154
|
+
name: "Scavio",
|
|
155
|
+
kind: "rest",
|
|
156
|
+
method: "POST",
|
|
157
|
+
baseURL: "https://api.scavio.dev",
|
|
158
|
+
path: "/api/v2/google",
|
|
159
|
+
queryIn: "body",
|
|
160
|
+
queryParam: "query",
|
|
161
|
+
countParam: "",
|
|
162
|
+
auth: { type: "bearer" },
|
|
163
|
+
keyRequired: true,
|
|
164
|
+
apiKeyRef: "SCAVIO_API_KEY",
|
|
165
|
+
response: "serper",
|
|
166
|
+
params: [
|
|
167
|
+
{ key: "hl", setting: "searchLang", when: "nonEmpty" },
|
|
168
|
+
{ key: "gl", setting: "country", when: "nonEmpty" }
|
|
169
|
+
],
|
|
170
|
+
officialUrl: "https://scavio.dev/",
|
|
171
|
+
hooks: []
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "firecrawl",
|
|
175
|
+
name: "Firecrawl",
|
|
176
|
+
kind: "rest",
|
|
177
|
+
method: "POST",
|
|
178
|
+
baseURL: "https://api.firecrawl.dev",
|
|
179
|
+
path: "/v2/search",
|
|
180
|
+
queryIn: "body",
|
|
181
|
+
queryParam: "query",
|
|
182
|
+
countParam: "limit",
|
|
183
|
+
auth: { type: "bearer" },
|
|
184
|
+
keyRequired: true,
|
|
185
|
+
apiKeyRef: "FIRECRAWL_API_KEY",
|
|
186
|
+
response: "firecrawl",
|
|
187
|
+
params: [
|
|
188
|
+
{ key: "sources", value: "web", when: "always" }
|
|
189
|
+
],
|
|
190
|
+
officialUrl: "https://firecrawl.dev/",
|
|
191
|
+
hooks: []
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
/** Location of one provider by id (the id doubles as the `provider` value). */
|
|
196
|
+
export function findProvider(id) {
|
|
197
|
+
for (const entry of REST_PROVIDERS) if (entry.id === id) return entry;
|
|
198
|
+
return void 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Set of ids that live in the metadata table. */
|
|
202
|
+
export function restProviderIds() {
|
|
203
|
+
const ids = /* @__PURE__ */ new Set();
|
|
204
|
+
for (const entry of REST_PROVIDERS) ids.add(entry.id);
|
|
205
|
+
return ids;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The built-in REST provider ids (seam-facing `provider` values). */
|
|
209
|
+
export const BUILTIN_REST_IDS = REST_PROVIDERS.map((entry) => entry.id);
|
|
210
|
+
|
|
211
|
+
/** The model-native (non-REST) provider id, kept on its own backend. */
|
|
212
|
+
export const TOOL_PROVIDER_IDS = ["deepseek-official"];
|