@omnicross/daemon 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,7 +11,8 @@ import {
11
11
  DEFAULT_ACCOUNT_PROBE,
12
12
  DEFAULT_OUTBOUND_PORT,
13
13
  getOutboundApiServer,
14
- normalizeServerConfig as normalizeServerConfig2
14
+ normalizeServerConfig as normalizeServerConfig2,
15
+ validateSearchServerConfig as validateSearchServerConfig2
15
16
  } from "@omnicross/core/outbound-api";
16
17
  import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api/subscriptionRegistryPort";
17
18
  import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
@@ -875,14 +876,16 @@ import http from "http";
875
876
  import {
876
877
  createNamedKey,
877
878
  DEFAULT_IMAGES_SERVER_CONFIG,
879
+ DEFAULT_SEARCH_SERVER_CONFIG as DEFAULT_SEARCH_SERVER_CONFIG2,
878
880
  effectiveOutboundPermissions as effectiveOutboundPermissions2,
879
881
  gatewayBindingToEndpointConfig,
880
882
  isKindMappedEndpoint,
881
- loadServerConfig as loadServerConfig2,
883
+ loadServerConfig as loadServerConfig3,
882
884
  mergeServerConfig,
883
885
  normalizeProxyConfig,
884
886
  saveServerConfig,
885
- validateOutboundPermissions
887
+ validateOutboundPermissions,
888
+ validateSearchServerConfig
886
889
  } from "@omnicross/core/outbound-api";
887
890
  import {
888
891
  IMAGE_CAPABILITY_UNAVAILABLE_REASONS
@@ -3783,6 +3786,473 @@ async function handleDashboard(deps) {
3783
3786
  return { status: 200, body: summary };
3784
3787
  }
3785
3788
 
3789
+ // src/admin/searchAdminApi.ts
3790
+ import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
3791
+ import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
3792
+ import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport } from "@omnicross/core/search/http";
3793
+
3794
+ // src/search/searchDoctorProjection.ts
3795
+ import { toSearchErrorShape } from "@omnicross/contracts/search-types";
3796
+ import {
3797
+ JINA_CAPABILITIES,
3798
+ SEARXNG_CAPABILITIES,
3799
+ TAVILY_CAPABILITIES,
3800
+ ZHIPU_CAPABILITIES
3801
+ } from "@omnicross/core/search/api";
3802
+ import { builtinHttpSearchContributions } from "@omnicross/core/search/http";
3803
+ var API_DOCTOR_PROVIDERS = [
3804
+ {
3805
+ id: "tavily",
3806
+ capabilities: TAVILY_CAPABILITIES,
3807
+ configured: (configs) => configs.tavily !== void 0,
3808
+ missingReason: "no API key configured"
3809
+ },
3810
+ {
3811
+ id: "jina",
3812
+ capabilities: JINA_CAPABILITIES,
3813
+ configured: (configs) => configs.jina !== void 0,
3814
+ // Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
3815
+ // asked for is still not enabled.
3816
+ missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
3817
+ },
3818
+ {
3819
+ id: "searxng",
3820
+ capabilities: SEARXNG_CAPABILITIES,
3821
+ configured: (configs) => configs.searxng !== void 0,
3822
+ missingReason: "no API host configured"
3823
+ },
3824
+ {
3825
+ id: "zhipu",
3826
+ capabilities: ZHIPU_CAPABILITIES,
3827
+ configured: (configs) => configs.zhipu !== void 0,
3828
+ missingReason: "no API key configured"
3829
+ },
3830
+ {
3831
+ id: "z.ai",
3832
+ capabilities: ZHIPU_CAPABILITIES,
3833
+ configured: (configs) => configs["z.ai"] !== void 0,
3834
+ missingReason: "no API key configured"
3835
+ }
3836
+ ];
3837
+ function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContributions(), apiConfigs) {
3838
+ const rows = contributions.map((contribution) => ({
3839
+ providerId: contribution.id,
3840
+ source: contribution.source,
3841
+ kind: contribution.kind,
3842
+ capabilities: contribution.capabilities
3843
+ }));
3844
+ if (apiConfigs === void 0) return rows;
3845
+ for (const provider of API_DOCTOR_PROVIDERS) {
3846
+ if (provider.configured(apiConfigs)) continue;
3847
+ rows.push({
3848
+ providerId: provider.id,
3849
+ source: "builtin",
3850
+ kind: "api",
3851
+ capabilities: provider.capabilities,
3852
+ status: "unconfigured",
3853
+ reason: provider.missingReason
3854
+ });
3855
+ }
3856
+ return rows;
3857
+ }
3858
+ var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
3859
+ function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
3860
+ if (outcome.kind === "results") {
3861
+ if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
3862
+ return {
3863
+ providerId,
3864
+ status: "degraded",
3865
+ checkedAt,
3866
+ reason: "reachable, but the engine returned no usable results (possible partial drift)"
3867
+ };
3868
+ }
3869
+ const error = toSearchErrorShape(outcome.error);
3870
+ const stage = error.details?.stage;
3871
+ const { status, reason } = classifySearchFailure(stage, error.code);
3872
+ return { providerId, status, checkedAt, reason, error };
3873
+ }
3874
+ function classifySearchFailure(stage, code) {
3875
+ if (stage === "challenge") {
3876
+ return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
3877
+ }
3878
+ if (stage === "trust") {
3879
+ return {
3880
+ status: "blocked",
3881
+ reason: "the engine served a page that failed the anti-decoy trust check"
3882
+ };
3883
+ }
3884
+ if (code === "policy_denied") {
3885
+ return {
3886
+ status: "blocked",
3887
+ reason: "the egress policy refused the request target"
3888
+ };
3889
+ }
3890
+ if (code === "parse_failed") {
3891
+ return {
3892
+ status: "failed",
3893
+ reason: "the response was not recognizable as a search result page (parser drift suspected)"
3894
+ };
3895
+ }
3896
+ if (code === "timeout") {
3897
+ return { status: "failed", reason: "the request exceeded its time budget" };
3898
+ }
3899
+ return { status: "failed", reason: `the request failed (${code})` };
3900
+ }
3901
+
3902
+ // src/search/SearchAssembly.ts
3903
+ import { createSearchRuntime } from "@omnicross/core/search";
3904
+ import { apiSearchContributions } from "@omnicross/core/search/api";
3905
+ import { builtinHttpSearchContributions as builtinHttpSearchContributions2 } from "@omnicross/core/search/http";
3906
+ function searchEgressPolicyFrom(config) {
3907
+ const hosts = config.egress.allowedPrivateHosts;
3908
+ return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
3909
+ }
3910
+ function searchPolicyFrom(config) {
3911
+ const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
3912
+ return {
3913
+ ...preferred !== void 0 ? { preferred } : {},
3914
+ ...allowed !== void 0 ? { allowed: [...allowed] } : {},
3915
+ fallbackEnabled,
3916
+ ...maxAttempts !== void 0 ? { maxAttempts } : {}
3917
+ };
3918
+ }
3919
+ function searchContributionsFrom(config) {
3920
+ return [
3921
+ ...builtinHttpSearchContributions2(),
3922
+ ...apiSearchContributions(config.providers, {
3923
+ egressPolicy: searchEgressPolicyFrom(config)
3924
+ })
3925
+ ];
3926
+ }
3927
+ function buildSearchRuntime(config, options = {}) {
3928
+ const logger = options.logger ?? null;
3929
+ return createSearchRuntime({
3930
+ contributions: options.contributions ?? searchContributionsFrom(config),
3931
+ policy: searchPolicyFrom(config),
3932
+ ...logger ? {
3933
+ onEvent: (event) => {
3934
+ logger.debug(`[search] ${formatSearchEvent(event)}`);
3935
+ }
3936
+ } : {}
3937
+ });
3938
+ }
3939
+ function formatSearchEvent(event) {
3940
+ const parts = [
3941
+ `type=${event.type}`,
3942
+ `request=${event.requestId}`,
3943
+ `queryHash=${event.queryHash}`,
3944
+ `durationMs=${event.durationMs}`
3945
+ ];
3946
+ if ("providerId" in event && event.providerId !== void 0) {
3947
+ parts.push(`provider=${event.providerId}`);
3948
+ }
3949
+ if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
3950
+ if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
3951
+ if ("resultCount" in event && event.resultCount !== void 0) {
3952
+ parts.push(`results=${event.resultCount}`);
3953
+ }
3954
+ if ("fallbackCount" in event && event.fallbackCount !== void 0) {
3955
+ parts.push(`fallbacks=${event.fallbackCount}`);
3956
+ }
3957
+ return parts.join(" ");
3958
+ }
3959
+
3960
+ // src/admin/searchAdminApi.ts
3961
+ var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
3962
+ var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
3963
+ "tavily",
3964
+ "jina",
3965
+ "searxng",
3966
+ "zhipu",
3967
+ "z.ai"
3968
+ ]);
3969
+ var SEARCH_QUERY_MAX_CODE_UNITS = 256;
3970
+ var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
3971
+ var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
3972
+ var SEARCH_QUERY_MAX_RESULTS = 5;
3973
+ function sanitizeResultField(value, cap) {
3974
+ const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
3975
+ return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
3976
+ }
3977
+ function writeJson(res, status, body) {
3978
+ res.writeHead(status, { "Content-Type": "application/json" });
3979
+ res.end(JSON.stringify(body));
3980
+ }
3981
+ function writeErr(res, status, message) {
3982
+ writeJson(res, status, { error: { type: "admin_api_error", message } });
3983
+ }
3984
+ var SEARCH_MAX_BODY_BYTES = 64 * 1024;
3985
+ var SearchBodyTooLargeError = class extends Error {
3986
+ };
3987
+ async function readJsonBody2(req) {
3988
+ const chunks = [];
3989
+ let bytes = 0;
3990
+ for await (const chunk of req) {
3991
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
3992
+ bytes += buffer.length;
3993
+ if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
3994
+ chunks.push(buffer);
3995
+ }
3996
+ const raw = Buffer.concat(chunks).toString("utf8");
3997
+ if (!raw.trim()) return {};
3998
+ try {
3999
+ const parsed = JSON.parse(raw);
4000
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4001
+ } catch {
4002
+ return {};
4003
+ }
4004
+ }
4005
+ async function readBodyOrReject(req, res) {
4006
+ try {
4007
+ return await readJsonBody2(req);
4008
+ } catch (error) {
4009
+ if (error instanceof SearchBodyTooLargeError) {
4010
+ writeErr(res, 400, error.message);
4011
+ return void 0;
4012
+ }
4013
+ throw error;
4014
+ }
4015
+ }
4016
+ async function handleSearchAdmin(req, res, method, rest, deps) {
4017
+ if (rest.length === 1 && rest[0] === "diagnostics") {
4018
+ if (!deps.searchStatus) {
4019
+ return writeErr(res, 501, "Search status is not available in this build");
4020
+ }
4021
+ if (method !== "GET") {
4022
+ return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
4023
+ }
4024
+ return handleSearchDiagnostics(res, deps);
4025
+ }
4026
+ if (rest.length === 1 && rest[0] === "test") {
4027
+ if (!deps.searchStatus) {
4028
+ return writeErr(res, 501, "Search status is not available in this build");
4029
+ }
4030
+ if (method !== "POST") {
4031
+ return writeErr(res, 405, `method ${method} not allowed on search test`);
4032
+ }
4033
+ return handleSearchTest(req, res, deps);
4034
+ }
4035
+ if (rest.length === 1 && rest[0] === "query") {
4036
+ if (!deps.searchStatus) {
4037
+ return writeErr(res, 501, "Search status is not available in this build");
4038
+ }
4039
+ if (method !== "POST") {
4040
+ return writeErr(res, 405, `method ${method} not allowed on search query`);
4041
+ }
4042
+ return handleSearchQuery(req, res, deps);
4043
+ }
4044
+ return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
4045
+ }
4046
+ async function handleSearchDiagnostics(res, deps) {
4047
+ const status = deps.searchStatus;
4048
+ const persisted = await loadServerConfig(deps.settingsStore);
4049
+ const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
4050
+ const rows = buildSearchDoctorSnapshot(
4051
+ status.runtime.listProviders(),
4052
+ search.providers
4053
+ );
4054
+ const snapshot = {
4055
+ rows,
4056
+ modes: {
4057
+ // codex is read from the LIVE config per request — an admin PUT has
4058
+ // already applied. responses/anthropic were captured at bootstrap.
4059
+ codex: search.modes.codex,
4060
+ responses: status.modes.responses,
4061
+ anthropic: status.modes.anthropic
4062
+ },
4063
+ applySemantics: { codex: "immediate", rest: "restart" }
4064
+ };
4065
+ return writeJson(res, 200, { diagnostics: snapshot });
4066
+ }
4067
+ async function handleSearchTest(req, res, deps) {
4068
+ const status = deps.searchStatus;
4069
+ const body = await readBodyOrReject(req, res);
4070
+ if (body === void 0) return;
4071
+ const providerId = body["providerId"];
4072
+ if (typeof providerId !== "string" || providerId.length === 0) {
4073
+ return writeErr(res, 400, "providerId must be a non-empty string");
4074
+ }
4075
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
4076
+ return writeErr(res, 404, `unknown search provider '${providerId}'`);
4077
+ }
4078
+ const persisted = await loadServerConfig(deps.settingsStore);
4079
+ const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
4080
+ const providers = search.providers;
4081
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4082
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4083
+ }
4084
+ const egressPolicy = searchEgressPolicyFrom(search);
4085
+ const fetchImpl = status.testFetch;
4086
+ const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4087
+ const contributions = [
4088
+ ...builtinHttpSearchContributions3(transport),
4089
+ ...apiSearchContributions2(search.providers, {
4090
+ egressPolicy,
4091
+ ...fetchImpl ? { fetchImpl } : {}
4092
+ })
4093
+ ];
4094
+ const contribution = contributions.find((c) => c.id === providerId);
4095
+ if (!contribution) {
4096
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4097
+ }
4098
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4099
+ try {
4100
+ const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
4101
+ const diagnostic = classifyLiveSearchOutcome(
4102
+ contribution.id,
4103
+ { kind: "results", count: results.length },
4104
+ checkedAt
4105
+ );
4106
+ const response = { diagnostic, resultCount: results.length };
4107
+ return writeJson(res, 200, { result: response });
4108
+ } catch (error) {
4109
+ const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
4110
+ const response = { diagnostic };
4111
+ return writeJson(res, 200, { result: response });
4112
+ }
4113
+ }
4114
+ async function handleSearchQuery(req, res, deps) {
4115
+ const status = deps.searchStatus;
4116
+ const body = await readBodyOrReject(req, res);
4117
+ if (body === void 0) return;
4118
+ const providerId = body["providerId"];
4119
+ if (typeof providerId !== "string" || providerId.length === 0) {
4120
+ return writeErr(res, 400, "providerId must be a non-empty string");
4121
+ }
4122
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
4123
+ return writeErr(res, 404, `unknown search provider '${providerId}'`);
4124
+ }
4125
+ const query2 = body["query"];
4126
+ if (typeof query2 !== "string" || query2.trim().length === 0) {
4127
+ return writeErr(res, 400, "query must be a non-empty string");
4128
+ }
4129
+ if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
4130
+ return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
4131
+ }
4132
+ if (QUERY_CONTROL_CHARS.test(query2)) {
4133
+ return writeErr(res, 400, "query must not contain control characters");
4134
+ }
4135
+ const persisted = await loadServerConfig(deps.settingsStore);
4136
+ const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
4137
+ const providers = search.providers;
4138
+ if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
4139
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4140
+ }
4141
+ const egressPolicy = searchEgressPolicyFrom(search);
4142
+ const fetchImpl = status.testFetch;
4143
+ const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
4144
+ const contributions = [
4145
+ ...builtinHttpSearchContributions3(transport),
4146
+ ...apiSearchContributions2(search.providers, {
4147
+ egressPolicy,
4148
+ ...fetchImpl ? { fetchImpl } : {}
4149
+ })
4150
+ ];
4151
+ const contribution = contributions.find((c) => c.id === providerId);
4152
+ if (!contribution) {
4153
+ return writeErr(res, 400, `search provider '${providerId}' is not configured`);
4154
+ }
4155
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
4156
+ try {
4157
+ const results = await contribution.provider.search(query2, { maxResults: 5 });
4158
+ const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
4159
+ title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
4160
+ url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
4161
+ content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
4162
+ }));
4163
+ const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
4164
+ contribution.id,
4165
+ { kind: "results", count: sanitized.length },
4166
+ checkedAt
4167
+ );
4168
+ const response = {
4169
+ diagnostic,
4170
+ resultCount: sanitized.length,
4171
+ results: sanitized
4172
+ };
4173
+ return writeJson(res, 200, { result: response });
4174
+ } catch (error) {
4175
+ const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
4176
+ const response = { diagnostic };
4177
+ return writeJson(res, 200, { result: response });
4178
+ }
4179
+ }
4180
+
4181
+ // src/admin/searchAdminView.ts
4182
+ var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
4183
+ var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
4184
+ function isRecord(value) {
4185
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4186
+ }
4187
+ function redactSearchServerConfig(search) {
4188
+ const providers = {};
4189
+ for (const [id, raw] of Object.entries(search.providers)) {
4190
+ const entry = raw;
4191
+ const view = {};
4192
+ if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
4193
+ if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
4194
+ if (API_KEY_PROVIDERS.has(id)) {
4195
+ view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
4196
+ }
4197
+ if (BASIC_AUTH_PROVIDERS.has(id)) {
4198
+ view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
4199
+ }
4200
+ providers[id] = view;
4201
+ }
4202
+ return {
4203
+ modes: search.modes,
4204
+ providers,
4205
+ egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
4206
+ policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
4207
+ };
4208
+ }
4209
+ function storedSecrets(current, id) {
4210
+ const entry = current.providers[id];
4211
+ if (!entry) return {};
4212
+ const out = {};
4213
+ if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
4214
+ if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
4215
+ out.basicAuthPassword = entry.basicAuthPassword;
4216
+ }
4217
+ return out;
4218
+ }
4219
+ function resolveSecretField(entry, field, stored) {
4220
+ if (!(field in entry)) {
4221
+ if (stored !== void 0) entry[field] = stored;
4222
+ return;
4223
+ }
4224
+ const value = entry[field];
4225
+ if (value === null) {
4226
+ delete entry[field];
4227
+ return;
4228
+ }
4229
+ if (typeof value === "string" && value.trim().length > 0) return;
4230
+ if (stored !== void 0) entry[field] = stored;
4231
+ else delete entry[field];
4232
+ }
4233
+ function preserveSearchSecrets(incoming, current) {
4234
+ if (!isRecord(incoming)) return incoming;
4235
+ const section = { ...incoming };
4236
+ const providersValue = section["providers"];
4237
+ if (!isRecord(providersValue)) return section;
4238
+ const providers = {};
4239
+ for (const [id, entryValue] of Object.entries(providersValue)) {
4240
+ if (!isRecord(entryValue)) {
4241
+ providers[id] = entryValue;
4242
+ continue;
4243
+ }
4244
+ const entry = { ...entryValue };
4245
+ delete entry["apiKeyConfigured"];
4246
+ delete entry["basicAuthPasswordConfigured"];
4247
+ const stored = storedSecrets(current, id);
4248
+ resolveSecretField(entry, "apiKey", stored.apiKey);
4249
+ resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
4250
+ providers[id] = entry;
4251
+ }
4252
+ section["providers"] = providers;
4253
+ return section;
4254
+ }
4255
+
3786
4256
  // src/admin/keyPolicyBody.ts
3787
4257
  function parseKeyPolicyBody(body) {
3788
4258
  const policy = {};
@@ -3845,7 +4315,7 @@ function parseKeyPolicyBody(body) {
3845
4315
  var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
3846
4316
  var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
3847
4317
  var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
3848
- function isRecord(value) {
4318
+ function isRecord2(value) {
3849
4319
  return !!value && typeof value === "object" && !Array.isArray(value);
3850
4320
  }
3851
4321
  function nonBlank(value) {
@@ -3865,7 +4335,7 @@ function validateGatewayBindingsSegment(patch) {
3865
4335
  const ids = /* @__PURE__ */ new Set();
3866
4336
  raw.forEach((entry, index) => {
3867
4337
  const path2 = `bindings[${index}]`;
3868
- if (!isRecord(entry)) {
4338
+ if (!isRecord2(entry)) {
3869
4339
  errors.push(`${path2} must be an object`);
3870
4340
  return;
3871
4341
  }
@@ -3894,12 +4364,12 @@ function validateGatewayBindingsSegment(patch) {
3894
4364
  } else if (entry.modelMappings.length > 100) {
3895
4365
  errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
3896
4366
  } else if (entry.modelMappings.some(
3897
- (mapping) => !isRecord(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
4367
+ (mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
3898
4368
  )) {
3899
4369
  errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
3900
4370
  }
3901
4371
  }
3902
- if (!isRecord(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
4372
+ if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
3903
4373
  errors.push(`${path2}.target is invalid`);
3904
4374
  } else {
3905
4375
  if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
@@ -3914,7 +4384,7 @@ function validateGatewayBindingsSegment(patch) {
3914
4384
  }
3915
4385
  }
3916
4386
  if (entry.modelMap !== void 0) {
3917
- if (!isRecord(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
4387
+ if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
3918
4388
  errors.push(`${path2}.modelMap must contain string values`);
3919
4389
  }
3920
4390
  }
@@ -3935,19 +4405,19 @@ function validateGatewayBindingsSegment(patch) {
3935
4405
  import {
3936
4406
  generateVoucherCode,
3937
4407
  hashVoucherCode,
3938
- loadServerConfig,
4408
+ loadServerConfig as loadServerConfig2,
3939
4409
  newVoucherId,
3940
4410
  toVoucherInfo,
3941
4411
  voucherCodePrefix
3942
4412
  } from "@omnicross/core/outbound-api";
3943
- function writeJson(res, status, body) {
4413
+ function writeJson2(res, status, body) {
3944
4414
  res.writeHead(status, { "Content-Type": "application/json" });
3945
4415
  res.end(JSON.stringify(body));
3946
4416
  }
3947
- function writeErr(res, status, message) {
3948
- writeJson(res, status, { error: { type: "voucher_error", message } });
4417
+ function writeErr2(res, status, message) {
4418
+ writeJson2(res, status, { error: { type: "voucher_error", message } });
3949
4419
  }
3950
- function readJsonBody2(req) {
4420
+ function readJsonBody3(req) {
3951
4421
  return new Promise((resolve10, reject) => {
3952
4422
  const chunks = [];
3953
4423
  req.on("data", (c) => chunks.push(c));
@@ -3998,26 +4468,26 @@ function parseVoucherCreateBody(body) {
3998
4468
  return { ok: true, input };
3999
4469
  }
4000
4470
  async function voucherEnabled(deps) {
4001
- const config = await loadServerConfig(deps.settingsStore);
4471
+ const config = await loadServerConfig2(deps.settingsStore);
4002
4472
  return config.voucher?.enabled === true;
4003
4473
  }
4004
4474
  async function handleVoucher(req, res, method, rest, deps) {
4005
4475
  const voucherDb = deps.voucherDb;
4006
- if (!voucherDb) return writeErr(res, 501, "Voucher feature is not available");
4476
+ if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
4007
4477
  if (method === "GET" && rest.length === 0) {
4008
4478
  const rows = await voucherDb.voucherList();
4009
- return writeJson(res, 200, { vouchers: rows.map(toVoucherInfo) });
4479
+ return writeJson2(res, 200, { vouchers: rows.map(toVoucherInfo) });
4010
4480
  }
4011
4481
  if (method === "POST" && rest.length === 0) {
4012
- if (!await voucherEnabled(deps)) return writeErr(res, 403, "Voucher feature is disabled");
4482
+ if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
4013
4483
  let body;
4014
4484
  try {
4015
- body = await readJsonBody2(req);
4485
+ body = await readJsonBody3(req);
4016
4486
  } catch {
4017
- return writeErr(res, 400, "Invalid JSON in request body");
4487
+ return writeErr2(res, 400, "Invalid JSON in request body");
4018
4488
  }
4019
4489
  const parsed = parseVoucherCreateBody(body);
4020
- if (!parsed.ok) return writeErr(res, 400, parsed.message);
4490
+ if (!parsed.ok) return writeErr2(res, 400, parsed.message);
4021
4491
  const code = generateVoucherCode();
4022
4492
  const created = await voucherDb.voucherCreate({
4023
4493
  id: newVoucherId(),
@@ -4025,7 +4495,7 @@ async function handleVoucher(req, res, method, rest, deps) {
4025
4495
  codePrefix: voucherCodePrefix(code),
4026
4496
  ...parsed.input
4027
4497
  });
4028
- return writeJson(res, 201, {
4498
+ return writeJson2(res, 201, {
4029
4499
  id: created.id,
4030
4500
  codePrefix: created.codePrefix,
4031
4501
  type: created.type,
@@ -4036,11 +4506,11 @@ async function handleVoucher(req, res, method, rest, deps) {
4036
4506
  }
4037
4507
  const id = rest[0];
4038
4508
  if (method === "POST" && id && rest[1] === "revoke") {
4039
- if (!await voucherEnabled(deps)) return writeErr(res, 403, "Voucher feature is disabled");
4509
+ if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
4040
4510
  const ok = await voucherDb.voucherRevokeCas(id, Date.now());
4041
- return writeJson(res, ok ? 200 : 409, { ok });
4511
+ return writeJson2(res, ok ? 200 : 409, { ok });
4042
4512
  }
4043
- return writeErr(res, 405, `method ${method} not allowed on voucher`);
4513
+ return writeErr2(res, 405, `method ${method} not allowed on voucher`);
4044
4514
  }
4045
4515
 
4046
4516
  // src/admin/webhookConfigBody.ts
@@ -4970,12 +5440,12 @@ async function handlePricingResolveConflicts(body, deps) {
4970
5440
  }
4971
5441
 
4972
5442
  // src/admin/accountAllowanceApi.ts
4973
- function writeJson2(res, status, body) {
5443
+ function writeJson3(res, status, body) {
4974
5444
  res.writeHead(status, { "Content-Type": "application/json" });
4975
5445
  res.end(JSON.stringify(body));
4976
5446
  }
4977
5447
  function writeError2(res, status, message) {
4978
- writeJson2(res, status, { error: { type: "account_allowance_error", message } });
5448
+ writeJson3(res, status, { error: { type: "account_allowance_error", message } });
4979
5449
  }
4980
5450
  function readJson2(req) {
4981
5451
  return new Promise((resolve10, reject) => {
@@ -5008,7 +5478,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5008
5478
  if (!service.getSchedulingStatus) {
5009
5479
  return writeError2(res, 501, "allowance scheduling diagnostics are not available");
5010
5480
  }
5011
- return writeJson2(res, 200, { scheduling: service.getSchedulingStatus() });
5481
+ return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
5012
5482
  }
5013
5483
  if (method === "GET") {
5014
5484
  const params = query(req);
@@ -5017,7 +5487,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5017
5487
  if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
5018
5488
  const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
5019
5489
  const allowances = await service.list({ providerId, accountId });
5020
- return writeJson2(res, 200, { allowances });
5490
+ return writeJson3(res, 200, { allowances });
5021
5491
  }
5022
5492
  if (method === "POST" && rest[0] === "refresh") {
5023
5493
  const body = await readJson2(req);
@@ -5032,7 +5502,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
5032
5502
  if (accountId && allowances.length === 0) {
5033
5503
  return writeError2(res, 404, `Claude account '${accountId}' not found`);
5034
5504
  }
5035
- return writeJson2(res, 200, { allowances });
5505
+ return writeJson3(res, 200, { allowances });
5036
5506
  }
5037
5507
  return writeError2(res, 405, `method ${method} not allowed on account allowances`);
5038
5508
  }
@@ -5051,7 +5521,7 @@ function readBody(req) {
5051
5521
  req.on("error", reject);
5052
5522
  });
5053
5523
  }
5054
- async function readJsonBody3(req) {
5524
+ async function readJsonBody4(req) {
5055
5525
  const raw = await readBody(req);
5056
5526
  if (!raw.trim()) return {};
5057
5527
  try {
@@ -5061,12 +5531,12 @@ async function readJsonBody3(req) {
5061
5531
  return {};
5062
5532
  }
5063
5533
  }
5064
- function writeJson3(res, status, body) {
5534
+ function writeJson4(res, status, body) {
5065
5535
  res.writeHead(status, { "Content-Type": "application/json" });
5066
5536
  res.end(JSON.stringify(body));
5067
5537
  }
5068
5538
  function writeJsonError(res, status, message) {
5069
- writeJson3(res, status, { error: { type: "admin_api_error", message } });
5539
+ writeJson4(res, status, { error: { type: "admin_api_error", message } });
5070
5540
  }
5071
5541
  function maskProviderApiKey(apiKey) {
5072
5542
  if (!apiKey) return "";
@@ -5173,6 +5643,8 @@ async function handleAdminApi(req, res, path2, deps) {
5173
5643
  return await handleServer(req, res, method, deps);
5174
5644
  case "images":
5175
5645
  return await handleImages(res, method, rest, deps);
5646
+ case "search":
5647
+ return await handleSearchAdmin(req, res, method, rest, deps);
5176
5648
  case "accounts":
5177
5649
  return await handleAccounts(req, res, method, rest, deps);
5178
5650
  case "cli":
@@ -5206,7 +5678,7 @@ function requestQuery(req) {
5206
5678
  return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
5207
5679
  }
5208
5680
  function writeResult(res, result) {
5209
- writeJson3(res, result.status, result.body);
5681
+ writeJson4(res, result.status, result.body);
5210
5682
  }
5211
5683
  async function handleUsage(req, res, method, rest, deps) {
5212
5684
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
@@ -5215,13 +5687,13 @@ async function handleUsage(req, res, method, rest, deps) {
5215
5687
  async function handleDashboardRoute(res, method, deps) {
5216
5688
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
5217
5689
  const result = await handleDashboard(deps);
5218
- return writeJson3(res, result.status, result.body);
5690
+ return writeJson4(res, result.status, result.body);
5219
5691
  }
5220
5692
  async function handlePricing(req, res, method, rest, deps) {
5221
5693
  if (rest.length === 0) {
5222
5694
  if (method === "GET") return writeResult(res, await handlePricingList(deps));
5223
5695
  if (method === "PUT") {
5224
- return writeResult(res, await handlePricingUpsert(await readJsonBody3(req), deps));
5696
+ return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
5225
5697
  }
5226
5698
  if (method === "DELETE") {
5227
5699
  return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
@@ -5232,7 +5704,7 @@ async function handlePricing(req, res, method, rest, deps) {
5232
5704
  return writeResult(res, await handlePricingFetchLatest(deps));
5233
5705
  }
5234
5706
  if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
5235
- return writeResult(res, await handlePricingResolveConflicts(await readJsonBody3(req), deps));
5707
+ return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
5236
5708
  }
5237
5709
  return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
5238
5710
  }
@@ -5246,15 +5718,15 @@ function migrationDeps(deps) {
5246
5718
  }
5247
5719
  async function handleMigrationExport(req, res, method, deps) {
5248
5720
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
5249
- const body = await readJsonBody3(req);
5721
+ const body = await readJsonBody4(req);
5250
5722
  const result = await handleExport(body, migrationDeps(deps));
5251
- return writeJson3(res, result.status, result.body);
5723
+ return writeJson4(res, result.status, result.body);
5252
5724
  }
5253
5725
  async function handleMigrationImport(req, res, method, deps) {
5254
5726
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
5255
- const body = await readJsonBody3(req);
5727
+ const body = await readJsonBody4(req);
5256
5728
  const result = await handleImport(body, migrationDeps(deps));
5257
- return writeJson3(res, result.status, result.body);
5729
+ return writeJson4(res, result.status, result.body);
5258
5730
  }
5259
5731
  async function handleProviders(req, res, method, rest, deps) {
5260
5732
  const cfg = loadConfig(deps.configPath);
@@ -5285,13 +5757,13 @@ async function handleProviders(req, res, method, rest, deps) {
5285
5757
  if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
5286
5758
  const row = cfg.providers.find((p) => p.id === rest[0]);
5287
5759
  if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
5288
- return writeJson3(res, 200, { apiKey: row.apiKey ?? "" });
5760
+ return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
5289
5761
  }
5290
5762
  if (method === "GET") {
5291
- return writeJson3(res, 200, { providers: cfg.providers.map(toProviderView) });
5763
+ return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
5292
5764
  }
5293
5765
  if (method === "POST") {
5294
- const body = await readJsonBody3(req);
5766
+ const body = await readJsonBody4(req);
5295
5767
  const provider = parseProviderInput(body, void 0);
5296
5768
  if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
5297
5769
  if (cfg.providers.some((p) => p.id === provider.id)) {
@@ -5299,25 +5771,25 @@ async function handleProviders(req, res, method, rest, deps) {
5299
5771
  }
5300
5772
  cfg.providers.push(provider);
5301
5773
  persistProviders(cfg, deps);
5302
- return writeJson3(res, 201, { provider: toProviderView(provider) });
5774
+ return writeJson4(res, 201, { provider: toProviderView(provider) });
5303
5775
  }
5304
5776
  const id = rest[0];
5305
5777
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5306
5778
  const idx = cfg.providers.findIndex((p) => p.id === id);
5307
5779
  if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
5308
5780
  if (method === "PUT") {
5309
- const body = await readJsonBody3(req);
5781
+ const body = await readJsonBody4(req);
5310
5782
  const existing = cfg.providers[idx];
5311
5783
  const updated = parseProviderInput(body, existing);
5312
5784
  if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
5313
5785
  cfg.providers[idx] = updated;
5314
5786
  persistProviders(cfg, deps);
5315
- return writeJson3(res, 200, { provider: toProviderView(updated) });
5787
+ return writeJson4(res, 200, { provider: toProviderView(updated) });
5316
5788
  }
5317
5789
  if (method === "DELETE") {
5318
5790
  cfg.providers.splice(idx, 1);
5319
5791
  persistProviders(cfg, deps);
5320
- return writeJson3(res, 200, { ok: true });
5792
+ return writeJson4(res, 200, { ok: true });
5321
5793
  }
5322
5794
  return writeJsonError(res, 405, `method ${method} not allowed on providers`);
5323
5795
  }
@@ -5326,7 +5798,7 @@ function persistProviders(cfg, deps) {
5326
5798
  deps.llmConfig.reload(cfg);
5327
5799
  }
5328
5800
  async function handleProviderReorder(req, res, cfg, deps) {
5329
- const body = await readJsonBody3(req);
5801
+ const body = await readJsonBody4(req);
5330
5802
  const rawOrder = body["order"];
5331
5803
  if (!Array.isArray(rawOrder)) {
5332
5804
  return writeJsonError(res, 400, "reorder requires { order: string[] }");
@@ -5350,14 +5822,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
5350
5822
  }
5351
5823
  cfg.providers = reordered;
5352
5824
  persistProviders(cfg, deps);
5353
- return writeJson3(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
5825
+ return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
5354
5826
  }
5355
5827
  async function handleDiscoverModels(res, id, cfg) {
5356
5828
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5357
5829
  const row = cfg.providers.find((p) => p.id === id);
5358
5830
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5359
5831
  if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
5360
- return writeJson3(res, 200, { models: [], unsupportedFormat: true });
5832
+ return writeJson4(res, 200, { models: [], unsupportedFormat: true });
5361
5833
  }
5362
5834
  const resolvedKey = resolveEnvKey(row.apiKey);
5363
5835
  const base = row.baseUrl.replace(/\/+$/, "");
@@ -5374,32 +5846,32 @@ async function handleDiscoverModels(res, id, cfg) {
5374
5846
  message = parsed?.error?.message || parsed?.message || message;
5375
5847
  } catch {
5376
5848
  }
5377
- return writeJson3(res, 200, {
5849
+ return writeJson4(res, 200, {
5378
5850
  models: [],
5379
5851
  error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
5380
5852
  });
5381
5853
  }
5382
5854
  const data = await response.json();
5383
5855
  const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
5384
- return writeJson3(res, 200, { models });
5856
+ return writeJson4(res, 200, { models });
5385
5857
  } catch (err5) {
5386
5858
  const message = err5 instanceof Error ? err5.message : String(err5);
5387
- return writeJson3(res, 200, { models: [], error: `discovery failed: ${message}` });
5859
+ return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
5388
5860
  }
5389
5861
  }
5390
5862
  async function handleTestModel(req, res, id, cfg) {
5391
5863
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5392
5864
  const row = cfg.providers.find((p) => p.id === id);
5393
5865
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5394
- const body = await readJsonBody3(req);
5866
+ const body = await readJsonBody4(req);
5395
5867
  const model = typeof body["model"] === "string" ? body["model"].trim() : "";
5396
5868
  if (!model) return writeJsonError(res, 400, "test requires a { model } string");
5397
5869
  if (row.apiFormat === "gemini") {
5398
- return writeJson3(res, 200, { ok: false, unsupportedFormat: true });
5870
+ return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
5399
5871
  }
5400
5872
  const resolvedKey = resolveEnvKey(row.apiKey);
5401
5873
  if (!resolvedKey) {
5402
- return writeJson3(res, 200, { ok: false, message: "no API key configured for this provider" });
5874
+ return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
5403
5875
  }
5404
5876
  let url = row.baseUrl.replace(/\/+$/, "");
5405
5877
  const prompt = "Reply with the single word: OK.";
@@ -5438,9 +5910,9 @@ async function handleTestModel(req, res, id, cfg) {
5438
5910
  message = parsed?.error?.message || parsed?.message || message;
5439
5911
  } catch {
5440
5912
  }
5441
- return writeJson3(res, 200, { ok: false, status: response.status, latencyMs, message });
5913
+ return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
5442
5914
  }
5443
- return writeJson3(res, 200, {
5915
+ return writeJson4(res, 200, {
5444
5916
  ok: true,
5445
5917
  status: response.status,
5446
5918
  latencyMs,
@@ -5448,7 +5920,7 @@ async function handleTestModel(req, res, id, cfg) {
5448
5920
  });
5449
5921
  } catch (err5) {
5450
5922
  const message = err5 instanceof Error ? err5.message : String(err5);
5451
- return writeJson3(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5923
+ return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
5452
5924
  }
5453
5925
  }
5454
5926
  function extractSampleText(text, apiFormat) {
@@ -5489,7 +5961,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
5489
5961
  const row = cfg.providers.find((p) => p.id === id);
5490
5962
  if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
5491
5963
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5492
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
5964
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
5493
5965
  }
5494
5966
  function parsePoolKeyInput(body, existing) {
5495
5967
  const out = {};
@@ -5508,7 +5980,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
5508
5980
  if (!id) return writeJsonError(res, 400, "provider id required in path");
5509
5981
  const idx = cfg.providers.findIndex((p) => p.id === id);
5510
5982
  if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
5511
- const body = await readJsonBody3(req);
5983
+ const body = await readJsonBody4(req);
5512
5984
  const parsed = parsePoolKeyInput(body);
5513
5985
  if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
5514
5986
  const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -5520,7 +5992,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
5520
5992
  row.apiKeys = [...row.apiKeys ?? [], entry];
5521
5993
  persistProviders(cfg, deps);
5522
5994
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5523
- return writeJson3(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
5995
+ return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
5524
5996
  }
5525
5997
  async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
5526
5998
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -5530,7 +6002,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
5530
6002
  const row = cfg.providers[idx];
5531
6003
  const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
5532
6004
  if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
5533
- const body = await readJsonBody3(req);
6005
+ const body = await readJsonBody4(req);
5534
6006
  const existing = row.apiKeys[keyIdx];
5535
6007
  const parsed = parsePoolKeyInput(body, existing);
5536
6008
  const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
@@ -5540,7 +6012,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
5540
6012
  row.apiKeys[keyIdx] = entry;
5541
6013
  persistProviders(cfg, deps);
5542
6014
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5543
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6015
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
5544
6016
  }
5545
6017
  async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
5546
6018
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -5554,7 +6026,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
5554
6026
  if (row.apiKeys.length === 0) row.apiKeys = void 0;
5555
6027
  persistProviders(cfg, deps);
5556
6028
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5557
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6029
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
5558
6030
  }
5559
6031
  async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
5560
6032
  if (!id) return writeJsonError(res, 400, "provider id required in path");
@@ -5564,11 +6036,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
5564
6036
  const row = cfg.providers[idx];
5565
6037
  const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
5566
6038
  if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
5567
- const body = await readJsonBody3(req);
6039
+ const body = await readJsonBody4(req);
5568
6040
  row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
5569
6041
  persistProviders(cfg, deps);
5570
6042
  const cooldown = await deps.apiKeyPool.getKeyHealth(id);
5571
- return writeJson3(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
6043
+ return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
5572
6044
  }
5573
6045
  function parseApiKeysInput(raw, existing) {
5574
6046
  if (!Array.isArray(raw)) return existing;
@@ -5754,13 +6226,13 @@ function handlePresets(res, method) {
5754
6226
  website: p.website,
5755
6227
  modelsEndpoint: p.modelsEndpoint
5756
6228
  }));
5757
- return writeJson3(res, 200, { presets, excluded });
6229
+ return writeJson4(res, 200, { presets, excluded });
5758
6230
  }
5759
6231
  async function handleKeys(req, res, method, rest, deps) {
5760
6232
  if (method === "GET" && rest.length === 0) {
5761
6233
  const rows = await deps.keyDb.outboundApiKeysList();
5762
6234
  const reader = deps.keySpendReader;
5763
- if (!reader) return writeJson3(res, 200, { keys: rows.map(toKeyInfo) });
6235
+ if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
5764
6236
  const now = Date.now();
5765
6237
  const keys = await Promise.all(
5766
6238
  rows.map(async (row) => {
@@ -5772,13 +6244,13 @@ async function handleKeys(req, res, method, rest, deps) {
5772
6244
  return info;
5773
6245
  })
5774
6246
  );
5775
- return writeJson3(res, 200, { keys });
6247
+ return writeJson4(res, 200, { keys });
5776
6248
  }
5777
6249
  if (method === "POST" && rest.length === 0) {
5778
- const body = await readJsonBody3(req);
6250
+ const body = await readJsonBody4(req);
5779
6251
  const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
5780
6252
  const created = await createNamedKey(deps.keyDb, name);
5781
- return writeJson3(res, 201, {
6253
+ return writeJson4(res, 201, {
5782
6254
  id: created.id,
5783
6255
  name: created.name,
5784
6256
  keyPrefix: created.keyPrefix,
@@ -5789,7 +6261,7 @@ async function handleKeys(req, res, method, rest, deps) {
5789
6261
  }
5790
6262
  if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
5791
6263
  const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
5792
- if (revealed !== null) return writeJson3(res, 200, { key: revealed });
6264
+ if (revealed !== null) return writeJson4(res, 200, { key: revealed });
5793
6265
  const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
5794
6266
  if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
5795
6267
  return writeJsonError(
@@ -5804,26 +6276,26 @@ async function handleKeys(req, res, method, rest, deps) {
5804
6276
  const bound = await integrationKeyRequirement(deps, id);
5805
6277
  if (bound) return writeJsonError(res, 409, bound);
5806
6278
  const ok = await deps.keyDb.outboundApiKeysRevoke(id);
5807
- return writeJson3(res, ok ? 200 : 404, { ok });
6279
+ return writeJson4(res, ok ? 200 : 404, { ok });
5808
6280
  }
5809
6281
  if (method === "DELETE" && id && !action) {
5810
6282
  const bound = await integrationKeyRequirement(deps, id);
5811
6283
  if (bound) return writeJsonError(res, 409, bound);
5812
6284
  const ok = await deps.keyDb.outboundApiKeysDelete(id);
5813
- return writeJson3(res, ok ? 200 : 404, { ok });
6285
+ return writeJson4(res, ok ? 200 : 404, { ok });
5814
6286
  }
5815
6287
  if (method === "POST" && id && action === "enabled") {
5816
- const body = await readJsonBody3(req);
6288
+ const body = await readJsonBody4(req);
5817
6289
  const enabled = body["enabled"] === true;
5818
6290
  if (!enabled) {
5819
6291
  const bound = await integrationKeyRequirement(deps, id);
5820
6292
  if (bound) return writeJsonError(res, 409, bound);
5821
6293
  }
5822
6294
  const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
5823
- return writeJson3(res, ok ? 200 : 404, { ok, enabled });
6295
+ return writeJson4(res, ok ? 200 : 404, { ok, enabled });
5824
6296
  }
5825
6297
  if (method === "POST" && id && action === "permissions") {
5826
- const body = await readJsonBody3(req);
6298
+ const body = await readJsonBody4(req);
5827
6299
  if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
5828
6300
  return writeJsonError(res, 400, "body must contain only permissions");
5829
6301
  }
@@ -5838,19 +6310,19 @@ async function handleKeys(req, res, method, rest, deps) {
5838
6310
  );
5839
6311
  }
5840
6312
  const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
5841
- if (!before) return writeJson3(res, 404, { ok: false });
5842
- if (before.revokedAt !== null) return writeJson3(res, 409, { ok: false });
6313
+ if (!before) return writeJson4(res, 404, { ok: false });
6314
+ if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
5843
6315
  const required = await integrationKeyRequirement(deps, id, permissions);
5844
6316
  if (required) return writeJsonError(res, 409, required);
5845
6317
  const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
5846
6318
  if (!ok) {
5847
6319
  const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
5848
- return writeJson3(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
6320
+ return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
5849
6321
  }
5850
- return writeJson3(res, 200, { ok: true, allowedEndpoints: permissions });
6322
+ return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
5851
6323
  }
5852
6324
  if (method === "POST" && id && action === "max-concurrency") {
5853
- const body = await readJsonBody3(req);
6325
+ const body = await readJsonBody4(req);
5854
6326
  const raw = body["maxConcurrency"];
5855
6327
  let value;
5856
6328
  if (raw === null) {
@@ -5865,14 +6337,14 @@ async function handleKeys(req, res, method, rest, deps) {
5865
6337
  );
5866
6338
  }
5867
6339
  const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
5868
- return writeJson3(res, ok ? 200 : 404, { ok, maxConcurrency: value });
6340
+ return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
5869
6341
  }
5870
6342
  if (method === "POST" && id && action === "policy") {
5871
- const body = await readJsonBody3(req);
6343
+ const body = await readJsonBody4(req);
5872
6344
  const parsed = parseKeyPolicyBody(body);
5873
6345
  if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
5874
6346
  const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
5875
- return writeJson3(res, ok ? 200 : 404, { ok });
6347
+ return writeJson4(res, ok ? 200 : 404, { ok });
5876
6348
  }
5877
6349
  return writeJsonError(res, 405, `method ${method} not allowed on keys`);
5878
6350
  }
@@ -5963,7 +6435,8 @@ function outboundServerConfigInput(config) {
5963
6435
  userMessageQueue: config.userMessageQueue,
5964
6436
  concurrencyQueue: config.concurrencyQueue,
5965
6437
  voucher: config.voucher,
5966
- anthropic: config.anthropic
6438
+ anthropic: config.anthropic,
6439
+ search: config.search
5967
6440
  };
5968
6441
  }
5969
6442
  function projectImagesConfigForAdmin(config) {
@@ -6026,15 +6499,21 @@ function currentImageGenerationId(deps) {
6026
6499
  }
6027
6500
  async function handleServer(req, res, method, deps) {
6028
6501
  if (method === "GET") {
6029
- const config = await loadServerConfig2(deps.settingsStore);
6502
+ const config = await loadServerConfig3(deps.settingsStore);
6030
6503
  let server = config;
6031
6504
  if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
6032
6505
  if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
6033
6506
  if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
6034
- return writeJson3(res, 200, { server: projectImagesConfigForAdmin(server) });
6507
+ if (config.search) {
6508
+ server = {
6509
+ ...server,
6510
+ search: redactSearchServerConfig(config.search)
6511
+ };
6512
+ }
6513
+ return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
6035
6514
  }
6036
6515
  if (method === "PUT") {
6037
- const patch = await readJsonBody3(req);
6516
+ const patch = await readJsonBody4(req);
6038
6517
  const queueErrors = validateQueueSegments(patch);
6039
6518
  if (queueErrors.length > 0) {
6040
6519
  return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
@@ -6063,7 +6542,7 @@ async function handleServer(req, res, method, deps) {
6063
6542
  if (billingErrors.length > 0) {
6064
6543
  return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
6065
6544
  }
6066
- const current = await loadServerConfig2(deps.settingsStore);
6545
+ const current = await loadServerConfig3(deps.settingsStore);
6067
6546
  let effectivePatch = patch;
6068
6547
  if (patch.proxy) {
6069
6548
  effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
@@ -6084,6 +6563,20 @@ async function handleServer(req, res, method, deps) {
6084
6563
  }
6085
6564
  effectivePatch = { ...effectivePatch, images };
6086
6565
  }
6566
+ if (patch.search !== void 0) {
6567
+ const searchPatch = preserveSearchSecrets(
6568
+ patch.search,
6569
+ current.search ?? DEFAULT_SEARCH_SERVER_CONFIG2
6570
+ );
6571
+ const searchErrors = validateSearchServerConfig(searchPatch);
6572
+ if (searchErrors.length > 0) {
6573
+ return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
6574
+ }
6575
+ effectivePatch = {
6576
+ ...effectivePatch,
6577
+ search: searchPatch
6578
+ };
6579
+ }
6087
6580
  const merged = mergeServerConfig(current, effectivePatch);
6088
6581
  const priorImageGenerationId = currentImageGenerationId(deps);
6089
6582
  try {
@@ -6136,7 +6629,11 @@ async function handleServer(req, res, method, deps) {
6136
6629
  } catch {
6137
6630
  }
6138
6631
  }
6139
- return writeJson3(res, 200, { server: projectImagesConfigForAdmin(merged) });
6632
+ const mergedForAdmin = merged.search ? {
6633
+ ...merged,
6634
+ search: redactSearchServerConfig(merged.search)
6635
+ } : merged;
6636
+ return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
6140
6637
  }
6141
6638
  return writeJsonError(res, 405, `method ${method} not allowed on server`);
6142
6639
  }
@@ -6153,7 +6650,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6153
6650
  sessionKey: query2.get("sessionKey") ?? void 0,
6154
6651
  limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
6155
6652
  });
6156
- return writeJson3(res, 200, {
6653
+ return writeJson4(res, 200, {
6157
6654
  available: true,
6158
6655
  records,
6159
6656
  capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
@@ -6169,7 +6666,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6169
6666
  providerId: query2.get("providerId") ?? void 0,
6170
6667
  accountId: query2.get("accountId") ?? void 0
6171
6668
  });
6172
- return writeJson3(res, 200, {
6669
+ return writeJson4(res, 200, {
6173
6670
  available: true,
6174
6671
  entries,
6175
6672
  collectedAt: Date.now()
@@ -6188,10 +6685,10 @@ async function handleAccounts(req, res, method, rest, deps) {
6188
6685
  const accounts = await deps.subscriptionAccounts.listAll();
6189
6686
  const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
6190
6687
  const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
6191
- return writeJson3(res, 200, { accounts, providerAccounts, externalCli });
6688
+ return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
6192
6689
  }
6193
6690
  if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
6194
- const body = await readJsonBody3(req);
6691
+ const body = await readJsonBody4(req);
6195
6692
  const parsed = validateAccountBatchBody(body);
6196
6693
  if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
6197
6694
  const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
@@ -6207,15 +6704,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6207
6704
  deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
6208
6705
  }
6209
6706
  }
6210
- return writeJson3(res, 200, { ok: true, affected: result.affected });
6707
+ return writeJson4(res, 200, { ok: true, affected: result.affected });
6211
6708
  }
6212
6709
  if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
6213
6710
  const result = handleCodexOAuthStatus(rest[2], deps);
6214
- return writeJson3(res, result.status, result.body);
6711
+ return writeJson4(res, result.status, result.body);
6215
6712
  }
6216
6713
  if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
6217
6714
  const result = handleCodexOAuthCancel(rest[2], deps);
6218
- return writeJson3(res, result.status, result.body);
6715
+ return writeJson4(res, result.status, result.body);
6219
6716
  }
6220
6717
  if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
6221
6718
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6237,7 +6734,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6237
6734
  resumeAt: entry.resumeAt
6238
6735
  })) ?? [];
6239
6736
  const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
6240
- return writeJson3(res, 200, { diagnostics });
6737
+ return writeJson4(res, 200, { diagnostics });
6241
6738
  }
6242
6739
  if (method === "GET" && rest.length === 3 && rest[2] === "events") {
6243
6740
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6249,17 +6746,17 @@ async function handleAccounts(req, res, method, rest, deps) {
6249
6746
  }
6250
6747
  const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
6251
6748
  const diagnostics = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
6252
- return writeJson3(res, 200, { events: snapshot?.records ?? [], diagnostics });
6749
+ return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
6253
6750
  }
6254
6751
  if (method === "PATCH" && rest.length === 2) {
6255
6752
  const providerId = asSubscriptionProviderId(rest[0]);
6256
6753
  if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
6257
- const body = await readJsonBody3(req);
6754
+ const body = await readJsonBody4(req);
6258
6755
  const patch = validateAccountMetadataPatch(body);
6259
6756
  if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
6260
6757
  const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
6261
6758
  if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
6262
- return writeJson3(res, 200, { ok: true });
6759
+ return writeJson4(res, 200, { ok: true });
6263
6760
  }
6264
6761
  if (method === "PUT" || method === "POST" || method === "DELETE") {
6265
6762
  const providerId = asSubscriptionProviderId(rest[0]);
@@ -6268,15 +6765,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6268
6765
  }
6269
6766
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
6270
6767
  const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
6271
- return writeJson3(res, result.status, result.body);
6768
+ return writeJson4(res, result.status, result.body);
6272
6769
  }
6273
6770
  if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
6274
- const body2 = await readJsonBody3(req);
6771
+ const body2 = await readJsonBody4(req);
6275
6772
  const result = await handleOAuthComplete(providerId, body2, deps);
6276
- return writeJson3(res, result.status, result.body);
6773
+ return writeJson4(res, result.status, result.body);
6277
6774
  }
6278
6775
  if (method === "POST" && rest[1] === "accounts") {
6279
- const body2 = await readJsonBody3(req);
6776
+ const body2 = await readJsonBody4(req);
6280
6777
  const block = validateTokenBody(providerId, body2);
6281
6778
  if (!block) {
6282
6779
  return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
@@ -6284,20 +6781,20 @@ async function handleAccounts(req, res, method, rest, deps) {
6284
6781
  const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
6285
6782
  await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
6286
6783
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
6287
- return writeJson3(res, 200, status2 ? { account: status2 } : { ok: true });
6784
+ return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
6288
6785
  }
6289
6786
  if (method === "POST" && rest[1] === "import-external") {
6290
6787
  if (providerId !== "claude" && providerId !== "codex") {
6291
6788
  return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
6292
6789
  }
6293
- const body2 = await readJsonBody3(req);
6790
+ const body2 = await readJsonBody4(req);
6294
6791
  const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
6295
6792
  const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
6296
6793
  if (!result.ok) {
6297
6794
  return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
6298
6795
  }
6299
6796
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
6300
- return writeJson3(res, 200, {
6797
+ return writeJson4(res, 200, {
6301
6798
  ok: true,
6302
6799
  account: status2 ?? void 0,
6303
6800
  nativeCredentialMode: result.nativeCredentialMode,
@@ -6312,7 +6809,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6312
6809
  const writer2 = deps.subscriptionTokenWriter;
6313
6810
  const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
6314
6811
  const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
6315
- return writeJson3(res, 200, { ok, account: status2 ?? void 0 });
6812
+ return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
6316
6813
  }
6317
6814
  if (method === "POST" && rest.length === 3 && rest[2] === "test") {
6318
6815
  const accountId = rest[1];
@@ -6322,7 +6819,7 @@ async function handleAccounts(req, res, method, rest, deps) {
6322
6819
  return writeJsonError(res, 404, `account '${accountId}' not found`);
6323
6820
  }
6324
6821
  const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
6325
- return writeJson3(res, 200, {
6822
+ return writeJson4(res, 200, {
6326
6823
  ok: result.ok,
6327
6824
  marked: result.marked,
6328
6825
  tier: result.tier,
@@ -6331,15 +6828,15 @@ async function handleAccounts(req, res, method, rest, deps) {
6331
6828
  }
6332
6829
  if (method === "POST" && rest[2] === "label") {
6333
6830
  const accountId = rest[1];
6334
- const body2 = await readJsonBody3(req);
6831
+ const body2 = await readJsonBody4(req);
6335
6832
  const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
6336
6833
  const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
6337
6834
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
6338
- return writeJson3(res, 200, { ok: true });
6835
+ return writeJson4(res, 200, { ok: true });
6339
6836
  }
6340
6837
  if (method === "POST" && rest[2] === "priority") {
6341
6838
  const accountId = rest[1];
6342
- const body2 = await readJsonBody3(req);
6839
+ const body2 = await readJsonBody4(req);
6343
6840
  const raw = body2["priority"];
6344
6841
  const priority = typeof raw === "number" ? raw : Number(raw);
6345
6842
  if (!Number.isFinite(priority)) {
@@ -6347,11 +6844,11 @@ async function handleAccounts(req, res, method, rest, deps) {
6347
6844
  }
6348
6845
  const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
6349
6846
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
6350
- return writeJson3(res, 200, { ok: true });
6847
+ return writeJson4(res, 200, { ok: true });
6351
6848
  }
6352
6849
  if (method === "POST" && rest[2] === "proxy") {
6353
6850
  const accountId = rest[1];
6354
- const body2 = await readJsonBody3(req);
6851
+ const body2 = await readJsonBody4(req);
6355
6852
  const rawProxy = body2["proxy"];
6356
6853
  let proxy;
6357
6854
  if (rawProxy !== null && rawProxy !== void 0) {
@@ -6360,63 +6857,63 @@ async function handleAccounts(req, res, method, rest, deps) {
6360
6857
  }
6361
6858
  const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
6362
6859
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
6363
- return writeJson3(res, 200, { ok: true });
6860
+ return writeJson4(res, 200, { ok: true });
6364
6861
  }
6365
6862
  if (method === "POST" && rest[2] === "supported-models") {
6366
6863
  const accountId = rest[1];
6367
- const body2 = await readJsonBody3(req);
6864
+ const body2 = await readJsonBody4(req);
6368
6865
  const parsed = validateSupportedModelsBody(body2["supportedModels"]);
6369
6866
  if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
6370
6867
  const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
6371
6868
  if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
6372
- return writeJson3(res, 200, { ok: true });
6869
+ return writeJson4(res, 200, { ok: true });
6373
6870
  }
6374
6871
  if (method === "PUT" && rest[1] === "active") {
6375
- const body2 = await readJsonBody3(req);
6872
+ const body2 = await readJsonBody4(req);
6376
6873
  const id = typeof body2["id"] === "string" ? body2["id"] : "";
6377
6874
  if (!id) return writeJsonError(res, 400, "active switch requires { id }");
6378
6875
  const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
6379
6876
  if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
6380
- return writeJson3(res, 200, { ok: true });
6877
+ return writeJson4(res, 200, { ok: true });
6381
6878
  }
6382
6879
  if (method === "DELETE" && rest.length === 2) {
6383
6880
  const accountId = rest[1];
6384
6881
  const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
6385
6882
  if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
6386
6883
  deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
6387
- return writeJson3(res, 200, { ok: true });
6884
+ return writeJson4(res, 200, { ok: true });
6388
6885
  }
6389
6886
  if (method === "DELETE" && rest.length === 1) {
6390
6887
  await deps.subscriptionTokenWriter.clearProvider(providerId);
6391
6888
  deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
6392
- return writeJson3(res, 200, { ok: true });
6889
+ return writeJson4(res, 200, { ok: true });
6393
6890
  }
6394
6891
  if (method === "DELETE") {
6395
6892
  return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
6396
6893
  }
6397
- const body = await readJsonBody3(req);
6894
+ const body = await readJsonBody4(req);
6398
6895
  const config = validateTokenBody(providerId, body);
6399
6896
  if (!config) {
6400
6897
  return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
6401
6898
  }
6402
6899
  await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
6403
6900
  const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
6404
- return writeJson3(res, 200, status ? { account: status } : { ok: true });
6901
+ return writeJson4(res, 200, status ? { account: status } : { ok: true });
6405
6902
  }
6406
6903
  return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
6407
6904
  }
6408
6905
  async function handleCli(req, res, method, rest, deps) {
6409
6906
  if (method === "GET" && rest.length === 0) {
6410
6907
  const result = handleCliList(process.platform, deps.cliPathProbe);
6411
- return writeJson3(res, result.status, result.body);
6908
+ return writeJson4(res, result.status, result.body);
6412
6909
  }
6413
6910
  if (method === "GET" && rest[0] === "sessions") {
6414
6911
  const result = handleCliSessions();
6415
- return writeJson3(res, result.status, result.body);
6912
+ return writeJson4(res, result.status, result.body);
6416
6913
  }
6417
6914
  if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
6418
6915
  const result = handleCliStop(rest[1]);
6419
- return writeJson3(res, result.status, result.body);
6916
+ return writeJson4(res, result.status, result.body);
6420
6917
  }
6421
6918
  if (method === "POST" && rest[1] === "install") {
6422
6919
  const cli = rest[0];
@@ -6424,14 +6921,14 @@ async function handleCli(req, res, method, rest, deps) {
6424
6921
  return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
6425
6922
  }
6426
6923
  const result = await handleCliInstall(cli, deps.cliCommandRunner);
6427
- return writeJson3(res, result.status, result.body);
6924
+ return writeJson4(res, result.status, result.body);
6428
6925
  }
6429
6926
  if (method === "POST" && rest[1] === "launch") {
6430
6927
  const cli = rest[0];
6431
6928
  if (!isLaunchCliId(cli)) {
6432
6929
  return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
6433
6930
  }
6434
- const body = await readJsonBody3(req);
6931
+ const body = await readJsonBody4(req);
6435
6932
  const providers = loadConfig(deps.configPath).providers ?? [];
6436
6933
  const result = await handleCliLaunch(cli, body, {
6437
6934
  llmConfig: deps.llmConfig,
@@ -6440,7 +6937,7 @@ async function handleCli(req, res, method, rest, deps) {
6440
6937
  opener: deps.cliTerminalOpener,
6441
6938
  probe: deps.cliPathProbe
6442
6939
  });
6443
- return writeJson3(res, result.status, result.body);
6940
+ return writeJson4(res, result.status, result.body);
6444
6941
  }
6445
6942
  return writeJsonError(res, 405, `method ${method} not allowed on cli`);
6446
6943
  }
@@ -6450,52 +6947,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
6450
6947
  const manager = factory();
6451
6948
  try {
6452
6949
  if (method === "GET" && rest.length === 0) {
6453
- return writeJson3(res, 200, {
6950
+ return writeJson4(res, 200, {
6454
6951
  integrations: await manager.listStatus(),
6455
6952
  gateway: deps.outboundApiServer.getStatus()
6456
6953
  });
6457
6954
  }
6458
6955
  if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
6459
6956
  await manager.rotateGatewayKey();
6460
- return writeJson3(res, 200, { ok: true, integrations: await manager.listStatus() });
6957
+ return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
6461
6958
  }
6462
6959
  const client = rest[0];
6463
6960
  if (!isIntegrationClient(client)) {
6464
6961
  return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
6465
6962
  }
6466
6963
  if (method === "POST" && rest[1] === "key") {
6467
- const body = await readJsonBody3(req);
6964
+ const body = await readJsonBody4(req);
6468
6965
  if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
6469
6966
  return writeJsonError(res, 400, "body must contain a non-empty keyId string");
6470
6967
  }
6471
6968
  const status = await manager.bindIntegrationKey(client, body.keyId.trim());
6472
- return writeJson3(res, 200, { integration: status });
6969
+ return writeJson4(res, 200, { integration: status });
6473
6970
  }
6474
6971
  if (method === "POST" && rest[1] === "plan") {
6475
- const body = await readJsonBody3(req);
6972
+ const body = await readJsonBody4(req);
6476
6973
  const configPath = body.configPath;
6477
6974
  if (configPath !== void 0 && typeof configPath !== "string") {
6478
6975
  return writeJsonError(res, 400, "configPath must be a string");
6479
6976
  }
6480
6977
  const plan = await manager.plan(client, configPath);
6481
- return writeJson3(res, 200, { plan });
6978
+ return writeJson4(res, 200, { plan });
6482
6979
  }
6483
6980
  if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
6484
- const body = await readJsonBody3(req);
6981
+ const body = await readJsonBody4(req);
6485
6982
  const configPath = body.configPath;
6486
6983
  if (configPath !== void 0 && typeof configPath !== "string") {
6487
6984
  return writeJsonError(res, 400, "configPath must be a string");
6488
6985
  }
6489
6986
  const status = await manager.install(client, configPath);
6490
- return writeJson3(res, 200, { integration: status });
6987
+ return writeJson4(res, 200, { integration: status });
6491
6988
  }
6492
6989
  if (method === "POST" && rest[1] === "repair") {
6493
6990
  const status = await manager.repair(client);
6494
- return writeJson3(res, 200, { integration: status });
6991
+ return writeJson4(res, 200, { integration: status });
6495
6992
  }
6496
6993
  if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
6497
6994
  const status = await manager.remove(client);
6498
- return writeJson3(res, 200, { integration: status });
6995
+ return writeJson4(res, 200, { integration: status });
6499
6996
  }
6500
6997
  return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
6501
6998
  } catch (error) {
@@ -6638,7 +7135,7 @@ async function handleImages(res, method, rest, deps) {
6638
7135
  }
6639
7136
  const reader = deps.imageRuntimeStatus;
6640
7137
  if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
6641
- const serverConfig = await loadServerConfig2(deps.settingsStore);
7138
+ const serverConfig = await loadServerConfig3(deps.settingsStore);
6642
7139
  const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
6643
7140
  const lifecycle = reader.status();
6644
7141
  const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
@@ -6658,7 +7155,7 @@ async function handleImages(res, method, rest, deps) {
6658
7155
  httpLeases: safeStatusCount(generation.httpLeases),
6659
7156
  hostedLeases: safeStatusCount(generation.hostedLeases)
6660
7157
  }));
6661
- return writeJson3(res, 200, {
7158
+ return writeJson4(res, 200, {
6662
7159
  configured: {
6663
7160
  enabled: images.enabled,
6664
7161
  provider: images.provider,
@@ -6686,7 +7183,7 @@ async function handleImages(res, method, rest, deps) {
6686
7183
  async function handleStatus(res, method, deps) {
6687
7184
  if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
6688
7185
  const status = deps.outboundApiServer.getStatus();
6689
- const serverConfig = await loadServerConfig2(deps.settingsStore);
7186
+ const serverConfig = await loadServerConfig3(deps.settingsStore);
6690
7187
  const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
6691
7188
  const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => gatewayBindingToEndpointConfig(binding));
6692
7189
  const useSubscription = routes.some((route) => route.useSubscription);
@@ -6724,14 +7221,14 @@ async function handleStatus(res, method, deps) {
6724
7221
  })() : void 0;
6725
7222
  if (status.running) {
6726
7223
  const queueStatus = deps.outboundApiServer.getQueueStatus();
6727
- return writeJson3(res, 200, {
7224
+ return writeJson4(res, 200, {
6728
7225
  ...status,
6729
7226
  endpoints,
6730
7227
  queueStatus,
6731
7228
  ...imageRuntime ? { imageRuntime } : {}
6732
7229
  });
6733
7230
  }
6734
- return writeJson3(res, 200, {
7231
+ return writeJson4(res, 200, {
6735
7232
  ...status,
6736
7233
  endpoints,
6737
7234
  ...imageRuntime ? { imageRuntime } : {}
@@ -6755,18 +7252,18 @@ function resolvePlaygroundPath(endpoint, body) {
6755
7252
  }
6756
7253
  async function handlePlayground(req, res, method, deps) {
6757
7254
  if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
6758
- const body = await readJsonBody3(req);
7255
+ const body = await readJsonBody4(req);
6759
7256
  const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
6760
7257
  const key = typeof body["key"] === "string" ? body["key"] : "";
6761
7258
  const payload = body["body"];
6762
7259
  const status = deps.outboundApiServer.getStatus();
6763
7260
  if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
6764
- const path2 = resolvePlaygroundPath(endpoint, isRecord2(payload) ? payload : {});
7261
+ const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
6765
7262
  if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
6766
7263
  const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
6767
7264
  await proxyToOutbound(res, status.port, path2, key, upstreamBody);
6768
7265
  }
6769
- function isRecord2(v) {
7266
+ function isRecord3(v) {
6770
7267
  return !!v && typeof v === "object" && !Array.isArray(v);
6771
7268
  }
6772
7269
  function proxyToOutbound(res, outboundPort, path2, key, body) {
@@ -6901,7 +7398,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
6901
7398
  }
6902
7399
 
6903
7400
  // src/admin/version.ts
6904
- var DAEMON_VERSION = true ? "0.2.0" : "0.0.0-dev";
7401
+ var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
6905
7402
 
6906
7403
  // src/admin/AdminServer.ts
6907
7404
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -17376,6 +17873,14 @@ function buildDaemon(config, paths) {
17376
17873
  onEvent: (row, at) => usageThroughput.record(row, at)
17377
17874
  });
17378
17875
  const initialImagesConfig = normalizeServerConfig2(decryptedConfig.server).images;
17876
+ const initialSearchConfig = normalizeServerConfig2(decryptedConfig.server).search;
17877
+ for (const issue of validateSearchServerConfig2(
17878
+ decryptedConfig.server?.search
17879
+ )) {
17880
+ logger.warn("[search] ignoring invalid config: " + issue);
17881
+ }
17882
+ const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
17883
+ const searchFrontendModes = initialSearchConfig.modes;
17379
17884
  const imageObservability = new ImageObservability();
17380
17885
  const imageRuntimeObservability = Object.freeze({
17381
17886
  telemetrySink: imageObservability.telemetrySink,
@@ -17505,7 +18010,9 @@ function buildDaemon(config, paths) {
17505
18010
  apiKeyPool,
17506
18011
  usageRecorder,
17507
18012
  openAIOperationRegistry,
17508
- responsesHostedImageIngress
18013
+ responsesHostedImageIngress,
18014
+ searchRuntime,
18015
+ searchFrontendModes
17509
18016
  });
17510
18017
  if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
17511
18018
  throw new Error(
@@ -17570,7 +18077,9 @@ function buildDaemon(config, paths) {
17570
18077
  keySpendTracker,
17571
18078
  // configurable-logging: route the server's OWN lifecycle + relay dispatch-error
17572
18079
  // lines through the injected logger (honors level/format/file sink).
17573
- logger
18080
+ logger,
18081
+ // plan 阶段5: the same instance the managed frontends hold.
18082
+ searchRuntime
17574
18083
  });
17575
18084
  const auditDir = defaultAuditDir(paths.configPath);
17576
18085
  const billingDir = defaultBillingDir(paths.configPath);
@@ -17592,6 +18101,10 @@ function buildDaemon(config, paths) {
17592
18101
  }),
17593
18102
  routeLeaseManager,
17594
18103
  subscriptionAccounts,
18104
+ // search-settings-ui D3: the daemon's ONE search runtime + its
18105
+ // bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
18106
+ // `POST /admin/api/search/test` (501 when a light embedder omits it).
18107
+ searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
17595
18108
  accountAllowanceService,
17596
18109
  allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
17597
18110
  accountProbeService: accountHealthProbeScheduler,
@@ -17728,6 +18241,8 @@ function buildDaemon(config, paths) {
17728
18241
  keyDb,
17729
18242
  settingsStore,
17730
18243
  openAIOperationRegistry,
18244
+ searchRuntime,
18245
+ searchFrontendModes,
17731
18246
  imageRuntimeManager,
17732
18247
  imageObservability,
17733
18248
  imageCleanupService,