@memberjunction/web-search-engine 0.0.1-seed.1 → 6.2.0-edge.0
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 +183 -0
- package/README.md +343 -3
- package/dist/BaseWebSearchProvider.d.ts +89 -0
- package/dist/BaseWebSearchProvider.d.ts.map +1 -0
- package/dist/BaseWebSearchProvider.js +130 -0
- package/dist/BaseWebSearchProvider.js.map +1 -0
- package/dist/WebSearchEngine.d.ts +107 -0
- package/dist/WebSearchEngine.d.ts.map +1 -0
- package/dist/WebSearchEngine.js +396 -0
- package/dist/WebSearchEngine.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/WebSearchQueryOperation.d.ts +8 -0
- package/dist/operations/WebSearchQueryOperation.d.ts.map +1 -0
- package/dist/operations/WebSearchQueryOperation.js +72 -0
- package/dist/operations/WebSearchQueryOperation.js.map +1 -0
- package/dist/providers/BraveWebSearchProvider.d.ts +41 -0
- package/dist/providers/BraveWebSearchProvider.d.ts.map +1 -0
- package/dist/providers/BraveWebSearchProvider.js +169 -0
- package/dist/providers/BraveWebSearchProvider.js.map +1 -0
- package/dist/providers/DuckDuckGoWebSearchProvider.d.ts +45 -0
- package/dist/providers/DuckDuckGoWebSearchProvider.d.ts.map +1 -0
- package/dist/providers/DuckDuckGoWebSearchProvider.js +116 -0
- package/dist/providers/DuckDuckGoWebSearchProvider.js.map +1 -0
- package/dist/providers/GoogleCustomSearchWebSearchProvider.d.ts +36 -0
- package/dist/providers/GoogleCustomSearchWebSearchProvider.d.ts.map +1 -0
- package/dist/providers/GoogleCustomSearchWebSearchProvider.js +125 -0
- package/dist/providers/GoogleCustomSearchWebSearchProvider.js.map +1 -0
- package/dist/providers/PerplexityWebSearchProvider.d.ts +40 -0
- package/dist/providers/PerplexityWebSearchProvider.d.ts.map +1 -0
- package/dist/providers/PerplexityWebSearchProvider.js +148 -0
- package/dist/providers/PerplexityWebSearchProvider.js.map +1 -0
- package/dist/providers/TavilyWebSearchProvider.d.ts +30 -0
- package/dist/providers/TavilyWebSearchProvider.d.ts.map +1 -0
- package/dist/providers/TavilyWebSearchProvider.js +126 -0
- package/dist/providers/TavilyWebSearchProvider.js.map +1 -0
- package/dist/providers/httpFailure.d.ts +29 -0
- package/dist/providers/httpFailure.d.ts.map +1 -0
- package/dist/providers/httpFailure.js +136 -0
- package/dist/providers/httpFailure.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Base class for web search drivers.
|
|
3
|
+
*
|
|
4
|
+
* Each driver wraps one external search vendor and normalises its response into
|
|
5
|
+
* {@link WebSearchHit}. Drivers are discovered at runtime via `@RegisterClass` and the
|
|
6
|
+
* `MJ: Web Search Providers` metadata entity, whose `DriverClass` column is the registration key.
|
|
7
|
+
*
|
|
8
|
+
* @module @memberjunction/web-search-engine
|
|
9
|
+
*/
|
|
10
|
+
import { LogError } from '@memberjunction/core';
|
|
11
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
12
|
+
/**
|
|
13
|
+
* Abstract base for a web search driver.
|
|
14
|
+
*
|
|
15
|
+
* Subclasses register with `@RegisterClass(BaseWebSearchProvider, 'DriverClassName')` to become
|
|
16
|
+
* discoverable by {@link WebSearchEngine}.
|
|
17
|
+
*
|
|
18
|
+
* Lifecycle, per provider record, at engine config time:
|
|
19
|
+
* 1. `WebSearchEngine` loads Active `MJ: Web Search Providers` records
|
|
20
|
+
* 2. ClassFactory creates an instance from `DriverClass`
|
|
21
|
+
* 3. {@link Initialize} receives the record's configuration and any decrypted credential values
|
|
22
|
+
* 4. {@link CheckAvailability} verifies the driver is actually operational
|
|
23
|
+
* 5. If {@link IsAvailable} is true, the driver joins the selection pool
|
|
24
|
+
*
|
|
25
|
+
* A driver that cannot work — no API key, missing optional dependency — must **self-disable in
|
|
26
|
+
* `CheckAvailability` rather than throw**. An unconfigured vendor is a normal state in a product
|
|
27
|
+
* shipped to many hosts, not an error, and the engine simply routes around it.
|
|
28
|
+
*/
|
|
29
|
+
export class BaseWebSearchProvider {
|
|
30
|
+
constructor() {
|
|
31
|
+
/** Configuration from the metadata record, set during {@link Initialize}. */
|
|
32
|
+
this.config = null;
|
|
33
|
+
this._available = false;
|
|
34
|
+
this._unavailableReason = null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Receive configuration from the provider's metadata record.
|
|
38
|
+
*
|
|
39
|
+
* The default stores it; override to do additional setup, and call `super.Initialize(...)`.
|
|
40
|
+
*/
|
|
41
|
+
async Initialize(config, _contextUser) {
|
|
42
|
+
this.config = config;
|
|
43
|
+
}
|
|
44
|
+
/** Whether this driver may participate in searches. False until `CheckAvailability` says otherwise. */
|
|
45
|
+
IsAvailable() {
|
|
46
|
+
return this._available;
|
|
47
|
+
}
|
|
48
|
+
/** Why this driver is unavailable, for diagnostics. Null when it is available. */
|
|
49
|
+
get UnavailableReason() {
|
|
50
|
+
return this._available ? null : this._unavailableReason;
|
|
51
|
+
}
|
|
52
|
+
/** Mark this driver operational. Call from {@link CheckAvailability}. */
|
|
53
|
+
MarkAvailable() {
|
|
54
|
+
this._available = true;
|
|
55
|
+
this._unavailableReason = null;
|
|
56
|
+
}
|
|
57
|
+
/** Mark this driver unusable, with a reason an administrator can act on. */
|
|
58
|
+
MarkUnavailable(reason) {
|
|
59
|
+
this._available = false;
|
|
60
|
+
this._unavailableReason = reason;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read a credential value, preferring the decrypted `MJ: Credentials` record and falling back
|
|
64
|
+
* to an environment variable.
|
|
65
|
+
*
|
|
66
|
+
* The fallback exists because the web search actions this engine supersedes read their keys
|
|
67
|
+
* from `mj.config.cjs` / the environment, and a host upgrading should not have to migrate its
|
|
68
|
+
* secrets into the Credential store on the same day it gains the engine.
|
|
69
|
+
*
|
|
70
|
+
* @param credentialKey - key within the credential record's decrypted values, e.g. `apiKey`
|
|
71
|
+
* @param envVarName - environment variable consulted when no credential value is present
|
|
72
|
+
*/
|
|
73
|
+
GetSecret(credentialKey, envVarName) {
|
|
74
|
+
const fromCredential = this.config?.CredentialValues?.[credentialKey];
|
|
75
|
+
if (typeof fromCredential === 'string' && fromCredential.trim().length > 0) {
|
|
76
|
+
return fromCredential.trim();
|
|
77
|
+
}
|
|
78
|
+
const fromEnv = process.env[envVarName];
|
|
79
|
+
return fromEnv && fromEnv.trim().length > 0 ? fromEnv.trim() : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** Read a non-secret setting from the record's `ProviderConfig` JSON. */
|
|
82
|
+
GetConfigValue(key) {
|
|
83
|
+
const value = this.config?.ProviderConfig?.[key];
|
|
84
|
+
return value === undefined ? undefined : value;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The effective result cap: the caller's request, bounded by the metadata override (when set)
|
|
88
|
+
* and then by the vendor's own hard cap.
|
|
89
|
+
*
|
|
90
|
+
* Clamped rather than rejected — a caller asking for 50 wants as many as possible, and most
|
|
91
|
+
* vendors reject the whole request when asked for more than they serve.
|
|
92
|
+
*/
|
|
93
|
+
ResolveMaxResults(requested, fallback = 10) {
|
|
94
|
+
const asked = Math.floor(requested ?? this.config?.MaxResultsOverride ?? fallback);
|
|
95
|
+
const metadataCap = this.config?.MaxResultsOverride ?? Number.MAX_SAFE_INTEGER;
|
|
96
|
+
return Math.min(Math.max(asked, 1), metadataCap, this.Capabilities.MaxResultsCap);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Enumerate every registered driver with its capabilities, for admin UI dropdowns.
|
|
100
|
+
*
|
|
101
|
+
* Instantiates each registration to read `Capabilities`, so a driver's constructor must stay
|
|
102
|
+
* side-effect free.
|
|
103
|
+
*/
|
|
104
|
+
static GetAvailableProviders() {
|
|
105
|
+
const registrations = MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseWebSearchProvider);
|
|
106
|
+
const result = [];
|
|
107
|
+
// The same class loaded through two module paths registers twice; dedupe by key so the
|
|
108
|
+
// admin dropdown does not show a driver more than once.
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
for (const registration of registrations ?? []) {
|
|
111
|
+
const key = registration.Key;
|
|
112
|
+
if (!key || seen.has(key)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
seen.add(key);
|
|
116
|
+
try {
|
|
117
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseWebSearchProvider, key);
|
|
118
|
+
if (instance) {
|
|
119
|
+
result.push({ DriverClass: key, Capabilities: instance.Capabilities });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
// One malformed registration must not blank the whole catalog the admin UI reads.
|
|
124
|
+
LogError(`BaseWebSearchProvider.GetAvailableProviders: could not instantiate "${key}"`, undefined, e);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=BaseWebSearchProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseWebSearchProvider.js","sourceRoot":"","sources":["../src/BaseWebSearchProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Metadata-driven web search with priority failover.
|
|
3
|
+
*
|
|
4
|
+
* The engine loads Active `MJ: Web Search Providers` records, instantiates each one's driver via
|
|
5
|
+
* ClassFactory, and serves a search from the highest-priority driver that is available and capable.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/web-search-engine
|
|
8
|
+
*/
|
|
9
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
10
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
11
|
+
import { RegisteredWebSearchProviderInfo, WebSearchParams, WebSearchResult } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Singleton engine that routes a web search to a configured external provider.
|
|
14
|
+
*
|
|
15
|
+
* ## Selection
|
|
16
|
+
*
|
|
17
|
+
* **With an explicit `Provider`** the engine resolves it by `Name` or `DriverClass` and then
|
|
18
|
+
* fails closed with a specific code — it never substitutes another vendor. The caller asked for
|
|
19
|
+
* a particular thing; quietly serving something else turns a deliberate choice into an invisible
|
|
20
|
+
* one.
|
|
21
|
+
*
|
|
22
|
+
* **Without one** it filters to Active, available and capability-satisfying drivers, orders by
|
|
23
|
+
* `Priority` ascending, and tries each in turn. A `transient` failure (rate limit, 5xx, timeout)
|
|
24
|
+
* moves to the next driver; a `permanent` failure stops the run, because a request every
|
|
25
|
+
* provider will reject should not cost five paid API calls and bury its own error message.
|
|
26
|
+
*
|
|
27
|
+
* Zero hits is a **success**. A narrow query legitimately matches nothing, and treating that as
|
|
28
|
+
* failure makes the engine retry a query that will keep returning nothing.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* await WebSearchEngine.Instance.Config(false, contextUser);
|
|
33
|
+
* const result = await WebSearchEngine.Instance.Search(
|
|
34
|
+
* { Query: 'association management trends', MaxResults: 10 },
|
|
35
|
+
* contextUser,
|
|
36
|
+
* );
|
|
37
|
+
* if (result.Success) {
|
|
38
|
+
* for (const hit of result.Hits) console.log(hit.Title, hit.URL);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare class WebSearchEngine extends BaseSingleton<WebSearchEngine> {
|
|
43
|
+
/** Entity holding the provider records. */
|
|
44
|
+
private static readonly PROVIDER_ENTITY;
|
|
45
|
+
private _entries;
|
|
46
|
+
private _loaded;
|
|
47
|
+
/**
|
|
48
|
+
* Why the last provider load failed, or null if it succeeded. Held as state rather than
|
|
49
|
+
* only logged: the log line fires once, at load, while callers keep arriving afterwards —
|
|
50
|
+
* so without this the engine answers every later search with "nothing is configured" for
|
|
51
|
+
* what was a read failure.
|
|
52
|
+
*/
|
|
53
|
+
private _loadError;
|
|
54
|
+
private _loading;
|
|
55
|
+
private _provider;
|
|
56
|
+
constructor();
|
|
57
|
+
static get Instance(): WebSearchEngine;
|
|
58
|
+
/** Providers currently loaded and available, in priority order. */
|
|
59
|
+
get AvailableProviders(): readonly {
|
|
60
|
+
Name: string;
|
|
61
|
+
DriverClass: string;
|
|
62
|
+
Priority: number;
|
|
63
|
+
}[];
|
|
64
|
+
/** Every registered driver with its capabilities, for admin UI. */
|
|
65
|
+
GetRegisteredDrivers(): RegisteredWebSearchProviderInfo[];
|
|
66
|
+
/**
|
|
67
|
+
* Load provider metadata and initialise every driver. Cheap no-op once loaded.
|
|
68
|
+
*
|
|
69
|
+
* Concurrent callers share one in-flight load rather than each issuing their own RunView —
|
|
70
|
+
* an agent fan-out can call `Config` from a dozen places in the same tick.
|
|
71
|
+
*
|
|
72
|
+
* @param forceRefresh - reload even if already configured (after an admin edits providers)
|
|
73
|
+
*/
|
|
74
|
+
Config(forceRefresh: boolean, contextUser: UserInfo, provider?: IMetadataProvider): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Run a web search.
|
|
77
|
+
*
|
|
78
|
+
* Never throws for a search-level failure — inspect `Success` and `ResultCode`, as with
|
|
79
|
+
* `RunView`. Exceptions escaping a driver are caught and recorded as attempts.
|
|
80
|
+
*/
|
|
81
|
+
Search(params: WebSearchParams, contextUser: UserInfo): Promise<WebSearchResult>;
|
|
82
|
+
/** Try each candidate in order, honouring the transient/permanent distinction. */
|
|
83
|
+
private runCandidates;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve an explicitly requested provider, or describe precisely why it cannot serve.
|
|
86
|
+
*
|
|
87
|
+
* Each rejection is a distinct code so a caller can tell "you never configured this" from
|
|
88
|
+
* "its key is missing" from "it cannot do answers" — three different fixes.
|
|
89
|
+
*/
|
|
90
|
+
private resolveExplicit;
|
|
91
|
+
/** Providers that loaded but cannot serve, keyed by lowercased name and driver class. */
|
|
92
|
+
private _unavailable;
|
|
93
|
+
/** Eligible providers in priority order, or a failure describing why none qualified. */
|
|
94
|
+
private resolveByPriority;
|
|
95
|
+
private fail;
|
|
96
|
+
private summarise;
|
|
97
|
+
/** Load provider records, instantiate drivers, and keep the ones that are operational. */
|
|
98
|
+
private loadProviders;
|
|
99
|
+
/** Instantiate, configure and availability-check one provider record. */
|
|
100
|
+
private initializeProvider;
|
|
101
|
+
/** Remember why a configured provider is not serving, so an explicit request can say so. */
|
|
102
|
+
private recordUnavailable;
|
|
103
|
+
private parseProviderConfig;
|
|
104
|
+
/** Decrypt the linked credential, when one is set. Null when absent or unreadable. */
|
|
105
|
+
private resolveCredentialValues;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=WebSearchEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSearchEngine.d.ts","sourceRoot":"","sources":["../src/WebSearchEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACH,iBAAiB,EAIjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,aAAa,EAAY,MAAM,wBAAwB,CAAC;AAEjE,OAAO,EACH,+BAA+B,EAE/B,eAAe,EAEf,eAAe,EAElB,MAAM,SAAS,CAAC;AAgBjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,eAAgB,SAAQ,aAAa,CAAC,eAAe,CAAC;IAC/D,2CAA2C;IAC3C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAA8B;IAErE,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,SAAS,CAAgC;;IAOjD,WAAkB,QAAQ,IAAI,eAAe,CAE5C;IAED,mEAAmE;IACnE,IAAW,kBAAkB,IAAI,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAElG;IAED,mEAAmE;IAC5D,oBAAoB,IAAI,+BAA+B,EAAE;IAIhE;;;;;;;OAOG;IACU,MAAM,CACf,YAAY,EAAE,OAAO,EACrB,WAAW,EAAE,QAAQ,EACrB,QAAQ,CAAC,EAAE,iBAAiB,GAC7B,OAAO,CAAC,IAAI,CAAC;IAkBhB;;;;;OAKG;IACU,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC;IAgD7F,kFAAkF;YACpE,aAAa;IA6E3B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA+BvB,yFAAyF;IACzF,OAAO,CAAC,YAAY,CAAmC;IAEvD,wFAAwF;IACxF,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,IAAI;IAUZ,OAAO,CAAC,SAAS;IAOjB,0FAA0F;YAC5E,aAAa;IAgD3B,yEAAyE;YAC3D,kBAAkB;IAmEhC,4FAA4F;IAC5F,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,mBAAmB;IAa3B,sFAAsF;YACxE,uBAAuB;CA6BxC"}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Metadata-driven web search with priority failover.
|
|
3
|
+
*
|
|
4
|
+
* The engine loads Active `MJ: Web Search Providers` records, instantiates each one's driver via
|
|
5
|
+
* ClassFactory, and serves a search from the highest-priority driver that is available and capable.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/web-search-engine
|
|
8
|
+
*/
|
|
9
|
+
import { LogError, LogStatus, RunView, } from '@memberjunction/core';
|
|
10
|
+
import { CredentialEngine } from '@memberjunction/credentials';
|
|
11
|
+
import { BaseSingleton, MJGlobal } from '@memberjunction/global';
|
|
12
|
+
import { BaseWebSearchProvider } from './BaseWebSearchProvider.js';
|
|
13
|
+
/**
|
|
14
|
+
* Singleton engine that routes a web search to a configured external provider.
|
|
15
|
+
*
|
|
16
|
+
* ## Selection
|
|
17
|
+
*
|
|
18
|
+
* **With an explicit `Provider`** the engine resolves it by `Name` or `DriverClass` and then
|
|
19
|
+
* fails closed with a specific code — it never substitutes another vendor. The caller asked for
|
|
20
|
+
* a particular thing; quietly serving something else turns a deliberate choice into an invisible
|
|
21
|
+
* one.
|
|
22
|
+
*
|
|
23
|
+
* **Without one** it filters to Active, available and capability-satisfying drivers, orders by
|
|
24
|
+
* `Priority` ascending, and tries each in turn. A `transient` failure (rate limit, 5xx, timeout)
|
|
25
|
+
* moves to the next driver; a `permanent` failure stops the run, because a request every
|
|
26
|
+
* provider will reject should not cost five paid API calls and bury its own error message.
|
|
27
|
+
*
|
|
28
|
+
* Zero hits is a **success**. A narrow query legitimately matches nothing, and treating that as
|
|
29
|
+
* failure makes the engine retry a query that will keep returning nothing.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* await WebSearchEngine.Instance.Config(false, contextUser);
|
|
34
|
+
* const result = await WebSearchEngine.Instance.Search(
|
|
35
|
+
* { Query: 'association management trends', MaxResults: 10 },
|
|
36
|
+
* contextUser,
|
|
37
|
+
* );
|
|
38
|
+
* if (result.Success) {
|
|
39
|
+
* for (const hit of result.Hits) console.log(hit.Title, hit.URL);
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class WebSearchEngine extends BaseSingleton {
|
|
44
|
+
/** Entity holding the provider records. */
|
|
45
|
+
static { this.PROVIDER_ENTITY = 'MJ: Web Search Providers'; }
|
|
46
|
+
// Constructor must be public to satisfy BaseSingleton.getInstance()
|
|
47
|
+
constructor() {
|
|
48
|
+
super();
|
|
49
|
+
this._entries = [];
|
|
50
|
+
this._loaded = false;
|
|
51
|
+
/**
|
|
52
|
+
* Why the last provider load failed, or null if it succeeded. Held as state rather than
|
|
53
|
+
* only logged: the log line fires once, at load, while callers keep arriving afterwards —
|
|
54
|
+
* so without this the engine answers every later search with "nothing is configured" for
|
|
55
|
+
* what was a read failure.
|
|
56
|
+
*/
|
|
57
|
+
this._loadError = null;
|
|
58
|
+
this._loading = null;
|
|
59
|
+
/** Providers that loaded but cannot serve, keyed by lowercased name and driver class. */
|
|
60
|
+
this._unavailable = new Map();
|
|
61
|
+
}
|
|
62
|
+
static get Instance() {
|
|
63
|
+
return WebSearchEngine.getInstance();
|
|
64
|
+
}
|
|
65
|
+
/** Providers currently loaded and available, in priority order. */
|
|
66
|
+
get AvailableProviders() {
|
|
67
|
+
return this._entries.map((e) => ({ Name: e.Name, DriverClass: e.DriverClass, Priority: e.Priority }));
|
|
68
|
+
}
|
|
69
|
+
/** Every registered driver with its capabilities, for admin UI. */
|
|
70
|
+
GetRegisteredDrivers() {
|
|
71
|
+
return BaseWebSearchProvider.GetAvailableProviders();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Load provider metadata and initialise every driver. Cheap no-op once loaded.
|
|
75
|
+
*
|
|
76
|
+
* Concurrent callers share one in-flight load rather than each issuing their own RunView —
|
|
77
|
+
* an agent fan-out can call `Config` from a dozen places in the same tick.
|
|
78
|
+
*
|
|
79
|
+
* @param forceRefresh - reload even if already configured (after an admin edits providers)
|
|
80
|
+
*/
|
|
81
|
+
async Config(forceRefresh, contextUser, provider) {
|
|
82
|
+
if (provider) {
|
|
83
|
+
this._provider = provider;
|
|
84
|
+
}
|
|
85
|
+
if (this._loaded && !forceRefresh) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (this._loading && !forceRefresh) {
|
|
89
|
+
return this._loading;
|
|
90
|
+
}
|
|
91
|
+
this._loading = this.loadProviders(contextUser);
|
|
92
|
+
try {
|
|
93
|
+
await this._loading;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
this._loading = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run a web search.
|
|
101
|
+
*
|
|
102
|
+
* Never throws for a search-level failure — inspect `Success` and `ResultCode`, as with
|
|
103
|
+
* `RunView`. Exceptions escaping a driver are caught and recorded as attempts.
|
|
104
|
+
*/
|
|
105
|
+
async Search(params, contextUser) {
|
|
106
|
+
const attempts = [];
|
|
107
|
+
const query = params.Query?.trim();
|
|
108
|
+
if (!query) {
|
|
109
|
+
return this.fail({ code: 'MISSING_QUERY', message: 'Query is required.' }, attempts);
|
|
110
|
+
}
|
|
111
|
+
await this.Config(false, contextUser);
|
|
112
|
+
// Checked BEFORE the empty-list branch: a failed read also leaves the list empty, and
|
|
113
|
+
// reporting that as "add a record" sends an operator to fix configuration that is fine.
|
|
114
|
+
if (this._loadError) {
|
|
115
|
+
return this.fail({
|
|
116
|
+
code: 'PROVIDER_LOAD_FAILED',
|
|
117
|
+
message: `Could not read "${WebSearchEngine.PROVIDER_ENTITY}": ${this._loadError}. ` +
|
|
118
|
+
'This is a read failure, not a missing configuration — check that the entity ' +
|
|
119
|
+
'exists in metadata and that the caller can read it.',
|
|
120
|
+
}, attempts);
|
|
121
|
+
}
|
|
122
|
+
if (this._entries.length === 0) {
|
|
123
|
+
return this.fail({
|
|
124
|
+
code: 'NO_PROVIDERS_CONFIGURED',
|
|
125
|
+
message: 'No web search providers are configured. Add an Active record to ' +
|
|
126
|
+
`"${WebSearchEngine.PROVIDER_ENTITY}" whose DriverClass matches a registered driver.`,
|
|
127
|
+
}, attempts);
|
|
128
|
+
}
|
|
129
|
+
const candidates = params.Provider
|
|
130
|
+
? this.resolveExplicit(params)
|
|
131
|
+
: this.resolveByPriority(params);
|
|
132
|
+
if ('code' in candidates) {
|
|
133
|
+
return this.fail(candidates, attempts);
|
|
134
|
+
}
|
|
135
|
+
return this.runCandidates(candidates, { ...params, Query: query }, contextUser, attempts);
|
|
136
|
+
}
|
|
137
|
+
/** Try each candidate in order, honouring the transient/permanent distinction. */
|
|
138
|
+
async runCandidates(candidates, params, contextUser, attempts) {
|
|
139
|
+
for (const entry of candidates) {
|
|
140
|
+
const startedAt = Date.now();
|
|
141
|
+
try {
|
|
142
|
+
const response = await entry.Provider.ExecuteSearch(params, contextUser);
|
|
143
|
+
const durationMs = Date.now() - startedAt;
|
|
144
|
+
if (response.Success) {
|
|
145
|
+
attempts.push({
|
|
146
|
+
ProviderName: entry.Name,
|
|
147
|
+
DriverClass: entry.DriverClass,
|
|
148
|
+
Succeeded: true,
|
|
149
|
+
DurationMs: durationMs,
|
|
150
|
+
HitCount: response.Hits.length,
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
Success: true,
|
|
154
|
+
ResultCode: 'SUCCESS',
|
|
155
|
+
Hits: response.Hits,
|
|
156
|
+
Answer: response.Answer,
|
|
157
|
+
ProviderUsed: entry.Name,
|
|
158
|
+
Attempts: attempts,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const failureKind = response.FailureKind ?? 'transient';
|
|
162
|
+
attempts.push({
|
|
163
|
+
ProviderName: entry.Name,
|
|
164
|
+
DriverClass: entry.DriverClass,
|
|
165
|
+
Succeeded: false,
|
|
166
|
+
DurationMs: durationMs,
|
|
167
|
+
FailureKind: failureKind,
|
|
168
|
+
ErrorMessage: response.ErrorMessage,
|
|
169
|
+
});
|
|
170
|
+
// A rejected request is rejected everywhere. Stop rather than paying four more
|
|
171
|
+
// vendors to tell us the same thing and hiding the real message behind theirs.
|
|
172
|
+
if (failureKind === 'permanent') {
|
|
173
|
+
return this.fail({
|
|
174
|
+
code: 'INVALID_REQUEST',
|
|
175
|
+
message: `${entry.Name} rejected the request and retrying elsewhere cannot help: ` +
|
|
176
|
+
`${response.ErrorMessage ?? 'no detail supplied'}`,
|
|
177
|
+
}, attempts);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
// A driver that throws is a bug in that driver, not a reason to fail the search.
|
|
182
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
183
|
+
LogError(`WebSearchEngine: driver "${entry.DriverClass}" threw`, undefined, e);
|
|
184
|
+
attempts.push({
|
|
185
|
+
ProviderName: entry.Name,
|
|
186
|
+
DriverClass: entry.DriverClass,
|
|
187
|
+
Succeeded: false,
|
|
188
|
+
DurationMs: Date.now() - startedAt,
|
|
189
|
+
FailureKind: 'transient',
|
|
190
|
+
ErrorMessage: message,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return this.fail({
|
|
195
|
+
code: 'ALL_PROVIDERS_FAILED',
|
|
196
|
+
message: `All ${attempts.length} provider(s) failed: ${this.summarise(attempts)}`,
|
|
197
|
+
}, attempts);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolve an explicitly requested provider, or describe precisely why it cannot serve.
|
|
201
|
+
*
|
|
202
|
+
* Each rejection is a distinct code so a caller can tell "you never configured this" from
|
|
203
|
+
* "its key is missing" from "it cannot do answers" — three different fixes.
|
|
204
|
+
*/
|
|
205
|
+
resolveExplicit(params) {
|
|
206
|
+
const wanted = params.Provider.trim().toLowerCase();
|
|
207
|
+
const entry = this._entries.find((e) => e.Name.toLowerCase() === wanted || e.DriverClass.toLowerCase() === wanted);
|
|
208
|
+
if (!entry) {
|
|
209
|
+
// _entries holds only Active + available providers, so distinguish the reasons by
|
|
210
|
+
// re-reading what was loaded rather than reporting a blanket "not found".
|
|
211
|
+
const known = this._unavailable.get(wanted);
|
|
212
|
+
if (known) {
|
|
213
|
+
return { code: known.code, message: known.message };
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
code: 'PROVIDER_NOT_FOUND',
|
|
217
|
+
message: `No web search provider named "${params.Provider}". Configured and available: ` +
|
|
218
|
+
`${this._entries.map((e) => e.Name).join(', ') || '(none)'}.`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (params.IncludeAnswer && !entry.Provider.Capabilities.Answer) {
|
|
222
|
+
return {
|
|
223
|
+
code: 'PROVIDER_LACKS_CAPABILITY',
|
|
224
|
+
message: `Provider "${entry.Name}" cannot produce a synthesized answer (IncludeAnswer was requested).`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return [entry];
|
|
228
|
+
}
|
|
229
|
+
/** Eligible providers in priority order, or a failure describing why none qualified. */
|
|
230
|
+
resolveByPriority(params) {
|
|
231
|
+
const eligible = this._entries.filter((e) => !params.IncludeAnswer || e.Provider.Capabilities.Answer);
|
|
232
|
+
if (eligible.length === 0) {
|
|
233
|
+
return {
|
|
234
|
+
code: 'NO_ELIGIBLE_PROVIDER',
|
|
235
|
+
message: params.IncludeAnswer
|
|
236
|
+
? 'No available provider can produce a synthesized answer. Configure Tavily or Perplexity, or drop IncludeAnswer.'
|
|
237
|
+
: 'No available web search provider.',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return eligible;
|
|
241
|
+
}
|
|
242
|
+
fail(failure, attempts) {
|
|
243
|
+
return {
|
|
244
|
+
Success: false,
|
|
245
|
+
ResultCode: failure.code,
|
|
246
|
+
Hits: [],
|
|
247
|
+
Attempts: attempts,
|
|
248
|
+
ErrorMessage: failure.message,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
summarise(attempts) {
|
|
252
|
+
return attempts
|
|
253
|
+
.filter((a) => !a.Succeeded)
|
|
254
|
+
.map((a) => `${a.ProviderName}: ${a.ErrorMessage ?? 'unknown error'}`)
|
|
255
|
+
.join(' | ');
|
|
256
|
+
}
|
|
257
|
+
/** Load provider records, instantiate drivers, and keep the ones that are operational. */
|
|
258
|
+
async loadProviders(contextUser) {
|
|
259
|
+
this._entries = [];
|
|
260
|
+
this._unavailable = new Map();
|
|
261
|
+
this._loadError = null;
|
|
262
|
+
const rv = this._provider
|
|
263
|
+
? RunView.FromMetadataProvider(this._provider)
|
|
264
|
+
: new RunView();
|
|
265
|
+
// `entity_object` with the generated subclass, matching how SearchEngine types its own
|
|
266
|
+
// provider records. `Fields` is deliberately absent — it is ignored for entity_object, and
|
|
267
|
+
// a hand-listed projection is how a column added to the table later goes silently unread.
|
|
268
|
+
//
|
|
269
|
+
// Every status is loaded, not just Active. Filtering in SQL made a non-Active provider
|
|
270
|
+
// indistinguishable from one that was never configured: it reached neither `_entries` nor
|
|
271
|
+
// `_unavailable`, so pinning it reported PROVIDER_NOT_FOUND and sent an operator looking
|
|
272
|
+
// for a record that is sitting right there, one column away from working. The table holds
|
|
273
|
+
// a handful of rows, so reading them all and filtering here costs nothing.
|
|
274
|
+
const result = await rv.RunView({
|
|
275
|
+
EntityName: WebSearchEngine.PROVIDER_ENTITY,
|
|
276
|
+
OrderBy: 'Priority ASC, Name ASC',
|
|
277
|
+
ResultType: 'entity_object',
|
|
278
|
+
}, contextUser);
|
|
279
|
+
// RunView does not throw — branch on Success or the engine silently reports
|
|
280
|
+
// "no providers configured" for what is actually a database error.
|
|
281
|
+
if (!result.Success) {
|
|
282
|
+
this._loadError = result.ErrorMessage || 'the provider view query failed without a message';
|
|
283
|
+
LogError(`WebSearchEngine: failed to load providers — ${this._loadError}`);
|
|
284
|
+
this._loaded = true;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this._loadError = null;
|
|
288
|
+
for (const record of result.Results ?? []) {
|
|
289
|
+
await this.initializeProvider(record, contextUser);
|
|
290
|
+
}
|
|
291
|
+
this._loaded = true;
|
|
292
|
+
LogStatus(`WebSearchEngine: ${this._entries.length} provider(s) available ` +
|
|
293
|
+
`(${this._entries.map((e) => e.Name).join(', ') || 'none'})`);
|
|
294
|
+
}
|
|
295
|
+
/** Instantiate, configure and availability-check one provider record. */
|
|
296
|
+
async initializeProvider(record, contextUser) {
|
|
297
|
+
const driverClass = record.DriverClass;
|
|
298
|
+
// Parked deliberately — remembered with its own code, and never instantiated. Building a
|
|
299
|
+
// driver for a provider an administrator switched off would run its constructor and
|
|
300
|
+
// availability check for a row that cannot serve anything.
|
|
301
|
+
if (record.Status !== 'Active') {
|
|
302
|
+
this.recordUnavailable(record, {
|
|
303
|
+
code: 'PROVIDER_NOT_ACTIVE',
|
|
304
|
+
message: `Provider "${record.Name}" is configured but its Status is "${record.Status}", ` +
|
|
305
|
+
'not Active. Set it to Active to bring it into the selection pool.',
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const driver = MJGlobal.Instance.ClassFactory.CreateInstance(BaseWebSearchProvider, driverClass);
|
|
311
|
+
if (!driver) {
|
|
312
|
+
const message = `No registered driver for DriverClass "${driverClass}" (provider "${record.Name}").`;
|
|
313
|
+
LogError(`WebSearchEngine: ${message}`);
|
|
314
|
+
this.recordUnavailable(record, { code: 'PROVIDER_UNAVAILABLE', message });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const config = {
|
|
318
|
+
Name: record.Name,
|
|
319
|
+
ProviderConfig: this.parseProviderConfig(record),
|
|
320
|
+
CredentialValues: await this.resolveCredentialValues(record, contextUser),
|
|
321
|
+
MaxResultsOverride: record.MaxResultsOverride ?? null,
|
|
322
|
+
Priority: record.Priority,
|
|
323
|
+
};
|
|
324
|
+
await driver.Initialize(config, contextUser);
|
|
325
|
+
await driver.CheckAvailability(contextUser);
|
|
326
|
+
if (driver.IsAvailable()) {
|
|
327
|
+
this._entries.push({
|
|
328
|
+
Name: record.Name,
|
|
329
|
+
DriverClass: driverClass,
|
|
330
|
+
Priority: record.Priority,
|
|
331
|
+
Provider: driver,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.recordUnavailable(record, {
|
|
336
|
+
code: 'PROVIDER_UNAVAILABLE',
|
|
337
|
+
message: `Provider "${record.Name}" is Active but not operational: ` +
|
|
338
|
+
`${driver.UnavailableReason ?? 'no reason reported'}.`,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
344
|
+
LogError(`WebSearchEngine: failed to initialize "${record.Name}"`, undefined, e);
|
|
345
|
+
this.recordUnavailable(record, {
|
|
346
|
+
code: 'PROVIDER_UNAVAILABLE',
|
|
347
|
+
message: `Provider "${record.Name}" failed to initialize: ${message}`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/** Remember why a configured provider is not serving, so an explicit request can say so. */
|
|
352
|
+
recordUnavailable(record, failure) {
|
|
353
|
+
this._unavailable.set(record.Name.toLowerCase(), failure);
|
|
354
|
+
this._unavailable.set(record.DriverClass.toLowerCase(), failure);
|
|
355
|
+
}
|
|
356
|
+
parseProviderConfig(record) {
|
|
357
|
+
if (!record.ProviderConfig) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
return JSON.parse(record.ProviderConfig);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Bad JSON disables tuning, not the provider — the driver's env fallback still works.
|
|
365
|
+
LogError(`WebSearchEngine: invalid JSON in ProviderConfig for "${record.Name}"; ignoring it.`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/** Decrypt the linked credential, when one is set. Null when absent or unreadable. */
|
|
370
|
+
async resolveCredentialValues(record, contextUser) {
|
|
371
|
+
if (!record.CredentialID) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
await CredentialEngine.Instance.Config(false, contextUser);
|
|
376
|
+
const credential = CredentialEngine.Instance.getCredentialById(record.CredentialID);
|
|
377
|
+
if (!credential) {
|
|
378
|
+
LogError(`WebSearchEngine: CredentialID ${record.CredentialID} on "${record.Name}" does not resolve.`);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const resolved = await CredentialEngine.Instance.getCredential(credential.Name, {
|
|
382
|
+
credentialId: record.CredentialID,
|
|
383
|
+
contextUser,
|
|
384
|
+
subsystem: 'WebSearch',
|
|
385
|
+
});
|
|
386
|
+
return resolved.values ?? null;
|
|
387
|
+
}
|
|
388
|
+
catch (e) {
|
|
389
|
+
// Returning null lets the driver fall back to its environment variable and
|
|
390
|
+
// self-disable cleanly if that is missing too.
|
|
391
|
+
LogError(`WebSearchEngine: could not decrypt credential for "${record.Name}"`, undefined, e);
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=WebSearchEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSearchEngine.js","sourceRoot":"","sources":["../src/WebSearchEngine.ts"],"names":[],"mappings":"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"}
|