ubersearch 0.0.0-development

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +374 -0
  3. package/package.json +76 -0
  4. package/src/app/index.ts +30 -0
  5. package/src/bootstrap/container.ts +157 -0
  6. package/src/cli.ts +380 -0
  7. package/src/config/defineConfig.ts +176 -0
  8. package/src/config/load.ts +368 -0
  9. package/src/config/types.ts +86 -0
  10. package/src/config/validation.ts +148 -0
  11. package/src/core/cache.ts +74 -0
  12. package/src/core/container.ts +268 -0
  13. package/src/core/credits/CreditManager.ts +158 -0
  14. package/src/core/credits/CreditStateProvider.ts +151 -0
  15. package/src/core/credits/FileCreditStateProvider.ts +137 -0
  16. package/src/core/credits/index.ts +3 -0
  17. package/src/core/docker/dockerComposeHelper.ts +177 -0
  18. package/src/core/docker/dockerLifecycleManager.ts +361 -0
  19. package/src/core/docker/index.ts +8 -0
  20. package/src/core/logger.ts +146 -0
  21. package/src/core/orchestrator.ts +103 -0
  22. package/src/core/paths.ts +157 -0
  23. package/src/core/provider/ILifecycleProvider.ts +120 -0
  24. package/src/core/provider/ProviderFactory.ts +120 -0
  25. package/src/core/provider.ts +61 -0
  26. package/src/core/serviceKeys.ts +45 -0
  27. package/src/core/strategy/AllProvidersStrategy.ts +245 -0
  28. package/src/core/strategy/FirstSuccessStrategy.ts +98 -0
  29. package/src/core/strategy/ISearchStrategy.ts +94 -0
  30. package/src/core/strategy/StrategyFactory.ts +204 -0
  31. package/src/core/strategy/index.ts +9 -0
  32. package/src/core/strategy/types.ts +56 -0
  33. package/src/core/types.ts +58 -0
  34. package/src/index.ts +1 -0
  35. package/src/plugin/PluginRegistry.ts +336 -0
  36. package/src/plugin/builtin.ts +130 -0
  37. package/src/plugin/index.ts +33 -0
  38. package/src/plugin/types.ts +212 -0
  39. package/src/providers/BaseProvider.ts +49 -0
  40. package/src/providers/brave.ts +66 -0
  41. package/src/providers/constants.ts +13 -0
  42. package/src/providers/helpers/index.ts +24 -0
  43. package/src/providers/helpers/lifecycleHelpers.ts +110 -0
  44. package/src/providers/helpers/resultMappers.ts +168 -0
  45. package/src/providers/index.ts +6 -0
  46. package/src/providers/linkup.ts +114 -0
  47. package/src/providers/retry.ts +95 -0
  48. package/src/providers/searchxng.ts +163 -0
  49. package/src/providers/tavily.ts +73 -0
  50. package/src/providers/types/index.ts +185 -0
  51. package/src/providers/utils.ts +182 -0
  52. package/src/tool/allSearchTool.ts +110 -0
  53. package/src/tool/interface.ts +71 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Core search types and error handling
3
+ */
4
+
5
+ export type EngineId = string;
6
+
7
+ export interface SearchQuery {
8
+ query: string;
9
+ limit?: number;
10
+ includeRaw?: boolean;
11
+ }
12
+
13
+ export interface SearchResultItem {
14
+ title: string;
15
+ url: string;
16
+ snippet: string;
17
+ score?: number;
18
+ sourceEngine: EngineId;
19
+ }
20
+
21
+ export interface SearchResponse {
22
+ engineId: EngineId;
23
+ items: SearchResultItem[];
24
+ raw?: unknown;
25
+ tookMs: number;
26
+ }
27
+
28
+ export type SearchFailureReason =
29
+ | "network_error"
30
+ | "api_error"
31
+ | "no_results"
32
+ | "low_credit"
33
+ | "config_error"
34
+ | "no_provider"
35
+ | "provider_unavailable"
36
+ | "unknown";
37
+
38
+ /**
39
+ * Error thrown when a search provider fails
40
+ */
41
+ export class SearchError extends Error {
42
+ engineId: EngineId;
43
+ reason: SearchFailureReason;
44
+ statusCode?: number;
45
+
46
+ constructor(
47
+ engineId: EngineId,
48
+ reason: SearchFailureReason,
49
+ message: string,
50
+ statusCode?: number,
51
+ ) {
52
+ super(message);
53
+ this.name = "SearchError";
54
+ this.engineId = engineId;
55
+ this.reason = reason;
56
+ this.statusCode = statusCode;
57
+ }
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./app/index";
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Plugin Registry
3
+ *
4
+ * Central registry for managing search provider plugins.
5
+ * Handles plugin registration, lookup, and provider creation.
6
+ */
7
+
8
+ import type { EngineConfigBase } from "../config/types";
9
+ import type {
10
+ CreateProviderOptions,
11
+ ManagedProvider,
12
+ PluginDefinition,
13
+ PluginInfo,
14
+ PluginRegistrationOptions,
15
+ PluginRegistrationResult,
16
+ } from "./types";
17
+
18
+ /**
19
+ * Central registry for search provider plugins
20
+ *
21
+ * The PluginRegistry provides:
22
+ * - Plugin registration with optional overwrite
23
+ * - Plugin lookup by type
24
+ * - Provider creation from registered plugins
25
+ * - Plugin metadata queries
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Get singleton instance
30
+ * const registry = PluginRegistry.getInstance();
31
+ *
32
+ * // Register a plugin
33
+ * registry.register(myPlugin);
34
+ *
35
+ * // Create a provider
36
+ * const provider = registry.createProvider('my-provider', config);
37
+ *
38
+ * // Check if plugin exists
39
+ * if (registry.has('my-provider')) { ... }
40
+ * ```
41
+ */
42
+ export class PluginRegistry {
43
+ private static instance: PluginRegistry | null = null;
44
+
45
+ /** Registered plugins by type */
46
+ private plugins = new Map<string, PluginDefinition>();
47
+
48
+ /** Track which plugins are built-in */
49
+ private builtInTypes = new Set<string>();
50
+
51
+ /**
52
+ * Get the singleton instance of PluginRegistry
53
+ */
54
+ static getInstance(): PluginRegistry {
55
+ if (!PluginRegistry.instance) {
56
+ PluginRegistry.instance = new PluginRegistry();
57
+ }
58
+ return PluginRegistry.instance;
59
+ }
60
+
61
+ /**
62
+ * Reset the singleton instance (useful for testing)
63
+ */
64
+ static resetInstance(): void {
65
+ PluginRegistry.instance = null;
66
+ }
67
+
68
+ /**
69
+ * Private constructor - use getInstance()
70
+ */
71
+ private constructor() {}
72
+
73
+ /**
74
+ * Register a plugin
75
+ *
76
+ * @param plugin - Plugin definition to register
77
+ * @param options - Registration options
78
+ * @returns Registration result
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const result = registry.register(myPlugin);
83
+ * if (!result.success) {
84
+ * console.error(result.message);
85
+ * }
86
+ * ```
87
+ */
88
+ async register(
89
+ plugin: PluginDefinition,
90
+ options: PluginRegistrationOptions = {},
91
+ ): Promise<PluginRegistrationResult> {
92
+ const { overwrite = false } = options;
93
+
94
+ // Check for existing registration
95
+ if (this.plugins.has(plugin.type)) {
96
+ if (!overwrite) {
97
+ return {
98
+ success: false,
99
+ type: plugin.type,
100
+ message: `Plugin type '${plugin.type}' is already registered. Use overwrite option to replace.`,
101
+ };
102
+ }
103
+
104
+ // Call onUnregister for existing plugin if it exists
105
+ const existing = this.plugins.get(plugin.type);
106
+ if (existing?.onUnregister) {
107
+ try {
108
+ await existing.onUnregister();
109
+ } catch (error) {
110
+ // Log but don't fail - we still want to register the new plugin
111
+ console.warn(`Error during onUnregister for plugin '${plugin.type}':`, error);
112
+ }
113
+ }
114
+ }
115
+
116
+ // Register the plugin
117
+ this.plugins.set(plugin.type, plugin);
118
+
119
+ // Call onRegister if provided
120
+ if (plugin.onRegister) {
121
+ try {
122
+ await plugin.onRegister();
123
+ } catch (error) {
124
+ // Rollback registration on error
125
+ this.plugins.delete(plugin.type);
126
+ return {
127
+ success: false,
128
+ type: plugin.type,
129
+ message: `Plugin onRegister failed: ${error instanceof Error ? error.message : String(error)}`,
130
+ };
131
+ }
132
+ }
133
+
134
+ return {
135
+ success: true,
136
+ type: plugin.type,
137
+ overwritten: overwrite && this.plugins.has(plugin.type),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Register a plugin synchronously (no lifecycle hooks called)
143
+ */
144
+ registerSync(
145
+ plugin: PluginDefinition,
146
+ options: PluginRegistrationOptions = {},
147
+ ): PluginRegistrationResult {
148
+ const { overwrite = false } = options;
149
+
150
+ if (this.plugins.has(plugin.type) && !overwrite) {
151
+ return {
152
+ success: false,
153
+ type: plugin.type,
154
+ message: `Plugin type '${plugin.type}' is already registered.`,
155
+ };
156
+ }
157
+
158
+ this.plugins.set(plugin.type, plugin);
159
+
160
+ return {
161
+ success: true,
162
+ type: plugin.type,
163
+ overwritten: overwrite && this.plugins.has(plugin.type),
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Mark a plugin type as built-in
169
+ * Built-in plugins are identified in metadata
170
+ */
171
+ markBuiltIn(type: string): void {
172
+ this.builtInTypes.add(type);
173
+ }
174
+
175
+ /**
176
+ * Unregister a plugin
177
+ *
178
+ * @param type - Plugin type to unregister
179
+ * @returns true if plugin was removed, false if not found
180
+ */
181
+ async unregister(type: string): Promise<boolean> {
182
+ const plugin = this.plugins.get(type);
183
+ if (!plugin) {
184
+ return false;
185
+ }
186
+
187
+ // Call onUnregister if provided
188
+ if (plugin.onUnregister) {
189
+ await plugin.onUnregister();
190
+ }
191
+
192
+ this.plugins.delete(type);
193
+ this.builtInTypes.delete(type);
194
+ return true;
195
+ }
196
+
197
+ /**
198
+ * Check if a plugin type is registered
199
+ */
200
+ has(type: string): boolean {
201
+ return this.plugins.has(type);
202
+ }
203
+
204
+ /**
205
+ * Get a plugin by type
206
+ */
207
+ get(type: string): PluginDefinition | undefined {
208
+ return this.plugins.get(type);
209
+ }
210
+
211
+ /**
212
+ * Get all registered plugin types
213
+ */
214
+ getTypes(): string[] {
215
+ return Array.from(this.plugins.keys());
216
+ }
217
+
218
+ /**
219
+ * Get metadata for all registered plugins
220
+ */
221
+ listPlugins(): PluginInfo[] {
222
+ return Array.from(this.plugins.values()).map((plugin) => ({
223
+ type: plugin.type,
224
+ displayName: plugin.displayName,
225
+ description: plugin.description,
226
+ docsUrl: plugin.docsUrl,
227
+ version: plugin.version,
228
+ hasLifecycle: plugin.hasLifecycle,
229
+ isBuiltIn: this.builtInTypes.has(plugin.type),
230
+ }));
231
+ }
232
+
233
+ /**
234
+ * Get metadata for a specific plugin
235
+ */
236
+ getPluginInfo(type: string): PluginInfo | undefined {
237
+ const plugin = this.plugins.get(type);
238
+ if (!plugin) {
239
+ return undefined;
240
+ }
241
+
242
+ return {
243
+ type: plugin.type,
244
+ displayName: plugin.displayName,
245
+ description: plugin.description,
246
+ docsUrl: plugin.docsUrl,
247
+ version: plugin.version,
248
+ hasLifecycle: plugin.hasLifecycle,
249
+ isBuiltIn: this.builtInTypes.has(type),
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Create a provider instance from a plugin
255
+ *
256
+ * @param config - Engine configuration (must include 'type' field)
257
+ * @param options - Creation options
258
+ * @returns Provider instance
259
+ * @throws Error if plugin not found or config validation fails
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * const config = { type: 'tavily', id: 'tavily', ... };
264
+ * const provider = registry.createProvider(config);
265
+ * ```
266
+ */
267
+ createProvider<T extends EngineConfigBase>(
268
+ config: T & { type: string },
269
+ options: CreateProviderOptions = {},
270
+ ): ManagedProvider {
271
+ const { container, skipValidation = false } = options;
272
+
273
+ const plugin = this.plugins.get(config.type);
274
+ if (!plugin) {
275
+ throw new Error(
276
+ `No plugin registered for type '${config.type}'. ` +
277
+ `Available types: ${this.getTypes().join(", ") || "none"}`,
278
+ );
279
+ }
280
+
281
+ // Validate config if schema provided and validation not skipped
282
+ let validatedConfig = config;
283
+ if (plugin.configSchema && !skipValidation) {
284
+ try {
285
+ validatedConfig = plugin.configSchema.validate(config) as T & { type: string };
286
+ } catch (error) {
287
+ throw new Error(
288
+ `Config validation failed for plugin '${config.type}': ` +
289
+ `${error instanceof Error ? error.message : String(error)}`,
290
+ );
291
+ }
292
+ }
293
+
294
+ // Create provider using factory
295
+ return plugin.factory(validatedConfig, container) as ManagedProvider;
296
+ }
297
+
298
+ /**
299
+ * Create multiple providers from configs
300
+ */
301
+ createProviders(
302
+ configs: Array<EngineConfigBase & { type: string }>,
303
+ options: CreateProviderOptions = {},
304
+ ): ManagedProvider[] {
305
+ return configs.map((config) => this.createProvider(config, options));
306
+ }
307
+
308
+ /**
309
+ * Clear all registered plugins
310
+ */
311
+ async clear(): Promise<void> {
312
+ // Call onUnregister for all plugins
313
+ for (const plugin of this.plugins.values()) {
314
+ if (plugin.onUnregister) {
315
+ try {
316
+ await plugin.onUnregister();
317
+ } catch (error) {
318
+ console.warn(`Error during onUnregister for plugin '${plugin.type}':`, error);
319
+ }
320
+ }
321
+ }
322
+
323
+ this.plugins.clear();
324
+ this.builtInTypes.clear();
325
+ }
326
+
327
+ /**
328
+ * Get count of registered plugins
329
+ */
330
+ get size(): number {
331
+ return this.plugins.size;
332
+ }
333
+ }
334
+
335
+ // Export singleton getter as convenience
336
+ export const getPluginRegistry = (): PluginRegistry => PluginRegistry.getInstance();
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Built-in Plugin Definitions
3
+ *
4
+ * Registers the core search providers as plugins:
5
+ * - Tavily
6
+ * - Brave
7
+ * - Linkup
8
+ * - SearchXNG
9
+ */
10
+
11
+ import type {
12
+ BraveConfig,
13
+ EngineConfigBase,
14
+ LinkupConfig,
15
+ SearchxngConfig,
16
+ TavilyConfig,
17
+ } from "../config/types";
18
+ import type { SearchProvider } from "../core/provider";
19
+ import { BraveProvider } from "../providers/brave";
20
+ import { LinkupProvider } from "../providers/linkup";
21
+ import { SearchxngProvider } from "../providers/searchxng";
22
+ import { TavilyProvider } from "../providers/tavily";
23
+ import { PluginRegistry } from "./PluginRegistry";
24
+ import type { PluginDefinition } from "./types";
25
+
26
+ /**
27
+ * Tavily plugin definition
28
+ */
29
+ export const tavilyPlugin: PluginDefinition<TavilyConfig, TavilyProvider> = {
30
+ type: "tavily",
31
+ displayName: "Tavily",
32
+ description: "AI-powered search API with high-quality results",
33
+ docsUrl: "https://docs.tavily.com/",
34
+ version: "1.0.0",
35
+ hasLifecycle: false,
36
+ factory: (config) => new TavilyProvider(config),
37
+ };
38
+
39
+ /**
40
+ * Brave plugin definition
41
+ */
42
+ export const bravePlugin: PluginDefinition<BraveConfig, BraveProvider> = {
43
+ type: "brave",
44
+ displayName: "Brave Search",
45
+ description: "Privacy-focused search engine API",
46
+ docsUrl: "https://api.search.brave.com/app/documentation",
47
+ version: "1.0.0",
48
+ hasLifecycle: false,
49
+ factory: (config) => new BraveProvider(config),
50
+ };
51
+
52
+ /**
53
+ * Linkup plugin definition
54
+ */
55
+ export const linkupPlugin: PluginDefinition<LinkupConfig, LinkupProvider> = {
56
+ type: "linkup",
57
+ displayName: "Linkup",
58
+ description: "Web search API with Docker support",
59
+ docsUrl: "https://docs.linkup.ai/",
60
+ version: "1.0.0",
61
+ hasLifecycle: true,
62
+ factory: (config) => new LinkupProvider(config),
63
+ };
64
+
65
+ /**
66
+ * SearchXNG plugin definition
67
+ */
68
+ export const searchxngPlugin: PluginDefinition<SearchxngConfig, SearchxngProvider> = {
69
+ type: "searchxng",
70
+ displayName: "SearXNG (Local)",
71
+ description: "Self-hosted meta search engine with Docker auto-start",
72
+ docsUrl: "https://docs.searxng.org/",
73
+ version: "1.0.0",
74
+ hasLifecycle: true,
75
+ factory: (config) => new SearchxngProvider(config),
76
+ };
77
+
78
+ /**
79
+ * All built-in plugins
80
+ * Using base types since specific configs/providers extend them
81
+ */
82
+ export const builtInPlugins: PluginDefinition<EngineConfigBase, SearchProvider>[] = [
83
+ tavilyPlugin,
84
+ bravePlugin,
85
+ linkupPlugin,
86
+ searchxngPlugin,
87
+ ];
88
+
89
+ /**
90
+ * Register all built-in plugins with the registry
91
+ *
92
+ * @param registry - Plugin registry instance (defaults to singleton)
93
+ * @returns Array of registration results
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Register with default singleton
98
+ * await registerBuiltInPlugins();
99
+ *
100
+ * // Or with custom registry
101
+ * const registry = new PluginRegistry();
102
+ * await registerBuiltInPlugins(registry);
103
+ * ```
104
+ */
105
+ export async function registerBuiltInPlugins(
106
+ registry: PluginRegistry = PluginRegistry.getInstance(),
107
+ ): Promise<void> {
108
+ for (const plugin of builtInPlugins) {
109
+ const result = registry.registerSync(plugin);
110
+ if (result.success) {
111
+ registry.markBuiltIn(plugin.type);
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Check if built-in plugins are registered
118
+ */
119
+ export function areBuiltInPluginsRegistered(
120
+ registry: PluginRegistry = PluginRegistry.getInstance(),
121
+ ): boolean {
122
+ return builtInPlugins.every((plugin) => registry.has(plugin.type));
123
+ }
124
+
125
+ /**
126
+ * Get list of built-in plugin types
127
+ */
128
+ export function getBuiltInPluginTypes(): string[] {
129
+ return builtInPlugins.map((p) => p.type);
130
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Plugin System
3
+ *
4
+ * Exports all plugin-related types, classes, and utilities.
5
+ */
6
+
7
+ // Built-in plugins
8
+ export {
9
+ areBuiltInPluginsRegistered,
10
+ bravePlugin,
11
+ builtInPlugins,
12
+ getBuiltInPluginTypes,
13
+ linkupPlugin,
14
+ registerBuiltInPlugins,
15
+ searchxngPlugin,
16
+ tavilyPlugin,
17
+ } from "./builtin";
18
+ // Registry
19
+ export { getPluginRegistry, PluginRegistry } from "./PluginRegistry";
20
+ // Types
21
+ export type {
22
+ CreateProviderOptions,
23
+ ManagedProvider,
24
+ PluginConfig,
25
+ PluginConfigSchema,
26
+ PluginDefinition,
27
+ PluginInfo,
28
+ PluginProvider,
29
+ PluginRegistrationOptions,
30
+ PluginRegistrationResult,
31
+ ProviderFactory,
32
+ } from "./types";
33
+ export { hasLifecycleManagement, isLifecycleProvider } from "./types";