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.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/package.json +76 -0
- package/src/app/index.ts +30 -0
- package/src/bootstrap/container.ts +157 -0
- package/src/cli.ts +380 -0
- package/src/config/defineConfig.ts +176 -0
- package/src/config/load.ts +368 -0
- package/src/config/types.ts +86 -0
- package/src/config/validation.ts +148 -0
- package/src/core/cache.ts +74 -0
- package/src/core/container.ts +268 -0
- package/src/core/credits/CreditManager.ts +158 -0
- package/src/core/credits/CreditStateProvider.ts +151 -0
- package/src/core/credits/FileCreditStateProvider.ts +137 -0
- package/src/core/credits/index.ts +3 -0
- package/src/core/docker/dockerComposeHelper.ts +177 -0
- package/src/core/docker/dockerLifecycleManager.ts +361 -0
- package/src/core/docker/index.ts +8 -0
- package/src/core/logger.ts +146 -0
- package/src/core/orchestrator.ts +103 -0
- package/src/core/paths.ts +157 -0
- package/src/core/provider/ILifecycleProvider.ts +120 -0
- package/src/core/provider/ProviderFactory.ts +120 -0
- package/src/core/provider.ts +61 -0
- package/src/core/serviceKeys.ts +45 -0
- package/src/core/strategy/AllProvidersStrategy.ts +245 -0
- package/src/core/strategy/FirstSuccessStrategy.ts +98 -0
- package/src/core/strategy/ISearchStrategy.ts +94 -0
- package/src/core/strategy/StrategyFactory.ts +204 -0
- package/src/core/strategy/index.ts +9 -0
- package/src/core/strategy/types.ts +56 -0
- package/src/core/types.ts +58 -0
- package/src/index.ts +1 -0
- package/src/plugin/PluginRegistry.ts +336 -0
- package/src/plugin/builtin.ts +130 -0
- package/src/plugin/index.ts +33 -0
- package/src/plugin/types.ts +212 -0
- package/src/providers/BaseProvider.ts +49 -0
- package/src/providers/brave.ts +66 -0
- package/src/providers/constants.ts +13 -0
- package/src/providers/helpers/index.ts +24 -0
- package/src/providers/helpers/lifecycleHelpers.ts +110 -0
- package/src/providers/helpers/resultMappers.ts +168 -0
- package/src/providers/index.ts +6 -0
- package/src/providers/linkup.ts +114 -0
- package/src/providers/retry.ts +95 -0
- package/src/providers/searchxng.ts +163 -0
- package/src/providers/tavily.ts +73 -0
- package/src/providers/types/index.ts +185 -0
- package/src/providers/utils.ts +182 -0
- package/src/tool/allSearchTool.ts +110 -0
- 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
|
+
}
|