@omnicross/daemon 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +19 -19
- package/dist/cli.cjs +852 -199
- package/dist/cli.js +826 -160
- package/dist/index.cjs +696 -189
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +668 -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,487 @@ 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 } from "@omnicross/core/search/http";
|
|
4603
|
+
|
|
4604
|
+
// src/search/searchDoctorProjection.ts
|
|
4605
|
+
import { toSearchErrorShape } from "@omnicross/contracts/search-types";
|
|
4606
|
+
import {
|
|
4607
|
+
JINA_CAPABILITIES,
|
|
4608
|
+
SEARXNG_CAPABILITIES,
|
|
4609
|
+
TAVILY_CAPABILITIES,
|
|
4610
|
+
ZHIPU_CAPABILITIES
|
|
4611
|
+
} from "@omnicross/core/search/api";
|
|
4612
|
+
import { builtinHttpSearchContributions } from "@omnicross/core/search/http";
|
|
4613
|
+
var API_DOCTOR_PROVIDERS = [
|
|
4614
|
+
{
|
|
4615
|
+
id: "tavily",
|
|
4616
|
+
capabilities: TAVILY_CAPABILITIES,
|
|
4617
|
+
configured: (configs) => configs.tavily !== void 0,
|
|
4618
|
+
missingReason: "no API key configured"
|
|
4619
|
+
},
|
|
4620
|
+
{
|
|
4621
|
+
id: "jina",
|
|
4622
|
+
capabilities: JINA_CAPABILITIES,
|
|
4623
|
+
configured: (configs) => configs.jina !== void 0,
|
|
4624
|
+
// Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
|
|
4625
|
+
// asked for is still not enabled.
|
|
4626
|
+
missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
|
|
4627
|
+
},
|
|
4628
|
+
{
|
|
4629
|
+
id: "searxng",
|
|
4630
|
+
capabilities: SEARXNG_CAPABILITIES,
|
|
4631
|
+
configured: (configs) => configs.searxng !== void 0,
|
|
4632
|
+
missingReason: "no API host configured"
|
|
4633
|
+
},
|
|
4634
|
+
{
|
|
4635
|
+
id: "zhipu",
|
|
4636
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
4637
|
+
configured: (configs) => configs.zhipu !== void 0,
|
|
4638
|
+
missingReason: "no API key configured"
|
|
4639
|
+
},
|
|
4640
|
+
{
|
|
4641
|
+
id: "z.ai",
|
|
4642
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
4643
|
+
configured: (configs) => configs["z.ai"] !== void 0,
|
|
4644
|
+
missingReason: "no API key configured"
|
|
4645
|
+
}
|
|
4646
|
+
];
|
|
4647
|
+
function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContributions(), apiConfigs) {
|
|
4648
|
+
const rows = contributions.map((contribution) => ({
|
|
4649
|
+
providerId: contribution.id,
|
|
4650
|
+
source: contribution.source,
|
|
4651
|
+
kind: contribution.kind,
|
|
4652
|
+
capabilities: contribution.capabilities
|
|
4653
|
+
}));
|
|
4654
|
+
if (apiConfigs === void 0) return rows;
|
|
4655
|
+
for (const provider of API_DOCTOR_PROVIDERS) {
|
|
4656
|
+
if (provider.configured(apiConfigs)) continue;
|
|
4657
|
+
rows.push({
|
|
4658
|
+
providerId: provider.id,
|
|
4659
|
+
source: "builtin",
|
|
4660
|
+
kind: "api",
|
|
4661
|
+
capabilities: provider.capabilities,
|
|
4662
|
+
status: "unconfigured",
|
|
4663
|
+
reason: provider.missingReason
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4666
|
+
return rows;
|
|
4667
|
+
}
|
|
4668
|
+
var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
|
|
4669
|
+
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
4670
|
+
if (outcome.kind === "results") {
|
|
4671
|
+
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
4672
|
+
return {
|
|
4673
|
+
providerId,
|
|
4674
|
+
status: "degraded",
|
|
4675
|
+
checkedAt,
|
|
4676
|
+
reason: "reachable, but the engine returned no usable results (possible partial drift)"
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
const error = toSearchErrorShape(outcome.error);
|
|
4680
|
+
const stage = error.details?.stage;
|
|
4681
|
+
const { status, reason } = classifySearchFailure(stage, error.code);
|
|
4682
|
+
return { providerId, status, checkedAt, reason, error };
|
|
4683
|
+
}
|
|
4684
|
+
function classifySearchFailure(stage, code) {
|
|
4685
|
+
if (stage === "challenge") {
|
|
4686
|
+
return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
|
|
4687
|
+
}
|
|
4688
|
+
if (stage === "trust") {
|
|
4689
|
+
return {
|
|
4690
|
+
status: "blocked",
|
|
4691
|
+
reason: "the engine served a page that failed the anti-decoy trust check"
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
if (code === "policy_denied") {
|
|
4695
|
+
return {
|
|
4696
|
+
status: "blocked",
|
|
4697
|
+
reason: "the egress policy refused the request target"
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4700
|
+
if (code === "parse_failed") {
|
|
4701
|
+
return {
|
|
4702
|
+
status: "failed",
|
|
4703
|
+
reason: "the response was not recognizable as a search result page (parser drift suspected)"
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
if (code === "timeout") {
|
|
4707
|
+
return { status: "failed", reason: "the request exceeded its time budget" };
|
|
4708
|
+
}
|
|
4709
|
+
return { status: "failed", reason: `the request failed (${code})` };
|
|
4710
|
+
}
|
|
4711
|
+
async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4712
|
+
const diagnostics = [];
|
|
4713
|
+
for (const contribution of contributions) {
|
|
4714
|
+
try {
|
|
4715
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4716
|
+
diagnostics.push(
|
|
4717
|
+
classifyLiveSearchOutcome(contribution.id, { kind: "results", count: results.length }, now())
|
|
4718
|
+
);
|
|
4719
|
+
} catch (error) {
|
|
4720
|
+
diagnostics.push(classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, now()));
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
return diagnostics;
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
// src/search/SearchAssembly.ts
|
|
4727
|
+
import { createSearchRuntime } from "@omnicross/core/search";
|
|
4728
|
+
import { apiSearchContributions } from "@omnicross/core/search/api";
|
|
4729
|
+
import { builtinHttpSearchContributions as builtinHttpSearchContributions2 } from "@omnicross/core/search/http";
|
|
4730
|
+
function searchEgressPolicyFrom(config) {
|
|
4731
|
+
const hosts = config.egress.allowedPrivateHosts;
|
|
4732
|
+
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
4733
|
+
}
|
|
4734
|
+
function searchPolicyFrom(config) {
|
|
4735
|
+
const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
|
|
4736
|
+
return {
|
|
4737
|
+
...preferred !== void 0 ? { preferred } : {},
|
|
4738
|
+
...allowed !== void 0 ? { allowed: [...allowed] } : {},
|
|
4739
|
+
fallbackEnabled,
|
|
4740
|
+
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
function searchContributionsFrom(config) {
|
|
4744
|
+
return [
|
|
4745
|
+
...builtinHttpSearchContributions2(),
|
|
4746
|
+
...apiSearchContributions(config.providers, {
|
|
4747
|
+
egressPolicy: searchEgressPolicyFrom(config)
|
|
4748
|
+
})
|
|
4749
|
+
];
|
|
4750
|
+
}
|
|
4751
|
+
function buildSearchRuntime(config, options = {}) {
|
|
4752
|
+
const logger = options.logger ?? null;
|
|
4753
|
+
return createSearchRuntime({
|
|
4754
|
+
contributions: options.contributions ?? searchContributionsFrom(config),
|
|
4755
|
+
policy: searchPolicyFrom(config),
|
|
4756
|
+
...logger ? {
|
|
4757
|
+
onEvent: (event) => {
|
|
4758
|
+
logger.debug(`[search] ${formatSearchEvent(event)}`);
|
|
4759
|
+
}
|
|
4760
|
+
} : {}
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
function formatSearchEvent(event) {
|
|
4764
|
+
const parts = [
|
|
4765
|
+
`type=${event.type}`,
|
|
4766
|
+
`request=${event.requestId}`,
|
|
4767
|
+
`queryHash=${event.queryHash}`,
|
|
4768
|
+
`durationMs=${event.durationMs}`
|
|
4769
|
+
];
|
|
4770
|
+
if ("providerId" in event && event.providerId !== void 0) {
|
|
4771
|
+
parts.push(`provider=${event.providerId}`);
|
|
4772
|
+
}
|
|
4773
|
+
if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
|
|
4774
|
+
if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
|
|
4775
|
+
if ("resultCount" in event && event.resultCount !== void 0) {
|
|
4776
|
+
parts.push(`results=${event.resultCount}`);
|
|
4777
|
+
}
|
|
4778
|
+
if ("fallbackCount" in event && event.fallbackCount !== void 0) {
|
|
4779
|
+
parts.push(`fallbacks=${event.fallbackCount}`);
|
|
4780
|
+
}
|
|
4781
|
+
return parts.join(" ");
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
// src/admin/searchAdminApi.ts
|
|
4785
|
+
var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
|
|
4786
|
+
var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
4787
|
+
"tavily",
|
|
4788
|
+
"jina",
|
|
4789
|
+
"searxng",
|
|
4790
|
+
"zhipu",
|
|
4791
|
+
"z.ai"
|
|
4792
|
+
]);
|
|
4793
|
+
var SEARCH_QUERY_MAX_CODE_UNITS = 256;
|
|
4794
|
+
var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
4795
|
+
var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
|
|
4796
|
+
var SEARCH_QUERY_MAX_RESULTS = 5;
|
|
4797
|
+
function sanitizeResultField(value, cap) {
|
|
4798
|
+
const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
|
|
4799
|
+
return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
|
|
4800
|
+
}
|
|
4801
|
+
function writeJson(res, status, body) {
|
|
4802
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4803
|
+
res.end(JSON.stringify(body));
|
|
4804
|
+
}
|
|
4805
|
+
function writeErr(res, status, message) {
|
|
4806
|
+
writeJson(res, status, { error: { type: "admin_api_error", message } });
|
|
4807
|
+
}
|
|
4808
|
+
var SEARCH_MAX_BODY_BYTES = 64 * 1024;
|
|
4809
|
+
var SearchBodyTooLargeError = class extends Error {
|
|
4810
|
+
};
|
|
4811
|
+
async function readJsonBody2(req) {
|
|
4812
|
+
const chunks = [];
|
|
4813
|
+
let bytes = 0;
|
|
4814
|
+
for await (const chunk of req) {
|
|
4815
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4816
|
+
bytes += buffer.length;
|
|
4817
|
+
if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
|
|
4818
|
+
chunks.push(buffer);
|
|
4819
|
+
}
|
|
4820
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
4821
|
+
if (!raw.trim()) return {};
|
|
4822
|
+
try {
|
|
4823
|
+
const parsed = JSON.parse(raw);
|
|
4824
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4825
|
+
} catch {
|
|
4826
|
+
return {};
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
async function readBodyOrReject(req, res) {
|
|
4830
|
+
try {
|
|
4831
|
+
return await readJsonBody2(req);
|
|
4832
|
+
} catch (error) {
|
|
4833
|
+
if (error instanceof SearchBodyTooLargeError) {
|
|
4834
|
+
writeErr(res, 400, error.message);
|
|
4835
|
+
return void 0;
|
|
4836
|
+
}
|
|
4837
|
+
throw error;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
async function handleSearchAdmin(req, res, method, rest, deps) {
|
|
4841
|
+
if (rest.length === 1 && rest[0] === "diagnostics") {
|
|
4842
|
+
if (!deps.searchStatus) {
|
|
4843
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4844
|
+
}
|
|
4845
|
+
if (method !== "GET") {
|
|
4846
|
+
return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
|
|
4847
|
+
}
|
|
4848
|
+
return handleSearchDiagnostics(res, deps);
|
|
4849
|
+
}
|
|
4850
|
+
if (rest.length === 1 && rest[0] === "test") {
|
|
4851
|
+
if (!deps.searchStatus) {
|
|
4852
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4853
|
+
}
|
|
4854
|
+
if (method !== "POST") {
|
|
4855
|
+
return writeErr(res, 405, `method ${method} not allowed on search test`);
|
|
4856
|
+
}
|
|
4857
|
+
return handleSearchTest(req, res, deps);
|
|
4858
|
+
}
|
|
4859
|
+
if (rest.length === 1 && rest[0] === "query") {
|
|
4860
|
+
if (!deps.searchStatus) {
|
|
4861
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4862
|
+
}
|
|
4863
|
+
if (method !== "POST") {
|
|
4864
|
+
return writeErr(res, 405, `method ${method} not allowed on search query`);
|
|
4865
|
+
}
|
|
4866
|
+
return handleSearchQuery(req, res, deps);
|
|
4867
|
+
}
|
|
4868
|
+
return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
|
|
4869
|
+
}
|
|
4870
|
+
async function handleSearchDiagnostics(res, deps) {
|
|
4871
|
+
const status = deps.searchStatus;
|
|
4872
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4873
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4874
|
+
const rows = buildSearchDoctorSnapshot(
|
|
4875
|
+
status.runtime.listProviders(),
|
|
4876
|
+
search.providers
|
|
4877
|
+
);
|
|
4878
|
+
const snapshot = {
|
|
4879
|
+
rows,
|
|
4880
|
+
modes: {
|
|
4881
|
+
// codex is read from the LIVE config per request — an admin PUT has
|
|
4882
|
+
// already applied. responses/anthropic were captured at bootstrap.
|
|
4883
|
+
codex: search.modes.codex,
|
|
4884
|
+
responses: status.modes.responses,
|
|
4885
|
+
anthropic: status.modes.anthropic
|
|
4886
|
+
},
|
|
4887
|
+
applySemantics: { codex: "immediate", rest: "restart" }
|
|
4888
|
+
};
|
|
4889
|
+
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4890
|
+
}
|
|
4891
|
+
async function handleSearchTest(req, res, deps) {
|
|
4892
|
+
const status = deps.searchStatus;
|
|
4893
|
+
const body = await readBodyOrReject(req, res);
|
|
4894
|
+
if (body === void 0) return;
|
|
4895
|
+
const providerId = body["providerId"];
|
|
4896
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4897
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4898
|
+
}
|
|
4899
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4900
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4901
|
+
}
|
|
4902
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4903
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4904
|
+
const providers = search.providers;
|
|
4905
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4906
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4907
|
+
}
|
|
4908
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4909
|
+
const fetchImpl = status.testFetch;
|
|
4910
|
+
const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
|
|
4911
|
+
const contributions = [
|
|
4912
|
+
...builtinHttpSearchContributions3(transport),
|
|
4913
|
+
...apiSearchContributions2(search.providers, {
|
|
4914
|
+
egressPolicy,
|
|
4915
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
4916
|
+
})
|
|
4917
|
+
];
|
|
4918
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4919
|
+
if (!contribution) {
|
|
4920
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4921
|
+
}
|
|
4922
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4923
|
+
try {
|
|
4924
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4925
|
+
const diagnostic = classifyLiveSearchOutcome(
|
|
4926
|
+
contribution.id,
|
|
4927
|
+
{ kind: "results", count: results.length },
|
|
4928
|
+
checkedAt
|
|
4929
|
+
);
|
|
4930
|
+
const response = { diagnostic, resultCount: results.length };
|
|
4931
|
+
return writeJson(res, 200, { result: response });
|
|
4932
|
+
} catch (error) {
|
|
4933
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
4934
|
+
const response = { diagnostic };
|
|
4935
|
+
return writeJson(res, 200, { result: response });
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
async function handleSearchQuery(req, res, deps) {
|
|
4939
|
+
const status = deps.searchStatus;
|
|
4940
|
+
const body = await readBodyOrReject(req, res);
|
|
4941
|
+
if (body === void 0) return;
|
|
4942
|
+
const providerId = body["providerId"];
|
|
4943
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4944
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4945
|
+
}
|
|
4946
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4947
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4948
|
+
}
|
|
4949
|
+
const query2 = body["query"];
|
|
4950
|
+
if (typeof query2 !== "string" || query2.trim().length === 0) {
|
|
4951
|
+
return writeErr(res, 400, "query must be a non-empty string");
|
|
4952
|
+
}
|
|
4953
|
+
if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
|
|
4954
|
+
return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
|
|
4955
|
+
}
|
|
4956
|
+
if (QUERY_CONTROL_CHARS.test(query2)) {
|
|
4957
|
+
return writeErr(res, 400, "query must not contain control characters");
|
|
4958
|
+
}
|
|
4959
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4960
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4961
|
+
const providers = search.providers;
|
|
4962
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4963
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4964
|
+
}
|
|
4965
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4966
|
+
const fetchImpl = status.testFetch;
|
|
4967
|
+
const transport = fetchImpl ? createSearchHttpTransport({ fetch: fetchImpl, egressPolicy }) : void 0;
|
|
4968
|
+
const contributions = [
|
|
4969
|
+
...builtinHttpSearchContributions3(transport),
|
|
4970
|
+
...apiSearchContributions2(search.providers, {
|
|
4971
|
+
egressPolicy,
|
|
4972
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
4973
|
+
})
|
|
4974
|
+
];
|
|
4975
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4976
|
+
if (!contribution) {
|
|
4977
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4978
|
+
}
|
|
4979
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4980
|
+
try {
|
|
4981
|
+
const results = await contribution.provider.search(query2, { maxResults: 5 });
|
|
4982
|
+
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4983
|
+
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4984
|
+
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4985
|
+
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4986
|
+
}));
|
|
4987
|
+
const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
4988
|
+
contribution.id,
|
|
4989
|
+
{ kind: "results", count: sanitized.length },
|
|
4990
|
+
checkedAt
|
|
4991
|
+
);
|
|
4992
|
+
const response = {
|
|
4993
|
+
diagnostic,
|
|
4994
|
+
resultCount: sanitized.length,
|
|
4995
|
+
results: sanitized
|
|
4996
|
+
};
|
|
4997
|
+
return writeJson(res, 200, { result: response });
|
|
4998
|
+
} catch (error) {
|
|
4999
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
5000
|
+
const response = { diagnostic };
|
|
5001
|
+
return writeJson(res, 200, { result: response });
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
// src/admin/searchAdminView.ts
|
|
5006
|
+
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
5007
|
+
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
5008
|
+
function isRecord(value) {
|
|
5009
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5010
|
+
}
|
|
5011
|
+
function redactSearchServerConfig(search) {
|
|
5012
|
+
const providers = {};
|
|
5013
|
+
for (const [id, raw] of Object.entries(search.providers)) {
|
|
5014
|
+
const entry = raw;
|
|
5015
|
+
const view = {};
|
|
5016
|
+
if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
|
|
5017
|
+
if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
|
|
5018
|
+
if (API_KEY_PROVIDERS.has(id)) {
|
|
5019
|
+
view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
|
|
5020
|
+
}
|
|
5021
|
+
if (BASIC_AUTH_PROVIDERS.has(id)) {
|
|
5022
|
+
view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
|
|
5023
|
+
}
|
|
5024
|
+
providers[id] = view;
|
|
5025
|
+
}
|
|
5026
|
+
return {
|
|
5027
|
+
modes: search.modes,
|
|
5028
|
+
providers,
|
|
5029
|
+
egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
|
|
5030
|
+
policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
function storedSecrets(current, id) {
|
|
5034
|
+
const entry = current.providers[id];
|
|
5035
|
+
if (!entry) return {};
|
|
5036
|
+
const out = {};
|
|
5037
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
|
|
5038
|
+
if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
|
|
5039
|
+
out.basicAuthPassword = entry.basicAuthPassword;
|
|
5040
|
+
}
|
|
5041
|
+
return out;
|
|
5042
|
+
}
|
|
5043
|
+
function resolveSecretField(entry, field, stored) {
|
|
5044
|
+
if (!(field in entry)) {
|
|
5045
|
+
if (stored !== void 0) entry[field] = stored;
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
const value = entry[field];
|
|
5049
|
+
if (value === null) {
|
|
5050
|
+
delete entry[field];
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
5053
|
+
if (typeof value === "string" && value.trim().length > 0) return;
|
|
5054
|
+
if (stored !== void 0) entry[field] = stored;
|
|
5055
|
+
else delete entry[field];
|
|
5056
|
+
}
|
|
5057
|
+
function preserveSearchSecrets(incoming, current) {
|
|
5058
|
+
if (!isRecord(incoming)) return incoming;
|
|
5059
|
+
const section = { ...incoming };
|
|
5060
|
+
const providersValue = section["providers"];
|
|
5061
|
+
if (!isRecord(providersValue)) return section;
|
|
5062
|
+
const providers = {};
|
|
5063
|
+
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
5064
|
+
if (!isRecord(entryValue)) {
|
|
5065
|
+
providers[id] = entryValue;
|
|
5066
|
+
continue;
|
|
5067
|
+
}
|
|
5068
|
+
const entry = { ...entryValue };
|
|
5069
|
+
delete entry["apiKeyConfigured"];
|
|
5070
|
+
delete entry["basicAuthPasswordConfigured"];
|
|
5071
|
+
const stored = storedSecrets(current, id);
|
|
5072
|
+
resolveSecretField(entry, "apiKey", stored.apiKey);
|
|
5073
|
+
resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
|
|
5074
|
+
providers[id] = entry;
|
|
5075
|
+
}
|
|
5076
|
+
section["providers"] = providers;
|
|
5077
|
+
return section;
|
|
5078
|
+
}
|
|
5079
|
+
|
|
4588
5080
|
// src/admin/keyPolicyBody.ts
|
|
4589
5081
|
function parseKeyPolicyBody(body) {
|
|
4590
5082
|
const policy = {};
|
|
@@ -4647,7 +5139,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4647
5139
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4648
5140
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4649
5141
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4650
|
-
function
|
|
5142
|
+
function isRecord2(value) {
|
|
4651
5143
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4652
5144
|
}
|
|
4653
5145
|
function nonBlank(value) {
|
|
@@ -4667,7 +5159,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4667
5159
|
const ids = /* @__PURE__ */ new Set();
|
|
4668
5160
|
raw.forEach((entry, index) => {
|
|
4669
5161
|
const path2 = `bindings[${index}]`;
|
|
4670
|
-
if (!
|
|
5162
|
+
if (!isRecord2(entry)) {
|
|
4671
5163
|
errors.push(`${path2} must be an object`);
|
|
4672
5164
|
return;
|
|
4673
5165
|
}
|
|
@@ -4696,12 +5188,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4696
5188
|
} else if (entry.modelMappings.length > 100) {
|
|
4697
5189
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4698
5190
|
} else if (entry.modelMappings.some(
|
|
4699
|
-
(mapping) => !
|
|
5191
|
+
(mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4700
5192
|
)) {
|
|
4701
5193
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4702
5194
|
}
|
|
4703
5195
|
}
|
|
4704
|
-
if (!
|
|
5196
|
+
if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4705
5197
|
errors.push(`${path2}.target is invalid`);
|
|
4706
5198
|
} else {
|
|
4707
5199
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4716,7 +5208,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4716
5208
|
}
|
|
4717
5209
|
}
|
|
4718
5210
|
if (entry.modelMap !== void 0) {
|
|
4719
|
-
if (!
|
|
5211
|
+
if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4720
5212
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4721
5213
|
}
|
|
4722
5214
|
}
|
|
@@ -4737,19 +5229,19 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4737
5229
|
import {
|
|
4738
5230
|
generateVoucherCode,
|
|
4739
5231
|
hashVoucherCode,
|
|
4740
|
-
loadServerConfig,
|
|
5232
|
+
loadServerConfig as loadServerConfig2,
|
|
4741
5233
|
newVoucherId,
|
|
4742
5234
|
toVoucherInfo,
|
|
4743
5235
|
voucherCodePrefix
|
|
4744
5236
|
} from "@omnicross/core/outbound-api";
|
|
4745
|
-
function
|
|
5237
|
+
function writeJson2(res, status, body) {
|
|
4746
5238
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4747
5239
|
res.end(JSON.stringify(body));
|
|
4748
5240
|
}
|
|
4749
|
-
function
|
|
4750
|
-
|
|
5241
|
+
function writeErr2(res, status, message) {
|
|
5242
|
+
writeJson2(res, status, { error: { type: "voucher_error", message } });
|
|
4751
5243
|
}
|
|
4752
|
-
function
|
|
5244
|
+
function readJsonBody3(req) {
|
|
4753
5245
|
return new Promise((resolve11, reject) => {
|
|
4754
5246
|
const chunks = [];
|
|
4755
5247
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -4800,26 +5292,26 @@ function parseVoucherCreateBody(body) {
|
|
|
4800
5292
|
return { ok: true, input };
|
|
4801
5293
|
}
|
|
4802
5294
|
async function voucherEnabled(deps) {
|
|
4803
|
-
const config = await
|
|
5295
|
+
const config = await loadServerConfig2(deps.settingsStore);
|
|
4804
5296
|
return config.voucher?.enabled === true;
|
|
4805
5297
|
}
|
|
4806
5298
|
async function handleVoucher(req, res, method, rest, deps) {
|
|
4807
5299
|
const voucherDb = deps.voucherDb;
|
|
4808
|
-
if (!voucherDb) return
|
|
5300
|
+
if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
|
|
4809
5301
|
if (method === "GET" && rest.length === 0) {
|
|
4810
5302
|
const rows = await voucherDb.voucherList();
|
|
4811
|
-
return
|
|
5303
|
+
return writeJson2(res, 200, { vouchers: rows.map(toVoucherInfo) });
|
|
4812
5304
|
}
|
|
4813
5305
|
if (method === "POST" && rest.length === 0) {
|
|
4814
|
-
if (!await voucherEnabled(deps)) return
|
|
5306
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4815
5307
|
let body;
|
|
4816
5308
|
try {
|
|
4817
|
-
body = await
|
|
5309
|
+
body = await readJsonBody3(req);
|
|
4818
5310
|
} catch {
|
|
4819
|
-
return
|
|
5311
|
+
return writeErr2(res, 400, "Invalid JSON in request body");
|
|
4820
5312
|
}
|
|
4821
5313
|
const parsed = parseVoucherCreateBody(body);
|
|
4822
|
-
if (!parsed.ok) return
|
|
5314
|
+
if (!parsed.ok) return writeErr2(res, 400, parsed.message);
|
|
4823
5315
|
const code = generateVoucherCode();
|
|
4824
5316
|
const created = await voucherDb.voucherCreate({
|
|
4825
5317
|
id: newVoucherId(),
|
|
@@ -4827,7 +5319,7 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4827
5319
|
codePrefix: voucherCodePrefix(code),
|
|
4828
5320
|
...parsed.input
|
|
4829
5321
|
});
|
|
4830
|
-
return
|
|
5322
|
+
return writeJson2(res, 201, {
|
|
4831
5323
|
id: created.id,
|
|
4832
5324
|
codePrefix: created.codePrefix,
|
|
4833
5325
|
type: created.type,
|
|
@@ -4838,11 +5330,11 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4838
5330
|
}
|
|
4839
5331
|
const id = rest[0];
|
|
4840
5332
|
if (method === "POST" && id && rest[1] === "revoke") {
|
|
4841
|
-
if (!await voucherEnabled(deps)) return
|
|
5333
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4842
5334
|
const ok = await voucherDb.voucherRevokeCas(id, Date.now());
|
|
4843
|
-
return
|
|
5335
|
+
return writeJson2(res, ok ? 200 : 409, { ok });
|
|
4844
5336
|
}
|
|
4845
|
-
return
|
|
5337
|
+
return writeErr2(res, 405, `method ${method} not allowed on voucher`);
|
|
4846
5338
|
}
|
|
4847
5339
|
|
|
4848
5340
|
// src/admin/webhookConfigBody.ts
|
|
@@ -5756,12 +6248,12 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5756
6248
|
}
|
|
5757
6249
|
|
|
5758
6250
|
// src/admin/accountAllowanceApi.ts
|
|
5759
|
-
function
|
|
6251
|
+
function writeJson3(res, status, body) {
|
|
5760
6252
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5761
6253
|
res.end(JSON.stringify(body));
|
|
5762
6254
|
}
|
|
5763
6255
|
function writeError2(res, status, message) {
|
|
5764
|
-
|
|
6256
|
+
writeJson3(res, status, { error: { type: "account_allowance_error", message } });
|
|
5765
6257
|
}
|
|
5766
6258
|
function readJson2(req) {
|
|
5767
6259
|
return new Promise((resolve11, reject) => {
|
|
@@ -5794,7 +6286,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5794
6286
|
if (!service.getSchedulingStatus) {
|
|
5795
6287
|
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
5796
6288
|
}
|
|
5797
|
-
return
|
|
6289
|
+
return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
5798
6290
|
}
|
|
5799
6291
|
if (method === "GET") {
|
|
5800
6292
|
const params = query(req);
|
|
@@ -5803,7 +6295,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5803
6295
|
if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
|
|
5804
6296
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5805
6297
|
const allowances = await service.list({ providerId, accountId });
|
|
5806
|
-
return
|
|
6298
|
+
return writeJson3(res, 200, { allowances });
|
|
5807
6299
|
}
|
|
5808
6300
|
if (method === "POST" && rest[0] === "refresh") {
|
|
5809
6301
|
const body = await readJson2(req);
|
|
@@ -5818,7 +6310,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5818
6310
|
if (accountId && allowances.length === 0) {
|
|
5819
6311
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
5820
6312
|
}
|
|
5821
|
-
return
|
|
6313
|
+
return writeJson3(res, 200, { allowances });
|
|
5822
6314
|
}
|
|
5823
6315
|
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
5824
6316
|
}
|
|
@@ -5837,7 +6329,7 @@ function readBody(req) {
|
|
|
5837
6329
|
req.on("error", reject);
|
|
5838
6330
|
});
|
|
5839
6331
|
}
|
|
5840
|
-
async function
|
|
6332
|
+
async function readJsonBody4(req) {
|
|
5841
6333
|
const raw = await readBody(req);
|
|
5842
6334
|
if (!raw.trim()) return {};
|
|
5843
6335
|
try {
|
|
@@ -5847,12 +6339,12 @@ async function readJsonBody3(req) {
|
|
|
5847
6339
|
return {};
|
|
5848
6340
|
}
|
|
5849
6341
|
}
|
|
5850
|
-
function
|
|
6342
|
+
function writeJson4(res, status, body) {
|
|
5851
6343
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5852
6344
|
res.end(JSON.stringify(body));
|
|
5853
6345
|
}
|
|
5854
6346
|
function writeJsonError(res, status, message) {
|
|
5855
|
-
|
|
6347
|
+
writeJson4(res, status, { error: { type: "admin_api_error", message } });
|
|
5856
6348
|
}
|
|
5857
6349
|
function maskProviderApiKey(apiKey) {
|
|
5858
6350
|
if (!apiKey) return "";
|
|
@@ -5959,6 +6451,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5959
6451
|
return await handleServer(req, res, method, deps);
|
|
5960
6452
|
case "images":
|
|
5961
6453
|
return await handleImages(res, method, rest, deps);
|
|
6454
|
+
case "search":
|
|
6455
|
+
return await handleSearchAdmin(req, res, method, rest, deps);
|
|
5962
6456
|
case "accounts":
|
|
5963
6457
|
return await handleAccounts(req, res, method, rest, deps);
|
|
5964
6458
|
case "cli":
|
|
@@ -5992,7 +6486,7 @@ function requestQuery(req) {
|
|
|
5992
6486
|
return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
|
|
5993
6487
|
}
|
|
5994
6488
|
function writeResult(res, result) {
|
|
5995
|
-
|
|
6489
|
+
writeJson4(res, result.status, result.body);
|
|
5996
6490
|
}
|
|
5997
6491
|
async function handleUsage(req, res, method, rest, deps) {
|
|
5998
6492
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
|
|
@@ -6001,13 +6495,13 @@ async function handleUsage(req, res, method, rest, deps) {
|
|
|
6001
6495
|
async function handleDashboardRoute(res, method, deps) {
|
|
6002
6496
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
|
|
6003
6497
|
const result = await handleDashboard(deps);
|
|
6004
|
-
return
|
|
6498
|
+
return writeJson4(res, result.status, result.body);
|
|
6005
6499
|
}
|
|
6006
6500
|
async function handlePricing(req, res, method, rest, deps) {
|
|
6007
6501
|
if (rest.length === 0) {
|
|
6008
6502
|
if (method === "GET") return writeResult(res, await handlePricingList(deps));
|
|
6009
6503
|
if (method === "PUT") {
|
|
6010
|
-
return writeResult(res, await handlePricingUpsert(await
|
|
6504
|
+
return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
|
|
6011
6505
|
}
|
|
6012
6506
|
if (method === "DELETE") {
|
|
6013
6507
|
return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
|
|
@@ -6018,7 +6512,7 @@ async function handlePricing(req, res, method, rest, deps) {
|
|
|
6018
6512
|
return writeResult(res, await handlePricingFetchLatest(deps));
|
|
6019
6513
|
}
|
|
6020
6514
|
if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
|
|
6021
|
-
return writeResult(res, await handlePricingResolveConflicts(await
|
|
6515
|
+
return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
|
|
6022
6516
|
}
|
|
6023
6517
|
return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
|
|
6024
6518
|
}
|
|
@@ -6032,15 +6526,15 @@ function migrationDeps(deps) {
|
|
|
6032
6526
|
}
|
|
6033
6527
|
async function handleMigrationExport(req, res, method, deps) {
|
|
6034
6528
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
|
|
6035
|
-
const body = await
|
|
6529
|
+
const body = await readJsonBody4(req);
|
|
6036
6530
|
const result = await handleExport(body, migrationDeps(deps));
|
|
6037
|
-
return
|
|
6531
|
+
return writeJson4(res, result.status, result.body);
|
|
6038
6532
|
}
|
|
6039
6533
|
async function handleMigrationImport(req, res, method, deps) {
|
|
6040
6534
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
|
|
6041
|
-
const body = await
|
|
6535
|
+
const body = await readJsonBody4(req);
|
|
6042
6536
|
const result = await handleImport(body, migrationDeps(deps));
|
|
6043
|
-
return
|
|
6537
|
+
return writeJson4(res, result.status, result.body);
|
|
6044
6538
|
}
|
|
6045
6539
|
async function handleProviders(req, res, method, rest, deps) {
|
|
6046
6540
|
const cfg = loadConfig(deps.configPath);
|
|
@@ -6071,13 +6565,13 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6071
6565
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
|
|
6072
6566
|
const row = cfg.providers.find((p) => p.id === rest[0]);
|
|
6073
6567
|
if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
|
|
6074
|
-
return
|
|
6568
|
+
return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
|
|
6075
6569
|
}
|
|
6076
6570
|
if (method === "GET") {
|
|
6077
|
-
return
|
|
6571
|
+
return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
|
|
6078
6572
|
}
|
|
6079
6573
|
if (method === "POST") {
|
|
6080
|
-
const body = await
|
|
6574
|
+
const body = await readJsonBody4(req);
|
|
6081
6575
|
const provider = parseProviderInput(body, void 0);
|
|
6082
6576
|
if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
|
|
6083
6577
|
if (cfg.providers.some((p) => p.id === provider.id)) {
|
|
@@ -6085,25 +6579,25 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6085
6579
|
}
|
|
6086
6580
|
cfg.providers.push(provider);
|
|
6087
6581
|
persistProviders(cfg, deps);
|
|
6088
|
-
return
|
|
6582
|
+
return writeJson4(res, 201, { provider: toProviderView(provider) });
|
|
6089
6583
|
}
|
|
6090
6584
|
const id = rest[0];
|
|
6091
6585
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6092
6586
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
6093
6587
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6094
6588
|
if (method === "PUT") {
|
|
6095
|
-
const body = await
|
|
6589
|
+
const body = await readJsonBody4(req);
|
|
6096
6590
|
const existing = cfg.providers[idx];
|
|
6097
6591
|
const updated = parseProviderInput(body, existing);
|
|
6098
6592
|
if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
|
|
6099
6593
|
cfg.providers[idx] = updated;
|
|
6100
6594
|
persistProviders(cfg, deps);
|
|
6101
|
-
return
|
|
6595
|
+
return writeJson4(res, 200, { provider: toProviderView(updated) });
|
|
6102
6596
|
}
|
|
6103
6597
|
if (method === "DELETE") {
|
|
6104
6598
|
cfg.providers.splice(idx, 1);
|
|
6105
6599
|
persistProviders(cfg, deps);
|
|
6106
|
-
return
|
|
6600
|
+
return writeJson4(res, 200, { ok: true });
|
|
6107
6601
|
}
|
|
6108
6602
|
return writeJsonError(res, 405, `method ${method} not allowed on providers`);
|
|
6109
6603
|
}
|
|
@@ -6112,7 +6606,7 @@ function persistProviders(cfg, deps) {
|
|
|
6112
6606
|
deps.llmConfig.reload(cfg);
|
|
6113
6607
|
}
|
|
6114
6608
|
async function handleProviderReorder(req, res, cfg, deps) {
|
|
6115
|
-
const body = await
|
|
6609
|
+
const body = await readJsonBody4(req);
|
|
6116
6610
|
const rawOrder = body["order"];
|
|
6117
6611
|
if (!Array.isArray(rawOrder)) {
|
|
6118
6612
|
return writeJsonError(res, 400, "reorder requires { order: string[] }");
|
|
@@ -6136,14 +6630,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
6136
6630
|
}
|
|
6137
6631
|
cfg.providers = reordered;
|
|
6138
6632
|
persistProviders(cfg, deps);
|
|
6139
|
-
return
|
|
6633
|
+
return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
6140
6634
|
}
|
|
6141
6635
|
async function handleDiscoverModels(res, id, cfg) {
|
|
6142
6636
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6143
6637
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6144
6638
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6145
6639
|
if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
|
|
6146
|
-
return
|
|
6640
|
+
return writeJson4(res, 200, { models: [], unsupportedFormat: true });
|
|
6147
6641
|
}
|
|
6148
6642
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6149
6643
|
const base = row.baseUrl.replace(/\/+$/, "");
|
|
@@ -6160,32 +6654,32 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6160
6654
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6161
6655
|
} catch {
|
|
6162
6656
|
}
|
|
6163
|
-
return
|
|
6657
|
+
return writeJson4(res, 200, {
|
|
6164
6658
|
models: [],
|
|
6165
6659
|
error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
|
|
6166
6660
|
});
|
|
6167
6661
|
}
|
|
6168
6662
|
const data = await response.json();
|
|
6169
6663
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6170
|
-
return
|
|
6664
|
+
return writeJson4(res, 200, { models });
|
|
6171
6665
|
} catch (err5) {
|
|
6172
6666
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6173
|
-
return
|
|
6667
|
+
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6174
6668
|
}
|
|
6175
6669
|
}
|
|
6176
6670
|
async function handleTestModel(req, res, id, cfg) {
|
|
6177
6671
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6178
6672
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6179
6673
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6180
|
-
const body = await
|
|
6674
|
+
const body = await readJsonBody4(req);
|
|
6181
6675
|
const model = typeof body["model"] === "string" ? body["model"].trim() : "";
|
|
6182
6676
|
if (!model) return writeJsonError(res, 400, "test requires a { model } string");
|
|
6183
6677
|
if (row.apiFormat === "gemini") {
|
|
6184
|
-
return
|
|
6678
|
+
return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
|
|
6185
6679
|
}
|
|
6186
6680
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6187
6681
|
if (!resolvedKey) {
|
|
6188
|
-
return
|
|
6682
|
+
return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
|
|
6189
6683
|
}
|
|
6190
6684
|
let url = row.baseUrl.replace(/\/+$/, "");
|
|
6191
6685
|
const prompt = "Reply with the single word: OK.";
|
|
@@ -6224,9 +6718,9 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6224
6718
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6225
6719
|
} catch {
|
|
6226
6720
|
}
|
|
6227
|
-
return
|
|
6721
|
+
return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
|
|
6228
6722
|
}
|
|
6229
|
-
return
|
|
6723
|
+
return writeJson4(res, 200, {
|
|
6230
6724
|
ok: true,
|
|
6231
6725
|
status: response.status,
|
|
6232
6726
|
latencyMs,
|
|
@@ -6234,7 +6728,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6234
6728
|
});
|
|
6235
6729
|
} catch (err5) {
|
|
6236
6730
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6237
|
-
return
|
|
6731
|
+
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6238
6732
|
}
|
|
6239
6733
|
}
|
|
6240
6734
|
function extractSampleText(text, apiFormat) {
|
|
@@ -6275,7 +6769,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6275
6769
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6276
6770
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6277
6771
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6278
|
-
return
|
|
6772
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6279
6773
|
}
|
|
6280
6774
|
function parsePoolKeyInput(body, existing) {
|
|
6281
6775
|
const out = {};
|
|
@@ -6294,7 +6788,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6294
6788
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6295
6789
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
6296
6790
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6297
|
-
const body = await
|
|
6791
|
+
const body = await readJsonBody4(req);
|
|
6298
6792
|
const parsed = parsePoolKeyInput(body);
|
|
6299
6793
|
if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
|
|
6300
6794
|
const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -6306,7 +6800,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6306
6800
|
row.apiKeys = [...row.apiKeys ?? [], entry];
|
|
6307
6801
|
persistProviders(cfg, deps);
|
|
6308
6802
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6309
|
-
return
|
|
6803
|
+
return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6310
6804
|
}
|
|
6311
6805
|
async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6312
6806
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6316,7 +6810,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6316
6810
|
const row = cfg.providers[idx];
|
|
6317
6811
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6318
6812
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6319
|
-
const body = await
|
|
6813
|
+
const body = await readJsonBody4(req);
|
|
6320
6814
|
const existing = row.apiKeys[keyIdx];
|
|
6321
6815
|
const parsed = parsePoolKeyInput(body, existing);
|
|
6322
6816
|
const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
|
|
@@ -6326,7 +6820,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6326
6820
|
row.apiKeys[keyIdx] = entry;
|
|
6327
6821
|
persistProviders(cfg, deps);
|
|
6328
6822
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6329
|
-
return
|
|
6823
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6330
6824
|
}
|
|
6331
6825
|
async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
6332
6826
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6340,7 +6834,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
|
6340
6834
|
if (row.apiKeys.length === 0) row.apiKeys = void 0;
|
|
6341
6835
|
persistProviders(cfg, deps);
|
|
6342
6836
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6343
|
-
return
|
|
6837
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6344
6838
|
}
|
|
6345
6839
|
async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6346
6840
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6350,11 +6844,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6350
6844
|
const row = cfg.providers[idx];
|
|
6351
6845
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6352
6846
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6353
|
-
const body = await
|
|
6847
|
+
const body = await readJsonBody4(req);
|
|
6354
6848
|
row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
|
|
6355
6849
|
persistProviders(cfg, deps);
|
|
6356
6850
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6357
|
-
return
|
|
6851
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6358
6852
|
}
|
|
6359
6853
|
function parseApiKeysInput(raw, existing) {
|
|
6360
6854
|
if (!Array.isArray(raw)) return existing;
|
|
@@ -6540,13 +7034,13 @@ function handlePresets(res, method) {
|
|
|
6540
7034
|
website: p.website,
|
|
6541
7035
|
modelsEndpoint: p.modelsEndpoint
|
|
6542
7036
|
}));
|
|
6543
|
-
return
|
|
7037
|
+
return writeJson4(res, 200, { presets, excluded });
|
|
6544
7038
|
}
|
|
6545
7039
|
async function handleKeys(req, res, method, rest, deps) {
|
|
6546
7040
|
if (method === "GET" && rest.length === 0) {
|
|
6547
7041
|
const rows = await deps.keyDb.outboundApiKeysList();
|
|
6548
7042
|
const reader = deps.keySpendReader;
|
|
6549
|
-
if (!reader) return
|
|
7043
|
+
if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
|
|
6550
7044
|
const now = Date.now();
|
|
6551
7045
|
const keys = await Promise.all(
|
|
6552
7046
|
rows.map(async (row) => {
|
|
@@ -6558,13 +7052,13 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6558
7052
|
return info;
|
|
6559
7053
|
})
|
|
6560
7054
|
);
|
|
6561
|
-
return
|
|
7055
|
+
return writeJson4(res, 200, { keys });
|
|
6562
7056
|
}
|
|
6563
7057
|
if (method === "POST" && rest.length === 0) {
|
|
6564
|
-
const body = await
|
|
7058
|
+
const body = await readJsonBody4(req);
|
|
6565
7059
|
const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
|
|
6566
7060
|
const created = await createNamedKey(deps.keyDb, name);
|
|
6567
|
-
return
|
|
7061
|
+
return writeJson4(res, 201, {
|
|
6568
7062
|
id: created.id,
|
|
6569
7063
|
name: created.name,
|
|
6570
7064
|
keyPrefix: created.keyPrefix,
|
|
@@ -6575,7 +7069,7 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6575
7069
|
}
|
|
6576
7070
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
6577
7071
|
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
6578
|
-
if (revealed !== null) return
|
|
7072
|
+
if (revealed !== null) return writeJson4(res, 200, { key: revealed });
|
|
6579
7073
|
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
6580
7074
|
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
6581
7075
|
return writeJsonError(
|
|
@@ -6590,26 +7084,26 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6590
7084
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6591
7085
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6592
7086
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
6593
|
-
return
|
|
7087
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6594
7088
|
}
|
|
6595
7089
|
if (method === "DELETE" && id && !action) {
|
|
6596
7090
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6597
7091
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6598
7092
|
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
6599
|
-
return
|
|
7093
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6600
7094
|
}
|
|
6601
7095
|
if (method === "POST" && id && action === "enabled") {
|
|
6602
|
-
const body = await
|
|
7096
|
+
const body = await readJsonBody4(req);
|
|
6603
7097
|
const enabled = body["enabled"] === true;
|
|
6604
7098
|
if (!enabled) {
|
|
6605
7099
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6606
7100
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6607
7101
|
}
|
|
6608
7102
|
const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
|
|
6609
|
-
return
|
|
7103
|
+
return writeJson4(res, ok ? 200 : 404, { ok, enabled });
|
|
6610
7104
|
}
|
|
6611
7105
|
if (method === "POST" && id && action === "permissions") {
|
|
6612
|
-
const body = await
|
|
7106
|
+
const body = await readJsonBody4(req);
|
|
6613
7107
|
if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
|
|
6614
7108
|
return writeJsonError(res, 400, "body must contain only permissions");
|
|
6615
7109
|
}
|
|
@@ -6624,19 +7118,19 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6624
7118
|
);
|
|
6625
7119
|
}
|
|
6626
7120
|
const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6627
|
-
if (!before) return
|
|
6628
|
-
if (before.revokedAt !== null) return
|
|
7121
|
+
if (!before) return writeJson4(res, 404, { ok: false });
|
|
7122
|
+
if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
|
|
6629
7123
|
const required = await integrationKeyRequirement(deps, id, permissions);
|
|
6630
7124
|
if (required) return writeJsonError(res, 409, required);
|
|
6631
7125
|
const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
|
|
6632
7126
|
if (!ok) {
|
|
6633
7127
|
const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6634
|
-
return
|
|
7128
|
+
return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
|
|
6635
7129
|
}
|
|
6636
|
-
return
|
|
7130
|
+
return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
|
|
6637
7131
|
}
|
|
6638
7132
|
if (method === "POST" && id && action === "max-concurrency") {
|
|
6639
|
-
const body = await
|
|
7133
|
+
const body = await readJsonBody4(req);
|
|
6640
7134
|
const raw = body["maxConcurrency"];
|
|
6641
7135
|
let value;
|
|
6642
7136
|
if (raw === null) {
|
|
@@ -6651,14 +7145,14 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6651
7145
|
);
|
|
6652
7146
|
}
|
|
6653
7147
|
const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
|
|
6654
|
-
return
|
|
7148
|
+
return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
|
|
6655
7149
|
}
|
|
6656
7150
|
if (method === "POST" && id && action === "policy") {
|
|
6657
|
-
const body = await
|
|
7151
|
+
const body = await readJsonBody4(req);
|
|
6658
7152
|
const parsed = parseKeyPolicyBody(body);
|
|
6659
7153
|
if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
|
|
6660
7154
|
const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
|
|
6661
|
-
return
|
|
7155
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6662
7156
|
}
|
|
6663
7157
|
return writeJsonError(res, 405, `method ${method} not allowed on keys`);
|
|
6664
7158
|
}
|
|
@@ -6749,7 +7243,8 @@ function outboundServerConfigInput(config) {
|
|
|
6749
7243
|
userMessageQueue: config.userMessageQueue,
|
|
6750
7244
|
concurrencyQueue: config.concurrencyQueue,
|
|
6751
7245
|
voucher: config.voucher,
|
|
6752
|
-
anthropic: config.anthropic
|
|
7246
|
+
anthropic: config.anthropic,
|
|
7247
|
+
search: config.search
|
|
6753
7248
|
};
|
|
6754
7249
|
}
|
|
6755
7250
|
function projectImagesConfigForAdmin(config) {
|
|
@@ -6812,15 +7307,21 @@ function currentImageGenerationId(deps) {
|
|
|
6812
7307
|
}
|
|
6813
7308
|
async function handleServer(req, res, method, deps) {
|
|
6814
7309
|
if (method === "GET") {
|
|
6815
|
-
const config = await
|
|
7310
|
+
const config = await loadServerConfig3(deps.settingsStore);
|
|
6816
7311
|
let server = config;
|
|
6817
7312
|
if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
|
|
6818
7313
|
if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
|
|
6819
7314
|
if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
|
|
6820
|
-
|
|
7315
|
+
if (config.search) {
|
|
7316
|
+
server = {
|
|
7317
|
+
...server,
|
|
7318
|
+
search: redactSearchServerConfig(config.search)
|
|
7319
|
+
};
|
|
7320
|
+
}
|
|
7321
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
|
|
6821
7322
|
}
|
|
6822
7323
|
if (method === "PUT") {
|
|
6823
|
-
const patch = await
|
|
7324
|
+
const patch = await readJsonBody4(req);
|
|
6824
7325
|
const queueErrors = validateQueueSegments(patch);
|
|
6825
7326
|
if (queueErrors.length > 0) {
|
|
6826
7327
|
return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
|
|
@@ -6849,7 +7350,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
6849
7350
|
if (billingErrors.length > 0) {
|
|
6850
7351
|
return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
|
|
6851
7352
|
}
|
|
6852
|
-
const current = await
|
|
7353
|
+
const current = await loadServerConfig3(deps.settingsStore);
|
|
6853
7354
|
let effectivePatch = patch;
|
|
6854
7355
|
if (patch.proxy) {
|
|
6855
7356
|
effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
|
|
@@ -6870,6 +7371,20 @@ async function handleServer(req, res, method, deps) {
|
|
|
6870
7371
|
}
|
|
6871
7372
|
effectivePatch = { ...effectivePatch, images };
|
|
6872
7373
|
}
|
|
7374
|
+
if (patch.search !== void 0) {
|
|
7375
|
+
const searchPatch = preserveSearchSecrets(
|
|
7376
|
+
patch.search,
|
|
7377
|
+
current.search ?? DEFAULT_SEARCH_SERVER_CONFIG2
|
|
7378
|
+
);
|
|
7379
|
+
const searchErrors = validateSearchServerConfig(searchPatch);
|
|
7380
|
+
if (searchErrors.length > 0) {
|
|
7381
|
+
return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
|
|
7382
|
+
}
|
|
7383
|
+
effectivePatch = {
|
|
7384
|
+
...effectivePatch,
|
|
7385
|
+
search: searchPatch
|
|
7386
|
+
};
|
|
7387
|
+
}
|
|
6873
7388
|
const merged = mergeServerConfig(current, effectivePatch);
|
|
6874
7389
|
const priorImageGenerationId = currentImageGenerationId(deps);
|
|
6875
7390
|
try {
|
|
@@ -6922,7 +7437,11 @@ async function handleServer(req, res, method, deps) {
|
|
|
6922
7437
|
} catch {
|
|
6923
7438
|
}
|
|
6924
7439
|
}
|
|
6925
|
-
|
|
7440
|
+
const mergedForAdmin = merged.search ? {
|
|
7441
|
+
...merged,
|
|
7442
|
+
search: redactSearchServerConfig(merged.search)
|
|
7443
|
+
} : merged;
|
|
7444
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
|
|
6926
7445
|
}
|
|
6927
7446
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
6928
7447
|
}
|
|
@@ -6939,7 +7458,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6939
7458
|
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
6940
7459
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
6941
7460
|
});
|
|
6942
|
-
return
|
|
7461
|
+
return writeJson4(res, 200, {
|
|
6943
7462
|
available: true,
|
|
6944
7463
|
records,
|
|
6945
7464
|
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
@@ -6955,7 +7474,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6955
7474
|
providerId: query2.get("providerId") ?? void 0,
|
|
6956
7475
|
accountId: query2.get("accountId") ?? void 0
|
|
6957
7476
|
});
|
|
6958
|
-
return
|
|
7477
|
+
return writeJson4(res, 200, {
|
|
6959
7478
|
available: true,
|
|
6960
7479
|
entries,
|
|
6961
7480
|
collectedAt: Date.now()
|
|
@@ -6974,10 +7493,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6974
7493
|
const accounts = await deps.subscriptionAccounts.listAll();
|
|
6975
7494
|
const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
6976
7495
|
const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
|
|
6977
|
-
return
|
|
7496
|
+
return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
|
|
6978
7497
|
}
|
|
6979
7498
|
if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
|
|
6980
|
-
const body = await
|
|
7499
|
+
const body = await readJsonBody4(req);
|
|
6981
7500
|
const parsed = validateAccountBatchBody(body);
|
|
6982
7501
|
if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
|
|
6983
7502
|
const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
|
|
@@ -6993,15 +7512,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6993
7512
|
deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
|
|
6994
7513
|
}
|
|
6995
7514
|
}
|
|
6996
|
-
return
|
|
7515
|
+
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6997
7516
|
}
|
|
6998
7517
|
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6999
7518
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
7000
|
-
return
|
|
7519
|
+
return writeJson4(res, result.status, result.body);
|
|
7001
7520
|
}
|
|
7002
7521
|
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
7003
7522
|
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
7004
|
-
return
|
|
7523
|
+
return writeJson4(res, result.status, result.body);
|
|
7005
7524
|
}
|
|
7006
7525
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
7007
7526
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7023,7 +7542,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7023
7542
|
resumeAt: entry.resumeAt
|
|
7024
7543
|
})) ?? [];
|
|
7025
7544
|
const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
|
|
7026
|
-
return
|
|
7545
|
+
return writeJson4(res, 200, { diagnostics });
|
|
7027
7546
|
}
|
|
7028
7547
|
if (method === "GET" && rest.length === 3 && rest[2] === "events") {
|
|
7029
7548
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7035,17 +7554,17 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7035
7554
|
}
|
|
7036
7555
|
const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
|
|
7037
7556
|
const diagnostics = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
|
|
7038
|
-
return
|
|
7557
|
+
return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
|
|
7039
7558
|
}
|
|
7040
7559
|
if (method === "PATCH" && rest.length === 2) {
|
|
7041
7560
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
7042
7561
|
if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
7043
|
-
const body = await
|
|
7562
|
+
const body = await readJsonBody4(req);
|
|
7044
7563
|
const patch = validateAccountMetadataPatch(body);
|
|
7045
7564
|
if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
|
|
7046
7565
|
const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
|
|
7047
7566
|
if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
|
|
7048
|
-
return
|
|
7567
|
+
return writeJson4(res, 200, { ok: true });
|
|
7049
7568
|
}
|
|
7050
7569
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
7051
7570
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -7054,15 +7573,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7054
7573
|
}
|
|
7055
7574
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
7056
7575
|
const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
|
|
7057
|
-
return
|
|
7576
|
+
return writeJson4(res, result.status, result.body);
|
|
7058
7577
|
}
|
|
7059
7578
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
7060
|
-
const body2 = await
|
|
7579
|
+
const body2 = await readJsonBody4(req);
|
|
7061
7580
|
const result = await handleOAuthComplete(providerId, body2, deps);
|
|
7062
|
-
return
|
|
7581
|
+
return writeJson4(res, result.status, result.body);
|
|
7063
7582
|
}
|
|
7064
7583
|
if (method === "POST" && rest[1] === "accounts") {
|
|
7065
|
-
const body2 = await
|
|
7584
|
+
const body2 = await readJsonBody4(req);
|
|
7066
7585
|
const block = validateTokenBody(providerId, body2);
|
|
7067
7586
|
if (!block) {
|
|
7068
7587
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
@@ -7070,20 +7589,20 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7070
7589
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
7071
7590
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
7072
7591
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7073
|
-
return
|
|
7592
|
+
return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
|
|
7074
7593
|
}
|
|
7075
7594
|
if (method === "POST" && rest[1] === "import-external") {
|
|
7076
7595
|
if (providerId !== "claude" && providerId !== "codex") {
|
|
7077
7596
|
return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
|
|
7078
7597
|
}
|
|
7079
|
-
const body2 = await
|
|
7598
|
+
const body2 = await readJsonBody4(req);
|
|
7080
7599
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
7081
7600
|
const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
|
|
7082
7601
|
if (!result.ok) {
|
|
7083
7602
|
return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
|
|
7084
7603
|
}
|
|
7085
7604
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7086
|
-
return
|
|
7605
|
+
return writeJson4(res, 200, {
|
|
7087
7606
|
ok: true,
|
|
7088
7607
|
account: status2 ?? void 0,
|
|
7089
7608
|
nativeCredentialMode: result.nativeCredentialMode,
|
|
@@ -7098,7 +7617,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7098
7617
|
const writer2 = deps.subscriptionTokenWriter;
|
|
7099
7618
|
const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
|
|
7100
7619
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7101
|
-
return
|
|
7620
|
+
return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
|
|
7102
7621
|
}
|
|
7103
7622
|
if (method === "POST" && rest.length === 3 && rest[2] === "test") {
|
|
7104
7623
|
const accountId = rest[1];
|
|
@@ -7108,7 +7627,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7108
7627
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7109
7628
|
}
|
|
7110
7629
|
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
7111
|
-
return
|
|
7630
|
+
return writeJson4(res, 200, {
|
|
7112
7631
|
ok: result.ok,
|
|
7113
7632
|
marked: result.marked,
|
|
7114
7633
|
tier: result.tier,
|
|
@@ -7117,15 +7636,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7117
7636
|
}
|
|
7118
7637
|
if (method === "POST" && rest[2] === "label") {
|
|
7119
7638
|
const accountId = rest[1];
|
|
7120
|
-
const body2 = await
|
|
7639
|
+
const body2 = await readJsonBody4(req);
|
|
7121
7640
|
const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
|
|
7122
7641
|
const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
|
|
7123
7642
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7124
|
-
return
|
|
7643
|
+
return writeJson4(res, 200, { ok: true });
|
|
7125
7644
|
}
|
|
7126
7645
|
if (method === "POST" && rest[2] === "priority") {
|
|
7127
7646
|
const accountId = rest[1];
|
|
7128
|
-
const body2 = await
|
|
7647
|
+
const body2 = await readJsonBody4(req);
|
|
7129
7648
|
const raw = body2["priority"];
|
|
7130
7649
|
const priority = typeof raw === "number" ? raw : Number(raw);
|
|
7131
7650
|
if (!Number.isFinite(priority)) {
|
|
@@ -7133,11 +7652,11 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7133
7652
|
}
|
|
7134
7653
|
const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
|
|
7135
7654
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7136
|
-
return
|
|
7655
|
+
return writeJson4(res, 200, { ok: true });
|
|
7137
7656
|
}
|
|
7138
7657
|
if (method === "POST" && rest[2] === "proxy") {
|
|
7139
7658
|
const accountId = rest[1];
|
|
7140
|
-
const body2 = await
|
|
7659
|
+
const body2 = await readJsonBody4(req);
|
|
7141
7660
|
const rawProxy = body2["proxy"];
|
|
7142
7661
|
let proxy;
|
|
7143
7662
|
if (rawProxy !== null && rawProxy !== void 0) {
|
|
@@ -7146,63 +7665,63 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7146
7665
|
}
|
|
7147
7666
|
const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
|
|
7148
7667
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7149
|
-
return
|
|
7668
|
+
return writeJson4(res, 200, { ok: true });
|
|
7150
7669
|
}
|
|
7151
7670
|
if (method === "POST" && rest[2] === "supported-models") {
|
|
7152
7671
|
const accountId = rest[1];
|
|
7153
|
-
const body2 = await
|
|
7672
|
+
const body2 = await readJsonBody4(req);
|
|
7154
7673
|
const parsed = validateSupportedModelsBody(body2["supportedModels"]);
|
|
7155
7674
|
if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
|
|
7156
7675
|
const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
|
|
7157
7676
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7158
|
-
return
|
|
7677
|
+
return writeJson4(res, 200, { ok: true });
|
|
7159
7678
|
}
|
|
7160
7679
|
if (method === "PUT" && rest[1] === "active") {
|
|
7161
|
-
const body2 = await
|
|
7680
|
+
const body2 = await readJsonBody4(req);
|
|
7162
7681
|
const id = typeof body2["id"] === "string" ? body2["id"] : "";
|
|
7163
7682
|
if (!id) return writeJsonError(res, 400, "active switch requires { id }");
|
|
7164
7683
|
const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
|
|
7165
7684
|
if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
|
|
7166
|
-
return
|
|
7685
|
+
return writeJson4(res, 200, { ok: true });
|
|
7167
7686
|
}
|
|
7168
7687
|
if (method === "DELETE" && rest.length === 2) {
|
|
7169
7688
|
const accountId = rest[1];
|
|
7170
7689
|
const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
|
|
7171
7690
|
if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7172
7691
|
deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
|
|
7173
|
-
return
|
|
7692
|
+
return writeJson4(res, 200, { ok: true });
|
|
7174
7693
|
}
|
|
7175
7694
|
if (method === "DELETE" && rest.length === 1) {
|
|
7176
7695
|
await deps.subscriptionTokenWriter.clearProvider(providerId);
|
|
7177
7696
|
deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
|
|
7178
|
-
return
|
|
7697
|
+
return writeJson4(res, 200, { ok: true });
|
|
7179
7698
|
}
|
|
7180
7699
|
if (method === "DELETE") {
|
|
7181
7700
|
return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
|
|
7182
7701
|
}
|
|
7183
|
-
const body = await
|
|
7702
|
+
const body = await readJsonBody4(req);
|
|
7184
7703
|
const config = validateTokenBody(providerId, body);
|
|
7185
7704
|
if (!config) {
|
|
7186
7705
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
7187
7706
|
}
|
|
7188
7707
|
await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
|
|
7189
7708
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7190
|
-
return
|
|
7709
|
+
return writeJson4(res, 200, status ? { account: status } : { ok: true });
|
|
7191
7710
|
}
|
|
7192
7711
|
return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
|
|
7193
7712
|
}
|
|
7194
7713
|
async function handleCli(req, res, method, rest, deps) {
|
|
7195
7714
|
if (method === "GET" && rest.length === 0) {
|
|
7196
7715
|
const result = handleCliList(process.platform, deps.cliPathProbe);
|
|
7197
|
-
return
|
|
7716
|
+
return writeJson4(res, result.status, result.body);
|
|
7198
7717
|
}
|
|
7199
7718
|
if (method === "GET" && rest[0] === "sessions") {
|
|
7200
7719
|
const result = handleCliSessions();
|
|
7201
|
-
return
|
|
7720
|
+
return writeJson4(res, result.status, result.body);
|
|
7202
7721
|
}
|
|
7203
7722
|
if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
|
|
7204
7723
|
const result = handleCliStop(rest[1]);
|
|
7205
|
-
return
|
|
7724
|
+
return writeJson4(res, result.status, result.body);
|
|
7206
7725
|
}
|
|
7207
7726
|
if (method === "POST" && rest[1] === "install") {
|
|
7208
7727
|
const cli = rest[0];
|
|
@@ -7210,14 +7729,14 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7210
7729
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7211
7730
|
}
|
|
7212
7731
|
const result = await handleCliInstall(cli, deps.cliCommandRunner);
|
|
7213
|
-
return
|
|
7732
|
+
return writeJson4(res, result.status, result.body);
|
|
7214
7733
|
}
|
|
7215
7734
|
if (method === "POST" && rest[1] === "launch") {
|
|
7216
7735
|
const cli = rest[0];
|
|
7217
7736
|
if (!isLaunchCliId(cli)) {
|
|
7218
7737
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7219
7738
|
}
|
|
7220
|
-
const body = await
|
|
7739
|
+
const body = await readJsonBody4(req);
|
|
7221
7740
|
const providers = loadConfig(deps.configPath).providers ?? [];
|
|
7222
7741
|
const result = await handleCliLaunch(cli, body, {
|
|
7223
7742
|
llmConfig: deps.llmConfig,
|
|
@@ -7226,7 +7745,7 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7226
7745
|
opener: deps.cliTerminalOpener,
|
|
7227
7746
|
probe: deps.cliPathProbe
|
|
7228
7747
|
});
|
|
7229
|
-
return
|
|
7748
|
+
return writeJson4(res, result.status, result.body);
|
|
7230
7749
|
}
|
|
7231
7750
|
return writeJsonError(res, 405, `method ${method} not allowed on cli`);
|
|
7232
7751
|
}
|
|
@@ -7236,52 +7755,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
|
|
|
7236
7755
|
const manager = factory();
|
|
7237
7756
|
try {
|
|
7238
7757
|
if (method === "GET" && rest.length === 0) {
|
|
7239
|
-
return
|
|
7758
|
+
return writeJson4(res, 200, {
|
|
7240
7759
|
integrations: await manager.listStatus(),
|
|
7241
7760
|
gateway: deps.outboundApiServer.getStatus()
|
|
7242
7761
|
});
|
|
7243
7762
|
}
|
|
7244
7763
|
if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
|
|
7245
7764
|
await manager.rotateGatewayKey();
|
|
7246
|
-
return
|
|
7765
|
+
return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
|
|
7247
7766
|
}
|
|
7248
7767
|
const client = rest[0];
|
|
7249
7768
|
if (!isIntegrationClient(client)) {
|
|
7250
7769
|
return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
|
|
7251
7770
|
}
|
|
7252
7771
|
if (method === "POST" && rest[1] === "key") {
|
|
7253
|
-
const body = await
|
|
7772
|
+
const body = await readJsonBody4(req);
|
|
7254
7773
|
if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
|
|
7255
7774
|
return writeJsonError(res, 400, "body must contain a non-empty keyId string");
|
|
7256
7775
|
}
|
|
7257
7776
|
const status = await manager.bindIntegrationKey(client, body.keyId.trim());
|
|
7258
|
-
return
|
|
7777
|
+
return writeJson4(res, 200, { integration: status });
|
|
7259
7778
|
}
|
|
7260
7779
|
if (method === "POST" && rest[1] === "plan") {
|
|
7261
|
-
const body = await
|
|
7780
|
+
const body = await readJsonBody4(req);
|
|
7262
7781
|
const configPath = body.configPath;
|
|
7263
7782
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7264
7783
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7265
7784
|
}
|
|
7266
7785
|
const plan = await manager.plan(client, configPath);
|
|
7267
|
-
return
|
|
7786
|
+
return writeJson4(res, 200, { plan });
|
|
7268
7787
|
}
|
|
7269
7788
|
if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
|
|
7270
|
-
const body = await
|
|
7789
|
+
const body = await readJsonBody4(req);
|
|
7271
7790
|
const configPath = body.configPath;
|
|
7272
7791
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7273
7792
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7274
7793
|
}
|
|
7275
7794
|
const status = await manager.install(client, configPath);
|
|
7276
|
-
return
|
|
7795
|
+
return writeJson4(res, 200, { integration: status });
|
|
7277
7796
|
}
|
|
7278
7797
|
if (method === "POST" && rest[1] === "repair") {
|
|
7279
7798
|
const status = await manager.repair(client);
|
|
7280
|
-
return
|
|
7799
|
+
return writeJson4(res, 200, { integration: status });
|
|
7281
7800
|
}
|
|
7282
7801
|
if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
|
|
7283
7802
|
const status = await manager.remove(client);
|
|
7284
|
-
return
|
|
7803
|
+
return writeJson4(res, 200, { integration: status });
|
|
7285
7804
|
}
|
|
7286
7805
|
return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
|
|
7287
7806
|
} catch (error) {
|
|
@@ -7424,7 +7943,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7424
7943
|
}
|
|
7425
7944
|
const reader = deps.imageRuntimeStatus;
|
|
7426
7945
|
if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
|
|
7427
|
-
const serverConfig = await
|
|
7946
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
7428
7947
|
const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
|
|
7429
7948
|
const lifecycle = reader.status();
|
|
7430
7949
|
const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
|
|
@@ -7444,7 +7963,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7444
7963
|
httpLeases: safeStatusCount(generation.httpLeases),
|
|
7445
7964
|
hostedLeases: safeStatusCount(generation.hostedLeases)
|
|
7446
7965
|
}));
|
|
7447
|
-
return
|
|
7966
|
+
return writeJson4(res, 200, {
|
|
7448
7967
|
configured: {
|
|
7449
7968
|
enabled: images.enabled,
|
|
7450
7969
|
provider: images.provider,
|
|
@@ -7472,7 +7991,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7472
7991
|
async function handleStatus(res, method, deps) {
|
|
7473
7992
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
|
|
7474
7993
|
const status = deps.outboundApiServer.getStatus();
|
|
7475
|
-
const serverConfig = await
|
|
7994
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
7476
7995
|
const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
|
|
7477
7996
|
const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => gatewayBindingToEndpointConfig(binding));
|
|
7478
7997
|
const useSubscription = routes.some((route) => route.useSubscription);
|
|
@@ -7510,14 +8029,14 @@ async function handleStatus(res, method, deps) {
|
|
|
7510
8029
|
})() : void 0;
|
|
7511
8030
|
if (status.running) {
|
|
7512
8031
|
const queueStatus = deps.outboundApiServer.getQueueStatus();
|
|
7513
|
-
return
|
|
8032
|
+
return writeJson4(res, 200, {
|
|
7514
8033
|
...status,
|
|
7515
8034
|
endpoints,
|
|
7516
8035
|
queueStatus,
|
|
7517
8036
|
...imageRuntime ? { imageRuntime } : {}
|
|
7518
8037
|
});
|
|
7519
8038
|
}
|
|
7520
|
-
return
|
|
8039
|
+
return writeJson4(res, 200, {
|
|
7521
8040
|
...status,
|
|
7522
8041
|
endpoints,
|
|
7523
8042
|
...imageRuntime ? { imageRuntime } : {}
|
|
@@ -7541,18 +8060,18 @@ function resolvePlaygroundPath(endpoint, body) {
|
|
|
7541
8060
|
}
|
|
7542
8061
|
async function handlePlayground(req, res, method, deps) {
|
|
7543
8062
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
|
|
7544
|
-
const body = await
|
|
8063
|
+
const body = await readJsonBody4(req);
|
|
7545
8064
|
const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
|
|
7546
8065
|
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
7547
8066
|
const payload = body["body"];
|
|
7548
8067
|
const status = deps.outboundApiServer.getStatus();
|
|
7549
8068
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7550
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8069
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
|
|
7551
8070
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7552
8071
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7553
8072
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7554
8073
|
}
|
|
7555
|
-
function
|
|
8074
|
+
function isRecord3(v) {
|
|
7556
8075
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7557
8076
|
}
|
|
7558
8077
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7687,7 +8206,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7687
8206
|
}
|
|
7688
8207
|
|
|
7689
8208
|
// src/admin/version.ts
|
|
7690
|
-
var DAEMON_VERSION = true ? "0.2.
|
|
8209
|
+
var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
|
|
7691
8210
|
|
|
7692
8211
|
// src/admin/AdminServer.ts
|
|
7693
8212
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -17582,6 +18101,14 @@ function buildDaemon(config, paths) {
|
|
|
17582
18101
|
onEvent: (row, at) => usageThroughput.record(row, at)
|
|
17583
18102
|
});
|
|
17584
18103
|
const initialImagesConfig = normalizeServerConfig2(decryptedConfig.server).images;
|
|
18104
|
+
const initialSearchConfig = normalizeServerConfig2(decryptedConfig.server).search;
|
|
18105
|
+
for (const issue of validateSearchServerConfig2(
|
|
18106
|
+
decryptedConfig.server?.search
|
|
18107
|
+
)) {
|
|
18108
|
+
logger.warn("[search] ignoring invalid config: " + issue);
|
|
18109
|
+
}
|
|
18110
|
+
const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
|
|
18111
|
+
const searchFrontendModes = initialSearchConfig.modes;
|
|
17585
18112
|
const imageObservability = new ImageObservability();
|
|
17586
18113
|
const imageRuntimeObservability = Object.freeze({
|
|
17587
18114
|
telemetrySink: imageObservability.telemetrySink,
|
|
@@ -17711,7 +18238,9 @@ function buildDaemon(config, paths) {
|
|
|
17711
18238
|
apiKeyPool,
|
|
17712
18239
|
usageRecorder,
|
|
17713
18240
|
openAIOperationRegistry,
|
|
17714
|
-
responsesHostedImageIngress
|
|
18241
|
+
responsesHostedImageIngress,
|
|
18242
|
+
searchRuntime,
|
|
18243
|
+
searchFrontendModes
|
|
17715
18244
|
});
|
|
17716
18245
|
if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
|
|
17717
18246
|
throw new Error(
|
|
@@ -17776,7 +18305,9 @@ function buildDaemon(config, paths) {
|
|
|
17776
18305
|
keySpendTracker,
|
|
17777
18306
|
// configurable-logging: route the server's OWN lifecycle + relay dispatch-error
|
|
17778
18307
|
// lines through the injected logger (honors level/format/file sink).
|
|
17779
|
-
logger
|
|
18308
|
+
logger,
|
|
18309
|
+
// plan 阶段5: the same instance the managed frontends hold.
|
|
18310
|
+
searchRuntime
|
|
17780
18311
|
});
|
|
17781
18312
|
const auditDir = defaultAuditDir(paths.configPath);
|
|
17782
18313
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
@@ -17798,6 +18329,10 @@ function buildDaemon(config, paths) {
|
|
|
17798
18329
|
}),
|
|
17799
18330
|
routeLeaseManager,
|
|
17800
18331
|
subscriptionAccounts,
|
|
18332
|
+
// search-settings-ui D3: the daemon's ONE search runtime + its
|
|
18333
|
+
// bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
|
|
18334
|
+
// `POST /admin/api/search/test` (501 when a light embedder omits it).
|
|
18335
|
+
searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
|
|
17801
18336
|
accountAllowanceService,
|
|
17802
18337
|
allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
|
|
17803
18338
|
accountProbeService: accountHealthProbeScheduler,
|
|
@@ -17934,6 +18469,8 @@ function buildDaemon(config, paths) {
|
|
|
17934
18469
|
keyDb,
|
|
17935
18470
|
settingsStore,
|
|
17936
18471
|
openAIOperationRegistry,
|
|
18472
|
+
searchRuntime,
|
|
18473
|
+
searchFrontendModes,
|
|
17937
18474
|
imageRuntimeManager,
|
|
17938
18475
|
imageObservability,
|
|
17939
18476
|
imageCleanupService,
|
|
@@ -18080,6 +18617,116 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
|
|
|
18080
18617
|
);
|
|
18081
18618
|
return true;
|
|
18082
18619
|
}
|
|
18620
|
+
function readSearchApiConfigFromEnv(env = process.env) {
|
|
18621
|
+
const configs = {};
|
|
18622
|
+
const read = (name) => {
|
|
18623
|
+
const value = env[`OMNICROSS_SEARCH_${name}`]?.trim();
|
|
18624
|
+
return value ? value : void 0;
|
|
18625
|
+
};
|
|
18626
|
+
const tavilyKey = read("TAVILY_API_KEY");
|
|
18627
|
+
if (tavilyKey) {
|
|
18628
|
+
configs.tavily = { apiKey: tavilyKey, ...optionalHost(read("TAVILY_API_HOST")) };
|
|
18629
|
+
}
|
|
18630
|
+
const jinaKey = read("JINA_API_KEY");
|
|
18631
|
+
const jinaHost = read("JINA_API_HOST");
|
|
18632
|
+
if (jinaKey || jinaHost) {
|
|
18633
|
+
configs.jina = { ...jinaKey ? { apiKey: jinaKey } : {}, ...optionalHost(jinaHost) };
|
|
18634
|
+
}
|
|
18635
|
+
const searxngHost = read("SEARXNG_API_HOST");
|
|
18636
|
+
if (searxngHost) {
|
|
18637
|
+
const username = read("SEARXNG_BASIC_AUTH_USERNAME");
|
|
18638
|
+
const password = read("SEARXNG_BASIC_AUTH_PASSWORD");
|
|
18639
|
+
configs.searxng = {
|
|
18640
|
+
apiHost: searxngHost,
|
|
18641
|
+
...username ? { basicAuthUsername: username } : {},
|
|
18642
|
+
...password ? { basicAuthPassword: password } : {}
|
|
18643
|
+
};
|
|
18644
|
+
}
|
|
18645
|
+
const zhipuKey = read("ZHIPU_API_KEY");
|
|
18646
|
+
if (zhipuKey) {
|
|
18647
|
+
configs.zhipu = { apiKey: zhipuKey, ...optionalHost(read("ZHIPU_API_HOST")) };
|
|
18648
|
+
}
|
|
18649
|
+
const zaiKey = read("Z_AI_API_KEY");
|
|
18650
|
+
if (zaiKey) {
|
|
18651
|
+
configs["z.ai"] = { apiKey: zaiKey, ...optionalHost(read("Z_AI_API_HOST")) };
|
|
18652
|
+
}
|
|
18653
|
+
return configs;
|
|
18654
|
+
}
|
|
18655
|
+
function optionalHost(apiHost) {
|
|
18656
|
+
return apiHost ? { apiHost } : {};
|
|
18657
|
+
}
|
|
18658
|
+
function resolveSearchApiConfigs(configured, env = process.env) {
|
|
18659
|
+
const fromEnv = readSearchApiConfigFromEnv(env);
|
|
18660
|
+
const resolved = {};
|
|
18661
|
+
const tavily = configured?.tavily ?? fromEnv.tavily;
|
|
18662
|
+
if (tavily) resolved.tavily = tavily;
|
|
18663
|
+
const jina = configured?.jina ?? fromEnv.jina;
|
|
18664
|
+
if (jina) resolved.jina = jina;
|
|
18665
|
+
const searxng = configured?.searxng ?? fromEnv.searxng;
|
|
18666
|
+
if (searxng) resolved.searxng = searxng;
|
|
18667
|
+
const zhipu = configured?.zhipu ?? fromEnv.zhipu;
|
|
18668
|
+
if (zhipu) resolved.zhipu = zhipu;
|
|
18669
|
+
const zai = configured?.["z.ai"] ?? fromEnv["z.ai"];
|
|
18670
|
+
if (zai) resolved["z.ai"] = zai;
|
|
18671
|
+
return resolved;
|
|
18672
|
+
}
|
|
18673
|
+
function formatCapabilities(capabilities) {
|
|
18674
|
+
return [
|
|
18675
|
+
`apiKey=${capabilities.requiresApiKey}`,
|
|
18676
|
+
`cancellation=${capabilities.supportsCancellation}`,
|
|
18677
|
+
`urlRead=${capabilities.supportsUrlRead}`,
|
|
18678
|
+
`region=${capabilities.supportsRegion}`,
|
|
18679
|
+
`language=${capabilities.supportsLanguage}`,
|
|
18680
|
+
`timeRange=${capabilities.supportsTimeRange}`,
|
|
18681
|
+
`maxResults=${capabilities.maxResults ?? "unbounded"}`
|
|
18682
|
+
].join(", ");
|
|
18683
|
+
}
|
|
18684
|
+
function formatDiagnostic(diagnostic) {
|
|
18685
|
+
const parts = [diagnostic.status];
|
|
18686
|
+
if (diagnostic.reason) parts.push(diagnostic.reason);
|
|
18687
|
+
if (diagnostic.error) {
|
|
18688
|
+
const { code, details } = diagnostic.error;
|
|
18689
|
+
parts.push(
|
|
18690
|
+
`code=${code}, transport=${details?.transport ?? "unknown"}, stage=${details?.stage ?? "unknown"}`
|
|
18691
|
+
);
|
|
18692
|
+
}
|
|
18693
|
+
return parts.join(" \u2014 ");
|
|
18694
|
+
}
|
|
18695
|
+
async function runSearchDoctor(live, env = process.env, source = {}) {
|
|
18696
|
+
const apiConfigs = resolveSearchApiConfigs(source.config?.providers, env);
|
|
18697
|
+
const egressPolicy = source.config && source.config.egress.allowedPrivateHosts.length > 0 ? { allowedPrivateHosts: [...source.config.egress.allowedPrivateHosts] } : void 0;
|
|
18698
|
+
const contributions = [
|
|
18699
|
+
...builtinHttpSearchContributions4(),
|
|
18700
|
+
...apiSearchContributions3(apiConfigs, { ...egressPolicy ? { egressPolicy } : {} })
|
|
18701
|
+
];
|
|
18702
|
+
const declarations = source.runtime?.listProviders() ?? contributions;
|
|
18703
|
+
console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
|
|
18704
|
+
const modes = source.config?.modes ?? DEFAULT_SEARCH_FRONTEND_MODES;
|
|
18705
|
+
console.info(
|
|
18706
|
+
` [i] frontend modes: ${SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
|
|
18707
|
+
);
|
|
18708
|
+
console.info(
|
|
18709
|
+
" [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
|
|
18710
|
+
);
|
|
18711
|
+
for (const row of buildSearchDoctorSnapshot(declarations, apiConfigs)) {
|
|
18712
|
+
const mark = row.status === "unconfigured" ? "\u2013" : "\u2713";
|
|
18713
|
+
const suffix = row.status ? ` \u2014 ${row.status}: ${row.reason ?? ""}` : "";
|
|
18714
|
+
console.info(
|
|
18715
|
+
` [${mark}] ${row.providerId}: source=${row.source}, kind=${row.kind}, ${formatCapabilities(row.capabilities)}${suffix}`
|
|
18716
|
+
);
|
|
18717
|
+
}
|
|
18718
|
+
if (!live) return 0;
|
|
18719
|
+
console.info(
|
|
18720
|
+
` [\u26A0] live search sends ONE fixed public query per provider ("${SEARCH_DOCTOR_QUERY}") to the engine itself`
|
|
18721
|
+
);
|
|
18722
|
+
let hardFailure = false;
|
|
18723
|
+
for (const diagnostic of await runSearchLiveChecks(contributions)) {
|
|
18724
|
+
const mark = diagnostic.status === "healthy" ? "\u2713" : diagnostic.status === "failed" ? "\u2717" : "\u26A0";
|
|
18725
|
+
if (diagnostic.status === "failed") hardFailure = true;
|
|
18726
|
+
console.info(` [${mark}] ${diagnostic.providerId}: ${formatDiagnostic(diagnostic)}`);
|
|
18727
|
+
}
|
|
18728
|
+
return hardFailure ? 1 : 0;
|
|
18729
|
+
}
|
|
18083
18730
|
async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
18084
18731
|
try {
|
|
18085
18732
|
const res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/messages/count_tokens`, {
|
|
@@ -18116,11 +18763,12 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
18116
18763
|
allowPositionals: true
|
|
18117
18764
|
});
|
|
18118
18765
|
const subject = positionals[0] ?? "claude";
|
|
18119
|
-
if (subject !== "claude" && subject !== "images") {
|
|
18120
|
-
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images')`);
|
|
18766
|
+
if (subject !== "claude" && subject !== "images" && subject !== "search") {
|
|
18767
|
+
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images', 'search')`);
|
|
18121
18768
|
}
|
|
18122
18769
|
const configPath = values.config;
|
|
18123
18770
|
if (!configPath) {
|
|
18771
|
+
if (subject === "search") return runSearchDoctor(values.live === true);
|
|
18124
18772
|
throw new Error("doctor: --config <path> is required (the same config `omnicross start` uses)");
|
|
18125
18773
|
}
|
|
18126
18774
|
const config = loadConfig(configPath);
|
|
@@ -18132,7 +18780,13 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
18132
18780
|
};
|
|
18133
18781
|
const daemon = await buildDaemon(config, paths);
|
|
18134
18782
|
try {
|
|
18135
|
-
const serverConfig = await
|
|
18783
|
+
const serverConfig = await loadServerConfig4(daemon.settingsStore);
|
|
18784
|
+
if (subject === "search") {
|
|
18785
|
+
return await runSearchDoctor(values.live === true, process.env, {
|
|
18786
|
+
...serverConfig.search ? { config: serverConfig.search } : {},
|
|
18787
|
+
runtime: daemon.searchRuntime
|
|
18788
|
+
});
|
|
18789
|
+
}
|
|
18136
18790
|
const checks = subject === "images" ? buildImagesDoctorChecks(await daemon.imageDoctor.inspectLocal(
|
|
18137
18791
|
serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG2
|
|
18138
18792
|
)) : buildClaudeDoctorChecks(serverConfig);
|
|
@@ -19209,7 +19863,7 @@ function tokensSuffix(configPath) {
|
|
|
19209
19863
|
|
|
19210
19864
|
// src/commands/start.ts
|
|
19211
19865
|
import { parseArgs as parseArgs10 } from "util";
|
|
19212
|
-
import { loadServerConfig as
|
|
19866
|
+
import { loadServerConfig as loadServerConfig5 } from "@omnicross/core/outbound-api";
|
|
19213
19867
|
import { getSharedAccountHealth as getSharedAccountHealth5 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
19214
19868
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling6 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
19215
19869
|
|
|
@@ -19271,7 +19925,7 @@ async function runStart(argv) {
|
|
|
19271
19925
|
await daemon.llmConfig.ready();
|
|
19272
19926
|
await daemon.migrateUsageStore();
|
|
19273
19927
|
await daemon.providerProxy.start();
|
|
19274
|
-
const serverConfig = await
|
|
19928
|
+
const serverConfig = await loadServerConfig5(daemon.settingsStore);
|
|
19275
19929
|
await daemon.imageCleanupService.runOnce();
|
|
19276
19930
|
daemon.imageCleanupService.start();
|
|
19277
19931
|
getSharedAccountHealth5().configure({
|
|
@@ -19294,7 +19948,9 @@ async function runStart(argv) {
|
|
|
19294
19948
|
voucher: serverConfig.voucher,
|
|
19295
19949
|
// claude-api-protocol-fidelity (§10): count_tokens strategy/budget,
|
|
19296
19950
|
// /v1/models shape, synthetic-ping heartbeat (hot-applied inside applyConfig).
|
|
19297
|
-
anthropic: serverConfig.anthropic
|
|
19951
|
+
anthropic: serverConfig.anthropic,
|
|
19952
|
+
// plan 阶段5: the Codex frontend mode is read live per request from here.
|
|
19953
|
+
search: serverConfig.search
|
|
19298
19954
|
});
|
|
19299
19955
|
let dashboardUrl = null;
|
|
19300
19956
|
if (!values["no-dashboard"]) {
|
|
@@ -19409,6 +20065,16 @@ Usage:
|
|
|
19409
20065
|
Check local Images config, roots, stores, permissions,
|
|
19410
20066
|
account and cached evidence. --live warns, then consumes
|
|
19411
20067
|
at most one minimal subscription image request.
|
|
20068
|
+
omnicross doctor search [--live] List the builtin search providers and their declared
|
|
20069
|
+
capabilities (offline, no config needed). --live sends ONE
|
|
20070
|
+
fixed public query per provider and reports healthy /
|
|
20071
|
+
degraded / blocked / failed.
|
|
20072
|
+
Keyed API providers show as unconfigured unless enabled
|
|
20073
|
+
through the DIAGNOSTIC-ONLY environment variables
|
|
20074
|
+
OMNICROSS_SEARCH_{TAVILY,JINA,SEARXNG,ZHIPU,Z_AI}_API_KEY
|
|
20075
|
+
/ _API_HOST (plus SEARXNG_BASIC_AUTH_USERNAME/_PASSWORD).
|
|
20076
|
+
These are read only by this command and are not the
|
|
20077
|
+
configuration system.
|
|
19412
20078
|
`;
|
|
19413
20079
|
async function main() {
|
|
19414
20080
|
const [, , subcommand, ...rest] = process.argv;
|