@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 +892 -219
- package/dist/cli.js +849 -160
- package/dist/index.cjs +733 -206
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +691 -153
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1139,8 +1139,16 @@ ${turn.responseBody}`);
|
|
|
1139
1139
|
import { parseArgs as parseArgs2 } from "util";
|
|
1140
1140
|
import {
|
|
1141
1141
|
DEFAULT_IMAGES_SERVER_CONFIG as DEFAULT_IMAGES_SERVER_CONFIG2,
|
|
1142
|
-
loadServerConfig as
|
|
1142
|
+
loadServerConfig as loadServerConfig4
|
|
1143
1143
|
} from "@omnicross/core/outbound-api";
|
|
1144
|
+
import {
|
|
1145
|
+
apiSearchContributions as apiSearchContributions3
|
|
1146
|
+
} from "@omnicross/core/search/api";
|
|
1147
|
+
import { builtinHttpSearchContributions as builtinHttpSearchContributions4 } from "@omnicross/core/search/http";
|
|
1148
|
+
import {
|
|
1149
|
+
DEFAULT_SEARCH_FRONTEND_MODES,
|
|
1150
|
+
SEARCH_FRONTEND_NAMES
|
|
1151
|
+
} from "@omnicross/core/search";
|
|
1144
1152
|
|
|
1145
1153
|
// src/bootstrap.ts
|
|
1146
1154
|
import { accessSync, constants as fsConstants, existsSync as existsSync29, mkdirSync as mkdirSync9 } from "fs";
|
|
@@ -1155,7 +1163,8 @@ import {
|
|
|
1155
1163
|
DEFAULT_ACCOUNT_PROBE,
|
|
1156
1164
|
DEFAULT_OUTBOUND_PORT,
|
|
1157
1165
|
getOutboundApiServer,
|
|
1158
|
-
normalizeServerConfig as normalizeServerConfig2
|
|
1166
|
+
normalizeServerConfig as normalizeServerConfig2,
|
|
1167
|
+
validateSearchServerConfig as validateSearchServerConfig2
|
|
1159
1168
|
} from "@omnicross/core/outbound-api";
|
|
1160
1169
|
import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api/subscriptionRegistryPort";
|
|
1161
1170
|
import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
@@ -2012,14 +2021,16 @@ import http from "http";
|
|
|
2012
2021
|
import {
|
|
2013
2022
|
createNamedKey,
|
|
2014
2023
|
DEFAULT_IMAGES_SERVER_CONFIG,
|
|
2024
|
+
DEFAULT_SEARCH_SERVER_CONFIG as DEFAULT_SEARCH_SERVER_CONFIG2,
|
|
2015
2025
|
effectiveOutboundPermissions as effectiveOutboundPermissions2,
|
|
2016
2026
|
gatewayBindingToEndpointConfig,
|
|
2017
2027
|
isKindMappedEndpoint,
|
|
2018
|
-
loadServerConfig as
|
|
2028
|
+
loadServerConfig as loadServerConfig3,
|
|
2019
2029
|
mergeServerConfig,
|
|
2020
2030
|
normalizeProxyConfig,
|
|
2021
2031
|
saveServerConfig,
|
|
2022
|
-
validateOutboundPermissions
|
|
2032
|
+
validateOutboundPermissions,
|
|
2033
|
+
validateSearchServerConfig
|
|
2023
2034
|
} from "@omnicross/core/outbound-api";
|
|
2024
2035
|
import {
|
|
2025
2036
|
IMAGE_CAPABILITY_UNAVAILABLE_REASONS
|
|
@@ -4585,6 +4596,510 @@ async function handleDashboard(deps) {
|
|
|
4585
4596
|
return { status: 200, body: summary };
|
|
4586
4597
|
}
|
|
4587
4598
|
|
|
4599
|
+
// src/admin/searchAdminApi.ts
|
|
4600
|
+
import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
|
|
4601
|
+
import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
|
|
4602
|
+
import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport as createSearchHttpTransport2 } from "@omnicross/core/search/http";
|
|
4603
|
+
import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
|
|
4604
|
+
|
|
4605
|
+
// src/search/searchDoctorProjection.ts
|
|
4606
|
+
import { toSearchErrorShape } from "@omnicross/contracts/search-types";
|
|
4607
|
+
import {
|
|
4608
|
+
JINA_CAPABILITIES,
|
|
4609
|
+
SEARXNG_CAPABILITIES,
|
|
4610
|
+
TAVILY_CAPABILITIES,
|
|
4611
|
+
ZHIPU_CAPABILITIES
|
|
4612
|
+
} from "@omnicross/core/search/api";
|
|
4613
|
+
import { builtinHttpSearchContributions } from "@omnicross/core/search/http";
|
|
4614
|
+
var API_DOCTOR_PROVIDERS = [
|
|
4615
|
+
{
|
|
4616
|
+
id: "tavily",
|
|
4617
|
+
capabilities: TAVILY_CAPABILITIES,
|
|
4618
|
+
configured: (configs) => configs.tavily !== void 0,
|
|
4619
|
+
missingReason: "no API key configured"
|
|
4620
|
+
},
|
|
4621
|
+
{
|
|
4622
|
+
id: "jina",
|
|
4623
|
+
capabilities: JINA_CAPABILITIES,
|
|
4624
|
+
configured: (configs) => configs.jina !== void 0,
|
|
4625
|
+
// Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
|
|
4626
|
+
// asked for is still not enabled.
|
|
4627
|
+
missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
|
|
4628
|
+
},
|
|
4629
|
+
{
|
|
4630
|
+
id: "searxng",
|
|
4631
|
+
capabilities: SEARXNG_CAPABILITIES,
|
|
4632
|
+
configured: (configs) => configs.searxng !== void 0,
|
|
4633
|
+
missingReason: "no API host configured"
|
|
4634
|
+
},
|
|
4635
|
+
{
|
|
4636
|
+
id: "zhipu",
|
|
4637
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
4638
|
+
configured: (configs) => configs.zhipu !== void 0,
|
|
4639
|
+
missingReason: "no API key configured"
|
|
4640
|
+
},
|
|
4641
|
+
{
|
|
4642
|
+
id: "z.ai",
|
|
4643
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
4644
|
+
configured: (configs) => configs["z.ai"] !== void 0,
|
|
4645
|
+
missingReason: "no API key configured"
|
|
4646
|
+
}
|
|
4647
|
+
];
|
|
4648
|
+
function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContributions(), apiConfigs) {
|
|
4649
|
+
const rows = contributions.map((contribution) => ({
|
|
4650
|
+
providerId: contribution.id,
|
|
4651
|
+
source: contribution.source,
|
|
4652
|
+
kind: contribution.kind,
|
|
4653
|
+
capabilities: contribution.capabilities
|
|
4654
|
+
}));
|
|
4655
|
+
if (apiConfigs === void 0) return rows;
|
|
4656
|
+
for (const provider of API_DOCTOR_PROVIDERS) {
|
|
4657
|
+
if (provider.configured(apiConfigs)) continue;
|
|
4658
|
+
rows.push({
|
|
4659
|
+
providerId: provider.id,
|
|
4660
|
+
source: "builtin",
|
|
4661
|
+
kind: "api",
|
|
4662
|
+
capabilities: provider.capabilities,
|
|
4663
|
+
status: "unconfigured",
|
|
4664
|
+
reason: provider.missingReason
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
return rows;
|
|
4668
|
+
}
|
|
4669
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
4670
|
+
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
4671
|
+
if (outcome.kind === "results") {
|
|
4672
|
+
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
4673
|
+
return {
|
|
4674
|
+
providerId,
|
|
4675
|
+
status: "degraded",
|
|
4676
|
+
checkedAt,
|
|
4677
|
+
reason: "reachable, but the engine returned no usable results (possible partial drift)"
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
const error = toSearchErrorShape(outcome.error);
|
|
4681
|
+
const stage = error.details?.stage;
|
|
4682
|
+
const { status, reason } = classifySearchFailure(stage, error.code);
|
|
4683
|
+
return { providerId, status, checkedAt, reason, error };
|
|
4684
|
+
}
|
|
4685
|
+
function classifySearchFailure(stage, code) {
|
|
4686
|
+
if (stage === "challenge") {
|
|
4687
|
+
return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
|
|
4688
|
+
}
|
|
4689
|
+
if (stage === "trust") {
|
|
4690
|
+
return {
|
|
4691
|
+
status: "blocked",
|
|
4692
|
+
reason: "the engine served a page that failed the anti-decoy trust check"
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
if (code === "policy_denied") {
|
|
4696
|
+
return {
|
|
4697
|
+
status: "blocked",
|
|
4698
|
+
reason: "the egress policy refused the request target"
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
if (code === "parse_failed") {
|
|
4702
|
+
return {
|
|
4703
|
+
status: "failed",
|
|
4704
|
+
reason: "the response was not recognizable as a search result page (parser drift suspected)"
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
if (code === "timeout") {
|
|
4708
|
+
return { status: "failed", reason: "the request exceeded its time budget" };
|
|
4709
|
+
}
|
|
4710
|
+
return { status: "failed", reason: `the request failed (${code})` };
|
|
4711
|
+
}
|
|
4712
|
+
async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4713
|
+
const diagnostics = [];
|
|
4714
|
+
for (const contribution of contributions) {
|
|
4715
|
+
try {
|
|
4716
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4717
|
+
diagnostics.push(
|
|
4718
|
+
classifyLiveSearchOutcome(contribution.id, { kind: "results", count: results.length }, now())
|
|
4719
|
+
);
|
|
4720
|
+
} catch (error) {
|
|
4721
|
+
diagnostics.push(classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, now()));
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
return diagnostics;
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
// src/search/SearchAssembly.ts
|
|
4728
|
+
import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
|
|
4729
|
+
import { createSearchRuntime } from "@omnicross/core/search";
|
|
4730
|
+
import { apiSearchContributions } from "@omnicross/core/search/api";
|
|
4731
|
+
import {
|
|
4732
|
+
builtinHttpSearchContributions as builtinHttpSearchContributions2,
|
|
4733
|
+
createSearchHttpTransport
|
|
4734
|
+
} from "@omnicross/core/search/http";
|
|
4735
|
+
function searchEgressPolicyFrom(config) {
|
|
4736
|
+
const hosts = config.egress.allowedPrivateHosts;
|
|
4737
|
+
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
4738
|
+
}
|
|
4739
|
+
function searchPolicyFrom(config) {
|
|
4740
|
+
const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
|
|
4741
|
+
return {
|
|
4742
|
+
...preferred !== void 0 ? { preferred } : {},
|
|
4743
|
+
...allowed !== void 0 ? { allowed: [...allowed] } : {},
|
|
4744
|
+
fallbackEnabled,
|
|
4745
|
+
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4748
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
4749
|
+
return resolveUpstreamDispatcher({ url });
|
|
4750
|
+
}
|
|
4751
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
4752
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
4753
|
+
return searchUpstreamProxyConfig({ url });
|
|
4754
|
+
}
|
|
4755
|
+
function searchContributionsFrom(config) {
|
|
4756
|
+
return [
|
|
4757
|
+
...builtinHttpSearchContributions2(
|
|
4758
|
+
createSearchHttpTransport({
|
|
4759
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
4760
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
4761
|
+
})
|
|
4762
|
+
),
|
|
4763
|
+
...apiSearchContributions(config.providers, {
|
|
4764
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
4765
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
4766
|
+
})
|
|
4767
|
+
];
|
|
4768
|
+
}
|
|
4769
|
+
function buildSearchRuntime(config, options = {}) {
|
|
4770
|
+
const logger = options.logger ?? null;
|
|
4771
|
+
return createSearchRuntime({
|
|
4772
|
+
contributions: options.contributions ?? searchContributionsFrom(config),
|
|
4773
|
+
policy: searchPolicyFrom(config),
|
|
4774
|
+
...logger ? {
|
|
4775
|
+
onEvent: (event) => {
|
|
4776
|
+
logger.debug(`[search] ${formatSearchEvent(event)}`);
|
|
4777
|
+
}
|
|
4778
|
+
} : {}
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
function formatSearchEvent(event) {
|
|
4782
|
+
const parts = [
|
|
4783
|
+
`type=${event.type}`,
|
|
4784
|
+
`request=${event.requestId}`,
|
|
4785
|
+
`queryHash=${event.queryHash}`,
|
|
4786
|
+
`durationMs=${event.durationMs}`
|
|
4787
|
+
];
|
|
4788
|
+
if ("providerId" in event && event.providerId !== void 0) {
|
|
4789
|
+
parts.push(`provider=${event.providerId}`);
|
|
4790
|
+
}
|
|
4791
|
+
if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
|
|
4792
|
+
if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
|
|
4793
|
+
if ("resultCount" in event && event.resultCount !== void 0) {
|
|
4794
|
+
parts.push(`results=${event.resultCount}`);
|
|
4795
|
+
}
|
|
4796
|
+
if ("fallbackCount" in event && event.fallbackCount !== void 0) {
|
|
4797
|
+
parts.push(`fallbacks=${event.fallbackCount}`);
|
|
4798
|
+
}
|
|
4799
|
+
return parts.join(" ");
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/admin/searchAdminApi.ts
|
|
4803
|
+
var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
|
|
4804
|
+
var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
4805
|
+
"tavily",
|
|
4806
|
+
"jina",
|
|
4807
|
+
"searxng",
|
|
4808
|
+
"zhipu",
|
|
4809
|
+
"z.ai"
|
|
4810
|
+
]);
|
|
4811
|
+
var SEARCH_QUERY_MAX_CODE_UNITS = 256;
|
|
4812
|
+
var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
4813
|
+
var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
|
|
4814
|
+
var SEARCH_QUERY_MAX_RESULTS = 5;
|
|
4815
|
+
function sanitizeResultField(value, cap) {
|
|
4816
|
+
const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
|
|
4817
|
+
return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
|
|
4818
|
+
}
|
|
4819
|
+
function writeJson(res, status, body) {
|
|
4820
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4821
|
+
res.end(JSON.stringify(body));
|
|
4822
|
+
}
|
|
4823
|
+
function writeErr(res, status, message) {
|
|
4824
|
+
writeJson(res, status, { error: { type: "admin_api_error", message } });
|
|
4825
|
+
}
|
|
4826
|
+
var SEARCH_MAX_BODY_BYTES = 64 * 1024;
|
|
4827
|
+
var SearchBodyTooLargeError = class extends Error {
|
|
4828
|
+
};
|
|
4829
|
+
async function readJsonBody2(req) {
|
|
4830
|
+
const chunks = [];
|
|
4831
|
+
let bytes = 0;
|
|
4832
|
+
for await (const chunk of req) {
|
|
4833
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4834
|
+
bytes += buffer.length;
|
|
4835
|
+
if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
|
|
4836
|
+
chunks.push(buffer);
|
|
4837
|
+
}
|
|
4838
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
4839
|
+
if (!raw.trim()) return {};
|
|
4840
|
+
try {
|
|
4841
|
+
const parsed = JSON.parse(raw);
|
|
4842
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4843
|
+
} catch {
|
|
4844
|
+
return {};
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
async function readBodyOrReject(req, res) {
|
|
4848
|
+
try {
|
|
4849
|
+
return await readJsonBody2(req);
|
|
4850
|
+
} catch (error) {
|
|
4851
|
+
if (error instanceof SearchBodyTooLargeError) {
|
|
4852
|
+
writeErr(res, 400, error.message);
|
|
4853
|
+
return void 0;
|
|
4854
|
+
}
|
|
4855
|
+
throw error;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
async function handleSearchAdmin(req, res, method, rest, deps) {
|
|
4859
|
+
if (rest.length === 1 && rest[0] === "diagnostics") {
|
|
4860
|
+
if (!deps.searchStatus) {
|
|
4861
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4862
|
+
}
|
|
4863
|
+
if (method !== "GET") {
|
|
4864
|
+
return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
|
|
4865
|
+
}
|
|
4866
|
+
return handleSearchDiagnostics(res, deps);
|
|
4867
|
+
}
|
|
4868
|
+
if (rest.length === 1 && rest[0] === "test") {
|
|
4869
|
+
if (!deps.searchStatus) {
|
|
4870
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4871
|
+
}
|
|
4872
|
+
if (method !== "POST") {
|
|
4873
|
+
return writeErr(res, 405, `method ${method} not allowed on search test`);
|
|
4874
|
+
}
|
|
4875
|
+
return handleSearchTest(req, res, deps);
|
|
4876
|
+
}
|
|
4877
|
+
if (rest.length === 1 && rest[0] === "query") {
|
|
4878
|
+
if (!deps.searchStatus) {
|
|
4879
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4880
|
+
}
|
|
4881
|
+
if (method !== "POST") {
|
|
4882
|
+
return writeErr(res, 405, `method ${method} not allowed on search query`);
|
|
4883
|
+
}
|
|
4884
|
+
return handleSearchQuery(req, res, deps);
|
|
4885
|
+
}
|
|
4886
|
+
return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
|
|
4887
|
+
}
|
|
4888
|
+
async function handleSearchDiagnostics(res, deps) {
|
|
4889
|
+
const status = deps.searchStatus;
|
|
4890
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4891
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4892
|
+
const rows = buildSearchDoctorSnapshot(
|
|
4893
|
+
status.runtime.listProviders(),
|
|
4894
|
+
search.providers
|
|
4895
|
+
);
|
|
4896
|
+
const snapshot = {
|
|
4897
|
+
rows,
|
|
4898
|
+
modes: {
|
|
4899
|
+
// codex is read from the LIVE config per request — an admin PUT has
|
|
4900
|
+
// already applied. responses/anthropic were captured at bootstrap.
|
|
4901
|
+
codex: search.modes.codex,
|
|
4902
|
+
responses: status.modes.responses,
|
|
4903
|
+
anthropic: status.modes.anthropic
|
|
4904
|
+
},
|
|
4905
|
+
applySemantics: { codex: "immediate", rest: "restart" }
|
|
4906
|
+
};
|
|
4907
|
+
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4908
|
+
}
|
|
4909
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
4910
|
+
if (fetchImpl) {
|
|
4911
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4912
|
+
return [
|
|
4913
|
+
...builtinHttpSearchContributions3(
|
|
4914
|
+
createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
|
|
4915
|
+
),
|
|
4916
|
+
...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
|
|
4917
|
+
];
|
|
4918
|
+
}
|
|
4919
|
+
return searchContributionsFrom(search);
|
|
4920
|
+
}
|
|
4921
|
+
async function handleSearchTest(req, res, deps) {
|
|
4922
|
+
const status = deps.searchStatus;
|
|
4923
|
+
const body = await readBodyOrReject(req, res);
|
|
4924
|
+
if (body === void 0) return;
|
|
4925
|
+
const providerId = body["providerId"];
|
|
4926
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4927
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4928
|
+
}
|
|
4929
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4930
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4931
|
+
}
|
|
4932
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4933
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4934
|
+
const providers = search.providers;
|
|
4935
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4936
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4937
|
+
}
|
|
4938
|
+
const fetchImpl = status.testFetch;
|
|
4939
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4940
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4941
|
+
if (!contribution) {
|
|
4942
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4943
|
+
}
|
|
4944
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4945
|
+
try {
|
|
4946
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4947
|
+
const diagnostic = classifyLiveSearchOutcome(
|
|
4948
|
+
contribution.id,
|
|
4949
|
+
{ kind: "results", count: results.length },
|
|
4950
|
+
checkedAt
|
|
4951
|
+
);
|
|
4952
|
+
const response = { diagnostic, resultCount: results.length };
|
|
4953
|
+
return writeJson(res, 200, { result: response });
|
|
4954
|
+
} catch (error) {
|
|
4955
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
4956
|
+
const response = { diagnostic };
|
|
4957
|
+
return writeJson(res, 200, { result: response });
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
async function handleSearchQuery(req, res, deps) {
|
|
4961
|
+
const status = deps.searchStatus;
|
|
4962
|
+
const body = await readBodyOrReject(req, res);
|
|
4963
|
+
if (body === void 0) return;
|
|
4964
|
+
const providerId = body["providerId"];
|
|
4965
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4966
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4967
|
+
}
|
|
4968
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4969
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4970
|
+
}
|
|
4971
|
+
const query2 = body["query"];
|
|
4972
|
+
if (typeof query2 !== "string" || query2.trim().length === 0) {
|
|
4973
|
+
return writeErr(res, 400, "query must be a non-empty string");
|
|
4974
|
+
}
|
|
4975
|
+
if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
|
|
4976
|
+
return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
|
|
4977
|
+
}
|
|
4978
|
+
if (QUERY_CONTROL_CHARS.test(query2)) {
|
|
4979
|
+
return writeErr(res, 400, "query must not contain control characters");
|
|
4980
|
+
}
|
|
4981
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4982
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4983
|
+
const providers = search.providers;
|
|
4984
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4985
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4986
|
+
}
|
|
4987
|
+
const fetchImpl = status.testFetch;
|
|
4988
|
+
const runtime = createSearchRuntime2({
|
|
4989
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
4990
|
+
policy: {
|
|
4991
|
+
...searchPolicyFrom(search),
|
|
4992
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
4993
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
4994
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
4995
|
+
fallbackEnabled: true,
|
|
4996
|
+
preferred: providerId
|
|
4997
|
+
}
|
|
4998
|
+
});
|
|
4999
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5000
|
+
try {
|
|
5001
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
5002
|
+
const results = orchestrated.results;
|
|
5003
|
+
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
5004
|
+
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
5005
|
+
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
5006
|
+
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
5007
|
+
}));
|
|
5008
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
5009
|
+
orchestrated.providerId,
|
|
5010
|
+
{ kind: "results", count: sanitized.length },
|
|
5011
|
+
checkedAt
|
|
5012
|
+
);
|
|
5013
|
+
const response = {
|
|
5014
|
+
diagnostic,
|
|
5015
|
+
providerUsed: orchestrated.providerId,
|
|
5016
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
5017
|
+
resultCount: sanitized.length,
|
|
5018
|
+
results: sanitized
|
|
5019
|
+
};
|
|
5020
|
+
return writeJson(res, 200, { result: response });
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
5023
|
+
const response = { diagnostic };
|
|
5024
|
+
return writeJson(res, 200, { result: response });
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
// src/admin/searchAdminView.ts
|
|
5029
|
+
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
5030
|
+
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
5031
|
+
function isRecord(value) {
|
|
5032
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5033
|
+
}
|
|
5034
|
+
function redactSearchServerConfig(search) {
|
|
5035
|
+
const providers = {};
|
|
5036
|
+
for (const [id, raw] of Object.entries(search.providers)) {
|
|
5037
|
+
const entry = raw;
|
|
5038
|
+
const view = {};
|
|
5039
|
+
if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
|
|
5040
|
+
if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
|
|
5041
|
+
if (API_KEY_PROVIDERS.has(id)) {
|
|
5042
|
+
view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
|
|
5043
|
+
}
|
|
5044
|
+
if (BASIC_AUTH_PROVIDERS.has(id)) {
|
|
5045
|
+
view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
|
|
5046
|
+
}
|
|
5047
|
+
providers[id] = view;
|
|
5048
|
+
}
|
|
5049
|
+
return {
|
|
5050
|
+
modes: search.modes,
|
|
5051
|
+
providers,
|
|
5052
|
+
egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
|
|
5053
|
+
policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
|
|
5054
|
+
};
|
|
5055
|
+
}
|
|
5056
|
+
function storedSecrets(current, id) {
|
|
5057
|
+
const entry = current.providers[id];
|
|
5058
|
+
if (!entry) return {};
|
|
5059
|
+
const out = {};
|
|
5060
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
|
|
5061
|
+
if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
|
|
5062
|
+
out.basicAuthPassword = entry.basicAuthPassword;
|
|
5063
|
+
}
|
|
5064
|
+
return out;
|
|
5065
|
+
}
|
|
5066
|
+
function resolveSecretField(entry, field, stored) {
|
|
5067
|
+
if (!(field in entry)) {
|
|
5068
|
+
if (stored !== void 0) entry[field] = stored;
|
|
5069
|
+
return;
|
|
5070
|
+
}
|
|
5071
|
+
const value = entry[field];
|
|
5072
|
+
if (value === null) {
|
|
5073
|
+
delete entry[field];
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
if (typeof value === "string" && value.trim().length > 0) return;
|
|
5077
|
+
if (stored !== void 0) entry[field] = stored;
|
|
5078
|
+
else delete entry[field];
|
|
5079
|
+
}
|
|
5080
|
+
function preserveSearchSecrets(incoming, current) {
|
|
5081
|
+
if (!isRecord(incoming)) return incoming;
|
|
5082
|
+
const section = { ...incoming };
|
|
5083
|
+
const providersValue = section["providers"];
|
|
5084
|
+
if (!isRecord(providersValue)) return section;
|
|
5085
|
+
const providers = {};
|
|
5086
|
+
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
5087
|
+
if (!isRecord(entryValue)) {
|
|
5088
|
+
providers[id] = entryValue;
|
|
5089
|
+
continue;
|
|
5090
|
+
}
|
|
5091
|
+
const entry = { ...entryValue };
|
|
5092
|
+
delete entry["apiKeyConfigured"];
|
|
5093
|
+
delete entry["basicAuthPasswordConfigured"];
|
|
5094
|
+
const stored = storedSecrets(current, id);
|
|
5095
|
+
resolveSecretField(entry, "apiKey", stored.apiKey);
|
|
5096
|
+
resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
|
|
5097
|
+
providers[id] = entry;
|
|
5098
|
+
}
|
|
5099
|
+
section["providers"] = providers;
|
|
5100
|
+
return section;
|
|
5101
|
+
}
|
|
5102
|
+
|
|
4588
5103
|
// src/admin/keyPolicyBody.ts
|
|
4589
5104
|
function parseKeyPolicyBody(body) {
|
|
4590
5105
|
const policy = {};
|
|
@@ -4647,7 +5162,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4647
5162
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4648
5163
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4649
5164
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4650
|
-
function
|
|
5165
|
+
function isRecord2(value) {
|
|
4651
5166
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4652
5167
|
}
|
|
4653
5168
|
function nonBlank(value) {
|
|
@@ -4667,7 +5182,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4667
5182
|
const ids = /* @__PURE__ */ new Set();
|
|
4668
5183
|
raw.forEach((entry, index) => {
|
|
4669
5184
|
const path2 = `bindings[${index}]`;
|
|
4670
|
-
if (!
|
|
5185
|
+
if (!isRecord2(entry)) {
|
|
4671
5186
|
errors.push(`${path2} must be an object`);
|
|
4672
5187
|
return;
|
|
4673
5188
|
}
|
|
@@ -4696,12 +5211,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4696
5211
|
} else if (entry.modelMappings.length > 100) {
|
|
4697
5212
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4698
5213
|
} else if (entry.modelMappings.some(
|
|
4699
|
-
(mapping) => !
|
|
5214
|
+
(mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4700
5215
|
)) {
|
|
4701
5216
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4702
5217
|
}
|
|
4703
5218
|
}
|
|
4704
|
-
if (!
|
|
5219
|
+
if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4705
5220
|
errors.push(`${path2}.target is invalid`);
|
|
4706
5221
|
} else {
|
|
4707
5222
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4716,7 +5231,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4716
5231
|
}
|
|
4717
5232
|
}
|
|
4718
5233
|
if (entry.modelMap !== void 0) {
|
|
4719
|
-
if (!
|
|
5234
|
+
if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4720
5235
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4721
5236
|
}
|
|
4722
5237
|
}
|
|
@@ -4737,19 +5252,19 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4737
5252
|
import {
|
|
4738
5253
|
generateVoucherCode,
|
|
4739
5254
|
hashVoucherCode,
|
|
4740
|
-
loadServerConfig,
|
|
5255
|
+
loadServerConfig as loadServerConfig2,
|
|
4741
5256
|
newVoucherId,
|
|
4742
5257
|
toVoucherInfo,
|
|
4743
5258
|
voucherCodePrefix
|
|
4744
5259
|
} from "@omnicross/core/outbound-api";
|
|
4745
|
-
function
|
|
5260
|
+
function writeJson2(res, status, body) {
|
|
4746
5261
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4747
5262
|
res.end(JSON.stringify(body));
|
|
4748
5263
|
}
|
|
4749
|
-
function
|
|
4750
|
-
|
|
5264
|
+
function writeErr2(res, status, message) {
|
|
5265
|
+
writeJson2(res, status, { error: { type: "voucher_error", message } });
|
|
4751
5266
|
}
|
|
4752
|
-
function
|
|
5267
|
+
function readJsonBody3(req) {
|
|
4753
5268
|
return new Promise((resolve11, reject) => {
|
|
4754
5269
|
const chunks = [];
|
|
4755
5270
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -4800,26 +5315,26 @@ function parseVoucherCreateBody(body) {
|
|
|
4800
5315
|
return { ok: true, input };
|
|
4801
5316
|
}
|
|
4802
5317
|
async function voucherEnabled(deps) {
|
|
4803
|
-
const config = await
|
|
5318
|
+
const config = await loadServerConfig2(deps.settingsStore);
|
|
4804
5319
|
return config.voucher?.enabled === true;
|
|
4805
5320
|
}
|
|
4806
5321
|
async function handleVoucher(req, res, method, rest, deps) {
|
|
4807
5322
|
const voucherDb = deps.voucherDb;
|
|
4808
|
-
if (!voucherDb) return
|
|
5323
|
+
if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
|
|
4809
5324
|
if (method === "GET" && rest.length === 0) {
|
|
4810
5325
|
const rows = await voucherDb.voucherList();
|
|
4811
|
-
return
|
|
5326
|
+
return writeJson2(res, 200, { vouchers: rows.map(toVoucherInfo) });
|
|
4812
5327
|
}
|
|
4813
5328
|
if (method === "POST" && rest.length === 0) {
|
|
4814
|
-
if (!await voucherEnabled(deps)) return
|
|
5329
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4815
5330
|
let body;
|
|
4816
5331
|
try {
|
|
4817
|
-
body = await
|
|
5332
|
+
body = await readJsonBody3(req);
|
|
4818
5333
|
} catch {
|
|
4819
|
-
return
|
|
5334
|
+
return writeErr2(res, 400, "Invalid JSON in request body");
|
|
4820
5335
|
}
|
|
4821
5336
|
const parsed = parseVoucherCreateBody(body);
|
|
4822
|
-
if (!parsed.ok) return
|
|
5337
|
+
if (!parsed.ok) return writeErr2(res, 400, parsed.message);
|
|
4823
5338
|
const code = generateVoucherCode();
|
|
4824
5339
|
const created = await voucherDb.voucherCreate({
|
|
4825
5340
|
id: newVoucherId(),
|
|
@@ -4827,7 +5342,7 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4827
5342
|
codePrefix: voucherCodePrefix(code),
|
|
4828
5343
|
...parsed.input
|
|
4829
5344
|
});
|
|
4830
|
-
return
|
|
5345
|
+
return writeJson2(res, 201, {
|
|
4831
5346
|
id: created.id,
|
|
4832
5347
|
codePrefix: created.codePrefix,
|
|
4833
5348
|
type: created.type,
|
|
@@ -4838,11 +5353,11 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4838
5353
|
}
|
|
4839
5354
|
const id = rest[0];
|
|
4840
5355
|
if (method === "POST" && id && rest[1] === "revoke") {
|
|
4841
|
-
if (!await voucherEnabled(deps)) return
|
|
5356
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4842
5357
|
const ok = await voucherDb.voucherRevokeCas(id, Date.now());
|
|
4843
|
-
return
|
|
5358
|
+
return writeJson2(res, ok ? 200 : 409, { ok });
|
|
4844
5359
|
}
|
|
4845
|
-
return
|
|
5360
|
+
return writeErr2(res, 405, `method ${method} not allowed on voucher`);
|
|
4846
5361
|
}
|
|
4847
5362
|
|
|
4848
5363
|
// src/admin/webhookConfigBody.ts
|
|
@@ -5756,12 +6271,12 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5756
6271
|
}
|
|
5757
6272
|
|
|
5758
6273
|
// src/admin/accountAllowanceApi.ts
|
|
5759
|
-
function
|
|
6274
|
+
function writeJson3(res, status, body) {
|
|
5760
6275
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5761
6276
|
res.end(JSON.stringify(body));
|
|
5762
6277
|
}
|
|
5763
6278
|
function writeError2(res, status, message) {
|
|
5764
|
-
|
|
6279
|
+
writeJson3(res, status, { error: { type: "account_allowance_error", message } });
|
|
5765
6280
|
}
|
|
5766
6281
|
function readJson2(req) {
|
|
5767
6282
|
return new Promise((resolve11, reject) => {
|
|
@@ -5794,7 +6309,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5794
6309
|
if (!service.getSchedulingStatus) {
|
|
5795
6310
|
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
5796
6311
|
}
|
|
5797
|
-
return
|
|
6312
|
+
return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
5798
6313
|
}
|
|
5799
6314
|
if (method === "GET") {
|
|
5800
6315
|
const params = query(req);
|
|
@@ -5803,7 +6318,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5803
6318
|
if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
|
|
5804
6319
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5805
6320
|
const allowances = await service.list({ providerId, accountId });
|
|
5806
|
-
return
|
|
6321
|
+
return writeJson3(res, 200, { allowances });
|
|
5807
6322
|
}
|
|
5808
6323
|
if (method === "POST" && rest[0] === "refresh") {
|
|
5809
6324
|
const body = await readJson2(req);
|
|
@@ -5818,7 +6333,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5818
6333
|
if (accountId && allowances.length === 0) {
|
|
5819
6334
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
5820
6335
|
}
|
|
5821
|
-
return
|
|
6336
|
+
return writeJson3(res, 200, { allowances });
|
|
5822
6337
|
}
|
|
5823
6338
|
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
5824
6339
|
}
|
|
@@ -5837,7 +6352,7 @@ function readBody(req) {
|
|
|
5837
6352
|
req.on("error", reject);
|
|
5838
6353
|
});
|
|
5839
6354
|
}
|
|
5840
|
-
async function
|
|
6355
|
+
async function readJsonBody4(req) {
|
|
5841
6356
|
const raw = await readBody(req);
|
|
5842
6357
|
if (!raw.trim()) return {};
|
|
5843
6358
|
try {
|
|
@@ -5847,12 +6362,12 @@ async function readJsonBody3(req) {
|
|
|
5847
6362
|
return {};
|
|
5848
6363
|
}
|
|
5849
6364
|
}
|
|
5850
|
-
function
|
|
6365
|
+
function writeJson4(res, status, body) {
|
|
5851
6366
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5852
6367
|
res.end(JSON.stringify(body));
|
|
5853
6368
|
}
|
|
5854
6369
|
function writeJsonError(res, status, message) {
|
|
5855
|
-
|
|
6370
|
+
writeJson4(res, status, { error: { type: "admin_api_error", message } });
|
|
5856
6371
|
}
|
|
5857
6372
|
function maskProviderApiKey(apiKey) {
|
|
5858
6373
|
if (!apiKey) return "";
|
|
@@ -5959,6 +6474,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5959
6474
|
return await handleServer(req, res, method, deps);
|
|
5960
6475
|
case "images":
|
|
5961
6476
|
return await handleImages(res, method, rest, deps);
|
|
6477
|
+
case "search":
|
|
6478
|
+
return await handleSearchAdmin(req, res, method, rest, deps);
|
|
5962
6479
|
case "accounts":
|
|
5963
6480
|
return await handleAccounts(req, res, method, rest, deps);
|
|
5964
6481
|
case "cli":
|
|
@@ -5992,7 +6509,7 @@ function requestQuery(req) {
|
|
|
5992
6509
|
return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
|
|
5993
6510
|
}
|
|
5994
6511
|
function writeResult(res, result) {
|
|
5995
|
-
|
|
6512
|
+
writeJson4(res, result.status, result.body);
|
|
5996
6513
|
}
|
|
5997
6514
|
async function handleUsage(req, res, method, rest, deps) {
|
|
5998
6515
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
|
|
@@ -6001,13 +6518,13 @@ async function handleUsage(req, res, method, rest, deps) {
|
|
|
6001
6518
|
async function handleDashboardRoute(res, method, deps) {
|
|
6002
6519
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
|
|
6003
6520
|
const result = await handleDashboard(deps);
|
|
6004
|
-
return
|
|
6521
|
+
return writeJson4(res, result.status, result.body);
|
|
6005
6522
|
}
|
|
6006
6523
|
async function handlePricing(req, res, method, rest, deps) {
|
|
6007
6524
|
if (rest.length === 0) {
|
|
6008
6525
|
if (method === "GET") return writeResult(res, await handlePricingList(deps));
|
|
6009
6526
|
if (method === "PUT") {
|
|
6010
|
-
return writeResult(res, await handlePricingUpsert(await
|
|
6527
|
+
return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
|
|
6011
6528
|
}
|
|
6012
6529
|
if (method === "DELETE") {
|
|
6013
6530
|
return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
|
|
@@ -6018,7 +6535,7 @@ async function handlePricing(req, res, method, rest, deps) {
|
|
|
6018
6535
|
return writeResult(res, await handlePricingFetchLatest(deps));
|
|
6019
6536
|
}
|
|
6020
6537
|
if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
|
|
6021
|
-
return writeResult(res, await handlePricingResolveConflicts(await
|
|
6538
|
+
return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
|
|
6022
6539
|
}
|
|
6023
6540
|
return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
|
|
6024
6541
|
}
|
|
@@ -6032,15 +6549,15 @@ function migrationDeps(deps) {
|
|
|
6032
6549
|
}
|
|
6033
6550
|
async function handleMigrationExport(req, res, method, deps) {
|
|
6034
6551
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
|
|
6035
|
-
const body = await
|
|
6552
|
+
const body = await readJsonBody4(req);
|
|
6036
6553
|
const result = await handleExport(body, migrationDeps(deps));
|
|
6037
|
-
return
|
|
6554
|
+
return writeJson4(res, result.status, result.body);
|
|
6038
6555
|
}
|
|
6039
6556
|
async function handleMigrationImport(req, res, method, deps) {
|
|
6040
6557
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
|
|
6041
|
-
const body = await
|
|
6558
|
+
const body = await readJsonBody4(req);
|
|
6042
6559
|
const result = await handleImport(body, migrationDeps(deps));
|
|
6043
|
-
return
|
|
6560
|
+
return writeJson4(res, result.status, result.body);
|
|
6044
6561
|
}
|
|
6045
6562
|
async function handleProviders(req, res, method, rest, deps) {
|
|
6046
6563
|
const cfg = loadConfig(deps.configPath);
|
|
@@ -6071,13 +6588,13 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6071
6588
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
|
|
6072
6589
|
const row = cfg.providers.find((p) => p.id === rest[0]);
|
|
6073
6590
|
if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
|
|
6074
|
-
return
|
|
6591
|
+
return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
|
|
6075
6592
|
}
|
|
6076
6593
|
if (method === "GET") {
|
|
6077
|
-
return
|
|
6594
|
+
return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
|
|
6078
6595
|
}
|
|
6079
6596
|
if (method === "POST") {
|
|
6080
|
-
const body = await
|
|
6597
|
+
const body = await readJsonBody4(req);
|
|
6081
6598
|
const provider = parseProviderInput(body, void 0);
|
|
6082
6599
|
if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
|
|
6083
6600
|
if (cfg.providers.some((p) => p.id === provider.id)) {
|
|
@@ -6085,25 +6602,25 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6085
6602
|
}
|
|
6086
6603
|
cfg.providers.push(provider);
|
|
6087
6604
|
persistProviders(cfg, deps);
|
|
6088
|
-
return
|
|
6605
|
+
return writeJson4(res, 201, { provider: toProviderView(provider) });
|
|
6089
6606
|
}
|
|
6090
6607
|
const id = rest[0];
|
|
6091
6608
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6092
6609
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
6093
6610
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6094
6611
|
if (method === "PUT") {
|
|
6095
|
-
const body = await
|
|
6612
|
+
const body = await readJsonBody4(req);
|
|
6096
6613
|
const existing = cfg.providers[idx];
|
|
6097
6614
|
const updated = parseProviderInput(body, existing);
|
|
6098
6615
|
if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
|
|
6099
6616
|
cfg.providers[idx] = updated;
|
|
6100
6617
|
persistProviders(cfg, deps);
|
|
6101
|
-
return
|
|
6618
|
+
return writeJson4(res, 200, { provider: toProviderView(updated) });
|
|
6102
6619
|
}
|
|
6103
6620
|
if (method === "DELETE") {
|
|
6104
6621
|
cfg.providers.splice(idx, 1);
|
|
6105
6622
|
persistProviders(cfg, deps);
|
|
6106
|
-
return
|
|
6623
|
+
return writeJson4(res, 200, { ok: true });
|
|
6107
6624
|
}
|
|
6108
6625
|
return writeJsonError(res, 405, `method ${method} not allowed on providers`);
|
|
6109
6626
|
}
|
|
@@ -6112,7 +6629,7 @@ function persistProviders(cfg, deps) {
|
|
|
6112
6629
|
deps.llmConfig.reload(cfg);
|
|
6113
6630
|
}
|
|
6114
6631
|
async function handleProviderReorder(req, res, cfg, deps) {
|
|
6115
|
-
const body = await
|
|
6632
|
+
const body = await readJsonBody4(req);
|
|
6116
6633
|
const rawOrder = body["order"];
|
|
6117
6634
|
if (!Array.isArray(rawOrder)) {
|
|
6118
6635
|
return writeJsonError(res, 400, "reorder requires { order: string[] }");
|
|
@@ -6136,14 +6653,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
6136
6653
|
}
|
|
6137
6654
|
cfg.providers = reordered;
|
|
6138
6655
|
persistProviders(cfg, deps);
|
|
6139
|
-
return
|
|
6656
|
+
return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
6140
6657
|
}
|
|
6141
6658
|
async function handleDiscoverModels(res, id, cfg) {
|
|
6142
6659
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6143
6660
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6144
6661
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6145
6662
|
if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
|
|
6146
|
-
return
|
|
6663
|
+
return writeJson4(res, 200, { models: [], unsupportedFormat: true });
|
|
6147
6664
|
}
|
|
6148
6665
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6149
6666
|
const base = row.baseUrl.replace(/\/+$/, "");
|
|
@@ -6160,32 +6677,32 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6160
6677
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6161
6678
|
} catch {
|
|
6162
6679
|
}
|
|
6163
|
-
return
|
|
6680
|
+
return writeJson4(res, 200, {
|
|
6164
6681
|
models: [],
|
|
6165
6682
|
error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
|
|
6166
6683
|
});
|
|
6167
6684
|
}
|
|
6168
6685
|
const data = await response.json();
|
|
6169
6686
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6170
|
-
return
|
|
6687
|
+
return writeJson4(res, 200, { models });
|
|
6171
6688
|
} catch (err5) {
|
|
6172
6689
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6173
|
-
return
|
|
6690
|
+
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6174
6691
|
}
|
|
6175
6692
|
}
|
|
6176
6693
|
async function handleTestModel(req, res, id, cfg) {
|
|
6177
6694
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6178
6695
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6179
6696
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6180
|
-
const body = await
|
|
6697
|
+
const body = await readJsonBody4(req);
|
|
6181
6698
|
const model = typeof body["model"] === "string" ? body["model"].trim() : "";
|
|
6182
6699
|
if (!model) return writeJsonError(res, 400, "test requires a { model } string");
|
|
6183
6700
|
if (row.apiFormat === "gemini") {
|
|
6184
|
-
return
|
|
6701
|
+
return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
|
|
6185
6702
|
}
|
|
6186
6703
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6187
6704
|
if (!resolvedKey) {
|
|
6188
|
-
return
|
|
6705
|
+
return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
|
|
6189
6706
|
}
|
|
6190
6707
|
let url = row.baseUrl.replace(/\/+$/, "");
|
|
6191
6708
|
const prompt = "Reply with the single word: OK.";
|
|
@@ -6224,9 +6741,9 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6224
6741
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6225
6742
|
} catch {
|
|
6226
6743
|
}
|
|
6227
|
-
return
|
|
6744
|
+
return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
|
|
6228
6745
|
}
|
|
6229
|
-
return
|
|
6746
|
+
return writeJson4(res, 200, {
|
|
6230
6747
|
ok: true,
|
|
6231
6748
|
status: response.status,
|
|
6232
6749
|
latencyMs,
|
|
@@ -6234,7 +6751,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6234
6751
|
});
|
|
6235
6752
|
} catch (err5) {
|
|
6236
6753
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6237
|
-
return
|
|
6754
|
+
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6238
6755
|
}
|
|
6239
6756
|
}
|
|
6240
6757
|
function extractSampleText(text, apiFormat) {
|
|
@@ -6275,7 +6792,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6275
6792
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6276
6793
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6277
6794
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6278
|
-
return
|
|
6795
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6279
6796
|
}
|
|
6280
6797
|
function parsePoolKeyInput(body, existing) {
|
|
6281
6798
|
const out = {};
|
|
@@ -6294,7 +6811,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6294
6811
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6295
6812
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
6296
6813
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6297
|
-
const body = await
|
|
6814
|
+
const body = await readJsonBody4(req);
|
|
6298
6815
|
const parsed = parsePoolKeyInput(body);
|
|
6299
6816
|
if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
|
|
6300
6817
|
const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -6306,7 +6823,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6306
6823
|
row.apiKeys = [...row.apiKeys ?? [], entry];
|
|
6307
6824
|
persistProviders(cfg, deps);
|
|
6308
6825
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6309
|
-
return
|
|
6826
|
+
return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6310
6827
|
}
|
|
6311
6828
|
async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6312
6829
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6316,7 +6833,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6316
6833
|
const row = cfg.providers[idx];
|
|
6317
6834
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6318
6835
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6319
|
-
const body = await
|
|
6836
|
+
const body = await readJsonBody4(req);
|
|
6320
6837
|
const existing = row.apiKeys[keyIdx];
|
|
6321
6838
|
const parsed = parsePoolKeyInput(body, existing);
|
|
6322
6839
|
const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
|
|
@@ -6326,7 +6843,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6326
6843
|
row.apiKeys[keyIdx] = entry;
|
|
6327
6844
|
persistProviders(cfg, deps);
|
|
6328
6845
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6329
|
-
return
|
|
6846
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6330
6847
|
}
|
|
6331
6848
|
async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
6332
6849
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6340,7 +6857,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
|
6340
6857
|
if (row.apiKeys.length === 0) row.apiKeys = void 0;
|
|
6341
6858
|
persistProviders(cfg, deps);
|
|
6342
6859
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6343
|
-
return
|
|
6860
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6344
6861
|
}
|
|
6345
6862
|
async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6346
6863
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6350,11 +6867,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6350
6867
|
const row = cfg.providers[idx];
|
|
6351
6868
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6352
6869
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6353
|
-
const body = await
|
|
6870
|
+
const body = await readJsonBody4(req);
|
|
6354
6871
|
row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
|
|
6355
6872
|
persistProviders(cfg, deps);
|
|
6356
6873
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6357
|
-
return
|
|
6874
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6358
6875
|
}
|
|
6359
6876
|
function parseApiKeysInput(raw, existing) {
|
|
6360
6877
|
if (!Array.isArray(raw)) return existing;
|
|
@@ -6540,13 +7057,13 @@ function handlePresets(res, method) {
|
|
|
6540
7057
|
website: p.website,
|
|
6541
7058
|
modelsEndpoint: p.modelsEndpoint
|
|
6542
7059
|
}));
|
|
6543
|
-
return
|
|
7060
|
+
return writeJson4(res, 200, { presets, excluded });
|
|
6544
7061
|
}
|
|
6545
7062
|
async function handleKeys(req, res, method, rest, deps) {
|
|
6546
7063
|
if (method === "GET" && rest.length === 0) {
|
|
6547
7064
|
const rows = await deps.keyDb.outboundApiKeysList();
|
|
6548
7065
|
const reader = deps.keySpendReader;
|
|
6549
|
-
if (!reader) return
|
|
7066
|
+
if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
|
|
6550
7067
|
const now = Date.now();
|
|
6551
7068
|
const keys = await Promise.all(
|
|
6552
7069
|
rows.map(async (row) => {
|
|
@@ -6558,13 +7075,13 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6558
7075
|
return info;
|
|
6559
7076
|
})
|
|
6560
7077
|
);
|
|
6561
|
-
return
|
|
7078
|
+
return writeJson4(res, 200, { keys });
|
|
6562
7079
|
}
|
|
6563
7080
|
if (method === "POST" && rest.length === 0) {
|
|
6564
|
-
const body = await
|
|
7081
|
+
const body = await readJsonBody4(req);
|
|
6565
7082
|
const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
|
|
6566
7083
|
const created = await createNamedKey(deps.keyDb, name);
|
|
6567
|
-
return
|
|
7084
|
+
return writeJson4(res, 201, {
|
|
6568
7085
|
id: created.id,
|
|
6569
7086
|
name: created.name,
|
|
6570
7087
|
keyPrefix: created.keyPrefix,
|
|
@@ -6575,7 +7092,7 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6575
7092
|
}
|
|
6576
7093
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
6577
7094
|
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
6578
|
-
if (revealed !== null) return
|
|
7095
|
+
if (revealed !== null) return writeJson4(res, 200, { key: revealed });
|
|
6579
7096
|
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
6580
7097
|
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
6581
7098
|
return writeJsonError(
|
|
@@ -6590,26 +7107,26 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6590
7107
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6591
7108
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6592
7109
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
6593
|
-
return
|
|
7110
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6594
7111
|
}
|
|
6595
7112
|
if (method === "DELETE" && id && !action) {
|
|
6596
7113
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6597
7114
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6598
7115
|
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
6599
|
-
return
|
|
7116
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6600
7117
|
}
|
|
6601
7118
|
if (method === "POST" && id && action === "enabled") {
|
|
6602
|
-
const body = await
|
|
7119
|
+
const body = await readJsonBody4(req);
|
|
6603
7120
|
const enabled = body["enabled"] === true;
|
|
6604
7121
|
if (!enabled) {
|
|
6605
7122
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6606
7123
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6607
7124
|
}
|
|
6608
7125
|
const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
|
|
6609
|
-
return
|
|
7126
|
+
return writeJson4(res, ok ? 200 : 404, { ok, enabled });
|
|
6610
7127
|
}
|
|
6611
7128
|
if (method === "POST" && id && action === "permissions") {
|
|
6612
|
-
const body = await
|
|
7129
|
+
const body = await readJsonBody4(req);
|
|
6613
7130
|
if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
|
|
6614
7131
|
return writeJsonError(res, 400, "body must contain only permissions");
|
|
6615
7132
|
}
|
|
@@ -6624,19 +7141,19 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6624
7141
|
);
|
|
6625
7142
|
}
|
|
6626
7143
|
const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6627
|
-
if (!before) return
|
|
6628
|
-
if (before.revokedAt !== null) return
|
|
7144
|
+
if (!before) return writeJson4(res, 404, { ok: false });
|
|
7145
|
+
if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
|
|
6629
7146
|
const required = await integrationKeyRequirement(deps, id, permissions);
|
|
6630
7147
|
if (required) return writeJsonError(res, 409, required);
|
|
6631
7148
|
const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
|
|
6632
7149
|
if (!ok) {
|
|
6633
7150
|
const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6634
|
-
return
|
|
7151
|
+
return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
|
|
6635
7152
|
}
|
|
6636
|
-
return
|
|
7153
|
+
return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
|
|
6637
7154
|
}
|
|
6638
7155
|
if (method === "POST" && id && action === "max-concurrency") {
|
|
6639
|
-
const body = await
|
|
7156
|
+
const body = await readJsonBody4(req);
|
|
6640
7157
|
const raw = body["maxConcurrency"];
|
|
6641
7158
|
let value;
|
|
6642
7159
|
if (raw === null) {
|
|
@@ -6651,14 +7168,14 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6651
7168
|
);
|
|
6652
7169
|
}
|
|
6653
7170
|
const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
|
|
6654
|
-
return
|
|
7171
|
+
return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
|
|
6655
7172
|
}
|
|
6656
7173
|
if (method === "POST" && id && action === "policy") {
|
|
6657
|
-
const body = await
|
|
7174
|
+
const body = await readJsonBody4(req);
|
|
6658
7175
|
const parsed = parseKeyPolicyBody(body);
|
|
6659
7176
|
if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
|
|
6660
7177
|
const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
|
|
6661
|
-
return
|
|
7178
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6662
7179
|
}
|
|
6663
7180
|
return writeJsonError(res, 405, `method ${method} not allowed on keys`);
|
|
6664
7181
|
}
|
|
@@ -6749,7 +7266,8 @@ function outboundServerConfigInput(config) {
|
|
|
6749
7266
|
userMessageQueue: config.userMessageQueue,
|
|
6750
7267
|
concurrencyQueue: config.concurrencyQueue,
|
|
6751
7268
|
voucher: config.voucher,
|
|
6752
|
-
anthropic: config.anthropic
|
|
7269
|
+
anthropic: config.anthropic,
|
|
7270
|
+
search: config.search
|
|
6753
7271
|
};
|
|
6754
7272
|
}
|
|
6755
7273
|
function projectImagesConfigForAdmin(config) {
|
|
@@ -6812,15 +7330,21 @@ function currentImageGenerationId(deps) {
|
|
|
6812
7330
|
}
|
|
6813
7331
|
async function handleServer(req, res, method, deps) {
|
|
6814
7332
|
if (method === "GET") {
|
|
6815
|
-
const config = await
|
|
7333
|
+
const config = await loadServerConfig3(deps.settingsStore);
|
|
6816
7334
|
let server = config;
|
|
6817
7335
|
if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
|
|
6818
7336
|
if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
|
|
6819
7337
|
if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
|
|
6820
|
-
|
|
7338
|
+
if (config.search) {
|
|
7339
|
+
server = {
|
|
7340
|
+
...server,
|
|
7341
|
+
search: redactSearchServerConfig(config.search)
|
|
7342
|
+
};
|
|
7343
|
+
}
|
|
7344
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
|
|
6821
7345
|
}
|
|
6822
7346
|
if (method === "PUT") {
|
|
6823
|
-
const patch = await
|
|
7347
|
+
const patch = await readJsonBody4(req);
|
|
6824
7348
|
const queueErrors = validateQueueSegments(patch);
|
|
6825
7349
|
if (queueErrors.length > 0) {
|
|
6826
7350
|
return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
|
|
@@ -6849,7 +7373,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
6849
7373
|
if (billingErrors.length > 0) {
|
|
6850
7374
|
return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
|
|
6851
7375
|
}
|
|
6852
|
-
const current = await
|
|
7376
|
+
const current = await loadServerConfig3(deps.settingsStore);
|
|
6853
7377
|
let effectivePatch = patch;
|
|
6854
7378
|
if (patch.proxy) {
|
|
6855
7379
|
effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
|
|
@@ -6870,6 +7394,20 @@ async function handleServer(req, res, method, deps) {
|
|
|
6870
7394
|
}
|
|
6871
7395
|
effectivePatch = { ...effectivePatch, images };
|
|
6872
7396
|
}
|
|
7397
|
+
if (patch.search !== void 0) {
|
|
7398
|
+
const searchPatch = preserveSearchSecrets(
|
|
7399
|
+
patch.search,
|
|
7400
|
+
current.search ?? DEFAULT_SEARCH_SERVER_CONFIG2
|
|
7401
|
+
);
|
|
7402
|
+
const searchErrors = validateSearchServerConfig(searchPatch);
|
|
7403
|
+
if (searchErrors.length > 0) {
|
|
7404
|
+
return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
|
|
7405
|
+
}
|
|
7406
|
+
effectivePatch = {
|
|
7407
|
+
...effectivePatch,
|
|
7408
|
+
search: searchPatch
|
|
7409
|
+
};
|
|
7410
|
+
}
|
|
6873
7411
|
const merged = mergeServerConfig(current, effectivePatch);
|
|
6874
7412
|
const priorImageGenerationId = currentImageGenerationId(deps);
|
|
6875
7413
|
try {
|
|
@@ -6922,7 +7460,11 @@ async function handleServer(req, res, method, deps) {
|
|
|
6922
7460
|
} catch {
|
|
6923
7461
|
}
|
|
6924
7462
|
}
|
|
6925
|
-
|
|
7463
|
+
const mergedForAdmin = merged.search ? {
|
|
7464
|
+
...merged,
|
|
7465
|
+
search: redactSearchServerConfig(merged.search)
|
|
7466
|
+
} : merged;
|
|
7467
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
|
|
6926
7468
|
}
|
|
6927
7469
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
6928
7470
|
}
|
|
@@ -6939,7 +7481,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6939
7481
|
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
6940
7482
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
6941
7483
|
});
|
|
6942
|
-
return
|
|
7484
|
+
return writeJson4(res, 200, {
|
|
6943
7485
|
available: true,
|
|
6944
7486
|
records,
|
|
6945
7487
|
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
@@ -6955,7 +7497,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6955
7497
|
providerId: query2.get("providerId") ?? void 0,
|
|
6956
7498
|
accountId: query2.get("accountId") ?? void 0
|
|
6957
7499
|
});
|
|
6958
|
-
return
|
|
7500
|
+
return writeJson4(res, 200, {
|
|
6959
7501
|
available: true,
|
|
6960
7502
|
entries,
|
|
6961
7503
|
collectedAt: Date.now()
|
|
@@ -6974,10 +7516,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6974
7516
|
const accounts = await deps.subscriptionAccounts.listAll();
|
|
6975
7517
|
const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
6976
7518
|
const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
|
|
6977
|
-
return
|
|
7519
|
+
return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
|
|
6978
7520
|
}
|
|
6979
7521
|
if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
|
|
6980
|
-
const body = await
|
|
7522
|
+
const body = await readJsonBody4(req);
|
|
6981
7523
|
const parsed = validateAccountBatchBody(body);
|
|
6982
7524
|
if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
|
|
6983
7525
|
const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
|
|
@@ -6993,15 +7535,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6993
7535
|
deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
|
|
6994
7536
|
}
|
|
6995
7537
|
}
|
|
6996
|
-
return
|
|
7538
|
+
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6997
7539
|
}
|
|
6998
7540
|
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6999
7541
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
7000
|
-
return
|
|
7542
|
+
return writeJson4(res, result.status, result.body);
|
|
7001
7543
|
}
|
|
7002
7544
|
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
7003
7545
|
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
7004
|
-
return
|
|
7546
|
+
return writeJson4(res, result.status, result.body);
|
|
7005
7547
|
}
|
|
7006
7548
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
7007
7549
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7023,7 +7565,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7023
7565
|
resumeAt: entry.resumeAt
|
|
7024
7566
|
})) ?? [];
|
|
7025
7567
|
const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
|
|
7026
|
-
return
|
|
7568
|
+
return writeJson4(res, 200, { diagnostics });
|
|
7027
7569
|
}
|
|
7028
7570
|
if (method === "GET" && rest.length === 3 && rest[2] === "events") {
|
|
7029
7571
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7035,17 +7577,17 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7035
7577
|
}
|
|
7036
7578
|
const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
|
|
7037
7579
|
const diagnostics = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
|
|
7038
|
-
return
|
|
7580
|
+
return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
|
|
7039
7581
|
}
|
|
7040
7582
|
if (method === "PATCH" && rest.length === 2) {
|
|
7041
7583
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
7042
7584
|
if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
7043
|
-
const body = await
|
|
7585
|
+
const body = await readJsonBody4(req);
|
|
7044
7586
|
const patch = validateAccountMetadataPatch(body);
|
|
7045
7587
|
if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
|
|
7046
7588
|
const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
|
|
7047
7589
|
if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
|
|
7048
|
-
return
|
|
7590
|
+
return writeJson4(res, 200, { ok: true });
|
|
7049
7591
|
}
|
|
7050
7592
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
7051
7593
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7054,15 +7596,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7054
7596
|
}
|
|
7055
7597
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
7056
7598
|
const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
|
|
7057
|
-
return
|
|
7599
|
+
return writeJson4(res, result.status, result.body);
|
|
7058
7600
|
}
|
|
7059
7601
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
7060
|
-
const body2 = await
|
|
7602
|
+
const body2 = await readJsonBody4(req);
|
|
7061
7603
|
const result = await handleOAuthComplete(providerId, body2, deps);
|
|
7062
|
-
return
|
|
7604
|
+
return writeJson4(res, result.status, result.body);
|
|
7063
7605
|
}
|
|
7064
7606
|
if (method === "POST" && rest[1] === "accounts") {
|
|
7065
|
-
const body2 = await
|
|
7607
|
+
const body2 = await readJsonBody4(req);
|
|
7066
7608
|
const block = validateTokenBody(providerId, body2);
|
|
7067
7609
|
if (!block) {
|
|
7068
7610
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
@@ -7070,20 +7612,20 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7070
7612
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
7071
7613
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
7072
7614
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7073
|
-
return
|
|
7615
|
+
return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
|
|
7074
7616
|
}
|
|
7075
7617
|
if (method === "POST" && rest[1] === "import-external") {
|
|
7076
7618
|
if (providerId !== "claude" && providerId !== "codex") {
|
|
7077
7619
|
return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
|
|
7078
7620
|
}
|
|
7079
|
-
const body2 = await
|
|
7621
|
+
const body2 = await readJsonBody4(req);
|
|
7080
7622
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
7081
7623
|
const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
|
|
7082
7624
|
if (!result.ok) {
|
|
7083
7625
|
return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
|
|
7084
7626
|
}
|
|
7085
7627
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7086
|
-
return
|
|
7628
|
+
return writeJson4(res, 200, {
|
|
7087
7629
|
ok: true,
|
|
7088
7630
|
account: status2 ?? void 0,
|
|
7089
7631
|
nativeCredentialMode: result.nativeCredentialMode,
|
|
@@ -7098,7 +7640,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7098
7640
|
const writer2 = deps.subscriptionTokenWriter;
|
|
7099
7641
|
const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
|
|
7100
7642
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7101
|
-
return
|
|
7643
|
+
return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
|
|
7102
7644
|
}
|
|
7103
7645
|
if (method === "POST" && rest.length === 3 && rest[2] === "test") {
|
|
7104
7646
|
const accountId = rest[1];
|
|
@@ -7108,7 +7650,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7108
7650
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7109
7651
|
}
|
|
7110
7652
|
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
7111
|
-
return
|
|
7653
|
+
return writeJson4(res, 200, {
|
|
7112
7654
|
ok: result.ok,
|
|
7113
7655
|
marked: result.marked,
|
|
7114
7656
|
tier: result.tier,
|
|
@@ -7117,15 +7659,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7117
7659
|
}
|
|
7118
7660
|
if (method === "POST" && rest[2] === "label") {
|
|
7119
7661
|
const accountId = rest[1];
|
|
7120
|
-
const body2 = await
|
|
7662
|
+
const body2 = await readJsonBody4(req);
|
|
7121
7663
|
const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
|
|
7122
7664
|
const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
|
|
7123
7665
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7124
|
-
return
|
|
7666
|
+
return writeJson4(res, 200, { ok: true });
|
|
7125
7667
|
}
|
|
7126
7668
|
if (method === "POST" && rest[2] === "priority") {
|
|
7127
7669
|
const accountId = rest[1];
|
|
7128
|
-
const body2 = await
|
|
7670
|
+
const body2 = await readJsonBody4(req);
|
|
7129
7671
|
const raw = body2["priority"];
|
|
7130
7672
|
const priority = typeof raw === "number" ? raw : Number(raw);
|
|
7131
7673
|
if (!Number.isFinite(priority)) {
|
|
@@ -7133,11 +7675,11 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7133
7675
|
}
|
|
7134
7676
|
const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
|
|
7135
7677
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7136
|
-
return
|
|
7678
|
+
return writeJson4(res, 200, { ok: true });
|
|
7137
7679
|
}
|
|
7138
7680
|
if (method === "POST" && rest[2] === "proxy") {
|
|
7139
7681
|
const accountId = rest[1];
|
|
7140
|
-
const body2 = await
|
|
7682
|
+
const body2 = await readJsonBody4(req);
|
|
7141
7683
|
const rawProxy = body2["proxy"];
|
|
7142
7684
|
let proxy;
|
|
7143
7685
|
if (rawProxy !== null && rawProxy !== void 0) {
|
|
@@ -7146,63 +7688,63 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7146
7688
|
}
|
|
7147
7689
|
const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
|
|
7148
7690
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7149
|
-
return
|
|
7691
|
+
return writeJson4(res, 200, { ok: true });
|
|
7150
7692
|
}
|
|
7151
7693
|
if (method === "POST" && rest[2] === "supported-models") {
|
|
7152
7694
|
const accountId = rest[1];
|
|
7153
|
-
const body2 = await
|
|
7695
|
+
const body2 = await readJsonBody4(req);
|
|
7154
7696
|
const parsed = validateSupportedModelsBody(body2["supportedModels"]);
|
|
7155
7697
|
if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
|
|
7156
7698
|
const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
|
|
7157
7699
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7158
|
-
return
|
|
7700
|
+
return writeJson4(res, 200, { ok: true });
|
|
7159
7701
|
}
|
|
7160
7702
|
if (method === "PUT" && rest[1] === "active") {
|
|
7161
|
-
const body2 = await
|
|
7703
|
+
const body2 = await readJsonBody4(req);
|
|
7162
7704
|
const id = typeof body2["id"] === "string" ? body2["id"] : "";
|
|
7163
7705
|
if (!id) return writeJsonError(res, 400, "active switch requires { id }");
|
|
7164
7706
|
const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
|
|
7165
7707
|
if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
|
|
7166
|
-
return
|
|
7708
|
+
return writeJson4(res, 200, { ok: true });
|
|
7167
7709
|
}
|
|
7168
7710
|
if (method === "DELETE" && rest.length === 2) {
|
|
7169
7711
|
const accountId = rest[1];
|
|
7170
7712
|
const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
|
|
7171
7713
|
if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7172
7714
|
deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
|
|
7173
|
-
return
|
|
7715
|
+
return writeJson4(res, 200, { ok: true });
|
|
7174
7716
|
}
|
|
7175
7717
|
if (method === "DELETE" && rest.length === 1) {
|
|
7176
7718
|
await deps.subscriptionTokenWriter.clearProvider(providerId);
|
|
7177
7719
|
deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
|
|
7178
|
-
return
|
|
7720
|
+
return writeJson4(res, 200, { ok: true });
|
|
7179
7721
|
}
|
|
7180
7722
|
if (method === "DELETE") {
|
|
7181
7723
|
return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
|
|
7182
7724
|
}
|
|
7183
|
-
const body = await
|
|
7725
|
+
const body = await readJsonBody4(req);
|
|
7184
7726
|
const config = validateTokenBody(providerId, body);
|
|
7185
7727
|
if (!config) {
|
|
7186
7728
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
7187
7729
|
}
|
|
7188
7730
|
await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
|
|
7189
7731
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7190
|
-
return
|
|
7732
|
+
return writeJson4(res, 200, status ? { account: status } : { ok: true });
|
|
7191
7733
|
}
|
|
7192
7734
|
return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
|
|
7193
7735
|
}
|
|
7194
7736
|
async function handleCli(req, res, method, rest, deps) {
|
|
7195
7737
|
if (method === "GET" && rest.length === 0) {
|
|
7196
7738
|
const result = handleCliList(process.platform, deps.cliPathProbe);
|
|
7197
|
-
return
|
|
7739
|
+
return writeJson4(res, result.status, result.body);
|
|
7198
7740
|
}
|
|
7199
7741
|
if (method === "GET" && rest[0] === "sessions") {
|
|
7200
7742
|
const result = handleCliSessions();
|
|
7201
|
-
return
|
|
7743
|
+
return writeJson4(res, result.status, result.body);
|
|
7202
7744
|
}
|
|
7203
7745
|
if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
|
|
7204
7746
|
const result = handleCliStop(rest[1]);
|
|
7205
|
-
return
|
|
7747
|
+
return writeJson4(res, result.status, result.body);
|
|
7206
7748
|
}
|
|
7207
7749
|
if (method === "POST" && rest[1] === "install") {
|
|
7208
7750
|
const cli = rest[0];
|
|
@@ -7210,14 +7752,14 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7210
7752
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7211
7753
|
}
|
|
7212
7754
|
const result = await handleCliInstall(cli, deps.cliCommandRunner);
|
|
7213
|
-
return
|
|
7755
|
+
return writeJson4(res, result.status, result.body);
|
|
7214
7756
|
}
|
|
7215
7757
|
if (method === "POST" && rest[1] === "launch") {
|
|
7216
7758
|
const cli = rest[0];
|
|
7217
7759
|
if (!isLaunchCliId(cli)) {
|
|
7218
7760
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7219
7761
|
}
|
|
7220
|
-
const body = await
|
|
7762
|
+
const body = await readJsonBody4(req);
|
|
7221
7763
|
const providers = loadConfig(deps.configPath).providers ?? [];
|
|
7222
7764
|
const result = await handleCliLaunch(cli, body, {
|
|
7223
7765
|
llmConfig: deps.llmConfig,
|
|
@@ -7226,7 +7768,7 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7226
7768
|
opener: deps.cliTerminalOpener,
|
|
7227
7769
|
probe: deps.cliPathProbe
|
|
7228
7770
|
});
|
|
7229
|
-
return
|
|
7771
|
+
return writeJson4(res, result.status, result.body);
|
|
7230
7772
|
}
|
|
7231
7773
|
return writeJsonError(res, 405, `method ${method} not allowed on cli`);
|
|
7232
7774
|
}
|
|
@@ -7236,52 +7778,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
|
|
|
7236
7778
|
const manager = factory();
|
|
7237
7779
|
try {
|
|
7238
7780
|
if (method === "GET" && rest.length === 0) {
|
|
7239
|
-
return
|
|
7781
|
+
return writeJson4(res, 200, {
|
|
7240
7782
|
integrations: await manager.listStatus(),
|
|
7241
7783
|
gateway: deps.outboundApiServer.getStatus()
|
|
7242
7784
|
});
|
|
7243
7785
|
}
|
|
7244
7786
|
if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
|
|
7245
7787
|
await manager.rotateGatewayKey();
|
|
7246
|
-
return
|
|
7788
|
+
return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
|
|
7247
7789
|
}
|
|
7248
7790
|
const client = rest[0];
|
|
7249
7791
|
if (!isIntegrationClient(client)) {
|
|
7250
7792
|
return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
|
|
7251
7793
|
}
|
|
7252
7794
|
if (method === "POST" && rest[1] === "key") {
|
|
7253
|
-
const body = await
|
|
7795
|
+
const body = await readJsonBody4(req);
|
|
7254
7796
|
if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
|
|
7255
7797
|
return writeJsonError(res, 400, "body must contain a non-empty keyId string");
|
|
7256
7798
|
}
|
|
7257
7799
|
const status = await manager.bindIntegrationKey(client, body.keyId.trim());
|
|
7258
|
-
return
|
|
7800
|
+
return writeJson4(res, 200, { integration: status });
|
|
7259
7801
|
}
|
|
7260
7802
|
if (method === "POST" && rest[1] === "plan") {
|
|
7261
|
-
const body = await
|
|
7803
|
+
const body = await readJsonBody4(req);
|
|
7262
7804
|
const configPath = body.configPath;
|
|
7263
7805
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7264
7806
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7265
7807
|
}
|
|
7266
7808
|
const plan = await manager.plan(client, configPath);
|
|
7267
|
-
return
|
|
7809
|
+
return writeJson4(res, 200, { plan });
|
|
7268
7810
|
}
|
|
7269
7811
|
if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
|
|
7270
|
-
const body = await
|
|
7812
|
+
const body = await readJsonBody4(req);
|
|
7271
7813
|
const configPath = body.configPath;
|
|
7272
7814
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7273
7815
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7274
7816
|
}
|
|
7275
7817
|
const status = await manager.install(client, configPath);
|
|
7276
|
-
return
|
|
7818
|
+
return writeJson4(res, 200, { integration: status });
|
|
7277
7819
|
}
|
|
7278
7820
|
if (method === "POST" && rest[1] === "repair") {
|
|
7279
7821
|
const status = await manager.repair(client);
|
|
7280
|
-
return
|
|
7822
|
+
return writeJson4(res, 200, { integration: status });
|
|
7281
7823
|
}
|
|
7282
7824
|
if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
|
|
7283
7825
|
const status = await manager.remove(client);
|
|
7284
|
-
return
|
|
7826
|
+
return writeJson4(res, 200, { integration: status });
|
|
7285
7827
|
}
|
|
7286
7828
|
return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
|
|
7287
7829
|
} catch (error) {
|
|
@@ -7424,7 +7966,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7424
7966
|
}
|
|
7425
7967
|
const reader = deps.imageRuntimeStatus;
|
|
7426
7968
|
if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
|
|
7427
|
-
const serverConfig = await
|
|
7969
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
7428
7970
|
const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
|
|
7429
7971
|
const lifecycle = reader.status();
|
|
7430
7972
|
const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
|
|
@@ -7444,7 +7986,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7444
7986
|
httpLeases: safeStatusCount(generation.httpLeases),
|
|
7445
7987
|
hostedLeases: safeStatusCount(generation.hostedLeases)
|
|
7446
7988
|
}));
|
|
7447
|
-
return
|
|
7989
|
+
return writeJson4(res, 200, {
|
|
7448
7990
|
configured: {
|
|
7449
7991
|
enabled: images.enabled,
|
|
7450
7992
|
provider: images.provider,
|
|
@@ -7472,7 +8014,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7472
8014
|
async function handleStatus(res, method, deps) {
|
|
7473
8015
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
|
|
7474
8016
|
const status = deps.outboundApiServer.getStatus();
|
|
7475
|
-
const serverConfig = await
|
|
8017
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
7476
8018
|
const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
|
|
7477
8019
|
const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => gatewayBindingToEndpointConfig(binding));
|
|
7478
8020
|
const useSubscription = routes.some((route) => route.useSubscription);
|
|
@@ -7510,14 +8052,14 @@ async function handleStatus(res, method, deps) {
|
|
|
7510
8052
|
})() : void 0;
|
|
7511
8053
|
if (status.running) {
|
|
7512
8054
|
const queueStatus = deps.outboundApiServer.getQueueStatus();
|
|
7513
|
-
return
|
|
8055
|
+
return writeJson4(res, 200, {
|
|
7514
8056
|
...status,
|
|
7515
8057
|
endpoints,
|
|
7516
8058
|
queueStatus,
|
|
7517
8059
|
...imageRuntime ? { imageRuntime } : {}
|
|
7518
8060
|
});
|
|
7519
8061
|
}
|
|
7520
|
-
return
|
|
8062
|
+
return writeJson4(res, 200, {
|
|
7521
8063
|
...status,
|
|
7522
8064
|
endpoints,
|
|
7523
8065
|
...imageRuntime ? { imageRuntime } : {}
|
|
@@ -7541,18 +8083,18 @@ function resolvePlaygroundPath(endpoint, body) {
|
|
|
7541
8083
|
}
|
|
7542
8084
|
async function handlePlayground(req, res, method, deps) {
|
|
7543
8085
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
|
|
7544
|
-
const body = await
|
|
8086
|
+
const body = await readJsonBody4(req);
|
|
7545
8087
|
const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
|
|
7546
8088
|
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
7547
8089
|
const payload = body["body"];
|
|
7548
8090
|
const status = deps.outboundApiServer.getStatus();
|
|
7549
8091
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7550
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8092
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
|
|
7551
8093
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7552
8094
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7553
8095
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7554
8096
|
}
|
|
7555
|
-
function
|
|
8097
|
+
function isRecord3(v) {
|
|
7556
8098
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7557
8099
|
}
|
|
7558
8100
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7687,7 +8229,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7687
8229
|
}
|
|
7688
8230
|
|
|
7689
8231
|
// src/admin/version.ts
|
|
7690
|
-
var DAEMON_VERSION = true ? "0.
|
|
8232
|
+
var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
7691
8233
|
|
|
7692
8234
|
// src/admin/AdminServer.ts
|
|
7693
8235
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -17582,6 +18124,14 @@ function buildDaemon(config, paths) {
|
|
|
17582
18124
|
onEvent: (row, at) => usageThroughput.record(row, at)
|
|
17583
18125
|
});
|
|
17584
18126
|
const initialImagesConfig = normalizeServerConfig2(decryptedConfig.server).images;
|
|
18127
|
+
const initialSearchConfig = normalizeServerConfig2(decryptedConfig.server).search;
|
|
18128
|
+
for (const issue of validateSearchServerConfig2(
|
|
18129
|
+
decryptedConfig.server?.search
|
|
18130
|
+
)) {
|
|
18131
|
+
logger.warn("[search] ignoring invalid config: " + issue);
|
|
18132
|
+
}
|
|
18133
|
+
const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
|
|
18134
|
+
const searchFrontendModes = initialSearchConfig.modes;
|
|
17585
18135
|
const imageObservability = new ImageObservability();
|
|
17586
18136
|
const imageRuntimeObservability = Object.freeze({
|
|
17587
18137
|
telemetrySink: imageObservability.telemetrySink,
|
|
@@ -17711,7 +18261,9 @@ function buildDaemon(config, paths) {
|
|
|
17711
18261
|
apiKeyPool,
|
|
17712
18262
|
usageRecorder,
|
|
17713
18263
|
openAIOperationRegistry,
|
|
17714
|
-
responsesHostedImageIngress
|
|
18264
|
+
responsesHostedImageIngress,
|
|
18265
|
+
searchRuntime,
|
|
18266
|
+
searchFrontendModes
|
|
17715
18267
|
});
|
|
17716
18268
|
if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
|
|
17717
18269
|
throw new Error(
|
|
@@ -17776,7 +18328,9 @@ function buildDaemon(config, paths) {
|
|
|
17776
18328
|
keySpendTracker,
|
|
17777
18329
|
// configurable-logging: route the server's OWN lifecycle + relay dispatch-error
|
|
17778
18330
|
// lines through the injected logger (honors level/format/file sink).
|
|
17779
|
-
logger
|
|
18331
|
+
logger,
|
|
18332
|
+
// plan 阶段5: the same instance the managed frontends hold.
|
|
18333
|
+
searchRuntime
|
|
17780
18334
|
});
|
|
17781
18335
|
const auditDir = defaultAuditDir(paths.configPath);
|
|
17782
18336
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
@@ -17798,6 +18352,10 @@ function buildDaemon(config, paths) {
|
|
|
17798
18352
|
}),
|
|
17799
18353
|
routeLeaseManager,
|
|
17800
18354
|
subscriptionAccounts,
|
|
18355
|
+
// search-settings-ui D3: the daemon's ONE search runtime + its
|
|
18356
|
+
// bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
|
|
18357
|
+
// `POST /admin/api/search/test` (501 when a light embedder omits it).
|
|
18358
|
+
searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
|
|
17801
18359
|
accountAllowanceService,
|
|
17802
18360
|
allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
|
|
17803
18361
|
accountProbeService: accountHealthProbeScheduler,
|
|
@@ -17934,6 +18492,8 @@ function buildDaemon(config, paths) {
|
|
|
17934
18492
|
keyDb,
|
|
17935
18493
|
settingsStore,
|
|
17936
18494
|
openAIOperationRegistry,
|
|
18495
|
+
searchRuntime,
|
|
18496
|
+
searchFrontendModes,
|
|
17937
18497
|
imageRuntimeManager,
|
|
17938
18498
|
imageObservability,
|
|
17939
18499
|
imageCleanupService,
|
|
@@ -18080,6 +18640,116 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
|
|
|
18080
18640
|
);
|
|
18081
18641
|
return true;
|
|
18082
18642
|
}
|
|
18643
|
+
function readSearchApiConfigFromEnv(env = process.env) {
|
|
18644
|
+
const configs = {};
|
|
18645
|
+
const read = (name) => {
|
|
18646
|
+
const value = env[`OMNICROSS_SEARCH_${name}`]?.trim();
|
|
18647
|
+
return value ? value : void 0;
|
|
18648
|
+
};
|
|
18649
|
+
const tavilyKey = read("TAVILY_API_KEY");
|
|
18650
|
+
if (tavilyKey) {
|
|
18651
|
+
configs.tavily = { apiKey: tavilyKey, ...optionalHost(read("TAVILY_API_HOST")) };
|
|
18652
|
+
}
|
|
18653
|
+
const jinaKey = read("JINA_API_KEY");
|
|
18654
|
+
const jinaHost = read("JINA_API_HOST");
|
|
18655
|
+
if (jinaKey || jinaHost) {
|
|
18656
|
+
configs.jina = { ...jinaKey ? { apiKey: jinaKey } : {}, ...optionalHost(jinaHost) };
|
|
18657
|
+
}
|
|
18658
|
+
const searxngHost = read("SEARXNG_API_HOST");
|
|
18659
|
+
if (searxngHost) {
|
|
18660
|
+
const username = read("SEARXNG_BASIC_AUTH_USERNAME");
|
|
18661
|
+
const password = read("SEARXNG_BASIC_AUTH_PASSWORD");
|
|
18662
|
+
configs.searxng = {
|
|
18663
|
+
apiHost: searxngHost,
|
|
18664
|
+
...username ? { basicAuthUsername: username } : {},
|
|
18665
|
+
...password ? { basicAuthPassword: password } : {}
|
|
18666
|
+
};
|
|
18667
|
+
}
|
|
18668
|
+
const zhipuKey = read("ZHIPU_API_KEY");
|
|
18669
|
+
if (zhipuKey) {
|
|
18670
|
+
configs.zhipu = { apiKey: zhipuKey, ...optionalHost(read("ZHIPU_API_HOST")) };
|
|
18671
|
+
}
|
|
18672
|
+
const zaiKey = read("Z_AI_API_KEY");
|
|
18673
|
+
if (zaiKey) {
|
|
18674
|
+
configs["z.ai"] = { apiKey: zaiKey, ...optionalHost(read("Z_AI_API_HOST")) };
|
|
18675
|
+
}
|
|
18676
|
+
return configs;
|
|
18677
|
+
}
|
|
18678
|
+
function optionalHost(apiHost) {
|
|
18679
|
+
return apiHost ? { apiHost } : {};
|
|
18680
|
+
}
|
|
18681
|
+
function resolveSearchApiConfigs(configured, env = process.env) {
|
|
18682
|
+
const fromEnv = readSearchApiConfigFromEnv(env);
|
|
18683
|
+
const resolved = {};
|
|
18684
|
+
const tavily = configured?.tavily ?? fromEnv.tavily;
|
|
18685
|
+
if (tavily) resolved.tavily = tavily;
|
|
18686
|
+
const jina = configured?.jina ?? fromEnv.jina;
|
|
18687
|
+
if (jina) resolved.jina = jina;
|
|
18688
|
+
const searxng = configured?.searxng ?? fromEnv.searxng;
|
|
18689
|
+
if (searxng) resolved.searxng = searxng;
|
|
18690
|
+
const zhipu = configured?.zhipu ?? fromEnv.zhipu;
|
|
18691
|
+
if (zhipu) resolved.zhipu = zhipu;
|
|
18692
|
+
const zai = configured?.["z.ai"] ?? fromEnv["z.ai"];
|
|
18693
|
+
if (zai) resolved["z.ai"] = zai;
|
|
18694
|
+
return resolved;
|
|
18695
|
+
}
|
|
18696
|
+
function formatCapabilities(capabilities) {
|
|
18697
|
+
return [
|
|
18698
|
+
`apiKey=${capabilities.requiresApiKey}`,
|
|
18699
|
+
`cancellation=${capabilities.supportsCancellation}`,
|
|
18700
|
+
`urlRead=${capabilities.supportsUrlRead}`,
|
|
18701
|
+
`region=${capabilities.supportsRegion}`,
|
|
18702
|
+
`language=${capabilities.supportsLanguage}`,
|
|
18703
|
+
`timeRange=${capabilities.supportsTimeRange}`,
|
|
18704
|
+
`maxResults=${capabilities.maxResults ?? "unbounded"}`
|
|
18705
|
+
].join(", ");
|
|
18706
|
+
}
|
|
18707
|
+
function formatDiagnostic(diagnostic) {
|
|
18708
|
+
const parts = [diagnostic.status];
|
|
18709
|
+
if (diagnostic.reason) parts.push(diagnostic.reason);
|
|
18710
|
+
if (diagnostic.error) {
|
|
18711
|
+
const { code, details } = diagnostic.error;
|
|
18712
|
+
parts.push(
|
|
18713
|
+
`code=${code}, transport=${details?.transport ?? "unknown"}, stage=${details?.stage ?? "unknown"}`
|
|
18714
|
+
);
|
|
18715
|
+
}
|
|
18716
|
+
return parts.join(" \u2014 ");
|
|
18717
|
+
}
|
|
18718
|
+
async function runSearchDoctor(live, env = process.env, source = {}) {
|
|
18719
|
+
const apiConfigs = resolveSearchApiConfigs(source.config?.providers, env);
|
|
18720
|
+
const egressPolicy = source.config && source.config.egress.allowedPrivateHosts.length > 0 ? { allowedPrivateHosts: [...source.config.egress.allowedPrivateHosts] } : void 0;
|
|
18721
|
+
const contributions = [
|
|
18722
|
+
...builtinHttpSearchContributions4(),
|
|
18723
|
+
...apiSearchContributions3(apiConfigs, { ...egressPolicy ? { egressPolicy } : {} })
|
|
18724
|
+
];
|
|
18725
|
+
const declarations = source.runtime?.listProviders() ?? contributions;
|
|
18726
|
+
console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
|
|
18727
|
+
const modes = source.config?.modes ?? DEFAULT_SEARCH_FRONTEND_MODES;
|
|
18728
|
+
console.info(
|
|
18729
|
+
` [i] frontend modes: ${SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
|
|
18730
|
+
);
|
|
18731
|
+
console.info(
|
|
18732
|
+
" [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
|
|
18733
|
+
);
|
|
18734
|
+
for (const row of buildSearchDoctorSnapshot(declarations, apiConfigs)) {
|
|
18735
|
+
const mark = row.status === "unconfigured" ? "\u2013" : "\u2713";
|
|
18736
|
+
const suffix = row.status ? ` \u2014 ${row.status}: ${row.reason ?? ""}` : "";
|
|
18737
|
+
console.info(
|
|
18738
|
+
` [${mark}] ${row.providerId}: source=${row.source}, kind=${row.kind}, ${formatCapabilities(row.capabilities)}${suffix}`
|
|
18739
|
+
);
|
|
18740
|
+
}
|
|
18741
|
+
if (!live) return 0;
|
|
18742
|
+
console.info(
|
|
18743
|
+
` [\u26A0] live search sends ONE fixed public query per provider ("${SEARCH_DOCTOR_QUERY}") to the engine itself`
|
|
18744
|
+
);
|
|
18745
|
+
let hardFailure = false;
|
|
18746
|
+
for (const diagnostic of await runSearchLiveChecks(contributions)) {
|
|
18747
|
+
const mark = diagnostic.status === "healthy" ? "\u2713" : diagnostic.status === "failed" ? "\u2717" : "\u26A0";
|
|
18748
|
+
if (diagnostic.status === "failed") hardFailure = true;
|
|
18749
|
+
console.info(` [${mark}] ${diagnostic.providerId}: ${formatDiagnostic(diagnostic)}`);
|
|
18750
|
+
}
|
|
18751
|
+
return hardFailure ? 1 : 0;
|
|
18752
|
+
}
|
|
18083
18753
|
async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
18084
18754
|
try {
|
|
18085
18755
|
const res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/messages/count_tokens`, {
|
|
@@ -18116,11 +18786,12 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
18116
18786
|
allowPositionals: true
|
|
18117
18787
|
});
|
|
18118
18788
|
const subject = positionals[0] ?? "claude";
|
|
18119
|
-
if (subject !== "claude" && subject !== "images") {
|
|
18120
|
-
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images')`);
|
|
18789
|
+
if (subject !== "claude" && subject !== "images" && subject !== "search") {
|
|
18790
|
+
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images', 'search')`);
|
|
18121
18791
|
}
|
|
18122
18792
|
const configPath = values.config;
|
|
18123
18793
|
if (!configPath) {
|
|
18794
|
+
if (subject === "search") return runSearchDoctor(values.live === true);
|
|
18124
18795
|
throw new Error("doctor: --config <path> is required (the same config `omnicross start` uses)");
|
|
18125
18796
|
}
|
|
18126
18797
|
const config = loadConfig(configPath);
|
|
@@ -18132,7 +18803,13 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
18132
18803
|
};
|
|
18133
18804
|
const daemon = await buildDaemon(config, paths);
|
|
18134
18805
|
try {
|
|
18135
|
-
const serverConfig = await
|
|
18806
|
+
const serverConfig = await loadServerConfig4(daemon.settingsStore);
|
|
18807
|
+
if (subject === "search") {
|
|
18808
|
+
return await runSearchDoctor(values.live === true, process.env, {
|
|
18809
|
+
...serverConfig.search ? { config: serverConfig.search } : {},
|
|
18810
|
+
runtime: daemon.searchRuntime
|
|
18811
|
+
});
|
|
18812
|
+
}
|
|
18136
18813
|
const checks = subject === "images" ? buildImagesDoctorChecks(await daemon.imageDoctor.inspectLocal(
|
|
18137
18814
|
serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG2
|
|
18138
18815
|
)) : buildClaudeDoctorChecks(serverConfig);
|
|
@@ -19209,7 +19886,7 @@ function tokensSuffix(configPath) {
|
|
|
19209
19886
|
|
|
19210
19887
|
// src/commands/start.ts
|
|
19211
19888
|
import { parseArgs as parseArgs10 } from "util";
|
|
19212
|
-
import { loadServerConfig as
|
|
19889
|
+
import { loadServerConfig as loadServerConfig5 } from "@omnicross/core/outbound-api";
|
|
19213
19890
|
import { getSharedAccountHealth as getSharedAccountHealth5 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
19214
19891
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling6 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
19215
19892
|
|
|
@@ -19271,7 +19948,7 @@ async function runStart(argv) {
|
|
|
19271
19948
|
await daemon.llmConfig.ready();
|
|
19272
19949
|
await daemon.migrateUsageStore();
|
|
19273
19950
|
await daemon.providerProxy.start();
|
|
19274
|
-
const serverConfig = await
|
|
19951
|
+
const serverConfig = await loadServerConfig5(daemon.settingsStore);
|
|
19275
19952
|
await daemon.imageCleanupService.runOnce();
|
|
19276
19953
|
daemon.imageCleanupService.start();
|
|
19277
19954
|
getSharedAccountHealth5().configure({
|
|
@@ -19294,7 +19971,9 @@ async function runStart(argv) {
|
|
|
19294
19971
|
voucher: serverConfig.voucher,
|
|
19295
19972
|
// claude-api-protocol-fidelity (§10): count_tokens strategy/budget,
|
|
19296
19973
|
// /v1/models shape, synthetic-ping heartbeat (hot-applied inside applyConfig).
|
|
19297
|
-
anthropic: serverConfig.anthropic
|
|
19974
|
+
anthropic: serverConfig.anthropic,
|
|
19975
|
+
// plan 阶段5: the Codex frontend mode is read live per request from here.
|
|
19976
|
+
search: serverConfig.search
|
|
19298
19977
|
});
|
|
19299
19978
|
let dashboardUrl = null;
|
|
19300
19979
|
if (!values["no-dashboard"]) {
|
|
@@ -19409,6 +20088,16 @@ Usage:
|
|
|
19409
20088
|
Check local Images config, roots, stores, permissions,
|
|
19410
20089
|
account and cached evidence. --live warns, then consumes
|
|
19411
20090
|
at most one minimal subscription image request.
|
|
20091
|
+
omnicross doctor search [--live] List the builtin search providers and their declared
|
|
20092
|
+
capabilities (offline, no config needed). --live sends ONE
|
|
20093
|
+
fixed public query per provider and reports healthy /
|
|
20094
|
+
degraded / blocked / failed.
|
|
20095
|
+
Keyed API providers show as unconfigured unless enabled
|
|
20096
|
+
through the DIAGNOSTIC-ONLY environment variables
|
|
20097
|
+
OMNICROSS_SEARCH_{TAVILY,JINA,SEARXNG,ZHIPU,Z_AI}_API_KEY
|
|
20098
|
+
/ _API_HOST (plus SEARXNG_BASIC_AUTH_USERNAME/_PASSWORD).
|
|
20099
|
+
These are read only by this command and are not the
|
|
20100
|
+
configuration system.
|
|
19412
20101
|
`;
|
|
19413
20102
|
async function main() {
|
|
19414
20103
|
const [, , subcommand, ...rest] = process.argv;
|