@omnicross/daemon 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.
package/dist/cli.cjs CHANGED
@@ -1160,7 +1160,10 @@ ${turn.responseBody}`);
1160
1160
 
1161
1161
  // src/commands/doctor.ts
1162
1162
  var import_node_util2 = require("util");
1163
- var import_outbound_api11 = require("@omnicross/core/outbound-api");
1163
+ var import_outbound_api12 = require("@omnicross/core/outbound-api");
1164
+ var import_api4 = require("@omnicross/core/search/api");
1165
+ var import_http4 = require("@omnicross/core/search/http");
1166
+ var import_search3 = require("@omnicross/core/search");
1164
1167
 
1165
1168
  // src/bootstrap.ts
1166
1169
  var import_node_fs35 = require("fs");
@@ -1170,17 +1173,17 @@ var import_billing_types = require("@omnicross/contracts/billing-types");
1170
1173
  var import_core4 = require("@omnicross/core");
1171
1174
  var import_GeminiCodeAssistProjectResolver = require("@omnicross/core/auth/GeminiCodeAssistProjectResolver");
1172
1175
  var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolService");
1173
- var import_outbound_api9 = require("@omnicross/core/outbound-api");
1176
+ var import_outbound_api10 = require("@omnicross/core/outbound-api");
1174
1177
  var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
1175
1178
  var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1176
1179
  var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
1177
1180
  var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1178
- var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
1181
+ var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
1179
1182
  var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
1180
1183
  var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
1181
1184
  var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
1182
1185
  var import_cli_launcher2 = require("@omnicross/cli-launcher");
1183
- var import_outbound_api10 = require("@omnicross/core/outbound-api");
1186
+ var import_outbound_api11 = require("@omnicross/core/outbound-api");
1184
1187
  var import_usage2 = require("@omnicross/core/usage");
1185
1188
  var import_subscriptions6 = require("@omnicross/subscriptions");
1186
1189
 
@@ -1977,11 +1980,11 @@ async function handleRouteLeaseApi(req, res, path2, deps) {
1977
1980
 
1978
1981
  // src/admin/adminApi.ts
1979
1982
  var import_node_http = __toESM(require("http"), 1);
1980
- var import_outbound_api4 = require("@omnicross/core/outbound-api");
1983
+ var import_outbound_api5 = require("@omnicross/core/outbound-api");
1981
1984
  var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
1982
1985
  var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
1983
1986
  var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
1984
- var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
1987
+ var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
1985
1988
 
1986
1989
  // src/image-generation/imagesConfigValidation.ts
1987
1990
  var import_outbound_api = require("@omnicross/core/outbound-api");
@@ -4502,6 +4505,502 @@ async function handleDashboard(deps) {
4502
4505
  return { status: 200, body: summary };
4503
4506
  }
4504
4507
 
4508
+ // src/admin/searchAdminApi.ts
4509
+ var import_outbound_api2 = require("@omnicross/core/outbound-api");
4510
+ var import_api3 = require("@omnicross/core/search/api");
4511
+ var import_http3 = require("@omnicross/core/search/http");
4512
+ var import_search2 = require("@omnicross/core/search");
4513
+
4514
+ // src/search/searchDoctorProjection.ts
4515
+ var import_search_types = require("@omnicross/contracts/search-types");
4516
+ var import_api = require("@omnicross/core/search/api");
4517
+ var import_http = require("@omnicross/core/search/http");
4518
+ var API_DOCTOR_PROVIDERS = [
4519
+ {
4520
+ id: "tavily",
4521
+ capabilities: import_api.TAVILY_CAPABILITIES,
4522
+ configured: (configs) => configs.tavily !== void 0,
4523
+ missingReason: "no API key configured"
4524
+ },
4525
+ {
4526
+ id: "jina",
4527
+ capabilities: import_api.JINA_CAPABILITIES,
4528
+ configured: (configs) => configs.jina !== void 0,
4529
+ // Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
4530
+ // asked for is still not enabled.
4531
+ missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
4532
+ },
4533
+ {
4534
+ id: "searxng",
4535
+ capabilities: import_api.SEARXNG_CAPABILITIES,
4536
+ configured: (configs) => configs.searxng !== void 0,
4537
+ missingReason: "no API host configured"
4538
+ },
4539
+ {
4540
+ id: "zhipu",
4541
+ capabilities: import_api.ZHIPU_CAPABILITIES,
4542
+ configured: (configs) => configs.zhipu !== void 0,
4543
+ missingReason: "no API key configured"
4544
+ },
4545
+ {
4546
+ id: "z.ai",
4547
+ capabilities: import_api.ZHIPU_CAPABILITIES,
4548
+ configured: (configs) => configs["z.ai"] !== void 0,
4549
+ missingReason: "no API key configured"
4550
+ }
4551
+ ];
4552
+ function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSearchContributions)(), apiConfigs) {
4553
+ const rows = contributions.map((contribution) => ({
4554
+ providerId: contribution.id,
4555
+ source: contribution.source,
4556
+ kind: contribution.kind,
4557
+ capabilities: contribution.capabilities
4558
+ }));
4559
+ if (apiConfigs === void 0) return rows;
4560
+ for (const provider of API_DOCTOR_PROVIDERS) {
4561
+ if (provider.configured(apiConfigs)) continue;
4562
+ rows.push({
4563
+ providerId: provider.id,
4564
+ source: "builtin",
4565
+ kind: "api",
4566
+ capabilities: provider.capabilities,
4567
+ status: "unconfigured",
4568
+ reason: provider.missingReason
4569
+ });
4570
+ }
4571
+ return rows;
4572
+ }
4573
+ var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
4574
+ function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
4575
+ if (outcome.kind === "results") {
4576
+ if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
4577
+ return {
4578
+ providerId,
4579
+ status: "degraded",
4580
+ checkedAt,
4581
+ reason: "reachable, but the engine returned no usable results (possible partial drift)"
4582
+ };
4583
+ }
4584
+ const error = (0, import_search_types.toSearchErrorShape)(outcome.error);
4585
+ const stage = error.details?.stage;
4586
+ const { status, reason } = classifySearchFailure(stage, error.code);
4587
+ return { providerId, status, checkedAt, reason, error };
4588
+ }
4589
+ function classifySearchFailure(stage, code) {
4590
+ if (stage === "challenge") {
4591
+ return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
4592
+ }
4593
+ if (stage === "trust") {
4594
+ return {
4595
+ status: "blocked",
4596
+ reason: "the engine served a page that failed the anti-decoy trust check"
4597
+ };
4598
+ }
4599
+ if (code === "policy_denied") {
4600
+ return {
4601
+ status: "blocked",
4602
+ reason: "the egress policy refused the request target"
4603
+ };
4604
+ }
4605
+ if (code === "parse_failed") {
4606
+ return {
4607
+ status: "failed",
4608
+ reason: "the response was not recognizable as a search result page (parser drift suspected)"
4609
+ };
4610
+ }
4611
+ if (code === "timeout") {
4612
+ return { status: "failed", reason: "the request exceeded its time budget" };
4613
+ }
4614
+ return { status: "failed", reason: `the request failed (${code})` };
4615
+ }
4616
+ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
4617
+ const diagnostics = [];
4618
+ for (const contribution of contributions) {
4619
+ try {
4620
+ const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
4621
+ diagnostics.push(
4622
+ classifyLiveSearchOutcome(contribution.id, { kind: "results", count: results.length }, now())
4623
+ );
4624
+ } catch (error) {
4625
+ diagnostics.push(classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, now()));
4626
+ }
4627
+ }
4628
+ return diagnostics;
4629
+ }
4630
+
4631
+ // src/search/SearchAssembly.ts
4632
+ var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
4633
+ var import_search = require("@omnicross/core/search");
4634
+ var import_api2 = require("@omnicross/core/search/api");
4635
+ var import_http2 = require("@omnicross/core/search/http");
4636
+ function searchEgressPolicyFrom(config) {
4637
+ const hosts = config.egress.allowedPrivateHosts;
4638
+ return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
4639
+ }
4640
+ function searchPolicyFrom(config) {
4641
+ const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
4642
+ return {
4643
+ ...preferred !== void 0 ? { preferred } : {},
4644
+ ...allowed !== void 0 ? { allowed: [...allowed] } : {},
4645
+ fallbackEnabled,
4646
+ ...maxAttempts !== void 0 ? { maxAttempts } : {}
4647
+ };
4648
+ }
4649
+ function resolveSearchUpstreamDispatcher(url) {
4650
+ return (0, import_upstreamFetch3.resolveUpstreamDispatcher)({ url });
4651
+ }
4652
+ var searchUpstreamProxyConfig = createUpstreamProxyResolver();
4653
+ function resolveSearchUpstreamProxyConfig(url) {
4654
+ return searchUpstreamProxyConfig({ url });
4655
+ }
4656
+ function searchContributionsFrom(config) {
4657
+ return [
4658
+ ...(0, import_http2.builtinHttpSearchContributions)(
4659
+ (0, import_http2.createSearchHttpTransport)({
4660
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
4661
+ resolveProxyConfig: resolveSearchUpstreamProxyConfig
4662
+ })
4663
+ ),
4664
+ ...(0, import_api2.apiSearchContributions)(config.providers, {
4665
+ egressPolicy: searchEgressPolicyFrom(config),
4666
+ resolveProxyDispatcher: resolveSearchUpstreamDispatcher
4667
+ })
4668
+ ];
4669
+ }
4670
+ function buildSearchRuntime(config, options = {}) {
4671
+ const logger = options.logger ?? null;
4672
+ return (0, import_search.createSearchRuntime)({
4673
+ contributions: options.contributions ?? searchContributionsFrom(config),
4674
+ policy: searchPolicyFrom(config),
4675
+ ...logger ? {
4676
+ onEvent: (event) => {
4677
+ logger.debug(`[search] ${formatSearchEvent(event)}`);
4678
+ }
4679
+ } : {}
4680
+ });
4681
+ }
4682
+ function formatSearchEvent(event) {
4683
+ const parts = [
4684
+ `type=${event.type}`,
4685
+ `request=${event.requestId}`,
4686
+ `queryHash=${event.queryHash}`,
4687
+ `durationMs=${event.durationMs}`
4688
+ ];
4689
+ if ("providerId" in event && event.providerId !== void 0) {
4690
+ parts.push(`provider=${event.providerId}`);
4691
+ }
4692
+ if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
4693
+ if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
4694
+ if ("resultCount" in event && event.resultCount !== void 0) {
4695
+ parts.push(`results=${event.resultCount}`);
4696
+ }
4697
+ if ("fallbackCount" in event && event.fallbackCount !== void 0) {
4698
+ parts.push(`fallbacks=${event.fallbackCount}`);
4699
+ }
4700
+ return parts.join(" ");
4701
+ }
4702
+
4703
+ // src/admin/searchAdminApi.ts
4704
+ var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
4705
+ var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
4706
+ "tavily",
4707
+ "jina",
4708
+ "searxng",
4709
+ "zhipu",
4710
+ "z.ai"
4711
+ ]);
4712
+ var SEARCH_QUERY_MAX_CODE_UNITS = 256;
4713
+ var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
4714
+ var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
4715
+ var SEARCH_QUERY_MAX_RESULTS = 5;
4716
+ function sanitizeResultField(value, cap) {
4717
+ const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
4718
+ return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
4719
+ }
4720
+ function writeJson(res, status, body) {
4721
+ res.writeHead(status, { "Content-Type": "application/json" });
4722
+ res.end(JSON.stringify(body));
4723
+ }
4724
+ function writeErr(res, status, message) {
4725
+ writeJson(res, status, { error: { type: "admin_api_error", message } });
4726
+ }
4727
+ var SEARCH_MAX_BODY_BYTES = 64 * 1024;
4728
+ var SearchBodyTooLargeError = class extends Error {
4729
+ };
4730
+ async function readJsonBody2(req) {
4731
+ const chunks = [];
4732
+ let bytes = 0;
4733
+ for await (const chunk of req) {
4734
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4735
+ bytes += buffer.length;
4736
+ if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
4737
+ chunks.push(buffer);
4738
+ }
4739
+ const raw = Buffer.concat(chunks).toString("utf8");
4740
+ if (!raw.trim()) return {};
4741
+ try {
4742
+ const parsed = JSON.parse(raw);
4743
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4744
+ } catch {
4745
+ return {};
4746
+ }
4747
+ }
4748
+ async function readBodyOrReject(req, res) {
4749
+ try {
4750
+ return await readJsonBody2(req);
4751
+ } catch (error) {
4752
+ if (error instanceof SearchBodyTooLargeError) {
4753
+ writeErr(res, 400, error.message);
4754
+ return void 0;
4755
+ }
4756
+ throw error;
4757
+ }
4758
+ }
4759
+ async function handleSearchAdmin(req, res, method, rest, deps) {
4760
+ if (rest.length === 1 && rest[0] === "diagnostics") {
4761
+ if (!deps.searchStatus) {
4762
+ return writeErr(res, 501, "Search status is not available in this build");
4763
+ }
4764
+ if (method !== "GET") {
4765
+ return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
4766
+ }
4767
+ return handleSearchDiagnostics(res, deps);
4768
+ }
4769
+ if (rest.length === 1 && rest[0] === "test") {
4770
+ if (!deps.searchStatus) {
4771
+ return writeErr(res, 501, "Search status is not available in this build");
4772
+ }
4773
+ if (method !== "POST") {
4774
+ return writeErr(res, 405, `method ${method} not allowed on search test`);
4775
+ }
4776
+ return handleSearchTest(req, res, deps);
4777
+ }
4778
+ if (rest.length === 1 && rest[0] === "query") {
4779
+ if (!deps.searchStatus) {
4780
+ return writeErr(res, 501, "Search status is not available in this build");
4781
+ }
4782
+ if (method !== "POST") {
4783
+ return writeErr(res, 405, `method ${method} not allowed on search query`);
4784
+ }
4785
+ return handleSearchQuery(req, res, deps);
4786
+ }
4787
+ return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
4788
+ }
4789
+ async function handleSearchDiagnostics(res, deps) {
4790
+ const status = deps.searchStatus;
4791
+ const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
4792
+ const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
4793
+ const rows = buildSearchDoctorSnapshot(
4794
+ status.runtime.listProviders(),
4795
+ search.providers
4796
+ );
4797
+ const snapshot = {
4798
+ rows,
4799
+ modes: {
4800
+ // codex is read from the LIVE config per request — an admin PUT has
4801
+ // already applied. responses/anthropic were captured at bootstrap.
4802
+ codex: search.modes.codex,
4803
+ responses: status.modes.responses,
4804
+ anthropic: status.modes.anthropic
4805
+ },
4806
+ applySemantics: { codex: "immediate", rest: "restart" }
4807
+ };
4808
+ return writeJson(res, 200, { diagnostics: snapshot });
4809
+ }
4810
+ function persistedSearchContributions(search, fetchImpl) {
4811
+ if (fetchImpl) {
4812
+ const egressPolicy = searchEgressPolicyFrom(search);
4813
+ return [
4814
+ ...(0, import_http3.builtinHttpSearchContributions)(
4815
+ (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy })
4816
+ ),
4817
+ ...(0, import_api3.apiSearchContributions)(search.providers, { egressPolicy, fetchImpl })
4818
+ ];
4819
+ }
4820
+ return searchContributionsFrom(search);
4821
+ }
4822
+ async function handleSearchTest(req, res, deps) {
4823
+ const status = deps.searchStatus;
4824
+ const body = await readBodyOrReject(req, res);
4825
+ if (body === void 0) return;
4826
+ const providerId = body["providerId"];
4827
+ if (typeof providerId !== "string" || providerId.length === 0) {
4828
+ return writeErr(res, 400, "providerId must be a non-empty string");
4829
+ }
4830
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
4831
+ return writeErr(res, 404, `unknown search provider '${providerId}'`);
4832
+ }
4833
+ const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
4834
+ const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
4835
+ const providers = search.providers;
4836
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4837
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4838
+ }
4839
+ const fetchImpl = status.testFetch;
4840
+ const contributions = persistedSearchContributions(search, fetchImpl);
4841
+ const contribution = contributions.find((c) => c.id === providerId);
4842
+ if (!contribution) {
4843
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4844
+ }
4845
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4846
+ try {
4847
+ const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
4848
+ const diagnostic = classifyLiveSearchOutcome(
4849
+ contribution.id,
4850
+ { kind: "results", count: results.length },
4851
+ checkedAt
4852
+ );
4853
+ const response = { diagnostic, resultCount: results.length };
4854
+ return writeJson(res, 200, { result: response });
4855
+ } catch (error) {
4856
+ const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
4857
+ const response = { diagnostic };
4858
+ return writeJson(res, 200, { result: response });
4859
+ }
4860
+ }
4861
+ async function handleSearchQuery(req, res, deps) {
4862
+ const status = deps.searchStatus;
4863
+ const body = await readBodyOrReject(req, res);
4864
+ if (body === void 0) return;
4865
+ const providerId = body["providerId"];
4866
+ if (typeof providerId !== "string" || providerId.length === 0) {
4867
+ return writeErr(res, 400, "providerId must be a non-empty string");
4868
+ }
4869
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
4870
+ return writeErr(res, 404, `unknown search provider '${providerId}'`);
4871
+ }
4872
+ const query2 = body["query"];
4873
+ if (typeof query2 !== "string" || query2.trim().length === 0) {
4874
+ return writeErr(res, 400, "query must be a non-empty string");
4875
+ }
4876
+ if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
4877
+ return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
4878
+ }
4879
+ if (QUERY_CONTROL_CHARS.test(query2)) {
4880
+ return writeErr(res, 400, "query must not contain control characters");
4881
+ }
4882
+ const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
4883
+ const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
4884
+ const providers = search.providers;
4885
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4886
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4887
+ }
4888
+ const fetchImpl = status.testFetch;
4889
+ const runtime = (0, import_search2.createSearchRuntime)({
4890
+ contributions: persistedSearchContributions(search, fetchImpl),
4891
+ policy: {
4892
+ ...searchPolicyFrom(search),
4893
+ // The panel always walks: it answers "does a search WORK for this
4894
+ // operator", not "does this one provider behave" — that is `/test`'s
4895
+ // job. The persisted policy's allowlist still bounds the walk.
4896
+ fallbackEnabled: true,
4897
+ preferred: providerId
4898
+ }
4899
+ });
4900
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4901
+ try {
4902
+ const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
4903
+ const results = orchestrated.results;
4904
+ const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4905
+ title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4906
+ url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4907
+ content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4908
+ }));
4909
+ const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4910
+ orchestrated.providerId,
4911
+ { kind: "results", count: sanitized.length },
4912
+ checkedAt
4913
+ );
4914
+ const response = {
4915
+ diagnostic,
4916
+ providerUsed: orchestrated.providerId,
4917
+ fallbackCount: orchestrated.fallbackCount,
4918
+ resultCount: sanitized.length,
4919
+ results: sanitized
4920
+ };
4921
+ return writeJson(res, 200, { result: response });
4922
+ } catch (error) {
4923
+ const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
4924
+ const response = { diagnostic };
4925
+ return writeJson(res, 200, { result: response });
4926
+ }
4927
+ }
4928
+
4929
+ // src/admin/searchAdminView.ts
4930
+ var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4931
+ var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4932
+ function isRecord(value) {
4933
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4934
+ }
4935
+ function redactSearchServerConfig(search) {
4936
+ const providers = {};
4937
+ for (const [id, raw] of Object.entries(search.providers)) {
4938
+ const entry = raw;
4939
+ const view = {};
4940
+ if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
4941
+ if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
4942
+ if (API_KEY_PROVIDERS.has(id)) {
4943
+ view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
4944
+ }
4945
+ if (BASIC_AUTH_PROVIDERS.has(id)) {
4946
+ view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
4947
+ }
4948
+ providers[id] = view;
4949
+ }
4950
+ return {
4951
+ modes: search.modes,
4952
+ providers,
4953
+ egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
4954
+ policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
4955
+ };
4956
+ }
4957
+ function storedSecrets(current, id) {
4958
+ const entry = current.providers[id];
4959
+ if (!entry) return {};
4960
+ const out = {};
4961
+ if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
4962
+ if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
4963
+ out.basicAuthPassword = entry.basicAuthPassword;
4964
+ }
4965
+ return out;
4966
+ }
4967
+ function resolveSecretField(entry, field, stored) {
4968
+ if (!(field in entry)) {
4969
+ if (stored !== void 0) entry[field] = stored;
4970
+ return;
4971
+ }
4972
+ const value = entry[field];
4973
+ if (value === null) {
4974
+ delete entry[field];
4975
+ return;
4976
+ }
4977
+ if (typeof value === "string" && value.trim().length > 0) return;
4978
+ if (stored !== void 0) entry[field] = stored;
4979
+ else delete entry[field];
4980
+ }
4981
+ function preserveSearchSecrets(incoming, current) {
4982
+ if (!isRecord(incoming)) return incoming;
4983
+ const section = { ...incoming };
4984
+ const providersValue = section["providers"];
4985
+ if (!isRecord(providersValue)) return section;
4986
+ const providers = {};
4987
+ for (const [id, entryValue] of Object.entries(providersValue)) {
4988
+ if (!isRecord(entryValue)) {
4989
+ providers[id] = entryValue;
4990
+ continue;
4991
+ }
4992
+ const entry = { ...entryValue };
4993
+ delete entry["apiKeyConfigured"];
4994
+ delete entry["basicAuthPasswordConfigured"];
4995
+ const stored = storedSecrets(current, id);
4996
+ resolveSecretField(entry, "apiKey", stored.apiKey);
4997
+ resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
4998
+ providers[id] = entry;
4999
+ }
5000
+ section["providers"] = providers;
5001
+ return section;
5002
+ }
5003
+
4505
5004
  // src/admin/keyPolicyBody.ts
4506
5005
  function parseKeyPolicyBody(body) {
4507
5006
  const policy = {};
@@ -4564,7 +5063,7 @@ function parseKeyPolicyBody(body) {
4564
5063
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
4565
5064
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
4566
5065
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
4567
- function isRecord(value) {
5066
+ function isRecord2(value) {
4568
5067
  return !!value && typeof value === "object" && !Array.isArray(value);
4569
5068
  }
4570
5069
  function nonBlank(value) {
@@ -4584,7 +5083,7 @@ function validateGatewayBindingsSegment(patch) {
4584
5083
  const ids = /* @__PURE__ */ new Set();
4585
5084
  raw.forEach((entry, index) => {
4586
5085
  const path2 = `bindings[${index}]`;
4587
- if (!isRecord(entry)) {
5086
+ if (!isRecord2(entry)) {
4588
5087
  errors.push(`${path2} must be an object`);
4589
5088
  return;
4590
5089
  }
@@ -4613,12 +5112,12 @@ function validateGatewayBindingsSegment(patch) {
4613
5112
  } else if (entry.modelMappings.length > 100) {
4614
5113
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
4615
5114
  } else if (entry.modelMappings.some(
4616
- (mapping) => !isRecord(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
5115
+ (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4617
5116
  )) {
4618
5117
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
4619
5118
  }
4620
5119
  }
4621
- if (!isRecord(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
5120
+ if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4622
5121
  errors.push(`${path2}.target is invalid`);
4623
5122
  } else {
4624
5123
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -4633,7 +5132,7 @@ function validateGatewayBindingsSegment(patch) {
4633
5132
  }
4634
5133
  }
4635
5134
  if (entry.modelMap !== void 0) {
4636
- if (!isRecord(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
5135
+ if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4637
5136
  errors.push(`${path2}.modelMap must contain string values`);
4638
5137
  }
4639
5138
  }
@@ -4651,15 +5150,15 @@ function validateGatewayBindingsSegment(patch) {
4651
5150
  }
4652
5151
 
4653
5152
  // src/admin/voucherAdmin.ts
4654
- var import_outbound_api2 = require("@omnicross/core/outbound-api");
4655
- function writeJson(res, status, body) {
5153
+ var import_outbound_api3 = require("@omnicross/core/outbound-api");
5154
+ function writeJson2(res, status, body) {
4656
5155
  res.writeHead(status, { "Content-Type": "application/json" });
4657
5156
  res.end(JSON.stringify(body));
4658
5157
  }
4659
- function writeErr(res, status, message) {
4660
- writeJson(res, status, { error: { type: "voucher_error", message } });
5158
+ function writeErr2(res, status, message) {
5159
+ writeJson2(res, status, { error: { type: "voucher_error", message } });
4661
5160
  }
4662
- function readJsonBody2(req) {
5161
+ function readJsonBody3(req) {
4663
5162
  return new Promise((resolve11, reject) => {
4664
5163
  const chunks = [];
4665
5164
  req.on("data", (c) => chunks.push(c));
@@ -4710,34 +5209,34 @@ function parseVoucherCreateBody(body) {
4710
5209
  return { ok: true, input };
4711
5210
  }
4712
5211
  async function voucherEnabled(deps) {
4713
- const config = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
5212
+ const config = await (0, import_outbound_api3.loadServerConfig)(deps.settingsStore);
4714
5213
  return config.voucher?.enabled === true;
4715
5214
  }
4716
5215
  async function handleVoucher(req, res, method, rest, deps) {
4717
5216
  const voucherDb = deps.voucherDb;
4718
- if (!voucherDb) return writeErr(res, 501, "Voucher feature is not available");
5217
+ if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
4719
5218
  if (method === "GET" && rest.length === 0) {
4720
5219
  const rows = await voucherDb.voucherList();
4721
- return writeJson(res, 200, { vouchers: rows.map(import_outbound_api2.toVoucherInfo) });
5220
+ return writeJson2(res, 200, { vouchers: rows.map(import_outbound_api3.toVoucherInfo) });
4722
5221
  }
4723
5222
  if (method === "POST" && rest.length === 0) {
4724
- if (!await voucherEnabled(deps)) return writeErr(res, 403, "Voucher feature is disabled");
5223
+ if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
4725
5224
  let body;
4726
5225
  try {
4727
- body = await readJsonBody2(req);
5226
+ body = await readJsonBody3(req);
4728
5227
  } catch {
4729
- return writeErr(res, 400, "Invalid JSON in request body");
5228
+ return writeErr2(res, 400, "Invalid JSON in request body");
4730
5229
  }
4731
5230
  const parsed = parseVoucherCreateBody(body);
4732
- if (!parsed.ok) return writeErr(res, 400, parsed.message);
4733
- const code = (0, import_outbound_api2.generateVoucherCode)();
5231
+ if (!parsed.ok) return writeErr2(res, 400, parsed.message);
5232
+ const code = (0, import_outbound_api3.generateVoucherCode)();
4734
5233
  const created = await voucherDb.voucherCreate({
4735
- id: (0, import_outbound_api2.newVoucherId)(),
4736
- codeHash: (0, import_outbound_api2.hashVoucherCode)(code),
4737
- codePrefix: (0, import_outbound_api2.voucherCodePrefix)(code),
5234
+ id: (0, import_outbound_api3.newVoucherId)(),
5235
+ codeHash: (0, import_outbound_api3.hashVoucherCode)(code),
5236
+ codePrefix: (0, import_outbound_api3.voucherCodePrefix)(code),
4738
5237
  ...parsed.input
4739
5238
  });
4740
- return writeJson(res, 201, {
5239
+ return writeJson2(res, 201, {
4741
5240
  id: created.id,
4742
5241
  codePrefix: created.codePrefix,
4743
5242
  type: created.type,
@@ -4748,11 +5247,11 @@ async function handleVoucher(req, res, method, rest, deps) {
4748
5247
  }
4749
5248
  const id = rest[0];
4750
5249
  if (method === "POST" && id && rest[1] === "revoke") {
4751
- if (!await voucherEnabled(deps)) return writeErr(res, 403, "Voucher feature is disabled");
5250
+ if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
4752
5251
  const ok = await voucherDb.voucherRevokeCas(id, Date.now());
4753
- return writeJson(res, ok ? 200 : 409, { ok });
5252
+ return writeJson2(res, ok ? 200 : 409, { ok });
4754
5253
  }
4755
- return writeErr(res, 405, `method ${method} not allowed on voucher`);
5254
+ return writeErr2(res, 405, `method ${method} not allowed on voucher`);
4756
5255
  }
4757
5256
 
4758
5257
  // src/admin/webhookConfigBody.ts
@@ -4903,7 +5402,7 @@ function applyBillingConfig(config) {
4903
5402
  }
4904
5403
 
4905
5404
  // src/migration/migration.ts
4906
- var import_outbound_api3 = require("@omnicross/core/outbound-api");
5405
+ var import_outbound_api4 = require("@omnicross/core/outbound-api");
4907
5406
 
4908
5407
  // src/ports/account-multi.ts
4909
5408
  var import_node_crypto8 = require("crypto");
@@ -5331,7 +5830,7 @@ async function gatherExport(deps, passphrase) {
5331
5830
  v: BUNDLE_VERSION,
5332
5831
  providers: cfg.providers,
5333
5832
  tokens,
5334
- server: (0, import_outbound_api3.normalizeServerConfig)(cfg.server)
5833
+ server: (0, import_outbound_api4.normalizeServerConfig)(cfg.server)
5335
5834
  };
5336
5835
  return sealPack(JSON.stringify(bundle), passphrase);
5337
5836
  }
@@ -5380,7 +5879,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
5380
5879
  const imageErrors = validateImagesAdminConfig(rawServer["images"]);
5381
5880
  if (imageErrors.length > 0) throw new Error("migration pack has an invalid Images config");
5382
5881
  }
5383
- validatedServer = (0, import_outbound_api3.normalizeServerConfig)(bundle.server);
5882
+ validatedServer = (0, import_outbound_api4.normalizeServerConfig)(bundle.server);
5384
5883
  }
5385
5884
  const validatedProviders = [];
5386
5885
  for (const raw of rawProviders) {
@@ -5661,12 +6160,12 @@ async function handlePricingResolveConflicts(body, deps) {
5661
6160
  }
5662
6161
 
5663
6162
  // src/admin/accountAllowanceApi.ts
5664
- function writeJson2(res, status, body) {
6163
+ function writeJson3(res, status, body) {
5665
6164
  res.writeHead(status, { "Content-Type": "application/json" });
5666
6165
  res.end(JSON.stringify(body));
5667
6166
  }
5668
6167
  function writeError2(res, status, message) {
5669
- writeJson2(res, status, { error: { type: "account_allowance_error", message } });
6168
+ writeJson3(res, status, { error: { type: "account_allowance_error", message } });
5670
6169
  }
5671
6170
  function readJson2(req) {
5672
6171
  return new Promise((resolve11, reject) => {
@@ -5699,7 +6198,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5699
6198
  if (!service.getSchedulingStatus) {
5700
6199
  return writeError2(res, 501, "allowance scheduling diagnostics are not available");
5701
6200
  }
5702
- return writeJson2(res, 200, { scheduling: service.getSchedulingStatus() });
6201
+ return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
5703
6202
  }
5704
6203
  if (method === "GET") {
5705
6204
  const params = query(req);
@@ -5708,7 +6207,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5708
6207
  if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
5709
6208
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5710
6209
  const allowances = await service.list({ providerId, accountId });
5711
- return writeJson2(res, 200, { allowances });
6210
+ return writeJson3(res, 200, { allowances });
5712
6211
  }
5713
6212
  if (method === "POST" && rest[0] === "refresh") {
5714
6213
  const body = await readJson2(req);
@@ -5723,7 +6222,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5723
6222
  if (accountId && allowances.length === 0) {
5724
6223
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
5725
6224
  }
5726
- return writeJson2(res, 200, { allowances });
6225
+ return writeJson3(res, 200, { allowances });
5727
6226
  }
5728
6227
  return writeError2(res, 405, `method ${method} not allowed on account allowances`);
5729
6228
  }
@@ -5739,7 +6238,7 @@ function readBody(req) {
5739
6238
  req.on("error", reject);
5740
6239
  });
5741
6240
  }
5742
- async function readJsonBody3(req) {
6241
+ async function readJsonBody4(req) {
5743
6242
  const raw = await readBody(req);
5744
6243
  if (!raw.trim()) return {};
5745
6244
  try {
@@ -5749,12 +6248,12 @@ async function readJsonBody3(req) {
5749
6248
  return {};
5750
6249
  }
5751
6250
  }
5752
- function writeJson3(res, status, body) {
6251
+ function writeJson4(res, status, body) {
5753
6252
  res.writeHead(status, { "Content-Type": "application/json" });
5754
6253
  res.end(JSON.stringify(body));
5755
6254
  }
5756
6255
  function writeJsonError(res, status, message) {
5757
- writeJson3(res, status, { error: { type: "admin_api_error", message } });
6256
+ writeJson4(res, status, { error: { type: "admin_api_error", message } });
5758
6257
  }
5759
6258
  function maskProviderApiKey(apiKey) {
5760
6259
  if (!apiKey) return "";
@@ -5775,7 +6274,7 @@ function toKeyInfo(row) {
5775
6274
  lastUsedAt: row.lastUsedAt,
5776
6275
  revoked: row.revokedAt !== null,
5777
6276
  kind: row.kind,
5778
- allowedEndpoints: [...(0, import_outbound_api4.effectiveOutboundPermissions)(row.allowedEndpoints)],
6277
+ allowedEndpoints: [...(0, import_outbound_api5.effectiveOutboundPermissions)(row.allowedEndpoints)],
5779
6278
  legacyPermissions: row.allowedEndpoints === void 0,
5780
6279
  loopbackOnly: row.loopbackOnly,
5781
6280
  maxConcurrency: row.maxConcurrency,
@@ -5861,6 +6360,8 @@ async function handleAdminApi(req, res, path2, deps) {
5861
6360
  return await handleServer(req, res, method, deps);
5862
6361
  case "images":
5863
6362
  return await handleImages(res, method, rest, deps);
6363
+ case "search":
6364
+ return await handleSearchAdmin(req, res, method, rest, deps);
5864
6365
  case "accounts":
5865
6366
  return await handleAccounts(req, res, method, rest, deps);
5866
6367
  case "cli":
@@ -5894,7 +6395,7 @@ function requestQuery(req) {
5894
6395
  return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
5895
6396
  }
5896
6397
  function writeResult(res, result) {
5897
- writeJson3(res, result.status, result.body);
6398
+ writeJson4(res, result.status, result.body);
5898
6399
  }
5899
6400
  async function handleUsage(req, res, method, rest, deps) {
5900
6401
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
@@ -5903,13 +6404,13 @@ async function handleUsage(req, res, method, rest, deps) {
5903
6404
  async function handleDashboardRoute(res, method, deps) {
5904
6405
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
5905
6406
  const result = await handleDashboard(deps);
5906
- return writeJson3(res, result.status, result.body);
6407
+ return writeJson4(res, result.status, result.body);
5907
6408
  }
5908
6409
  async function handlePricing(req, res, method, rest, deps) {
5909
6410
  if (rest.length === 0) {
5910
6411
  if (method === "GET") return writeResult(res, await handlePricingList(deps));
5911
6412
  if (method === "PUT") {
5912
- return writeResult(res, await handlePricingUpsert(await readJsonBody3(req), deps));
6413
+ return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
5913
6414
  }
5914
6415
  if (method === "DELETE") {
5915
6416
  return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
@@ -5920,7 +6421,7 @@ async function handlePricing(req, res, method, rest, deps) {
5920
6421
  return writeResult(res, await handlePricingFetchLatest(deps));
5921
6422
  }
5922
6423
  if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
5923
- return writeResult(res, await handlePricingResolveConflicts(await readJsonBody3(req), deps));
6424
+ return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
5924
6425
  }
5925
6426
  return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
5926
6427
  }
@@ -5934,15 +6435,15 @@ function migrationDeps(deps) {
5934
6435
  }
5935
6436
  async function handleMigrationExport(req, res, method, deps) {
5936
6437
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
5937
- const body = await readJsonBody3(req);
6438
+ const body = await readJsonBody4(req);
5938
6439
  const result = await handleExport(body, migrationDeps(deps));
5939
- return writeJson3(res, result.status, result.body);
6440
+ return writeJson4(res, result.status, result.body);
5940
6441
  }
5941
6442
  async function handleMigrationImport(req, res, method, deps) {
5942
6443
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
5943
- const body = await readJsonBody3(req);
6444
+ const body = await readJsonBody4(req);
5944
6445
  const result = await handleImport(body, migrationDeps(deps));
5945
- return writeJson3(res, result.status, result.body);
6446
+ return writeJson4(res, result.status, result.body);
5946
6447
  }
5947
6448
  async function handleProviders(req, res, method, rest, deps) {
5948
6449
  const cfg = loadConfig(deps.configPath);
@@ -5973,13 +6474,13 @@ async function handleProviders(req, res, method, rest, deps) {
5973
6474
  if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
5974
6475
  const row = cfg.providers.find((p) => p.id === rest[0]);
5975
6476
  if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
5976
- return writeJson3(res, 200, { apiKey: row.apiKey ?? "" });
6477
+ return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
5977
6478
  }
5978
6479
  if (method === "GET") {
5979
- return writeJson3(res, 200, { providers: cfg.providers.map(toProviderView) });
6480
+ return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
5980
6481
  }
5981
6482
  if (method === "POST") {
5982
- const body = await readJsonBody3(req);
6483
+ const body = await readJsonBody4(req);
5983
6484
  const provider = parseProviderInput(body, void 0);
5984
6485
  if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
5985
6486
  if (cfg.providers.some((p) => p.id === provider.id)) {
@@ -5987,25 +6488,25 @@ async function handleProviders(req, res, method, rest, deps) {
5987
6488
  }
5988
6489
  cfg.providers.push(provider);
5989
6490
  persistProviders(cfg, deps);
5990
- return writeJson3(res, 201, { provider: toProviderView(provider) });
6491
+ return writeJson4(res, 201, { provider: toProviderView(provider) });
5991
6492
  }
5992
6493
  const id = rest[0];
5993
6494
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5994
6495
  const idx = cfg.providers.findIndex((p) => p.id === id);
5995
6496
  if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
5996
6497
  if (method === "PUT") {
5997
- const body = await readJsonBody3(req);
6498
+ const body = await readJsonBody4(req);
5998
6499
  const existing = cfg.providers[idx];
5999
6500
  const updated = parseProviderInput(body, existing);
6000
6501
  if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
6001
6502
  cfg.providers[idx] = updated;
6002
6503
  persistProviders(cfg, deps);
6003
- return writeJson3(res, 200, { provider: toProviderView(updated) });
6504
+ return writeJson4(res, 200, { provider: toProviderView(updated) });
6004
6505
  }
6005
6506
  if (method === "DELETE") {
6006
6507
  cfg.providers.splice(idx, 1);
6007
6508
  persistProviders(cfg, deps);
6008
- return writeJson3(res, 200, { ok: true });
6509
+ return writeJson4(res, 200, { ok: true });
6009
6510
  }
6010
6511
  return writeJsonError(res, 405, `method ${method} not allowed on providers`);
6011
6512
  }
@@ -6014,7 +6515,7 @@ function persistProviders(cfg, deps) {
6014
6515
  deps.llmConfig.reload(cfg);
6015
6516
  }
6016
6517
  async function handleProviderReorder(req, res, cfg, deps) {
6017
- const body = await readJsonBody3(req);
6518
+ const body = await readJsonBody4(req);
6018
6519
  const rawOrder = body["order"];
6019
6520
  if (!Array.isArray(rawOrder)) {
6020
6521
  return writeJsonError(res, 400, "reorder requires { order: string[] }");
@@ -6038,14 +6539,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
6038
6539
  }
6039
6540
  cfg.providers = reordered;
6040
6541
  persistProviders(cfg, deps);
6041
- return writeJson3(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
6542
+ return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
6042
6543
  }
6043
6544
  async function handleDiscoverModels(res, id, cfg) {
6044
6545
  if (!id) return writeJsonError(res, 400, "provider id required in path");
6045
6546
  const row = cfg.providers.find((p) => p.id === id);
6046
6547
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6047
6548
  if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
6048
- return writeJson3(res, 200, { models: [], unsupportedFormat: true });
6549
+ return writeJson4(res, 200, { models: [], unsupportedFormat: true });
6049
6550
  }
6050
6551
  const resolvedKey = resolveEnvKey(row.apiKey);
6051
6552
  const base = row.baseUrl.replace(/\/+$/, "");
@@ -6053,7 +6554,7 @@ async function handleDiscoverModels(res, id, cfg) {
6053
6554
  try {
6054
6555
  const headers = { Accept: "application/json" };
6055
6556
  if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
6056
- const response = await (0, import_upstreamFetch3.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6557
+ const response = await (0, import_upstreamFetch4.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
6057
6558
  if (!response.ok) {
6058
6559
  const text = await response.text().catch(() => "");
6059
6560
  let message = text.slice(0, 300);
@@ -6062,32 +6563,32 @@ async function handleDiscoverModels(res, id, cfg) {
6062
6563
  message = parsed?.error?.message || parsed?.message || message;
6063
6564
  } catch {
6064
6565
  }
6065
- return writeJson3(res, 200, {
6566
+ return writeJson4(res, 200, {
6066
6567
  models: [],
6067
6568
  error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
6068
6569
  });
6069
6570
  }
6070
6571
  const data = await response.json();
6071
6572
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
6072
- return writeJson3(res, 200, { models });
6573
+ return writeJson4(res, 200, { models });
6073
6574
  } catch (err5) {
6074
6575
  const message = err5 instanceof Error ? err5.message : String(err5);
6075
- return writeJson3(res, 200, { models: [], error: `discovery failed: ${message}` });
6576
+ return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
6076
6577
  }
6077
6578
  }
6078
6579
  async function handleTestModel(req, res, id, cfg) {
6079
6580
  if (!id) return writeJsonError(res, 400, "provider id required in path");
6080
6581
  const row = cfg.providers.find((p) => p.id === id);
6081
6582
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6082
- const body = await readJsonBody3(req);
6583
+ const body = await readJsonBody4(req);
6083
6584
  const model = typeof body["model"] === "string" ? body["model"].trim() : "";
6084
6585
  if (!model) return writeJsonError(res, 400, "test requires a { model } string");
6085
6586
  if (row.apiFormat === "gemini") {
6086
- return writeJson3(res, 200, { ok: false, unsupportedFormat: true });
6587
+ return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
6087
6588
  }
6088
6589
  const resolvedKey = resolveEnvKey(row.apiKey);
6089
6590
  if (!resolvedKey) {
6090
- return writeJson3(res, 200, { ok: false, message: "no API key configured for this provider" });
6591
+ return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
6091
6592
  }
6092
6593
  let url = row.baseUrl.replace(/\/+$/, "");
6093
6594
  const prompt = "Reply with the single word: OK.";
@@ -6112,7 +6613,7 @@ async function handleTestModel(req, res, id, cfg) {
6112
6613
  }
6113
6614
  const startedAt = Date.now();
6114
6615
  try {
6115
- const response = await (0, import_upstreamFetch3.fetchUpstream)(
6616
+ const response = await (0, import_upstreamFetch4.fetchUpstream)(
6116
6617
  url,
6117
6618
  { method: "POST", headers, body: JSON.stringify(payload) },
6118
6619
  { providerId: "byo" }
@@ -6126,9 +6627,9 @@ async function handleTestModel(req, res, id, cfg) {
6126
6627
  message = parsed?.error?.message || parsed?.message || message;
6127
6628
  } catch {
6128
6629
  }
6129
- return writeJson3(res, 200, { ok: false, status: response.status, latencyMs, message });
6630
+ return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
6130
6631
  }
6131
- return writeJson3(res, 200, {
6632
+ return writeJson4(res, 200, {
6132
6633
  ok: true,
6133
6634
  status: response.status,
6134
6635
  latencyMs,
@@ -6136,7 +6637,7 @@ async function handleTestModel(req, res, id, cfg) {
6136
6637
  });
6137
6638
  } catch (err5) {
6138
6639
  const message = err5 instanceof Error ? err5.message : String(err5);
6139
- return writeJson3(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6640
+ return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
6140
6641
  }
6141
6642
  }
6142
6643
  function extractSampleText(text, apiFormat) {
@@ -6177,7 +6678,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
6177
6678
  const row = cfg.providers.find((p) => p.id === id);
6178
6679
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
6179
6680
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6180
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6681
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6181
6682
  }
6182
6683
  function parsePoolKeyInput(body, existing) {
6183
6684
  const out = {};
@@ -6196,7 +6697,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
6196
6697
  if (!id) return writeJsonError(res, 400, "provider id required in path");
6197
6698
  const idx = cfg.providers.findIndex((p) => p.id === id);
6198
6699
  if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
6199
- const body = await readJsonBody3(req);
6700
+ const body = await readJsonBody4(req);
6200
6701
  const parsed = parsePoolKeyInput(body);
6201
6702
  if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
6202
6703
  const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -6208,7 +6709,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
6208
6709
  row.apiKeys = [...row.apiKeys ?? [], entry];
6209
6710
  persistProviders(cfg, deps);
6210
6711
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6211
- return writeJson3(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
6712
+ return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
6212
6713
  }
6213
6714
  async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
6214
6715
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -6218,7 +6719,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
6218
6719
  const row = cfg.providers[idx];
6219
6720
  const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
6220
6721
  if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
6221
- const body = await readJsonBody3(req);
6722
+ const body = await readJsonBody4(req);
6222
6723
  const existing = row.apiKeys[keyIdx];
6223
6724
  const parsed = parsePoolKeyInput(body, existing);
6224
6725
  const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
@@ -6228,7 +6729,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
6228
6729
  row.apiKeys[keyIdx] = entry;
6229
6730
  persistProviders(cfg, deps);
6230
6731
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6231
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6732
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6232
6733
  }
6233
6734
  async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
6234
6735
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -6242,7 +6743,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
6242
6743
  if (row.apiKeys.length === 0) row.apiKeys = void 0;
6243
6744
  persistProviders(cfg, deps);
6244
6745
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6245
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6746
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6246
6747
  }
6247
6748
  async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
6248
6749
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -6252,11 +6753,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
6252
6753
  const row = cfg.providers[idx];
6253
6754
  const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
6254
6755
  if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
6255
- const body = await readJsonBody3(req);
6756
+ const body = await readJsonBody4(req);
6256
6757
  row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
6257
6758
  persistProviders(cfg, deps);
6258
6759
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
6259
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6760
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6260
6761
  }
6261
6762
  function parseApiKeysInput(raw, existing) {
6262
6763
  if (!Array.isArray(raw)) return existing;
@@ -6442,13 +6943,13 @@ function handlePresets(res, method) {
6442
6943
  website: p.website,
6443
6944
  modelsEndpoint: p.modelsEndpoint
6444
6945
  }));
6445
- return writeJson3(res, 200, { presets, excluded });
6946
+ return writeJson4(res, 200, { presets, excluded });
6446
6947
  }
6447
6948
  async function handleKeys(req, res, method, rest, deps) {
6448
6949
  if (method === "GET" && rest.length === 0) {
6449
6950
  const rows = await deps.keyDb.outboundApiKeysList();
6450
6951
  const reader = deps.keySpendReader;
6451
- if (!reader) return writeJson3(res, 200, { keys: rows.map(toKeyInfo) });
6952
+ if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
6452
6953
  const now = Date.now();
6453
6954
  const keys = await Promise.all(
6454
6955
  rows.map(async (row) => {
@@ -6460,13 +6961,13 @@ async function handleKeys(req, res, method, rest, deps) {
6460
6961
  return info;
6461
6962
  })
6462
6963
  );
6463
- return writeJson3(res, 200, { keys });
6964
+ return writeJson4(res, 200, { keys });
6464
6965
  }
6465
6966
  if (method === "POST" && rest.length === 0) {
6466
- const body = await readJsonBody3(req);
6967
+ const body = await readJsonBody4(req);
6467
6968
  const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
6468
- const created = await (0, import_outbound_api4.createNamedKey)(deps.keyDb, name);
6469
- return writeJson3(res, 201, {
6969
+ const created = await (0, import_outbound_api5.createNamedKey)(deps.keyDb, name);
6970
+ return writeJson4(res, 201, {
6470
6971
  id: created.id,
6471
6972
  name: created.name,
6472
6973
  keyPrefix: created.keyPrefix,
@@ -6477,7 +6978,7 @@ async function handleKeys(req, res, method, rest, deps) {
6477
6978
  }
6478
6979
  if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
6479
6980
  const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
6480
- if (revealed !== null) return writeJson3(res, 200, { key: revealed });
6981
+ if (revealed !== null) return writeJson4(res, 200, { key: revealed });
6481
6982
  const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
6482
6983
  if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
6483
6984
  return writeJsonError(
@@ -6492,32 +6993,32 @@ async function handleKeys(req, res, method, rest, deps) {
6492
6993
  const bound = await integrationKeyRequirement(deps, id);
6493
6994
  if (bound) return writeJsonError(res, 409, bound);
6494
6995
  const ok = await deps.keyDb.outboundApiKeysRevoke(id);
6495
- return writeJson3(res, ok ? 200 : 404, { ok });
6996
+ return writeJson4(res, ok ? 200 : 404, { ok });
6496
6997
  }
6497
6998
  if (method === "DELETE" && id && !action) {
6498
6999
  const bound = await integrationKeyRequirement(deps, id);
6499
7000
  if (bound) return writeJsonError(res, 409, bound);
6500
7001
  const ok = await deps.keyDb.outboundApiKeysDelete(id);
6501
- return writeJson3(res, ok ? 200 : 404, { ok });
7002
+ return writeJson4(res, ok ? 200 : 404, { ok });
6502
7003
  }
6503
7004
  if (method === "POST" && id && action === "enabled") {
6504
- const body = await readJsonBody3(req);
7005
+ const body = await readJsonBody4(req);
6505
7006
  const enabled = body["enabled"] === true;
6506
7007
  if (!enabled) {
6507
7008
  const bound = await integrationKeyRequirement(deps, id);
6508
7009
  if (bound) return writeJsonError(res, 409, bound);
6509
7010
  }
6510
7011
  const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
6511
- return writeJson3(res, ok ? 200 : 404, { ok, enabled });
7012
+ return writeJson4(res, ok ? 200 : 404, { ok, enabled });
6512
7013
  }
6513
7014
  if (method === "POST" && id && action === "permissions") {
6514
- const body = await readJsonBody3(req);
7015
+ const body = await readJsonBody4(req);
6515
7016
  if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
6516
7017
  return writeJsonError(res, 400, "body must contain only permissions");
6517
7018
  }
6518
7019
  let permissions;
6519
7020
  try {
6520
- permissions = (0, import_outbound_api4.validateOutboundPermissions)(body["permissions"]);
7021
+ permissions = (0, import_outbound_api5.validateOutboundPermissions)(body["permissions"]);
6521
7022
  } catch {
6522
7023
  return writeJsonError(
6523
7024
  res,
@@ -6526,19 +7027,19 @@ async function handleKeys(req, res, method, rest, deps) {
6526
7027
  );
6527
7028
  }
6528
7029
  const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
6529
- if (!before) return writeJson3(res, 404, { ok: false });
6530
- if (before.revokedAt !== null) return writeJson3(res, 409, { ok: false });
7030
+ if (!before) return writeJson4(res, 404, { ok: false });
7031
+ if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
6531
7032
  const required = await integrationKeyRequirement(deps, id, permissions);
6532
7033
  if (required) return writeJsonError(res, 409, required);
6533
7034
  const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
6534
7035
  if (!ok) {
6535
7036
  const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
6536
- return writeJson3(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
7037
+ return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
6537
7038
  }
6538
- return writeJson3(res, 200, { ok: true, allowedEndpoints: permissions });
7039
+ return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
6539
7040
  }
6540
7041
  if (method === "POST" && id && action === "max-concurrency") {
6541
- const body = await readJsonBody3(req);
7042
+ const body = await readJsonBody4(req);
6542
7043
  const raw = body["maxConcurrency"];
6543
7044
  let value;
6544
7045
  if (raw === null) {
@@ -6553,14 +7054,14 @@ async function handleKeys(req, res, method, rest, deps) {
6553
7054
  );
6554
7055
  }
6555
7056
  const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
6556
- return writeJson3(res, ok ? 200 : 404, { ok, maxConcurrency: value });
7057
+ return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
6557
7058
  }
6558
7059
  if (method === "POST" && id && action === "policy") {
6559
- const body = await readJsonBody3(req);
7060
+ const body = await readJsonBody4(req);
6560
7061
  const parsed = parseKeyPolicyBody(body);
6561
7062
  if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
6562
7063
  const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
6563
- return writeJson3(res, ok ? 200 : 404, { ok });
7064
+ return writeJson4(res, ok ? 200 : 404, { ok });
6564
7065
  }
6565
7066
  return writeJsonError(res, 405, `method ${method} not allowed on keys`);
6566
7067
  }
@@ -6651,7 +7152,8 @@ function outboundServerConfigInput(config) {
6651
7152
  userMessageQueue: config.userMessageQueue,
6652
7153
  concurrencyQueue: config.concurrencyQueue,
6653
7154
  voucher: config.voucher,
6654
- anthropic: config.anthropic
7155
+ anthropic: config.anthropic,
7156
+ search: config.search
6655
7157
  };
6656
7158
  }
6657
7159
  function projectImagesConfigForAdmin(config) {
@@ -6687,8 +7189,8 @@ function sameConfigValue(left, right) {
6687
7189
  return JSON.stringify(left) === JSON.stringify(right);
6688
7190
  }
6689
7191
  function imageConfigurationAuditFields(previous, next) {
6690
- const before = previous ?? import_outbound_api4.DEFAULT_IMAGES_SERVER_CONFIG;
6691
- const after = next ?? import_outbound_api4.DEFAULT_IMAGES_SERVER_CONFIG;
7192
+ const before = previous ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
7193
+ const after = next ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
6692
7194
  const fields = [];
6693
7195
  if (before.enabled !== after.enabled) fields.push("enablement");
6694
7196
  if (before.provider !== after.provider) fields.push("provider");
@@ -6714,15 +7216,21 @@ function currentImageGenerationId(deps) {
6714
7216
  }
6715
7217
  async function handleServer(req, res, method, deps) {
6716
7218
  if (method === "GET") {
6717
- const config = await (0, import_outbound_api4.loadServerConfig)(deps.settingsStore);
7219
+ const config = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
6718
7220
  let server = config;
6719
7221
  if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
6720
7222
  if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
6721
7223
  if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
6722
- return writeJson3(res, 200, { server: projectImagesConfigForAdmin(server) });
7224
+ if (config.search) {
7225
+ server = {
7226
+ ...server,
7227
+ search: redactSearchServerConfig(config.search)
7228
+ };
7229
+ }
7230
+ return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
6723
7231
  }
6724
7232
  if (method === "PUT") {
6725
- const patch = await readJsonBody3(req);
7233
+ const patch = await readJsonBody4(req);
6726
7234
  const queueErrors = validateQueueSegments(patch);
6727
7235
  if (queueErrors.length > 0) {
6728
7236
  return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
@@ -6751,7 +7259,7 @@ async function handleServer(req, res, method, deps) {
6751
7259
  if (billingErrors.length > 0) {
6752
7260
  return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
6753
7261
  }
6754
- const current = await (0, import_outbound_api4.loadServerConfig)(deps.settingsStore);
7262
+ const current = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
6755
7263
  let effectivePatch = patch;
6756
7264
  if (patch.proxy) {
6757
7265
  effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
@@ -6772,7 +7280,21 @@ async function handleServer(req, res, method, deps) {
6772
7280
  }
6773
7281
  effectivePatch = { ...effectivePatch, images };
6774
7282
  }
6775
- const merged = (0, import_outbound_api4.mergeServerConfig)(current, effectivePatch);
7283
+ if (patch.search !== void 0) {
7284
+ const searchPatch = preserveSearchSecrets(
7285
+ patch.search,
7286
+ current.search ?? import_outbound_api5.DEFAULT_SEARCH_SERVER_CONFIG
7287
+ );
7288
+ const searchErrors = (0, import_outbound_api5.validateSearchServerConfig)(searchPatch);
7289
+ if (searchErrors.length > 0) {
7290
+ return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
7291
+ }
7292
+ effectivePatch = {
7293
+ ...effectivePatch,
7294
+ search: searchPatch
7295
+ };
7296
+ }
7297
+ const merged = (0, import_outbound_api5.mergeServerConfig)(current, effectivePatch);
6776
7298
  const priorImageGenerationId = currentImageGenerationId(deps);
6777
7299
  try {
6778
7300
  await applyServerConfigTransaction(current, merged, {
@@ -6802,7 +7324,7 @@ async function handleServer(req, res, method, deps) {
6802
7324
  throw error;
6803
7325
  }
6804
7326
  },
6805
- persist: (next) => (0, import_outbound_api4.saveServerConfig)(deps.settingsStore, next),
7327
+ persist: (next) => (0, import_outbound_api5.saveServerConfig)(deps.settingsStore, next),
6806
7328
  restorePersisted: (snapshot) => deps.settingsStore.restoreDocumentSnapshot(snapshot)
6807
7329
  });
6808
7330
  } catch (error) {
@@ -6824,7 +7346,11 @@ async function handleServer(req, res, method, deps) {
6824
7346
  } catch {
6825
7347
  }
6826
7348
  }
6827
- return writeJson3(res, 200, { server: projectImagesConfigForAdmin(merged) });
7349
+ const mergedForAdmin = merged.search ? {
7350
+ ...merged,
7351
+ search: redactSearchServerConfig(merged.search)
7352
+ } : merged;
7353
+ return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
6828
7354
  }
6829
7355
  return writeJsonError(res, 405, `method ${method} not allowed on server`);
6830
7356
  }
@@ -6841,7 +7367,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6841
7367
  sessionKey: query2.get("sessionKey") ?? void 0,
6842
7368
  limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
6843
7369
  });
6844
- return writeJson3(res, 200, {
7370
+ return writeJson4(res, 200, {
6845
7371
  available: true,
6846
7372
  records,
6847
7373
  capacity: import_AccountRouteActivity.ACCOUNT_ROUTE_ACTIVITY_LIMIT,
@@ -6857,7 +7383,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6857
7383
  providerId: query2.get("providerId") ?? void 0,
6858
7384
  accountId: query2.get("accountId") ?? void 0
6859
7385
  });
6860
- return writeJson3(res, 200, {
7386
+ return writeJson4(res, 200, {
6861
7387
  available: true,
6862
7388
  entries,
6863
7389
  collectedAt: Date.now()
@@ -6876,10 +7402,10 @@ async function handleAccounts(req, res, method, rest, deps) {
6876
7402
  const accounts = await deps.subscriptionAccounts.listAll();
6877
7403
  const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
6878
7404
  const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
6879
- return writeJson3(res, 200, { accounts, providerAccounts, externalCli });
7405
+ return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
6880
7406
  }
6881
7407
  if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
6882
- const body = await readJsonBody3(req);
7408
+ const body = await readJsonBody4(req);
6883
7409
  const parsed = validateAccountBatchBody(body);
6884
7410
  if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
6885
7411
  const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
@@ -6895,15 +7421,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6895
7421
  deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
6896
7422
  }
6897
7423
  }
6898
- return writeJson3(res, 200, { ok: true, affected: result.affected });
7424
+ return writeJson4(res, 200, { ok: true, affected: result.affected });
6899
7425
  }
6900
7426
  if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6901
7427
  const result = handleCodexOAuthStatus(rest[2], deps);
6902
- return writeJson3(res, result.status, result.body);
7428
+ return writeJson4(res, result.status, result.body);
6903
7429
  }
6904
7430
  if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6905
7431
  const result = handleCodexOAuthCancel(rest[2], deps);
6906
- return writeJson3(res, result.status, result.body);
7432
+ return writeJson4(res, result.status, result.body);
6907
7433
  }
6908
7434
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
6909
7435
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6925,7 +7451,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6925
7451
  resumeAt: entry.resumeAt
6926
7452
  })) ?? [];
6927
7453
  const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
6928
- return writeJson3(res, 200, { diagnostics });
7454
+ return writeJson4(res, 200, { diagnostics });
6929
7455
  }
6930
7456
  if (method === "GET" && rest.length === 3 && rest[2] === "events") {
6931
7457
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6937,17 +7463,17 @@ async function handleAccounts(req, res, method, rest, deps) {
6937
7463
  }
6938
7464
  const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
6939
7465
  const diagnostics = (0, import_SubscriptionAccountHealth.getSharedAccountHealth)().getDiagnostics({ providerId, accountId });
6940
- return writeJson3(res, 200, { events: snapshot?.records ?? [], diagnostics });
7466
+ return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
6941
7467
  }
6942
7468
  if (method === "PATCH" && rest.length === 2) {
6943
7469
  const providerId = asSubscriptionProviderId(rest[0]);
6944
7470
  if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6945
- const body = await readJsonBody3(req);
7471
+ const body = await readJsonBody4(req);
6946
7472
  const patch = validateAccountMetadataPatch(body);
6947
7473
  if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
6948
7474
  const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
6949
7475
  if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
6950
- return writeJson3(res, 200, { ok: true });
7476
+ return writeJson4(res, 200, { ok: true });
6951
7477
  }
6952
7478
  if (method === "PUT" || method === "POST" || method === "DELETE") {
6953
7479
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6956,15 +7482,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6956
7482
  }
6957
7483
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6958
7484
  const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
6959
- return writeJson3(res, result.status, result.body);
7485
+ return writeJson4(res, result.status, result.body);
6960
7486
  }
6961
7487
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
6962
- const body2 = await readJsonBody3(req);
7488
+ const body2 = await readJsonBody4(req);
6963
7489
  const result = await handleOAuthComplete(providerId, body2, deps);
6964
- return writeJson3(res, result.status, result.body);
7490
+ return writeJson4(res, result.status, result.body);
6965
7491
  }
6966
7492
  if (method === "POST" && rest[1] === "accounts") {
6967
- const body2 = await readJsonBody3(req);
7493
+ const body2 = await readJsonBody4(req);
6968
7494
  const block = validateTokenBody(providerId, body2);
6969
7495
  if (!block) {
6970
7496
  return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
@@ -6972,20 +7498,20 @@ async function handleAccounts(req, res, method, rest, deps) {
6972
7498
  const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
6973
7499
  await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
6974
7500
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
6975
- return writeJson3(res, 200, status2 ? { account: status2 } : { ok: true });
7501
+ return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
6976
7502
  }
6977
7503
  if (method === "POST" && rest[1] === "import-external") {
6978
7504
  if (providerId !== "claude" && providerId !== "codex") {
6979
7505
  return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
6980
7506
  }
6981
- const body2 = await readJsonBody3(req);
7507
+ const body2 = await readJsonBody4(req);
6982
7508
  const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
6983
7509
  const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
6984
7510
  if (!result.ok) {
6985
7511
  return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
6986
7512
  }
6987
7513
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
6988
- return writeJson3(res, 200, {
7514
+ return writeJson4(res, 200, {
6989
7515
  ok: true,
6990
7516
  account: status2 ?? void 0,
6991
7517
  nativeCredentialMode: result.nativeCredentialMode,
@@ -7000,7 +7526,7 @@ async function handleAccounts(req, res, method, rest, deps) {
7000
7526
  const writer2 = deps.subscriptionTokenWriter;
7001
7527
  const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
7002
7528
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
7003
- return writeJson3(res, 200, { ok, account: status2 ?? void 0 });
7529
+ return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
7004
7530
  }
7005
7531
  if (method === "POST" && rest.length === 3 && rest[2] === "test") {
7006
7532
  const accountId = rest[1];
@@ -7010,7 +7536,7 @@ async function handleAccounts(req, res, method, rest, deps) {
7010
7536
  return writeJsonError(res, 404, `account '${accountId}' not found`);
7011
7537
  }
7012
7538
  const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
7013
- return writeJson3(res, 200, {
7539
+ return writeJson4(res, 200, {
7014
7540
  ok: result.ok,
7015
7541
  marked: result.marked,
7016
7542
  tier: result.tier,
@@ -7019,15 +7545,15 @@ async function handleAccounts(req, res, method, rest, deps) {
7019
7545
  }
7020
7546
  if (method === "POST" && rest[2] === "label") {
7021
7547
  const accountId = rest[1];
7022
- const body2 = await readJsonBody3(req);
7548
+ const body2 = await readJsonBody4(req);
7023
7549
  const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
7024
7550
  const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
7025
7551
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
7026
- return writeJson3(res, 200, { ok: true });
7552
+ return writeJson4(res, 200, { ok: true });
7027
7553
  }
7028
7554
  if (method === "POST" && rest[2] === "priority") {
7029
7555
  const accountId = rest[1];
7030
- const body2 = await readJsonBody3(req);
7556
+ const body2 = await readJsonBody4(req);
7031
7557
  const raw = body2["priority"];
7032
7558
  const priority = typeof raw === "number" ? raw : Number(raw);
7033
7559
  if (!Number.isFinite(priority)) {
@@ -7035,76 +7561,76 @@ async function handleAccounts(req, res, method, rest, deps) {
7035
7561
  }
7036
7562
  const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
7037
7563
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
7038
- return writeJson3(res, 200, { ok: true });
7564
+ return writeJson4(res, 200, { ok: true });
7039
7565
  }
7040
7566
  if (method === "POST" && rest[2] === "proxy") {
7041
7567
  const accountId = rest[1];
7042
- const body2 = await readJsonBody3(req);
7568
+ const body2 = await readJsonBody4(req);
7043
7569
  const rawProxy = body2["proxy"];
7044
7570
  let proxy;
7045
7571
  if (rawProxy !== null && rawProxy !== void 0) {
7046
- proxy = (0, import_outbound_api4.normalizeProxyConfig)(rawProxy);
7572
+ proxy = (0, import_outbound_api5.normalizeProxyConfig)(rawProxy);
7047
7573
  if (!proxy) return writeJsonError(res, 400, "invalid proxy config");
7048
7574
  }
7049
7575
  const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
7050
7576
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
7051
- return writeJson3(res, 200, { ok: true });
7577
+ return writeJson4(res, 200, { ok: true });
7052
7578
  }
7053
7579
  if (method === "POST" && rest[2] === "supported-models") {
7054
7580
  const accountId = rest[1];
7055
- const body2 = await readJsonBody3(req);
7581
+ const body2 = await readJsonBody4(req);
7056
7582
  const parsed = validateSupportedModelsBody(body2["supportedModels"]);
7057
7583
  if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
7058
7584
  const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
7059
7585
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
7060
- return writeJson3(res, 200, { ok: true });
7586
+ return writeJson4(res, 200, { ok: true });
7061
7587
  }
7062
7588
  if (method === "PUT" && rest[1] === "active") {
7063
- const body2 = await readJsonBody3(req);
7589
+ const body2 = await readJsonBody4(req);
7064
7590
  const id = typeof body2["id"] === "string" ? body2["id"] : "";
7065
7591
  if (!id) return writeJsonError(res, 400, "active switch requires { id }");
7066
7592
  const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
7067
7593
  if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
7068
- return writeJson3(res, 200, { ok: true });
7594
+ return writeJson4(res, 200, { ok: true });
7069
7595
  }
7070
7596
  if (method === "DELETE" && rest.length === 2) {
7071
7597
  const accountId = rest[1];
7072
7598
  const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
7073
7599
  if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
7074
7600
  deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
7075
- return writeJson3(res, 200, { ok: true });
7601
+ return writeJson4(res, 200, { ok: true });
7076
7602
  }
7077
7603
  if (method === "DELETE" && rest.length === 1) {
7078
7604
  await deps.subscriptionTokenWriter.clearProvider(providerId);
7079
7605
  deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
7080
- return writeJson3(res, 200, { ok: true });
7606
+ return writeJson4(res, 200, { ok: true });
7081
7607
  }
7082
7608
  if (method === "DELETE") {
7083
7609
  return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
7084
7610
  }
7085
- const body = await readJsonBody3(req);
7611
+ const body = await readJsonBody4(req);
7086
7612
  const config = validateTokenBody(providerId, body);
7087
7613
  if (!config) {
7088
7614
  return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
7089
7615
  }
7090
7616
  await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
7091
7617
  const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
7092
- return writeJson3(res, 200, status ? { account: status } : { ok: true });
7618
+ return writeJson4(res, 200, status ? { account: status } : { ok: true });
7093
7619
  }
7094
7620
  return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
7095
7621
  }
7096
7622
  async function handleCli(req, res, method, rest, deps) {
7097
7623
  if (method === "GET" && rest.length === 0) {
7098
7624
  const result = handleCliList(process.platform, deps.cliPathProbe);
7099
- return writeJson3(res, result.status, result.body);
7625
+ return writeJson4(res, result.status, result.body);
7100
7626
  }
7101
7627
  if (method === "GET" && rest[0] === "sessions") {
7102
7628
  const result = handleCliSessions();
7103
- return writeJson3(res, result.status, result.body);
7629
+ return writeJson4(res, result.status, result.body);
7104
7630
  }
7105
7631
  if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
7106
7632
  const result = handleCliStop(rest[1]);
7107
- return writeJson3(res, result.status, result.body);
7633
+ return writeJson4(res, result.status, result.body);
7108
7634
  }
7109
7635
  if (method === "POST" && rest[1] === "install") {
7110
7636
  const cli = rest[0];
@@ -7112,14 +7638,14 @@ async function handleCli(req, res, method, rest, deps) {
7112
7638
  return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
7113
7639
  }
7114
7640
  const result = await handleCliInstall(cli, deps.cliCommandRunner);
7115
- return writeJson3(res, result.status, result.body);
7641
+ return writeJson4(res, result.status, result.body);
7116
7642
  }
7117
7643
  if (method === "POST" && rest[1] === "launch") {
7118
7644
  const cli = rest[0];
7119
7645
  if (!isLaunchCliId(cli)) {
7120
7646
  return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
7121
7647
  }
7122
- const body = await readJsonBody3(req);
7648
+ const body = await readJsonBody4(req);
7123
7649
  const providers = loadConfig(deps.configPath).providers ?? [];
7124
7650
  const result = await handleCliLaunch(cli, body, {
7125
7651
  llmConfig: deps.llmConfig,
@@ -7128,7 +7654,7 @@ async function handleCli(req, res, method, rest, deps) {
7128
7654
  opener: deps.cliTerminalOpener,
7129
7655
  probe: deps.cliPathProbe
7130
7656
  });
7131
- return writeJson3(res, result.status, result.body);
7657
+ return writeJson4(res, result.status, result.body);
7132
7658
  }
7133
7659
  return writeJsonError(res, 405, `method ${method} not allowed on cli`);
7134
7660
  }
@@ -7138,52 +7664,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
7138
7664
  const manager = factory();
7139
7665
  try {
7140
7666
  if (method === "GET" && rest.length === 0) {
7141
- return writeJson3(res, 200, {
7667
+ return writeJson4(res, 200, {
7142
7668
  integrations: await manager.listStatus(),
7143
7669
  gateway: deps.outboundApiServer.getStatus()
7144
7670
  });
7145
7671
  }
7146
7672
  if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
7147
7673
  await manager.rotateGatewayKey();
7148
- return writeJson3(res, 200, { ok: true, integrations: await manager.listStatus() });
7674
+ return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
7149
7675
  }
7150
7676
  const client = rest[0];
7151
7677
  if (!isIntegrationClient(client)) {
7152
7678
  return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
7153
7679
  }
7154
7680
  if (method === "POST" && rest[1] === "key") {
7155
- const body = await readJsonBody3(req);
7681
+ const body = await readJsonBody4(req);
7156
7682
  if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
7157
7683
  return writeJsonError(res, 400, "body must contain a non-empty keyId string");
7158
7684
  }
7159
7685
  const status = await manager.bindIntegrationKey(client, body.keyId.trim());
7160
- return writeJson3(res, 200, { integration: status });
7686
+ return writeJson4(res, 200, { integration: status });
7161
7687
  }
7162
7688
  if (method === "POST" && rest[1] === "plan") {
7163
- const body = await readJsonBody3(req);
7689
+ const body = await readJsonBody4(req);
7164
7690
  const configPath = body.configPath;
7165
7691
  if (configPath !== void 0 && typeof configPath !== "string") {
7166
7692
  return writeJsonError(res, 400, "configPath must be a string");
7167
7693
  }
7168
7694
  const plan = await manager.plan(client, configPath);
7169
- return writeJson3(res, 200, { plan });
7695
+ return writeJson4(res, 200, { plan });
7170
7696
  }
7171
7697
  if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
7172
- const body = await readJsonBody3(req);
7698
+ const body = await readJsonBody4(req);
7173
7699
  const configPath = body.configPath;
7174
7700
  if (configPath !== void 0 && typeof configPath !== "string") {
7175
7701
  return writeJsonError(res, 400, "configPath must be a string");
7176
7702
  }
7177
7703
  const status = await manager.install(client, configPath);
7178
- return writeJson3(res, 200, { integration: status });
7704
+ return writeJson4(res, 200, { integration: status });
7179
7705
  }
7180
7706
  if (method === "POST" && rest[1] === "repair") {
7181
7707
  const status = await manager.repair(client);
7182
- return writeJson3(res, 200, { integration: status });
7708
+ return writeJson4(res, 200, { integration: status });
7183
7709
  }
7184
7710
  if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
7185
7711
  const status = await manager.remove(client);
7186
- return writeJson3(res, 200, { integration: status });
7712
+ return writeJson4(res, 200, { integration: status });
7187
7713
  }
7188
7714
  return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
7189
7715
  } catch (error) {
@@ -7326,8 +7852,8 @@ async function handleImages(res, method, rest, deps) {
7326
7852
  }
7327
7853
  const reader = deps.imageRuntimeStatus;
7328
7854
  if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
7329
- const serverConfig = await (0, import_outbound_api4.loadServerConfig)(deps.settingsStore);
7330
- const images = serverConfig.images ?? import_outbound_api4.DEFAULT_IMAGES_SERVER_CONFIG;
7855
+ const serverConfig = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
7856
+ const images = serverConfig.images ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
7331
7857
  const lifecycle = reader.status();
7332
7858
  const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
7333
7859
  const resources = safeRuntimeResources(reader.resourceStatus());
@@ -7346,7 +7872,7 @@ async function handleImages(res, method, rest, deps) {
7346
7872
  httpLeases: safeStatusCount(generation.httpLeases),
7347
7873
  hostedLeases: safeStatusCount(generation.hostedLeases)
7348
7874
  }));
7349
- return writeJson3(res, 200, {
7875
+ return writeJson4(res, 200, {
7350
7876
  configured: {
7351
7877
  enabled: images.enabled,
7352
7878
  provider: images.provider,
@@ -7374,11 +7900,11 @@ async function handleImages(res, method, rest, deps) {
7374
7900
  async function handleStatus(res, method, deps) {
7375
7901
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
7376
7902
  const status = deps.outboundApiServer.getStatus();
7377
- const serverConfig = await (0, import_outbound_api4.loadServerConfig)(deps.settingsStore);
7903
+ const serverConfig = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
7378
7904
  const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
7379
- const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => (0, import_outbound_api4.gatewayBindingToEndpointConfig)(binding));
7905
+ const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => (0, import_outbound_api5.gatewayBindingToEndpointConfig)(binding));
7380
7906
  const useSubscription = routes.some((route) => route.useSubscription);
7381
- if ((0, import_outbound_api4.isKindMappedEndpoint)(endpoint)) {
7907
+ if ((0, import_outbound_api5.isKindMappedEndpoint)(endpoint)) {
7382
7908
  const kinds = {};
7383
7909
  for (const route of routes) {
7384
7910
  for (const [kind, ref] of Object.entries(route.modelMap ?? {})) {
@@ -7412,14 +7938,14 @@ async function handleStatus(res, method, deps) {
7412
7938
  })() : void 0;
7413
7939
  if (status.running) {
7414
7940
  const queueStatus = deps.outboundApiServer.getQueueStatus();
7415
- return writeJson3(res, 200, {
7941
+ return writeJson4(res, 200, {
7416
7942
  ...status,
7417
7943
  endpoints,
7418
7944
  queueStatus,
7419
7945
  ...imageRuntime ? { imageRuntime } : {}
7420
7946
  });
7421
7947
  }
7422
- return writeJson3(res, 200, {
7948
+ return writeJson4(res, 200, {
7423
7949
  ...status,
7424
7950
  endpoints,
7425
7951
  ...imageRuntime ? { imageRuntime } : {}
@@ -7443,18 +7969,18 @@ function resolvePlaygroundPath(endpoint, body) {
7443
7969
  }
7444
7970
  async function handlePlayground(req, res, method, deps) {
7445
7971
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
7446
- const body = await readJsonBody3(req);
7972
+ const body = await readJsonBody4(req);
7447
7973
  const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
7448
7974
  const key = typeof body["key"] === "string" ? body["key"] : "";
7449
7975
  const payload = body["body"];
7450
7976
  const status = deps.outboundApiServer.getStatus();
7451
7977
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
7452
- const path2 = resolvePlaygroundPath(endpoint, isRecord2(payload) ? payload : {});
7978
+ const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
7453
7979
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
7454
7980
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
7455
7981
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
7456
7982
  }
7457
- function isRecord2(v) {
7983
+ function isRecord3(v) {
7458
7984
  return !!v && typeof v === "object" && !Array.isArray(v);
7459
7985
  }
7460
7986
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -7590,7 +8116,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
7590
8116
  }
7591
8117
 
7592
8118
  // src/admin/version.ts
7593
- var DAEMON_VERSION = true ? "0.2.0" : "0.0.0-dev";
8119
+ var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
7594
8120
 
7595
8121
  // src/admin/AdminServer.ts
7596
8122
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -8010,14 +8536,14 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
8010
8536
 
8011
8537
  // src/image-generation/ImageDoctorService.ts
8012
8538
  var import_image_generation = require("@omnicross/core/image-generation");
8013
- var import_outbound_api6 = require("@omnicross/core/outbound-api");
8539
+ var import_outbound_api7 = require("@omnicross/core/outbound-api");
8014
8540
  var import_subscriptions3 = require("@omnicross/subscriptions");
8015
8541
 
8016
8542
  // src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
8017
8543
  var import_node_crypto13 = require("crypto");
8018
8544
  var import_node_fs14 = require("fs");
8019
8545
  var import_node_path14 = require("path");
8020
- var import_outbound_api5 = require("@omnicross/core/outbound-api");
8546
+ var import_outbound_api6 = require("@omnicross/core/outbound-api");
8021
8547
 
8022
8548
  // src/image-generation/imageTenantHmac.ts
8023
8549
  var import_node_crypto12 = require("crypto");
@@ -8090,7 +8616,7 @@ var ACCOUNT_DOMAIN = Buffer.from("omnicross:codex-image-evidence:account:v1\0",
8090
8616
  var ACCOUNT_KEY = /^[a-f0-9]{64}$/u;
8091
8617
  var SIZE = /^(?:auto|[1-9][0-9]{1,4}x[1-9][0-9]{1,4})$/u;
8092
8618
  var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
8093
- var PHYSICAL_RETENTION_TTL_MS = import_outbound_api5.IMAGE_SERVER_HARD_CEILINGS.evidenceTtlMs;
8619
+ var PHYSICAL_RETENTION_TTL_MS = import_outbound_api6.IMAGE_SERVER_HARD_CEILINGS.evidenceTtlMs;
8094
8620
  function exactKeys(value, allowed) {
8095
8621
  const allow = new Set(allowed);
8096
8622
  return Object.keys(value).every((key) => allow.has(key));
@@ -8464,7 +8990,7 @@ function createImageDoctorService(options) {
8464
8990
  const activePaths = () => options.storageCatalog.active().resolver;
8465
8991
  return Object.freeze({
8466
8992
  inspectLocal: async (config) => {
8467
- const configErrors = (0, import_outbound_api6.validateImagesServerConfig)(config);
8993
+ const configErrors = (0, import_outbound_api7.validateImagesServerConfig)(config);
8468
8994
  let verifiedAreas = 0;
8469
8995
  try {
8470
8996
  const paths = activePaths();
@@ -8506,7 +9032,7 @@ function createImageDoctorService(options) {
8506
9032
  continue;
8507
9033
  }
8508
9034
  try {
8509
- const permissions = (0, import_outbound_api6.validateOutboundPermissions)(row.allowedEndpoints);
9035
+ const permissions = (0, import_outbound_api7.validateOutboundPermissions)(row.allowedEndpoints);
8510
9036
  if (permissions.includes("images")) imagesAuthorizedRows += 1;
8511
9037
  } catch {
8512
9038
  invalidRows += 1;
@@ -8810,7 +9336,7 @@ var ImageCleanupService = class {
8810
9336
  // src/image-generation/ImageRuntimeGenerationFactory.ts
8811
9337
  var import_node_crypto16 = require("crypto");
8812
9338
  var import_image_generation5 = require("@omnicross/core/image-generation");
8813
- var import_outbound_api7 = require("@omnicross/core/outbound-api");
9339
+ var import_outbound_api8 = require("@omnicross/core/outbound-api");
8814
9340
  var import_subscriptions4 = require("@omnicross/subscriptions");
8815
9341
 
8816
9342
  // src/image-generation/ImageApiRuntimeResolver.ts
@@ -9278,7 +9804,7 @@ function createImageRuntimeGeneration(options) {
9278
9804
  throw new TypeError("image runtime generation id is invalid");
9279
9805
  }
9280
9806
  const config = snapshotConfig(options.config);
9281
- const configErrors = (0, import_outbound_api7.validateImagesServerConfig)(config);
9807
+ const configErrors = (0, import_outbound_api8.validateImagesServerConfig)(config);
9282
9808
  if (configErrors.length > 0) {
9283
9809
  throw new TypeError(`image runtime configuration is invalid: ${configErrors[0]}`);
9284
9810
  }
@@ -12870,7 +13396,7 @@ function safeStringify(value) {
12870
13396
  var import_node_crypto20 = require("crypto");
12871
13397
  var import_node_fs20 = require("fs");
12872
13398
  var import_node_path20 = require("path");
12873
- var import_outbound_api8 = require("@omnicross/core/outbound-api");
13399
+ var import_outbound_api9 = require("@omnicross/core/outbound-api");
12874
13400
  function atomicReplaceDocument(targetPath, contents) {
12875
13401
  const tempPath = (0, import_node_path20.join)(
12876
13402
  (0, import_node_path20.dirname)(targetPath),
@@ -12918,13 +13444,13 @@ var JsonApiServerSettingsStore = class {
12918
13444
  box;
12919
13445
  atomicReplace;
12920
13446
  async get(key) {
12921
- if (key !== import_outbound_api8.OUTBOUND_API_SERVER_CONFIG_KEY) return void 0;
13447
+ if (key !== import_outbound_api9.OUTBOUND_API_SERVER_CONFIG_KEY) return void 0;
12922
13448
  const file = this.readFile();
12923
13449
  if (file.server === void 0) return void 0;
12924
13450
  return this.decryptSecrets(file.server);
12925
13451
  }
12926
13452
  async set(key, value) {
12927
- if (key !== import_outbound_api8.OUTBOUND_API_SERVER_CONFIG_KEY) return;
13453
+ if (key !== import_outbound_api9.OUTBOUND_API_SERVER_CONFIG_KEY) return;
12928
13454
  const file = this.readFile();
12929
13455
  file.server = this.encryptSecrets(value);
12930
13456
  this.atomicReplace(
@@ -14563,7 +15089,7 @@ var import_node_fs27 = require("fs");
14563
15089
  var import_node_path27 = require("path");
14564
15090
  var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
14565
15091
  var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
14566
- var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
15092
+ var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
14567
15093
  var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
14568
15094
  var import_subscriptions5 = require("@omnicross/subscriptions");
14569
15095
 
@@ -14711,7 +15237,7 @@ var JsonSubscriptionCredentialStore = class {
14711
15237
  * a plaintext token pair into `upstream-trace.jsonl`.
14712
15238
  */
14713
15239
  buildRefreshFetch(providerId, accountId) {
14714
- return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
15240
+ return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch5.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
14715
15241
  }
14716
15242
  /**
14717
15243
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -15269,7 +15795,7 @@ var JsonSubscriptionCredentialStore = class {
15269
15795
  };
15270
15796
 
15271
15797
  // src/AccountHealthProbeScheduler.ts
15272
- var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
15798
+ var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
15273
15799
 
15274
15800
  // src/probe/CodexGenerationProbe.ts
15275
15801
  var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
@@ -15430,7 +15956,7 @@ var AccountHealthProbeScheduler = class {
15430
15956
  this.logger = logger;
15431
15957
  this.config = config;
15432
15958
  this.now = opts.now ?? Date.now;
15433
- this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch5.fetchUpstream;
15959
+ this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch6.fetchUpstream;
15434
15960
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15435
15961
  this.planFor = opts.planFor ?? probePlanFor;
15436
15962
  }
@@ -16586,7 +17112,7 @@ var AuditWriter = class {
16586
17112
  var import_node_fs33 = require("fs");
16587
17113
  var import_node_crypto24 = require("crypto");
16588
17114
  var import_node_path34 = require("path");
16589
- var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
17115
+ var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
16590
17116
 
16591
17117
  // src/billing/billingFiles.ts
16592
17118
  var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
@@ -16609,7 +17135,7 @@ var BillingPublisher = class {
16609
17135
  constructor(billingDir, logger, opts = {}) {
16610
17136
  this.billingDir = billingDir;
16611
17137
  this.logger = logger;
16612
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch6.fetchUpstream)(url, init));
17138
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
16613
17139
  this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
16614
17140
  this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
16615
17141
  this.now = opts.now ?? Date.now;
@@ -17018,7 +17544,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
17018
17544
 
17019
17545
  // src/webhook/WebhookDispatcher.ts
17020
17546
  var import_node_crypto25 = require("crypto");
17021
- var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
17547
+ var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
17022
17548
  var WEBHOOK_MAX_ATTEMPTS = 3;
17023
17549
  var WEBHOOK_QUEUE_MAX = 1e3;
17024
17550
  var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
@@ -17038,7 +17564,7 @@ var WebhookDispatcher = class {
17038
17564
  sleep;
17039
17565
  now;
17040
17566
  constructor(opts = {}) {
17041
- this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch7.fetchUpstream)(url, init));
17567
+ this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init));
17042
17568
  this.logger = opts.logger;
17043
17569
  this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
17044
17570
  this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
@@ -17269,7 +17795,7 @@ function buildDaemon(config, paths) {
17269
17795
  );
17270
17796
  (0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
17271
17797
  (0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
17272
- (0, import_outbound_api9.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17798
+ (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17273
17799
  );
17274
17800
  const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
17275
17801
  const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
@@ -17290,7 +17816,7 @@ function buildDaemon(config, paths) {
17290
17816
  logger
17291
17817
  );
17292
17818
  claudeAllowanceRefreshScheduler.configure(
17293
- (0, import_outbound_api9.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17819
+ (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
17294
17820
  );
17295
17821
  const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
17296
17822
  (0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
@@ -17300,7 +17826,7 @@ function buildDaemon(config, paths) {
17300
17826
  );
17301
17827
  (0, import_subscriptions6.setSubscriptionProviderRegistry)(subscriptionRegistry);
17302
17828
  setServerProxyConfig(decryptedConfig.server?.proxy);
17303
- (0, import_upstreamFetch8.setUpstreamProxyResolver)(
17829
+ (0, import_upstreamFetch9.setUpstreamProxyResolver)(
17304
17830
  createUpstreamProxyResolver({
17305
17831
  getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
17306
17832
  })
@@ -17323,7 +17849,7 @@ function buildDaemon(config, paths) {
17323
17849
  const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
17324
17850
  // Catalog egress follows the same global/env proxy policy as every other
17325
17851
  // daemon upstream call; no provider/account override applies here.
17326
- fetchImpl: ((input, init) => (0, import_upstreamFetch8.fetchUpstream)(String(input), init ?? {}))
17852
+ fetchImpl: ((input, init) => (0, import_upstreamFetch9.fetchUpstream)(String(input), init ?? {}))
17327
17853
  });
17328
17854
  const pricingRefreshScheduler = new PricingRefreshScheduler(
17329
17855
  pricingEngine,
@@ -17335,13 +17861,21 @@ function buildDaemon(config, paths) {
17335
17861
  defaultUsageEventsPath(paths.configPath),
17336
17862
  async (providerId, model) => await pricingEngine.getEntry(providerId, model) !== null
17337
17863
  );
17338
- const keySpendTracker = new import_outbound_api10.KeySpendTracker(usageEventStore);
17864
+ const keySpendTracker = new import_outbound_api11.KeySpendTracker(usageEventStore);
17339
17865
  const usageThroughput = (0, import_usage2.getSharedUsageThroughputTracker)();
17340
17866
  const usageRecorder = new import_usage2.UsageRecorder(usageEventStore, pricingEngine, logger, {
17341
17867
  onRecord: (apiKeyId, costUsd, at) => keySpendTracker.add(apiKeyId, costUsd, at),
17342
17868
  onEvent: (row, at) => usageThroughput.record(row, at)
17343
17869
  });
17344
- const initialImagesConfig = (0, import_outbound_api9.normalizeServerConfig)(decryptedConfig.server).images;
17870
+ const initialImagesConfig = (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).images;
17871
+ const initialSearchConfig = (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).search;
17872
+ for (const issue of (0, import_outbound_api10.validateSearchServerConfig)(
17873
+ decryptedConfig.server?.search
17874
+ )) {
17875
+ logger.warn("[search] ignoring invalid config: " + issue);
17876
+ }
17877
+ const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
17878
+ const searchFrontendModes = initialSearchConfig.modes;
17345
17879
  const imageObservability = new ImageObservability();
17346
17880
  const imageRuntimeObservability = Object.freeze({
17347
17881
  telemetrySink: imageObservability.telemetrySink,
@@ -17471,7 +18005,9 @@ function buildDaemon(config, paths) {
17471
18005
  apiKeyPool,
17472
18006
  usageRecorder,
17473
18007
  openAIOperationRegistry,
17474
- responsesHostedImageIngress
18008
+ responsesHostedImageIngress,
18009
+ searchRuntime,
18010
+ searchFrontendModes
17475
18011
  });
17476
18012
  if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
17477
18013
  throw new Error(
@@ -17505,7 +18041,7 @@ function buildDaemon(config, paths) {
17505
18041
  credentialStore,
17506
18042
  (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)(),
17507
18043
  logger,
17508
- import_outbound_api9.DEFAULT_ACCOUNT_PROBE
18044
+ import_outbound_api10.DEFAULT_ACCOUNT_PROBE
17509
18045
  );
17510
18046
  const getHealthReport = () => buildHealthReport({
17511
18047
  version: DAEMON_VERSION,
@@ -17522,7 +18058,7 @@ function buildDaemon(config, paths) {
17522
18058
  // `/health` body stays byte-identical (zero regression).
17523
18059
  subscriptionAccountsHealthy: () => accountHealthProbeScheduler.enabled ? accountHealthProbeScheduler.probedAccountsHealthy() : void 0
17524
18060
  });
17525
- const outboundApiServer = (0, import_outbound_api9.getOutboundApiServer)({
18061
+ const outboundApiServer = (0, import_outbound_api10.getOutboundApiServer)({
17526
18062
  db: keyDb,
17527
18063
  // voucher-redemption #9: the key-authenticated `POST /redeem` endpoint redeems
17528
18064
  // cards against the presenting key (gated on `voucher.enabled`).
@@ -17536,7 +18072,9 @@ function buildDaemon(config, paths) {
17536
18072
  keySpendTracker,
17537
18073
  // configurable-logging: route the server's OWN lifecycle + relay dispatch-error
17538
18074
  // lines through the injected logger (honors level/format/file sink).
17539
- logger
18075
+ logger,
18076
+ // plan 阶段5: the same instance the managed frontends hold.
18077
+ searchRuntime
17540
18078
  });
17541
18079
  const auditDir = defaultAuditDir(paths.configPath);
17542
18080
  const billingDir = defaultBillingDir(paths.configPath);
@@ -17558,6 +18096,10 @@ function buildDaemon(config, paths) {
17558
18096
  }),
17559
18097
  routeLeaseManager,
17560
18098
  subscriptionAccounts,
18099
+ // search-settings-ui D3: the daemon's ONE search runtime + its
18100
+ // bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
18101
+ // `POST /admin/api/search/test` (501 when a light embedder omits it).
18102
+ searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
17561
18103
  accountAllowanceService,
17562
18104
  allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
17563
18105
  accountProbeService: accountHealthProbeScheduler,
@@ -17587,7 +18129,7 @@ function buildDaemon(config, paths) {
17587
18129
  // — `server.proxy.byProvider[...]` was silently skipped — and the call was
17588
18130
  // excluded from the upstream trace, so a failing login left no evidence.
17589
18131
  // `redactBodies` keeps the code/verifier + minted token out of that trace.
17590
- oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { providerId, redactBodies: true }),
18132
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, redactBodies: true }),
17591
18133
  subscriptionAccountAppender: credentialStore,
17592
18134
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
17593
18135
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -17607,7 +18149,7 @@ function buildDaemon(config, paths) {
17607
18149
  cliCommandRunner: paths.cliCommandRunner,
17608
18150
  integrationManagerFactory: () => {
17609
18151
  const live = outboundApiServer.getStatus();
17610
- const port = live.port || decryptedConfig.server?.port || import_outbound_api9.DEFAULT_OUTBOUND_PORT;
18152
+ const port = live.port || decryptedConfig.server?.port || import_outbound_api10.DEFAULT_OUTBOUND_PORT;
17611
18153
  return new IntegrationManager({
17612
18154
  configPath: paths.configPath,
17613
18155
  gatewayBaseUrl: live.loopbackUrl ?? `http://127.0.0.1:${port}`,
@@ -17653,7 +18195,7 @@ function buildDaemon(config, paths) {
17653
18195
  });
17654
18196
  const webhookDispatcher = new WebhookDispatcher({
17655
18197
  logger,
17656
- fetchImpl: (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init)
18198
+ fetchImpl: (url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init)
17657
18199
  });
17658
18200
  setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
17659
18201
  const auditWriter = new AuditWriter(auditDir, logger);
@@ -17694,6 +18236,8 @@ function buildDaemon(config, paths) {
17694
18236
  keyDb,
17695
18237
  settingsStore,
17696
18238
  openAIOperationRegistry,
18239
+ searchRuntime,
18240
+ searchFrontendModes,
17697
18241
  imageRuntimeManager,
17698
18242
  imageObservability,
17699
18243
  imageCleanupService,
@@ -17830,7 +18374,7 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
17830
18374
  console.info(
17831
18375
  " [\u26A0] live Images verification may consume subscription quota; one minimal low-quality PNG request will be sent"
17832
18376
  );
17833
- const result = await doctor.verifyLive(config.images ?? import_outbound_api11.DEFAULT_IMAGES_SERVER_CONFIG, signal);
18377
+ const result = await doctor.verifyLive(config.images ?? import_outbound_api12.DEFAULT_IMAGES_SERVER_CONFIG, signal);
17834
18378
  if (!result.ok) {
17835
18379
  console.info(` [\u2717] live Images verification: ${result.code}`);
17836
18380
  return false;
@@ -17840,6 +18384,116 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
17840
18384
  );
17841
18385
  return true;
17842
18386
  }
18387
+ function readSearchApiConfigFromEnv(env = process.env) {
18388
+ const configs = {};
18389
+ const read = (name) => {
18390
+ const value = env[`OMNICROSS_SEARCH_${name}`]?.trim();
18391
+ return value ? value : void 0;
18392
+ };
18393
+ const tavilyKey = read("TAVILY_API_KEY");
18394
+ if (tavilyKey) {
18395
+ configs.tavily = { apiKey: tavilyKey, ...optionalHost(read("TAVILY_API_HOST")) };
18396
+ }
18397
+ const jinaKey = read("JINA_API_KEY");
18398
+ const jinaHost = read("JINA_API_HOST");
18399
+ if (jinaKey || jinaHost) {
18400
+ configs.jina = { ...jinaKey ? { apiKey: jinaKey } : {}, ...optionalHost(jinaHost) };
18401
+ }
18402
+ const searxngHost = read("SEARXNG_API_HOST");
18403
+ if (searxngHost) {
18404
+ const username = read("SEARXNG_BASIC_AUTH_USERNAME");
18405
+ const password = read("SEARXNG_BASIC_AUTH_PASSWORD");
18406
+ configs.searxng = {
18407
+ apiHost: searxngHost,
18408
+ ...username ? { basicAuthUsername: username } : {},
18409
+ ...password ? { basicAuthPassword: password } : {}
18410
+ };
18411
+ }
18412
+ const zhipuKey = read("ZHIPU_API_KEY");
18413
+ if (zhipuKey) {
18414
+ configs.zhipu = { apiKey: zhipuKey, ...optionalHost(read("ZHIPU_API_HOST")) };
18415
+ }
18416
+ const zaiKey = read("Z_AI_API_KEY");
18417
+ if (zaiKey) {
18418
+ configs["z.ai"] = { apiKey: zaiKey, ...optionalHost(read("Z_AI_API_HOST")) };
18419
+ }
18420
+ return configs;
18421
+ }
18422
+ function optionalHost(apiHost) {
18423
+ return apiHost ? { apiHost } : {};
18424
+ }
18425
+ function resolveSearchApiConfigs(configured, env = process.env) {
18426
+ const fromEnv = readSearchApiConfigFromEnv(env);
18427
+ const resolved = {};
18428
+ const tavily = configured?.tavily ?? fromEnv.tavily;
18429
+ if (tavily) resolved.tavily = tavily;
18430
+ const jina = configured?.jina ?? fromEnv.jina;
18431
+ if (jina) resolved.jina = jina;
18432
+ const searxng = configured?.searxng ?? fromEnv.searxng;
18433
+ if (searxng) resolved.searxng = searxng;
18434
+ const zhipu = configured?.zhipu ?? fromEnv.zhipu;
18435
+ if (zhipu) resolved.zhipu = zhipu;
18436
+ const zai = configured?.["z.ai"] ?? fromEnv["z.ai"];
18437
+ if (zai) resolved["z.ai"] = zai;
18438
+ return resolved;
18439
+ }
18440
+ function formatCapabilities(capabilities) {
18441
+ return [
18442
+ `apiKey=${capabilities.requiresApiKey}`,
18443
+ `cancellation=${capabilities.supportsCancellation}`,
18444
+ `urlRead=${capabilities.supportsUrlRead}`,
18445
+ `region=${capabilities.supportsRegion}`,
18446
+ `language=${capabilities.supportsLanguage}`,
18447
+ `timeRange=${capabilities.supportsTimeRange}`,
18448
+ `maxResults=${capabilities.maxResults ?? "unbounded"}`
18449
+ ].join(", ");
18450
+ }
18451
+ function formatDiagnostic(diagnostic) {
18452
+ const parts = [diagnostic.status];
18453
+ if (diagnostic.reason) parts.push(diagnostic.reason);
18454
+ if (diagnostic.error) {
18455
+ const { code, details } = diagnostic.error;
18456
+ parts.push(
18457
+ `code=${code}, transport=${details?.transport ?? "unknown"}, stage=${details?.stage ?? "unknown"}`
18458
+ );
18459
+ }
18460
+ return parts.join(" \u2014 ");
18461
+ }
18462
+ async function runSearchDoctor(live, env = process.env, source = {}) {
18463
+ const apiConfigs = resolveSearchApiConfigs(source.config?.providers, env);
18464
+ const egressPolicy = source.config && source.config.egress.allowedPrivateHosts.length > 0 ? { allowedPrivateHosts: [...source.config.egress.allowedPrivateHosts] } : void 0;
18465
+ const contributions = [
18466
+ ...(0, import_http4.builtinHttpSearchContributions)(),
18467
+ ...(0, import_api4.apiSearchContributions)(apiConfigs, { ...egressPolicy ? { egressPolicy } : {} })
18468
+ ];
18469
+ const declarations = source.runtime?.listProviders() ?? contributions;
18470
+ console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
18471
+ const modes = source.config?.modes ?? import_search3.DEFAULT_SEARCH_FRONTEND_MODES;
18472
+ console.info(
18473
+ ` [i] frontend modes: ${import_search3.SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
18474
+ );
18475
+ console.info(
18476
+ " [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
18477
+ );
18478
+ for (const row of buildSearchDoctorSnapshot(declarations, apiConfigs)) {
18479
+ const mark = row.status === "unconfigured" ? "\u2013" : "\u2713";
18480
+ const suffix = row.status ? ` \u2014 ${row.status}: ${row.reason ?? ""}` : "";
18481
+ console.info(
18482
+ ` [${mark}] ${row.providerId}: source=${row.source}, kind=${row.kind}, ${formatCapabilities(row.capabilities)}${suffix}`
18483
+ );
18484
+ }
18485
+ if (!live) return 0;
18486
+ console.info(
18487
+ ` [\u26A0] live search sends ONE fixed public query per provider ("${SEARCH_DOCTOR_QUERY}") to the engine itself`
18488
+ );
18489
+ let hardFailure = false;
18490
+ for (const diagnostic of await runSearchLiveChecks(contributions)) {
18491
+ const mark = diagnostic.status === "healthy" ? "\u2713" : diagnostic.status === "failed" ? "\u2717" : "\u26A0";
18492
+ if (diagnostic.status === "failed") hardFailure = true;
18493
+ console.info(` [${mark}] ${diagnostic.providerId}: ${formatDiagnostic(diagnostic)}`);
18494
+ }
18495
+ return hardFailure ? 1 : 0;
18496
+ }
17843
18497
  async function runLiveProbe(url, key, fetchImpl = fetch) {
17844
18498
  try {
17845
18499
  const res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/messages/count_tokens`, {
@@ -17876,11 +18530,12 @@ async function runDoctor(argv, fetchImpl = fetch) {
17876
18530
  allowPositionals: true
17877
18531
  });
17878
18532
  const subject = positionals[0] ?? "claude";
17879
- if (subject !== "claude" && subject !== "images") {
17880
- throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images')`);
18533
+ if (subject !== "claude" && subject !== "images" && subject !== "search") {
18534
+ throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images', 'search')`);
17881
18535
  }
17882
18536
  const configPath = values.config;
17883
18537
  if (!configPath) {
18538
+ if (subject === "search") return runSearchDoctor(values.live === true);
17884
18539
  throw new Error("doctor: --config <path> is required (the same config `omnicross start` uses)");
17885
18540
  }
17886
18541
  const config = loadConfig(configPath);
@@ -17892,9 +18547,15 @@ async function runDoctor(argv, fetchImpl = fetch) {
17892
18547
  };
17893
18548
  const daemon = await buildDaemon(config, paths);
17894
18549
  try {
17895
- const serverConfig = await (0, import_outbound_api11.loadServerConfig)(daemon.settingsStore);
18550
+ const serverConfig = await (0, import_outbound_api12.loadServerConfig)(daemon.settingsStore);
18551
+ if (subject === "search") {
18552
+ return await runSearchDoctor(values.live === true, process.env, {
18553
+ ...serverConfig.search ? { config: serverConfig.search } : {},
18554
+ runtime: daemon.searchRuntime
18555
+ });
18556
+ }
17896
18557
  const checks = subject === "images" ? buildImagesDoctorChecks(await daemon.imageDoctor.inspectLocal(
17897
- serverConfig.images ?? import_outbound_api11.DEFAULT_IMAGES_SERVER_CONFIG
18558
+ serverConfig.images ?? import_outbound_api12.DEFAULT_IMAGES_SERVER_CONFIG
17898
18559
  )) : buildClaudeDoctorChecks(serverConfig);
17899
18560
  let hardFailure = false;
17900
18561
  console.info(subject === "images" ? "omnicross doctor images \u2014 local metadata only" : `omnicross doctor ${subject} \u2014 config: ${configPath}`);
@@ -18117,7 +18778,7 @@ function isClient(value) {
18117
18778
 
18118
18779
  // src/commands/keys.ts
18119
18780
  var import_node_util5 = require("util");
18120
- var import_outbound_api12 = require("@omnicross/core/outbound-api");
18781
+ var import_outbound_api13 = require("@omnicross/core/outbound-api");
18121
18782
  async function runKeys(argv) {
18122
18783
  const { values, positionals } = (0, import_node_util5.parseArgs)({
18123
18784
  args: argv,
@@ -18143,7 +18804,7 @@ async function runKeys(argv) {
18143
18804
  }
18144
18805
  async function keysAdd(db, name) {
18145
18806
  if (!name) throw new Error("keys add: a <name> is required");
18146
- const created = await (0, import_outbound_api12.createNamedKey)(db, name);
18807
+ const created = await (0, import_outbound_api13.createNamedKey)(db, name);
18147
18808
  console.info(`Created key '${created.name}' (id: ${created.id}).`);
18148
18809
  console.info("");
18149
18810
  console.info(` ${created.plaintextOnce}`);
@@ -18399,7 +19060,7 @@ function spawnCliInherit(plan) {
18399
19060
  var import_node_child_process3 = require("child_process");
18400
19061
  var import_node_readline2 = require("readline");
18401
19062
  var import_node_util7 = require("util");
18402
- var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
19063
+ var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
18403
19064
  var import_subscriptions7 = require("@omnicross/subscriptions");
18404
19065
  var PROVIDERS2 = ["claude", "codex", "gemini"];
18405
19066
  async function runLogin(argv, deps) {
@@ -18431,10 +19092,10 @@ async function runLogin(argv, deps) {
18431
19092
  };
18432
19093
  const box = resolveSecretBox(values["master-key-file"]);
18433
19094
  setSecretBox(box);
18434
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(createUpstreamProxyResolver());
19095
+ (0, import_upstreamFetch10.setUpstreamProxyResolver)(createUpstreamProxyResolver());
18435
19096
  try {
18436
19097
  const tokensPath = defaultTokensPath(values.config);
18437
- const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
19098
+ const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch10.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
18438
19099
  const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
18439
19100
  const expiresAt = await runProviderLogin(
18440
19101
  provider,
@@ -18447,7 +19108,7 @@ async function runLogin(argv, deps) {
18447
19108
  console.info(` token: [stored, encrypted] expiresAt: ${expiresAt ?? "n/a"}`);
18448
19109
  } finally {
18449
19110
  setSecretBox(null);
18450
- (0, import_upstreamFetch9.setUpstreamProxyResolver)(null);
19111
+ (0, import_upstreamFetch10.setUpstreamProxyResolver)(null);
18451
19112
  }
18452
19113
  }
18453
19114
  async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
@@ -18966,7 +19627,7 @@ function tokensSuffix(configPath) {
18966
19627
 
18967
19628
  // src/commands/start.ts
18968
19629
  var import_node_util10 = require("util");
18969
- var import_outbound_api13 = require("@omnicross/core/outbound-api");
19630
+ var import_outbound_api14 = require("@omnicross/core/outbound-api");
18970
19631
  var import_SubscriptionAccountHealth5 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
18971
19632
  var import_AccountAllowanceScheduling6 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
18972
19633
 
@@ -19028,7 +19689,7 @@ async function runStart(argv) {
19028
19689
  await daemon.llmConfig.ready();
19029
19690
  await daemon.migrateUsageStore();
19030
19691
  await daemon.providerProxy.start();
19031
- const serverConfig = await (0, import_outbound_api13.loadServerConfig)(daemon.settingsStore);
19692
+ const serverConfig = await (0, import_outbound_api14.loadServerConfig)(daemon.settingsStore);
19032
19693
  await daemon.imageCleanupService.runOnce();
19033
19694
  daemon.imageCleanupService.start();
19034
19695
  (0, import_SubscriptionAccountHealth5.getSharedAccountHealth)().configure({
@@ -19051,7 +19712,9 @@ async function runStart(argv) {
19051
19712
  voucher: serverConfig.voucher,
19052
19713
  // claude-api-protocol-fidelity (§10): count_tokens strategy/budget,
19053
19714
  // /v1/models shape, synthetic-ping heartbeat (hot-applied inside applyConfig).
19054
- anthropic: serverConfig.anthropic
19715
+ anthropic: serverConfig.anthropic,
19716
+ // plan 阶段5: the Codex frontend mode is read live per request from here.
19717
+ search: serverConfig.search
19055
19718
  });
19056
19719
  let dashboardUrl = null;
19057
19720
  if (!values["no-dashboard"]) {
@@ -19166,6 +19829,16 @@ Usage:
19166
19829
  Check local Images config, roots, stores, permissions,
19167
19830
  account and cached evidence. --live warns, then consumes
19168
19831
  at most one minimal subscription image request.
19832
+ omnicross doctor search [--live] List the builtin search providers and their declared
19833
+ capabilities (offline, no config needed). --live sends ONE
19834
+ fixed public query per provider and reports healthy /
19835
+ degraded / blocked / failed.
19836
+ Keyed API providers show as unconfigured unless enabled
19837
+ through the DIAGNOSTIC-ONLY environment variables
19838
+ OMNICROSS_SEARCH_{TAVILY,JINA,SEARXNG,ZHIPU,Z_AI}_API_KEY
19839
+ / _API_HOST (plus SEARXNG_BASIC_AUTH_USERNAME/_PASSWORD).
19840
+ These are read only by this command and are not the
19841
+ configuration system.
19169
19842
  `;
19170
19843
  async function main() {
19171
19844
  const [, , subcommand, ...rest] = process.argv;