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,182 @@
1
+ /**
2
+ * Shared Provider Utilities
3
+ *
4
+ * Common functionality extracted from search providers to reduce duplication
5
+ * and ensure consistent behavior across all providers.
6
+ */
7
+
8
+ import type { EngineId } from "../core/types";
9
+ import { SearchError } from "../core/types";
10
+
11
+ /**
12
+ * Options for HTTP requests
13
+ */
14
+ export interface FetchOptions {
15
+ /** Request method */
16
+ method: "GET" | "POST";
17
+ /** Request headers */
18
+ headers: Record<string, string>;
19
+ /** Request body (for POST requests) */
20
+ body?: string;
21
+ /** Request timeout in milliseconds */
22
+ timeoutMs?: number;
23
+ }
24
+
25
+ /**
26
+ * Result of a fetch operation
27
+ */
28
+ export interface FetchResult<T> {
29
+ /** Parsed response data */
30
+ data: T;
31
+ /** Response status code */
32
+ status: number;
33
+ /** Time taken in milliseconds */
34
+ tookMs: number;
35
+ }
36
+
37
+ /**
38
+ * Get API key from environment variable
39
+ *
40
+ * @param engineId - Engine ID for error messages
41
+ * @param envVarName - Environment variable name
42
+ * @returns API key value
43
+ * @throws SearchError if environment variable is not set
44
+ */
45
+ export function getApiKey(engineId: EngineId, envVarName: string): string {
46
+ const apiKey = process.env[envVarName];
47
+ if (!apiKey) {
48
+ throw new SearchError(engineId, "config_error", `Missing environment variable: ${envVarName}`);
49
+ }
50
+ return apiKey;
51
+ }
52
+
53
+ /**
54
+ * Perform an HTTP fetch with error handling and timeout support
55
+ *
56
+ * @param engineId - Engine ID for error messages
57
+ * @param url - URL to fetch
58
+ * @param options - Fetch options
59
+ * @param providerDisplayName - Optional provider display name for error messages
60
+ * @returns Parsed JSON response
61
+ * @throws SearchError on network errors, HTTP errors, or parse errors
62
+ */
63
+ export async function fetchWithErrorHandling<T>(
64
+ engineId: EngineId,
65
+ url: string,
66
+ options: FetchOptions,
67
+ providerDisplayName?: string,
68
+ ): Promise<FetchResult<T>> {
69
+ const started = Date.now();
70
+ let response: Response;
71
+
72
+ // Set up abort controller for timeout
73
+ const controller = new AbortController();
74
+ const timeoutId = options.timeoutMs
75
+ ? setTimeout(() => controller.abort(), options.timeoutMs)
76
+ : undefined;
77
+
78
+ try {
79
+ response = await fetch(url, {
80
+ method: options.method,
81
+ headers: options.headers,
82
+ body: options.body,
83
+ signal: controller.signal,
84
+ });
85
+ } catch (error) {
86
+ if (timeoutId) {
87
+ clearTimeout(timeoutId);
88
+ }
89
+
90
+ // Handle abort/timeout
91
+ if (error instanceof Error && error.name === "AbortError") {
92
+ throw new SearchError(
93
+ engineId,
94
+ "network_error",
95
+ `Request timeout after ${options.timeoutMs}ms`,
96
+ );
97
+ }
98
+
99
+ throw new SearchError(
100
+ engineId,
101
+ "network_error",
102
+ `Network error: ${error instanceof Error ? error.message : String(error)}`,
103
+ );
104
+ } finally {
105
+ if (timeoutId) {
106
+ clearTimeout(timeoutId);
107
+ }
108
+ }
109
+
110
+ // Handle HTTP errors
111
+ if (!response.ok) {
112
+ let errorBody = "";
113
+ try {
114
+ errorBody = await response.text();
115
+ } catch {
116
+ // Ignore error body parsing failures
117
+ }
118
+
119
+ const errorPrefix = providerDisplayName ? `${providerDisplayName} API error` : "API error";
120
+ throw new SearchError(
121
+ engineId,
122
+ "api_error",
123
+ `${errorPrefix}: HTTP ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`,
124
+ response.status,
125
+ );
126
+ }
127
+
128
+ // Parse JSON response
129
+ let data: T;
130
+ try {
131
+ data = (await response.json()) as T;
132
+ } catch (error) {
133
+ const errorPrefix = providerDisplayName
134
+ ? `Invalid JSON response from ${providerDisplayName}`
135
+ : "Invalid JSON response";
136
+ throw new SearchError(
137
+ engineId,
138
+ "api_error",
139
+ `${errorPrefix}: ${error instanceof Error ? error.message : String(error)}`,
140
+ );
141
+ }
142
+
143
+ return {
144
+ data,
145
+ status: response.status,
146
+ tookMs: Date.now() - started,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Validate that a response contains results
152
+ *
153
+ * @param engineId - Engine ID for error messages
154
+ * @param results - Results array to validate
155
+ * @param providerName - Optional provider name for custom error message
156
+ * @throws SearchError if results are empty or invalid
157
+ */
158
+ export function validateResults(
159
+ engineId: EngineId,
160
+ results: unknown,
161
+ providerName?: string,
162
+ ): asserts results is unknown[] {
163
+ if (!Array.isArray(results) || results.length === 0) {
164
+ const message = providerName ? `${providerName} returned no results` : "No results returned";
165
+ throw new SearchError(engineId, "no_results", message);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Build a URL with query parameters
171
+ *
172
+ * @param baseUrl - Base URL
173
+ * @param params - Query parameters
174
+ * @returns URL with query string
175
+ */
176
+ export function buildUrl(baseUrl: string, params: Record<string, string | number>): string {
177
+ const searchParams = new URLSearchParams();
178
+ for (const [key, value] of Object.entries(params)) {
179
+ searchParams.append(key, String(value));
180
+ }
181
+ return `${baseUrl}?${searchParams.toString()}`;
182
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Main allsearch tool function
3
+ *
4
+ * This is the single public interface that orchestrates the entire search process
5
+ */
6
+
7
+ import { bootstrapContainer } from "../bootstrap/container";
8
+ import type { CreditManager } from "../core/credits";
9
+ import type { AllSearchOrchestrator } from "../core/orchestrator";
10
+ import { ServiceKeys } from "../core/serviceKeys";
11
+ import type { AllSearchInput, AllSearchOutput } from "./interface";
12
+
13
+ /**
14
+ * Options for multiSearch function
15
+ */
16
+ export interface AllSearchOptions {
17
+ /** Explicit config file path */
18
+ configPath?: string;
19
+ /** Container override for testing (dependency injection) */
20
+ containerOverride?: {
21
+ get<T>(serviceId: string): T;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Execute a allsearch across configured providers
27
+ *
28
+ * This function:
29
+ * 1. Bootstraps the DI container (or uses provided override)
30
+ * 2. Resolves orchestrator from container
31
+ * 3. Executes search with chosen strategy
32
+ * 4. Returns normalized results
33
+ *
34
+ * @param input Search input parameters
35
+ * @param options Optional config path or options object
36
+ * @returns Search results with metadata
37
+ */
38
+ export async function multiSearch(
39
+ input: AllSearchInput,
40
+ options?: string | AllSearchOptions,
41
+ ): Promise<AllSearchOutput> {
42
+ // Handle backwards compatibility: string arg is config path
43
+ const opts: AllSearchOptions =
44
+ typeof options === "string" ? { configPath: options } : (options ?? {});
45
+
46
+ // Bootstrap the DI container or use override
47
+ const container = opts.containerOverride ?? (await bootstrapContainer(opts.configPath));
48
+
49
+ // Resolve orchestrator from container
50
+ const orchestrator = container.get<AllSearchOrchestrator>(ServiceKeys.ORCHESTRATOR);
51
+
52
+ // Execute search
53
+ const result = await orchestrator.run(input.query, {
54
+ limit: input.limit,
55
+ engineOrderOverride: input.engines,
56
+ includeRaw: input.includeRaw,
57
+ strategy: input.strategy ?? "all",
58
+ });
59
+
60
+ // Format output
61
+ return {
62
+ query: result.query,
63
+ items: result.results.map((r) => ({
64
+ title: r.title,
65
+ url: r.url,
66
+ snippet: r.snippet,
67
+ score: r.score,
68
+ sourceEngine: r.sourceEngine,
69
+ })),
70
+ enginesTried: result.engineAttempts.map((a) => ({
71
+ engineId: a.engineId,
72
+ success: a.success,
73
+ reason: a.reason,
74
+ })),
75
+ credits: result.credits,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Options for getCreditStatus function
81
+ */
82
+ export interface GetCreditStatusOptions {
83
+ /** Explicit config file path */
84
+ configPath?: string;
85
+ /** Container override for testing (dependency injection) */
86
+ containerOverride?: {
87
+ get<T>(serviceId: string): T;
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Get current credit status for all engines
93
+ *
94
+ * Useful for checking credit availability before searching
95
+ *
96
+ * @param options Optional config path (string) or options object
97
+ */
98
+ export async function getCreditStatus(
99
+ options?: string | GetCreditStatusOptions,
100
+ ): Promise<AllSearchOutput["credits"]> {
101
+ // Handle backwards compatibility: string arg is config path
102
+ const opts: GetCreditStatusOptions =
103
+ typeof options === "string" ? { configPath: options } : (options ?? {});
104
+
105
+ // Bootstrap the DI container or use override
106
+ const container = opts.containerOverride ?? (await bootstrapContainer(opts.configPath));
107
+ const creditManager = container.get<CreditManager>(ServiceKeys.CREDIT_MANAGER);
108
+
109
+ return creditManager.listSnapshots();
110
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Input and output interfaces for the allsearch tool
3
+ */
4
+
5
+ export interface AllSearchInput {
6
+ /** Search query string */
7
+ query: string;
8
+
9
+ /** Maximum number of results to return per provider */
10
+ limit?: number;
11
+
12
+ /** Specific engines to use (overrides config default order) */
13
+ engines?: string[];
14
+
15
+ /** Include raw provider responses in output */
16
+ includeRaw?: boolean;
17
+
18
+ /** Search strategy: 'all' (query all) or 'first-success' (stop after first success) */
19
+ strategy?: "all" | "first-success";
20
+
21
+ /**
22
+ * Execute searches in parallel (only applies to 'all' strategy)
23
+ * When true, all providers are queried simultaneously
24
+ * When false (default), providers are queried sequentially
25
+ */
26
+ parallel?: boolean;
27
+ }
28
+
29
+ export interface AllSearchEngineAttempt {
30
+ /** Provider/engine ID that was attempted */
31
+ engineId: string;
32
+
33
+ /** Whether the attempt succeeded */
34
+ success: boolean;
35
+
36
+ /** Reason for failure (if success=false) */
37
+ reason?: string;
38
+ }
39
+
40
+ export interface AllSearchOutputItem {
41
+ /** Result title */
42
+ title: string;
43
+
44
+ /** Result URL */
45
+ url: string;
46
+
47
+ /** Snippet/excerpt */
48
+ snippet: string;
49
+
50
+ /** Relevance score (if provided by engine) */
51
+ score?: number;
52
+
53
+ /** Source engine that returned this result */
54
+ sourceEngine: string;
55
+ }
56
+
57
+ import type { CreditSnapshot } from "../core/credits";
58
+
59
+ export interface AllSearchOutput {
60
+ /** Original query */
61
+ query: string;
62
+
63
+ /** Combined search results from all successful providers */
64
+ items: AllSearchOutputItem[];
65
+
66
+ /** Metadata about which engines were tried and their outcomes */
67
+ enginesTried: AllSearchEngineAttempt[];
68
+
69
+ /** Credit snapshots for each engine after the search */
70
+ credits?: CreditSnapshot[];
71
+ }