@canonry/canonry 4.143.0 → 4.145.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{AuditHistoryPanel--kNrRAQm.js → AuditHistoryPanel-CV9g5mLR.js} +1 -1
- package/assets/assets/{BacklinksPage-CtW1w9lh.js → BacklinksPage-RTcYKA5I.js} +1 -1
- package/assets/assets/{ChartPrimitives-C-nMEqdu.js → ChartPrimitives-BMR0Dqnf.js} +1 -1
- package/assets/assets/{HistoryPage-CAPJDSbc.js → HistoryPage-DAN9QO6x.js} +1 -1
- package/assets/assets/ProjectPage-aFV2nddp.js +9 -0
- package/assets/assets/{RunRow-aDzTzGSK.js → RunRow-WDCIS9MJ.js} +1 -1
- package/assets/assets/{RunsPage-Dy9oWMBK.js → RunsPage-D3Zxd6oc.js} +1 -1
- package/assets/assets/SettingsPage-DcCFVeA7.js +1 -0
- package/assets/assets/{TrafficPage-B4ADqeKG.js → TrafficPage-D0tPBu5O.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CFcoL_PR.js → TrafficSourceDetailPage-CtyEE9eJ.js} +1 -1
- package/assets/assets/{arrow-left-DV9xcX1v.js → arrow-left-hBxXIGS1.js} +1 -1
- package/assets/assets/{extract-error-message-DXtgw8Q8.js → extract-error-message-DRu89RHb.js} +1 -1
- package/assets/assets/index-ANFzZ3nU.css +1 -0
- package/assets/assets/index-DSOfEiTH.js +213 -0
- package/assets/assets/{trash-2-DMV6uGU1.js → trash-2-DP0ukgbl.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-KUPTGRME.js → chunk-EFBTZ6ZT.js} +4653 -3599
- package/dist/{chunk-VN3CI6K6.js → chunk-GTBCGJNT.js} +7436 -2479
- package/dist/{chunk-3G5B3IMZ.js → chunk-MBLATP5L.js} +1303 -71
- package/dist/{chunk-TJ6SDLXL.js → chunk-XNAJXSZ2.js} +346 -97
- package/dist/cli.js +409 -149
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-V6JYDYGF.js → intelligence-service-DVMEVHAG.js} +2 -2
- package/dist/mcp.js +5 -5
- package/package.json +10 -10
- package/assets/assets/ProjectPage-79R3wJW6.js +0 -9
- package/assets/assets/SettingsPage-DTAs1BrZ.js +0 -1
- package/assets/assets/index-CYA3jd24.css +0 -1
- package/assets/assets/index-H2iFFrr1.js +0 -213
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-MBLATP5L.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
agentSessions,
|
|
28
28
|
agentToolEvents,
|
|
29
29
|
aiUserFetchEventsHourly,
|
|
30
|
+
anyUsersExist,
|
|
30
31
|
apiKeys,
|
|
31
32
|
apiRoutes,
|
|
32
33
|
auditLog,
|
|
@@ -97,6 +98,7 @@ import {
|
|
|
97
98
|
llmUsageEvents,
|
|
98
99
|
loadDuckdb,
|
|
99
100
|
markSessionFailed,
|
|
101
|
+
measurementRunCompleteness,
|
|
100
102
|
migrate,
|
|
101
103
|
nextRunFromCron,
|
|
102
104
|
notifications,
|
|
@@ -115,6 +117,7 @@ import {
|
|
|
115
117
|
researchRunQueries,
|
|
116
118
|
researchRuns,
|
|
117
119
|
resolveDestination,
|
|
120
|
+
resolveTrustProxy,
|
|
118
121
|
resolveVercelSyncDeadlineMs,
|
|
119
122
|
resolveWebhookTarget,
|
|
120
123
|
runs,
|
|
@@ -123,7 +126,7 @@ import {
|
|
|
123
126
|
siteAuditSnapshots,
|
|
124
127
|
toAlertView,
|
|
125
128
|
usageCounters
|
|
126
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-GTBCGJNT.js";
|
|
127
130
|
import {
|
|
128
131
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
129
132
|
AGENT_PROVIDER_IDS,
|
|
@@ -181,6 +184,7 @@ import {
|
|
|
181
184
|
embedClientConfigForRequest,
|
|
182
185
|
factorStatusFromScore,
|
|
183
186
|
filterCapturedCitedUrls,
|
|
187
|
+
forbidden,
|
|
184
188
|
formatIsoDateInTimeZone,
|
|
185
189
|
frameAncestorsHeaderValue,
|
|
186
190
|
hostMatchesAnyDomain,
|
|
@@ -193,11 +197,14 @@ import {
|
|
|
193
197
|
isVertexGroundingRedirect,
|
|
194
198
|
mapWithConcurrency,
|
|
195
199
|
normalizeIdTokens,
|
|
200
|
+
normalizeMeasurementExecutionQueryText,
|
|
196
201
|
normalizeServedModel,
|
|
197
202
|
normalizeUrlPath,
|
|
198
203
|
notFound,
|
|
204
|
+
parseMeasurementRunManifestV1,
|
|
199
205
|
parseOriginList,
|
|
200
206
|
providerError,
|
|
207
|
+
providerSupportsLocationContext,
|
|
201
208
|
registrableDomain,
|
|
202
209
|
resolveSnapshotRequestQueries,
|
|
203
210
|
serializeForInlineScript,
|
|
@@ -210,7 +217,7 @@ import {
|
|
|
210
217
|
validationError,
|
|
211
218
|
winnabilityClassLabel,
|
|
212
219
|
withRetry
|
|
213
|
-
} from "./chunk-
|
|
220
|
+
} from "./chunk-EFBTZ6ZT.js";
|
|
214
221
|
|
|
215
222
|
// src/telemetry.ts
|
|
216
223
|
import crypto from "crypto";
|
|
@@ -917,6 +924,8 @@ var geminiAdapter = {
|
|
|
917
924
|
name: "gemini",
|
|
918
925
|
displayName: "Gemini",
|
|
919
926
|
mode: "api",
|
|
927
|
+
// normalize.ts folds the location into the prompt the model receives.
|
|
928
|
+
supportsLocationContext: true,
|
|
920
929
|
keyUrl: "https://aistudio.google.com/apikey",
|
|
921
930
|
// Upstream model list: https://ai.google.dev/gemini-api/docs/models
|
|
922
931
|
modelRegistry: {
|
|
@@ -1265,6 +1274,8 @@ var openaiAdapter = {
|
|
|
1265
1274
|
name: "openai",
|
|
1266
1275
|
displayName: "OpenAI",
|
|
1267
1276
|
mode: "api",
|
|
1277
|
+
// normalize.ts sets `user_location` on the web_search tool.
|
|
1278
|
+
supportsLocationContext: true,
|
|
1268
1279
|
keyUrl: "https://platform.openai.com/api-keys",
|
|
1269
1280
|
// Upstream model list: https://platform.openai.com/docs/models
|
|
1270
1281
|
modelRegistry: {
|
|
@@ -1656,6 +1667,8 @@ var claudeAdapter = {
|
|
|
1656
1667
|
name: "claude",
|
|
1657
1668
|
displayName: "Claude",
|
|
1658
1669
|
mode: "api",
|
|
1670
|
+
// normalize.ts sets `user_location` on the web_search tool.
|
|
1671
|
+
supportsLocationContext: true,
|
|
1659
1672
|
keyUrl: "https://platform.claude.com/settings/keys",
|
|
1660
1673
|
// Upstream model list: https://platform.claude.com/docs/en/about-claude/models/overview
|
|
1661
1674
|
modelRegistry: {
|
|
@@ -1901,6 +1914,8 @@ var localAdapter = {
|
|
|
1901
1914
|
name: "local",
|
|
1902
1915
|
displayName: "Local",
|
|
1903
1916
|
mode: "api",
|
|
1917
|
+
// normalize.ts folds the location into the system message.
|
|
1918
|
+
supportsLocationContext: true,
|
|
1904
1919
|
modelRegistry: {
|
|
1905
1920
|
defaultModel: "llama3",
|
|
1906
1921
|
validationPattern: /./,
|
|
@@ -2444,6 +2459,9 @@ var cdpChatgptAdapter = {
|
|
|
2444
2459
|
name: "cdp:chatgpt",
|
|
2445
2460
|
displayName: "ChatGPT (Browser)",
|
|
2446
2461
|
mode: "browser",
|
|
2462
|
+
// The browser session's own geolocation decides; the configured location
|
|
2463
|
+
// never reaches the request, so a row from this adapter claims no place.
|
|
2464
|
+
supportsLocationContext: false,
|
|
2447
2465
|
modelRegistry: {
|
|
2448
2466
|
defaultModel: "chatgpt-web",
|
|
2449
2467
|
validationPattern: /./,
|
|
@@ -2798,6 +2816,8 @@ var perplexityAdapter = {
|
|
|
2798
2816
|
name: "perplexity",
|
|
2799
2817
|
displayName: "Perplexity",
|
|
2800
2818
|
mode: "api",
|
|
2819
|
+
// normalize.ts folds the location into the prompt the model receives.
|
|
2820
|
+
supportsLocationContext: true,
|
|
2801
2821
|
keyUrl: "https://www.perplexity.ai/settings/api",
|
|
2802
2822
|
// Upstream model list: https://docs.perplexity.ai/guides/model-cards
|
|
2803
2823
|
modelRegistry: {
|
|
@@ -3281,6 +3301,15 @@ var ProviderExecutionGate = class {
|
|
|
3281
3301
|
}
|
|
3282
3302
|
}
|
|
3283
3303
|
};
|
|
3304
|
+
var sharedGates = /* @__PURE__ */ new Map();
|
|
3305
|
+
function getSharedProviderExecutionGate(providerName, maxConcurrency, maxPerMinute) {
|
|
3306
|
+
const key = providerName.trim().toLocaleLowerCase("en");
|
|
3307
|
+
const existing = sharedGates.get(key);
|
|
3308
|
+
if (existing) return existing;
|
|
3309
|
+
const gate = new ProviderExecutionGate(maxConcurrency, maxPerMinute);
|
|
3310
|
+
sharedGates.set(key, gate);
|
|
3311
|
+
return gate;
|
|
3312
|
+
}
|
|
3284
3313
|
|
|
3285
3314
|
// src/usage-quota.ts
|
|
3286
3315
|
import crypto3 from "crypto";
|
|
@@ -3421,6 +3450,35 @@ async function runWithConcurrency(items, limit, worker) {
|
|
|
3421
3450
|
await Promise.all(Array.from({ length: cap }, next));
|
|
3422
3451
|
}
|
|
3423
3452
|
var PROVIDER_FANOUT_DEFAULT = 8;
|
|
3453
|
+
function resolvePlanExecution(run, projectQueries) {
|
|
3454
|
+
if (!run.measurementPlanVersionId || !run.measurementManifest) return null;
|
|
3455
|
+
const manifest = parseMeasurementRunManifestV1(run.measurementManifest);
|
|
3456
|
+
const queryIdByText = /* @__PURE__ */ new Map();
|
|
3457
|
+
for (const row of projectQueries) {
|
|
3458
|
+
const key = normalizeMeasurementExecutionQueryText(row.query);
|
|
3459
|
+
if (!queryIdByText.has(key)) queryIdByText.set(key, row.id);
|
|
3460
|
+
}
|
|
3461
|
+
const unitsByProvider = /* @__PURE__ */ new Map();
|
|
3462
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
3463
|
+
for (const slot of manifest.expectedSlots) {
|
|
3464
|
+
nodes.add(slot.executionId);
|
|
3465
|
+
const units = unitsByProvider.get(slot.provider) ?? [];
|
|
3466
|
+
units.push({
|
|
3467
|
+
executionId: slot.executionId,
|
|
3468
|
+
queryText: slot.queryText,
|
|
3469
|
+
context: slot.context,
|
|
3470
|
+
queryId: queryIdByText.get(normalizeMeasurementExecutionQueryText(slot.queryText)) ?? null,
|
|
3471
|
+
requestedModel: slot.requestedModel
|
|
3472
|
+
});
|
|
3473
|
+
unitsByProvider.set(slot.provider, units);
|
|
3474
|
+
}
|
|
3475
|
+
return {
|
|
3476
|
+
manifest,
|
|
3477
|
+
unitsByProvider,
|
|
3478
|
+
nodeCount: nodes.size,
|
|
3479
|
+
maxUnitsPerProvider: Math.max(0, ...[...unitsByProvider.values()].map((units) => units.length))
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3424
3482
|
function resolveProviderFanout() {
|
|
3425
3483
|
const raw = process.env.CANONRY_PROVIDER_FANOUT;
|
|
3426
3484
|
if (!raw) return PROVIDER_FANOUT_DEFAULT;
|
|
@@ -3504,6 +3562,7 @@ var JobRunner = class {
|
|
|
3504
3562
|
let runLocation;
|
|
3505
3563
|
let activeProviders = [];
|
|
3506
3564
|
let projectQueries = [];
|
|
3565
|
+
let planExecution = null;
|
|
3507
3566
|
let runTrigger;
|
|
3508
3567
|
let canonicalDomain;
|
|
3509
3568
|
const providerDispatchCounts = /* @__PURE__ */ new Map();
|
|
@@ -3545,17 +3604,24 @@ var JobRunner = class {
|
|
|
3545
3604
|
runLocation = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
3546
3605
|
}
|
|
3547
3606
|
}
|
|
3548
|
-
const
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3607
|
+
const scopedQueryNames = existingRun.queries;
|
|
3608
|
+
projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and2(eq2(queries.projectId, projectId), inArray(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq2(queries.projectId, projectId)).all();
|
|
3609
|
+
planExecution = resolvePlanExecution(existingRun, projectQueries);
|
|
3610
|
+
if (planExecution) {
|
|
3611
|
+
const plan = planExecution;
|
|
3612
|
+
const manifestProviders = [...plan.unitsByProvider.keys()];
|
|
3613
|
+
activeProviders = manifestProviders.map((name) => this.registry.get(name)).filter((entry) => entry !== void 0);
|
|
3614
|
+
} else {
|
|
3615
|
+
const projectProviders = providerOverride ?? project.providers;
|
|
3616
|
+
activeProviders = this.registry.getForProject(projectProviders).map((entry) => {
|
|
3617
|
+
const model = project.providerModels[entry.adapter.name];
|
|
3618
|
+
return model === void 0 ? entry : { ...entry, config: { ...entry.config, model } };
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3553
3621
|
if (activeProviders.length === 0) {
|
|
3554
3622
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
3555
3623
|
}
|
|
3556
3624
|
log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
3557
|
-
const scopedQueryNames = existingRun.queries;
|
|
3558
|
-
projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and2(eq2(queries.projectId, projectId), inArray(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq2(queries.projectId, projectId)).all();
|
|
3559
3625
|
const projectCompetitors = this.db.select().from(competitors).where(eq2(competitors.projectId, projectId)).all();
|
|
3560
3626
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
3561
3627
|
const allDomains = effectiveDomains({
|
|
@@ -3569,12 +3635,12 @@ var JobRunner = class {
|
|
|
3569
3635
|
const executionContext = {
|
|
3570
3636
|
providerCount: activeProviders.length,
|
|
3571
3637
|
providers: activeProviders.map((provider) => provider.adapter.name),
|
|
3572
|
-
queryCount: projectQueries.length,
|
|
3638
|
+
queryCount: planExecution?.nodeCount ?? projectQueries.length,
|
|
3573
3639
|
...runLocation ? { location: runLocation.label } : {},
|
|
3574
3640
|
...runTrigger ? { trigger: runTrigger } : {},
|
|
3575
3641
|
...canonicalDomain ? { canonicalDomain } : {}
|
|
3576
3642
|
};
|
|
3577
|
-
const queriesPerProvider = projectQueries.length;
|
|
3643
|
+
const queriesPerProvider = planExecution?.maxUnitsPerProvider ?? projectQueries.length;
|
|
3578
3644
|
const todayPeriod = getCurrentUsageDay();
|
|
3579
3645
|
for (const p of activeProviders) {
|
|
3580
3646
|
const providerScope = `${projectId}:${p.adapter.name}`;
|
|
@@ -3591,7 +3657,8 @@ var JobRunner = class {
|
|
|
3591
3657
|
for (const provider of activeProviders) {
|
|
3592
3658
|
executionGates.set(
|
|
3593
3659
|
provider.adapter.name,
|
|
3594
|
-
|
|
3660
|
+
getSharedProviderExecutionGate(
|
|
3661
|
+
provider.adapter.name,
|
|
3595
3662
|
provider.config.quotaPolicy.maxConcurrency,
|
|
3596
3663
|
provider.config.quotaPolicy.maxRequestsPerMinute
|
|
3597
3664
|
)
|
|
@@ -3756,21 +3823,194 @@ var JobRunner = class {
|
|
|
3756
3823
|
}
|
|
3757
3824
|
}
|
|
3758
3825
|
};
|
|
3826
|
+
const processNodeForProvider = async (registeredProvider, unit) => {
|
|
3827
|
+
const { adapter, config: providerConfig } = registeredProvider;
|
|
3828
|
+
const providerName = adapter.name;
|
|
3829
|
+
const gate = executionGates.get(providerName);
|
|
3830
|
+
if (!gate) {
|
|
3831
|
+
throw new Error(`Missing execution gate for provider ${providerName}`);
|
|
3832
|
+
}
|
|
3833
|
+
const config = unit.requestedModel ? { ...providerConfig, model: unit.requestedModel } : providerConfig;
|
|
3834
|
+
const requestedContext = unit.context;
|
|
3835
|
+
const supportedContext = requestedContext && providerSupportsLocationContext(adapter) ? { status: "applied", resolved: requestedContext } : null;
|
|
3836
|
+
try {
|
|
3837
|
+
await gate.run(async () => {
|
|
3838
|
+
this.throwIfRunCancelled(runId);
|
|
3839
|
+
providerDispatchCounts.set(providerName, (providerDispatchCounts.get(providerName) ?? 0) + 1);
|
|
3840
|
+
const raw = await adapter.executeTrackedQuery(
|
|
3841
|
+
{
|
|
3842
|
+
query: unit.queryText,
|
|
3843
|
+
canonicalDomains: allDomains,
|
|
3844
|
+
competitorDomains,
|
|
3845
|
+
location: requestedContext ?? void 0
|
|
3846
|
+
},
|
|
3847
|
+
config
|
|
3848
|
+
);
|
|
3849
|
+
this.throwIfRunCancelled(runId);
|
|
3850
|
+
const providerResult = adapter.normalizeResult(raw);
|
|
3851
|
+
const rawGroundingSources = providerResult.groundingSources;
|
|
3852
|
+
const normalized = {
|
|
3853
|
+
...providerResult,
|
|
3854
|
+
groundingSources: Array.isArray(rawGroundingSources) ? rawGroundingSources : []
|
|
3855
|
+
};
|
|
3856
|
+
let citedUrlCapture;
|
|
3857
|
+
try {
|
|
3858
|
+
citedUrlCapture = await captureCitedUrls(providerName, rawGroundingSources);
|
|
3859
|
+
} catch (err) {
|
|
3860
|
+
citedUrlCapture = {
|
|
3861
|
+
citedUrls: [],
|
|
3862
|
+
captureStatus: "failed",
|
|
3863
|
+
sourceCount: normalized.groundingSources.length,
|
|
3864
|
+
resolvedCount: 0,
|
|
3865
|
+
captureVersion: CITED_URL_CAPTURE_VERSION
|
|
3866
|
+
};
|
|
3867
|
+
log.warn("query.cited-url-capture-failed", {
|
|
3868
|
+
runId,
|
|
3869
|
+
provider: providerName,
|
|
3870
|
+
query: unit.queryText,
|
|
3871
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
this.throwIfRunCancelled(runId);
|
|
3875
|
+
const citationState = determineCitationState(normalized, allDomains);
|
|
3876
|
+
const answerMentioned = determineAnswerMentioned(
|
|
3877
|
+
normalized.answerText,
|
|
3878
|
+
allBrandNames,
|
|
3879
|
+
allDomains
|
|
3880
|
+
);
|
|
3881
|
+
const overlap = computeCompetitorOverlap(normalized, competitorDomains);
|
|
3882
|
+
const extractedCompetitors = extractRecommendedCompetitors(
|
|
3883
|
+
normalized.answerText,
|
|
3884
|
+
allDomains,
|
|
3885
|
+
normalized.citedDomains,
|
|
3886
|
+
competitorDomains,
|
|
3887
|
+
allBrandNames
|
|
3888
|
+
);
|
|
3889
|
+
const snapshotId = crypto4.randomUUID();
|
|
3890
|
+
let screenshotRelPath = null;
|
|
3891
|
+
if (raw.screenshotPath && fs2.existsSync(raw.screenshotPath)) {
|
|
3892
|
+
const screenshotDir = path3.join(os3.homedir(), ".canonry", "screenshots", runId);
|
|
3893
|
+
if (!fs2.existsSync(screenshotDir)) fs2.mkdirSync(screenshotDir, { recursive: true });
|
|
3894
|
+
const destPath = path3.join(screenshotDir, `${snapshotId}.png`);
|
|
3895
|
+
fs2.renameSync(raw.screenshotPath, destPath);
|
|
3896
|
+
screenshotRelPath = `${runId}/${snapshotId}.png`;
|
|
3897
|
+
}
|
|
3898
|
+
this.db.insert(querySnapshots).values({
|
|
3899
|
+
id: snapshotId,
|
|
3900
|
+
runId,
|
|
3901
|
+
queryId: unit.queryId,
|
|
3902
|
+
queryText: unit.queryText,
|
|
3903
|
+
provider: providerName,
|
|
3904
|
+
// `model` is what was REQUESTED and `served_model` is what
|
|
3905
|
+
// answered. The manifest froze the request, so it is the
|
|
3906
|
+
// authority here: an adapter that reports its own default rather
|
|
3907
|
+
// than what it was handed would otherwise overwrite the identity
|
|
3908
|
+
// the revision recorded, and nothing would show that it moved.
|
|
3909
|
+
model: unit.requestedModel ?? raw.model,
|
|
3910
|
+
servedModel: raw.servedModel ?? null,
|
|
3911
|
+
citationState,
|
|
3912
|
+
answerMentioned,
|
|
3913
|
+
answerText: normalized.answerText,
|
|
3914
|
+
citedDomains: normalized.citedDomains,
|
|
3915
|
+
citedUrls: citedUrlCapture.citedUrls,
|
|
3916
|
+
captureStatus: citedUrlCapture.captureStatus,
|
|
3917
|
+
sourceCount: citedUrlCapture.sourceCount,
|
|
3918
|
+
resolvedCount: citedUrlCapture.resolvedCount,
|
|
3919
|
+
captureVersion: citedUrlCapture.captureVersion,
|
|
3920
|
+
retrievalStatus: normalized.retrievalStatus,
|
|
3921
|
+
retrievalContract: raw.retrievalContract,
|
|
3922
|
+
competitorOverlap: overlap,
|
|
3923
|
+
recommendedCompetitors: extractedCompetitors,
|
|
3924
|
+
// Only claim the geography the provider actually honoured. A
|
|
3925
|
+
// requested-but-unsupported context stores `location: null` —
|
|
3926
|
+
// "no claim" — rather than the label we asked for, mirroring
|
|
3927
|
+
// `supportedContext` itself: this field is never non-null when
|
|
3928
|
+
// that one is null.
|
|
3929
|
+
location: supportedContext ? requestedContext?.label ?? null : null,
|
|
3930
|
+
measurementExecutionId: unit.executionId,
|
|
3931
|
+
requestedContext,
|
|
3932
|
+
supportedContext,
|
|
3933
|
+
screenshotPath: screenshotRelPath,
|
|
3934
|
+
rawResponse: JSON.stringify({
|
|
3935
|
+
model: raw.model,
|
|
3936
|
+
servedModel: raw.servedModel ?? null,
|
|
3937
|
+
groundingSources: normalized.groundingSources,
|
|
3938
|
+
searchQueries: normalized.searchQueries,
|
|
3939
|
+
apiResponse: raw.rawResponse
|
|
3940
|
+
}),
|
|
3941
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3942
|
+
}).run();
|
|
3943
|
+
totalSnapshotsInserted++;
|
|
3944
|
+
log.info("query.citation", {
|
|
3945
|
+
runId,
|
|
3946
|
+
provider: providerName,
|
|
3947
|
+
query: unit.queryText,
|
|
3948
|
+
executionId: unit.executionId,
|
|
3949
|
+
location: requestedContext?.label ?? null,
|
|
3950
|
+
citationState,
|
|
3951
|
+
answerMentioned
|
|
3952
|
+
});
|
|
3953
|
+
});
|
|
3954
|
+
} catch (err) {
|
|
3955
|
+
if (err instanceof RunCancelledError) {
|
|
3956
|
+
throw err;
|
|
3957
|
+
}
|
|
3958
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3959
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
3960
|
+
log.error("query.failed", { runId, provider: providerName, query: unit.queryText, executionId: unit.executionId, error: msg, stack });
|
|
3961
|
+
if (!providerErrors.has(providerName)) {
|
|
3962
|
+
providerErrors.set(providerName, msg);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3759
3966
|
providerCallStart = Date.now();
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3967
|
+
if (planExecution) {
|
|
3968
|
+
const plan = planExecution;
|
|
3969
|
+
const unitsFor = (provider) => plan.unitsByProvider.get(provider.adapter.name.trim().toLocaleLowerCase("en")) ?? [];
|
|
3970
|
+
log.info("run.plan-dispatch", {
|
|
3971
|
+
runId,
|
|
3972
|
+
expectedSlots: plan.manifest.expectedSlots.length,
|
|
3973
|
+
executionNodes: plan.nodeCount,
|
|
3974
|
+
providers: [...plan.unitsByProvider.keys()]
|
|
3975
|
+
});
|
|
3976
|
+
await runWithConcurrency(apiProviders, resolveProviderFanout(), async (registeredProvider) => {
|
|
3977
|
+
await Promise.all(unitsFor(registeredProvider).map(async (unit) => {
|
|
3978
|
+
await processNodeForProvider(registeredProvider, unit);
|
|
3979
|
+
}));
|
|
3980
|
+
});
|
|
3981
|
+
for (const registeredProvider of browserProviders) {
|
|
3982
|
+
for (const unit of unitsFor(registeredProvider)) {
|
|
3983
|
+
await processNodeForProvider(registeredProvider, unit);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
} else {
|
|
3987
|
+
await runWithConcurrency(apiProviders, resolveProviderFanout(), async (registeredProvider) => {
|
|
3988
|
+
await Promise.all(projectQueries.map(async (q) => {
|
|
3989
|
+
await processQueryForProvider(registeredProvider, q);
|
|
3990
|
+
}));
|
|
3991
|
+
});
|
|
3992
|
+
for (const registeredProvider of browserProviders) {
|
|
3993
|
+
for (const q of projectQueries) {
|
|
3994
|
+
await processQueryForProvider(registeredProvider, q);
|
|
3995
|
+
}
|
|
3768
3996
|
}
|
|
3769
3997
|
}
|
|
3770
3998
|
providerCallEnd = Date.now();
|
|
3771
3999
|
this.throwIfRunCancelled(runId);
|
|
3772
|
-
|
|
3773
|
-
|
|
4000
|
+
if (planExecution) {
|
|
4001
|
+
for (const [provider, units] of planExecution.unitsByProvider) {
|
|
4002
|
+
const providerName = provider;
|
|
4003
|
+
const dispatched = providerDispatchCounts.get(providerName) ?? 0;
|
|
4004
|
+
if (dispatched >= units.length || providerErrors.has(providerName)) continue;
|
|
4005
|
+
providerErrors.set(
|
|
4006
|
+
providerName,
|
|
4007
|
+
`${units.length - dispatched} expected measurement(s) did not run: no ${provider} provider was available to this worker.`
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
const planShortfall = planExecution ? Math.max(0, planExecution.manifest.expectedSlots.length - totalSnapshotsInserted) : 0;
|
|
4012
|
+
const allFailed = totalSnapshotsInserted === 0 && (providerErrors.size > 0 || planShortfall > 0);
|
|
4013
|
+
const someFailed = providerErrors.size > 0 || planShortfall > 0;
|
|
3774
4014
|
if (allFailed) {
|
|
3775
4015
|
const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
|
|
3776
4016
|
this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq2(runs.id, runId)).run();
|
|
@@ -3822,7 +4062,7 @@ var JobRunner = class {
|
|
|
3822
4062
|
const executionContext = {
|
|
3823
4063
|
providerCount: activeProviders.length,
|
|
3824
4064
|
providers: activeProviders.map((provider) => provider.adapter.name),
|
|
3825
|
-
queryCount: projectQueries.length,
|
|
4065
|
+
queryCount: planExecution?.nodeCount ?? projectQueries.length,
|
|
3826
4066
|
...runLocation ? { location: runLocation.label } : {},
|
|
3827
4067
|
...runTrigger ? { trigger: runTrigger } : {},
|
|
3828
4068
|
...canonicalDomain ? { canonicalDomain } : {}
|
|
@@ -3916,11 +4156,13 @@ var JobRunner = class {
|
|
|
3916
4156
|
finishedAt: runs.finishedAt,
|
|
3917
4157
|
error: runs.error,
|
|
3918
4158
|
trigger: runs.trigger,
|
|
3919
|
-
queries: runs.queries
|
|
4159
|
+
queries: runs.queries,
|
|
4160
|
+
measurementPlanVersionId: runs.measurementPlanVersionId,
|
|
4161
|
+
measurementManifest: runs.measurementManifest
|
|
3920
4162
|
}).from(runs).where(eq2(runs.id, runId)).get();
|
|
3921
4163
|
}
|
|
3922
4164
|
isRunCancelled(runId) {
|
|
3923
|
-
return this.
|
|
4165
|
+
return this.db.select({ status: runs.status }).from(runs).where(eq2(runs.id, runId)).get()?.status === "cancelled";
|
|
3924
4166
|
}
|
|
3925
4167
|
throwIfRunCancelled(runId) {
|
|
3926
4168
|
if (this.isRunCancelled(runId)) {
|
|
@@ -7178,7 +7420,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
7178
7420
|
return result;
|
|
7179
7421
|
}
|
|
7180
7422
|
async function backfillInsightsCommand(project, opts) {
|
|
7181
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
7423
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-DVMEVHAG.js");
|
|
7182
7424
|
const config = loadConfig();
|
|
7183
7425
|
const db = createClient(config.database);
|
|
7184
7426
|
migrate(db);
|
|
@@ -8435,12 +8677,17 @@ var Scheduler = class {
|
|
|
8435
8677
|
resolvedLocation = loc;
|
|
8436
8678
|
}
|
|
8437
8679
|
const locationLabel = resolvedLocation?.label ?? null;
|
|
8680
|
+
const scheduleProviders = currentSchedule.providers;
|
|
8681
|
+
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
8438
8682
|
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
8439
8683
|
createdAt: now,
|
|
8440
8684
|
kind: "answer-visibility",
|
|
8441
8685
|
projectId,
|
|
8442
8686
|
trigger: "scheduled",
|
|
8443
|
-
location: locationLabel
|
|
8687
|
+
location: locationLabel,
|
|
8688
|
+
providers,
|
|
8689
|
+
runnableProviders: this.callbacks.getRunnableProviderNames?.(),
|
|
8690
|
+
providerModels: this.callbacks.getEffectiveProviderModels?.()
|
|
8444
8691
|
});
|
|
8445
8692
|
if (queueResult.conflict) {
|
|
8446
8693
|
log13.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
@@ -8456,8 +8703,6 @@ var Scheduler = class {
|
|
|
8456
8703
|
nextRunAt,
|
|
8457
8704
|
updatedAt: now
|
|
8458
8705
|
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
8459
|
-
const scheduleProviders = currentSchedule.providers;
|
|
8460
|
-
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
8461
8706
|
log13.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
8462
8707
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
8463
8708
|
} catch (err) {
|
|
@@ -8692,6 +8937,15 @@ var Notifier = class {
|
|
|
8692
8937
|
computeTransitions(runId, projectId) {
|
|
8693
8938
|
const thisRun = this.db.select().from(runs).where(eq15(runs.id, runId)).get();
|
|
8694
8939
|
if (!thisRun) return [];
|
|
8940
|
+
const completeness = measurementRunCompleteness(this.db, runId);
|
|
8941
|
+
if (completeness.planned && !completeness.complete) {
|
|
8942
|
+
log15.info("transitions.skipped-incomplete", {
|
|
8943
|
+
runId,
|
|
8944
|
+
executed: completeness.executed,
|
|
8945
|
+
expected: completeness.expected
|
|
8946
|
+
});
|
|
8947
|
+
return [];
|
|
8948
|
+
}
|
|
8695
8949
|
const groupSiblings = this.db.select().from(runs).where(and12(
|
|
8696
8950
|
eq15(runs.projectId, projectId),
|
|
8697
8951
|
eq15(runs.kind, thisRun.kind),
|
|
@@ -8843,6 +9097,21 @@ var RunCoordinator = class {
|
|
|
8843
9097
|
log16.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
8844
9098
|
return;
|
|
8845
9099
|
}
|
|
9100
|
+
const completeness = measurementRunCompleteness(this.db, runId);
|
|
9101
|
+
if (completeness.planned && !completeness.complete) {
|
|
9102
|
+
log16.info("measurement.incomplete-skip-derived", {
|
|
9103
|
+
runId,
|
|
9104
|
+
projectId,
|
|
9105
|
+
executed: completeness.executed,
|
|
9106
|
+
expected: completeness.expected
|
|
9107
|
+
});
|
|
9108
|
+
try {
|
|
9109
|
+
await this.notifier.onRunCompleted(runId, projectId);
|
|
9110
|
+
} catch (err) {
|
|
9111
|
+
log16.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
9112
|
+
}
|
|
9113
|
+
return;
|
|
9114
|
+
}
|
|
8846
9115
|
let insightCount = 0;
|
|
8847
9116
|
let criticalOrHigh = 0;
|
|
8848
9117
|
if (kind === RunKinds["answer-visibility"]) {
|
|
@@ -11298,70 +11567,6 @@ function formatAuditFactorScore(factor) {
|
|
|
11298
11567
|
var log19 = createLogger("Snapshot");
|
|
11299
11568
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
11300
11569
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
11301
|
-
var ProviderExecutionGate2 = class {
|
|
11302
|
-
constructor(maxConcurrency, maxPerMinute) {
|
|
11303
|
-
this.maxConcurrency = maxConcurrency;
|
|
11304
|
-
this.maxPerMinute = maxPerMinute;
|
|
11305
|
-
}
|
|
11306
|
-
maxConcurrency;
|
|
11307
|
-
maxPerMinute;
|
|
11308
|
-
window = [];
|
|
11309
|
-
waiters = [];
|
|
11310
|
-
rateLimitChain = Promise.resolve();
|
|
11311
|
-
inFlight = 0;
|
|
11312
|
-
async run(task) {
|
|
11313
|
-
await this.acquire();
|
|
11314
|
-
try {
|
|
11315
|
-
await this.waitForRateLimit();
|
|
11316
|
-
return await task();
|
|
11317
|
-
} finally {
|
|
11318
|
-
this.release();
|
|
11319
|
-
}
|
|
11320
|
-
}
|
|
11321
|
-
async acquire() {
|
|
11322
|
-
if (this.inFlight < Math.max(1, this.maxConcurrency)) {
|
|
11323
|
-
this.inFlight++;
|
|
11324
|
-
return;
|
|
11325
|
-
}
|
|
11326
|
-
await new Promise((resolve) => {
|
|
11327
|
-
this.waiters.push(resolve);
|
|
11328
|
-
});
|
|
11329
|
-
this.inFlight++;
|
|
11330
|
-
}
|
|
11331
|
-
release() {
|
|
11332
|
-
this.inFlight = Math.max(0, this.inFlight - 1);
|
|
11333
|
-
const next = this.waiters.shift();
|
|
11334
|
-
next?.();
|
|
11335
|
-
}
|
|
11336
|
-
async waitForRateLimit() {
|
|
11337
|
-
let releaseChain;
|
|
11338
|
-
const previousChain = this.rateLimitChain;
|
|
11339
|
-
this.rateLimitChain = new Promise((resolve) => {
|
|
11340
|
-
releaseChain = resolve;
|
|
11341
|
-
});
|
|
11342
|
-
await previousChain;
|
|
11343
|
-
try {
|
|
11344
|
-
const now = Date.now();
|
|
11345
|
-
const windowStart = now - 6e4;
|
|
11346
|
-
while (this.window.length > 0 && this.window[0] < windowStart) {
|
|
11347
|
-
this.window.shift();
|
|
11348
|
-
}
|
|
11349
|
-
if (this.window.length >= this.maxPerMinute) {
|
|
11350
|
-
const oldestInWindow = this.window[0];
|
|
11351
|
-
const waitMs = oldestInWindow + 6e4 - now + 50;
|
|
11352
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
11353
|
-
const nowAfterWait = Date.now();
|
|
11354
|
-
const newWindowStart = nowAfterWait - 6e4;
|
|
11355
|
-
while (this.window.length > 0 && this.window[0] < newWindowStart) {
|
|
11356
|
-
this.window.shift();
|
|
11357
|
-
}
|
|
11358
|
-
}
|
|
11359
|
-
this.window.push(Date.now());
|
|
11360
|
-
} finally {
|
|
11361
|
-
releaseChain?.();
|
|
11362
|
-
}
|
|
11363
|
-
}
|
|
11364
|
-
};
|
|
11365
11570
|
var SnapshotService = class {
|
|
11366
11571
|
constructor(registry) {
|
|
11367
11572
|
this.registry = registry;
|
|
@@ -11495,7 +11700,8 @@ var SnapshotService = class {
|
|
|
11495
11700
|
for (const provider of ctx.providers) {
|
|
11496
11701
|
gates.set(
|
|
11497
11702
|
provider.adapter.name,
|
|
11498
|
-
|
|
11703
|
+
getSharedProviderExecutionGate(
|
|
11704
|
+
provider.adapter.name,
|
|
11499
11705
|
provider.config.quotaPolicy.maxConcurrency,
|
|
11500
11706
|
provider.config.quotaPolicy.maxRequestsPerMinute
|
|
11501
11707
|
)
|
|
@@ -11909,7 +12115,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
11909
12115
|
const domains = effectiveDomains(project);
|
|
11910
12116
|
const brands = effectiveBrandNames(project);
|
|
11911
12117
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
11912
|
-
const gate =
|
|
12118
|
+
const gate = getSharedProviderExecutionGate(run.provider, provider.config.quotaPolicy.maxConcurrency, provider.config.quotaPolicy.maxRequestsPerMinute);
|
|
11913
12119
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
11914
12120
|
try {
|
|
11915
12121
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11998,6 +12204,14 @@ var DEFAULT_QUOTA = {
|
|
|
11998
12204
|
};
|
|
11999
12205
|
var SESSION_COOKIE_NAME = "canonry_session";
|
|
12000
12206
|
var SESSION_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
12207
|
+
function effectiveProviderModels(registry) {
|
|
12208
|
+
const models = {};
|
|
12209
|
+
for (const provider of registry.getAll()) {
|
|
12210
|
+
const model = provider.config.model ?? provider.adapter.modelRegistry.defaultModel;
|
|
12211
|
+
if (model) models[provider.adapter.name] = model;
|
|
12212
|
+
}
|
|
12213
|
+
return models;
|
|
12214
|
+
}
|
|
12001
12215
|
function parseBooleanEnv(value) {
|
|
12002
12216
|
const normalized = value?.trim().toLowerCase();
|
|
12003
12217
|
if (!normalized) return void 0;
|
|
@@ -12204,8 +12418,10 @@ async function createServer(opts) {
|
|
|
12204
12418
|
}
|
|
12205
12419
|
}
|
|
12206
12420
|
} : true;
|
|
12421
|
+
const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
|
|
12207
12422
|
const app = Fastify({
|
|
12208
|
-
logger
|
|
12423
|
+
logger,
|
|
12424
|
+
trustProxy
|
|
12209
12425
|
});
|
|
12210
12426
|
const registry = new ProviderRegistry();
|
|
12211
12427
|
const providers = opts.config.providers ?? {};
|
|
@@ -12606,6 +12822,10 @@ async function createServer(opts) {
|
|
|
12606
12822
|
app.log.error({ runId, err }, "Scheduled job runner failed");
|
|
12607
12823
|
});
|
|
12608
12824
|
},
|
|
12825
|
+
// Same source of truth the HTTP routes use, so a scheduled sweep resolves
|
|
12826
|
+
// "all configured providers" exactly as a hand-triggered one does.
|
|
12827
|
+
getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
|
|
12828
|
+
getEffectiveProviderModels: () => effectiveProviderModels(registry),
|
|
12609
12829
|
onTrafficSyncRequested: (projectName, sourceId) => {
|
|
12610
12830
|
aeroClient.trafficSync(projectName, sourceId).catch((err) => {
|
|
12611
12831
|
app.log.error(
|
|
@@ -12967,7 +13187,12 @@ async function createServer(opts) {
|
|
|
12967
13187
|
const key = opts.db.select().from(apiKeys).where(eq21(apiKeys.keyHash, hashApiKey(parts[1]))).get();
|
|
12968
13188
|
return Boolean(key && !key.revokedAt);
|
|
12969
13189
|
};
|
|
13190
|
+
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
13191
|
+
const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
|
|
12970
13192
|
app.get(apiPrefix + "/session", async (request, reply) => {
|
|
13193
|
+
if (namedAccountsInUse()) {
|
|
13194
|
+
return reply.send({ authenticated: false, setupRequired: false });
|
|
13195
|
+
}
|
|
12971
13196
|
if (!dashboardRequirePassword) {
|
|
12972
13197
|
return reply.send({ authenticated: true, setupRequired: false });
|
|
12973
13198
|
}
|
|
@@ -12978,6 +13203,10 @@ async function createServer(opts) {
|
|
|
12978
13203
|
});
|
|
12979
13204
|
});
|
|
12980
13205
|
app.post(apiPrefix + "/session/setup", async (request, reply) => {
|
|
13206
|
+
if (namedAccountsInUse()) {
|
|
13207
|
+
const err = forbidden(NAMED_ACCOUNTS_MESSAGE);
|
|
13208
|
+
return reply.status(err.statusCode).send(err.toJSON());
|
|
13209
|
+
}
|
|
12981
13210
|
if (!dashboardRequirePassword) {
|
|
12982
13211
|
return reply.send({ authenticated: true, setupRequired: false });
|
|
12983
13212
|
}
|
|
@@ -13005,6 +13234,10 @@ async function createServer(opts) {
|
|
|
13005
13234
|
return reply.send({ authenticated: true });
|
|
13006
13235
|
});
|
|
13007
13236
|
app.post(apiPrefix + "/session", async (request, reply) => {
|
|
13237
|
+
if (namedAccountsInUse()) {
|
|
13238
|
+
const err2 = forbidden(NAMED_ACCOUNTS_MESSAGE);
|
|
13239
|
+
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
13240
|
+
}
|
|
13008
13241
|
if (!dashboardRequirePassword) {
|
|
13009
13242
|
return reply.send({ authenticated: true, setupRequired: false });
|
|
13010
13243
|
}
|
|
@@ -13117,6 +13350,21 @@ async function createServer(opts) {
|
|
|
13117
13350
|
skipAuth: false,
|
|
13118
13351
|
sessionCookieName: SESSION_COOKIE_NAME,
|
|
13119
13352
|
resolveSessionApiKeyId,
|
|
13353
|
+
// Named-account sessions ride the same cookie path as the older dashboard
|
|
13354
|
+
// cookie, so a sub-path mount behaves the same for both. `secure` is only
|
|
13355
|
+
// forced ON: the config flag knows an https public URL when there is one,
|
|
13356
|
+
// but it cannot see a reverse proxy terminating TLS in front of a
|
|
13357
|
+
// plain-http bind. Passing `false` there would mark the cookie insecure on
|
|
13358
|
+
// exactly the deployment that needs it most, so an unset flag defers to
|
|
13359
|
+
// how the request actually arrived.
|
|
13360
|
+
userSessionCookie: {
|
|
13361
|
+
path: sessionCookiePath,
|
|
13362
|
+
...sessionCookieSecure ? { secure: true } : {}
|
|
13363
|
+
},
|
|
13364
|
+
// Whether an operator has declared which hops to believe. The per-caller
|
|
13365
|
+
// sign-in budget depends on this being the truth rather than something
|
|
13366
|
+
// inferred from a header the caller controls.
|
|
13367
|
+
trustProxyConfigured: trustProxy !== false,
|
|
13120
13368
|
...embed.enabled && embed.projectTabs ? { embedProjectTabs: embed.projectTabs } : {},
|
|
13121
13369
|
explainContentRecommendation,
|
|
13122
13370
|
briefContentRecommendation,
|
|
@@ -13405,6 +13653,7 @@ async function createServer(opts) {
|
|
|
13405
13653
|
});
|
|
13406
13654
|
},
|
|
13407
13655
|
getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
|
|
13656
|
+
getEffectiveProviderModels: () => effectiveProviderModels(registry),
|
|
13408
13657
|
onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
|
|
13409
13658
|
const name = providerName;
|
|
13410
13659
|
if (!adapterMap[name]) return null;
|