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,212 @@
1
+ /**
2
+ * Plugin System Types
3
+ *
4
+ * Defines the interfaces and types for the allsearch plugin system.
5
+ * Plugins allow adding new search providers without modifying core code.
6
+ */
7
+
8
+ import type { EngineConfigBase } from "../config/types";
9
+ import type { Container } from "../core/container";
10
+ import type { ILifecycleProvider, SearchProvider } from "../core/provider";
11
+
12
+ /**
13
+ * Configuration schema for a plugin's engine configuration
14
+ * Used for validation and TypeScript type inference
15
+ */
16
+ export interface PluginConfigSchema<T extends EngineConfigBase = EngineConfigBase> {
17
+ /** The type discriminator for this plugin */
18
+ readonly type: string;
19
+
20
+ /**
21
+ * Validate the configuration
22
+ * @param config - Raw configuration object
23
+ * @returns Validated config or throws on validation failure
24
+ */
25
+ validate(config: unknown): T;
26
+
27
+ /**
28
+ * Get default values for optional fields
29
+ */
30
+ getDefaults(): Partial<T>;
31
+ }
32
+
33
+ /**
34
+ * Factory function type for creating provider instances
35
+ */
36
+ export type ProviderFactory<
37
+ TConfig extends EngineConfigBase = EngineConfigBase,
38
+ TProvider extends SearchProvider = SearchProvider,
39
+ > = (config: TConfig, container?: Container) => TProvider;
40
+
41
+ /**
42
+ * Plugin definition interface
43
+ *
44
+ * A plugin provides everything needed to integrate a new search provider:
45
+ * - Type identifier for config discrimination
46
+ * - Config schema for validation
47
+ * - Factory function for creating instances
48
+ * - Metadata about the plugin
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const myPlugin: PluginDefinition<MyConfig, MyProvider> = {
53
+ * type: 'my-provider',
54
+ * displayName: 'My Search Provider',
55
+ * description: 'A custom search provider',
56
+ * configSchema: myConfigSchema,
57
+ * factory: (config, container) => new MyProvider(config),
58
+ * hasLifecycle: false,
59
+ * };
60
+ * ```
61
+ */
62
+ export interface PluginDefinition<
63
+ TConfig extends EngineConfigBase = EngineConfigBase,
64
+ TProvider extends SearchProvider = SearchProvider,
65
+ > {
66
+ /**
67
+ * Unique type identifier for this plugin
68
+ * Used as discriminator in config objects (config.type)
69
+ */
70
+ readonly type: string;
71
+
72
+ /**
73
+ * Human-readable name for display
74
+ */
75
+ readonly displayName: string;
76
+
77
+ /**
78
+ * Brief description of the plugin
79
+ */
80
+ readonly description?: string;
81
+
82
+ /**
83
+ * URL to documentation
84
+ */
85
+ readonly docsUrl?: string;
86
+
87
+ /**
88
+ * Version of the plugin
89
+ */
90
+ readonly version?: string;
91
+
92
+ /**
93
+ * Configuration schema for validation
94
+ */
95
+ readonly configSchema?: PluginConfigSchema<TConfig>;
96
+
97
+ /**
98
+ * Factory function to create provider instances
99
+ */
100
+ readonly factory: ProviderFactory<TConfig, TProvider>;
101
+
102
+ /**
103
+ * Whether this provider implements ILifecycleProvider
104
+ * If true, init/shutdown will be called
105
+ */
106
+ readonly hasLifecycle: boolean;
107
+
108
+ /**
109
+ * Optional initialization hook called when plugin is registered
110
+ */
111
+ onRegister?(): void | Promise<void>;
112
+
113
+ /**
114
+ * Optional cleanup hook called when plugin is unregistered
115
+ */
116
+ onUnregister?(): void | Promise<void>;
117
+ }
118
+
119
+ /**
120
+ * Helper type to extract config type from a plugin definition
121
+ */
122
+ export type PluginConfig<T> = T extends PluginDefinition<infer C, unknown> ? C : never;
123
+
124
+ /**
125
+ * Helper type to extract provider type from a plugin definition
126
+ */
127
+ export type PluginProvider<T> = T extends PluginDefinition<unknown, infer P> ? P : never;
128
+
129
+ /**
130
+ * Registration options when adding a plugin to the registry
131
+ */
132
+ export interface PluginRegistrationOptions {
133
+ /**
134
+ * Whether to overwrite if plugin type already exists
135
+ * @default false
136
+ */
137
+ overwrite?: boolean;
138
+ }
139
+
140
+ /**
141
+ * Result of plugin registration
142
+ */
143
+ export interface PluginRegistrationResult {
144
+ success: boolean;
145
+ type: string;
146
+ message?: string;
147
+ overwritten?: boolean;
148
+ }
149
+
150
+ /**
151
+ * Plugin metadata returned by registry queries
152
+ */
153
+ export interface PluginInfo {
154
+ type: string;
155
+ displayName: string;
156
+ description?: string;
157
+ docsUrl?: string;
158
+ version?: string;
159
+ hasLifecycle: boolean;
160
+ isBuiltIn: boolean;
161
+ }
162
+
163
+ /**
164
+ * Options for creating a provider from a plugin
165
+ */
166
+ export interface CreateProviderOptions {
167
+ /**
168
+ * DI container for dependency injection
169
+ */
170
+ container?: Container;
171
+
172
+ /**
173
+ * Whether to skip config validation
174
+ * @default false
175
+ */
176
+ skipValidation?: boolean;
177
+ }
178
+
179
+ /**
180
+ * Extended provider type that combines SearchProvider with optional lifecycle
181
+ */
182
+ export type ManagedProvider = SearchProvider & Partial<ILifecycleProvider>;
183
+
184
+ /**
185
+ * Type guard to check if a provider implements ILifecycleProvider
186
+ */
187
+ export function isLifecycleProvider(
188
+ provider: SearchProvider,
189
+ ): provider is SearchProvider & ILifecycleProvider {
190
+ if (provider == null || typeof provider !== "object") {
191
+ return false;
192
+ }
193
+ const obj = provider as Record<string, unknown>;
194
+ return (
195
+ "init" in provider &&
196
+ "healthcheck" in provider &&
197
+ "shutdown" in provider &&
198
+ typeof obj.init === "function" &&
199
+ typeof obj.healthcheck === "function" &&
200
+ typeof obj.shutdown === "function"
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Type guard to check if a provider has lifecycle management enabled
206
+ */
207
+ export function hasLifecycleManagement(provider: SearchProvider): boolean {
208
+ if (isLifecycleProvider(provider)) {
209
+ return typeof provider.isLifecycleManaged === "function" ? provider.isLifecycleManaged() : true;
210
+ }
211
+ return false;
212
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Base Provider Class
3
+ *
4
+ * Shared functionality for all search providers to reduce duplication
5
+ */
6
+
7
+ import type { EngineConfigBase } from "../config/types";
8
+ import type { ProviderMetadata, SearchProvider } from "../core/provider";
9
+ import type { SearchQuery, SearchResponse } from "../core/types";
10
+ import { SearchError } from "../core/types";
11
+ import { getApiKey, validateResults } from "./utils";
12
+
13
+ export abstract class BaseProvider<T extends EngineConfigBase> implements SearchProvider {
14
+ readonly id: string;
15
+ protected config: T;
16
+
17
+ constructor(config: T) {
18
+ this.id = config.id;
19
+ this.config = config;
20
+ }
21
+
22
+ getMetadata(): ProviderMetadata {
23
+ return {
24
+ id: this.id,
25
+ displayName: this.config.displayName,
26
+ docsUrl: this.getDocsUrl(),
27
+ };
28
+ }
29
+
30
+ abstract search(query: SearchQuery): Promise<SearchResponse>;
31
+ protected abstract getDocsUrl(): string;
32
+ protected abstract getApiKeyEnv(): string;
33
+
34
+ protected getApiKey(): string {
35
+ return getApiKey(this.id, this.getApiKeyEnv());
36
+ }
37
+
38
+ protected validateResults(results: unknown, providerName: string): void {
39
+ validateResults(this.id, results, providerName);
40
+ }
41
+
42
+ protected throwSearchError(
43
+ type: "config_error" | "network_error" | "api_error" | "no_results" | "provider_unavailable",
44
+ message: string,
45
+ statusCode?: number,
46
+ ): never {
47
+ throw new SearchError(this.id, type, message, statusCode);
48
+ }
49
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Brave Search Provider Implementation
3
+ */
4
+
5
+ import type { BraveConfig } from "../config/types";
6
+ import type { SearchQuery, SearchResponse } from "../core/types";
7
+ import { BaseProvider } from "./BaseProvider";
8
+ import { PROVIDER_DEFAULTS } from "./constants";
9
+ import type { BraveApiResponse, BraveWebResult } from "./types";
10
+ import { buildUrl, fetchWithErrorHandling } from "./utils";
11
+
12
+ export class BraveProvider extends BaseProvider<BraveConfig> {
13
+ protected getDocsUrl(): string {
14
+ return "https://api.search.brave.com/app/documentation";
15
+ }
16
+
17
+ protected getApiKeyEnv(): string {
18
+ return this.config.apiKeyEnv;
19
+ }
20
+
21
+ async search(query: SearchQuery): Promise<SearchResponse> {
22
+ const apiKey = this.getApiKey();
23
+
24
+ const limit = query.limit ?? this.config.defaultLimit ?? 10;
25
+ const url = buildUrl(this.config.endpoint, {
26
+ q: query.query,
27
+ count: limit,
28
+ });
29
+
30
+ // Make request with error handling
31
+ const { data: json, tookMs } = await fetchWithErrorHandling<BraveApiResponse>(
32
+ this.id,
33
+ url,
34
+ {
35
+ method: "GET",
36
+ headers: {
37
+ Accept: "application/json",
38
+ "X-Subscription-Token": apiKey,
39
+ },
40
+ timeoutMs: PROVIDER_DEFAULTS.DEFAULT_TIMEOUT_MS,
41
+ },
42
+ "Brave",
43
+ );
44
+
45
+ // Extract web results (handle both response formats)
46
+ const webResults: BraveWebResult[] = json.web?.results ?? json.results ?? [];
47
+
48
+ this.validateResults(webResults, "Brave");
49
+
50
+ // Map to normalized format
51
+ const items = webResults.map((r: BraveWebResult) => ({
52
+ title: r.title ?? r.url,
53
+ url: r.url,
54
+ snippet: r.description ?? r.snippet ?? r.abstract ?? "",
55
+ score: r.rank ?? r.score,
56
+ sourceEngine: this.id,
57
+ }));
58
+
59
+ return {
60
+ engineId: this.id,
61
+ items,
62
+ raw: query.includeRaw ? json : undefined,
63
+ tookMs,
64
+ };
65
+ }
66
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Provider Constants
3
+ *
4
+ * Shared constants for provider configuration and behavior
5
+ */
6
+
7
+ export const PROVIDER_DEFAULTS = {
8
+ SEARXNG_INIT_TIMEOUT_MS: 60000,
9
+ SEARXNG_STARTUP_DELAY_MS: 3000,
10
+ LINKUP_INIT_TIMEOUT_MS: 30000,
11
+ DEFAULT_TIMEOUT_MS: 30000,
12
+ DEFAULT_RESULT_LIMIT: 10,
13
+ } as const;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Provider Helper Functions
3
+ *
4
+ * Reusable utilities for building search providers with less boilerplate.
5
+ */
6
+
7
+ // Lifecycle helpers for Docker-managed providers
8
+ export {
9
+ addLifecycleMethods,
10
+ createDockerLifecycle,
11
+ createLifecycleMethods,
12
+ type DockerConfigurable,
13
+ type DockerDefaults,
14
+ } from "./lifecycleHelpers";
15
+
16
+ // Result mapping helpers
17
+ export {
18
+ DEFAULT_FIELD_MAPPINGS,
19
+ type FieldMappings,
20
+ getFirstMatch,
21
+ mapSearchResult,
22
+ mapSearchResults,
23
+ PROVIDER_MAPPINGS,
24
+ } from "./resultMappers";
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Lifecycle Helper Functions for Docker-Managed Providers
3
+ *
4
+ * Provides reusable functions for creating Docker lifecycle managers
5
+ * and implementing ILifecycleProvider methods via composition.
6
+ */
7
+
8
+ import type { DockerConfigurable } from "../../config/types";
9
+ import { DockerLifecycleManager } from "../../core/docker/dockerLifecycleManager";
10
+ import type { ILifecycleProvider } from "../../core/provider";
11
+
12
+ // Re-export for convenience
13
+ export type { DockerConfigurable };
14
+
15
+ /**
16
+ * Default values for Docker lifecycle configuration
17
+ */
18
+ export interface DockerDefaults {
19
+ /** Default for autoStart (default: false) */
20
+ autoStart?: boolean;
21
+ /** Default for autoStop (default: false) */
22
+ autoStop?: boolean;
23
+ /** Default initialization timeout in ms (default: 30000) */
24
+ initTimeoutMs?: number;
25
+ /** Optional project root directory for Docker commands */
26
+ projectRoot?: string;
27
+ }
28
+
29
+ /**
30
+ * Create a DockerLifecycleManager from config with sensible defaults
31
+ *
32
+ * @param config - Engine config containing Docker settings
33
+ * @param defaults - Default values to use when config values are undefined
34
+ * @returns Configured DockerLifecycleManager instance
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const manager = createDockerLifecycle(config, {
39
+ * autoStart: true,
40
+ * autoStop: true,
41
+ * initTimeoutMs: 60000,
42
+ * });
43
+ * ```
44
+ */
45
+ export function createDockerLifecycle(
46
+ config: DockerConfigurable,
47
+ defaults: DockerDefaults = {},
48
+ ): DockerLifecycleManager {
49
+ return new DockerLifecycleManager({
50
+ containerName: config.containerName,
51
+ composeFile: config.composeFile,
52
+ healthEndpoint: config.healthEndpoint,
53
+ autoStart: config.autoStart ?? defaults.autoStart ?? false,
54
+ autoStop: config.autoStop ?? defaults.autoStop ?? false,
55
+ initTimeoutMs: config.initTimeoutMs ?? defaults.initTimeoutMs ?? 30000,
56
+ projectRoot: defaults.projectRoot,
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Create ILifecycleProvider methods that delegate to a DockerLifecycleManager
62
+ *
63
+ * This implements the composition pattern - rather than inheritance,
64
+ * we create an object with lifecycle methods that delegate to the manager.
65
+ *
66
+ * @param manager - The DockerLifecycleManager to delegate to
67
+ * @returns Object implementing ILifecycleProvider interface
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const lifecycleMethods = createLifecycleMethods(this.lifecycleManager);
72
+ * // Now spread or assign these methods to your provider
73
+ * ```
74
+ */
75
+ export function createLifecycleMethods(manager: DockerLifecycleManager): ILifecycleProvider {
76
+ return {
77
+ init: () => manager.init(),
78
+ healthcheck: () => manager.healthcheck(),
79
+ shutdown: () => manager.shutdown(),
80
+ validateConfig: () => manager.validateDockerConfig(),
81
+ isLifecycleManaged: () => true,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Mixin function to add lifecycle methods to a provider class instance
87
+ *
88
+ * @param target - The provider instance to add methods to
89
+ * @param manager - The DockerLifecycleManager to delegate to
90
+ * @returns The target with lifecycle methods added
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * class MyProvider {
95
+ * private lifecycleManager: DockerLifecycleManager;
96
+ *
97
+ * constructor(config: MyConfig) {
98
+ * this.lifecycleManager = createDockerLifecycle(config, { autoStart: true });
99
+ * addLifecycleMethods(this, this.lifecycleManager);
100
+ * }
101
+ * }
102
+ * ```
103
+ */
104
+ export function addLifecycleMethods<T extends object>(
105
+ target: T,
106
+ manager: DockerLifecycleManager,
107
+ ): T & ILifecycleProvider {
108
+ const methods = createLifecycleMethods(manager);
109
+ return Object.assign(target, methods);
110
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Result Mapping Helper Functions
3
+ *
4
+ * Provides utilities for mapping API responses to normalized SearchResultItem format.
5
+ * Uses a configuration-based approach to handle different field names across providers.
6
+ */
7
+
8
+ import type { SearchResultItem } from "../../core/types";
9
+
10
+ /**
11
+ * Configuration for mapping fields from raw API response to SearchResultItem
12
+ *
13
+ * Each property is an array of field names to try, in order of preference.
14
+ * The first non-null/undefined value found will be used.
15
+ */
16
+ export interface FieldMappings {
17
+ /** Fields to try for the title (default: ['title', 'name', 'url']) */
18
+ title?: string[];
19
+ /** Fields to try for the URL (default: ['url', 'link', 'href']) */
20
+ url?: string[];
21
+ /** Fields to try for the snippet (default: ['content', 'description', 'snippet']) */
22
+ snippet?: string[];
23
+ /** Fields to try for the score (default: ['score', 'rank', 'relevance']) */
24
+ score?: string[];
25
+ /** Field to use for source engine (default: none, uses provided engineId) */
26
+ sourceEngine?: string;
27
+ }
28
+
29
+ /**
30
+ * Default field mappings that work for most providers
31
+ */
32
+ export const DEFAULT_FIELD_MAPPINGS: Required<Omit<FieldMappings, "sourceEngine">> = {
33
+ title: ["title", "name", "url"],
34
+ url: ["url", "link", "href"],
35
+ snippet: ["content", "description", "snippet", "excerpt"],
36
+ score: ["score", "rank", "relevance", "priority"],
37
+ };
38
+
39
+ /**
40
+ * Get the first non-null/undefined value from an object by trying multiple keys
41
+ *
42
+ * @param obj - Object to extract value from
43
+ * @param keys - Array of keys to try, in order of preference
44
+ * @returns The first found value, or undefined if none found
45
+ */
46
+ export function getFirstMatch<T = unknown>(
47
+ obj: Record<string, unknown>,
48
+ keys: string[],
49
+ ): T | undefined {
50
+ for (const key of keys) {
51
+ const value = obj[key];
52
+ if (value !== null && value !== undefined) {
53
+ return value as T;
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ /**
60
+ * Map a single raw result object to a SearchResultItem
61
+ *
62
+ * @param raw - Raw result object from API
63
+ * @param engineId - Engine ID to use for sourceEngine
64
+ * @param mappings - Optional custom field mappings (merged with defaults)
65
+ * @returns Normalized SearchResultItem
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Using default mappings
70
+ * const item = mapSearchResult(rawResult, 'tavily');
71
+ *
72
+ * // Using custom mappings
73
+ * const item = mapSearchResult(rawResult, 'custom-engine', {
74
+ * title: ['headline', 'title', 'name'],
75
+ * snippet: ['body', 'text', 'content'],
76
+ * });
77
+ * ```
78
+ */
79
+ export function mapSearchResult(
80
+ raw: unknown,
81
+ engineId: string,
82
+ mappings: FieldMappings = {},
83
+ ): SearchResultItem {
84
+ const r = raw as Record<string, unknown>;
85
+
86
+ // Merge with defaults
87
+ const titleKeys = mappings.title ?? DEFAULT_FIELD_MAPPINGS.title;
88
+ const urlKeys = mappings.url ?? DEFAULT_FIELD_MAPPINGS.url;
89
+ const snippetKeys = mappings.snippet ?? DEFAULT_FIELD_MAPPINGS.snippet;
90
+ const scoreKeys = mappings.score ?? DEFAULT_FIELD_MAPPINGS.score;
91
+
92
+ return {
93
+ title: String(getFirstMatch(r, titleKeys) ?? "Untitled"),
94
+ url: String(getFirstMatch(r, urlKeys) ?? ""),
95
+ snippet: String(getFirstMatch(r, snippetKeys) ?? ""),
96
+ score: getFirstMatch<number>(r, scoreKeys),
97
+ sourceEngine: mappings.sourceEngine ? String(r[mappings.sourceEngine] ?? engineId) : engineId,
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Map an array of raw results to SearchResultItems
103
+ *
104
+ * @param results - Array of raw result objects
105
+ * @param engineId - Engine ID to use for sourceEngine
106
+ * @param mappings - Optional custom field mappings
107
+ * @param filter - Optional filter function to exclude invalid results
108
+ * @returns Array of normalized SearchResultItems
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const items = mapSearchResults(
113
+ * json.results,
114
+ * 'brave',
115
+ * { title: ['title', 'name'] },
116
+ * (r) => r.url != null // Filter out results without URL
117
+ * );
118
+ * ```
119
+ */
120
+ export function mapSearchResults(
121
+ results: unknown[],
122
+ engineId: string,
123
+ mappings: FieldMappings = {},
124
+ filter?: (raw: Record<string, unknown>) => boolean,
125
+ ): SearchResultItem[] {
126
+ return results
127
+ .filter((r): r is Record<string, unknown> => {
128
+ if (r == null || typeof r !== "object") {
129
+ return false;
130
+ }
131
+ if (filter) {
132
+ return filter(r as Record<string, unknown>);
133
+ }
134
+ return true;
135
+ })
136
+ .map((r) => mapSearchResult(r, engineId, mappings));
137
+ }
138
+
139
+ /**
140
+ * Pre-configured field mappings for known providers
141
+ */
142
+ export const PROVIDER_MAPPINGS = {
143
+ tavily: {
144
+ title: ["title", "url"],
145
+ url: ["url"],
146
+ snippet: ["content", "snippet"],
147
+ score: ["score"],
148
+ },
149
+ brave: {
150
+ title: ["title", "url"],
151
+ url: ["url"],
152
+ snippet: ["description", "snippet"],
153
+ score: ["score"],
154
+ },
155
+ linkup: {
156
+ title: ["name", "title", "url"],
157
+ url: ["url"],
158
+ snippet: ["content", "snippet", "description"],
159
+ score: ["score", "relevance"],
160
+ },
161
+ searchxng: {
162
+ title: ["title", "url"],
163
+ url: ["url"],
164
+ snippet: ["content", "description"],
165
+ score: ["score", "rank"],
166
+ sourceEngine: "engine",
167
+ },
168
+ } as const satisfies Record<string, FieldMappings>;
@@ -0,0 +1,6 @@
1
+ export { BraveProvider } from "./brave";
2
+ export * as providerHelpers from "./helpers";
3
+ export { LinkupProvider } from "./linkup";
4
+ export { SearchxngProvider } from "./searchxng";
5
+ export { TavilyProvider } from "./tavily";
6
+ export * from "./types";