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
package/src/cli.ts
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Search CLI
|
|
4
|
+
*
|
|
5
|
+
* Unified search interface for multiple search providers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { bootstrapContainer, isLifecycleProvider } from "./bootstrap/container";
|
|
9
|
+
import type { ProviderRegistry } from "./core/provider";
|
|
10
|
+
import { ServiceKeys } from "./core/serviceKeys";
|
|
11
|
+
import type { AllSearchOutput } from "./tool/interface";
|
|
12
|
+
import { getCreditStatus, multiSearch } from "./tool/multiSearchTool";
|
|
13
|
+
|
|
14
|
+
// Parse command line arguments
|
|
15
|
+
let args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
// Parse --config first and remove from args (global option that can appear anywhere)
|
|
18
|
+
const configIdx = args.indexOf("--config");
|
|
19
|
+
let configPath: string | undefined;
|
|
20
|
+
if (configIdx !== -1) {
|
|
21
|
+
configPath = args[configIdx + 1];
|
|
22
|
+
if (!configPath || configPath.startsWith("--")) {
|
|
23
|
+
console.error("Error: --config requires a file path");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
args.splice(configIdx, 2);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Show help
|
|
30
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
31
|
+
console.log(`
|
|
32
|
+
allsearch — Unified search across multiple providers
|
|
33
|
+
|
|
34
|
+
USAGE:
|
|
35
|
+
allsearch <query> [options]
|
|
36
|
+
allsearch credits
|
|
37
|
+
|
|
38
|
+
ARGUMENTS:
|
|
39
|
+
<query> Search query (required, unless using 'credits' command)
|
|
40
|
+
credits Show credit status for all engines
|
|
41
|
+
health Run health checks on all providers
|
|
42
|
+
|
|
43
|
+
OPTIONS:
|
|
44
|
+
--json Output results as JSON
|
|
45
|
+
--engines <engine,list> Use specific engines (comma-separated)
|
|
46
|
+
--strategy <strategy> Search strategy: 'all' or 'first-success' (default: all)
|
|
47
|
+
--limit <number> Maximum results per engine
|
|
48
|
+
--include-raw Include raw provider responses
|
|
49
|
+
--config <path> Path to configuration file
|
|
50
|
+
--help, -h Show this help message
|
|
51
|
+
|
|
52
|
+
EXAMPLES:
|
|
53
|
+
allsearch "best TypeScript ORM 2025"
|
|
54
|
+
allsearch "llm observability" --engines tavily,brave --json
|
|
55
|
+
allsearch "hawaii dev meetups" --strategy first-success
|
|
56
|
+
allsearch credits
|
|
57
|
+
allsearch health
|
|
58
|
+
allsearch --config /path/to/config.json credits
|
|
59
|
+
allsearch "query" --config /path/to/config.json
|
|
60
|
+
|
|
61
|
+
CONFIGURATION:
|
|
62
|
+
Config files are searched in order:
|
|
63
|
+
1. ./allsearch.config.json
|
|
64
|
+
2. $XDG_CONFIG_HOME/allsearch/config.json
|
|
65
|
+
3. ~/.config/allsearch/config.json
|
|
66
|
+
|
|
67
|
+
ENVIRONMENT:
|
|
68
|
+
Set API keys in environment variables:
|
|
69
|
+
- TAVILY_API_KEY for Tavily
|
|
70
|
+
- BRAVE_API_KEY for Brave Search
|
|
71
|
+
`);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Credits command
|
|
76
|
+
if (args[0] === "credits") {
|
|
77
|
+
await showCredits(configPath);
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Health check command
|
|
82
|
+
if (args[0] === "health") {
|
|
83
|
+
await runHealthChecks(configPath);
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Parse options
|
|
88
|
+
const options = {
|
|
89
|
+
json: args.includes("--json"),
|
|
90
|
+
includeRaw: args.includes("--include-raw"),
|
|
91
|
+
engines: undefined as string[] | undefined,
|
|
92
|
+
strategy: undefined as "all" | "first-success" | undefined,
|
|
93
|
+
limit: undefined as number | undefined,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Parse --engines
|
|
97
|
+
const enginesIdx = args.indexOf("--engines");
|
|
98
|
+
if (enginesIdx !== -1) {
|
|
99
|
+
const enginesArg = args[enginesIdx + 1];
|
|
100
|
+
if (enginesArg !== undefined) {
|
|
101
|
+
options.engines = enginesArg.split(",").map((e) => e.trim());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse --strategy
|
|
106
|
+
const strategyIdx = args.indexOf("--strategy");
|
|
107
|
+
if (strategyIdx !== -1 && args[strategyIdx + 1]) {
|
|
108
|
+
const strategy = args[strategyIdx + 1];
|
|
109
|
+
if (strategy === "all" || strategy === "first-success") {
|
|
110
|
+
options.strategy = strategy;
|
|
111
|
+
} else {
|
|
112
|
+
console.error(`Invalid strategy: ${strategy}. Must be 'all' or 'first-success'`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Parse --limit
|
|
118
|
+
const limitIdx = args.indexOf("--limit");
|
|
119
|
+
if (limitIdx !== -1) {
|
|
120
|
+
const limitArg = args[limitIdx + 1];
|
|
121
|
+
if (limitArg !== undefined) {
|
|
122
|
+
const limit = parseInt(limitArg, 10);
|
|
123
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
124
|
+
console.error(`Invalid limit: ${limitArg}. Must be a positive number`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
options.limit = limit;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Extract query (non-option arguments)
|
|
132
|
+
// Filter out option flags (--*) and their values
|
|
133
|
+
const optionsWithValues = ["--engines", "--strategy", "--limit", "--config"];
|
|
134
|
+
const queryParts = args.filter((arg, idx) => {
|
|
135
|
+
// Skip arguments starting with --
|
|
136
|
+
if (arg.startsWith("--")) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
// Skip special commands
|
|
140
|
+
if (["credits", "health"].includes(arg)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
// Skip values that follow option flags
|
|
144
|
+
const prevArg = args[idx - 1];
|
|
145
|
+
if (prevArg && optionsWithValues.includes(prevArg)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const query = queryParts.join(" ").trim();
|
|
152
|
+
|
|
153
|
+
if (!query) {
|
|
154
|
+
console.error("Error: Query is required");
|
|
155
|
+
console.error("Run with --help for usage information");
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Execute search
|
|
160
|
+
async function main() {
|
|
161
|
+
try {
|
|
162
|
+
// Bootstrap the DI container
|
|
163
|
+
const _container = await bootstrapContainer(configPath);
|
|
164
|
+
|
|
165
|
+
const result = await multiSearch(
|
|
166
|
+
{
|
|
167
|
+
query,
|
|
168
|
+
limit: options.limit,
|
|
169
|
+
engines: options.engines,
|
|
170
|
+
includeRaw: options.includeRaw,
|
|
171
|
+
strategy: options.strategy,
|
|
172
|
+
},
|
|
173
|
+
configPath,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (options.json) {
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
} else {
|
|
179
|
+
printHumanReadable(result);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error("Search failed:", error instanceof Error ? error.message : String(error));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Print results in human-readable format
|
|
189
|
+
*/
|
|
190
|
+
function printHumanReadable(result: AllSearchOutput) {
|
|
191
|
+
console.log(`\nQuery: "${result.query}"`);
|
|
192
|
+
console.log(`Found ${result.items.length} results\n`);
|
|
193
|
+
|
|
194
|
+
if (result.items.length === 0) {
|
|
195
|
+
console.log("No results found.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Group results by engine
|
|
200
|
+
const byEngine = new Map<string, typeof result.items>();
|
|
201
|
+
for (const item of result.items) {
|
|
202
|
+
if (!byEngine.has(item.sourceEngine)) {
|
|
203
|
+
byEngine.set(item.sourceEngine, []);
|
|
204
|
+
}
|
|
205
|
+
byEngine.get(item.sourceEngine)?.push(item);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Print results
|
|
209
|
+
for (const [engineId, items] of byEngine) {
|
|
210
|
+
if (items.length > 0) {
|
|
211
|
+
console.log(`\n${"=".repeat(60)}`);
|
|
212
|
+
console.log(`${engineId} (${items.length} results)`);
|
|
213
|
+
console.log(`${"=".repeat(60)}`);
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < items.length && i < 5; i++) {
|
|
216
|
+
const item = items[i];
|
|
217
|
+
if (!item) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
console.log(`\n${i + 1}. ${item.title}`);
|
|
221
|
+
console.log(` ${item.url}`);
|
|
222
|
+
if (item.score) {
|
|
223
|
+
console.log(` Score: ${item.score}`);
|
|
224
|
+
}
|
|
225
|
+
if (item.snippet) {
|
|
226
|
+
console.log(
|
|
227
|
+
` ${item.snippet.substring(0, 200)}${item.snippet.length > 200 ? "..." : ""}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (items.length > 5) {
|
|
233
|
+
console.log(`\n ... and ${items.length - 5} more results`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Print engine status
|
|
239
|
+
console.log(`\n${"=".repeat(60)}`);
|
|
240
|
+
console.log("Engine Status");
|
|
241
|
+
console.log(`${"=".repeat(60)}`);
|
|
242
|
+
for (const attempt of result.enginesTried) {
|
|
243
|
+
const status = attempt.success
|
|
244
|
+
? "✓ Success"
|
|
245
|
+
: `✗ Failed${attempt.reason ? ` (${attempt.reason})` : ""}`;
|
|
246
|
+
console.log(`${attempt.engineId.padEnd(15)} ${status}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Print credit warnings
|
|
250
|
+
if (result.credits) {
|
|
251
|
+
const lowCredits = result.credits.filter((c) => c.remaining < c.quota * 0.2);
|
|
252
|
+
if (lowCredits.length > 0) {
|
|
253
|
+
console.log(`\n⚠️ Low credit warnings:`);
|
|
254
|
+
for (const credit of lowCredits) {
|
|
255
|
+
console.log(` ${credit.engineId}: ${credit.remaining} remaining of ${credit.quota}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Run health checks on all providers
|
|
265
|
+
*/
|
|
266
|
+
async function runHealthChecks(configPath?: string) {
|
|
267
|
+
try {
|
|
268
|
+
// Bootstrap the DI container
|
|
269
|
+
const container = await bootstrapContainer(configPath);
|
|
270
|
+
const registry = container.get<ProviderRegistry>(ServiceKeys.PROVIDER_REGISTRY);
|
|
271
|
+
const providers = registry.list();
|
|
272
|
+
|
|
273
|
+
console.log("\nProvider Health Checks");
|
|
274
|
+
console.log("=".repeat(60));
|
|
275
|
+
|
|
276
|
+
if (providers.length === 0) {
|
|
277
|
+
console.log("No providers are registered.");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const results: Array<{
|
|
282
|
+
engineId: string;
|
|
283
|
+
status: "healthy" | "unhealthy" | "skipped";
|
|
284
|
+
message: string;
|
|
285
|
+
}> = [];
|
|
286
|
+
|
|
287
|
+
for (const provider of providers) {
|
|
288
|
+
const engineId = provider.id;
|
|
289
|
+
|
|
290
|
+
// Check if provider implements lifecycle methods
|
|
291
|
+
if (isLifecycleProvider(provider)) {
|
|
292
|
+
try {
|
|
293
|
+
await provider.healthcheck();
|
|
294
|
+
results.push({
|
|
295
|
+
engineId,
|
|
296
|
+
status: "healthy",
|
|
297
|
+
message: "Health check passed",
|
|
298
|
+
});
|
|
299
|
+
console.log(`✓ ${engineId.padEnd(15)} Healthy`);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
results.push({
|
|
303
|
+
engineId,
|
|
304
|
+
status: "unhealthy",
|
|
305
|
+
message,
|
|
306
|
+
});
|
|
307
|
+
console.log(`✗ ${engineId.padEnd(15)} Unhealthy - ${message}`);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
results.push({
|
|
311
|
+
engineId,
|
|
312
|
+
status: "skipped",
|
|
313
|
+
message: "Health checks not supported",
|
|
314
|
+
});
|
|
315
|
+
console.log(`- ${engineId.padEnd(15)} Skipped (no health check support)`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Summary
|
|
320
|
+
const healthy = results.filter((r) => r.status === "healthy").length;
|
|
321
|
+
const unhealthy = results.filter((r) => r.status === "unhealthy").length;
|
|
322
|
+
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
323
|
+
|
|
324
|
+
console.log(`\nSummary: ${healthy} healthy, ${unhealthy} unhealthy, ${skipped} skipped`);
|
|
325
|
+
|
|
326
|
+
if (unhealthy > 0) {
|
|
327
|
+
console.log("\n⚠️ Some providers are unhealthy. Check configuration and connectivity.");
|
|
328
|
+
process.exit(1);
|
|
329
|
+
} else {
|
|
330
|
+
console.log("\n✓ All providers are healthy");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
console.log();
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error("Health check failed:", error instanceof Error ? error.message : String(error));
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Show credit status
|
|
342
|
+
*/
|
|
343
|
+
async function showCredits(configPath?: string) {
|
|
344
|
+
try {
|
|
345
|
+
const credits = await getCreditStatus(configPath);
|
|
346
|
+
|
|
347
|
+
if (!credits || credits.length === 0) {
|
|
348
|
+
console.log("No credits configured or no engines enabled.");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
console.log("\nCredit Status");
|
|
353
|
+
console.log("=".repeat(60));
|
|
354
|
+
|
|
355
|
+
for (const credit of credits) {
|
|
356
|
+
const usedPercent = ((credit.used / credit.quota) * 100).toFixed(1);
|
|
357
|
+
const status = credit.isExhausted
|
|
358
|
+
? "⚠️ EXHAUSTED"
|
|
359
|
+
: credit.remaining < credit.quota * 0.2
|
|
360
|
+
? "⚠️ Low"
|
|
361
|
+
: "✓ OK";
|
|
362
|
+
|
|
363
|
+
console.log(`\n${credit.engineId}`);
|
|
364
|
+
console.log(` Used: ${credit.used} / ${credit.quota} (${usedPercent}%)`);
|
|
365
|
+
console.log(` Remaining: ${credit.remaining}`);
|
|
366
|
+
console.log(` Status: ${status}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
console.log();
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error(
|
|
372
|
+
"Failed to load credits:",
|
|
373
|
+
error instanceof Error ? error.message : String(error),
|
|
374
|
+
);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Run main
|
|
380
|
+
main();
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe helpers for defining allsearch configurations.
|
|
5
|
+
* Inspired by Vite's defineConfig pattern.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SearchProvider } from "../core/provider";
|
|
9
|
+
import type { PluginDefinition } from "../plugin/types";
|
|
10
|
+
import type {
|
|
11
|
+
BraveConfig,
|
|
12
|
+
EngineConfig,
|
|
13
|
+
EngineConfigBase,
|
|
14
|
+
LinkupConfig,
|
|
15
|
+
AllSearchConfig,
|
|
16
|
+
SearchxngConfig,
|
|
17
|
+
TavilyConfig,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extended configuration that supports plugins
|
|
22
|
+
*/
|
|
23
|
+
export interface ExtendedSearchConfig extends AllSearchConfig {
|
|
24
|
+
/**
|
|
25
|
+
* Custom plugins to register
|
|
26
|
+
* These will be registered before creating providers
|
|
27
|
+
*/
|
|
28
|
+
plugins?: PluginDefinition[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration factory function type
|
|
33
|
+
* Allows async config creation for loading secrets, etc.
|
|
34
|
+
*/
|
|
35
|
+
export type ConfigFactory = () => ExtendedSearchConfig | Promise<ExtendedSearchConfig>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Define a allsearch configuration with full type safety
|
|
39
|
+
*
|
|
40
|
+
* @param config - Configuration object or factory function
|
|
41
|
+
* @returns The configuration (passthrough with type inference)
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // allsearch.config.ts
|
|
46
|
+
* import { defineConfig } from 'allsearch';
|
|
47
|
+
*
|
|
48
|
+
* export default defineConfig({
|
|
49
|
+
* defaultEngineOrder: ['tavily', 'brave'],
|
|
50
|
+
* engines: [
|
|
51
|
+
* defineTavily({
|
|
52
|
+
* id: 'tavily',
|
|
53
|
+
* enabled: true,
|
|
54
|
+
* displayName: 'Tavily Search',
|
|
55
|
+
* apiKeyEnv: 'TAVILY_API_KEY',
|
|
56
|
+
* endpoint: 'https://api.tavily.com/search',
|
|
57
|
+
* searchDepth: 'basic',
|
|
58
|
+
* monthlyQuota: 1000,
|
|
59
|
+
* creditCostPerSearch: 1,
|
|
60
|
+
* lowCreditThresholdPercent: 80,
|
|
61
|
+
* }),
|
|
62
|
+
* ],
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function defineConfig(config: ExtendedSearchConfig): ExtendedSearchConfig;
|
|
67
|
+
export function defineConfig(factory: ConfigFactory): ConfigFactory;
|
|
68
|
+
export function defineConfig(
|
|
69
|
+
configOrFactory: ExtendedSearchConfig | ConfigFactory,
|
|
70
|
+
): ExtendedSearchConfig | ConfigFactory {
|
|
71
|
+
return configOrFactory;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Helper to define a Tavily engine configuration
|
|
76
|
+
*/
|
|
77
|
+
export function defineTavily(
|
|
78
|
+
config: Omit<TavilyConfig, "type"> & Partial<Pick<TavilyConfig, "type">>,
|
|
79
|
+
): TavilyConfig {
|
|
80
|
+
return {
|
|
81
|
+
type: "tavily",
|
|
82
|
+
...config,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Helper to define a Brave engine configuration
|
|
88
|
+
*/
|
|
89
|
+
export function defineBrave(
|
|
90
|
+
config: Omit<BraveConfig, "type"> & Partial<Pick<BraveConfig, "type">>,
|
|
91
|
+
): BraveConfig {
|
|
92
|
+
return {
|
|
93
|
+
type: "brave",
|
|
94
|
+
...config,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper to define a Linkup engine configuration
|
|
100
|
+
*/
|
|
101
|
+
export function defineLinkup(
|
|
102
|
+
config: Omit<LinkupConfig, "type"> & Partial<Pick<LinkupConfig, "type">>,
|
|
103
|
+
): LinkupConfig {
|
|
104
|
+
return {
|
|
105
|
+
type: "linkup",
|
|
106
|
+
...config,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Helper to define a SearXNG engine configuration
|
|
112
|
+
*/
|
|
113
|
+
export function defineSearchxng(
|
|
114
|
+
config: Omit<SearchxngConfig, "type"> & Partial<Pick<SearchxngConfig, "type">>,
|
|
115
|
+
): SearchxngConfig {
|
|
116
|
+
return {
|
|
117
|
+
type: "searchxng",
|
|
118
|
+
...config,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generic helper for custom engine types
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* interface MyCustomConfig extends EngineConfigBase {
|
|
128
|
+
* type: 'my-custom';
|
|
129
|
+
* customOption: string;
|
|
130
|
+
* }
|
|
131
|
+
*
|
|
132
|
+
* const myEngine = defineEngine<MyCustomConfig>({
|
|
133
|
+
* type: 'my-custom',
|
|
134
|
+
* id: 'my-custom',
|
|
135
|
+
* // ...
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function defineEngine<T extends EngineConfig>(config: T): T {
|
|
140
|
+
return config;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Helper to define a custom plugin
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const myPlugin = definePlugin({
|
|
149
|
+
* type: 'my-search',
|
|
150
|
+
* displayName: 'My Search',
|
|
151
|
+
* hasLifecycle: false,
|
|
152
|
+
* factory: (config) => new MySearchProvider(config),
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function definePlugin<TConfig extends EngineConfigBase, TProvider extends SearchProvider>(
|
|
157
|
+
plugin: PluginDefinition<TConfig, TProvider>,
|
|
158
|
+
): PluginDefinition<TConfig, TProvider> {
|
|
159
|
+
return plugin;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create a configuration with defaults
|
|
164
|
+
*/
|
|
165
|
+
export function createConfig(
|
|
166
|
+
engines: EngineConfig[],
|
|
167
|
+
options: Partial<Omit<ExtendedSearchConfig, "engines">> = {},
|
|
168
|
+
): ExtendedSearchConfig {
|
|
169
|
+
const enabledEngines = engines.filter((e) => e.enabled);
|
|
170
|
+
return {
|
|
171
|
+
defaultEngineOrder: options.defaultEngineOrder ?? enabledEngines.map((e) => e.id),
|
|
172
|
+
engines,
|
|
173
|
+
storage: options.storage,
|
|
174
|
+
plugins: options.plugins,
|
|
175
|
+
};
|
|
176
|
+
}
|