@omnicross/daemon 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +19 -19
- package/dist/cli.cjs +852 -199
- package/dist/cli.js +826 -160
- package/dist/index.cjs +696 -189
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +668 -153
- package/package.json +6 -6
package/dist/cli.cjs
CHANGED
|
@@ -1160,7 +1160,10 @@ ${turn.responseBody}`);
|
|
|
1160
1160
|
|
|
1161
1161
|
// src/commands/doctor.ts
|
|
1162
1162
|
var import_node_util2 = require("util");
|
|
1163
|
-
var
|
|
1163
|
+
var import_outbound_api12 = require("@omnicross/core/outbound-api");
|
|
1164
|
+
var import_api4 = require("@omnicross/core/search/api");
|
|
1165
|
+
var import_http4 = require("@omnicross/core/search/http");
|
|
1166
|
+
var import_search2 = require("@omnicross/core/search");
|
|
1164
1167
|
|
|
1165
1168
|
// src/bootstrap.ts
|
|
1166
1169
|
var import_node_fs35 = require("fs");
|
|
@@ -1170,7 +1173,7 @@ var import_billing_types = require("@omnicross/contracts/billing-types");
|
|
|
1170
1173
|
var import_core4 = require("@omnicross/core");
|
|
1171
1174
|
var import_GeminiCodeAssistProjectResolver = require("@omnicross/core/auth/GeminiCodeAssistProjectResolver");
|
|
1172
1175
|
var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolService");
|
|
1173
|
-
var
|
|
1176
|
+
var import_outbound_api10 = require("@omnicross/core/outbound-api");
|
|
1174
1177
|
var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
|
|
1175
1178
|
var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
1176
1179
|
var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
@@ -1180,7 +1183,7 @@ var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/
|
|
|
1180
1183
|
var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
|
|
1181
1184
|
var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
|
|
1182
1185
|
var import_cli_launcher2 = require("@omnicross/cli-launcher");
|
|
1183
|
-
var
|
|
1186
|
+
var import_outbound_api11 = require("@omnicross/core/outbound-api");
|
|
1184
1187
|
var import_usage2 = require("@omnicross/core/usage");
|
|
1185
1188
|
var import_subscriptions6 = require("@omnicross/subscriptions");
|
|
1186
1189
|
|
|
@@ -1977,7 +1980,7 @@ async function handleRouteLeaseApi(req, res, path2, deps) {
|
|
|
1977
1980
|
|
|
1978
1981
|
// src/admin/adminApi.ts
|
|
1979
1982
|
var import_node_http = __toESM(require("http"), 1);
|
|
1980
|
-
var
|
|
1983
|
+
var import_outbound_api5 = require("@omnicross/core/outbound-api");
|
|
1981
1984
|
var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
|
|
1982
1985
|
var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
1983
1986
|
var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
@@ -4502,6 +4505,482 @@ async function handleDashboard(deps) {
|
|
|
4502
4505
|
return { status: 200, body: summary };
|
|
4503
4506
|
}
|
|
4504
4507
|
|
|
4508
|
+
// src/admin/searchAdminApi.ts
|
|
4509
|
+
var import_outbound_api2 = require("@omnicross/core/outbound-api");
|
|
4510
|
+
var import_api3 = require("@omnicross/core/search/api");
|
|
4511
|
+
var import_http3 = require("@omnicross/core/search/http");
|
|
4512
|
+
|
|
4513
|
+
// src/search/searchDoctorProjection.ts
|
|
4514
|
+
var import_search_types = require("@omnicross/contracts/search-types");
|
|
4515
|
+
var import_api = require("@omnicross/core/search/api");
|
|
4516
|
+
var import_http = require("@omnicross/core/search/http");
|
|
4517
|
+
var API_DOCTOR_PROVIDERS = [
|
|
4518
|
+
{
|
|
4519
|
+
id: "tavily",
|
|
4520
|
+
capabilities: import_api.TAVILY_CAPABILITIES,
|
|
4521
|
+
configured: (configs) => configs.tavily !== void 0,
|
|
4522
|
+
missingReason: "no API key configured"
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
id: "jina",
|
|
4526
|
+
capabilities: import_api.JINA_CAPABILITIES,
|
|
4527
|
+
configured: (configs) => configs.jina !== void 0,
|
|
4528
|
+
// Honest about the asymmetry: Jina CAN run keyless, but a provider nobody
|
|
4529
|
+
// asked for is still not enabled.
|
|
4530
|
+
missingReason: "not configured (Jina can run without a key, but must be enabled explicitly)"
|
|
4531
|
+
},
|
|
4532
|
+
{
|
|
4533
|
+
id: "searxng",
|
|
4534
|
+
capabilities: import_api.SEARXNG_CAPABILITIES,
|
|
4535
|
+
configured: (configs) => configs.searxng !== void 0,
|
|
4536
|
+
missingReason: "no API host configured"
|
|
4537
|
+
},
|
|
4538
|
+
{
|
|
4539
|
+
id: "zhipu",
|
|
4540
|
+
capabilities: import_api.ZHIPU_CAPABILITIES,
|
|
4541
|
+
configured: (configs) => configs.zhipu !== void 0,
|
|
4542
|
+
missingReason: "no API key configured"
|
|
4543
|
+
},
|
|
4544
|
+
{
|
|
4545
|
+
id: "z.ai",
|
|
4546
|
+
capabilities: import_api.ZHIPU_CAPABILITIES,
|
|
4547
|
+
configured: (configs) => configs["z.ai"] !== void 0,
|
|
4548
|
+
missingReason: "no API key configured"
|
|
4549
|
+
}
|
|
4550
|
+
];
|
|
4551
|
+
function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSearchContributions)(), apiConfigs) {
|
|
4552
|
+
const rows = contributions.map((contribution) => ({
|
|
4553
|
+
providerId: contribution.id,
|
|
4554
|
+
source: contribution.source,
|
|
4555
|
+
kind: contribution.kind,
|
|
4556
|
+
capabilities: contribution.capabilities
|
|
4557
|
+
}));
|
|
4558
|
+
if (apiConfigs === void 0) return rows;
|
|
4559
|
+
for (const provider of API_DOCTOR_PROVIDERS) {
|
|
4560
|
+
if (provider.configured(apiConfigs)) continue;
|
|
4561
|
+
rows.push({
|
|
4562
|
+
providerId: provider.id,
|
|
4563
|
+
source: "builtin",
|
|
4564
|
+
kind: "api",
|
|
4565
|
+
capabilities: provider.capabilities,
|
|
4566
|
+
status: "unconfigured",
|
|
4567
|
+
reason: provider.missingReason
|
|
4568
|
+
});
|
|
4569
|
+
}
|
|
4570
|
+
return rows;
|
|
4571
|
+
}
|
|
4572
|
+
var SEARCH_DOCTOR_QUERY = "mozilla developer network http headers";
|
|
4573
|
+
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
4574
|
+
if (outcome.kind === "results") {
|
|
4575
|
+
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
4576
|
+
return {
|
|
4577
|
+
providerId,
|
|
4578
|
+
status: "degraded",
|
|
4579
|
+
checkedAt,
|
|
4580
|
+
reason: "reachable, but the engine returned no usable results (possible partial drift)"
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
const error = (0, import_search_types.toSearchErrorShape)(outcome.error);
|
|
4584
|
+
const stage = error.details?.stage;
|
|
4585
|
+
const { status, reason } = classifySearchFailure(stage, error.code);
|
|
4586
|
+
return { providerId, status, checkedAt, reason, error };
|
|
4587
|
+
}
|
|
4588
|
+
function classifySearchFailure(stage, code) {
|
|
4589
|
+
if (stage === "challenge") {
|
|
4590
|
+
return { status: "blocked", reason: "the engine served a bot challenge instead of results" };
|
|
4591
|
+
}
|
|
4592
|
+
if (stage === "trust") {
|
|
4593
|
+
return {
|
|
4594
|
+
status: "blocked",
|
|
4595
|
+
reason: "the engine served a page that failed the anti-decoy trust check"
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
if (code === "policy_denied") {
|
|
4599
|
+
return {
|
|
4600
|
+
status: "blocked",
|
|
4601
|
+
reason: "the egress policy refused the request target"
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
if (code === "parse_failed") {
|
|
4605
|
+
return {
|
|
4606
|
+
status: "failed",
|
|
4607
|
+
reason: "the response was not recognizable as a search result page (parser drift suspected)"
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
if (code === "timeout") {
|
|
4611
|
+
return { status: "failed", reason: "the request exceeded its time budget" };
|
|
4612
|
+
}
|
|
4613
|
+
return { status: "failed", reason: `the request failed (${code})` };
|
|
4614
|
+
}
|
|
4615
|
+
async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4616
|
+
const diagnostics = [];
|
|
4617
|
+
for (const contribution of contributions) {
|
|
4618
|
+
try {
|
|
4619
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4620
|
+
diagnostics.push(
|
|
4621
|
+
classifyLiveSearchOutcome(contribution.id, { kind: "results", count: results.length }, now())
|
|
4622
|
+
);
|
|
4623
|
+
} catch (error) {
|
|
4624
|
+
diagnostics.push(classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, now()));
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
return diagnostics;
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
// src/search/SearchAssembly.ts
|
|
4631
|
+
var import_search = require("@omnicross/core/search");
|
|
4632
|
+
var import_api2 = require("@omnicross/core/search/api");
|
|
4633
|
+
var import_http2 = require("@omnicross/core/search/http");
|
|
4634
|
+
function searchEgressPolicyFrom(config) {
|
|
4635
|
+
const hosts = config.egress.allowedPrivateHosts;
|
|
4636
|
+
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
4637
|
+
}
|
|
4638
|
+
function searchPolicyFrom(config) {
|
|
4639
|
+
const { preferred, allowed, fallbackEnabled, maxAttempts } = config.policy;
|
|
4640
|
+
return {
|
|
4641
|
+
...preferred !== void 0 ? { preferred } : {},
|
|
4642
|
+
...allowed !== void 0 ? { allowed: [...allowed] } : {},
|
|
4643
|
+
fallbackEnabled,
|
|
4644
|
+
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
function searchContributionsFrom(config) {
|
|
4648
|
+
return [
|
|
4649
|
+
...(0, import_http2.builtinHttpSearchContributions)(),
|
|
4650
|
+
...(0, import_api2.apiSearchContributions)(config.providers, {
|
|
4651
|
+
egressPolicy: searchEgressPolicyFrom(config)
|
|
4652
|
+
})
|
|
4653
|
+
];
|
|
4654
|
+
}
|
|
4655
|
+
function buildSearchRuntime(config, options = {}) {
|
|
4656
|
+
const logger = options.logger ?? null;
|
|
4657
|
+
return (0, import_search.createSearchRuntime)({
|
|
4658
|
+
contributions: options.contributions ?? searchContributionsFrom(config),
|
|
4659
|
+
policy: searchPolicyFrom(config),
|
|
4660
|
+
...logger ? {
|
|
4661
|
+
onEvent: (event) => {
|
|
4662
|
+
logger.debug(`[search] ${formatSearchEvent(event)}`);
|
|
4663
|
+
}
|
|
4664
|
+
} : {}
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
function formatSearchEvent(event) {
|
|
4668
|
+
const parts = [
|
|
4669
|
+
`type=${event.type}`,
|
|
4670
|
+
`request=${event.requestId}`,
|
|
4671
|
+
`queryHash=${event.queryHash}`,
|
|
4672
|
+
`durationMs=${event.durationMs}`
|
|
4673
|
+
];
|
|
4674
|
+
if ("providerId" in event && event.providerId !== void 0) {
|
|
4675
|
+
parts.push(`provider=${event.providerId}`);
|
|
4676
|
+
}
|
|
4677
|
+
if ("outcome" in event && event.outcome !== void 0) parts.push(`outcome=${event.outcome}`);
|
|
4678
|
+
if ("errorCode" in event && event.errorCode !== void 0) parts.push(`error=${event.errorCode}`);
|
|
4679
|
+
if ("resultCount" in event && event.resultCount !== void 0) {
|
|
4680
|
+
parts.push(`results=${event.resultCount}`);
|
|
4681
|
+
}
|
|
4682
|
+
if ("fallbackCount" in event && event.fallbackCount !== void 0) {
|
|
4683
|
+
parts.push(`fallbacks=${event.fallbackCount}`);
|
|
4684
|
+
}
|
|
4685
|
+
return parts.join(" ");
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
// src/admin/searchAdminApi.ts
|
|
4689
|
+
var KEYLESS_HTTP_PROVIDER_IDS = /* @__PURE__ */ new Set(["http-bing", "http-duckduckgo"]);
|
|
4690
|
+
var API_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
4691
|
+
"tavily",
|
|
4692
|
+
"jina",
|
|
4693
|
+
"searxng",
|
|
4694
|
+
"zhipu",
|
|
4695
|
+
"z.ai"
|
|
4696
|
+
]);
|
|
4697
|
+
var SEARCH_QUERY_MAX_CODE_UNITS = 256;
|
|
4698
|
+
var QUERY_CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
4699
|
+
var SEARCH_RESULT_FIELD_CAPS = { title: 512, url: 2048, content: 1024 };
|
|
4700
|
+
var SEARCH_QUERY_MAX_RESULTS = 5;
|
|
4701
|
+
function sanitizeResultField(value, cap) {
|
|
4702
|
+
const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
|
|
4703
|
+
return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
|
|
4704
|
+
}
|
|
4705
|
+
function writeJson(res, status, body) {
|
|
4706
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4707
|
+
res.end(JSON.stringify(body));
|
|
4708
|
+
}
|
|
4709
|
+
function writeErr(res, status, message) {
|
|
4710
|
+
writeJson(res, status, { error: { type: "admin_api_error", message } });
|
|
4711
|
+
}
|
|
4712
|
+
var SEARCH_MAX_BODY_BYTES = 64 * 1024;
|
|
4713
|
+
var SearchBodyTooLargeError = class extends Error {
|
|
4714
|
+
};
|
|
4715
|
+
async function readJsonBody2(req) {
|
|
4716
|
+
const chunks = [];
|
|
4717
|
+
let bytes = 0;
|
|
4718
|
+
for await (const chunk of req) {
|
|
4719
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4720
|
+
bytes += buffer.length;
|
|
4721
|
+
if (bytes > SEARCH_MAX_BODY_BYTES) throw new SearchBodyTooLargeError("request body is too large");
|
|
4722
|
+
chunks.push(buffer);
|
|
4723
|
+
}
|
|
4724
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
4725
|
+
if (!raw.trim()) return {};
|
|
4726
|
+
try {
|
|
4727
|
+
const parsed = JSON.parse(raw);
|
|
4728
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4729
|
+
} catch {
|
|
4730
|
+
return {};
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
async function readBodyOrReject(req, res) {
|
|
4734
|
+
try {
|
|
4735
|
+
return await readJsonBody2(req);
|
|
4736
|
+
} catch (error) {
|
|
4737
|
+
if (error instanceof SearchBodyTooLargeError) {
|
|
4738
|
+
writeErr(res, 400, error.message);
|
|
4739
|
+
return void 0;
|
|
4740
|
+
}
|
|
4741
|
+
throw error;
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
async function handleSearchAdmin(req, res, method, rest, deps) {
|
|
4745
|
+
if (rest.length === 1 && rest[0] === "diagnostics") {
|
|
4746
|
+
if (!deps.searchStatus) {
|
|
4747
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4748
|
+
}
|
|
4749
|
+
if (method !== "GET") {
|
|
4750
|
+
return writeErr(res, 405, `method ${method} not allowed on search diagnostics`);
|
|
4751
|
+
}
|
|
4752
|
+
return handleSearchDiagnostics(res, deps);
|
|
4753
|
+
}
|
|
4754
|
+
if (rest.length === 1 && rest[0] === "test") {
|
|
4755
|
+
if (!deps.searchStatus) {
|
|
4756
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4757
|
+
}
|
|
4758
|
+
if (method !== "POST") {
|
|
4759
|
+
return writeErr(res, 405, `method ${method} not allowed on search test`);
|
|
4760
|
+
}
|
|
4761
|
+
return handleSearchTest(req, res, deps);
|
|
4762
|
+
}
|
|
4763
|
+
if (rest.length === 1 && rest[0] === "query") {
|
|
4764
|
+
if (!deps.searchStatus) {
|
|
4765
|
+
return writeErr(res, 501, "Search status is not available in this build");
|
|
4766
|
+
}
|
|
4767
|
+
if (method !== "POST") {
|
|
4768
|
+
return writeErr(res, 405, `method ${method} not allowed on search query`);
|
|
4769
|
+
}
|
|
4770
|
+
return handleSearchQuery(req, res, deps);
|
|
4771
|
+
}
|
|
4772
|
+
return writeErr(res, 404, `unknown search route '/${rest.join("/")}'`);
|
|
4773
|
+
}
|
|
4774
|
+
async function handleSearchDiagnostics(res, deps) {
|
|
4775
|
+
const status = deps.searchStatus;
|
|
4776
|
+
const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
|
|
4777
|
+
const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4778
|
+
const rows = buildSearchDoctorSnapshot(
|
|
4779
|
+
status.runtime.listProviders(),
|
|
4780
|
+
search.providers
|
|
4781
|
+
);
|
|
4782
|
+
const snapshot = {
|
|
4783
|
+
rows,
|
|
4784
|
+
modes: {
|
|
4785
|
+
// codex is read from the LIVE config per request — an admin PUT has
|
|
4786
|
+
// already applied. responses/anthropic were captured at bootstrap.
|
|
4787
|
+
codex: search.modes.codex,
|
|
4788
|
+
responses: status.modes.responses,
|
|
4789
|
+
anthropic: status.modes.anthropic
|
|
4790
|
+
},
|
|
4791
|
+
applySemantics: { codex: "immediate", rest: "restart" }
|
|
4792
|
+
};
|
|
4793
|
+
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4794
|
+
}
|
|
4795
|
+
async function handleSearchTest(req, res, deps) {
|
|
4796
|
+
const status = deps.searchStatus;
|
|
4797
|
+
const body = await readBodyOrReject(req, res);
|
|
4798
|
+
if (body === void 0) return;
|
|
4799
|
+
const providerId = body["providerId"];
|
|
4800
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4801
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4802
|
+
}
|
|
4803
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4804
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4805
|
+
}
|
|
4806
|
+
const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
|
|
4807
|
+
const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4808
|
+
const providers = search.providers;
|
|
4809
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4810
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4811
|
+
}
|
|
4812
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4813
|
+
const fetchImpl = status.testFetch;
|
|
4814
|
+
const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
|
|
4815
|
+
const contributions = [
|
|
4816
|
+
...(0, import_http3.builtinHttpSearchContributions)(transport),
|
|
4817
|
+
...(0, import_api3.apiSearchContributions)(search.providers, {
|
|
4818
|
+
egressPolicy,
|
|
4819
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
4820
|
+
})
|
|
4821
|
+
];
|
|
4822
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4823
|
+
if (!contribution) {
|
|
4824
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4825
|
+
}
|
|
4826
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4827
|
+
try {
|
|
4828
|
+
const results = await contribution.provider.search(SEARCH_DOCTOR_QUERY, { maxResults: 5 });
|
|
4829
|
+
const diagnostic = classifyLiveSearchOutcome(
|
|
4830
|
+
contribution.id,
|
|
4831
|
+
{ kind: "results", count: results.length },
|
|
4832
|
+
checkedAt
|
|
4833
|
+
);
|
|
4834
|
+
const response = { diagnostic, resultCount: results.length };
|
|
4835
|
+
return writeJson(res, 200, { result: response });
|
|
4836
|
+
} catch (error) {
|
|
4837
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
4838
|
+
const response = { diagnostic };
|
|
4839
|
+
return writeJson(res, 200, { result: response });
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
async function handleSearchQuery(req, res, deps) {
|
|
4843
|
+
const status = deps.searchStatus;
|
|
4844
|
+
const body = await readBodyOrReject(req, res);
|
|
4845
|
+
if (body === void 0) return;
|
|
4846
|
+
const providerId = body["providerId"];
|
|
4847
|
+
if (typeof providerId !== "string" || providerId.length === 0) {
|
|
4848
|
+
return writeErr(res, 400, "providerId must be a non-empty string");
|
|
4849
|
+
}
|
|
4850
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && !API_PROVIDER_IDS.has(providerId)) {
|
|
4851
|
+
return writeErr(res, 404, `unknown search provider '${providerId}'`);
|
|
4852
|
+
}
|
|
4853
|
+
const query2 = body["query"];
|
|
4854
|
+
if (typeof query2 !== "string" || query2.trim().length === 0) {
|
|
4855
|
+
return writeErr(res, 400, "query must be a non-empty string");
|
|
4856
|
+
}
|
|
4857
|
+
if (query2.length > SEARCH_QUERY_MAX_CODE_UNITS) {
|
|
4858
|
+
return writeErr(res, 400, `query must be at most ${SEARCH_QUERY_MAX_CODE_UNITS} characters`);
|
|
4859
|
+
}
|
|
4860
|
+
if (QUERY_CONTROL_CHARS.test(query2)) {
|
|
4861
|
+
return writeErr(res, 400, "query must not contain control characters");
|
|
4862
|
+
}
|
|
4863
|
+
const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
|
|
4864
|
+
const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4865
|
+
const providers = search.providers;
|
|
4866
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4867
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4868
|
+
}
|
|
4869
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4870
|
+
const fetchImpl = status.testFetch;
|
|
4871
|
+
const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
|
|
4872
|
+
const contributions = [
|
|
4873
|
+
...(0, import_http3.builtinHttpSearchContributions)(transport),
|
|
4874
|
+
...(0, import_api3.apiSearchContributions)(search.providers, {
|
|
4875
|
+
egressPolicy,
|
|
4876
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
4877
|
+
})
|
|
4878
|
+
];
|
|
4879
|
+
const contribution = contributions.find((c) => c.id === providerId);
|
|
4880
|
+
if (!contribution) {
|
|
4881
|
+
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4882
|
+
}
|
|
4883
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4884
|
+
try {
|
|
4885
|
+
const results = await contribution.provider.search(query2, { maxResults: 5 });
|
|
4886
|
+
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4887
|
+
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4888
|
+
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4889
|
+
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4890
|
+
}));
|
|
4891
|
+
const diagnostic = sanitized.length === 0 ? { providerId: contribution.id, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
4892
|
+
contribution.id,
|
|
4893
|
+
{ kind: "results", count: sanitized.length },
|
|
4894
|
+
checkedAt
|
|
4895
|
+
);
|
|
4896
|
+
const response = {
|
|
4897
|
+
diagnostic,
|
|
4898
|
+
resultCount: sanitized.length,
|
|
4899
|
+
results: sanitized
|
|
4900
|
+
};
|
|
4901
|
+
return writeJson(res, 200, { result: response });
|
|
4902
|
+
} catch (error) {
|
|
4903
|
+
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
4904
|
+
const response = { diagnostic };
|
|
4905
|
+
return writeJson(res, 200, { result: response });
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
// src/admin/searchAdminView.ts
|
|
4910
|
+
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4911
|
+
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4912
|
+
function isRecord(value) {
|
|
4913
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4914
|
+
}
|
|
4915
|
+
function redactSearchServerConfig(search) {
|
|
4916
|
+
const providers = {};
|
|
4917
|
+
for (const [id, raw] of Object.entries(search.providers)) {
|
|
4918
|
+
const entry = raw;
|
|
4919
|
+
const view = {};
|
|
4920
|
+
if (entry.apiHost !== void 0) view.apiHost = entry.apiHost;
|
|
4921
|
+
if (entry.basicAuthUsername !== void 0) view.basicAuthUsername = entry.basicAuthUsername;
|
|
4922
|
+
if (API_KEY_PROVIDERS.has(id)) {
|
|
4923
|
+
view.apiKeyConfigured = typeof entry.apiKey === "string" && entry.apiKey.length > 0;
|
|
4924
|
+
}
|
|
4925
|
+
if (BASIC_AUTH_PROVIDERS.has(id)) {
|
|
4926
|
+
view.basicAuthPasswordConfigured = typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0;
|
|
4927
|
+
}
|
|
4928
|
+
providers[id] = view;
|
|
4929
|
+
}
|
|
4930
|
+
return {
|
|
4931
|
+
modes: search.modes,
|
|
4932
|
+
providers,
|
|
4933
|
+
egress: { allowedPrivateHosts: [...search.egress.allowedPrivateHosts] },
|
|
4934
|
+
policy: { ...search.policy, ...search.policy.allowed ? { allowed: [...search.policy.allowed] } : {} }
|
|
4935
|
+
};
|
|
4936
|
+
}
|
|
4937
|
+
function storedSecrets(current, id) {
|
|
4938
|
+
const entry = current.providers[id];
|
|
4939
|
+
if (!entry) return {};
|
|
4940
|
+
const out = {};
|
|
4941
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) out.apiKey = entry.apiKey;
|
|
4942
|
+
if (typeof entry.basicAuthPassword === "string" && entry.basicAuthPassword.length > 0) {
|
|
4943
|
+
out.basicAuthPassword = entry.basicAuthPassword;
|
|
4944
|
+
}
|
|
4945
|
+
return out;
|
|
4946
|
+
}
|
|
4947
|
+
function resolveSecretField(entry, field, stored) {
|
|
4948
|
+
if (!(field in entry)) {
|
|
4949
|
+
if (stored !== void 0) entry[field] = stored;
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
const value = entry[field];
|
|
4953
|
+
if (value === null) {
|
|
4954
|
+
delete entry[field];
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
if (typeof value === "string" && value.trim().length > 0) return;
|
|
4958
|
+
if (stored !== void 0) entry[field] = stored;
|
|
4959
|
+
else delete entry[field];
|
|
4960
|
+
}
|
|
4961
|
+
function preserveSearchSecrets(incoming, current) {
|
|
4962
|
+
if (!isRecord(incoming)) return incoming;
|
|
4963
|
+
const section = { ...incoming };
|
|
4964
|
+
const providersValue = section["providers"];
|
|
4965
|
+
if (!isRecord(providersValue)) return section;
|
|
4966
|
+
const providers = {};
|
|
4967
|
+
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4968
|
+
if (!isRecord(entryValue)) {
|
|
4969
|
+
providers[id] = entryValue;
|
|
4970
|
+
continue;
|
|
4971
|
+
}
|
|
4972
|
+
const entry = { ...entryValue };
|
|
4973
|
+
delete entry["apiKeyConfigured"];
|
|
4974
|
+
delete entry["basicAuthPasswordConfigured"];
|
|
4975
|
+
const stored = storedSecrets(current, id);
|
|
4976
|
+
resolveSecretField(entry, "apiKey", stored.apiKey);
|
|
4977
|
+
resolveSecretField(entry, "basicAuthPassword", stored.basicAuthPassword);
|
|
4978
|
+
providers[id] = entry;
|
|
4979
|
+
}
|
|
4980
|
+
section["providers"] = providers;
|
|
4981
|
+
return section;
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4505
4984
|
// src/admin/keyPolicyBody.ts
|
|
4506
4985
|
function parseKeyPolicyBody(body) {
|
|
4507
4986
|
const policy = {};
|
|
@@ -4564,7 +5043,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4564
5043
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4565
5044
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4566
5045
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4567
|
-
function
|
|
5046
|
+
function isRecord2(value) {
|
|
4568
5047
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4569
5048
|
}
|
|
4570
5049
|
function nonBlank(value) {
|
|
@@ -4584,7 +5063,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4584
5063
|
const ids = /* @__PURE__ */ new Set();
|
|
4585
5064
|
raw.forEach((entry, index) => {
|
|
4586
5065
|
const path2 = `bindings[${index}]`;
|
|
4587
|
-
if (!
|
|
5066
|
+
if (!isRecord2(entry)) {
|
|
4588
5067
|
errors.push(`${path2} must be an object`);
|
|
4589
5068
|
return;
|
|
4590
5069
|
}
|
|
@@ -4613,12 +5092,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4613
5092
|
} else if (entry.modelMappings.length > 100) {
|
|
4614
5093
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4615
5094
|
} else if (entry.modelMappings.some(
|
|
4616
|
-
(mapping) => !
|
|
5095
|
+
(mapping) => !isRecord2(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4617
5096
|
)) {
|
|
4618
5097
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4619
5098
|
}
|
|
4620
5099
|
}
|
|
4621
|
-
if (!
|
|
5100
|
+
if (!isRecord2(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4622
5101
|
errors.push(`${path2}.target is invalid`);
|
|
4623
5102
|
} else {
|
|
4624
5103
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4633,7 +5112,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4633
5112
|
}
|
|
4634
5113
|
}
|
|
4635
5114
|
if (entry.modelMap !== void 0) {
|
|
4636
|
-
if (!
|
|
5115
|
+
if (!isRecord2(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4637
5116
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4638
5117
|
}
|
|
4639
5118
|
}
|
|
@@ -4651,15 +5130,15 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4651
5130
|
}
|
|
4652
5131
|
|
|
4653
5132
|
// src/admin/voucherAdmin.ts
|
|
4654
|
-
var
|
|
4655
|
-
function
|
|
5133
|
+
var import_outbound_api3 = require("@omnicross/core/outbound-api");
|
|
5134
|
+
function writeJson2(res, status, body) {
|
|
4656
5135
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4657
5136
|
res.end(JSON.stringify(body));
|
|
4658
5137
|
}
|
|
4659
|
-
function
|
|
4660
|
-
|
|
5138
|
+
function writeErr2(res, status, message) {
|
|
5139
|
+
writeJson2(res, status, { error: { type: "voucher_error", message } });
|
|
4661
5140
|
}
|
|
4662
|
-
function
|
|
5141
|
+
function readJsonBody3(req) {
|
|
4663
5142
|
return new Promise((resolve11, reject) => {
|
|
4664
5143
|
const chunks = [];
|
|
4665
5144
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -4710,34 +5189,34 @@ function parseVoucherCreateBody(body) {
|
|
|
4710
5189
|
return { ok: true, input };
|
|
4711
5190
|
}
|
|
4712
5191
|
async function voucherEnabled(deps) {
|
|
4713
|
-
const config = await (0,
|
|
5192
|
+
const config = await (0, import_outbound_api3.loadServerConfig)(deps.settingsStore);
|
|
4714
5193
|
return config.voucher?.enabled === true;
|
|
4715
5194
|
}
|
|
4716
5195
|
async function handleVoucher(req, res, method, rest, deps) {
|
|
4717
5196
|
const voucherDb = deps.voucherDb;
|
|
4718
|
-
if (!voucherDb) return
|
|
5197
|
+
if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
|
|
4719
5198
|
if (method === "GET" && rest.length === 0) {
|
|
4720
5199
|
const rows = await voucherDb.voucherList();
|
|
4721
|
-
return
|
|
5200
|
+
return writeJson2(res, 200, { vouchers: rows.map(import_outbound_api3.toVoucherInfo) });
|
|
4722
5201
|
}
|
|
4723
5202
|
if (method === "POST" && rest.length === 0) {
|
|
4724
|
-
if (!await voucherEnabled(deps)) return
|
|
5203
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4725
5204
|
let body;
|
|
4726
5205
|
try {
|
|
4727
|
-
body = await
|
|
5206
|
+
body = await readJsonBody3(req);
|
|
4728
5207
|
} catch {
|
|
4729
|
-
return
|
|
5208
|
+
return writeErr2(res, 400, "Invalid JSON in request body");
|
|
4730
5209
|
}
|
|
4731
5210
|
const parsed = parseVoucherCreateBody(body);
|
|
4732
|
-
if (!parsed.ok) return
|
|
4733
|
-
const code = (0,
|
|
5211
|
+
if (!parsed.ok) return writeErr2(res, 400, parsed.message);
|
|
5212
|
+
const code = (0, import_outbound_api3.generateVoucherCode)();
|
|
4734
5213
|
const created = await voucherDb.voucherCreate({
|
|
4735
|
-
id: (0,
|
|
4736
|
-
codeHash: (0,
|
|
4737
|
-
codePrefix: (0,
|
|
5214
|
+
id: (0, import_outbound_api3.newVoucherId)(),
|
|
5215
|
+
codeHash: (0, import_outbound_api3.hashVoucherCode)(code),
|
|
5216
|
+
codePrefix: (0, import_outbound_api3.voucherCodePrefix)(code),
|
|
4738
5217
|
...parsed.input
|
|
4739
5218
|
});
|
|
4740
|
-
return
|
|
5219
|
+
return writeJson2(res, 201, {
|
|
4741
5220
|
id: created.id,
|
|
4742
5221
|
codePrefix: created.codePrefix,
|
|
4743
5222
|
type: created.type,
|
|
@@ -4748,11 +5227,11 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
4748
5227
|
}
|
|
4749
5228
|
const id = rest[0];
|
|
4750
5229
|
if (method === "POST" && id && rest[1] === "revoke") {
|
|
4751
|
-
if (!await voucherEnabled(deps)) return
|
|
5230
|
+
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
4752
5231
|
const ok = await voucherDb.voucherRevokeCas(id, Date.now());
|
|
4753
|
-
return
|
|
5232
|
+
return writeJson2(res, ok ? 200 : 409, { ok });
|
|
4754
5233
|
}
|
|
4755
|
-
return
|
|
5234
|
+
return writeErr2(res, 405, `method ${method} not allowed on voucher`);
|
|
4756
5235
|
}
|
|
4757
5236
|
|
|
4758
5237
|
// src/admin/webhookConfigBody.ts
|
|
@@ -4903,7 +5382,7 @@ function applyBillingConfig(config) {
|
|
|
4903
5382
|
}
|
|
4904
5383
|
|
|
4905
5384
|
// src/migration/migration.ts
|
|
4906
|
-
var
|
|
5385
|
+
var import_outbound_api4 = require("@omnicross/core/outbound-api");
|
|
4907
5386
|
|
|
4908
5387
|
// src/ports/account-multi.ts
|
|
4909
5388
|
var import_node_crypto8 = require("crypto");
|
|
@@ -5331,7 +5810,7 @@ async function gatherExport(deps, passphrase) {
|
|
|
5331
5810
|
v: BUNDLE_VERSION,
|
|
5332
5811
|
providers: cfg.providers,
|
|
5333
5812
|
tokens,
|
|
5334
|
-
server: (0,
|
|
5813
|
+
server: (0, import_outbound_api4.normalizeServerConfig)(cfg.server)
|
|
5335
5814
|
};
|
|
5336
5815
|
return sealPack(JSON.stringify(bundle), passphrase);
|
|
5337
5816
|
}
|
|
@@ -5380,7 +5859,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5380
5859
|
const imageErrors = validateImagesAdminConfig(rawServer["images"]);
|
|
5381
5860
|
if (imageErrors.length > 0) throw new Error("migration pack has an invalid Images config");
|
|
5382
5861
|
}
|
|
5383
|
-
validatedServer = (0,
|
|
5862
|
+
validatedServer = (0, import_outbound_api4.normalizeServerConfig)(bundle.server);
|
|
5384
5863
|
}
|
|
5385
5864
|
const validatedProviders = [];
|
|
5386
5865
|
for (const raw of rawProviders) {
|
|
@@ -5661,12 +6140,12 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5661
6140
|
}
|
|
5662
6141
|
|
|
5663
6142
|
// src/admin/accountAllowanceApi.ts
|
|
5664
|
-
function
|
|
6143
|
+
function writeJson3(res, status, body) {
|
|
5665
6144
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5666
6145
|
res.end(JSON.stringify(body));
|
|
5667
6146
|
}
|
|
5668
6147
|
function writeError2(res, status, message) {
|
|
5669
|
-
|
|
6148
|
+
writeJson3(res, status, { error: { type: "account_allowance_error", message } });
|
|
5670
6149
|
}
|
|
5671
6150
|
function readJson2(req) {
|
|
5672
6151
|
return new Promise((resolve11, reject) => {
|
|
@@ -5699,7 +6178,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5699
6178
|
if (!service.getSchedulingStatus) {
|
|
5700
6179
|
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
5701
6180
|
}
|
|
5702
|
-
return
|
|
6181
|
+
return writeJson3(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
5703
6182
|
}
|
|
5704
6183
|
if (method === "GET") {
|
|
5705
6184
|
const params = query(req);
|
|
@@ -5708,7 +6187,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5708
6187
|
if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
|
|
5709
6188
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5710
6189
|
const allowances = await service.list({ providerId, accountId });
|
|
5711
|
-
return
|
|
6190
|
+
return writeJson3(res, 200, { allowances });
|
|
5712
6191
|
}
|
|
5713
6192
|
if (method === "POST" && rest[0] === "refresh") {
|
|
5714
6193
|
const body = await readJson2(req);
|
|
@@ -5723,7 +6202,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5723
6202
|
if (accountId && allowances.length === 0) {
|
|
5724
6203
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
5725
6204
|
}
|
|
5726
|
-
return
|
|
6205
|
+
return writeJson3(res, 200, { allowances });
|
|
5727
6206
|
}
|
|
5728
6207
|
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
5729
6208
|
}
|
|
@@ -5739,7 +6218,7 @@ function readBody(req) {
|
|
|
5739
6218
|
req.on("error", reject);
|
|
5740
6219
|
});
|
|
5741
6220
|
}
|
|
5742
|
-
async function
|
|
6221
|
+
async function readJsonBody4(req) {
|
|
5743
6222
|
const raw = await readBody(req);
|
|
5744
6223
|
if (!raw.trim()) return {};
|
|
5745
6224
|
try {
|
|
@@ -5749,12 +6228,12 @@ async function readJsonBody3(req) {
|
|
|
5749
6228
|
return {};
|
|
5750
6229
|
}
|
|
5751
6230
|
}
|
|
5752
|
-
function
|
|
6231
|
+
function writeJson4(res, status, body) {
|
|
5753
6232
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5754
6233
|
res.end(JSON.stringify(body));
|
|
5755
6234
|
}
|
|
5756
6235
|
function writeJsonError(res, status, message) {
|
|
5757
|
-
|
|
6236
|
+
writeJson4(res, status, { error: { type: "admin_api_error", message } });
|
|
5758
6237
|
}
|
|
5759
6238
|
function maskProviderApiKey(apiKey) {
|
|
5760
6239
|
if (!apiKey) return "";
|
|
@@ -5775,7 +6254,7 @@ function toKeyInfo(row) {
|
|
|
5775
6254
|
lastUsedAt: row.lastUsedAt,
|
|
5776
6255
|
revoked: row.revokedAt !== null,
|
|
5777
6256
|
kind: row.kind,
|
|
5778
|
-
allowedEndpoints: [...(0,
|
|
6257
|
+
allowedEndpoints: [...(0, import_outbound_api5.effectiveOutboundPermissions)(row.allowedEndpoints)],
|
|
5779
6258
|
legacyPermissions: row.allowedEndpoints === void 0,
|
|
5780
6259
|
loopbackOnly: row.loopbackOnly,
|
|
5781
6260
|
maxConcurrency: row.maxConcurrency,
|
|
@@ -5861,6 +6340,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5861
6340
|
return await handleServer(req, res, method, deps);
|
|
5862
6341
|
case "images":
|
|
5863
6342
|
return await handleImages(res, method, rest, deps);
|
|
6343
|
+
case "search":
|
|
6344
|
+
return await handleSearchAdmin(req, res, method, rest, deps);
|
|
5864
6345
|
case "accounts":
|
|
5865
6346
|
return await handleAccounts(req, res, method, rest, deps);
|
|
5866
6347
|
case "cli":
|
|
@@ -5894,7 +6375,7 @@ function requestQuery(req) {
|
|
|
5894
6375
|
return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
|
|
5895
6376
|
}
|
|
5896
6377
|
function writeResult(res, result) {
|
|
5897
|
-
|
|
6378
|
+
writeJson4(res, result.status, result.body);
|
|
5898
6379
|
}
|
|
5899
6380
|
async function handleUsage(req, res, method, rest, deps) {
|
|
5900
6381
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on usage`);
|
|
@@ -5903,13 +6384,13 @@ async function handleUsage(req, res, method, rest, deps) {
|
|
|
5903
6384
|
async function handleDashboardRoute(res, method, deps) {
|
|
5904
6385
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on dashboard`);
|
|
5905
6386
|
const result = await handleDashboard(deps);
|
|
5906
|
-
return
|
|
6387
|
+
return writeJson4(res, result.status, result.body);
|
|
5907
6388
|
}
|
|
5908
6389
|
async function handlePricing(req, res, method, rest, deps) {
|
|
5909
6390
|
if (rest.length === 0) {
|
|
5910
6391
|
if (method === "GET") return writeResult(res, await handlePricingList(deps));
|
|
5911
6392
|
if (method === "PUT") {
|
|
5912
|
-
return writeResult(res, await handlePricingUpsert(await
|
|
6393
|
+
return writeResult(res, await handlePricingUpsert(await readJsonBody4(req), deps));
|
|
5913
6394
|
}
|
|
5914
6395
|
if (method === "DELETE") {
|
|
5915
6396
|
return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
|
|
@@ -5920,7 +6401,7 @@ async function handlePricing(req, res, method, rest, deps) {
|
|
|
5920
6401
|
return writeResult(res, await handlePricingFetchLatest(deps));
|
|
5921
6402
|
}
|
|
5922
6403
|
if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
|
|
5923
|
-
return writeResult(res, await handlePricingResolveConflicts(await
|
|
6404
|
+
return writeResult(res, await handlePricingResolveConflicts(await readJsonBody4(req), deps));
|
|
5924
6405
|
}
|
|
5925
6406
|
return writeJsonError(res, 404, `unknown pricing route '${rest.join("/")}'`);
|
|
5926
6407
|
}
|
|
@@ -5934,15 +6415,15 @@ function migrationDeps(deps) {
|
|
|
5934
6415
|
}
|
|
5935
6416
|
async function handleMigrationExport(req, res, method, deps) {
|
|
5936
6417
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on export`);
|
|
5937
|
-
const body = await
|
|
6418
|
+
const body = await readJsonBody4(req);
|
|
5938
6419
|
const result = await handleExport(body, migrationDeps(deps));
|
|
5939
|
-
return
|
|
6420
|
+
return writeJson4(res, result.status, result.body);
|
|
5940
6421
|
}
|
|
5941
6422
|
async function handleMigrationImport(req, res, method, deps) {
|
|
5942
6423
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on import`);
|
|
5943
|
-
const body = await
|
|
6424
|
+
const body = await readJsonBody4(req);
|
|
5944
6425
|
const result = await handleImport(body, migrationDeps(deps));
|
|
5945
|
-
return
|
|
6426
|
+
return writeJson4(res, result.status, result.body);
|
|
5946
6427
|
}
|
|
5947
6428
|
async function handleProviders(req, res, method, rest, deps) {
|
|
5948
6429
|
const cfg = loadConfig(deps.configPath);
|
|
@@ -5973,13 +6454,13 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5973
6454
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
|
|
5974
6455
|
const row = cfg.providers.find((p) => p.id === rest[0]);
|
|
5975
6456
|
if (!row) return writeJsonError(res, 404, `provider '${rest[0]}' not found`);
|
|
5976
|
-
return
|
|
6457
|
+
return writeJson4(res, 200, { apiKey: row.apiKey ?? "" });
|
|
5977
6458
|
}
|
|
5978
6459
|
if (method === "GET") {
|
|
5979
|
-
return
|
|
6460
|
+
return writeJson4(res, 200, { providers: cfg.providers.map(toProviderView) });
|
|
5980
6461
|
}
|
|
5981
6462
|
if (method === "POST") {
|
|
5982
|
-
const body = await
|
|
6463
|
+
const body = await readJsonBody4(req);
|
|
5983
6464
|
const provider = parseProviderInput(body, void 0);
|
|
5984
6465
|
if (!provider) return writeJsonError(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
|
|
5985
6466
|
if (cfg.providers.some((p) => p.id === provider.id)) {
|
|
@@ -5987,25 +6468,25 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5987
6468
|
}
|
|
5988
6469
|
cfg.providers.push(provider);
|
|
5989
6470
|
persistProviders(cfg, deps);
|
|
5990
|
-
return
|
|
6471
|
+
return writeJson4(res, 201, { provider: toProviderView(provider) });
|
|
5991
6472
|
}
|
|
5992
6473
|
const id = rest[0];
|
|
5993
6474
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5994
6475
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
5995
6476
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5996
6477
|
if (method === "PUT") {
|
|
5997
|
-
const body = await
|
|
6478
|
+
const body = await readJsonBody4(req);
|
|
5998
6479
|
const existing = cfg.providers[idx];
|
|
5999
6480
|
const updated = parseProviderInput(body, existing);
|
|
6000
6481
|
if (!updated) return writeJsonError(res, 400, "invalid provider (apiFormat, baseUrl required)");
|
|
6001
6482
|
cfg.providers[idx] = updated;
|
|
6002
6483
|
persistProviders(cfg, deps);
|
|
6003
|
-
return
|
|
6484
|
+
return writeJson4(res, 200, { provider: toProviderView(updated) });
|
|
6004
6485
|
}
|
|
6005
6486
|
if (method === "DELETE") {
|
|
6006
6487
|
cfg.providers.splice(idx, 1);
|
|
6007
6488
|
persistProviders(cfg, deps);
|
|
6008
|
-
return
|
|
6489
|
+
return writeJson4(res, 200, { ok: true });
|
|
6009
6490
|
}
|
|
6010
6491
|
return writeJsonError(res, 405, `method ${method} not allowed on providers`);
|
|
6011
6492
|
}
|
|
@@ -6014,7 +6495,7 @@ function persistProviders(cfg, deps) {
|
|
|
6014
6495
|
deps.llmConfig.reload(cfg);
|
|
6015
6496
|
}
|
|
6016
6497
|
async function handleProviderReorder(req, res, cfg, deps) {
|
|
6017
|
-
const body = await
|
|
6498
|
+
const body = await readJsonBody4(req);
|
|
6018
6499
|
const rawOrder = body["order"];
|
|
6019
6500
|
if (!Array.isArray(rawOrder)) {
|
|
6020
6501
|
return writeJsonError(res, 400, "reorder requires { order: string[] }");
|
|
@@ -6038,14 +6519,14 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
6038
6519
|
}
|
|
6039
6520
|
cfg.providers = reordered;
|
|
6040
6521
|
persistProviders(cfg, deps);
|
|
6041
|
-
return
|
|
6522
|
+
return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
6042
6523
|
}
|
|
6043
6524
|
async function handleDiscoverModels(res, id, cfg) {
|
|
6044
6525
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6045
6526
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6046
6527
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6047
6528
|
if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
|
|
6048
|
-
return
|
|
6529
|
+
return writeJson4(res, 200, { models: [], unsupportedFormat: true });
|
|
6049
6530
|
}
|
|
6050
6531
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6051
6532
|
const base = row.baseUrl.replace(/\/+$/, "");
|
|
@@ -6062,32 +6543,32 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6062
6543
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6063
6544
|
} catch {
|
|
6064
6545
|
}
|
|
6065
|
-
return
|
|
6546
|
+
return writeJson4(res, 200, {
|
|
6066
6547
|
models: [],
|
|
6067
6548
|
error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
|
|
6068
6549
|
});
|
|
6069
6550
|
}
|
|
6070
6551
|
const data = await response.json();
|
|
6071
6552
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6072
|
-
return
|
|
6553
|
+
return writeJson4(res, 200, { models });
|
|
6073
6554
|
} catch (err5) {
|
|
6074
6555
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6075
|
-
return
|
|
6556
|
+
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6076
6557
|
}
|
|
6077
6558
|
}
|
|
6078
6559
|
async function handleTestModel(req, res, id, cfg) {
|
|
6079
6560
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6080
6561
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6081
6562
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6082
|
-
const body = await
|
|
6563
|
+
const body = await readJsonBody4(req);
|
|
6083
6564
|
const model = typeof body["model"] === "string" ? body["model"].trim() : "";
|
|
6084
6565
|
if (!model) return writeJsonError(res, 400, "test requires a { model } string");
|
|
6085
6566
|
if (row.apiFormat === "gemini") {
|
|
6086
|
-
return
|
|
6567
|
+
return writeJson4(res, 200, { ok: false, unsupportedFormat: true });
|
|
6087
6568
|
}
|
|
6088
6569
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
6089
6570
|
if (!resolvedKey) {
|
|
6090
|
-
return
|
|
6571
|
+
return writeJson4(res, 200, { ok: false, message: "no API key configured for this provider" });
|
|
6091
6572
|
}
|
|
6092
6573
|
let url = row.baseUrl.replace(/\/+$/, "");
|
|
6093
6574
|
const prompt = "Reply with the single word: OK.";
|
|
@@ -6126,9 +6607,9 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6126
6607
|
message = parsed?.error?.message || parsed?.message || message;
|
|
6127
6608
|
} catch {
|
|
6128
6609
|
}
|
|
6129
|
-
return
|
|
6610
|
+
return writeJson4(res, 200, { ok: false, status: response.status, latencyMs, message });
|
|
6130
6611
|
}
|
|
6131
|
-
return
|
|
6612
|
+
return writeJson4(res, 200, {
|
|
6132
6613
|
ok: true,
|
|
6133
6614
|
status: response.status,
|
|
6134
6615
|
latencyMs,
|
|
@@ -6136,7 +6617,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6136
6617
|
});
|
|
6137
6618
|
} catch (err5) {
|
|
6138
6619
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
6139
|
-
return
|
|
6620
|
+
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6140
6621
|
}
|
|
6141
6622
|
}
|
|
6142
6623
|
function extractSampleText(text, apiFormat) {
|
|
@@ -6177,7 +6658,7 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6177
6658
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6178
6659
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6179
6660
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6180
|
-
return
|
|
6661
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6181
6662
|
}
|
|
6182
6663
|
function parsePoolKeyInput(body, existing) {
|
|
6183
6664
|
const out = {};
|
|
@@ -6196,7 +6677,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6196
6677
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
6197
6678
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
6198
6679
|
if (idx < 0) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6199
|
-
const body = await
|
|
6680
|
+
const body = await readJsonBody4(req);
|
|
6200
6681
|
const parsed = parsePoolKeyInput(body);
|
|
6201
6682
|
if (!parsed.apiKey) return writeJsonError(res, 400, "add requires a non-empty apiKey");
|
|
6202
6683
|
const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -6208,7 +6689,7 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
6208
6689
|
row.apiKeys = [...row.apiKeys ?? [], entry];
|
|
6209
6690
|
persistProviders(cfg, deps);
|
|
6210
6691
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6211
|
-
return
|
|
6692
|
+
return writeJson4(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6212
6693
|
}
|
|
6213
6694
|
async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6214
6695
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6218,7 +6699,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6218
6699
|
const row = cfg.providers[idx];
|
|
6219
6700
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6220
6701
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6221
|
-
const body = await
|
|
6702
|
+
const body = await readJsonBody4(req);
|
|
6222
6703
|
const existing = row.apiKeys[keyIdx];
|
|
6223
6704
|
const parsed = parsePoolKeyInput(body, existing);
|
|
6224
6705
|
const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
|
|
@@ -6228,7 +6709,7 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6228
6709
|
row.apiKeys[keyIdx] = entry;
|
|
6229
6710
|
persistProviders(cfg, deps);
|
|
6230
6711
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6231
|
-
return
|
|
6712
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6232
6713
|
}
|
|
6233
6714
|
async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
6234
6715
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6242,7 +6723,7 @@ async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
|
6242
6723
|
if (row.apiKeys.length === 0) row.apiKeys = void 0;
|
|
6243
6724
|
persistProviders(cfg, deps);
|
|
6244
6725
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6245
|
-
return
|
|
6726
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6246
6727
|
}
|
|
6247
6728
|
async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
6248
6729
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
@@ -6252,11 +6733,11 @@ async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
6252
6733
|
const row = cfg.providers[idx];
|
|
6253
6734
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
6254
6735
|
if (keyIdx < 0) return writeJsonError(res, 404, `pool key '${keyId}' not found`);
|
|
6255
|
-
const body = await
|
|
6736
|
+
const body = await readJsonBody4(req);
|
|
6256
6737
|
row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
|
|
6257
6738
|
persistProviders(cfg, deps);
|
|
6258
6739
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6259
|
-
return
|
|
6740
|
+
return writeJson4(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
6260
6741
|
}
|
|
6261
6742
|
function parseApiKeysInput(raw, existing) {
|
|
6262
6743
|
if (!Array.isArray(raw)) return existing;
|
|
@@ -6442,13 +6923,13 @@ function handlePresets(res, method) {
|
|
|
6442
6923
|
website: p.website,
|
|
6443
6924
|
modelsEndpoint: p.modelsEndpoint
|
|
6444
6925
|
}));
|
|
6445
|
-
return
|
|
6926
|
+
return writeJson4(res, 200, { presets, excluded });
|
|
6446
6927
|
}
|
|
6447
6928
|
async function handleKeys(req, res, method, rest, deps) {
|
|
6448
6929
|
if (method === "GET" && rest.length === 0) {
|
|
6449
6930
|
const rows = await deps.keyDb.outboundApiKeysList();
|
|
6450
6931
|
const reader = deps.keySpendReader;
|
|
6451
|
-
if (!reader) return
|
|
6932
|
+
if (!reader) return writeJson4(res, 200, { keys: rows.map(toKeyInfo) });
|
|
6452
6933
|
const now = Date.now();
|
|
6453
6934
|
const keys = await Promise.all(
|
|
6454
6935
|
rows.map(async (row) => {
|
|
@@ -6460,13 +6941,13 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6460
6941
|
return info;
|
|
6461
6942
|
})
|
|
6462
6943
|
);
|
|
6463
|
-
return
|
|
6944
|
+
return writeJson4(res, 200, { keys });
|
|
6464
6945
|
}
|
|
6465
6946
|
if (method === "POST" && rest.length === 0) {
|
|
6466
|
-
const body = await
|
|
6947
|
+
const body = await readJsonBody4(req);
|
|
6467
6948
|
const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
|
|
6468
|
-
const created = await (0,
|
|
6469
|
-
return
|
|
6949
|
+
const created = await (0, import_outbound_api5.createNamedKey)(deps.keyDb, name);
|
|
6950
|
+
return writeJson4(res, 201, {
|
|
6470
6951
|
id: created.id,
|
|
6471
6952
|
name: created.name,
|
|
6472
6953
|
keyPrefix: created.keyPrefix,
|
|
@@ -6477,7 +6958,7 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6477
6958
|
}
|
|
6478
6959
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
6479
6960
|
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
6480
|
-
if (revealed !== null) return
|
|
6961
|
+
if (revealed !== null) return writeJson4(res, 200, { key: revealed });
|
|
6481
6962
|
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
6482
6963
|
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
6483
6964
|
return writeJsonError(
|
|
@@ -6492,32 +6973,32 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6492
6973
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6493
6974
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6494
6975
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
6495
|
-
return
|
|
6976
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6496
6977
|
}
|
|
6497
6978
|
if (method === "DELETE" && id && !action) {
|
|
6498
6979
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6499
6980
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6500
6981
|
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
6501
|
-
return
|
|
6982
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6502
6983
|
}
|
|
6503
6984
|
if (method === "POST" && id && action === "enabled") {
|
|
6504
|
-
const body = await
|
|
6985
|
+
const body = await readJsonBody4(req);
|
|
6505
6986
|
const enabled = body["enabled"] === true;
|
|
6506
6987
|
if (!enabled) {
|
|
6507
6988
|
const bound = await integrationKeyRequirement(deps, id);
|
|
6508
6989
|
if (bound) return writeJsonError(res, 409, bound);
|
|
6509
6990
|
}
|
|
6510
6991
|
const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
|
|
6511
|
-
return
|
|
6992
|
+
return writeJson4(res, ok ? 200 : 404, { ok, enabled });
|
|
6512
6993
|
}
|
|
6513
6994
|
if (method === "POST" && id && action === "permissions") {
|
|
6514
|
-
const body = await
|
|
6995
|
+
const body = await readJsonBody4(req);
|
|
6515
6996
|
if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
|
|
6516
6997
|
return writeJsonError(res, 400, "body must contain only permissions");
|
|
6517
6998
|
}
|
|
6518
6999
|
let permissions;
|
|
6519
7000
|
try {
|
|
6520
|
-
permissions = (0,
|
|
7001
|
+
permissions = (0, import_outbound_api5.validateOutboundPermissions)(body["permissions"]);
|
|
6521
7002
|
} catch {
|
|
6522
7003
|
return writeJsonError(
|
|
6523
7004
|
res,
|
|
@@ -6526,19 +7007,19 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6526
7007
|
);
|
|
6527
7008
|
}
|
|
6528
7009
|
const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6529
|
-
if (!before) return
|
|
6530
|
-
if (before.revokedAt !== null) return
|
|
7010
|
+
if (!before) return writeJson4(res, 404, { ok: false });
|
|
7011
|
+
if (before.revokedAt !== null) return writeJson4(res, 409, { ok: false });
|
|
6531
7012
|
const required = await integrationKeyRequirement(deps, id, permissions);
|
|
6532
7013
|
if (required) return writeJsonError(res, 409, required);
|
|
6533
7014
|
const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
|
|
6534
7015
|
if (!ok) {
|
|
6535
7016
|
const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
6536
|
-
return
|
|
7017
|
+
return writeJson4(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
|
|
6537
7018
|
}
|
|
6538
|
-
return
|
|
7019
|
+
return writeJson4(res, 200, { ok: true, allowedEndpoints: permissions });
|
|
6539
7020
|
}
|
|
6540
7021
|
if (method === "POST" && id && action === "max-concurrency") {
|
|
6541
|
-
const body = await
|
|
7022
|
+
const body = await readJsonBody4(req);
|
|
6542
7023
|
const raw = body["maxConcurrency"];
|
|
6543
7024
|
let value;
|
|
6544
7025
|
if (raw === null) {
|
|
@@ -6553,14 +7034,14 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
6553
7034
|
);
|
|
6554
7035
|
}
|
|
6555
7036
|
const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
|
|
6556
|
-
return
|
|
7037
|
+
return writeJson4(res, ok ? 200 : 404, { ok, maxConcurrency: value });
|
|
6557
7038
|
}
|
|
6558
7039
|
if (method === "POST" && id && action === "policy") {
|
|
6559
|
-
const body = await
|
|
7040
|
+
const body = await readJsonBody4(req);
|
|
6560
7041
|
const parsed = parseKeyPolicyBody(body);
|
|
6561
7042
|
if (!parsed.ok) return writeJsonError(res, 400, parsed.message);
|
|
6562
7043
|
const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
|
|
6563
|
-
return
|
|
7044
|
+
return writeJson4(res, ok ? 200 : 404, { ok });
|
|
6564
7045
|
}
|
|
6565
7046
|
return writeJsonError(res, 405, `method ${method} not allowed on keys`);
|
|
6566
7047
|
}
|
|
@@ -6651,7 +7132,8 @@ function outboundServerConfigInput(config) {
|
|
|
6651
7132
|
userMessageQueue: config.userMessageQueue,
|
|
6652
7133
|
concurrencyQueue: config.concurrencyQueue,
|
|
6653
7134
|
voucher: config.voucher,
|
|
6654
|
-
anthropic: config.anthropic
|
|
7135
|
+
anthropic: config.anthropic,
|
|
7136
|
+
search: config.search
|
|
6655
7137
|
};
|
|
6656
7138
|
}
|
|
6657
7139
|
function projectImagesConfigForAdmin(config) {
|
|
@@ -6687,8 +7169,8 @@ function sameConfigValue(left, right) {
|
|
|
6687
7169
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
6688
7170
|
}
|
|
6689
7171
|
function imageConfigurationAuditFields(previous, next) {
|
|
6690
|
-
const before = previous ??
|
|
6691
|
-
const after = next ??
|
|
7172
|
+
const before = previous ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
|
|
7173
|
+
const after = next ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
|
|
6692
7174
|
const fields = [];
|
|
6693
7175
|
if (before.enabled !== after.enabled) fields.push("enablement");
|
|
6694
7176
|
if (before.provider !== after.provider) fields.push("provider");
|
|
@@ -6714,15 +7196,21 @@ function currentImageGenerationId(deps) {
|
|
|
6714
7196
|
}
|
|
6715
7197
|
async function handleServer(req, res, method, deps) {
|
|
6716
7198
|
if (method === "GET") {
|
|
6717
|
-
const config = await (0,
|
|
7199
|
+
const config = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
|
|
6718
7200
|
let server = config;
|
|
6719
7201
|
if (config.proxy) server = { ...server, proxy: redactOutboundProxy(config.proxy) };
|
|
6720
7202
|
if (config.webhook) server = { ...server, webhook: redactWebhookConfig(config.webhook) };
|
|
6721
7203
|
if (config.billing) server = { ...server, billing: redactBillingConfig(config.billing) };
|
|
6722
|
-
|
|
7204
|
+
if (config.search) {
|
|
7205
|
+
server = {
|
|
7206
|
+
...server,
|
|
7207
|
+
search: redactSearchServerConfig(config.search)
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
7210
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(server) });
|
|
6723
7211
|
}
|
|
6724
7212
|
if (method === "PUT") {
|
|
6725
|
-
const patch = await
|
|
7213
|
+
const patch = await readJsonBody4(req);
|
|
6726
7214
|
const queueErrors = validateQueueSegments(patch);
|
|
6727
7215
|
if (queueErrors.length > 0) {
|
|
6728
7216
|
return writeJsonError(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
|
|
@@ -6751,7 +7239,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
6751
7239
|
if (billingErrors.length > 0) {
|
|
6752
7240
|
return writeJsonError(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
|
|
6753
7241
|
}
|
|
6754
|
-
const current = await (0,
|
|
7242
|
+
const current = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
|
|
6755
7243
|
let effectivePatch = patch;
|
|
6756
7244
|
if (patch.proxy) {
|
|
6757
7245
|
effectivePatch = { ...effectivePatch, proxy: preserveOutboundProxySecrets(patch.proxy, current.proxy) };
|
|
@@ -6772,7 +7260,21 @@ async function handleServer(req, res, method, deps) {
|
|
|
6772
7260
|
}
|
|
6773
7261
|
effectivePatch = { ...effectivePatch, images };
|
|
6774
7262
|
}
|
|
6775
|
-
|
|
7263
|
+
if (patch.search !== void 0) {
|
|
7264
|
+
const searchPatch = preserveSearchSecrets(
|
|
7265
|
+
patch.search,
|
|
7266
|
+
current.search ?? import_outbound_api5.DEFAULT_SEARCH_SERVER_CONFIG
|
|
7267
|
+
);
|
|
7268
|
+
const searchErrors = (0, import_outbound_api5.validateSearchServerConfig)(searchPatch);
|
|
7269
|
+
if (searchErrors.length > 0) {
|
|
7270
|
+
return writeJsonError(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
|
|
7271
|
+
}
|
|
7272
|
+
effectivePatch = {
|
|
7273
|
+
...effectivePatch,
|
|
7274
|
+
search: searchPatch
|
|
7275
|
+
};
|
|
7276
|
+
}
|
|
7277
|
+
const merged = (0, import_outbound_api5.mergeServerConfig)(current, effectivePatch);
|
|
6776
7278
|
const priorImageGenerationId = currentImageGenerationId(deps);
|
|
6777
7279
|
try {
|
|
6778
7280
|
await applyServerConfigTransaction(current, merged, {
|
|
@@ -6802,7 +7304,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
6802
7304
|
throw error;
|
|
6803
7305
|
}
|
|
6804
7306
|
},
|
|
6805
|
-
persist: (next) => (0,
|
|
7307
|
+
persist: (next) => (0, import_outbound_api5.saveServerConfig)(deps.settingsStore, next),
|
|
6806
7308
|
restorePersisted: (snapshot) => deps.settingsStore.restoreDocumentSnapshot(snapshot)
|
|
6807
7309
|
});
|
|
6808
7310
|
} catch (error) {
|
|
@@ -6824,7 +7326,11 @@ async function handleServer(req, res, method, deps) {
|
|
|
6824
7326
|
} catch {
|
|
6825
7327
|
}
|
|
6826
7328
|
}
|
|
6827
|
-
|
|
7329
|
+
const mergedForAdmin = merged.search ? {
|
|
7330
|
+
...merged,
|
|
7331
|
+
search: redactSearchServerConfig(merged.search)
|
|
7332
|
+
} : merged;
|
|
7333
|
+
return writeJson4(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
|
|
6828
7334
|
}
|
|
6829
7335
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
6830
7336
|
}
|
|
@@ -6841,7 +7347,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6841
7347
|
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
6842
7348
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
6843
7349
|
});
|
|
6844
|
-
return
|
|
7350
|
+
return writeJson4(res, 200, {
|
|
6845
7351
|
available: true,
|
|
6846
7352
|
records,
|
|
6847
7353
|
capacity: import_AccountRouteActivity.ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
@@ -6857,7 +7363,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6857
7363
|
providerId: query2.get("providerId") ?? void 0,
|
|
6858
7364
|
accountId: query2.get("accountId") ?? void 0
|
|
6859
7365
|
});
|
|
6860
|
-
return
|
|
7366
|
+
return writeJson4(res, 200, {
|
|
6861
7367
|
available: true,
|
|
6862
7368
|
entries,
|
|
6863
7369
|
collectedAt: Date.now()
|
|
@@ -6876,10 +7382,10 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6876
7382
|
const accounts = await deps.subscriptionAccounts.listAll();
|
|
6877
7383
|
const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
6878
7384
|
const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
|
|
6879
|
-
return
|
|
7385
|
+
return writeJson4(res, 200, { accounts, providerAccounts, externalCli });
|
|
6880
7386
|
}
|
|
6881
7387
|
if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
|
|
6882
|
-
const body = await
|
|
7388
|
+
const body = await readJsonBody4(req);
|
|
6883
7389
|
const parsed = validateAccountBatchBody(body);
|
|
6884
7390
|
if (!parsed) return writeJsonError(res, 400, "invalid account batch request");
|
|
6885
7391
|
const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
|
|
@@ -6895,15 +7401,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6895
7401
|
deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
|
|
6896
7402
|
}
|
|
6897
7403
|
}
|
|
6898
|
-
return
|
|
7404
|
+
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6899
7405
|
}
|
|
6900
7406
|
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6901
7407
|
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
6902
|
-
return
|
|
7408
|
+
return writeJson4(res, result.status, result.body);
|
|
6903
7409
|
}
|
|
6904
7410
|
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6905
7411
|
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
6906
|
-
return
|
|
7412
|
+
return writeJson4(res, result.status, result.body);
|
|
6907
7413
|
}
|
|
6908
7414
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
6909
7415
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6925,7 +7431,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6925
7431
|
resumeAt: entry.resumeAt
|
|
6926
7432
|
})) ?? [];
|
|
6927
7433
|
const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
|
|
6928
|
-
return
|
|
7434
|
+
return writeJson4(res, 200, { diagnostics });
|
|
6929
7435
|
}
|
|
6930
7436
|
if (method === "GET" && rest.length === 3 && rest[2] === "events") {
|
|
6931
7437
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6937,17 +7443,17 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6937
7443
|
}
|
|
6938
7444
|
const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
|
|
6939
7445
|
const diagnostics = (0, import_SubscriptionAccountHealth.getSharedAccountHealth)().getDiagnostics({ providerId, accountId });
|
|
6940
|
-
return
|
|
7446
|
+
return writeJson4(res, 200, { events: snapshot?.records ?? [], diagnostics });
|
|
6941
7447
|
}
|
|
6942
7448
|
if (method === "PATCH" && rest.length === 2) {
|
|
6943
7449
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
6944
7450
|
if (!providerId) return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6945
|
-
const body = await
|
|
7451
|
+
const body = await readJsonBody4(req);
|
|
6946
7452
|
const patch = validateAccountMetadataPatch(body);
|
|
6947
7453
|
if (!patch) return writeJsonError(res, 400, "invalid account metadata patch");
|
|
6948
7454
|
const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
|
|
6949
7455
|
if (!result.ok) return writeJsonError(res, 404, `account '${rest[1]}' not found`);
|
|
6950
|
-
return
|
|
7456
|
+
return writeJson4(res, 200, { ok: true });
|
|
6951
7457
|
}
|
|
6952
7458
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
6953
7459
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
@@ -6956,15 +7462,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6956
7462
|
}
|
|
6957
7463
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6958
7464
|
const result = providerId === "codex" ? handleCodexOAuthStart(deps) : handleOAuthStart(providerId, deps);
|
|
6959
|
-
return
|
|
7465
|
+
return writeJson4(res, result.status, result.body);
|
|
6960
7466
|
}
|
|
6961
7467
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
6962
|
-
const body2 = await
|
|
7468
|
+
const body2 = await readJsonBody4(req);
|
|
6963
7469
|
const result = await handleOAuthComplete(providerId, body2, deps);
|
|
6964
|
-
return
|
|
7470
|
+
return writeJson4(res, result.status, result.body);
|
|
6965
7471
|
}
|
|
6966
7472
|
if (method === "POST" && rest[1] === "accounts") {
|
|
6967
|
-
const body2 = await
|
|
7473
|
+
const body2 = await readJsonBody4(req);
|
|
6968
7474
|
const block = validateTokenBody(providerId, body2);
|
|
6969
7475
|
if (!block) {
|
|
6970
7476
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
@@ -6972,20 +7478,20 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6972
7478
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
6973
7479
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
6974
7480
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6975
|
-
return
|
|
7481
|
+
return writeJson4(res, 200, status2 ? { account: status2 } : { ok: true });
|
|
6976
7482
|
}
|
|
6977
7483
|
if (method === "POST" && rest[1] === "import-external") {
|
|
6978
7484
|
if (providerId !== "claude" && providerId !== "codex") {
|
|
6979
7485
|
return writeJsonError(res, 400, `provider '${providerId}' has no external CLI store`);
|
|
6980
7486
|
}
|
|
6981
|
-
const body2 = await
|
|
7487
|
+
const body2 = await readJsonBody4(req);
|
|
6982
7488
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
6983
7489
|
const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
|
|
6984
7490
|
if (!result.ok) {
|
|
6985
7491
|
return writeJsonError(res, 409, `no usable external ${providerId} CLI credential found`);
|
|
6986
7492
|
}
|
|
6987
7493
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
6988
|
-
return
|
|
7494
|
+
return writeJson4(res, 200, {
|
|
6989
7495
|
ok: true,
|
|
6990
7496
|
account: status2 ?? void 0,
|
|
6991
7497
|
nativeCredentialMode: result.nativeCredentialMode,
|
|
@@ -7000,7 +7506,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7000
7506
|
const writer2 = deps.subscriptionTokenWriter;
|
|
7001
7507
|
const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
|
|
7002
7508
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7003
|
-
return
|
|
7509
|
+
return writeJson4(res, 200, { ok, account: status2 ?? void 0 });
|
|
7004
7510
|
}
|
|
7005
7511
|
if (method === "POST" && rest.length === 3 && rest[2] === "test") {
|
|
7006
7512
|
const accountId = rest[1];
|
|
@@ -7010,7 +7516,7 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7010
7516
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7011
7517
|
}
|
|
7012
7518
|
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
7013
|
-
return
|
|
7519
|
+
return writeJson4(res, 200, {
|
|
7014
7520
|
ok: result.ok,
|
|
7015
7521
|
marked: result.marked,
|
|
7016
7522
|
tier: result.tier,
|
|
@@ -7019,15 +7525,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7019
7525
|
}
|
|
7020
7526
|
if (method === "POST" && rest[2] === "label") {
|
|
7021
7527
|
const accountId = rest[1];
|
|
7022
|
-
const body2 = await
|
|
7528
|
+
const body2 = await readJsonBody4(req);
|
|
7023
7529
|
const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
|
|
7024
7530
|
const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
|
|
7025
7531
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7026
|
-
return
|
|
7532
|
+
return writeJson4(res, 200, { ok: true });
|
|
7027
7533
|
}
|
|
7028
7534
|
if (method === "POST" && rest[2] === "priority") {
|
|
7029
7535
|
const accountId = rest[1];
|
|
7030
|
-
const body2 = await
|
|
7536
|
+
const body2 = await readJsonBody4(req);
|
|
7031
7537
|
const raw = body2["priority"];
|
|
7032
7538
|
const priority = typeof raw === "number" ? raw : Number(raw);
|
|
7033
7539
|
if (!Number.isFinite(priority)) {
|
|
@@ -7035,76 +7541,76 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7035
7541
|
}
|
|
7036
7542
|
const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
|
|
7037
7543
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7038
|
-
return
|
|
7544
|
+
return writeJson4(res, 200, { ok: true });
|
|
7039
7545
|
}
|
|
7040
7546
|
if (method === "POST" && rest[2] === "proxy") {
|
|
7041
7547
|
const accountId = rest[1];
|
|
7042
|
-
const body2 = await
|
|
7548
|
+
const body2 = await readJsonBody4(req);
|
|
7043
7549
|
const rawProxy = body2["proxy"];
|
|
7044
7550
|
let proxy;
|
|
7045
7551
|
if (rawProxy !== null && rawProxy !== void 0) {
|
|
7046
|
-
proxy = (0,
|
|
7552
|
+
proxy = (0, import_outbound_api5.normalizeProxyConfig)(rawProxy);
|
|
7047
7553
|
if (!proxy) return writeJsonError(res, 400, "invalid proxy config");
|
|
7048
7554
|
}
|
|
7049
7555
|
const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
|
|
7050
7556
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7051
|
-
return
|
|
7557
|
+
return writeJson4(res, 200, { ok: true });
|
|
7052
7558
|
}
|
|
7053
7559
|
if (method === "POST" && rest[2] === "supported-models") {
|
|
7054
7560
|
const accountId = rest[1];
|
|
7055
|
-
const body2 = await
|
|
7561
|
+
const body2 = await readJsonBody4(req);
|
|
7056
7562
|
const parsed = validateSupportedModelsBody(body2["supportedModels"]);
|
|
7057
7563
|
if (!parsed.ok) return writeJsonError(res, 400, "invalid supportedModels");
|
|
7058
7564
|
const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
|
|
7059
7565
|
if (!result.ok) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7060
|
-
return
|
|
7566
|
+
return writeJson4(res, 200, { ok: true });
|
|
7061
7567
|
}
|
|
7062
7568
|
if (method === "PUT" && rest[1] === "active") {
|
|
7063
|
-
const body2 = await
|
|
7569
|
+
const body2 = await readJsonBody4(req);
|
|
7064
7570
|
const id = typeof body2["id"] === "string" ? body2["id"] : "";
|
|
7065
7571
|
if (!id) return writeJsonError(res, 400, "active switch requires { id }");
|
|
7066
7572
|
const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
|
|
7067
7573
|
if (!result.ok) return writeJsonError(res, 404, `account '${id}' not found`);
|
|
7068
|
-
return
|
|
7574
|
+
return writeJson4(res, 200, { ok: true });
|
|
7069
7575
|
}
|
|
7070
7576
|
if (method === "DELETE" && rest.length === 2) {
|
|
7071
7577
|
const accountId = rest[1];
|
|
7072
7578
|
const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
|
|
7073
7579
|
if (!result.removed) return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
7074
7580
|
deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
|
|
7075
|
-
return
|
|
7581
|
+
return writeJson4(res, 200, { ok: true });
|
|
7076
7582
|
}
|
|
7077
7583
|
if (method === "DELETE" && rest.length === 1) {
|
|
7078
7584
|
await deps.subscriptionTokenWriter.clearProvider(providerId);
|
|
7079
7585
|
deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
|
|
7080
|
-
return
|
|
7586
|
+
return writeJson4(res, 200, { ok: true });
|
|
7081
7587
|
}
|
|
7082
7588
|
if (method === "DELETE") {
|
|
7083
7589
|
return writeJsonError(res, 405, "method DELETE not allowed on this accounts path");
|
|
7084
7590
|
}
|
|
7085
|
-
const body = await
|
|
7591
|
+
const body = await readJsonBody4(req);
|
|
7086
7592
|
const config = validateTokenBody(providerId, body);
|
|
7087
7593
|
if (!config) {
|
|
7088
7594
|
return writeJsonError(res, 400, `malformed token body for provider '${providerId}'`);
|
|
7089
7595
|
}
|
|
7090
7596
|
await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
|
|
7091
7597
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
7092
|
-
return
|
|
7598
|
+
return writeJson4(res, 200, status ? { account: status } : { ok: true });
|
|
7093
7599
|
}
|
|
7094
7600
|
return writeJsonError(res, 405, `method ${method} not allowed on accounts`);
|
|
7095
7601
|
}
|
|
7096
7602
|
async function handleCli(req, res, method, rest, deps) {
|
|
7097
7603
|
if (method === "GET" && rest.length === 0) {
|
|
7098
7604
|
const result = handleCliList(process.platform, deps.cliPathProbe);
|
|
7099
|
-
return
|
|
7605
|
+
return writeJson4(res, result.status, result.body);
|
|
7100
7606
|
}
|
|
7101
7607
|
if (method === "GET" && rest[0] === "sessions") {
|
|
7102
7608
|
const result = handleCliSessions();
|
|
7103
|
-
return
|
|
7609
|
+
return writeJson4(res, result.status, result.body);
|
|
7104
7610
|
}
|
|
7105
7611
|
if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
|
|
7106
7612
|
const result = handleCliStop(rest[1]);
|
|
7107
|
-
return
|
|
7613
|
+
return writeJson4(res, result.status, result.body);
|
|
7108
7614
|
}
|
|
7109
7615
|
if (method === "POST" && rest[1] === "install") {
|
|
7110
7616
|
const cli = rest[0];
|
|
@@ -7112,14 +7618,14 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7112
7618
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7113
7619
|
}
|
|
7114
7620
|
const result = await handleCliInstall(cli, deps.cliCommandRunner);
|
|
7115
|
-
return
|
|
7621
|
+
return writeJson4(res, result.status, result.body);
|
|
7116
7622
|
}
|
|
7117
7623
|
if (method === "POST" && rest[1] === "launch") {
|
|
7118
7624
|
const cli = rest[0];
|
|
7119
7625
|
if (!isLaunchCliId(cli)) {
|
|
7120
7626
|
return writeJsonError(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
7121
7627
|
}
|
|
7122
|
-
const body = await
|
|
7628
|
+
const body = await readJsonBody4(req);
|
|
7123
7629
|
const providers = loadConfig(deps.configPath).providers ?? [];
|
|
7124
7630
|
const result = await handleCliLaunch(cli, body, {
|
|
7125
7631
|
llmConfig: deps.llmConfig,
|
|
@@ -7128,7 +7634,7 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
7128
7634
|
opener: deps.cliTerminalOpener,
|
|
7129
7635
|
probe: deps.cliPathProbe
|
|
7130
7636
|
});
|
|
7131
|
-
return
|
|
7637
|
+
return writeJson4(res, result.status, result.body);
|
|
7132
7638
|
}
|
|
7133
7639
|
return writeJsonError(res, 405, `method ${method} not allowed on cli`);
|
|
7134
7640
|
}
|
|
@@ -7138,52 +7644,52 @@ async function handleIntegrations(req, res, method, rest, deps) {
|
|
|
7138
7644
|
const manager = factory();
|
|
7139
7645
|
try {
|
|
7140
7646
|
if (method === "GET" && rest.length === 0) {
|
|
7141
|
-
return
|
|
7647
|
+
return writeJson4(res, 200, {
|
|
7142
7648
|
integrations: await manager.listStatus(),
|
|
7143
7649
|
gateway: deps.outboundApiServer.getStatus()
|
|
7144
7650
|
});
|
|
7145
7651
|
}
|
|
7146
7652
|
if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
|
|
7147
7653
|
await manager.rotateGatewayKey();
|
|
7148
|
-
return
|
|
7654
|
+
return writeJson4(res, 200, { ok: true, integrations: await manager.listStatus() });
|
|
7149
7655
|
}
|
|
7150
7656
|
const client = rest[0];
|
|
7151
7657
|
if (!isIntegrationClient(client)) {
|
|
7152
7658
|
return writeJsonError(res, 400, `unknown integration client '${client ?? ""}'`);
|
|
7153
7659
|
}
|
|
7154
7660
|
if (method === "POST" && rest[1] === "key") {
|
|
7155
|
-
const body = await
|
|
7661
|
+
const body = await readJsonBody4(req);
|
|
7156
7662
|
if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
|
|
7157
7663
|
return writeJsonError(res, 400, "body must contain a non-empty keyId string");
|
|
7158
7664
|
}
|
|
7159
7665
|
const status = await manager.bindIntegrationKey(client, body.keyId.trim());
|
|
7160
|
-
return
|
|
7666
|
+
return writeJson4(res, 200, { integration: status });
|
|
7161
7667
|
}
|
|
7162
7668
|
if (method === "POST" && rest[1] === "plan") {
|
|
7163
|
-
const body = await
|
|
7669
|
+
const body = await readJsonBody4(req);
|
|
7164
7670
|
const configPath = body.configPath;
|
|
7165
7671
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7166
7672
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7167
7673
|
}
|
|
7168
7674
|
const plan = await manager.plan(client, configPath);
|
|
7169
|
-
return
|
|
7675
|
+
return writeJson4(res, 200, { plan });
|
|
7170
7676
|
}
|
|
7171
7677
|
if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
|
|
7172
|
-
const body = await
|
|
7678
|
+
const body = await readJsonBody4(req);
|
|
7173
7679
|
const configPath = body.configPath;
|
|
7174
7680
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
7175
7681
|
return writeJsonError(res, 400, "configPath must be a string");
|
|
7176
7682
|
}
|
|
7177
7683
|
const status = await manager.install(client, configPath);
|
|
7178
|
-
return
|
|
7684
|
+
return writeJson4(res, 200, { integration: status });
|
|
7179
7685
|
}
|
|
7180
7686
|
if (method === "POST" && rest[1] === "repair") {
|
|
7181
7687
|
const status = await manager.repair(client);
|
|
7182
|
-
return
|
|
7688
|
+
return writeJson4(res, 200, { integration: status });
|
|
7183
7689
|
}
|
|
7184
7690
|
if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
|
|
7185
7691
|
const status = await manager.remove(client);
|
|
7186
|
-
return
|
|
7692
|
+
return writeJson4(res, 200, { integration: status });
|
|
7187
7693
|
}
|
|
7188
7694
|
return writeJsonError(res, 405, `method ${method} not allowed on integrations`);
|
|
7189
7695
|
} catch (error) {
|
|
@@ -7326,8 +7832,8 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7326
7832
|
}
|
|
7327
7833
|
const reader = deps.imageRuntimeStatus;
|
|
7328
7834
|
if (!reader) return writeJsonError(res, 501, "Images runtime status is not available");
|
|
7329
|
-
const serverConfig = await (0,
|
|
7330
|
-
const images = serverConfig.images ??
|
|
7835
|
+
const serverConfig = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
|
|
7836
|
+
const images = serverConfig.images ?? import_outbound_api5.DEFAULT_IMAGES_SERVER_CONFIG;
|
|
7331
7837
|
const lifecycle = reader.status();
|
|
7332
7838
|
const capability = await reader.inspectCapability(IMAGE_ADMIN_STATUS_TENANT);
|
|
7333
7839
|
const resources = safeRuntimeResources(reader.resourceStatus());
|
|
@@ -7346,7 +7852,7 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7346
7852
|
httpLeases: safeStatusCount(generation.httpLeases),
|
|
7347
7853
|
hostedLeases: safeStatusCount(generation.hostedLeases)
|
|
7348
7854
|
}));
|
|
7349
|
-
return
|
|
7855
|
+
return writeJson4(res, 200, {
|
|
7350
7856
|
configured: {
|
|
7351
7857
|
enabled: images.enabled,
|
|
7352
7858
|
provider: images.provider,
|
|
@@ -7374,11 +7880,11 @@ async function handleImages(res, method, rest, deps) {
|
|
|
7374
7880
|
async function handleStatus(res, method, deps) {
|
|
7375
7881
|
if (method !== "GET") return writeJsonError(res, 405, `method ${method} not allowed on status`);
|
|
7376
7882
|
const status = deps.outboundApiServer.getStatus();
|
|
7377
|
-
const serverConfig = await (0,
|
|
7883
|
+
const serverConfig = await (0, import_outbound_api5.loadServerConfig)(deps.settingsStore);
|
|
7378
7884
|
const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
|
|
7379
|
-
const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => (0,
|
|
7885
|
+
const routes = (serverConfig.bindings ?? []).filter((binding) => binding.enabled && binding.endpoint === endpoint).map((binding) => (0, import_outbound_api5.gatewayBindingToEndpointConfig)(binding));
|
|
7380
7886
|
const useSubscription = routes.some((route) => route.useSubscription);
|
|
7381
|
-
if ((0,
|
|
7887
|
+
if ((0, import_outbound_api5.isKindMappedEndpoint)(endpoint)) {
|
|
7382
7888
|
const kinds = {};
|
|
7383
7889
|
for (const route of routes) {
|
|
7384
7890
|
for (const [kind, ref] of Object.entries(route.modelMap ?? {})) {
|
|
@@ -7412,14 +7918,14 @@ async function handleStatus(res, method, deps) {
|
|
|
7412
7918
|
})() : void 0;
|
|
7413
7919
|
if (status.running) {
|
|
7414
7920
|
const queueStatus = deps.outboundApiServer.getQueueStatus();
|
|
7415
|
-
return
|
|
7921
|
+
return writeJson4(res, 200, {
|
|
7416
7922
|
...status,
|
|
7417
7923
|
endpoints,
|
|
7418
7924
|
queueStatus,
|
|
7419
7925
|
...imageRuntime ? { imageRuntime } : {}
|
|
7420
7926
|
});
|
|
7421
7927
|
}
|
|
7422
|
-
return
|
|
7928
|
+
return writeJson4(res, 200, {
|
|
7423
7929
|
...status,
|
|
7424
7930
|
endpoints,
|
|
7425
7931
|
...imageRuntime ? { imageRuntime } : {}
|
|
@@ -7443,18 +7949,18 @@ function resolvePlaygroundPath(endpoint, body) {
|
|
|
7443
7949
|
}
|
|
7444
7950
|
async function handlePlayground(req, res, method, deps) {
|
|
7445
7951
|
if (method !== "POST") return writeJsonError(res, 405, `method ${method} not allowed on playground`);
|
|
7446
|
-
const body = await
|
|
7952
|
+
const body = await readJsonBody4(req);
|
|
7447
7953
|
const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
|
|
7448
7954
|
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
7449
7955
|
const payload = body["body"];
|
|
7450
7956
|
const status = deps.outboundApiServer.getStatus();
|
|
7451
7957
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7452
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
7958
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord3(payload) ? payload : {});
|
|
7453
7959
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7454
7960
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7455
7961
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7456
7962
|
}
|
|
7457
|
-
function
|
|
7963
|
+
function isRecord3(v) {
|
|
7458
7964
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7459
7965
|
}
|
|
7460
7966
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7590,7 +8096,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7590
8096
|
}
|
|
7591
8097
|
|
|
7592
8098
|
// src/admin/version.ts
|
|
7593
|
-
var DAEMON_VERSION = true ? "0.2.
|
|
8099
|
+
var DAEMON_VERSION = true ? "0.2.1" : "0.0.0-dev";
|
|
7594
8100
|
|
|
7595
8101
|
// src/admin/AdminServer.ts
|
|
7596
8102
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -8010,14 +8516,14 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
8010
8516
|
|
|
8011
8517
|
// src/image-generation/ImageDoctorService.ts
|
|
8012
8518
|
var import_image_generation = require("@omnicross/core/image-generation");
|
|
8013
|
-
var
|
|
8519
|
+
var import_outbound_api7 = require("@omnicross/core/outbound-api");
|
|
8014
8520
|
var import_subscriptions3 = require("@omnicross/subscriptions");
|
|
8015
8521
|
|
|
8016
8522
|
// src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
|
|
8017
8523
|
var import_node_crypto13 = require("crypto");
|
|
8018
8524
|
var import_node_fs14 = require("fs");
|
|
8019
8525
|
var import_node_path14 = require("path");
|
|
8020
|
-
var
|
|
8526
|
+
var import_outbound_api6 = require("@omnicross/core/outbound-api");
|
|
8021
8527
|
|
|
8022
8528
|
// src/image-generation/imageTenantHmac.ts
|
|
8023
8529
|
var import_node_crypto12 = require("crypto");
|
|
@@ -8090,7 +8596,7 @@ var ACCOUNT_DOMAIN = Buffer.from("omnicross:codex-image-evidence:account:v1\0",
|
|
|
8090
8596
|
var ACCOUNT_KEY = /^[a-f0-9]{64}$/u;
|
|
8091
8597
|
var SIZE = /^(?:auto|[1-9][0-9]{1,4}x[1-9][0-9]{1,4})$/u;
|
|
8092
8598
|
var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
8093
|
-
var PHYSICAL_RETENTION_TTL_MS =
|
|
8599
|
+
var PHYSICAL_RETENTION_TTL_MS = import_outbound_api6.IMAGE_SERVER_HARD_CEILINGS.evidenceTtlMs;
|
|
8094
8600
|
function exactKeys(value, allowed) {
|
|
8095
8601
|
const allow = new Set(allowed);
|
|
8096
8602
|
return Object.keys(value).every((key) => allow.has(key));
|
|
@@ -8464,7 +8970,7 @@ function createImageDoctorService(options) {
|
|
|
8464
8970
|
const activePaths = () => options.storageCatalog.active().resolver;
|
|
8465
8971
|
return Object.freeze({
|
|
8466
8972
|
inspectLocal: async (config) => {
|
|
8467
|
-
const configErrors = (0,
|
|
8973
|
+
const configErrors = (0, import_outbound_api7.validateImagesServerConfig)(config);
|
|
8468
8974
|
let verifiedAreas = 0;
|
|
8469
8975
|
try {
|
|
8470
8976
|
const paths = activePaths();
|
|
@@ -8506,7 +9012,7 @@ function createImageDoctorService(options) {
|
|
|
8506
9012
|
continue;
|
|
8507
9013
|
}
|
|
8508
9014
|
try {
|
|
8509
|
-
const permissions = (0,
|
|
9015
|
+
const permissions = (0, import_outbound_api7.validateOutboundPermissions)(row.allowedEndpoints);
|
|
8510
9016
|
if (permissions.includes("images")) imagesAuthorizedRows += 1;
|
|
8511
9017
|
} catch {
|
|
8512
9018
|
invalidRows += 1;
|
|
@@ -8810,7 +9316,7 @@ var ImageCleanupService = class {
|
|
|
8810
9316
|
// src/image-generation/ImageRuntimeGenerationFactory.ts
|
|
8811
9317
|
var import_node_crypto16 = require("crypto");
|
|
8812
9318
|
var import_image_generation5 = require("@omnicross/core/image-generation");
|
|
8813
|
-
var
|
|
9319
|
+
var import_outbound_api8 = require("@omnicross/core/outbound-api");
|
|
8814
9320
|
var import_subscriptions4 = require("@omnicross/subscriptions");
|
|
8815
9321
|
|
|
8816
9322
|
// src/image-generation/ImageApiRuntimeResolver.ts
|
|
@@ -9278,7 +9784,7 @@ function createImageRuntimeGeneration(options) {
|
|
|
9278
9784
|
throw new TypeError("image runtime generation id is invalid");
|
|
9279
9785
|
}
|
|
9280
9786
|
const config = snapshotConfig(options.config);
|
|
9281
|
-
const configErrors = (0,
|
|
9787
|
+
const configErrors = (0, import_outbound_api8.validateImagesServerConfig)(config);
|
|
9282
9788
|
if (configErrors.length > 0) {
|
|
9283
9789
|
throw new TypeError(`image runtime configuration is invalid: ${configErrors[0]}`);
|
|
9284
9790
|
}
|
|
@@ -12870,7 +13376,7 @@ function safeStringify(value) {
|
|
|
12870
13376
|
var import_node_crypto20 = require("crypto");
|
|
12871
13377
|
var import_node_fs20 = require("fs");
|
|
12872
13378
|
var import_node_path20 = require("path");
|
|
12873
|
-
var
|
|
13379
|
+
var import_outbound_api9 = require("@omnicross/core/outbound-api");
|
|
12874
13380
|
function atomicReplaceDocument(targetPath, contents) {
|
|
12875
13381
|
const tempPath = (0, import_node_path20.join)(
|
|
12876
13382
|
(0, import_node_path20.dirname)(targetPath),
|
|
@@ -12918,13 +13424,13 @@ var JsonApiServerSettingsStore = class {
|
|
|
12918
13424
|
box;
|
|
12919
13425
|
atomicReplace;
|
|
12920
13426
|
async get(key) {
|
|
12921
|
-
if (key !==
|
|
13427
|
+
if (key !== import_outbound_api9.OUTBOUND_API_SERVER_CONFIG_KEY) return void 0;
|
|
12922
13428
|
const file = this.readFile();
|
|
12923
13429
|
if (file.server === void 0) return void 0;
|
|
12924
13430
|
return this.decryptSecrets(file.server);
|
|
12925
13431
|
}
|
|
12926
13432
|
async set(key, value) {
|
|
12927
|
-
if (key !==
|
|
13433
|
+
if (key !== import_outbound_api9.OUTBOUND_API_SERVER_CONFIG_KEY) return;
|
|
12928
13434
|
const file = this.readFile();
|
|
12929
13435
|
file.server = this.encryptSecrets(value);
|
|
12930
13436
|
this.atomicReplace(
|
|
@@ -17269,7 +17775,7 @@ function buildDaemon(config, paths) {
|
|
|
17269
17775
|
);
|
|
17270
17776
|
(0, import_AccountAllowanceStore4.setSharedAccountAllowanceStore)(accountAllowanceStore);
|
|
17271
17777
|
(0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
|
|
17272
|
-
(0,
|
|
17778
|
+
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17273
17779
|
);
|
|
17274
17780
|
const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
|
|
17275
17781
|
const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
|
|
@@ -17290,7 +17796,7 @@ function buildDaemon(config, paths) {
|
|
|
17290
17796
|
logger
|
|
17291
17797
|
);
|
|
17292
17798
|
claudeAllowanceRefreshScheduler.configure(
|
|
17293
|
-
(0,
|
|
17799
|
+
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17294
17800
|
);
|
|
17295
17801
|
const subscriptionAccounts = new import_subscriptions6.SubscriptionAccountService(credentialStore);
|
|
17296
17802
|
(0, import_subscriptions6.setSubscriptionAccountService)(subscriptionAccounts);
|
|
@@ -17335,13 +17841,21 @@ function buildDaemon(config, paths) {
|
|
|
17335
17841
|
defaultUsageEventsPath(paths.configPath),
|
|
17336
17842
|
async (providerId, model) => await pricingEngine.getEntry(providerId, model) !== null
|
|
17337
17843
|
);
|
|
17338
|
-
const keySpendTracker = new
|
|
17844
|
+
const keySpendTracker = new import_outbound_api11.KeySpendTracker(usageEventStore);
|
|
17339
17845
|
const usageThroughput = (0, import_usage2.getSharedUsageThroughputTracker)();
|
|
17340
17846
|
const usageRecorder = new import_usage2.UsageRecorder(usageEventStore, pricingEngine, logger, {
|
|
17341
17847
|
onRecord: (apiKeyId, costUsd, at) => keySpendTracker.add(apiKeyId, costUsd, at),
|
|
17342
17848
|
onEvent: (row, at) => usageThroughput.record(row, at)
|
|
17343
17849
|
});
|
|
17344
|
-
const initialImagesConfig = (0,
|
|
17850
|
+
const initialImagesConfig = (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).images;
|
|
17851
|
+
const initialSearchConfig = (0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).search;
|
|
17852
|
+
for (const issue of (0, import_outbound_api10.validateSearchServerConfig)(
|
|
17853
|
+
decryptedConfig.server?.search
|
|
17854
|
+
)) {
|
|
17855
|
+
logger.warn("[search] ignoring invalid config: " + issue);
|
|
17856
|
+
}
|
|
17857
|
+
const searchRuntime = buildSearchRuntime(initialSearchConfig, { logger });
|
|
17858
|
+
const searchFrontendModes = initialSearchConfig.modes;
|
|
17345
17859
|
const imageObservability = new ImageObservability();
|
|
17346
17860
|
const imageRuntimeObservability = Object.freeze({
|
|
17347
17861
|
telemetrySink: imageObservability.telemetrySink,
|
|
@@ -17471,7 +17985,9 @@ function buildDaemon(config, paths) {
|
|
|
17471
17985
|
apiKeyPool,
|
|
17472
17986
|
usageRecorder,
|
|
17473
17987
|
openAIOperationRegistry,
|
|
17474
|
-
responsesHostedImageIngress
|
|
17988
|
+
responsesHostedImageIngress,
|
|
17989
|
+
searchRuntime,
|
|
17990
|
+
searchFrontendModes
|
|
17475
17991
|
});
|
|
17476
17992
|
if (providerProxy.getDeps().openAIOperationRegistry !== openAIOperationRegistry) {
|
|
17477
17993
|
throw new Error(
|
|
@@ -17505,7 +18021,7 @@ function buildDaemon(config, paths) {
|
|
|
17505
18021
|
credentialStore,
|
|
17506
18022
|
(0, import_SubscriptionAccountHealth4.getSharedAccountHealth)(),
|
|
17507
18023
|
logger,
|
|
17508
|
-
|
|
18024
|
+
import_outbound_api10.DEFAULT_ACCOUNT_PROBE
|
|
17509
18025
|
);
|
|
17510
18026
|
const getHealthReport = () => buildHealthReport({
|
|
17511
18027
|
version: DAEMON_VERSION,
|
|
@@ -17522,7 +18038,7 @@ function buildDaemon(config, paths) {
|
|
|
17522
18038
|
// `/health` body stays byte-identical (zero regression).
|
|
17523
18039
|
subscriptionAccountsHealthy: () => accountHealthProbeScheduler.enabled ? accountHealthProbeScheduler.probedAccountsHealthy() : void 0
|
|
17524
18040
|
});
|
|
17525
|
-
const outboundApiServer = (0,
|
|
18041
|
+
const outboundApiServer = (0, import_outbound_api10.getOutboundApiServer)({
|
|
17526
18042
|
db: keyDb,
|
|
17527
18043
|
// voucher-redemption #9: the key-authenticated `POST /redeem` endpoint redeems
|
|
17528
18044
|
// cards against the presenting key (gated on `voucher.enabled`).
|
|
@@ -17536,7 +18052,9 @@ function buildDaemon(config, paths) {
|
|
|
17536
18052
|
keySpendTracker,
|
|
17537
18053
|
// configurable-logging: route the server's OWN lifecycle + relay dispatch-error
|
|
17538
18054
|
// lines through the injected logger (honors level/format/file sink).
|
|
17539
|
-
logger
|
|
18055
|
+
logger,
|
|
18056
|
+
// plan 阶段5: the same instance the managed frontends hold.
|
|
18057
|
+
searchRuntime
|
|
17540
18058
|
});
|
|
17541
18059
|
const auditDir = defaultAuditDir(paths.configPath);
|
|
17542
18060
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
@@ -17558,6 +18076,10 @@ function buildDaemon(config, paths) {
|
|
|
17558
18076
|
}),
|
|
17559
18077
|
routeLeaseManager,
|
|
17560
18078
|
subscriptionAccounts,
|
|
18079
|
+
// search-settings-ui D3: the daemon's ONE search runtime + its
|
|
18080
|
+
// bootstrap-captured modes, for `GET /admin/api/search/diagnostics` and
|
|
18081
|
+
// `POST /admin/api/search/test` (501 when a light embedder omits it).
|
|
18082
|
+
searchStatus: { runtime: searchRuntime, modes: searchFrontendModes },
|
|
17561
18083
|
accountAllowanceService,
|
|
17562
18084
|
allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
|
|
17563
18085
|
accountProbeService: accountHealthProbeScheduler,
|
|
@@ -17607,7 +18129,7 @@ function buildDaemon(config, paths) {
|
|
|
17607
18129
|
cliCommandRunner: paths.cliCommandRunner,
|
|
17608
18130
|
integrationManagerFactory: () => {
|
|
17609
18131
|
const live = outboundApiServer.getStatus();
|
|
17610
|
-
const port = live.port || decryptedConfig.server?.port ||
|
|
18132
|
+
const port = live.port || decryptedConfig.server?.port || import_outbound_api10.DEFAULT_OUTBOUND_PORT;
|
|
17611
18133
|
return new IntegrationManager({
|
|
17612
18134
|
configPath: paths.configPath,
|
|
17613
18135
|
gatewayBaseUrl: live.loopbackUrl ?? `http://127.0.0.1:${port}`,
|
|
@@ -17694,6 +18216,8 @@ function buildDaemon(config, paths) {
|
|
|
17694
18216
|
keyDb,
|
|
17695
18217
|
settingsStore,
|
|
17696
18218
|
openAIOperationRegistry,
|
|
18219
|
+
searchRuntime,
|
|
18220
|
+
searchFrontendModes,
|
|
17697
18221
|
imageRuntimeManager,
|
|
17698
18222
|
imageObservability,
|
|
17699
18223
|
imageCleanupService,
|
|
@@ -17830,7 +18354,7 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
|
|
|
17830
18354
|
console.info(
|
|
17831
18355
|
" [\u26A0] live Images verification may consume subscription quota; one minimal low-quality PNG request will be sent"
|
|
17832
18356
|
);
|
|
17833
|
-
const result = await doctor.verifyLive(config.images ??
|
|
18357
|
+
const result = await doctor.verifyLive(config.images ?? import_outbound_api12.DEFAULT_IMAGES_SERVER_CONFIG, signal);
|
|
17834
18358
|
if (!result.ok) {
|
|
17835
18359
|
console.info(` [\u2717] live Images verification: ${result.code}`);
|
|
17836
18360
|
return false;
|
|
@@ -17840,6 +18364,116 @@ async function runImagesLiveDoctor(config, doctor, signal = new AbortController(
|
|
|
17840
18364
|
);
|
|
17841
18365
|
return true;
|
|
17842
18366
|
}
|
|
18367
|
+
function readSearchApiConfigFromEnv(env = process.env) {
|
|
18368
|
+
const configs = {};
|
|
18369
|
+
const read = (name) => {
|
|
18370
|
+
const value = env[`OMNICROSS_SEARCH_${name}`]?.trim();
|
|
18371
|
+
return value ? value : void 0;
|
|
18372
|
+
};
|
|
18373
|
+
const tavilyKey = read("TAVILY_API_KEY");
|
|
18374
|
+
if (tavilyKey) {
|
|
18375
|
+
configs.tavily = { apiKey: tavilyKey, ...optionalHost(read("TAVILY_API_HOST")) };
|
|
18376
|
+
}
|
|
18377
|
+
const jinaKey = read("JINA_API_KEY");
|
|
18378
|
+
const jinaHost = read("JINA_API_HOST");
|
|
18379
|
+
if (jinaKey || jinaHost) {
|
|
18380
|
+
configs.jina = { ...jinaKey ? { apiKey: jinaKey } : {}, ...optionalHost(jinaHost) };
|
|
18381
|
+
}
|
|
18382
|
+
const searxngHost = read("SEARXNG_API_HOST");
|
|
18383
|
+
if (searxngHost) {
|
|
18384
|
+
const username = read("SEARXNG_BASIC_AUTH_USERNAME");
|
|
18385
|
+
const password = read("SEARXNG_BASIC_AUTH_PASSWORD");
|
|
18386
|
+
configs.searxng = {
|
|
18387
|
+
apiHost: searxngHost,
|
|
18388
|
+
...username ? { basicAuthUsername: username } : {},
|
|
18389
|
+
...password ? { basicAuthPassword: password } : {}
|
|
18390
|
+
};
|
|
18391
|
+
}
|
|
18392
|
+
const zhipuKey = read("ZHIPU_API_KEY");
|
|
18393
|
+
if (zhipuKey) {
|
|
18394
|
+
configs.zhipu = { apiKey: zhipuKey, ...optionalHost(read("ZHIPU_API_HOST")) };
|
|
18395
|
+
}
|
|
18396
|
+
const zaiKey = read("Z_AI_API_KEY");
|
|
18397
|
+
if (zaiKey) {
|
|
18398
|
+
configs["z.ai"] = { apiKey: zaiKey, ...optionalHost(read("Z_AI_API_HOST")) };
|
|
18399
|
+
}
|
|
18400
|
+
return configs;
|
|
18401
|
+
}
|
|
18402
|
+
function optionalHost(apiHost) {
|
|
18403
|
+
return apiHost ? { apiHost } : {};
|
|
18404
|
+
}
|
|
18405
|
+
function resolveSearchApiConfigs(configured, env = process.env) {
|
|
18406
|
+
const fromEnv = readSearchApiConfigFromEnv(env);
|
|
18407
|
+
const resolved = {};
|
|
18408
|
+
const tavily = configured?.tavily ?? fromEnv.tavily;
|
|
18409
|
+
if (tavily) resolved.tavily = tavily;
|
|
18410
|
+
const jina = configured?.jina ?? fromEnv.jina;
|
|
18411
|
+
if (jina) resolved.jina = jina;
|
|
18412
|
+
const searxng = configured?.searxng ?? fromEnv.searxng;
|
|
18413
|
+
if (searxng) resolved.searxng = searxng;
|
|
18414
|
+
const zhipu = configured?.zhipu ?? fromEnv.zhipu;
|
|
18415
|
+
if (zhipu) resolved.zhipu = zhipu;
|
|
18416
|
+
const zai = configured?.["z.ai"] ?? fromEnv["z.ai"];
|
|
18417
|
+
if (zai) resolved["z.ai"] = zai;
|
|
18418
|
+
return resolved;
|
|
18419
|
+
}
|
|
18420
|
+
function formatCapabilities(capabilities) {
|
|
18421
|
+
return [
|
|
18422
|
+
`apiKey=${capabilities.requiresApiKey}`,
|
|
18423
|
+
`cancellation=${capabilities.supportsCancellation}`,
|
|
18424
|
+
`urlRead=${capabilities.supportsUrlRead}`,
|
|
18425
|
+
`region=${capabilities.supportsRegion}`,
|
|
18426
|
+
`language=${capabilities.supportsLanguage}`,
|
|
18427
|
+
`timeRange=${capabilities.supportsTimeRange}`,
|
|
18428
|
+
`maxResults=${capabilities.maxResults ?? "unbounded"}`
|
|
18429
|
+
].join(", ");
|
|
18430
|
+
}
|
|
18431
|
+
function formatDiagnostic(diagnostic) {
|
|
18432
|
+
const parts = [diagnostic.status];
|
|
18433
|
+
if (diagnostic.reason) parts.push(diagnostic.reason);
|
|
18434
|
+
if (diagnostic.error) {
|
|
18435
|
+
const { code, details } = diagnostic.error;
|
|
18436
|
+
parts.push(
|
|
18437
|
+
`code=${code}, transport=${details?.transport ?? "unknown"}, stage=${details?.stage ?? "unknown"}`
|
|
18438
|
+
);
|
|
18439
|
+
}
|
|
18440
|
+
return parts.join(" \u2014 ");
|
|
18441
|
+
}
|
|
18442
|
+
async function runSearchDoctor(live, env = process.env, source = {}) {
|
|
18443
|
+
const apiConfigs = resolveSearchApiConfigs(source.config?.providers, env);
|
|
18444
|
+
const egressPolicy = source.config && source.config.egress.allowedPrivateHosts.length > 0 ? { allowedPrivateHosts: [...source.config.egress.allowedPrivateHosts] } : void 0;
|
|
18445
|
+
const contributions = [
|
|
18446
|
+
...(0, import_http4.builtinHttpSearchContributions)(),
|
|
18447
|
+
...(0, import_api4.apiSearchContributions)(apiConfigs, { ...egressPolicy ? { egressPolicy } : {} })
|
|
18448
|
+
];
|
|
18449
|
+
const declarations = source.runtime?.listProviders() ?? contributions;
|
|
18450
|
+
console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
|
|
18451
|
+
const modes = source.config?.modes ?? import_search2.DEFAULT_SEARCH_FRONTEND_MODES;
|
|
18452
|
+
console.info(
|
|
18453
|
+
` [i] frontend modes: ${import_search2.SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
|
|
18454
|
+
);
|
|
18455
|
+
console.info(
|
|
18456
|
+
" [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
|
|
18457
|
+
);
|
|
18458
|
+
for (const row of buildSearchDoctorSnapshot(declarations, apiConfigs)) {
|
|
18459
|
+
const mark = row.status === "unconfigured" ? "\u2013" : "\u2713";
|
|
18460
|
+
const suffix = row.status ? ` \u2014 ${row.status}: ${row.reason ?? ""}` : "";
|
|
18461
|
+
console.info(
|
|
18462
|
+
` [${mark}] ${row.providerId}: source=${row.source}, kind=${row.kind}, ${formatCapabilities(row.capabilities)}${suffix}`
|
|
18463
|
+
);
|
|
18464
|
+
}
|
|
18465
|
+
if (!live) return 0;
|
|
18466
|
+
console.info(
|
|
18467
|
+
` [\u26A0] live search sends ONE fixed public query per provider ("${SEARCH_DOCTOR_QUERY}") to the engine itself`
|
|
18468
|
+
);
|
|
18469
|
+
let hardFailure = false;
|
|
18470
|
+
for (const diagnostic of await runSearchLiveChecks(contributions)) {
|
|
18471
|
+
const mark = diagnostic.status === "healthy" ? "\u2713" : diagnostic.status === "failed" ? "\u2717" : "\u26A0";
|
|
18472
|
+
if (diagnostic.status === "failed") hardFailure = true;
|
|
18473
|
+
console.info(` [${mark}] ${diagnostic.providerId}: ${formatDiagnostic(diagnostic)}`);
|
|
18474
|
+
}
|
|
18475
|
+
return hardFailure ? 1 : 0;
|
|
18476
|
+
}
|
|
17843
18477
|
async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
17844
18478
|
try {
|
|
17845
18479
|
const res = await fetchImpl(`${url.replace(/\/+$/, "")}/v1/messages/count_tokens`, {
|
|
@@ -17876,11 +18510,12 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
17876
18510
|
allowPositionals: true
|
|
17877
18511
|
});
|
|
17878
18512
|
const subject = positionals[0] ?? "claude";
|
|
17879
|
-
if (subject !== "claude" && subject !== "images") {
|
|
17880
|
-
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images')`);
|
|
18513
|
+
if (subject !== "claude" && subject !== "images" && subject !== "search") {
|
|
18514
|
+
throw new Error(`doctor: unknown subject '${subject}' (supported: 'claude', 'images', 'search')`);
|
|
17881
18515
|
}
|
|
17882
18516
|
const configPath = values.config;
|
|
17883
18517
|
if (!configPath) {
|
|
18518
|
+
if (subject === "search") return runSearchDoctor(values.live === true);
|
|
17884
18519
|
throw new Error("doctor: --config <path> is required (the same config `omnicross start` uses)");
|
|
17885
18520
|
}
|
|
17886
18521
|
const config = loadConfig(configPath);
|
|
@@ -17892,9 +18527,15 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
17892
18527
|
};
|
|
17893
18528
|
const daemon = await buildDaemon(config, paths);
|
|
17894
18529
|
try {
|
|
17895
|
-
const serverConfig = await (0,
|
|
18530
|
+
const serverConfig = await (0, import_outbound_api12.loadServerConfig)(daemon.settingsStore);
|
|
18531
|
+
if (subject === "search") {
|
|
18532
|
+
return await runSearchDoctor(values.live === true, process.env, {
|
|
18533
|
+
...serverConfig.search ? { config: serverConfig.search } : {},
|
|
18534
|
+
runtime: daemon.searchRuntime
|
|
18535
|
+
});
|
|
18536
|
+
}
|
|
17896
18537
|
const checks = subject === "images" ? buildImagesDoctorChecks(await daemon.imageDoctor.inspectLocal(
|
|
17897
|
-
serverConfig.images ??
|
|
18538
|
+
serverConfig.images ?? import_outbound_api12.DEFAULT_IMAGES_SERVER_CONFIG
|
|
17898
18539
|
)) : buildClaudeDoctorChecks(serverConfig);
|
|
17899
18540
|
let hardFailure = false;
|
|
17900
18541
|
console.info(subject === "images" ? "omnicross doctor images \u2014 local metadata only" : `omnicross doctor ${subject} \u2014 config: ${configPath}`);
|
|
@@ -18117,7 +18758,7 @@ function isClient(value) {
|
|
|
18117
18758
|
|
|
18118
18759
|
// src/commands/keys.ts
|
|
18119
18760
|
var import_node_util5 = require("util");
|
|
18120
|
-
var
|
|
18761
|
+
var import_outbound_api13 = require("@omnicross/core/outbound-api");
|
|
18121
18762
|
async function runKeys(argv) {
|
|
18122
18763
|
const { values, positionals } = (0, import_node_util5.parseArgs)({
|
|
18123
18764
|
args: argv,
|
|
@@ -18143,7 +18784,7 @@ async function runKeys(argv) {
|
|
|
18143
18784
|
}
|
|
18144
18785
|
async function keysAdd(db, name) {
|
|
18145
18786
|
if (!name) throw new Error("keys add: a <name> is required");
|
|
18146
|
-
const created = await (0,
|
|
18787
|
+
const created = await (0, import_outbound_api13.createNamedKey)(db, name);
|
|
18147
18788
|
console.info(`Created key '${created.name}' (id: ${created.id}).`);
|
|
18148
18789
|
console.info("");
|
|
18149
18790
|
console.info(` ${created.plaintextOnce}`);
|
|
@@ -18966,7 +19607,7 @@ function tokensSuffix(configPath) {
|
|
|
18966
19607
|
|
|
18967
19608
|
// src/commands/start.ts
|
|
18968
19609
|
var import_node_util10 = require("util");
|
|
18969
|
-
var
|
|
19610
|
+
var import_outbound_api14 = require("@omnicross/core/outbound-api");
|
|
18970
19611
|
var import_SubscriptionAccountHealth5 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
18971
19612
|
var import_AccountAllowanceScheduling6 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
18972
19613
|
|
|
@@ -19028,7 +19669,7 @@ async function runStart(argv) {
|
|
|
19028
19669
|
await daemon.llmConfig.ready();
|
|
19029
19670
|
await daemon.migrateUsageStore();
|
|
19030
19671
|
await daemon.providerProxy.start();
|
|
19031
|
-
const serverConfig = await (0,
|
|
19672
|
+
const serverConfig = await (0, import_outbound_api14.loadServerConfig)(daemon.settingsStore);
|
|
19032
19673
|
await daemon.imageCleanupService.runOnce();
|
|
19033
19674
|
daemon.imageCleanupService.start();
|
|
19034
19675
|
(0, import_SubscriptionAccountHealth5.getSharedAccountHealth)().configure({
|
|
@@ -19051,7 +19692,9 @@ async function runStart(argv) {
|
|
|
19051
19692
|
voucher: serverConfig.voucher,
|
|
19052
19693
|
// claude-api-protocol-fidelity (§10): count_tokens strategy/budget,
|
|
19053
19694
|
// /v1/models shape, synthetic-ping heartbeat (hot-applied inside applyConfig).
|
|
19054
|
-
anthropic: serverConfig.anthropic
|
|
19695
|
+
anthropic: serverConfig.anthropic,
|
|
19696
|
+
// plan 阶段5: the Codex frontend mode is read live per request from here.
|
|
19697
|
+
search: serverConfig.search
|
|
19055
19698
|
});
|
|
19056
19699
|
let dashboardUrl = null;
|
|
19057
19700
|
if (!values["no-dashboard"]) {
|
|
@@ -19166,6 +19809,16 @@ Usage:
|
|
|
19166
19809
|
Check local Images config, roots, stores, permissions,
|
|
19167
19810
|
account and cached evidence. --live warns, then consumes
|
|
19168
19811
|
at most one minimal subscription image request.
|
|
19812
|
+
omnicross doctor search [--live] List the builtin search providers and their declared
|
|
19813
|
+
capabilities (offline, no config needed). --live sends ONE
|
|
19814
|
+
fixed public query per provider and reports healthy /
|
|
19815
|
+
degraded / blocked / failed.
|
|
19816
|
+
Keyed API providers show as unconfigured unless enabled
|
|
19817
|
+
through the DIAGNOSTIC-ONLY environment variables
|
|
19818
|
+
OMNICROSS_SEARCH_{TAVILY,JINA,SEARXNG,ZHIPU,Z_AI}_API_KEY
|
|
19819
|
+
/ _API_HOST (plus SEARXNG_BASIC_AUTH_USERNAME/_PASSWORD).
|
|
19820
|
+
These are read only by this command and are not the
|
|
19821
|
+
configuration system.
|
|
19169
19822
|
`;
|
|
19170
19823
|
async function main() {
|
|
19171
19824
|
const [, , subcommand, ...rest] = process.argv;
|