@omnicross/core 0.2.0 → 0.3.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.
Files changed (119) hide show
  1. package/dist/{BuiltinToolExecutor-CS2WpXhM.d.cts → BuiltinToolExecutor-BHgH-EaZ.d.cts} +25 -2
  2. package/dist/{BuiltinToolExecutor-BluWyeob.d.ts → BuiltinToolExecutor-Bi1LmiTP.d.ts} +25 -2
  3. package/dist/{CompletionService-BiJqftc-.d.cts → CompletionService-BniP7j88.d.cts} +2 -2
  4. package/dist/{CompletionService-NXN_Av5W.d.ts → CompletionService-BqrKPwfA.d.ts} +2 -2
  5. package/dist/{ProviderProxy-CgIcCVwk.d.cts → ProviderProxy-Bz_nkOwq.d.cts} +1 -1
  6. package/dist/{ProviderProxy-DYoY5XoT.d.ts → ProviderProxy-Ckr6oTrE.d.ts} +1 -1
  7. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +2 -2
  8. package/dist/auth/GeminiCodeAssistProjectResolver.js +1 -1
  9. package/dist/{chunk-FJNKXOWE.js → chunk-2C3TTBS7.js} +4 -4
  10. package/dist/{chunk-NZYN7C3P.cjs → chunk-2RCCLQYV.cjs} +53 -53
  11. package/dist/{chunk-CDHS2QDC.js → chunk-4PFWFODB.js} +53 -53
  12. package/dist/{chunk-OST5RJOJ.cjs → chunk-4Z3ZLLC2.cjs} +13 -7
  13. package/dist/chunk-7RJI3NDI.cjs +236 -0
  14. package/dist/chunk-BTBEJRGL.cjs +781 -0
  15. package/dist/chunk-DBBR6RHF.js +236 -0
  16. package/dist/chunk-DXWFBBSK.js +781 -0
  17. package/dist/chunk-E4QHVXV5.cjs +118 -0
  18. package/dist/chunk-L7O2YQ5O.js +386 -0
  19. package/dist/{chunk-WSJE42SG.js → chunk-LKDAN4D7.js} +3684 -2462
  20. package/dist/{chunk-DEIUR2TH.js → chunk-LQYS2N47.js} +12 -8
  21. package/dist/chunk-MZEGQSKQ.js +118 -0
  22. package/dist/chunk-PTU7SXM3.cjs +386 -0
  23. package/dist/{chunk-CTKM773J.cjs → chunk-UPQIWJIE.cjs} +12 -8
  24. package/dist/{chunk-CFSGYFOZ.js → chunk-VL5TUDTM.js} +7 -1
  25. package/dist/{chunk-7B56AJBB.cjs → chunk-VSYQ6XYN.cjs} +2233 -1011
  26. package/dist/{chunk-TVYA5JSQ.cjs → chunk-ZAPN7XA6.cjs} +5 -5
  27. package/dist/completion/BuiltinToolExecutor.cjs +2 -2
  28. package/dist/completion/BuiltinToolExecutor.d.cts +3 -1
  29. package/dist/completion/BuiltinToolExecutor.d.ts +3 -1
  30. package/dist/completion/BuiltinToolExecutor.js +1 -1
  31. package/dist/completion/CompletionService.cjs +11 -10
  32. package/dist/completion/CompletionService.d.cts +6 -3
  33. package/dist/completion/CompletionService.d.ts +6 -3
  34. package/dist/completion/CompletionService.js +10 -9
  35. package/dist/completion.cjs +11 -10
  36. package/dist/completion.d.cts +6 -3
  37. package/dist/completion.d.ts +6 -3
  38. package/dist/completion.js +10 -9
  39. package/dist/egress-I46O_p5L.d.cts +126 -0
  40. package/dist/egress-I46O_p5L.d.ts +126 -0
  41. package/dist/frontends-Doz0RaWu.d.cts +97 -0
  42. package/dist/frontends-Doz0RaWu.d.ts +97 -0
  43. package/dist/image-generation.cjs +2 -2
  44. package/dist/image-generation.d.cts +7 -4
  45. package/dist/image-generation.d.ts +7 -4
  46. package/dist/image-generation.js +1 -1
  47. package/dist/index.cjs +25 -10
  48. package/dist/index.d.cts +12 -8
  49. package/dist/index.d.ts +12 -8
  50. package/dist/index.js +24 -9
  51. package/dist/openai-operation.d.cts +4 -1
  52. package/dist/openai-operation.d.ts +4 -1
  53. package/dist/outbound-api/routeResolver.d.cts +9 -5
  54. package/dist/outbound-api/routeResolver.d.ts +9 -5
  55. package/dist/outbound-api/subscriptionRegistryPort.d.cts +4 -1
  56. package/dist/outbound-api/subscriptionRegistryPort.d.ts +4 -1
  57. package/dist/outbound-api/types.d.cts +8 -4
  58. package/dist/outbound-api/types.d.ts +8 -4
  59. package/dist/outbound-api.cjs +25 -10
  60. package/dist/outbound-api.d.cts +117 -8
  61. package/dist/outbound-api.d.ts +117 -8
  62. package/dist/outbound-api.js +24 -9
  63. package/dist/pipeline/AccountAllowanceScheduling.d.cts +8 -4
  64. package/dist/pipeline/AccountAllowanceScheduling.d.ts +8 -4
  65. package/dist/pipeline/upstreamFetch.cjs +2 -2
  66. package/dist/pipeline/upstreamFetch.js +1 -1
  67. package/dist/ports.d.cts +8 -4
  68. package/dist/ports.d.ts +8 -4
  69. package/dist/provider-proxy/ProviderProxy.cjs +11 -10
  70. package/dist/provider-proxy/ProviderProxy.d.cts +5 -2
  71. package/dist/provider-proxy/ProviderProxy.d.ts +5 -2
  72. package/dist/provider-proxy/ProviderProxy.js +10 -9
  73. package/dist/provider-proxy/ingress/providerProxyShared.cjs +13 -10
  74. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +22 -3
  75. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +22 -3
  76. package/dist/provider-proxy/ingress/providerProxyShared.js +12 -9
  77. package/dist/provider-proxy/types.d.cts +4 -1
  78. package/dist/provider-proxy/types.d.ts +4 -1
  79. package/dist/provider-proxy.cjs +11 -10
  80. package/dist/provider-proxy.d.cts +10 -6
  81. package/dist/provider-proxy.d.ts +10 -6
  82. package/dist/provider-proxy.js +10 -9
  83. package/dist/proxy-UZTtjPZx.d.cts +18 -0
  84. package/dist/proxy-UZTtjPZx.d.ts +18 -0
  85. package/dist/{routeResolver-rpKQLyDO.d.cts → routeResolver-Da0yvkL1.d.cts} +2 -2
  86. package/dist/{routeResolver-CTCcSHWu.d.ts → routeResolver-STwMz_G4.d.ts} +2 -2
  87. package/dist/runtime-5NCT3J3T.cjs +8 -0
  88. package/dist/runtime-JTHUWCBM.js +8 -0
  89. package/dist/runtime-t8AZacFF.d.cts +233 -0
  90. package/dist/runtime-t8AZacFF.d.ts +233 -0
  91. package/dist/search/api.cjs +766 -0
  92. package/dist/search/api.d.cts +385 -0
  93. package/dist/search/api.d.ts +385 -0
  94. package/dist/search/api.js +766 -0
  95. package/dist/search/http.cjs +37 -0
  96. package/dist/search/http.d.cts +323 -0
  97. package/dist/search/http.d.ts +323 -0
  98. package/dist/search/http.js +37 -0
  99. package/dist/search.cjs +57 -0
  100. package/dist/search.d.cts +113 -0
  101. package/dist/search.d.ts +113 -0
  102. package/dist/search.js +57 -0
  103. package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -3
  104. package/dist/transformer/transformers/AnthropicTransformer.js +2 -2
  105. package/dist/transformer/transformers.cjs +7 -7
  106. package/dist/transformer/transformers.js +6 -6
  107. package/dist/transformer.cjs +6 -6
  108. package/dist/transformer.js +5 -5
  109. package/dist/{types-DsHBBk3h.d.cts → types-BSIJdsyl.d.cts} +15 -0
  110. package/dist/{types-CbglB73l.d.cts → types-Be-yt_6T.d.cts} +1 -1
  111. package/dist/{types-CVRge76p.d.ts → types-By9Tg6-I.d.ts} +15 -0
  112. package/dist/types-CbCZqLGg.d.cts +127 -0
  113. package/dist/types-CbCZqLGg.d.ts +127 -0
  114. package/dist/{types-jjU24eYG.d.cts → types-D-oHYusb.d.cts} +73 -4
  115. package/dist/{types-Bd72H4SB.d.ts → types-Dm4_36V4.d.ts} +1 -1
  116. package/dist/{types-5T223WYQ.d.ts → types-Dvlpjn0d.d.ts} +73 -4
  117. package/package.json +6 -3
  118. package/dist/{chunk-6OC3ORW6.js → chunk-EUZRH544.js} +3 -3
  119. package/dist/{chunk-SQMAEDNE.cjs → chunk-POUTD5AM.cjs} +2 -2
@@ -0,0 +1,118 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/search/frontends.ts
2
+ var SEARCH_FRONTEND_NAMES = Object.freeze([
3
+ "codex",
4
+ "responses",
5
+ "anthropic"
6
+ ]);
7
+ var SEARCH_FRONTEND_MODES = Object.freeze([
8
+ "native",
9
+ "managed",
10
+ "off"
11
+ ]);
12
+ var DEFAULT_SEARCH_FRONTEND_MODES = Object.freeze({
13
+ codex: "off",
14
+ responses: "native",
15
+ anthropic: "native"
16
+ });
17
+ var SEARCH_UNSUPPORTED_CAPABILITY_CODE = "unsupported_capability";
18
+ function searchErrorHttpStatus(code) {
19
+ switch (code) {
20
+ case "rate_limited":
21
+ return 429;
22
+ case "timeout":
23
+ return 504;
24
+ case "cancelled":
25
+ return 499;
26
+ case "config_missing":
27
+ return 503;
28
+ case "auth_failed":
29
+ case "upstream_unavailable":
30
+ case "parse_failed":
31
+ case "policy_denied":
32
+ return 502;
33
+ default:
34
+ return 502;
35
+ }
36
+ }
37
+ function isSearchFrontendMode(value) {
38
+ return typeof value === "string" && SEARCH_FRONTEND_MODES.includes(value);
39
+ }
40
+ function record(value) {
41
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
42
+ }
43
+ function normalizeSearchFrontendModes(value) {
44
+ const raw = _nullishCoalesce(record(value), () => ( {}));
45
+ const modes = {
46
+ ...DEFAULT_SEARCH_FRONTEND_MODES
47
+ };
48
+ for (const name of SEARCH_FRONTEND_NAMES) {
49
+ const candidate = raw[name];
50
+ if (isSearchFrontendMode(candidate)) modes[name] = candidate;
51
+ }
52
+ return Object.freeze(modes);
53
+ }
54
+ function validateSearchFrontendModes(value, path = "$.search.modes") {
55
+ if (value === void 0) return [];
56
+ const raw = record(value);
57
+ if (!raw) return [`${path}: expected an object`];
58
+ const errors = [];
59
+ for (const [key, candidate] of Object.entries(raw)) {
60
+ if (!SEARCH_FRONTEND_NAMES.includes(key)) {
61
+ errors.push(`${path}.${key}: unknown frontend (expected ${SEARCH_FRONTEND_NAMES.join(", ")})`);
62
+ continue;
63
+ }
64
+ if (isSearchFrontendMode(candidate)) continue;
65
+ errors.push(
66
+ `${path}.${key}: not a mode (got ${typeof candidate}; expected ${SEARCH_FRONTEND_MODES.join(", ")})`
67
+ );
68
+ }
69
+ return errors;
70
+ }
71
+
72
+ // src/search/webSearchBackendCompat.ts
73
+
74
+
75
+
76
+ var _searchcompat = require('@omnicross/contracts/search-compat');
77
+ var _searchtypes = require('@omnicross/contracts/search-types');
78
+ function searchRuntimeAsWebSearchBackend(runtime, options = {}) {
79
+ return {
80
+ async search(query, providerId, searchOptions) {
81
+ try {
82
+ const response = await runtime.search({
83
+ query,
84
+ provider: providerId,
85
+ // `WebSearchOptions` is a strict subset of `SearchOptions` with
86
+ // identical field names — asserted in the contracts tests.
87
+ ...searchOptions === void 0 ? {} : { options: searchOptions }
88
+ });
89
+ return _searchcompat.toLegacyWebSearchResponse.call(void 0, response);
90
+ } catch (error) {
91
+ return _searchcompat.searchErrorToLegacyWebSearchResponse.call(void 0, query, _searchtypes.toSearchErrorShape.call(void 0, error));
92
+ }
93
+ },
94
+ isProviderEnabled(id) {
95
+ return runtime.listProviders().some((descriptor) => descriptor.id === id);
96
+ },
97
+ async readUrl(url, readOptions) {
98
+ if (options.readUrl !== void 0) return options.readUrl(url, readOptions);
99
+ return {
100
+ success: false,
101
+ url,
102
+ error: "URL reading is not available through the search runtime adapter"
103
+ };
104
+ }
105
+ };
106
+ }
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+ exports.SEARCH_FRONTEND_NAMES = SEARCH_FRONTEND_NAMES; exports.SEARCH_FRONTEND_MODES = SEARCH_FRONTEND_MODES; exports.DEFAULT_SEARCH_FRONTEND_MODES = DEFAULT_SEARCH_FRONTEND_MODES; exports.SEARCH_UNSUPPORTED_CAPABILITY_CODE = SEARCH_UNSUPPORTED_CAPABILITY_CODE; exports.searchErrorHttpStatus = searchErrorHttpStatus; exports.isSearchFrontendMode = isSearchFrontendMode; exports.normalizeSearchFrontendModes = normalizeSearchFrontendModes; exports.validateSearchFrontendModes = validateSearchFrontendModes; exports.searchRuntimeAsWebSearchBackend = searchRuntimeAsWebSearchBackend;
@@ -0,0 +1,386 @@
1
+ import {
2
+ builtinHttpSearchContributions
3
+ } from "./chunk-DXWFBBSK.js";
4
+
5
+ // src/search/orchestrator.ts
6
+ import { SearchProviderError, toSearchErrorShape } from "@omnicross/contracts/search-types";
7
+ import { createHash, randomUUID } from "crypto";
8
+
9
+ // src/search/normalize.ts
10
+ var DEFAULT_NORMALIZED_MAX_RESULTS = 5;
11
+ var SERP_REDIRECT_RULES = [
12
+ { domain: "google.com", paths: ["/search", "/goto"] },
13
+ { domain: "bing.com", paths: ["/search"] },
14
+ { domain: "duckduckgo.com", paths: ["/html/", "/l/"] },
15
+ { domain: "baidu.com", paths: ["/s"] }
16
+ ];
17
+ function isDirectResultUrl(rawUrl) {
18
+ let url;
19
+ try {
20
+ url = new URL(rawUrl);
21
+ } catch {
22
+ return false;
23
+ }
24
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
25
+ const host = url.hostname.toLowerCase();
26
+ for (const rule of SERP_REDIRECT_RULES) {
27
+ if (host !== rule.domain && !host.endsWith(`.${rule.domain}`)) continue;
28
+ if (rule.paths.includes(url.pathname)) return false;
29
+ }
30
+ return true;
31
+ }
32
+ function normalizedResultLimit(maxResults) {
33
+ const requested = maxResults === void 0 || !Number.isFinite(maxResults) ? DEFAULT_NORMALIZED_MAX_RESULTS : Math.floor(maxResults);
34
+ return Math.max(1, requested);
35
+ }
36
+ function normalizeSearchResults(results, maxResults) {
37
+ const limit = normalizedResultLimit(maxResults);
38
+ const seenUrls = /* @__PURE__ */ new Set();
39
+ const normalized = [];
40
+ for (const result of results) {
41
+ if (result === null || typeof result !== "object") continue;
42
+ const title = typeof result.title === "string" ? result.title.trim() : "";
43
+ const url = typeof result.url === "string" ? result.url.trim() : "";
44
+ if (title.length === 0 || !isDirectResultUrl(url)) continue;
45
+ if (seenUrls.has(url)) continue;
46
+ seenUrls.add(url);
47
+ normalized.push({
48
+ title,
49
+ url,
50
+ content: typeof result.content === "string" ? result.content.trim() : ""
51
+ });
52
+ if (normalized.length >= limit) break;
53
+ }
54
+ return normalized;
55
+ }
56
+
57
+ // src/search/orchestrator.ts
58
+ var QUERY_HASH_LENGTH = 12;
59
+ function hashSearchQuery(query) {
60
+ return createHash("sha256").update(query).digest("hex").slice(0, QUERY_HASH_LENGTH);
61
+ }
62
+ var SearchOrchestrator = class {
63
+ registry;
64
+ defaultPolicy;
65
+ onEvent;
66
+ constructor(registry, options = {}) {
67
+ this.registry = registry;
68
+ this.defaultPolicy = options.policy ?? {};
69
+ this.onEvent = options.onEvent;
70
+ }
71
+ /**
72
+ * Run one search.
73
+ *
74
+ * @param request - the query, an optional pinned provider, and per-request
75
+ * options. `options` is passed to the provider untouched: providers own
76
+ * their own timeouts and the orchestrator adds no second timeout layer.
77
+ * @param policyOverride - per-request policy, merged field-by-field over the
78
+ * orchestrator's default.
79
+ * @throws {SearchProviderError} when no provider produced a response.
80
+ */
81
+ async search(request, policyOverride) {
82
+ const policy = { ...this.defaultPolicy, ...policyOverride };
83
+ const requestId = randomUUID();
84
+ const queryHash = hashSearchQuery(request.query);
85
+ const startedAt = Date.now();
86
+ const attempts = [];
87
+ const emitComplete = (providerId, resultCount) => {
88
+ this.emit({
89
+ type: "search_complete",
90
+ requestId,
91
+ queryHash,
92
+ ...providerId === void 0 ? {} : { providerId },
93
+ resultCount,
94
+ fallbackCount: fallbackCountOf(attempts),
95
+ durationMs: Date.now() - startedAt
96
+ });
97
+ };
98
+ let plan;
99
+ try {
100
+ plan = this.planCandidates(request, policy);
101
+ } catch (error) {
102
+ emitComplete(void 0, 0);
103
+ throw error;
104
+ }
105
+ if (plan.skippedPreferred !== void 0) {
106
+ this.emit({
107
+ type: "search_attempt",
108
+ requestId,
109
+ queryHash,
110
+ providerId: plan.skippedPreferred.providerId,
111
+ outcome: "failed",
112
+ errorCode: plan.skippedPreferred.errorCode,
113
+ durationMs: 0
114
+ });
115
+ }
116
+ let lastError;
117
+ for (const candidate of plan.candidates) {
118
+ const attemptStartedAt = Date.now();
119
+ try {
120
+ const produced = await candidate.provider.search(request.query, request.options);
121
+ const durationMs = Date.now() - attemptStartedAt;
122
+ const results = normalizeSearchResults(produced, request.options?.maxResults);
123
+ attempts.push({
124
+ providerId: candidate.id,
125
+ outcome: "success",
126
+ resultCount: results.length,
127
+ durationMs
128
+ });
129
+ this.emit({
130
+ type: "search_attempt",
131
+ requestId,
132
+ queryHash,
133
+ providerId: candidate.id,
134
+ outcome: "success",
135
+ resultCount: results.length,
136
+ durationMs
137
+ });
138
+ emitComplete(candidate.id, results.length);
139
+ return {
140
+ query: request.query,
141
+ providerId: candidate.id,
142
+ results,
143
+ attempts,
144
+ fallbackCount: fallbackCountOf(attempts)
145
+ };
146
+ } catch (error) {
147
+ const durationMs = Date.now() - attemptStartedAt;
148
+ const shape = toSearchErrorShape(error);
149
+ const isCancelled = shape.code === "cancelled" || request.options?.signal?.aborted === true;
150
+ const errorCode = isCancelled ? "cancelled" : shape.code;
151
+ attempts.push({
152
+ providerId: candidate.id,
153
+ outcome: "failed",
154
+ errorCode,
155
+ durationMs
156
+ });
157
+ this.emit({
158
+ type: "search_attempt",
159
+ requestId,
160
+ queryHash,
161
+ providerId: candidate.id,
162
+ outcome: "failed",
163
+ errorCode,
164
+ durationMs
165
+ });
166
+ if (isCancelled) {
167
+ emitComplete(void 0, 0);
168
+ throw shape.code === "cancelled" ? error : new SearchProviderError("cancelled", "search was cancelled by the caller", {
169
+ providerId: candidate.id,
170
+ retryable: false,
171
+ cause: error
172
+ });
173
+ }
174
+ lastError = asSearchProviderError(error, shape.code, shape.message, candidate.id);
175
+ }
176
+ }
177
+ emitComplete(void 0, 0);
178
+ throw exhaustionError(attempts, lastError);
179
+ }
180
+ /** Resolve the candidate list for one request. */
181
+ planCandidates(request, policy) {
182
+ if (request.provider !== void 0) {
183
+ const pinned = this.registry.get(request.provider);
184
+ if (pinned === void 0) {
185
+ throw new SearchProviderError(
186
+ "upstream_unavailable",
187
+ `search provider '${request.provider}' is not registered in this runtime`,
188
+ { providerId: request.provider, retryable: false }
189
+ );
190
+ }
191
+ if (!isAllowed(pinned.id, policy)) {
192
+ throw new SearchProviderError(
193
+ "policy_denied",
194
+ `search provider '${request.provider}' is not allowed by the active search policy`,
195
+ { providerId: request.provider, retryable: false }
196
+ );
197
+ }
198
+ return { candidates: [pinned] };
199
+ }
200
+ const allowed = this.registry.list().filter((entry) => isAllowed(entry.id, policy));
201
+ let ordered = allowed;
202
+ let skippedPreferred;
203
+ if (policy.preferred !== void 0) {
204
+ const preferred = allowed.find((entry) => entry.id === policy.preferred);
205
+ if (preferred === void 0) {
206
+ skippedPreferred = {
207
+ providerId: policy.preferred,
208
+ errorCode: this.registry.has(policy.preferred) ? "policy_denied" : "upstream_unavailable"
209
+ };
210
+ } else {
211
+ ordered = [preferred, ...allowed.filter((entry) => entry.id !== preferred.id)];
212
+ }
213
+ }
214
+ if (policy.fallbackEnabled === false) ordered = ordered.slice(0, 1);
215
+ if (policy.maxAttempts !== void 0) {
216
+ ordered = ordered.slice(0, Math.max(0, Math.floor(policy.maxAttempts)));
217
+ }
218
+ return { candidates: ordered, ...skippedPreferred === void 0 ? {} : { skippedPreferred } };
219
+ }
220
+ /** Deliver one event. A listener that throws must never fail a search. */
221
+ emit(event) {
222
+ if (this.onEvent === void 0) return;
223
+ try {
224
+ this.onEvent(event);
225
+ } catch {
226
+ }
227
+ }
228
+ };
229
+ function fallbackCountOf(attempts) {
230
+ return Math.max(0, attempts.length - 1);
231
+ }
232
+ function isAllowed(id, policy) {
233
+ return policy.allowed === void 0 || policy.allowed.includes(id);
234
+ }
235
+ function asSearchProviderError(error, code, message, providerId) {
236
+ if (error instanceof SearchProviderError) return error;
237
+ return new SearchProviderError(code, message, { providerId, cause: error });
238
+ }
239
+ function exhaustionError(attempts, lastError) {
240
+ if (attempts.length === 1 && lastError !== void 0) return lastError;
241
+ if (attempts.length === 0) {
242
+ return new SearchProviderError(
243
+ "upstream_unavailable",
244
+ "no eligible search provider was available (0 attempts)",
245
+ { retryable: false }
246
+ );
247
+ }
248
+ const trail = attempts.map((attempt) => `${attempt.providerId}:${attempt.errorCode ?? "unknown"}`).join(",");
249
+ return new SearchProviderError(
250
+ lastError?.code ?? "upstream_unavailable",
251
+ `search failed after ${attempts.length} attempt(s)`,
252
+ { retryable: lastError?.retryable, details: { attempts: trail }, cause: lastError }
253
+ );
254
+ }
255
+
256
+ // src/search/registry.ts
257
+ var SearchRegistryError = class _SearchRegistryError extends Error {
258
+ constructor(message) {
259
+ super(message);
260
+ this.name = "SearchRegistryError";
261
+ Object.setPrototypeOf(this, _SearchRegistryError.prototype);
262
+ }
263
+ };
264
+ var SearchProviderRegistry = class {
265
+ entries = /* @__PURE__ */ new Map();
266
+ allowBuiltinOverride;
267
+ nextSequence = 0;
268
+ constructor(options = {}) {
269
+ this.allowBuiltinOverride = options.allowBuiltinOverride === true;
270
+ }
271
+ /**
272
+ * Register one provider.
273
+ *
274
+ * @param contribution - the provider with its origin, transport, and
275
+ * capabilities declared.
276
+ * @param context - who is registering, for a Phase-2 host. Retained as-is;
277
+ * no rule reads it today, and the parameter exists so adding one later is
278
+ * not a signature change for every caller.
279
+ * @throws {SearchRegistryError} on a blank id, a duplicate id, or a host
280
+ * contribution colliding with a builtin without `allowBuiltinOverride`.
281
+ */
282
+ register(contribution, context) {
283
+ const id = contribution.id;
284
+ if (typeof id !== "string" || id.trim().length === 0) {
285
+ throw new SearchRegistryError("a search provider contribution needs a non-blank id");
286
+ }
287
+ const existing = this.entries.get(id);
288
+ let sequence = this.nextSequence;
289
+ if (existing !== void 0) {
290
+ const isPermittedOverride = this.allowBuiltinOverride && contribution.source === "host" && existing.contribution.source === "builtin";
291
+ if (!isPermittedOverride) {
292
+ throw new SearchRegistryError(
293
+ existing.contribution.source === "builtin" && contribution.source === "host" ? `search provider '${id}' is a builtin; overriding it requires allowBuiltinOverride` : `search provider '${id}' is already registered`
294
+ );
295
+ }
296
+ sequence = existing.sequence;
297
+ } else {
298
+ this.nextSequence += 1;
299
+ }
300
+ this.entries.set(id, { contribution, context, sequence });
301
+ }
302
+ /** Whether an id is currently registered. */
303
+ has(id) {
304
+ return this.entries.has(id);
305
+ }
306
+ /** The contribution registered under `id`, if any. */
307
+ get(id) {
308
+ return this.entries.get(id)?.contribution;
309
+ }
310
+ /**
311
+ * Remove a provider.
312
+ *
313
+ * The Phase-2 host-disconnect hook: when a host goes away its contributions
314
+ * go with it, and the id becomes free to register again.
315
+ *
316
+ * @returns whether something was removed.
317
+ */
318
+ unregister(id) {
319
+ return this.entries.delete(id);
320
+ }
321
+ /**
322
+ * Every registered contribution, in candidate order.
323
+ *
324
+ * `priorityHint` ascending; contributions without a hint come AFTER every
325
+ * hinted one (an absent hint is "no opinion", not "highest priority"); ties
326
+ * keep registration order. The builtin HTTP contributions carry no hints, so
327
+ * today this is exactly registration order.
328
+ */
329
+ list() {
330
+ return [...this.entries.values()].sort((a, b) => {
331
+ const aHint = a.contribution.priorityHint;
332
+ const bHint = b.contribution.priorityHint;
333
+ if (aHint !== bHint) {
334
+ if (aHint === void 0) return 1;
335
+ if (bHint === void 0) return -1;
336
+ return aHint - bHint;
337
+ }
338
+ return a.sequence - b.sequence;
339
+ }).map((entry) => entry.contribution);
340
+ }
341
+ };
342
+
343
+ // src/search/runtime.ts
344
+ function createSearchRuntime(options = {}) {
345
+ const registry = new SearchProviderRegistry({
346
+ allowBuiltinOverride: options.allowBuiltinOverride === true
347
+ });
348
+ for (const contribution of options.contributions ?? builtinHttpSearchContributions()) {
349
+ registry.register(contribution);
350
+ }
351
+ const orchestrator = new SearchOrchestrator(registry, {
352
+ ...options.policy === void 0 ? {} : { policy: options.policy },
353
+ ...options.onEvent === void 0 ? {} : { onEvent: options.onEvent }
354
+ });
355
+ return {
356
+ search(request) {
357
+ return orchestrator.search(request);
358
+ },
359
+ registerContribution(contribution, context) {
360
+ registry.register(contribution, context);
361
+ },
362
+ unregisterContribution(id) {
363
+ return registry.unregister(id);
364
+ },
365
+ listProviders() {
366
+ return registry.list().map((contribution) => ({
367
+ id: contribution.id,
368
+ source: contribution.source,
369
+ kind: contribution.kind,
370
+ capabilities: { ...contribution.capabilities }
371
+ }));
372
+ }
373
+ };
374
+ }
375
+
376
+ export {
377
+ DEFAULT_NORMALIZED_MAX_RESULTS,
378
+ isDirectResultUrl,
379
+ normalizedResultLimit,
380
+ normalizeSearchResults,
381
+ hashSearchQuery,
382
+ SearchOrchestrator,
383
+ SearchRegistryError,
384
+ SearchProviderRegistry,
385
+ createSearchRuntime
386
+ };