chat-agent-toolkit 1.2.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.
Files changed (48) hide show
  1. package/README.md +234 -0
  2. package/package.json +101 -0
  3. package/src/config/config-manager.ts +319 -0
  4. package/src/config/config-types.ts +65 -0
  5. package/src/config/environment-variables.ts +8 -0
  6. package/src/config/index.ts +26 -0
  7. package/src/config/language-models-database.ts +1426 -0
  8. package/src/config/mcp-server-registry.ts +24 -0
  9. package/src/config/model-registry.ts +256 -0
  10. package/src/config/model-tester.ts +211 -0
  11. package/src/config/model-utils.ts +193 -0
  12. package/src/config/provider-ui-config.ts +196 -0
  13. package/src/connectors/composio.json +154 -0
  14. package/src/index.ts +22 -0
  15. package/src/memory/ARCHITECTURE.md +302 -0
  16. package/src/memory/MASTRA_INTEGRATION.md +106 -0
  17. package/src/memory/README.md +224 -0
  18. package/src/memory/agent-memory-manager.ts +416 -0
  19. package/src/memory/example.ts +343 -0
  20. package/src/memory/index.ts +47 -0
  21. package/src/memory/mastra-integration.ts +609 -0
  22. package/src/memory/storage/drizzle-storage.ts +205 -0
  23. package/src/memory/storage/in-memory-storage.ts +551 -0
  24. package/src/memory/storage/storage-interface.ts +68 -0
  25. package/src/memory/types.ts +125 -0
  26. package/src/models/providers.ts +5 -0
  27. package/src/models/registry.ts +4 -0
  28. package/src/models/types.ts +4 -0
  29. package/src/search.ts +5 -0
  30. package/src/tools/composio-mastra.ts +305 -0
  31. package/src/tools/composio-mcp.ts +205 -0
  32. package/src/tools/index.ts +13 -0
  33. package/src/tools/qwksearch-api-tools.ts +327 -0
  34. package/src/tools/search/doc-utils.ts +75 -0
  35. package/src/tools/search/document.ts +47 -0
  36. package/src/tools/search/index.ts +13 -0
  37. package/src/tools/search/link-summarizer.ts +112 -0
  38. package/src/tools/search/meta-search-types.ts +62 -0
  39. package/src/tools/search/metaSearchAgent.ts +454 -0
  40. package/src/tools/search/search-handlers.ts +85 -0
  41. package/src/tools/search/suggestionGeneratorAgent.ts +54 -0
  42. package/src/types.d.ts +137 -0
  43. package/src/utils/README.md +98 -0
  44. package/src/utils/chat-helpers.ts +18 -0
  45. package/src/utils/index.ts +2 -0
  46. package/src/utils/markdown-to-html.ts +53 -0
  47. package/src/utils/outputParser.ts +73 -0
  48. package/src/utils/provider-image-cropper.ts +130 -0
@@ -0,0 +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;
@@ -0,0 +1,256 @@
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 } 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() {
63
+ return this.activeProviders.map((p) => ({
64
+ id: p.id,
65
+ name: p.name,
66
+ type: p.type,
67
+ chatModels: mergeChatModels(p),
68
+ }));
69
+ }
70
+
71
+ /**
72
+ * Finds a provider by id, falling back to free providers in order:
73
+ * OpenRouter (no daily limits, best for guests), Groq (fastest, daily limits),
74
+ * then NVIDIA, then the first configured provider.
75
+ * Client-side provider ids are config hashes that go stale whenever
76
+ * server env config changes, so a graceful fallback keeps existing
77
+ * chat sessions working after a redeploy.
78
+ */
79
+ private findProvider(providerId?: string): ConfigModelProvider | undefined {
80
+ const providers = this.activeProviders;
81
+ let provider = providers.find((p) => p.id === providerId);
82
+
83
+ if (!provider && providers.length > 0) {
84
+ // Prioritize OpenRouter (no daily limits) over Groq (has daily limits)
85
+ provider =
86
+ providers.find((p) => p.name.toLowerCase().includes("openrouter")) ??
87
+ providers.find((p) => p.name.toLowerCase().includes("groq")) ??
88
+ providers.find((p) => p.name.toLowerCase().includes("nvidia")) ??
89
+ providers[0];
90
+ }
91
+
92
+ return provider;
93
+ }
94
+
95
+ /** Whether the resolved provider was configured from environment variables. */
96
+ isProviderEnvBased(providerId?: string): boolean {
97
+ return this.findProvider(providerId)?.isEnvBased === true;
98
+ }
99
+
100
+ /**
101
+ * Instantiates a Vercel AI SDK language model for the given provider and
102
+ * model key. Falls back to the provider's first/default model when no key
103
+ * is given. Temperature is a per-call setting in the AI SDK, so callers
104
+ * pass it to generateText/streamText rather than the model instance.
105
+ */
106
+ async loadChatModel(providerId?: string, modelKey?: string): Promise<any> {
107
+ const provider = this.findProvider(providerId);
108
+
109
+ if (!provider) {
110
+ throw new Error(
111
+ "No model providers configured. Please add a provider in settings.",
112
+ );
113
+ }
114
+
115
+ const type = provider.type.toLowerCase();
116
+ const config = provider.config || {};
117
+ const apiKey = config.apiKey || "";
118
+ const availableModels = mergeChatModels(provider);
119
+ const modelName =
120
+ modelKey || availableModels[0]?.key || "";
121
+
122
+ console.log(
123
+ `[ModelRegistry] loadChatModel: providerId=${providerId} provider=${provider.name} type=${type} requestedModel=${modelKey ?? "(default)"} resolvedModel=${modelName}`,
124
+ );
125
+
126
+ if (!modelName) {
127
+ throw new Error(`No chat models available for provider "${provider.name}"`);
128
+ }
129
+
130
+ // Validate that the requested model exists in the provider's model list
131
+ if (modelKey && !availableModels.some((m) => m.key === modelKey)) {
132
+ console.warn(
133
+ `[ModelRegistry] Requested model "${modelKey}" not found in provider "${provider.name}". Available models: ${availableModels.map((m) => m.key).join(", ")}`,
134
+ );
135
+ throw new Error(
136
+ `Model "${modelKey}" is not available for provider "${provider.name}". Please select a different model in Settings.`,
137
+ );
138
+ }
139
+
140
+ // Validate API key is present
141
+ if (!apiKey) {
142
+ throw new Error(
143
+ `No API key configured for provider "${provider.name}". Please add your API key in Settings → Model Providers.`,
144
+ );
145
+ }
146
+
147
+ // OpenAI-compatible providers differ only by base URL.
148
+ const openAICompatibleBaseURLs: Record<string, string> = {
149
+ openai: "https://api.openai.com/v1",
150
+ togetherai: "https://api.together.xyz/v1",
151
+ perplexity: "https://api.perplexity.ai",
152
+ nvidia: "https://integrate.api.nvidia.com/v1",
153
+ openrouter: "https://openrouter.ai/api/v1",
154
+ deepseek: "https://api.deepseek.com",
155
+ xai: "https://api.x.ai/v1",
156
+ };
157
+
158
+ if (type in openAICompatibleBaseURLs) {
159
+ const { createOpenAI } = await import("@ai-sdk/openai");
160
+ return createOpenAI({
161
+ apiKey,
162
+ baseURL: config.baseURL || openAICompatibleBaseURLs[type],
163
+ }).chat(modelName);
164
+ }
165
+
166
+ switch (type) {
167
+ case "cloudflare": {
168
+ const { createOpenAI } = await import("@ai-sdk/openai");
169
+ const [cfApiToken, accountId] = apiKey.split(":");
170
+ return createOpenAI({
171
+ apiKey: cfApiToken,
172
+ baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
173
+ }).chat(modelName);
174
+ }
175
+ case "groq": {
176
+ const { createGroq } = await import("@ai-sdk/groq");
177
+ return createGroq({ apiKey })(modelName);
178
+ }
179
+ case "anthropic": {
180
+ const { createAnthropic } = await import("@ai-sdk/anthropic");
181
+ return createAnthropic({ apiKey })(modelName);
182
+ }
183
+ case "gemini":
184
+ case "google": {
185
+ const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
186
+ return createGoogleGenerativeAI({ apiKey })(modelName);
187
+ }
188
+ default:
189
+ throw new Error(`Unsupported provider type: ${type}`);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Registers a new provider. Accepts both `(type, config)` — used by the
195
+ * providers API route — and `(type, name, config)`.
196
+ */
197
+ async addProvider(type: string, nameOrConfig: any, config?: Record<string, any>) {
198
+ let name: string;
199
+ let finalConfig: Record<string, any>;
200
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
201
+ finalConfig = nameOrConfig;
202
+ const section = getModelProvidersUIConfigSection().find(
203
+ (s) => s.key === type,
204
+ );
205
+ name = section?.name || type.toUpperCase();
206
+ } else {
207
+ name = String(nameOrConfig);
208
+ finalConfig = config || {};
209
+ }
210
+
211
+ const newProvider = configManager.addModelProvider(type, name, finalConfig);
212
+ return {
213
+ ...newProvider,
214
+ chatModels: getDefaultChatModels(type),
215
+ };
216
+ }
217
+
218
+ async removeProvider(providerId: string) {
219
+ configManager.removeModelProvider(providerId);
220
+ }
221
+
222
+ /**
223
+ * Updates a provider's config. Accepts both `(id, config)` — used by the
224
+ * providers API route — and `(id, name, config)`.
225
+ */
226
+ async updateProvider(providerId: string, nameOrConfig: any, config?: Record<string, any>) {
227
+ let name: string;
228
+ let finalConfig: Record<string, any>;
229
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null && !config) {
230
+ finalConfig = nameOrConfig;
231
+ const existing = this.activeProviders.find((p) => p.id === providerId);
232
+ name = existing?.name || providerId;
233
+ } else {
234
+ name = String(nameOrConfig);
235
+ finalConfig = config || {};
236
+ }
237
+
238
+ const updated = await configManager.updateModelProvider(
239
+ providerId,
240
+ name,
241
+ finalConfig,
242
+ );
243
+ return {
244
+ ...updated,
245
+ chatModels: mergeChatModels(updated),
246
+ };
247
+ }
248
+
249
+ async addProviderModel(providerId: string, type: "chat", model: any) {
250
+ return configManager.addProviderModel(providerId, type, model);
251
+ }
252
+
253
+ async removeProviderModel(providerId: string, type: "chat", modelKey: string) {
254
+ configManager.removeProviderModel(providerId, type, modelKey);
255
+ }
256
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * @fileoverview Model availability testing and validation
3
+ * Tests which models are actually working for a given provider
4
+ */
5
+
6
+ import { generateText } from "ai";
7
+ import { createLLMProvider } from "write-language/provider-factory";
8
+
9
+ export interface ModelTestResult {
10
+ modelId: string;
11
+ modelName: string;
12
+ available: boolean;
13
+ error?: string;
14
+ latency?: number;
15
+ type?: string;
16
+ }
17
+
18
+ export interface ProviderTestResult {
19
+ provider: string;
20
+ totalModels: number;
21
+ availableModels: ModelTestResult[];
22
+ unavailableModels: ModelTestResult[];
23
+ testDuration: number;
24
+ }
25
+
26
+ /**
27
+ * Test a single model to see if it's working
28
+ */
29
+ export async function testModel(
30
+ provider: string,
31
+ apiKey: string,
32
+ modelId: string,
33
+ modelName: string,
34
+ modelType: string = "text-generation",
35
+ timeout: number = 10000
36
+ ): Promise<ModelTestResult> {
37
+ const startTime = Date.now();
38
+
39
+ try {
40
+ // Only test text-generation models
41
+ if (modelType !== "text-generation") {
42
+ return {
43
+ modelId,
44
+ modelName,
45
+ available: false,
46
+ error: `Skipped: ${modelType} models not testable via text generation`,
47
+ type: modelType,
48
+ };
49
+ }
50
+
51
+ const model = createLLMProvider(
52
+ provider.toLowerCase(),
53
+ apiKey,
54
+ modelId,
55
+ 0.1
56
+ );
57
+
58
+ if (!model) {
59
+ return {
60
+ modelId,
61
+ modelName,
62
+ available: false,
63
+ error: "Provider not supported",
64
+ type: modelType,
65
+ };
66
+ }
67
+
68
+ // Create a promise that will timeout
69
+ const timeoutPromise = new Promise((_, reject) =>
70
+ setTimeout(() => reject(new Error("Test timeout")), timeout)
71
+ );
72
+
73
+ // Test with a simple prompt
74
+ const testPromise = generateText({
75
+ model,
76
+ prompt: "Reply with just 'OK'",
77
+ maxTokens: 10,
78
+ });
79
+
80
+ const result = await Promise.race([testPromise, timeoutPromise]) as any;
81
+
82
+ const latency = Date.now() - startTime;
83
+
84
+ return {
85
+ modelId,
86
+ modelName,
87
+ available: true,
88
+ latency,
89
+ type: modelType,
90
+ };
91
+ } catch (error: any) {
92
+ return {
93
+ modelId,
94
+ modelName,
95
+ available: false,
96
+ error: error.message || String(error),
97
+ latency: Date.now() - startTime,
98
+ type: modelType,
99
+ };
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Test all models for a provider
105
+ */
106
+ export async function testProviderModels(
107
+ provider: string,
108
+ apiKey: string,
109
+ models: Array<{ id: string; name: string; type?: string; free?: boolean }>,
110
+ options: {
111
+ onlyFree?: boolean;
112
+ concurrency?: number;
113
+ timeout?: number;
114
+ onProgress?: (current: number, total: number, modelName: string) => void;
115
+ } = {}
116
+ ): Promise<ProviderTestResult> {
117
+ const {
118
+ onlyFree = false,
119
+ concurrency = 3,
120
+ timeout = 10000,
121
+ onProgress,
122
+ } = options;
123
+
124
+ const startTime = Date.now();
125
+
126
+ // Filter models
127
+ let modelsToTest = models;
128
+ if (onlyFree) {
129
+ modelsToTest = models.filter((m) => m.free !== false);
130
+ }
131
+
132
+ const results: ModelTestResult[] = [];
133
+ const queue = [...modelsToTest];
134
+ let completed = 0;
135
+
136
+ // Test models with concurrency control
137
+ const workers = Array(Math.min(concurrency, modelsToTest.length))
138
+ .fill(null)
139
+ .map(async () => {
140
+ while (queue.length > 0) {
141
+ const model = queue.shift();
142
+ if (!model) break;
143
+
144
+ const result = await testModel(
145
+ provider,
146
+ apiKey,
147
+ model.id,
148
+ model.name,
149
+ model.type || "text-generation",
150
+ timeout
151
+ );
152
+
153
+ results.push(result);
154
+ completed++;
155
+
156
+ if (onProgress) {
157
+ onProgress(completed, modelsToTest.length, model.name);
158
+ }
159
+ }
160
+ });
161
+
162
+ await Promise.all(workers);
163
+
164
+ const availableModels = results.filter((r) => r.available);
165
+ const unavailableModels = results.filter((r) => !r.available);
166
+
167
+ return {
168
+ provider,
169
+ totalModels: modelsToTest.length,
170
+ availableModels,
171
+ unavailableModels,
172
+ testDuration: Date.now() - startTime,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Get only free models from a model list
178
+ */
179
+ export function getOnlyFreeModels<T extends { free?: boolean }>(
180
+ models: T[]
181
+ ): T[] {
182
+ return models.filter((m) => m.free === true);
183
+ }
184
+
185
+ /**
186
+ * Categorize models by type
187
+ */
188
+ export function categorizeModelsByType<
189
+ T extends { type?: string; id: string; name: string }
190
+ >(models: T[]): Record<string, T[]> {
191
+ const categories: Record<string, T[]> = {
192
+ "text-generation": [],
193
+ vision: [],
194
+ embedding: [],
195
+ reranker: [],
196
+ image: [],
197
+ video: [],
198
+ audio: [],
199
+ other: [],
200
+ };
201
+
202
+ for (const model of models) {
203
+ const type = model.type || "text-generation";
204
+ if (!categories[type]) {
205
+ categories[type] = [];
206
+ }
207
+ categories[type].push(model);
208
+ }
209
+
210
+ return categories;
211
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Utility functions for working with AI models
3
+ */
4
+
5
+ /**
6
+ * List of model IDs that are free to use (no per-token cost)
7
+ * Updated regularly as providers change their pricing
8
+ */
9
+ export const FREE_MODELS = new Set([
10
+ // OpenRouter free models
11
+ "meta-llama/llama-3.3-70b-instruct",
12
+ "nvidia/llama-3.1-nemotron-70b-instruct",
13
+ "qwen/qwen-2.5-72b-instruct",
14
+ "deepseek/deepseek-v3",
15
+ "deepseek/deepseek-r1",
16
+ "meta-llama/llama-4-scout-17b-16e-instruct",
17
+
18
+ // NVIDIA NIM free tier models
19
+ "moonshotai/kimi-k2.5",
20
+ "nvidia/nemotron-nano-12b-v2-vl",
21
+ "nvidia/llama-nemotron",
22
+ "qwen/qwen2.5",
23
+ "meta/llama-4",
24
+
25
+ // Groq free tier models
26
+ "deepseek-r1-distill-llama-70b",
27
+ "meta-llama/llama-4-maverick-17b-128e-instruct",
28
+ "meta-llama/llama-4-scout-17b-16e-instruct",
29
+ "llama-3.3-70b-versatile",
30
+ "llama-3.3-70b-specdec",
31
+ "llama-3.2-3b-preview",
32
+ "llama-3.2-11b-vision-preview",
33
+ "llama-3.2-90b-vision-preview",
34
+ "llama-3.1-70b-versatile",
35
+ "llama-3.1-8b-instant",
36
+ "mixtral-8x7b-32768",
37
+
38
+ // Cloudflare Workers AI (free tier)
39
+ "llama-4-scout-17b-16e-instruct",
40
+ "llama-3.3-70b-instruct-fp8-fast",
41
+ "llama-3.1-8b-instruct-fast",
42
+ "gemma-3-12b-it",
43
+ "mistral-small-3.1-24b-instruct",
44
+ "qwq-32b",
45
+ "qwen2.5-coder-32b-instruct",
46
+ "deepseek-r1-distill-qwen-32b",
47
+ ]);
48
+
49
+ /**
50
+ * Providers that offer free tiers with generous limits
51
+ */
52
+ export const FREE_TIER_PROVIDERS = new Set([
53
+ "openrouter", // Free models available
54
+ "nvidia", // Free NIM tier
55
+ "groq", // Free tier with rate limits
56
+ ]);
57
+
58
+ /**
59
+ * Check if a model is free to use (no per-token cost)
60
+ * Note: Free models may have rate limits
61
+ */
62
+ export function isModelFree(modelId: string): boolean {
63
+ return FREE_MODELS.has(modelId);
64
+ }
65
+
66
+ /**
67
+ * Check if a provider offers a free tier
68
+ */
69
+ export function hasFreeTier(providerType: string): boolean {
70
+ return FREE_TIER_PROVIDERS.has(providerType.toLowerCase());
71
+ }
72
+
73
+ /**
74
+ * Get a display name for a model with cost indicator
75
+ * Example: "Llama 3.3 70B (Free)" or "GPT-4o"
76
+ */
77
+ export function getModelDisplayName(modelName: string, modelId: string): string {
78
+ const isFree = isModelFree(modelId);
79
+
80
+ // If already has "(Free)" in the name, return as-is
81
+ if (modelName.includes("(Free)")) {
82
+ return modelName;
83
+ }
84
+
85
+ return isFree ? `${modelName} (Free)` : modelName;
86
+ }
87
+
88
+ /**
89
+ * Get provider-specific information
90
+ */
91
+ export function getProviderInfo(providerType: string): {
92
+ name: string;
93
+ hasFreeTier: boolean;
94
+ signupUrl: string;
95
+ docsUrl: string;
96
+ } {
97
+ const type = providerType.toLowerCase();
98
+
99
+ const providers: Record<string, any> = {
100
+ openrouter: {
101
+ name: "OpenRouter",
102
+ hasFreeTier: true,
103
+ signupUrl: "https://openrouter.ai",
104
+ docsUrl: "https://openrouter.ai/docs",
105
+ },
106
+ nvidia: {
107
+ name: "NVIDIA",
108
+ hasFreeTier: true,
109
+ signupUrl: "https://build.nvidia.com",
110
+ docsUrl: "https://docs.api.nvidia.com/nim/",
111
+ },
112
+ groq: {
113
+ name: "Groq",
114
+ hasFreeTier: true,
115
+ signupUrl: "https://console.groq.com",
116
+ docsUrl: "https://console.groq.com/docs",
117
+ },
118
+ anthropic: {
119
+ name: "Anthropic",
120
+ hasFreeTier: false,
121
+ signupUrl: "https://console.anthropic.com",
122
+ docsUrl: "https://docs.anthropic.com",
123
+ },
124
+ openai: {
125
+ name: "OpenAI",
126
+ hasFreeTier: false,
127
+ signupUrl: "https://platform.openai.com",
128
+ docsUrl: "https://platform.openai.com/docs",
129
+ },
130
+ google: {
131
+ name: "Google Gemini",
132
+ hasFreeTier: true,
133
+ signupUrl: "https://aistudio.google.com",
134
+ docsUrl: "https://ai.google.dev/docs",
135
+ },
136
+ gemini: {
137
+ name: "Google Gemini",
138
+ hasFreeTier: true,
139
+ signupUrl: "https://aistudio.google.com",
140
+ docsUrl: "https://ai.google.dev/docs",
141
+ },
142
+ deepseek: {
143
+ name: "DeepSeek",
144
+ hasFreeTier: false,
145
+ signupUrl: "https://platform.deepseek.com",
146
+ docsUrl: "https://platform.deepseek.com/docs",
147
+ },
148
+ cloudflare: {
149
+ name: "Cloudflare Workers AI",
150
+ hasFreeTier: true,
151
+ signupUrl: "https://dash.cloudflare.com",
152
+ docsUrl: "https://developers.cloudflare.com/workers-ai/",
153
+ },
154
+ };
155
+
156
+ return providers[type] || {
157
+ name: providerType,
158
+ hasFreeTier: false,
159
+ signupUrl: "",
160
+ docsUrl: "",
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Get recommended free providers for getting started
166
+ */
167
+ export function getRecommendedFreeProviders(): Array<{
168
+ type: string;
169
+ name: string;
170
+ reason: string;
171
+ signupUrl: string;
172
+ }> {
173
+ return [
174
+ {
175
+ type: "openrouter",
176
+ name: "OpenRouter",
177
+ reason: "Access to Llama 3.3 70B, Nemotron, and other free models",
178
+ signupUrl: "https://openrouter.ai",
179
+ },
180
+ {
181
+ type: "groq",
182
+ name: "Groq",
183
+ reason: "Ultra-fast inference on Llama models, generous free tier",
184
+ signupUrl: "https://console.groq.com",
185
+ },
186
+ {
187
+ type: "nvidia",
188
+ name: "NVIDIA",
189
+ reason: "Free access to Nemotron and other powerful models",
190
+ signupUrl: "https://build.nvidia.com",
191
+ },
192
+ ];
193
+ }