@memberjunction/web-search-engine 0.0.1-seed.2 → 6.2.0-edge.1

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 (47) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +343 -3
  3. package/dist/BaseWebSearchProvider.d.ts +89 -0
  4. package/dist/BaseWebSearchProvider.d.ts.map +1 -0
  5. package/dist/BaseWebSearchProvider.js +130 -0
  6. package/dist/BaseWebSearchProvider.js.map +1 -0
  7. package/dist/WebSearchEngine.d.ts +107 -0
  8. package/dist/WebSearchEngine.d.ts.map +1 -0
  9. package/dist/WebSearchEngine.js +396 -0
  10. package/dist/WebSearchEngine.js.map +1 -0
  11. package/dist/index.d.ts +28 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +43 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/operations/WebSearchQueryOperation.d.ts +8 -0
  16. package/dist/operations/WebSearchQueryOperation.d.ts.map +1 -0
  17. package/dist/operations/WebSearchQueryOperation.js +72 -0
  18. package/dist/operations/WebSearchQueryOperation.js.map +1 -0
  19. package/dist/providers/BraveWebSearchProvider.d.ts +41 -0
  20. package/dist/providers/BraveWebSearchProvider.d.ts.map +1 -0
  21. package/dist/providers/BraveWebSearchProvider.js +169 -0
  22. package/dist/providers/BraveWebSearchProvider.js.map +1 -0
  23. package/dist/providers/DuckDuckGoWebSearchProvider.d.ts +45 -0
  24. package/dist/providers/DuckDuckGoWebSearchProvider.d.ts.map +1 -0
  25. package/dist/providers/DuckDuckGoWebSearchProvider.js +116 -0
  26. package/dist/providers/DuckDuckGoWebSearchProvider.js.map +1 -0
  27. package/dist/providers/GoogleCustomSearchWebSearchProvider.d.ts +36 -0
  28. package/dist/providers/GoogleCustomSearchWebSearchProvider.d.ts.map +1 -0
  29. package/dist/providers/GoogleCustomSearchWebSearchProvider.js +125 -0
  30. package/dist/providers/GoogleCustomSearchWebSearchProvider.js.map +1 -0
  31. package/dist/providers/PerplexityWebSearchProvider.d.ts +40 -0
  32. package/dist/providers/PerplexityWebSearchProvider.d.ts.map +1 -0
  33. package/dist/providers/PerplexityWebSearchProvider.js +148 -0
  34. package/dist/providers/PerplexityWebSearchProvider.js.map +1 -0
  35. package/dist/providers/TavilyWebSearchProvider.d.ts +30 -0
  36. package/dist/providers/TavilyWebSearchProvider.d.ts.map +1 -0
  37. package/dist/providers/TavilyWebSearchProvider.js +126 -0
  38. package/dist/providers/TavilyWebSearchProvider.js.map +1 -0
  39. package/dist/providers/httpFailure.d.ts +35 -0
  40. package/dist/providers/httpFailure.d.ts.map +1 -0
  41. package/dist/providers/httpFailure.js +148 -0
  42. package/dist/providers/httpFailure.js.map +1 -0
  43. package/dist/types.d.ts +214 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +17 -0
  46. package/dist/types.js.map +1 -0
  47. 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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"}