chat-agent-toolkit 1.2.32 → 1.2.34

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.
Files changed (67) hide show
  1. package/dist/research-agent.cjs.js +1 -1
  2. package/dist/research-agent.cjs.js.map +1 -1
  3. package/dist/research-agent.es.js +1 -1
  4. package/dist/research-agent.es.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/config/config-manager.ts +295 -295
  7. package/src/config/config-types.ts +65 -65
  8. package/src/config/environment-variables.ts +16 -16
  9. package/src/config/index.ts +26 -26
  10. package/src/config/language-models-database.ts +1434 -1434
  11. package/src/config/mcp-server-registry.ts +24 -24
  12. package/src/config/model-registry.ts +271 -271
  13. package/src/config/model-tester.ts +211 -211
  14. package/src/config/model-utils.ts +193 -193
  15. package/src/config/provider-ui-config.ts +196 -196
  16. package/src/connectors/open-connector.json +130 -130
  17. package/src/index.ts +26 -26
  18. package/src/memory/ARCHITECTURE.md +302 -302
  19. package/src/memory/README.md +224 -224
  20. package/src/memory/agent-memory-manager.ts +416 -416
  21. package/src/memory/example.ts +343 -343
  22. package/src/memory/index.ts +47 -47
  23. package/src/memory/mastra-integration.ts +604 -604
  24. package/src/memory/storage/drizzle-storage.ts +236 -236
  25. package/src/memory/storage/in-memory-storage.ts +551 -551
  26. package/src/memory/storage/storage-interface.ts +68 -68
  27. package/src/memory/types.ts +125 -125
  28. package/src/models/types.ts +4 -4
  29. package/src/provider-logos/01-ai.png +0 -0
  30. package/src/provider-logos/anthropic.png +0 -0
  31. package/src/provider-logos/aws-bedrock.png +0 -0
  32. package/src/provider-logos/aws-sagemaker.png +0 -0
  33. package/src/provider-logos/azure-openai-service.png +0 -0
  34. package/src/provider-logos/chatglm.png +0 -0
  35. package/src/provider-logos/cohere.png +0 -0
  36. package/src/provider-logos/gemini.png +0 -0
  37. package/src/provider-logos/groqcloud.png +0 -0
  38. package/src/provider-logos/huggingface.png +0 -0
  39. package/src/provider-logos/jina.png +0 -0
  40. package/src/provider-logos/localai.png +0 -0
  41. package/src/provider-logos/mistral-ai.png +0 -0
  42. package/src/provider-logos/moonshot-ai.png +0 -0
  43. package/src/provider-logos/ollama.png +0 -0
  44. package/src/provider-logos/openai.png +0 -0
  45. package/src/provider-logos/openllm.png +0 -0
  46. package/src/provider-logos/openrouter.png +0 -0
  47. package/src/provider-logos/replicate.png +0 -0
  48. package/src/provider-logos/together-ai.png +0 -0
  49. package/src/provider-logos/tongyi.png +0 -0
  50. package/src/provider-logos/xorbits-inference.png +0 -0
  51. package/src/provider-logos/zhipu-ai.png +0 -0
  52. package/src/tools/index.ts +13 -13
  53. package/src/tools/open-connector-mastra.ts +270 -270
  54. package/src/tools/open-connector-mcp.ts +170 -170
  55. package/src/tools/qwksearch-api-tools.ts +326 -326
  56. package/src/tools/search/doc-utils.ts +139 -139
  57. package/src/tools/search/document.ts +47 -47
  58. package/src/tools/search/index.ts +14 -14
  59. package/src/tools/search/link-summarizer.ts +112 -112
  60. package/src/tools/search/meta-search-types.ts +62 -62
  61. package/src/tools/search/metaSearchAgent.ts +465 -465
  62. package/src/tools/search/search-handlers.ts +97 -97
  63. package/src/tools/search/suggestionGeneratorAgent.ts +56 -56
  64. package/src/types.d.ts +137 -137
  65. package/src/utils/index.ts +2 -2
  66. package/src/utils/markdown-to-html.ts +53 -53
  67. 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
+ }