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