@omnicross/daemon 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +892 -219
- package/dist/cli.js +849 -160
- package/dist/index.cjs +733 -206
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +691 -153
- package/package.json +6 -6
package/dist/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,496 @@ 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 as createSearchHttpTransport2 } from "@omnicross/core/search/http";
|
|
3793
|
+
import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
|
|
3794
|
+
|
|
3795
|
+
// src/search/searchDoctorProjection.ts
|
|
3796
|
+
import { toSearchErrorShape } from "@omnicross/contracts/search-types";
|
|
3797
|
+
import {
|
|
3798
|
+
JINA_CAPABILITIES,
|
|
3799
|
+
SEARXNG_CAPABILITIES,
|
|
3800
|
+
TAVILY_CAPABILITIES,
|
|
3801
|
+
ZHIPU_CAPABILITIES
|
|
3802
|
+
} from "@omnicross/core/search/api";
|
|
3803
|
+
import { builtinHttpSearchContributions } from "@omnicross/core/search/http";
|
|
3804
|
+
var API_DOCTOR_PROVIDERS = [
|
|
3805
|
+
{
|
|
3806
|
+
id: "tavily",
|
|
3807
|
+
capabilities: TAVILY_CAPABILITIES,
|
|
3808
|
+
configured: (configs) => configs.tavily !== void 0,
|
|
3809
|
+
missingReason: "no API key configured"
|
|
3810
|
+
},
|
|
3811
|
+
{
|
|
3812
|
+
id: "jina",
|
|
3813
|
+
capabilities: JINA_CAPABILITIES,
|
|
3814
|
+
configured: (configs) => configs.jina !== void 0,
|
|
3815
|
+
// Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
|
|
3816
|
+
// asked for is still not enabled.
|
|
3817
|
+
missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
|
|
3818
|
+
},
|
|
3819
|
+
{
|
|
3820
|
+
id: "searxng",
|
|
3821
|
+
capabilities: SEARXNG_CAPABILITIES,
|
|
3822
|
+
configured: (configs) => configs.searxng !== void 0,
|
|
3823
|
+
missingReason: "no API host configured"
|
|
3824
|
+
},
|
|
3825
|
+
{
|
|
3826
|
+
id: "zhipu",
|
|
3827
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
3828
|
+
configured: (configs) => configs.zhipu !== void 0,
|
|
3829
|
+
missingReason: "no API key configured"
|
|
3830
|
+
},
|
|
3831
|
+
{
|
|
3832
|
+
id: "z.ai",
|
|
3833
|
+
capabilities: ZHIPU_CAPABILITIES,
|
|
3834
|
+
configured: (configs) => configs["z.ai"] !== void 0,
|
|
3835
|
+
missingReason: "no API key configured"
|
|
3836
|
+
}
|
|
3837
|
+
];
|
|
3838
|
+
function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContributions(), apiConfigs) {
|
|
3839
|
+
const rows = contributions.map((contribution) => ({
|
|
3840
|
+
providerId: contribution.id,
|
|
3841
|
+
source: contribution.source,
|
|
3842
|
+
kind: contribution.kind,
|
|
3843
|
+
capabilities: contribution.capabilities
|
|
3844
|
+
}));
|
|
3845
|
+
if (apiConfigs === void 0) return rows;
|
|
3846
|
+
for (const provider of API_DOCTOR_PROVIDERS) {
|
|
3847
|
+
if (provider.configured(apiConfigs)) continue;
|
|
3848
|
+
rows.push({
|
|
3849
|
+
providerId: provider.id,
|
|
3850
|
+
source: "builtin",
|
|
3851
|
+
kind: "api",
|
|
3852
|
+
capabilities: provider.capabilities,
|
|
3853
|
+
status: "unconfigured",
|
|
3854
|
+
reason: provider.missingReason
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
return rows;
|
|
3858
|
+
}
|
|
3859
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
3860
|
+
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
3861
|
+
if (outcome.kind === "results") {
|
|
3862
|
+
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
3863
|
+
return {
|
|
3864
|
+
providerId,
|
|
3865
|
+
status: "degraded",
|
|
3866
|
+
checkedAt,
|
|
3867
|
+
reason: "reachable, but the engine returned no usable results (possible partial drift)"
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
const error = toSearchErrorShape(outcome.error);
|
|
3871
|
+
const stage = error.details?.stage;
|
|
3872
|
+
const { status, reason } = classifySearchFailure(stage, error.code);
|
|
3873
|
+
return { providerId, status, checkedAt, reason, error };
|
|
3874
|
+
}
|
|
3875
|
+
function classifySearchFailure(stage, code) {
|
|
3876
|
+
if (stage === "challenge") {
|
|
3877
|
+
return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
|
|
3878
|
+
}
|
|
3879
|
+
if (stage === "trust") {
|
|
3880
|
+
return {
|
|
3881
|
+
status: "blocked",
|
|
3882
|
+
reason: "the engine served a page that failed the anti-decoy trust check"
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
if (code === "policy_denied") {
|
|
3886
|
+
return {
|
|
3887
|
+
status: "blocked",
|
|
3888
|
+
reason: "the egress policy refused the request target"
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
if (code === "parse_failed") {
|
|
3892
|
+
return {
|
|
3893
|
+
status: "failed",
|
|
3894
|
+
reason: "the response was not recognizable as a search result page (parser drift suspected)"
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
if (code === "timeout") {
|
|
3898
|
+
return { status: "failed", reason: "the request exceeded its time budget" };
|
|
3899
|
+
}
|
|
3900
|
+
return { status: "failed", reason: `the request failed (${code})` };
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
// src/search/SearchAssembly.ts
|
|
3904
|
+
import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
|
|
3905
|
+
import { createSearchRuntime } from "@omnicross/core/search";
|
|
3906
|
+
import { apiSearchContributions } from "@omnicross/core/search/api";
|
|
3907
|
+
import {
|
|
3908
|
+
builtinHttpSearchContributions as builtinHttpSearchContributions2,
|
|
3909
|
+
createSearchHttpTransport
|
|
3910
|
+
} from "@omnicross/core/search/http";
|
|
3911
|
+
function searchEgressPolicyFrom(config) {
|
|
3912
|
+
const hosts = config.egress.allowedPrivateHosts;
|
|
3913
|
+
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
3914
|
+
}
|
|
3915
|
+
function searchPolicyFrom(config) {
|
|
3916
|
+
const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
|
|
3917
|
+
return {
|
|
3918
|
+
...preferred !== void 0 ? { preferred } : {},
|
|
3919
|
+
...allowed !== void 0 ? { allowed: [...allowed] } : {},
|
|
3920
|
+
fallbackEnabled,
|
|
3921
|
+
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
3922
|
+
};
|
|
3923
|
+
}
|
|
3924
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
3925
|
+
return resolveUpstreamDispatcher({ url });
|
|
3926
|
+
}
|
|
3927
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
3928
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
3929
|
+
return searchUpstreamProxyConfig({ url });
|
|
3930
|
+
}
|
|
3931
|
+
function searchContributionsFrom(config) {
|
|
3932
|
+
return [
|
|
3933
|
+
...builtinHttpSearchContributions2(
|
|
3934
|
+
createSearchHttpTransport({
|
|
3935
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
3936
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
3937
|
+
})
|
|
3938
|
+
),
|
|
3939
|
+
...apiSearchContributions(config.providers, {
|
|
3940
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
3941
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
3942
|
+
})
|
|
3943
|
+
];
|
|
3944
|
+
}
|
|
3945
|
+
function buildSearchRuntime(config, options = {}) {
|
|
3946
|
+
const logger = options.logger ?? null;
|
|
3947
|
+
return createSearchRuntime({
|
|
3948
|
+
contributions: options.contributions ?? searchContributionsFrom(config),
|
|
3949
|
+
policy: searchPolicyFrom(config),
|
|
3950
|
+
...logger ? {
|
|
3951
|
+
onEvent: (event) => {
|
|
3952
|
+
logger.debug(`[search] ${formatSearchEvent(event)}`);
|
|
3953
|
+
}
|
|
3954
|
+
} : {}
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3957
|
+
function formatSearchEvent(event) {
|
|
3958
|
+
const parts = [
|
|
3959
|
+
`type=${event.type}`,
|
|
3960
|
+
`request=${event.requestId}`,
|
|
3961
|
+
`queryHash=${event.queryHash}`,
|
|
3962
|
+
`durationMs=${event.durationMs}`
|
|
3963
|
+
];
|
|
3964
|
+
if ("providerId" in event && event.providerId !== void 0) {
|
|
3965
|
+
parts.push(`provider=${event.providerId}`);
|
|
3966
|
+
}
|
|
3967
|
+
if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
|
|
3968
|
+
if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
|
|
3969
|
+
if ("resultCount" in event && event.resultCount !== void 0) {
|
|
3970
|
+
parts.push(`results=${event.resultCount}`);
|
|
3971
|
+
}
|
|
3972
|
+
if ("fallbackCount" in event && event.fallbackCount !== void 0) {
|
|
3973
|
+
parts.push(`fallbacks=${event.fallbackCount}`);
|
|
3974
|
+
}
|
|
3975
|
+
return parts.join(" ");
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
// src/admin/searchAdminApi.ts
|
|
3979
|
+
var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
|
|
3980
|
+
var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
3981
|
+
"tavily",
|
|
3982
|
+
"jina",
|
|
3983
|
+
"searxng",
|
|
3984
|
+
"zhipu",
|
|
3985
|
+
"z.ai"
|
|
3986
|
+
]);
|
|
3987
|
+
var SEARCH_QUERY_MAX_CODE_UNITS = 256;
|
|
3988
|
+
var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
3989
|
+
var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
|
|
3990
|
+
var SEARCH_QUERY_MAX_RESULTS = 5;
|
|
3991
|
+
function sanitizeResultField(value, cap) {
|
|
3992
|
+
const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
|
|
3993
|
+
return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
|
|
3994
|
+
}
|
|
3995
|
+
function writeJson(res, status, body) {
|
|
3996
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
3997
|
+
res.end(JSON.stringify(body));
|
|
3998
|
+
}
|
|
3999
|
+
function writeErr(res, status, message) {
|
|
4000
|
+
writeJson(res, status, { error: { type: "admin_api_error", message } });
|
|
4001
|
+
}
|
|
4002
|
+
var SEARCH_MAX_BODY_BYTES = 64 * 1024;
|
|
4003
|
+
var SearchBodyTooLargeError = class extends Error {
|
|
4004
|
+
};
|
|
4005
|
+
async function readJsonBody2(req) {
|
|
4006
|
+
const chunks = [];
|
|
4007
|
+
let bytes = 0;
|
|
4008
|
+
for await (const chunk of req) {
|
|
4009
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4010
|
+
bytes += buffer.length;
|
|
4011
|
+
if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
|
|
4012
|
+
chunks.push(buffer);
|
|
4013
|
+
}
|
|
4014
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
4015
|
+
if (!raw.trim()) return {};
|
|
4016
|
+
try {
|
|
4017
|
+
const parsed = JSON.parse(raw);
|
|
4018
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4019
|
+
} catch {
|
|
4020
|
+
return {};
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
async function readBodyOrReject(req, res) {
|
|
4024
|
+
try {
|
|
4025
|
+
return await readJsonBody2(req);
|
|
4026
|
+
} catch (error) {
|
|
4027
|
+
if (error instanceof SearchBodyTooLargeError) {
|
|
4028
|
+
writeErr(res, 400, error.message);
|
|
4029
|
+
return void 0;
|
|
4030
|
+
}
|
|
4031
|
+
throw error;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
async function handleSearchAdmin(req, res, method, rest, deps) {
|
|
4035
|
+
if (rest.length === 1 && rest[0] === "diagnostics") {
|
|
4036
|
+
if (!deps.searchStatus) {
|
|
4037
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4038
|
+
}
|
|
4039
|
+
if (method !== "GET") {
|
|
4040
|
+
return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
|
|
4041
|
+
}
|
|
4042
|
+
return handleSearchDiagnostics(res, deps);
|
|
4043
|
+
}
|
|
4044
|
+
if (rest.length === 1 && rest[0] === "test") {
|
|
4045
|
+
if (!deps.searchStatus) {
|
|
4046
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4047
|
+
}
|
|
4048
|
+
if (method !== "POST") {
|
|
4049
|
+
return writeErr(res, 405, `method ${method} not allowed on search test`);
|
|
4050
|
+
}
|
|
4051
|
+
return handleSearchTest(req, res, deps);
|
|
4052
|
+
}
|
|
4053
|
+
if (rest.length === 1 && rest[0] === "query") {
|
|
4054
|
+
if (!deps.searchStatus) {
|
|
4055
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4056
|
+
}
|
|
4057
|
+
if (method !== "POST") {
|
|
4058
|
+
return writeErr(res, 405, `method ${method} not allowed on search query`);
|
|
4059
|
+
}
|
|
4060
|
+
return handleSearchQuery(req, res, deps);
|
|
4061
|
+
}
|
|
4062
|
+
return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
|
|
4063
|
+
}
|
|
4064
|
+
async function handleSearchDiagnostics(res, deps) {
|
|
4065
|
+
const status = deps.searchStatus;
|
|
4066
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4067
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4068
|
+
const rows = buildSearchDoctorSnapshot(
|
|
4069
|
+
status.runtime.listProviders(),
|
|
4070
|
+
search.providers
|
|
4071
|
+
);
|
|
4072
|
+
const snapshot = {
|
|
4073
|
+
rows,
|
|
4074
|
+
modes: {
|
|
4075
|
+
// codex is read from the LIVE config per request — an admin PUT has
|
|
4076
|
+
// already applied. responses/anthropic were captured at bootstrap.
|
|
4077
|
+
codex: search.modes.codex,
|
|
4078
|
+
responses: status.modes.responses,
|
|
4079
|
+
anthropic: status.modes.anthropic
|
|
4080
|
+
},
|
|
4081
|
+
applySemantics: { codex: "immediate", rest: "restart" }
|
|
4082
|
+
};
|
|
4083
|
+
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4084
|
+
}
|
|
4085
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
4086
|
+
if (fetchImpl) {
|
|
4087
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4088
|
+
return [
|
|
4089
|
+
...builtinHttpSearchContributions3(
|
|
4090
|
+
createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
|
|
4091
|
+
),
|
|
4092
|
+
...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
|
|
4093
|
+
];
|
|
4094
|
+
}
|
|
4095
|
+
return searchContributionsFrom(search);
|
|
4096
|
+
}
|
|
4097
|
+
async function handleSearchTest(req, res, deps) {
|
|
4098
|
+
const status = deps.searchStatus;
|
|
4099
|
+
const body = await readBodyOrReject(req, res);
|
|
4100
|
+
if (body === void 0) return;
|
|
4101
|
+
const providerId = body["providerId"];
|
|
4102
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4103
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4104
|
+
}
|
|
4105
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4106
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4107
|
+
}
|
|
4108
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4109
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4110
|
+
const providers = search.providers;
|
|
4111
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4112
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4113
|
+
}
|
|
4114
|
+
const fetchImpl = status.testFetch;
|
|
4115
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4116
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4117
|
+
if (!contribution) {
|
|
4118
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4119
|
+
}
|
|
4120
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4121
|
+
try {
|
|
4122
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4123
|
+
const diagnostic = classifyLiveSearchOutcome(
|
|
4124
|
+
contribution.id,
|
|
4125
|
+
{ kind: "results", count: results.length },
|
|
4126
|
+
checkedAt
|
|
4127
|
+
);
|
|
4128
|
+
const response = { diagnostic, resultCount: results.length };
|
|
4129
|
+
return writeJson(res, 200, { result: response });
|
|
4130
|
+
} catch (error) {
|
|
4131
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
4132
|
+
const response = { diagnostic };
|
|
4133
|
+
return writeJson(res, 200, { result: response });
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
async function handleSearchQuery(req, res, deps) {
|
|
4137
|
+
const status = deps.searchStatus;
|
|
4138
|
+
const body = await readBodyOrReject(req, res);
|
|
4139
|
+
if (body === void 0) return;
|
|
4140
|
+
const providerId = body["providerId"];
|
|
4141
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4142
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4143
|
+
}
|
|
4144
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4145
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4146
|
+
}
|
|
4147
|
+
const query2 = body["query"];
|
|
4148
|
+
if (typeof query2 !== "string" || query2.trim().length === 0) {
|
|
4149
|
+
return writeErr(res, 400, "query must be a non-empty string");
|
|
4150
|
+
}
|
|
4151
|
+
if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
|
|
4152
|
+
return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
|
|
4153
|
+
}
|
|
4154
|
+
if (QUERY_CONTROL_CHARS.test(query2)) {
|
|
4155
|
+
return writeErr(res, 400, "query must not contain control characters");
|
|
4156
|
+
}
|
|
4157
|
+
const persisted = await loadServerConfig(deps.settingsStore);
|
|
4158
|
+
const search = persisted.search ?? DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4159
|
+
const providers = search.providers;
|
|
4160
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4161
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4162
|
+
}
|
|
4163
|
+
const fetchImpl = status.testFetch;
|
|
4164
|
+
const runtime = createSearchRuntime2({
|
|
4165
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
4166
|
+
policy: {
|
|
4167
|
+
...searchPolicyFrom(search),
|
|
4168
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
4169
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
4170
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
4171
|
+
fallbackEnabled: true,
|
|
4172
|
+
preferred: providerId
|
|
4173
|
+
}
|
|
4174
|
+
});
|
|
4175
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4176
|
+
try {
|
|
4177
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
4178
|
+
const results = orchestrated.results;
|
|
4179
|
+
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4180
|
+
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4181
|
+
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4182
|
+
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4183
|
+
}));
|
|
4184
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
4185
|
+
orchestrated.providerId,
|
|
4186
|
+
{ kind: "results", count: sanitized.length },
|
|
4187
|
+
checkedAt
|
|
4188
|
+
);
|
|
4189
|
+
const response = {
|
|
4190
|
+
diagnostic,
|
|
4191
|
+
providerUsed: orchestrated.providerId,
|
|
4192
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
4193
|
+
resultCount: sanitized.length,
|
|
4194
|
+
results: sanitized
|
|
4195
|
+
};
|
|
4196
|
+
return writeJson(res, 200, { result: response });
|
|
4197
|
+
} catch (error) {
|
|
4198
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
4199
|
+
const response = { diagnostic };
|
|
4200
|
+
return writeJson(res, 200, { result: response });
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
// src/admin/searchAdminView.ts
|
|
4205
|
+
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4206
|
+
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4207
|
+
function isRecord(value) {
|
|
4208
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4209
|
+
}
|
|
4210
|
+
function redactSearchServerConfig(search) {
|
|
4211
|
+
const providers = {};
|
|
4212
|
+
for (const [id, raw] of Object.entries(search.providers)) {
|
|
4213
|
+
const entry = raw;
|
|
4214
|
+
const view = {};
|
|
4215
|
+
if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
|
|
4216
|
+
if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
|
|
4217
|
+
if (API_KEY_PROVIDERS.has(id)) {
|
|
4218
|
+
view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
|
|
4219
|
+
}
|
|
4220
|
+
if (BASIC_AUTH_PROVIDERS.has(id)) {
|
|
4221
|
+
view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
|
|
4222
|
+
}
|
|
4223
|
+
providers[id] = view;
|
|
4224
|
+
}
|
|
4225
|
+
return {
|
|
4226
|
+
modes: search.modes,
|
|
4227
|
+
providers,
|
|
4228
|
+
egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
|
|
4229
|
+
policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
function storedSecrets(current, id) {
|
|
4233
|
+
const entry = current.providers[id];
|
|
4234
|
+
if (!entry) return {};
|
|
4235
|
+
const out = {};
|
|
4236
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
|
|
4237
|
+
if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
|
|
4238
|
+
out.basicAuthPassword = entry.basicAuthPassword;
|
|
4239
|
+
}
|
|
4240
|
+
return out;
|
|
4241
|
+
}
|
|
4242
|
+
function resolveSecretField(entry, field, stored) {
|
|
4243
|
+
if (!(field in entry)) {
|
|
4244
|
+
if (stored !== void 0) entry[field] = stored;
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
const value = entry[field];
|
|
4248
|
+
if (value === null) {
|
|
4249
|
+
delete entry[field];
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
if (typeof value === "string" && value.trim().length > 0) return;
|
|
4253
|
+
if (stored !== void 0) entry[field] = stored;
|
|
4254
|
+
else delete entry[field];
|
|
4255
|
+
}
|
|
4256
|
+
function preserveSearchSecrets(incoming, current) {
|
|
4257
|
+
if (!isRecord(incoming)) return incoming;
|
|
4258
|
+
const section = { ...incoming };
|
|
4259
|
+
const providersValue = section["providers"];
|
|
4260
|
+
if (!isRecord(providersValue)) return section;
|
|
4261
|
+
const providers = {};
|
|
4262
|
+
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4263
|
+
if (!isRecord(entryValue)) {
|
|
4264
|
+
providers[id] = entryValue;
|
|
4265
|
+
continue;
|
|
4266
|
+
}
|
|
4267
|
+
const entry = { ...entryValue };
|
|
4268
|
+
delete entry["apiKeyConfigured"];
|
|
4269
|
+
delete entry["basicAuthPasswordConfigured"];
|
|
4270
|
+
const stored = storedSecrets(current, id);
|
|
4271
|
+
resolveSecretField(entry, "apiKey", stored.apiKey);
|
|
4272
|
+
resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
|
|
4273
|
+
providers[id] = entry;
|
|
4274
|
+
}
|
|
4275
|
+
section["providers"] = providers;
|
|
4276
|
+
return section;
|
|
4277
|
+
}
|
|
4278
|
+
|
|
3786
4279
|
// src/admin/keyPolicyBody.ts
|
|
3787
4280
|
function parseKeyPolicyBody(body) {
|
|
3788
4281
|
const policy = {};
|
|
@@ -3845,7 +4338,7 @@ function parseKeyPolicyBody(body) {
|
|
|
3845
4338
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
3846
4339
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
3847
4340
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
3848
|
-
function
|
|
4341
|
+
function isRecord2(value) {
|
|
3849
4342
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3850
4343
|
}
|
|
3851
4344
|
function nonBlank(value) {
|
|
@@ -3865,7 +4358,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
3865
4358
|
const ids = /* @__PURE__ */ new Set();
|
|
3866
4359
|
raw.forEach((entry, index) => {
|
|
3867
4360
|
const path2 = `bindings[${index}]`;
|
|
3868
|
-
if (!
|
|
4361
|
+
if (!isRecord2(entry)) {
|
|
3869
4362
|
errors.push(`${path2} must be an object`);
|
|
3870
4363
|
return;
|
|
3871
4364
|
}
|
|
@@ -3894,12 +4387,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
3894
4387
|
} else if (entry.modelMappings.length > 100) {
|
|
3895
4388
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
3896
4389
|
} else if (entry.modelMappings.some(
|
|
3897
|
-
(mapping) => !
|
|
4390
|
+
(mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
3898
4391
|
)) {
|
|
3899
4392
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
3900
4393
|
}
|
|
3901
4394
|
}
|
|
3902
|
-
if (!
|
|
4395
|
+
if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
3903
4396
|
errors.push(`${path2}.target is invalid`);
|
|
3904
4397
|
} else {
|
|
3905
4398
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -3914,7 +4407,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
3914
4407
|
}
|
|
3915
4408
|
}
|
|
3916
4409
|
if (entry.modelMap !== void 0) {
|
|
3917
|
-
if (!
|
|
4410
|
+
if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
3918
4411
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
3919
4412
|
}
|
|
3920
4413
|
}
|
|
@@ -3935,19 +4428,19 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
3935
4428
|
import {
|
|
3936
4429
|
generateVoucherCode,
|
|
3937
4430
|
hashVoucherCode,
|
|
3938
|
-
loadServerConfig,
|
|
4431
|
+
loadServerConfig as loadServerConfig2,
|
|
3939
4432
|
newVoucherId,
|
|
3940
4433
|
toVoucherInfo,
|
|
3941
4434
|
voucherCodePrefix
|
|
3942
4435
|
} from "@omnicross/core/outbound-api";
|
|
3943
|
-
function
|
|
4436
|
+
function writeJson2(res, status, body) {
|
|
3944
4437
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
3945
4438
|
res.end(JSON.stringify(body));
|
|
3946
4439
|
}
|
|
3947
|
-
function
|
|
3948
|
-
|
|
4440
|
+
function writeErr2(res, status, message) {
|
|
4441
|
+
writeJson2(res, status, { error: { type: "voucher_error", message } });
|
|
3949
4442
|
}
|
|
3950
|
-
function
|
|
4443
|
+
function readJsonBody3(req) {
|
|
3951
4444
|
return new Promise((resolve10, reject) => {
|
|
3952
4445
|
const chunks = [];
|
|
3953
4446
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -3998,26 +4491,26 @@ function parseVoucherCreateBody(body) {
|
|
|
3998
4491
|
return { ok: true, input };
|
|
3999
4492
|
}
|
|
4000
4493
|
async function voucherEnabled(deps) {
|
|
4001
|
-
const config = await
|
|
4494
|
+
const config = await loadServerConfig2(deps.settingsStore);
|
|
4002
4495
|
return config.voucher?.enabled === true;
|
|
4003
4496
|
}
|
|
4004
4497
|
async function handleVoucher(req, res, method, rest, deps) {
|
|
4005
4498
|
const voucherDb = deps.voucherDb;
|
|
4006
|
-
if (!voucherDb) return
|
|
4499
|
+
if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
|
|
4007
4500
|
if (method === "GET" && rest.length === 0) {
|
|
4008
4501
|
const rows = await voucherDb.voucherList();
|
|
4009
|
-
return
|
|
4502
|
+
return writeJson2(res, 200, { vouchers: rows.map(toVoucherInfo) });
|
|
4010
4503
|
}
|
|
4011
4504
|
if (method === "POST" && rest.length === 0) {
|
|
4012
|
-
if (!await voucherEnabled(deps)) return
|
|
4505
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4013
4506
|
let body;
|
|
4014
4507
|
try {
|
|
4015
|
-
body = await
|
|
4508
|
+
body = await readJsonBody3(req);
|
|
4016
4509
|
} catch {
|
|
4017
|
-
return
|
|
4510
|
+
return writeErr2(res, 400, "Invalid JSON in request body");
|
|
4018
4511
|
}
|
|
4019
4512
|
const parsed = parseVoucherCreateBody(body);
|
|
4020
|
-
if (!parsed.ok) return
|
|
4513
|
+
if (!parsed.ok) return writeErr2(res, 400, parsed.message);
|
|
4021
4514
|
const code = generateVoucherCode();
|
|
4022
4515
|
const created = await voucherDb.voucherCreate({
|
|
4023
4516
|
id: newVoucherId(),
|
|
@@ -4025,7 +4518,7 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4025
4518
|
codePrefix: voucherCodePrefix(code),
|
|
4026
4519
|
...parsed.input
|
|
4027
4520
|
});
|
|
4028
|
-
return
|
|
4521
|
+
return writeJson2(res, 201, {
|
|
4029
4522
|
id: created.id,
|
|
4030
4523
|
codePrefix: created.codePrefix,
|
|
4031
4524
|
type: created.type,
|
|
@@ -4036,11 +4529,11 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4036
4529
|
}
|
|
4037
4530
|
const id = rest[0];
|
|
4038
4531
|
if (method === "POST" && id && rest[1] === "revoke") {
|
|
4039
|
-
if (!await voucherEnabled(deps)) return
|
|
4532
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4040
4533
|
const ok = await voucherDb.voucherRevokeCas(id, Date.now());
|
|
4041
|
-
return
|
|
4534
|
+
return writeJson2(res, ok ? 200 : 409, { ok });
|
|
4042
4535
|
}
|
|
4043
|
-
return
|
|
4536
|
+
return writeErr2(res, 405, `method ${method} not allowed on voucher`);
|
|
4044
4537
|
}
|
|
4045
4538
|
|
|
4046
4539
|
// src/admin/webhookConfigBody.ts
|
|
@@ -4970,12 +5463,12 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
4970
5463
|
}
|
|
4971
5464
|
|
|
4972
5465
|
// src/admin/accountAllowanceApi.ts
|
|
4973
|
-
function
|
|
5466
|
+
function writeJson3(res, status, body) {
|
|
4974
5467
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4975
5468
|
res.end(JSON.stringify(body));
|
|
4976
5469
|
}
|
|
4977
5470
|
function writeError2(res, status, message) {
|
|
4978
|
-
|
|
5471
|
+
writeJson3(res, status, { error: { type: "account_allowance_error", message } });
|
|
4979
5472
|
}
|
|
4980
5473
|
function readJson2(req) {
|
|
4981
5474
|
return new Promise((resolve10, reject) => {
|
|
@@ -5008,7 +5501,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5008
5501
|
if (!service.getSchedulingStatus) {
|
|
5009
5502
|
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
5010
5503
|
}
|
|
5011
|
-
return
|
|
5504
|
+
return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
5012
5505
|
}
|
|
5013
5506
|
if (method === "GET") {
|
|
5014
5507
|
const params = query(req);
|
|
@@ -5017,7 +5510,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5017
5510
|
if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
|
|
5018
5511
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5019
5512
|
const allowances = await service.list({ providerId, accountId });
|
|
5020
|
-
return
|
|
5513
|
+
return writeJson3(res, 200, { allowances });
|
|
5021
5514
|
}
|
|
5022
5515
|
if (method === "POST" && rest[0] === "refresh") {
|
|
5023
5516
|
const body = await readJson2(req);
|
|
@@ -5032,7 +5525,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5032
5525
|
if (accountId && allowances.length === 0) {
|
|
5033
5526
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
5034
5527
|
}
|
|
5035
|
-
return
|
|
5528
|
+
return writeJson3(res, 200, { allowances });
|
|
5036
5529
|
}
|
|
5037
5530
|
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
5038
5531
|
}
|
|
@@ -5051,7 +5544,7 @@ function readBody(req) {
|
|
|
5051
5544
|
req.on("error", reject);
|
|
5052
5545
|
});
|
|
5053
5546
|
}
|
|
5054
|
-
async function
|
|
5547
|
+
async function readJsonBody4(req) {
|
|
5055
5548
|
const raw = await readBody(req);
|
|
5056
5549
|
if (!raw.trim()) return {};
|
|
5057
5550
|
try {
|
|
@@ -5061,12 +5554,12 @@ async function readJsonBody3(req) {
|
|
|
5061
5554
|
return {};
|
|
5062
5555
|
}
|
|
5063
5556
|
}
|
|
5064
|
-
function
|
|
5557
|
+
function writeJson4(res, status, body) {
|
|
5065
5558
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5066
5559
|
res.end(JSON.stringify(body));
|
|
5067
5560
|
}
|
|
5068
5561
|
function writeJsonError(res, status, message) {
|
|
5069
|
-
|
|
5562
|
+
writeJson4(res, status, { error: { type: "admin_api_error", message } });
|
|
5070
5563
|
}
|
|
5071
5564
|
function maskProviderApiKey(apiKey) {
|
|
5072
5565
|
if (!apiKey) return "";
|
|
@@ -5173,6 +5666,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5173
5666
|
return await handleServer(req, res, method, deps);
|
|
5174
5667
|
case "images":
|
|
5175
5668
|
return await handleImages(res, method, rest, deps);
|
|
5669
|
+
case "search":
|
|
5670
|
+
return await handleSearchAdmin(req, res, method, rest, deps);
|
|
5176
5671
|
case "accounts":
|
|
5177
5672
|
return await handleAccounts(req, res, method, rest, deps);
|
|
5178
5673
|
case "cli":
|
|
@@ -5206,7 +5701,7 @@ function requestQuery(req) {
|
|
|
5206
5701
|
return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
|
|
5207
5702
|
}
|
|
5208
5703
|
function writeResult(res, result) {
|
|
5209
|
-
|
|
5704
|
+
writeJson4(res, result.status, result.body);
|
|
5210
5705
|
}
|
|
5211
5706
|
async function handleUsage(req, res, method, rest, deps) {
|
|
5212
5707
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
|
|
@@ -5215,13 +5710,13 @@ async function handleUsage(req, res, method, rest, deps) {
|
|
|
5215
5710
|
async function handleDashboardRoute(res, method, deps) {
|
|
5216
5711
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
|
|
5217
5712
|
const result = await handleDashboard(deps);
|
|
5218
|
-
return
|
|
5713
|
+
return writeJson4(res, result.status, result.body);
|
|
5219
5714
|
}
|
|
5220
5715
|
async function handlePricing(req, res, method, rest, deps) {
|
|
5221
5716
|
if (rest.length === 0) {
|
|
5222
5717
|
if (method === "GET") return writeResult(res, await handlePricingList(deps));
|
|
5223
5718
|
if (method === "PUT") {
|
|
5224
|
-
return writeResult(res, await handlePricingUpsert(await
|
|
5719
|
+
return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
|
|
5225
5720
|
}
|
|
5226
5721
|
if (method === "DELETE") {
|
|
5227
5722
|
return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
|
|
@@ -5232,7 +5727,7 @@ async function handlePricing(req, res, method, rest, deps) {
|
|
|
5232
5727
|
return writeResult(res, await handlePricingFetchLatest(deps));
|
|
5233
5728
|
}
|
|
5234
5729
|
if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
|
|
5235
|
-
return writeResult(res, await handlePricingResolveConflicts(await
|
|
5730
|
+
return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
|
|
5236
5731
|
}
|
|
5237
5732
|
return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
|
|
5238
5733
|
}
|
|
@@ -5246,15 +5741,15 @@ function migrationDeps(deps) {
|
|
|
5246
5741
|
}
|
|
5247
5742
|
async function handleMigrationExport(req, res, method, deps) {
|
|
5248
5743
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
|
|
5249
|
-
const body = await
|
|
5744
|
+
const body = await readJsonBody4(req);
|
|
5250
5745
|
const result = await handleExport(body, migrationDeps(deps));
|
|
5251
|
-
return
|
|
5746
|
+
return writeJson4(res, result.status, result.body);
|
|
5252
5747
|
}
|
|
5253
5748
|
async function handleMigrationImport(req, res, method, deps) {
|
|
5254
5749
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
|
|
5255
|
-
const body = await
|
|
5750
|
+
const body = await readJsonBody4(req);
|
|
5256
5751
|
const result = await handleImport(body, migrationDeps(deps));
|
|
5257
|
-
return
|
|
5752
|
+
return writeJson4(res, result.status, result.body);
|
|
5258
5753
|
}
|
|
5259
5754
|
async function handleProviders(req, res, method, rest, deps) {
|
|
5260
5755
|
const cfg = loadConfig(deps.configPath);
|
|
@@ -5285,13 +5780,13 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5285
5780
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
|
|
5286
5781
|
const row = cfg.providers.find((p) => p.id === rest[0]);
|
|
5287
5782
|
if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
|
|
5288
|
-
return
|
|
5783
|
+
return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
|
|
5289
5784
|
}
|
|
5290
5785
|
if (method === "GET") {
|
|
5291
|
-
return
|
|
5786
|
+
return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
|
|
5292
5787
|
}
|
|
5293
5788
|
if (method === "POST") {
|
|
5294
|
-
const body = await
|
|
5789
|
+
const body = await readJsonBody4(req);
|
|
5295
5790
|
const provider = parseProviderInput(body, void 0);
|
|
5296
5791
|
if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
|
|
5297
5792
|
if (cfg.providers.some((p) => p.id === provider.id)) {
|
|
@@ -5299,25 +5794,25 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5299
5794
|
}
|
|
5300
5795
|
cfg.providers.push(provider);
|
|
5301
5796
|
persistProviders(cfg, deps);
|
|
5302
|
-
return
|
|
5797
|
+
return writeJson4(res, 201, { provider: toProviderView(provider) });
|
|
5303
5798
|
}
|
|
5304
5799
|
const id = rest[0];
|
|
5305
5800
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5306
5801
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
5307
5802
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5308
5803
|
if (method === "PUT") {
|
|
5309
|
-
const body = await
|
|
5804
|
+
const body = await readJsonBody4(req);
|
|
5310
5805
|
const existing = cfg.providers[idx];
|
|
5311
5806
|
const updated = parseProviderInput(body, existing);
|
|
5312
5807
|
if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
|
|
5313
5808
|
cfg.providers[idx] = updated;
|
|
5314
5809
|
persistProviders(cfg, deps);
|
|
5315
|
-
return
|
|
5810
|
+
return writeJson4(res, 200, { provider: toProviderView(updated) });
|
|
5316
5811
|
}
|
|
5317
5812
|
if (method === "DELETE") {
|
|
5318
5813
|
cfg.providers.splice(idx, 1);
|
|
5319
5814
|
persistProviders(cfg, deps);
|
|
5320
|
-
return
|
|
5815
|
+
return writeJson4(res, 200, { ok: true });
|
|
5321
5816
|
}
|
|
5322
5817
|
return writeJsonError(res, 405, `method ${method} not allowed on providers`);
|
|
5323
5818
|
}
|
|
@@ -5326,7 +5821,7 @@ function persistProviders(cfg, deps) {
|
|
|
5326
5821
|
deps.llmConfig.reload(cfg);
|
|
5327
5822
|
}
|
|
5328
5823
|
async function handleProviderReorder(req, res, cfg, deps) {
|
|
5329
|
-
const body = await
|
|
5824
|
+
const body = await readJsonBody4(req);
|
|
5330
5825
|
const rawOrder = body["order"];
|
|
5331
5826
|
if (!Array.isArray(rawOrder)) {
|
|
5332
5827
|
return writeJsonError(res, 400, "reorder requires { order: string[] }");
|
|
@@ -5350,14 +5845,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
5350
5845
|
}
|
|
5351
5846
|
cfg.providers = reordered;
|
|
5352
5847
|
persistProviders(cfg, deps);
|
|
5353
|
-
return
|
|
5848
|
+
return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
5354
5849
|
}
|
|
5355
5850
|
async function handleDiscoverModels(res, id, cfg) {
|
|
5356
5851
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5357
5852
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5358
5853
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5359
5854
|
if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
|
|
5360
|
-
return
|
|
5855
|
+
return writeJson4(res, 200, { models: [], unsupportedFormat: true });
|
|
5361
5856
|
}
|
|
5362
5857
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
5363
5858
|
const base = row.baseUrl.replace(/\/+$/, "");
|
|
@@ -5374,32 +5869,32 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5374
5869
|
message = parsed?.error?.message || parsed?.message || message;
|
|
5375
5870
|
} catch {
|
|
5376
5871
|
}
|
|
5377
|
-
return
|
|
5872
|
+
return writeJson4(res, 200, {
|
|
5378
5873
|
models: [],
|
|
5379
5874
|
error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
|
|
5380
5875
|
});
|
|
5381
5876
|
}
|
|
5382
5877
|
const data = await response.json();
|
|
5383
5878
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
5384
|
-
return
|
|
5879
|
+
return writeJson4(res, 200, { models });
|
|
5385
5880
|
} catch (err5) {
|
|
5386
5881
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
5387
|
-
return
|
|
5882
|
+
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
5388
5883
|
}
|
|
5389
5884
|
}
|
|
5390
5885
|
async function handleTestModel(req, res, id, cfg) {
|
|
5391
5886
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5392
5887
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5393
5888
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5394
|
-
const body = await
|
|
5889
|
+
const body = await readJsonBody4(req);
|
|
5395
5890
|
const model = typeof body["model"] === "string" ? body["model"].trim() : "";
|
|
5396
5891
|
if (!model) return writeJsonError(res, 400, "test requires a { model } string");
|
|
5397
5892
|
if (row.apiFormat === "gemini") {
|
|
5398
|
-
return
|
|
5893
|
+
return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
|
|
5399
5894
|
}
|
|
5400
5895
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
5401
5896
|
if (!resolvedKey) {
|
|
5402
|
-
return
|
|
5897
|
+
return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
|
|
5403
5898
|
}
|
|
5404
5899
|
let url = row.baseUrl.replace(/\/+$/, "");
|
|
5405
5900
|
const prompt = "Reply with the single word: OK.";
|
|
@@ -5438,9 +5933,9 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5438
5933
|
message = parsed?.error?.message || parsed?.message || message;
|
|
5439
5934
|
} catch {
|
|
5440
5935
|
}
|
|
5441
|
-
return
|
|
5936
|
+
return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
|
|
5442
5937
|
}
|
|
5443
|
-
return
|
|
5938
|
+
return writeJson4(res, 200, {
|
|
5444
5939
|
ok: true,
|
|
5445
5940
|
status: response.status,
|
|
5446
5941
|
latencyMs,
|
|
@@ -5448,7 +5943,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5448
5943
|
});
|
|
5449
5944
|
} catch (err5) {
|
|
5450
5945
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
5451
|
-
return
|
|
5946
|
+
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
5452
5947
|
}
|
|
5453
5948
|
}
|
|
5454
5949
|
function extractSampleText(text, apiFormat) {
|
|
@@ -5489,7 +5984,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
5489
5984
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5490
5985
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5491
5986
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5492
|
-
return
|
|
5987
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
5493
5988
|
}
|
|
5494
5989
|
function parsePoolKeyInput(body, existing) {
|
|
5495
5990
|
const out = {};
|
|
@@ -5508,7 +6003,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
5508
6003
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5509
6004
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
5510
6005
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5511
|
-
const body = await
|
|
6006
|
+
const body = await readJsonBody4(req);
|
|
5512
6007
|
const parsed = parsePoolKeyInput(body);
|
|
5513
6008
|
if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
|
|
5514
6009
|
const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -5520,7 +6015,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
5520
6015
|
row.apiKeys = [...row.apiKeys ?? [], entry];
|
|
5521
6016
|
persistProviders(cfg, deps);
|
|
5522
6017
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5523
|
-
return
|
|
6018
|
+
return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
5524
6019
|
}
|
|
5525
6020
|
async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
5526
6021
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -5530,7 +6025,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
5530
6025
|
const row = cfg.providers[idx];
|
|
5531
6026
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
5532
6027
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
5533
|
-
const body = await
|
|
6028
|
+
const body = await readJsonBody4(req);
|
|
5534
6029
|
const existing = row.apiKeys[keyIdx];
|
|
5535
6030
|
const parsed = parsePoolKeyInput(body, existing);
|
|
5536
6031
|
const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
|
|
@@ -5540,7 +6035,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
5540
6035
|
row.apiKeys[keyIdx] = entry;
|
|
5541
6036
|
persistProviders(cfg, deps);
|
|
5542
6037
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5543
|
-
return
|
|
6038
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
5544
6039
|
}
|
|
5545
6040
|
async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
5546
6041
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -5554,7 +6049,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
|
5554
6049
|
if (row.apiKeys.length === 0) row.apiKeys = void 0;
|
|
5555
6050
|
persistProviders(cfg, deps);
|
|
5556
6051
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5557
|
-
return
|
|
6052
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
5558
6053
|
}
|
|
5559
6054
|
async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
5560
6055
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -5564,11 +6059,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
5564
6059
|
const row = cfg.providers[idx];
|
|
5565
6060
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
5566
6061
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
5567
|
-
const body = await
|
|
6062
|
+
const body = await readJsonBody4(req);
|
|
5568
6063
|
row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
|
|
5569
6064
|
persistProviders(cfg, deps);
|
|
5570
6065
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5571
|
-
return
|
|
6066
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
5572
6067
|
}
|
|
5573
6068
|
function parseApiKeysInput(raw, existing) {
|
|
5574
6069
|
if (!Array.isArray(raw)) return existing;
|
|
@@ -5754,13 +6249,13 @@ function handlePresets(res, method) {
|
|
|
5754
6249
|
website: p.website,
|
|
5755
6250
|
modelsEndpoint: p.modelsEndpoint
|
|
5756
6251
|
}));
|
|
5757
|
-
return
|
|
6252
|
+
return writeJson4(res, 200, { presets, excluded });
|
|
5758
6253
|
}
|
|
5759
6254
|
async function handleKeys(req, res, method, rest, deps) {
|
|
5760
6255
|
if (method === "GET" && rest.length === 0) {
|
|
5761
6256
|
const rows = await deps.keyDb.outboundApiKeysList();
|
|
5762
6257
|
const reader = deps.keySpendReader;
|
|
5763
|
-
if (!reader) return
|
|
6258
|
+
if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
|
|
5764
6259
|
const now = Date.now();
|
|
5765
6260
|
const keys = await Promise.all(
|
|
5766
6261
|
rows.map(async (row) => {
|
|
@@ -5772,13 +6267,13 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5772
6267
|
return info;
|
|
5773
6268
|
})
|
|
5774
6269
|
);
|
|
5775
|
-
return
|
|
6270
|
+
return writeJson4(res, 200, { keys });
|
|
5776
6271
|
}
|
|
5777
6272
|
if (method === "POST" && rest.length === 0) {
|
|
5778
|
-
const body = await
|
|
6273
|
+
const body = await readJsonBody4(req);
|
|
5779
6274
|
const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
|
|
5780
6275
|
const created = await createNamedKey(deps.keyDb, name);
|
|
5781
|
-
return
|
|
6276
|
+
return writeJson4(res, 201, {
|
|
5782
6277
|
id: created.id,
|
|
5783
6278
|
name: created.name,
|
|
5784
6279
|
keyPrefix: created.keyPrefix,
|
|
@@ -5789,7 +6284,7 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5789
6284
|
}
|
|
5790
6285
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
5791
6286
|
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
5792
|
-
if (revealed !== null) return
|
|
6287
|
+
if (revealed !== null) return writeJson4(res, 200, { key: revealed });
|
|
5793
6288
|
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
5794
6289
|
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
5795
6290
|
return writeJsonError(
|
|
@@ -5804,26 +6299,26 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5804
6299
|
const bound = await integrationKeyRequirement(deps, id);
|
|
5805
6300
|
if (bound) return writeJsonError(res, 409, bound);
|
|
5806
6301
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
5807
|
-
return
|
|
6302
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
5808
6303
|
}
|
|
5809
6304
|
if (method === "DELETE" && id && !action) {
|
|
5810
6305
|
const bound = await integrationKeyRequirement(deps, id);
|
|
5811
6306
|
if (bound) return writeJsonError(res, 409, bound);
|
|
5812
6307
|
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
5813
|
-
return
|
|
6308
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
5814
6309
|
}
|
|
5815
6310
|
if (method === "POST" && id && action === "enabled") {
|
|
5816
|
-
const body = await
|
|
6311
|
+
const body = await readJsonBody4(req);
|
|
5817
6312
|
const enabled = body["enabled"] === true;
|
|
5818
6313
|
if (!enabled) {
|
|
5819
6314
|
const bound = await integrationKeyRequirement(deps, id);
|
|
5820
6315
|
if (bound) return writeJsonError(res, 409, bound);
|
|
5821
6316
|
}
|
|
5822
6317
|
const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
|
|
5823
|
-
return
|
|
6318
|
+
return writeJson4(res, ok ? 200 : 404, { ok, enabled });
|
|
5824
6319
|
}
|
|
5825
6320
|
if (method === "POST" && id && action === "permissions") {
|
|
5826
|
-
const body = await
|
|
6321
|
+
const body = await readJsonBody4(req);
|
|
5827
6322
|
if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
|
|
5828
6323
|
return writeJsonError(res, 400, "body must contain only permissions");
|
|
5829
6324
|
}
|
|
@@ -5838,19 +6333,19 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5838
6333
|
);
|
|
5839
6334
|
}
|
|
5840
6335
|
const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
5841
|
-
if (!before) return
|
|
5842
|
-
if (before.revokedAt !== null) return
|
|
6336
|
+
if (!before) return writeJson4(res, 404, { ok: false });
|
|
6337
|
+
if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
|
|
5843
6338
|
const required = await integrationKeyRequirement(deps, id, permissions);
|
|
5844
6339
|
if (required) return writeJsonError(res, 409, required);
|
|
5845
6340
|
const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
|
|
5846
6341
|
if (!ok) {
|
|
5847
6342
|
const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
5848
|
-
return
|
|
6343
|
+
return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
|
|
5849
6344
|
}
|
|
5850
|
-
return
|
|
6345
|
+
return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
|
|
5851
6346
|
}
|
|
5852
6347
|
if (method === "POST" && id && action === "max-concurrency") {
|
|
5853
|
-
const body = await
|
|
6348
|
+
const body = await readJsonBody4(req);
|
|
5854
6349
|
const raw = body["maxConcurrency"];
|
|
5855
6350
|
let value;
|
|
5856
6351
|
if (raw === null) {
|
|
@@ -5865,14 +6360,14 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5865
6360
|
);
|
|
5866
6361
|
}
|
|
5867
6362
|
const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
|
|
5868
|
-
return
|
|
6363
|
+
return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
|
|
5869
6364
|
}
|
|
5870
6365
|
if (method === "POST" && id && action === "policy") {
|
|
5871
|
-
const body = await
|
|
6366
|
+
const body = await readJsonBody4(req);
|
|
5872
6367
|
const parsed = parseKeyPolicyBody(body);
|
|
5873
6368
|
if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
|
|
5874
6369
|
const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
|
|
5875
|
-
return
|
|
6370
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
5876
6371
|
}
|
|
5877
6372
|
return writeJsonError(res, 405, `method ${method} not allowed on keys`);
|
|
5878
6373
|
}
|
|
@@ -5963,7 +6458,8 @@ function outboundServerConfigInput(config) {
|
|
|
5963
6458
|
userMessageQueue: config.userMessageQueue,
|
|
5964
6459
|
concurrencyQueue: config.concurrencyQueue,
|
|
5965
6460
|
voucher: config.voucher,
|
|
5966
|
-
anthropic: config.anthropic
|
|
6461
|
+
anthropic: config.anthropic,
|
|
6462
|
+
search: config.search
|
|
5967
6463
|
};
|
|
5968
6464
|
}
|
|
5969
6465
|
function projectImagesConfigForAdmin(config) {
|
|
@@ -6026,15 +6522,21 @@ function currentImageGenerationId(deps) {
|
|
|
6026
6522
|
}
|
|
6027
6523
|
async function handleServer(req, res, method, deps) {
|
|
6028
6524
|
if (method === "GET") {
|
|
6029
|
-
const config = await
|
|
6525
|
+
const config = await loadServerConfig3(deps.settingsStore);
|
|
6030
6526
|
let server = config;
|
|
6031
6527
|
if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
|
|
6032
6528
|
if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
|
|
6033
6529
|
if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
|
|
6034
|
-
|
|
6530
|
+
if (config.search) {
|
|
6531
|
+
server = {
|
|
6532
|
+
...server,
|
|
6533
|
+
search: redactSearchServerConfig(config.search)
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
|
|
6035
6537
|
}
|
|
6036
6538
|
if (method === "PUT") {
|
|
6037
|
-
const patch = await
|
|
6539
|
+
const patch = await readJsonBody4(req);
|
|
6038
6540
|
const queueErrors = validateQueueSegments(patch);
|
|
6039
6541
|
if (queueErrors.length > 0) {
|
|
6040
6542
|
return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
|
|
@@ -6063,7 +6565,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
6063
6565
|
if (billingErrors.length > 0) {
|
|
6064
6566
|
return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
|
|
6065
6567
|
}
|
|
6066
|
-
const current = await
|
|
6568
|
+
const current = await loadServerConfig3(deps.settingsStore);
|
|
6067
6569
|
let effectivePatch = patch;
|
|
6068
6570
|
if (patch.proxy) {
|
|
6069
6571
|
effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
|
|
@@ -6084,6 +6586,20 @@ async function handleServer(req, res, method, deps) {
|
|
|
6084
6586
|
}
|
|
6085
6587
|
effectivePatch = { ...effectivePatch, images };
|
|
6086
6588
|
}
|
|
6589
|
+
if (patch.search !== void 0) {
|
|
6590
|
+
const searchPatch = preserveSearchSecrets(
|
|
6591
|
+
patch.search,
|
|
6592
|
+
current.search ?? DEFAULT_SEARCH_SERVER_CONFIG2
|
|
6593
|
+
);
|
|
6594
|
+
const searchErrors = validateSearchServerConfig(searchPatch);
|
|
6595
|
+
if (searchErrors.length > 0) {
|
|
6596
|
+
return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
|
|
6597
|
+
}
|
|
6598
|
+
effectivePatch = {
|
|
6599
|
+
...effectivePatch,
|
|
6600
|
+
search: searchPatch
|
|
6601
|
+
};
|
|
6602
|
+
}
|
|
6087
6603
|
const merged = mergeServerConfig(current, effectivePatch);
|
|
6088
6604
|
const priorImageGenerationId = currentImageGenerationId(deps);
|
|
6089
6605
|
try {
|
|
@@ -6136,7 +6652,11 @@ async function handleServer(req, res, method, deps) {
|
|
|
6136
6652
|
} catch {
|
|
6137
6653
|
}
|
|
6138
6654
|
}
|
|
6139
|
-
|
|
6655
|
+
const mergedForAdmin = merged.search ? {
|
|
6656
|
+
...merged,
|
|
6657
|
+
search: redactSearchServerConfig(merged.search)
|
|
6658
|
+
} : merged;
|
|
6659
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
|
|
6140
6660
|
}
|
|
6141
6661
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
6142
6662
|
}
|
|
@@ -6153,7 +6673,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6153
6673
|
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
6154
6674
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
6155
6675
|
});
|
|
6156
|
-
return
|
|
6676
|
+
return writeJson4(res, 200, {
|
|
6157
6677
|
available: true,
|
|
6158
6678
|
records,
|
|
6159
6679
|
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
@@ -6169,7 +6689,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6169
6689
|
providerId: query2.get("providerId") ?? void 0,
|
|
6170
6690
|
accountId: query2.get("accountId") ?? void 0
|
|
6171
6691
|
});
|
|
6172
|
-
return
|
|
6692
|
+
return writeJson4(res, 200, {
|
|
6173
6693
|
available: true,
|
|
6174
6694
|
entries,
|
|
6175
6695
|
collectedAt: Date.now()
|
|
@@ -6188,10 +6708,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6188
6708
|
const accounts = await deps.subscriptionAccounts.listAll();
|
|
6189
6709
|
const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
6190
6710
|
const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
|
|
6191
|
-
return
|
|
6711
|
+
return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
|
|
6192
6712
|
}
|
|
6193
6713
|
if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
|
|
6194
|
-
const body = await
|
|
6714
|
+
const body = await readJsonBody4(req);
|
|
6195
6715
|
const parsed = validateAccountBatchBody(body);
|
|
6196
6716
|
if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
|
|
6197
6717
|
const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
|
|
@@ -6207,15 +6727,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6207
6727
|
deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
|
|
6208
6728
|
}
|
|
6209
6729
|
}
|
|
6210
|
-
return
|
|
6730
|
+
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6211
6731
|
}
|
|
6212
6732
|
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6213
6733
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
6214
|
-
return
|
|
6734
|
+
return writeJson4(res, result.status, result.body);
|
|
6215
6735
|
}
|
|
6216
6736
|
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6217
6737
|
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
6218
|
-
return
|
|
6738
|
+
return writeJson4(res, result.status, result.body);
|
|
6219
6739
|
}
|
|
6220
6740
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
6221
6741
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6237,7 +6757,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6237
6757
|
resumeAt: entry.resumeAt
|
|
6238
6758
|
})) ?? [];
|
|
6239
6759
|
const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
|
|
6240
|
-
return
|
|
6760
|
+
return writeJson4(res, 200, { diagnostics });
|
|
6241
6761
|
}
|
|
6242
6762
|
if (method === "GET" && rest.length === 3 && rest[2] === "events") {
|
|
6243
6763
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6249,17 +6769,17 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6249
6769
|
}
|
|
6250
6770
|
const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
|
|
6251
6771
|
const diagnostics = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
|
|
6252
|
-
return
|
|
6772
|
+
return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
|
|
6253
6773
|
}
|
|
6254
6774
|
if (method === "PATCH" && rest.length === 2) {
|
|
6255
6775
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
6256
6776
|
if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6257
|
-
const body = await
|
|
6777
|
+
const body = await readJsonBody4(req);
|
|
6258
6778
|
const patch = validateAccountMetadataPatch(body);
|
|
6259
6779
|
if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
|
|
6260
6780
|
const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
|
|
6261
6781
|
if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
|
|
6262
|
-
return
|
|
6782
|
+
return writeJson4(res, 200, { ok: true });
|
|
6263
6783
|
}
|
|
6264
6784
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
6265
6785
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6268,15 +6788,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6268
6788
|
}
|
|
6269
6789
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6270
6790
|
const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
|
|
6271
|
-
return
|
|
6791
|
+
return writeJson4(res, result.status, result.body);
|
|
6272
6792
|
}
|
|
6273
6793
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
6274
|
-
const body2 = await
|
|
6794
|
+
const body2 = await readJsonBody4(req);
|
|
6275
6795
|
const result = await handleOAuthComplete(providerId, body2, deps);
|
|
6276
|
-
return
|
|
6796
|
+
return writeJson4(res, result.status, result.body);
|
|
6277
6797
|
}
|
|
6278
6798
|
if (method === "POST" && rest[1] === "accounts") {
|
|
6279
|
-
const body2 = await
|
|
6799
|
+
const body2 = await readJsonBody4(req);
|
|
6280
6800
|
const block = validateTokenBody(providerId, body2);
|
|
6281
6801
|
if (!block) {
|
|
6282
6802
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
@@ -6284,20 +6804,20 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6284
6804
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
6285
6805
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
6286
6806
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6287
|
-
return
|
|
6807
|
+
return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
|
|
6288
6808
|
}
|
|
6289
6809
|
if (method === "POST" && rest[1] === "import-external") {
|
|
6290
6810
|
if (providerId !== "claude" && providerId !== "codex") {
|
|
6291
6811
|
return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
|
|
6292
6812
|
}
|
|
6293
|
-
const body2 = await
|
|
6813
|
+
const body2 = await readJsonBody4(req);
|
|
6294
6814
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
6295
6815
|
const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
|
|
6296
6816
|
if (!result.ok) {
|
|
6297
6817
|
return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
|
|
6298
6818
|
}
|
|
6299
6819
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6300
|
-
return
|
|
6820
|
+
return writeJson4(res, 200, {
|
|
6301
6821
|
ok: true,
|
|
6302
6822
|
account: status2 ?? void 0,
|
|
6303
6823
|
nativeCredentialMode: result.nativeCredentialMode,
|
|
@@ -6312,7 +6832,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6312
6832
|
const writer2 = deps.subscriptionTokenWriter;
|
|
6313
6833
|
const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
|
|
6314
6834
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6315
|
-
return
|
|
6835
|
+
return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
|
|
6316
6836
|
}
|
|
6317
6837
|
if (method === "POST" && rest.length === 3 && rest[2] === "test") {
|
|
6318
6838
|
const accountId = rest[1];
|
|
@@ -6322,7 +6842,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6322
6842
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6323
6843
|
}
|
|
6324
6844
|
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
6325
|
-
return
|
|
6845
|
+
return writeJson4(res, 200, {
|
|
6326
6846
|
ok: result.ok,
|
|
6327
6847
|
marked: result.marked,
|
|
6328
6848
|
tier: result.tier,
|
|
@@ -6331,15 +6851,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6331
6851
|
}
|
|
6332
6852
|
if (method === "POST" && rest[2] === "label") {
|
|
6333
6853
|
const accountId = rest[1];
|
|
6334
|
-
const body2 = await
|
|
6854
|
+
const body2 = await readJsonBody4(req);
|
|
6335
6855
|
const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
|
|
6336
6856
|
const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
|
|
6337
6857
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6338
|
-
return
|
|
6858
|
+
return writeJson4(res, 200, { ok: true });
|
|
6339
6859
|
}
|
|
6340
6860
|
if (method === "POST" && rest[2] === "priority") {
|
|
6341
6861
|
const accountId = rest[1];
|
|
6342
|
-
const body2 = await
|
|
6862
|
+
const body2 = await readJsonBody4(req);
|
|
6343
6863
|
const raw = body2["priority"];
|
|
6344
6864
|
const priority = typeof raw === "number" ? raw : Number(raw);
|
|
6345
6865
|
if (!Number.isFinite(priority)) {
|
|
@@ -6347,11 +6867,11 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6347
6867
|
}
|
|
6348
6868
|
const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
|
|
6349
6869
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6350
|
-
return
|
|
6870
|
+
return writeJson4(res, 200, { ok: true });
|
|
6351
6871
|
}
|
|
6352
6872
|
if (method === "POST" && rest[2] === "proxy") {
|
|
6353
6873
|
const accountId = rest[1];
|
|
6354
|
-
const body2 = await
|
|
6874
|
+
const body2 = await readJsonBody4(req);
|
|
6355
6875
|
const rawProxy = body2["proxy"];
|
|
6356
6876
|
let proxy;
|
|
6357
6877
|
if (rawProxy !== null && rawProxy !== void 0) {
|
|
@@ -6360,63 +6880,63 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6360
6880
|
}
|
|
6361
6881
|
const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
|
|
6362
6882
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6363
|
-
return
|
|
6883
|
+
return writeJson4(res, 200, { ok: true });
|
|
6364
6884
|
}
|
|
6365
6885
|
if (method === "POST" && rest[2] === "supported-models") {
|
|
6366
6886
|
const accountId = rest[1];
|
|
6367
|
-
const body2 = await
|
|
6887
|
+
const body2 = await readJsonBody4(req);
|
|
6368
6888
|
const parsed = validateSupportedModelsBody(body2["supportedModels"]);
|
|
6369
6889
|
if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
|
|
6370
6890
|
const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
|
|
6371
6891
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6372
|
-
return
|
|
6892
|
+
return writeJson4(res, 200, { ok: true });
|
|
6373
6893
|
}
|
|
6374
6894
|
if (method === "PUT" && rest[1] === "active") {
|
|
6375
|
-
const body2 = await
|
|
6895
|
+
const body2 = await readJsonBody4(req);
|
|
6376
6896
|
const id = typeof body2["id"] === "string" ? body2["id"] : "";
|
|
6377
6897
|
if (!id) return writeJsonError(res, 400, "active switch requires { id }");
|
|
6378
6898
|
const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
|
|
6379
6899
|
if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
|
|
6380
|
-
return
|
|
6900
|
+
return writeJson4(res, 200, { ok: true });
|
|
6381
6901
|
}
|
|
6382
6902
|
if (method === "DELETE" && rest.length === 2) {
|
|
6383
6903
|
const accountId = rest[1];
|
|
6384
6904
|
const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
|
|
6385
6905
|
if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
6386
6906
|
deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
|
|
6387
|
-
return
|
|
6907
|
+
return writeJson4(res, 200, { ok: true });
|
|
6388
6908
|
}
|
|
6389
6909
|
if (method === "DELETE" && rest.length === 1) {
|
|
6390
6910
|
await deps.subscriptionTokenWriter.clearProvider(providerId);
|
|
6391
6911
|
deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
|
|
6392
|
-
return
|
|
6912
|
+
return writeJson4(res, 200, { ok: true });
|
|
6393
6913
|
}
|
|
6394
6914
|
if (method === "DELETE") {
|
|
6395
6915
|
return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
|
|
6396
6916
|
}
|
|
6397
|
-
const body = await
|
|
6917
|
+
const body = await readJsonBody4(req);
|
|
6398
6918
|
const config = validateTokenBody(providerId, body);
|
|
6399
6919
|
if (!config) {
|
|
6400
6920
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
6401
6921
|
}
|
|
6402
6922
|
await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
|
|
6403
6923
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6404
|
-
return
|
|
6924
|
+
return writeJson4(res, 200, status ? { account: status } : { ok: true });
|
|
6405
6925
|
}
|
|
6406
6926
|
return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
|
|
6407
6927
|
}
|
|
6408
6928
|
async function handleCli(req, res, method, rest, deps) {
|
|
6409
6929
|
if (method === "GET" && rest.length === 0) {
|
|
6410
6930
|
const result = handleCliList(process.platform, deps.cliPathProbe);
|
|
6411
|
-
return
|
|
6931
|
+
return writeJson4(res, result.status, result.body);
|
|
6412
6932
|
}
|
|
6413
6933
|
if (method === "GET" && rest[0] === "sessions") {
|
|
6414
6934
|
const result = handleCliSessions();
|
|
6415
|
-
return
|
|
6935
|
+
return writeJson4(res, result.status, result.body);
|
|
6416
6936
|
}
|
|
6417
6937
|
if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
|
|
6418
6938
|
const result = handleCliStop(rest[1]);
|
|
6419
|
-
return
|
|
6939
|
+
return writeJson4(res, result.status, result.body);
|
|
6420
6940
|
}
|
|
6421
6941
|
if (method === "POST" && rest[1] === "install") {
|
|
6422
6942
|
const cli = rest[0];
|
|
@@ -6424,14 +6944,14 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
6424
6944
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
6425
6945
|
}
|
|
6426
6946
|
const result = await handleCliInstall(cli, deps.cliCommandRunner);
|
|
6427
|
-
return
|
|
6947
|
+
return writeJson4(res, result.status, result.body);
|
|
6428
6948
|
}
|
|
6429
6949
|
if (method === "POST" && rest[1] === "launch") {
|
|
6430
6950
|
const cli = rest[0];
|
|
6431
6951
|
if (!isLaunchCliId(cli)) {
|
|
6432
6952
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
6433
6953
|
}
|
|
6434
|
-
const body = await
|
|
6954
|
+
const body = await readJsonBody4(req);
|
|
6435
6955
|
const providers = loadConfig(deps.configPath).providers ?? [];
|
|
6436
6956
|
const result = await handleCliLaunch(cli, body, {
|
|
6437
6957
|
llmConfig: deps.llmConfig,
|
|
@@ -6440,7 +6960,7 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
6440
6960
|
opener: deps.cliTerminalOpener,
|
|
6441
6961
|
probe: deps.cliPathProbe
|
|
6442
6962
|
});
|
|
6443
|
-
return
|
|
6963
|
+
return writeJson4(res, result.status, result.body);
|
|
6444
6964
|
}
|
|
6445
6965
|
return writeJsonError(res, 405, `method ${method} not allowed on cli`);
|
|
6446
6966
|
}
|
|
@@ -6450,52 +6970,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
|
|
|
6450
6970
|
const manager = factory();
|
|
6451
6971
|
try {
|
|
6452
6972
|
if (method === "GET" && rest.length === 0) {
|
|
6453
|
-
return
|
|
6973
|
+
return writeJson4(res, 200, {
|
|
6454
6974
|
integrations: await manager.listStatus(),
|
|
6455
6975
|
gateway: deps.outboundApiServer.getStatus()
|
|
6456
6976
|
});
|
|
6457
6977
|
}
|
|
6458
6978
|
if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
|
|
6459
6979
|
await manager.rotateGatewayKey();
|
|
6460
|
-
return
|
|
6980
|
+
return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
|
|
6461
6981
|
}
|
|
6462
6982
|
const client = rest[0];
|
|
6463
6983
|
if (!isIntegrationClient(client)) {
|
|
6464
6984
|
return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
|
|
6465
6985
|
}
|
|
6466
6986
|
if (method === "POST" && rest[1] === "key") {
|
|
6467
|
-
const body = await
|
|
6987
|
+
const body = await readJsonBody4(req);
|
|
6468
6988
|
if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
|
|
6469
6989
|
return writeJsonError(res, 400, "body must contain a non-empty keyId string");
|
|
6470
6990
|
}
|
|
6471
6991
|
const status = await manager.bindIntegrationKey(client, body.keyId.trim());
|
|
6472
|
-
return
|
|
6992
|
+
return writeJson4(res, 200, { integration: status });
|
|
6473
6993
|
}
|
|
6474
6994
|
if (method === "POST" && rest[1] === "plan") {
|
|
6475
|
-
const body = await
|
|
6995
|
+
const body = await readJsonBody4(req);
|
|
6476
6996
|
const configPath = body.configPath;
|
|
6477
6997
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
6478
6998
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
6479
6999
|
}
|
|
6480
7000
|
const plan = await manager.plan(client, configPath);
|
|
6481
|
-
return
|
|
7001
|
+
return writeJson4(res, 200, { plan });
|
|
6482
7002
|
}
|
|
6483
7003
|
if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
|
|
6484
|
-
const body = await
|
|
7004
|
+
const body = await readJsonBody4(req);
|
|
6485
7005
|
const configPath = body.configPath;
|
|
6486
7006
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
6487
7007
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
6488
7008
|
}
|
|
6489
7009
|
const status = await manager.install(client, configPath);
|
|
6490
|
-
return
|
|
7010
|
+
return writeJson4(res, 200, { integration: status });
|
|
6491
7011
|
}
|
|
6492
7012
|
if (method === "POST" && rest[1] === "repair") {
|
|
6493
7013
|
const status = await manager.repair(client);
|
|
6494
|
-
return
|
|
7014
|
+
return writeJson4(res, 200, { integration: status });
|
|
6495
7015
|
}
|
|
6496
7016
|
if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
|
|
6497
7017
|
const status = await manager.remove(client);
|
|
6498
|
-
return
|
|
7018
|
+
return writeJson4(res, 200, { integration: status });
|
|
6499
7019
|
}
|
|
6500
7020
|
return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
|
|
6501
7021
|
} catch (error) {
|
|
@@ -6638,7 +7158,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
6638
7158
|
}
|
|
6639
7159
|
const reader = deps.imageRuntimeStatus;
|
|
6640
7160
|
if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
|
|
6641
|
-
const serverConfig = await
|
|
7161
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
6642
7162
|
const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
|
|
6643
7163
|
const lifecycle = reader.status();
|
|
6644
7164
|
const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
|
|
@@ -6658,7 +7178,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
6658
7178
|
httpLeases: safeStatusCount(generation.httpLeases),
|
|
6659
7179
|
hostedLeases: safeStatusCount(generation.hostedLeases)
|
|
6660
7180
|
}));
|
|
6661
|
-
return
|
|
7181
|
+
return writeJson4(res, 200, {
|
|
6662
7182
|
configured: {
|
|
6663
7183
|
enabled: images.enabled,
|
|
6664
7184
|
provider: images.provider,
|
|
@@ -6686,7 +7206,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
6686
7206
|
async function handleStatus(res, method, deps) {
|
|
6687
7207
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
|
|
6688
7208
|
const status = deps.outboundApiServer.getStatus();
|
|
6689
|
-
const serverConfig = await
|
|
7209
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
6690
7210
|
const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
|
|
6691
7211
|
const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => gatewayBindingToEndpointConfig(binding));
|
|
6692
7212
|
const useSubscription = routes.some((route) => route.useSubscription);
|
|
@@ -6724,14 +7244,14 @@ async function handleStatus(res, method, deps) {
|
|
|
6724
7244
|
})() : void 0;
|
|
6725
7245
|
if (status.running) {
|
|
6726
7246
|
const queueStatus = deps.outboundApiServer.getQueueStatus();
|
|
6727
|
-
return
|
|
7247
|
+
return writeJson4(res, 200, {
|
|
6728
7248
|
...status,
|
|
6729
7249
|
endpoints,
|
|
6730
7250
|
queueStatus,
|
|
6731
7251
|
...imageRuntime ? { imageRuntime } : {}
|
|
6732
7252
|
});
|
|
6733
7253
|
}
|
|
6734
|
-
return
|
|
7254
|
+
return writeJson4(res, 200, {
|
|
6735
7255
|
...status,
|
|
6736
7256
|
endpoints,
|
|
6737
7257
|
...imageRuntime ? { imageRuntime } : {}
|
|
@@ -6755,18 +7275,18 @@ function resolvePlaygroundPath(endpoint, body) {
|
|
|
6755
7275
|
}
|
|
6756
7276
|
async function handlePlayground(req, res, method, deps) {
|
|
6757
7277
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
|
|
6758
|
-
const body = await
|
|
7278
|
+
const body = await readJsonBody4(req);
|
|
6759
7279
|
const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
|
|
6760
7280
|
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
6761
7281
|
const payload = body["body"];
|
|
6762
7282
|
const status = deps.outboundApiServer.getStatus();
|
|
6763
7283
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
6764
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
7284
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
|
|
6765
7285
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
6766
7286
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
6767
7287
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
6768
7288
|
}
|
|
6769
|
-
function
|
|
7289
|
+
function isRecord3(v) {
|
|
6770
7290
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
6771
7291
|
}
|
|
6772
7292
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -6901,7 +7421,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
6901
7421
|
}
|
|
6902
7422
|
|
|
6903
7423
|
// src/admin/version.ts
|
|
6904
|
-
var DAEMON_VERSION = true ? "0.
|
|
7424
|
+
var DAEMON_VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
6905
7425
|
|
|
6906
7426
|
// src/admin/AdminServer.ts
|
|
6907
7427
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -17376,6 +17896,14 @@ function buildDaemon(config, paths) {
|
|
|
17376
17896
|
onEvent: (row, at) => usageThroughput.record(row, at)
|
|
17377
17897
|
});
|
|
17378
17898
|
const initialImagesConfig = normalizeServerConfig2(decryptedConfig.server).images;
|
|
17899
|
+
const initialSearchConfig = normalizeServerConfig2(decryptedConfig.server).search;
|
|
17900
|
+
for (const issue of validateSearchServerConfig2(
|
|
17901
|
+
decryptedConfig.server?.search
|
|
17902
|
+
)) {
|
|
17903
|
+
logger.warn("[search] ignoring invalid config: " + issue);
|
|
17904
|
+
}
|
|
17905
|
+
const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
|
|
17906
|
+
const searchFrontendModes = initialSearchConfig.modes;
|
|
17379
17907
|
const imageObservability = new ImageObservability();
|
|
17380
17908
|
const imageRuntimeObservability = Object.freeze({
|
|
17381
17909
|
telemetrySink: imageObservability.telemetrySink,
|
|
@@ -17505,7 +18033,9 @@ function buildDaemon(config, paths) {
|
|
|
17505
18033
|
apiKeyPool,
|
|
17506
18034
|
usageRecorder,
|
|
17507
18035
|
openAIOperationRegistry,
|
|
17508
|
-
responsesHostedImageIngress
|
|
18036
|
+
responsesHostedImageIngress,
|
|
18037
|
+
searchRuntime,
|
|
18038
|
+
searchFrontendModes
|
|
17509
18039
|
});
|
|
17510
18040
|
if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
|
|
17511
18041
|
throw new Error(
|
|
@@ -17570,7 +18100,9 @@ function buildDaemon(config, paths) {
|
|
|
17570
18100
|
keySpendTracker,
|
|
17571
18101
|
// configurable-logging: route the server's OWN lifecycle + relay dispatch-error
|
|
17572
18102
|
// lines through the injected logger (honors level/format/file sink).
|
|
17573
|
-
logger
|
|
18103
|
+
logger,
|
|
18104
|
+
// plan 阶段5: the same instance the managed frontends hold.
|
|
18105
|
+
searchRuntime
|
|
17574
18106
|
});
|
|
17575
18107
|
const auditDir = defaultAuditDir(paths.configPath);
|
|
17576
18108
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
@@ -17592,6 +18124,10 @@ function buildDaemon(config, paths) {
|
|
|
17592
18124
|
}),
|
|
17593
18125
|
routeLeaseManager,
|
|
17594
18126
|
subscriptionAccounts,
|
|
18127
|
+
// search-settings-ui D3: the daemon's ONE search runtime + its
|
|
18128
|
+
// bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
|
|
18129
|
+
// `POST /admin/api/search/test` (501 when a light embedder omits it).
|
|
18130
|
+
searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
|
|
17595
18131
|
accountAllowanceService,
|
|
17596
18132
|
allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
|
|
17597
18133
|
accountProbeService: accountHealthProbeScheduler,
|
|
@@ -17728,6 +18264,8 @@ function buildDaemon(config, paths) {
|
|
|
17728
18264
|
keyDb,
|
|
17729
18265
|
settingsStore,
|
|
17730
18266
|
openAIOperationRegistry,
|
|
18267
|
+
searchRuntime,
|
|
18268
|
+
searchFrontendModes,
|
|
17731
18269
|
imageRuntimeManager,
|
|
17732
18270
|
imageObservability,
|
|
17733
18271
|
imageCleanupService,
|