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,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>;
|