chat-agent-toolkit 1.2.33 → 1.2.35
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/dist/research-agent.cjs.js.map +1 -1
- package/dist/research-agent.es.js.map +1 -1
- package/package.json +1 -1
- package/src/config/config-manager.ts +295 -295
- package/src/config/config-types.ts +65 -65
- package/src/config/environment-variables.ts +16 -16
- package/src/config/index.ts +26 -26
- package/src/config/language-models-database.ts +1434 -1434
- package/src/config/mcp-server-registry.ts +24 -24
- package/src/config/model-registry.ts +271 -271
- package/src/config/model-tester.ts +211 -211
- package/src/config/model-utils.ts +193 -193
- package/src/config/provider-ui-config.ts +196 -196
- package/src/connectors/open-connector.json +130 -130
- package/src/index.ts +26 -26
- package/src/memory/ARCHITECTURE.md +302 -302
- package/src/memory/README.md +224 -224
- package/src/memory/agent-memory-manager.ts +416 -416
- package/src/memory/example.ts +343 -343
- package/src/memory/index.ts +47 -47
- package/src/memory/mastra-integration.ts +604 -604
- package/src/memory/storage/drizzle-storage.ts +236 -236
- package/src/memory/storage/in-memory-storage.ts +551 -551
- package/src/memory/storage/storage-interface.ts +68 -68
- package/src/memory/types.ts +125 -125
- package/src/models/types.ts +4 -4
- package/src/tools/index.ts +13 -13
- package/src/tools/open-connector-mastra.ts +270 -270
- package/src/tools/open-connector-mcp.ts +170 -170
- package/src/tools/qwksearch-api-tools.ts +326 -326
- package/src/tools/search/doc-utils.ts +139 -139
- package/src/tools/search/document.ts +47 -47
- package/src/tools/search/index.ts +14 -14
- package/src/tools/search/link-summarizer.ts +112 -112
- package/src/tools/search/meta-search-types.ts +62 -62
- package/src/tools/search/metaSearchAgent.ts +465 -465
- package/src/tools/search/search-handlers.ts +97 -97
- package/src/tools/search/suggestionGeneratorAgent.ts +56 -56
- package/src/types.d.ts +137 -137
- package/src/utils/index.ts +2 -2
- package/src/utils/markdown-to-html.ts +53 -53
- package/src/utils/outputParser.ts +73 -73
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { configManager } from "./index";
|
|
2
|
-
import type { ConfigModelProvider } from "./config-types";
|
|
3
|
-
|
|
4
|
-
export const getConfiguredModelProviders = (): ConfigModelProvider[] => {
|
|
5
|
-
return configManager.getConfig("modelProviders", []);
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const getConfiguredModelProviderById = (
|
|
9
|
-
id: string,
|
|
10
|
-
): ConfigModelProvider | undefined => {
|
|
11
|
-
return getConfiguredModelProviders().find((p) => p.id === id) ?? undefined;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const getSearxngURL = () =>
|
|
15
|
-
configManager.getConfig("search.searxngURL", "");
|
|
16
|
-
|
|
17
|
-
export const getTavilyApiKey = () =>
|
|
18
|
-
configManager.getConfig("search.tavilyApiKey", "");
|
|
19
|
-
|
|
20
|
-
export const getSourceScrapeCount = (): number =>
|
|
21
|
-
parseInt(configManager.getConfig("search.sourceScrapeCount", "3"), 10) || 3;
|
|
22
|
-
|
|
23
|
-
export const getSourceScrapeTimeout = (): number =>
|
|
24
|
-
parseInt(configManager.getConfig("search.sourceScrapeTimeout", "5"), 10) || 5;
|
|
1
|
+
import { configManager } from "./index";
|
|
2
|
+
import type { ConfigModelProvider } from "./config-types";
|
|
3
|
+
|
|
4
|
+
export const getConfiguredModelProviders = (): ConfigModelProvider[] => {
|
|
5
|
+
return configManager.getConfig("modelProviders", []);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getConfiguredModelProviderById = (
|
|
9
|
+
id: string,
|
|
10
|
+
): ConfigModelProvider | undefined => {
|
|
11
|
+
return getConfiguredModelProviders().find((p) => p.id === id) ?? undefined;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getSearxngURL = () =>
|
|
15
|
+
configManager.getConfig("search.searxngURL", "");
|
|
16
|
+
|
|
17
|
+
export const getTavilyApiKey = () =>
|
|
18
|
+
configManager.getConfig("search.tavilyApiKey", "");
|
|
19
|
+
|
|
20
|
+
export const getSourceScrapeCount = (): number =>
|
|
21
|
+
parseInt(configManager.getConfig("search.sourceScrapeCount", "3"), 10) || 3;
|
|
22
|
+
|
|
23
|
+
export const getSourceScrapeTimeout = (): number =>
|
|
24
|
+
parseInt(configManager.getConfig("search.sourceScrapeTimeout", "5"), 10) || 5;
|
|
@@ -1,271 +1,271 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Registry for managing and loading Vercel AI SDK model providers.
|
|
3
|
-
*
|
|
4
|
-
* Exposes the full API expected by the app's API routes and chat handler:
|
|
5
|
-
* - `activeProviders` (sync getter) / `getActiveProviders()`
|
|
6
|
-
* - `isProviderEnvBased(providerId)`
|
|
7
|
-
* - `loadChatModel(providerId, modelKey)` — returns an AI SDK `LanguageModel`
|
|
8
|
-
* - provider/model CRUD passthroughs to {@link configManager}
|
|
9
|
-
*/
|
|
10
|
-
import configManager from "./config-manager";
|
|
11
|
-
import { LANGUAGE_MODELS, getGuestSafeProviders } from "./language-models-database";
|
|
12
|
-
import { getModelProvidersUIConfigSection } from "./provider-ui-config";
|
|
13
|
-
import type { ConfigModelProvider, Model } from "./config-types";
|
|
14
|
-
|
|
15
|
-
// Maps a provider UI key to the matching `provider` name in the
|
|
16
|
-
// LANGUAGE_MODELS database (e.g. the "gemini" UI key ↔ "Google" model list).
|
|
17
|
-
const PROVIDER_KEY_TO_DB_NAME: Record<string, string> = {
|
|
18
|
-
openai: "openai",
|
|
19
|
-
anthropic: "anthropic",
|
|
20
|
-
gemini: "google",
|
|
21
|
-
groq: "groq",
|
|
22
|
-
deepseek: "deepseek",
|
|
23
|
-
nvidia: "nvidia",
|
|
24
|
-
openrouter: "openrouter",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/** Default chat models for a provider type from the LANGUAGE_MODELS database. */
|
|
28
|
-
const getDefaultChatModels = (providerType: string): Model[] => {
|
|
29
|
-
const dbName = PROVIDER_KEY_TO_DB_NAME[providerType] ?? providerType;
|
|
30
|
-
const entry = LANGUAGE_MODELS.find(
|
|
31
|
-
(p) => p.provider.toLowerCase() === dbName.toLowerCase(),
|
|
32
|
-
);
|
|
33
|
-
if (!entry?.models) return [];
|
|
34
|
-
return entry.models.map((m: any) => ({ name: m.name, key: m.id }));
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/** Merges default and user-added models, deduplicated by model key. */
|
|
38
|
-
const mergeChatModels = (provider: ConfigModelProvider): Model[] => {
|
|
39
|
-
const merged = new Map<string, Model>();
|
|
40
|
-
for (const m of getDefaultChatModels(provider.type)) {
|
|
41
|
-
merged.set(m.key, m);
|
|
42
|
-
}
|
|
43
|
-
for (const m of provider.chatModels || []) {
|
|
44
|
-
merged.set(m.key, { key: m.key, name: m.name });
|
|
45
|
-
}
|
|
46
|
-
return [...merged.values()];
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default class ModelRegistry {
|
|
50
|
-
/**
|
|
51
|
-
* Currently configured providers (env-based + user-added).
|
|
52
|
-
* Sync getter used by the chat handler for logging and lookups.
|
|
53
|
-
*/
|
|
54
|
-
get activeProviders(): ConfigModelProvider[] {
|
|
55
|
-
return configManager.getCurrentConfig().modelProviders;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Providers with their full chat model lists (defaults merged with
|
|
60
|
-
* user-added models), shaped for the settings/providers UI.
|
|
61
|
-
*/
|
|
62
|
-
async getActiveProviders(guestMode: boolean = false) {
|
|
63
|
-
const providers = this.activeProviders;
|
|
64
|
-
const dbModels = guestMode ? getGuestSafeProviders() : LANGUAGE_MODELS;
|
|
65
|
-
|
|
66
|
-
return providers.map((p) => ({
|
|
67
|
-
id: p.id,
|
|
68
|
-
name: p.name,
|
|
69
|
-
type: p.type,
|
|
70
|
-
chatModels: guestMode ? this.getGuestChatModels(p, dbModels) : mergeChatModels(p),
|
|
71
|
-
})).filter((p) => p.chatModels.length > 0);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get guest-safe chat models for a provider (only tested working models).
|
|
76
|
-
*/
|
|
77
|
-
private getGuestChatModels(provider: ConfigModelProvider, guestProviders: typeof LANGUAGE_MODELS): Model[] {
|
|
78
|
-
const dbName = PROVIDER_KEY_TO_DB_NAME[provider.type.toLowerCase()];
|
|
79
|
-
const dbEntry = guestProviders.find(
|
|
80
|
-
(p) => p.provider.toLowerCase() === (dbName?.toLowerCase() || provider.type.toLowerCase()),
|
|
81
|
-
);
|
|
82
|
-
if (!dbEntry?.models) return [];
|
|
83
|
-
return dbEntry.models.map((m: any) => ({ name: m.name, key: m.id }));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Finds a provider by id, falling back to free providers in order:
|
|
88
|
-
* OpenRouter (no daily limits, best for guests), Groq (fastest, daily limits),
|
|
89
|
-
* then NVIDIA, then the first configured provider.
|
|
90
|
-
* Client-side provider ids are config hashes that go stale whenever
|
|
91
|
-
* server env config changes, so a graceful fallback keeps existing
|
|
92
|
-
* chat sessions working after a redeploy.
|
|
93
|
-
*/
|
|
94
|
-
private findProvider(providerId?: string): ConfigModelProvider | undefined {
|
|
95
|
-
const providers = this.activeProviders;
|
|
96
|
-
let provider = providers.find((p) => p.id === providerId);
|
|
97
|
-
|
|
98
|
-
if (!provider && providers.length > 0) {
|
|
99
|
-
// Prioritize OpenRouter (no daily limits) over Groq (has daily limits)
|
|
100
|
-
provider =
|
|
101
|
-
providers.find((p) => p.name.toLowerCase().includes("openrouter")) ??
|
|
102
|
-
providers.find((p) => p.name.toLowerCase().includes("groq")) ??
|
|
103
|
-
providers.find((p) => p.name.toLowerCase().includes("nvidia")) ??
|
|
104
|
-
providers[0];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return provider;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Whether the resolved provider was configured from environment variables. */
|
|
111
|
-
isProviderEnvBased(providerId?: string): boolean {
|
|
112
|
-
return this.findProvider(providerId)?.isEnvBased === true;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Instantiates a Vercel AI SDK language model for the given provider and
|
|
117
|
-
* model key. Falls back to the provider's first/default model when no key
|
|
118
|
-
* is given. Temperature is a per-call setting in the AI SDK, so callers
|
|
119
|
-
* pass it to generateText/streamText rather than the model instance.
|
|
120
|
-
*/
|
|
121
|
-
async loadChatModel(providerId?: string, modelKey?: string): Promise<any> {
|
|
122
|
-
const provider = this.findProvider(providerId);
|
|
123
|
-
|
|
124
|
-
if (!provider) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
"No model providers configured. Please add a provider in settings.",
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const type = provider.type.toLowerCase();
|
|
131
|
-
const config = provider.config || {};
|
|
132
|
-
const apiKey = config.apiKey || "";
|
|
133
|
-
const availableModels = mergeChatModels(provider);
|
|
134
|
-
const modelName =
|
|
135
|
-
modelKey || availableModels[0]?.key || "";
|
|
136
|
-
|
|
137
|
-
console.log(
|
|
138
|
-
`[ModelRegistry] loadChatModel: providerId=${providerId} provider=${provider.name} type=${type} requestedModel=${modelKey ?? "(default)"} resolvedModel=${modelName}`,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
if (!modelName) {
|
|
142
|
-
throw new Error(`No chat models available for provider "${provider.name}"`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Validate that the requested model exists in the provider's model list
|
|
146
|
-
if (modelKey && !availableModels.some((m) => m.key === modelKey)) {
|
|
147
|
-
console.warn(
|
|
148
|
-
`[ModelRegistry] Requested model "${modelKey}" not found in provider "${provider.name}". Available models: ${availableModels.map((m) => m.key).join(", ")}`,
|
|
149
|
-
);
|
|
150
|
-
throw new Error(
|
|
151
|
-
`Model "${modelKey}" is not available for provider "${provider.name}". Please select a different model in Settings.`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Validate API key is present
|
|
156
|
-
if (!apiKey) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`No API key configured for provider "${provider.name}". Please add your API key in Settings → Model Providers.`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// OpenAI-compatible providers differ only by base URL.
|
|
163
|
-
const openAICompatibleBaseURLs: Record<string, string> = {
|
|
164
|
-
openai: "https://api.openai.com/v1",
|
|
165
|
-
togetherai: "https://api.together.xyz/v1",
|
|
166
|
-
perplexity: "https://api.perplexity.ai",
|
|
167
|
-
nvidia: "https://integrate.api.nvidia.com/v1",
|
|
168
|
-
openrouter: "https://openrouter.ai/api/v1",
|
|
169
|
-
deepseek: "https://api.deepseek.com",
|
|
170
|
-
xai: "https://api.x.ai/v1",
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
if (type in openAICompatibleBaseURLs) {
|
|
174
|
-
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
175
|
-
return createOpenAI({
|
|
176
|
-
apiKey,
|
|
177
|
-
baseURL: config.baseURL || openAICompatibleBaseURLs[type],
|
|
178
|
-
}).chat(modelName);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
switch (type) {
|
|
182
|
-
case "cloudflare": {
|
|
183
|
-
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
184
|
-
const [cfApiToken, accountId] = apiKey.split(":");
|
|
185
|
-
return createOpenAI({
|
|
186
|
-
apiKey: cfApiToken,
|
|
187
|
-
baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
|
|
188
|
-
}).chat(modelName);
|
|
189
|
-
}
|
|
190
|
-
case "groq": {
|
|
191
|
-
const { createGroq } = await import("@ai-sdk/groq");
|
|
192
|
-
return createGroq({ apiKey })(modelName);
|
|
193
|
-
}
|
|
194
|
-
case "anthropic": {
|
|
195
|
-
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
196
|
-
return createAnthropic({ apiKey })(modelName);
|
|
197
|
-
}
|
|
198
|
-
case "gemini":
|
|
199
|
-
case "google": {
|
|
200
|
-
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
201
|
-
return createGoogleGenerativeAI({ apiKey })(modelName);
|
|
202
|
-
}
|
|
203
|
-
default:
|
|
204
|
-
throw new Error(`Unsupported provider type: ${type}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Registers a new provider. Accepts both `(type, config)` — used by the
|
|
210
|
-
* providers API route — and `(type, name, config)`.
|
|
211
|
-
*/
|
|
212
|
-
async addProvider(type: string, nameOrConfig: any, config?: Record<string, any>) {
|
|
213
|
-
let name: string;
|
|
214
|
-
let finalConfig: Record<string, any>;
|
|
215
|
-
if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
|
|
216
|
-
finalConfig = nameOrConfig;
|
|
217
|
-
const section = getModelProvidersUIConfigSection().find(
|
|
218
|
-
(s) => s.key === type,
|
|
219
|
-
);
|
|
220
|
-
name = section?.name || type.toUpperCase();
|
|
221
|
-
} else {
|
|
222
|
-
name = String(nameOrConfig);
|
|
223
|
-
finalConfig = config || {};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const newProvider = configManager.addModelProvider(type, name, finalConfig);
|
|
227
|
-
return {
|
|
228
|
-
...newProvider,
|
|
229
|
-
chatModels: getDefaultChatModels(type),
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async removeProvider(providerId: string) {
|
|
234
|
-
configManager.removeModelProvider(providerId);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Updates a provider's config. Accepts both `(id, config)` — used by the
|
|
239
|
-
* providers API route — and `(id, name, config)`.
|
|
240
|
-
*/
|
|
241
|
-
async updateProvider(providerId: string, nameOrConfig: any, config?: Record<string, any>) {
|
|
242
|
-
let name: string;
|
|
243
|
-
let finalConfig: Record<string, any>;
|
|
244
|
-
if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
|
|
245
|
-
finalConfig = nameOrConfig;
|
|
246
|
-
const existing = this.activeProviders.find((p) => p.id === providerId);
|
|
247
|
-
name = existing?.name || providerId;
|
|
248
|
-
} else {
|
|
249
|
-
name = String(nameOrConfig);
|
|
250
|
-
finalConfig = config || {};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const updated = await configManager.updateModelProvider(
|
|
254
|
-
providerId,
|
|
255
|
-
name,
|
|
256
|
-
finalConfig,
|
|
257
|
-
);
|
|
258
|
-
return {
|
|
259
|
-
...updated,
|
|
260
|
-
chatModels: mergeChatModels(updated),
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async addProviderModel(providerId: string, type: "chat", model: any) {
|
|
265
|
-
return configManager.addProviderModel(providerId, type, model);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async removeProviderModel(providerId: string, type: "chat", modelKey: string) {
|
|
269
|
-
configManager.removeProviderModel(providerId, type, modelKey);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Registry for managing and loading Vercel AI SDK model providers.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the full API expected by the app's API routes and chat handler:
|
|
5
|
+
* - `activeProviders` (sync getter) / `getActiveProviders()`
|
|
6
|
+
* - `isProviderEnvBased(providerId)`
|
|
7
|
+
* - `loadChatModel(providerId, modelKey)` — returns an AI SDK `LanguageModel`
|
|
8
|
+
* - provider/model CRUD passthroughs to {@link configManager}
|
|
9
|
+
*/
|
|
10
|
+
import configManager from "./config-manager";
|
|
11
|
+
import { LANGUAGE_MODELS, getGuestSafeProviders } from "./language-models-database";
|
|
12
|
+
import { getModelProvidersUIConfigSection } from "./provider-ui-config";
|
|
13
|
+
import type { ConfigModelProvider, Model } from "./config-types";
|
|
14
|
+
|
|
15
|
+
// Maps a provider UI key to the matching `provider` name in the
|
|
16
|
+
// LANGUAGE_MODELS database (e.g. the "gemini" UI key ↔ "Google" model list).
|
|
17
|
+
const PROVIDER_KEY_TO_DB_NAME: Record<string, string> = {
|
|
18
|
+
openai: "openai",
|
|
19
|
+
anthropic: "anthropic",
|
|
20
|
+
gemini: "google",
|
|
21
|
+
groq: "groq",
|
|
22
|
+
deepseek: "deepseek",
|
|
23
|
+
nvidia: "nvidia",
|
|
24
|
+
openrouter: "openrouter",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Default chat models for a provider type from the LANGUAGE_MODELS database. */
|
|
28
|
+
const getDefaultChatModels = (providerType: string): Model[] => {
|
|
29
|
+
const dbName = PROVIDER_KEY_TO_DB_NAME[providerType] ?? providerType;
|
|
30
|
+
const entry = LANGUAGE_MODELS.find(
|
|
31
|
+
(p) => p.provider.toLowerCase() === dbName.toLowerCase(),
|
|
32
|
+
);
|
|
33
|
+
if (!entry?.models) return [];
|
|
34
|
+
return entry.models.map((m: any) => ({ name: m.name, key: m.id }));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Merges default and user-added models, deduplicated by model key. */
|
|
38
|
+
const mergeChatModels = (provider: ConfigModelProvider): Model[] => {
|
|
39
|
+
const merged = new Map<string, Model>();
|
|
40
|
+
for (const m of getDefaultChatModels(provider.type)) {
|
|
41
|
+
merged.set(m.key, m);
|
|
42
|
+
}
|
|
43
|
+
for (const m of provider.chatModels || []) {
|
|
44
|
+
merged.set(m.key, { key: m.key, name: m.name });
|
|
45
|
+
}
|
|
46
|
+
return [...merged.values()];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default class ModelRegistry {
|
|
50
|
+
/**
|
|
51
|
+
* Currently configured providers (env-based + user-added).
|
|
52
|
+
* Sync getter used by the chat handler for logging and lookups.
|
|
53
|
+
*/
|
|
54
|
+
get activeProviders(): ConfigModelProvider[] {
|
|
55
|
+
return configManager.getCurrentConfig().modelProviders;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Providers with their full chat model lists (defaults merged with
|
|
60
|
+
* user-added models), shaped for the settings/providers UI.
|
|
61
|
+
*/
|
|
62
|
+
async getActiveProviders(guestMode: boolean = false) {
|
|
63
|
+
const providers = this.activeProviders;
|
|
64
|
+
const dbModels = guestMode ? getGuestSafeProviders() : LANGUAGE_MODELS;
|
|
65
|
+
|
|
66
|
+
return providers.map((p) => ({
|
|
67
|
+
id: p.id,
|
|
68
|
+
name: p.name,
|
|
69
|
+
type: p.type,
|
|
70
|
+
chatModels: guestMode ? this.getGuestChatModels(p, dbModels) : mergeChatModels(p),
|
|
71
|
+
})).filter((p) => p.chatModels.length > 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get guest-safe chat models for a provider (only tested working models).
|
|
76
|
+
*/
|
|
77
|
+
private getGuestChatModels(provider: ConfigModelProvider, guestProviders: typeof LANGUAGE_MODELS): Model[] {
|
|
78
|
+
const dbName = PROVIDER_KEY_TO_DB_NAME[provider.type.toLowerCase()];
|
|
79
|
+
const dbEntry = guestProviders.find(
|
|
80
|
+
(p) => p.provider.toLowerCase() === (dbName?.toLowerCase() || provider.type.toLowerCase()),
|
|
81
|
+
);
|
|
82
|
+
if (!dbEntry?.models) return [];
|
|
83
|
+
return dbEntry.models.map((m: any) => ({ name: m.name, key: m.id }));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Finds a provider by id, falling back to free providers in order:
|
|
88
|
+
* OpenRouter (no daily limits, best for guests), Groq (fastest, daily limits),
|
|
89
|
+
* then NVIDIA, then the first configured provider.
|
|
90
|
+
* Client-side provider ids are config hashes that go stale whenever
|
|
91
|
+
* server env config changes, so a graceful fallback keeps existing
|
|
92
|
+
* chat sessions working after a redeploy.
|
|
93
|
+
*/
|
|
94
|
+
private findProvider(providerId?: string): ConfigModelProvider | undefined {
|
|
95
|
+
const providers = this.activeProviders;
|
|
96
|
+
let provider = providers.find((p) => p.id === providerId);
|
|
97
|
+
|
|
98
|
+
if (!provider && providers.length > 0) {
|
|
99
|
+
// Prioritize OpenRouter (no daily limits) over Groq (has daily limits)
|
|
100
|
+
provider =
|
|
101
|
+
providers.find((p) => p.name.toLowerCase().includes("openrouter")) ??
|
|
102
|
+
providers.find((p) => p.name.toLowerCase().includes("groq")) ??
|
|
103
|
+
providers.find((p) => p.name.toLowerCase().includes("nvidia")) ??
|
|
104
|
+
providers[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return provider;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Whether the resolved provider was configured from environment variables. */
|
|
111
|
+
isProviderEnvBased(providerId?: string): boolean {
|
|
112
|
+
return this.findProvider(providerId)?.isEnvBased === true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Instantiates a Vercel AI SDK language model for the given provider and
|
|
117
|
+
* model key. Falls back to the provider's first/default model when no key
|
|
118
|
+
* is given. Temperature is a per-call setting in the AI SDK, so callers
|
|
119
|
+
* pass it to generateText/streamText rather than the model instance.
|
|
120
|
+
*/
|
|
121
|
+
async loadChatModel(providerId?: string, modelKey?: string): Promise<any> {
|
|
122
|
+
const provider = this.findProvider(providerId);
|
|
123
|
+
|
|
124
|
+
if (!provider) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"No model providers configured. Please add a provider in settings.",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const type = provider.type.toLowerCase();
|
|
131
|
+
const config = provider.config || {};
|
|
132
|
+
const apiKey = config.apiKey || "";
|
|
133
|
+
const availableModels = mergeChatModels(provider);
|
|
134
|
+
const modelName =
|
|
135
|
+
modelKey || availableModels[0]?.key || "";
|
|
136
|
+
|
|
137
|
+
console.log(
|
|
138
|
+
`[ModelRegistry] loadChatModel: providerId=${providerId} provider=${provider.name} type=${type} requestedModel=${modelKey ?? "(default)"} resolvedModel=${modelName}`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!modelName) {
|
|
142
|
+
throw new Error(`No chat models available for provider "${provider.name}"`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Validate that the requested model exists in the provider's model list
|
|
146
|
+
if (modelKey && !availableModels.some((m) => m.key === modelKey)) {
|
|
147
|
+
console.warn(
|
|
148
|
+
`[ModelRegistry] Requested model "${modelKey}" not found in provider "${provider.name}". Available models: ${availableModels.map((m) => m.key).join(", ")}`,
|
|
149
|
+
);
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Model "${modelKey}" is not available for provider "${provider.name}". Please select a different model in Settings.`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Validate API key is present
|
|
156
|
+
if (!apiKey) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`No API key configured for provider "${provider.name}". Please add your API key in Settings → Model Providers.`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// OpenAI-compatible providers differ only by base URL.
|
|
163
|
+
const openAICompatibleBaseURLs: Record<string, string> = {
|
|
164
|
+
openai: "https://api.openai.com/v1",
|
|
165
|
+
togetherai: "https://api.together.xyz/v1",
|
|
166
|
+
perplexity: "https://api.perplexity.ai",
|
|
167
|
+
nvidia: "https://integrate.api.nvidia.com/v1",
|
|
168
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
169
|
+
deepseek: "https://api.deepseek.com",
|
|
170
|
+
xai: "https://api.x.ai/v1",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (type in openAICompatibleBaseURLs) {
|
|
174
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
175
|
+
return createOpenAI({
|
|
176
|
+
apiKey,
|
|
177
|
+
baseURL: config.baseURL || openAICompatibleBaseURLs[type],
|
|
178
|
+
}).chat(modelName);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
switch (type) {
|
|
182
|
+
case "cloudflare": {
|
|
183
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
184
|
+
const [cfApiToken, accountId] = apiKey.split(":");
|
|
185
|
+
return createOpenAI({
|
|
186
|
+
apiKey: cfApiToken,
|
|
187
|
+
baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
|
|
188
|
+
}).chat(modelName);
|
|
189
|
+
}
|
|
190
|
+
case "groq": {
|
|
191
|
+
const { createGroq } = await import("@ai-sdk/groq");
|
|
192
|
+
return createGroq({ apiKey })(modelName);
|
|
193
|
+
}
|
|
194
|
+
case "anthropic": {
|
|
195
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
196
|
+
return createAnthropic({ apiKey })(modelName);
|
|
197
|
+
}
|
|
198
|
+
case "gemini":
|
|
199
|
+
case "google": {
|
|
200
|
+
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
201
|
+
return createGoogleGenerativeAI({ apiKey })(modelName);
|
|
202
|
+
}
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`Unsupported provider type: ${type}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Registers a new provider. Accepts both `(type, config)` — used by the
|
|
210
|
+
* providers API route — and `(type, name, config)`.
|
|
211
|
+
*/
|
|
212
|
+
async addProvider(type: string, nameOrConfig: any, config?: Record<string, any>) {
|
|
213
|
+
let name: string;
|
|
214
|
+
let finalConfig: Record<string, any>;
|
|
215
|
+
if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
|
|
216
|
+
finalConfig = nameOrConfig;
|
|
217
|
+
const section = getModelProvidersUIConfigSection().find(
|
|
218
|
+
(s) => s.key === type,
|
|
219
|
+
);
|
|
220
|
+
name = section?.name || type.toUpperCase();
|
|
221
|
+
} else {
|
|
222
|
+
name = String(nameOrConfig);
|
|
223
|
+
finalConfig = config || {};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const newProvider = configManager.addModelProvider(type, name, finalConfig);
|
|
227
|
+
return {
|
|
228
|
+
...newProvider,
|
|
229
|
+
chatModels: getDefaultChatModels(type),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async removeProvider(providerId: string) {
|
|
234
|
+
configManager.removeModelProvider(providerId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Updates a provider's config. Accepts both `(id, config)` — used by the
|
|
239
|
+
* providers API route — and `(id, name, config)`.
|
|
240
|
+
*/
|
|
241
|
+
async updateProvider(providerId: string, nameOrConfig: any, config?: Record<string, any>) {
|
|
242
|
+
let name: string;
|
|
243
|
+
let finalConfig: Record<string, any>;
|
|
244
|
+
if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
|
|
245
|
+
finalConfig = nameOrConfig;
|
|
246
|
+
const existing = this.activeProviders.find((p) => p.id === providerId);
|
|
247
|
+
name = existing?.name || providerId;
|
|
248
|
+
} else {
|
|
249
|
+
name = String(nameOrConfig);
|
|
250
|
+
finalConfig = config || {};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const updated = await configManager.updateModelProvider(
|
|
254
|
+
providerId,
|
|
255
|
+
name,
|
|
256
|
+
finalConfig,
|
|
257
|
+
);
|
|
258
|
+
return {
|
|
259
|
+
...updated,
|
|
260
|
+
chatModels: mergeChatModels(updated),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async addProviderModel(providerId: string, type: "chat", model: any) {
|
|
265
|
+
return configManager.addProviderModel(providerId, type, model);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async removeProviderModel(providerId: string, type: "chat", modelKey: string) {
|
|
269
|
+
configManager.removeProviderModel(providerId, type, modelKey);
|
|
270
|
+
}
|
|
271
|
+
}
|