@agenr/agenr-plugin 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MEHOGUZE.js → chunk-6T5RXGIR.js} +989 -70
- package/dist/{chunk-Y2BC7RCE.js → chunk-7TDALVPY.js} +1434 -305
- package/dist/{chunk-XD3446YW.js → chunk-DGV6D6Q3.js} +2 -21
- package/dist/chunk-IMQIJPIP.js +886 -0
- package/dist/chunk-MJIB6J5S.js +3059 -0
- package/dist/index.js +1466 -124
- package/openclaw.plugin.json +86 -2
- package/package.json +1 -1
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_CROSS_ENCODER_ALPHA,
|
|
3
|
+
DEFAULT_CROSS_ENCODER_TOP_K,
|
|
4
|
+
DEFAULT_MMR_LAMBDA,
|
|
5
|
+
DEFAULT_SEEDED_RERANK_WEIGHT,
|
|
6
|
+
DEFAULT_STRONG_SEED_SCORE_GAP,
|
|
7
|
+
DEFAULT_STRONG_SEED_TOP_N,
|
|
8
|
+
applyCrossEncoderRerank,
|
|
2
9
|
buildLexicalPlan,
|
|
3
|
-
combinedRelevance,
|
|
4
10
|
computeLexicalScore,
|
|
5
11
|
cosineSimilarity,
|
|
6
12
|
describeClaimKeyNormalizationFailure,
|
|
13
|
+
maximalMarginalRelevance,
|
|
7
14
|
normalizeClaimKey,
|
|
8
15
|
recall,
|
|
9
16
|
resolveClaimSlotPolicy,
|
|
17
|
+
rrfFuse,
|
|
18
|
+
rrfFuseVectorLexical,
|
|
19
|
+
seededRerank,
|
|
20
|
+
selectStrongSeeds,
|
|
21
|
+
sharesEpisodeLineage,
|
|
22
|
+
sharesProcedureLineage,
|
|
10
23
|
tokenize
|
|
11
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-6T5RXGIR.js";
|
|
12
25
|
|
|
13
26
|
// src/adapters/db/client.ts
|
|
14
27
|
import fs from "fs/promises";
|
|
@@ -1476,14 +1489,14 @@ function normalizeProcedureSources(value, label, filePath, options = {}) {
|
|
|
1476
1489
|
function normalizeProcedureSource(record, label, filePath) {
|
|
1477
1490
|
rejectUnexpectedProcedureFields(record, SOURCE_KEYS, label, filePath);
|
|
1478
1491
|
const kind = readProcedureSourceKind(record.kind, `${label}.kind`, filePath, PROCEDURE_SOURCE_KINDS);
|
|
1479
|
-
const
|
|
1492
|
+
const path4 = readOptionalProcedureString(record.path, `${label}.path`, filePath);
|
|
1480
1493
|
const locator = readOptionalProcedureString(record.locator, `${label}.locator`, filePath);
|
|
1481
1494
|
const sourceLabel = readOptionalProcedureString(record.label, `${label}.label`, filePath);
|
|
1482
1495
|
switch (kind) {
|
|
1483
1496
|
case "skill":
|
|
1484
1497
|
case "doc":
|
|
1485
1498
|
case "repo_file":
|
|
1486
|
-
if (!
|
|
1499
|
+
if (!path4) {
|
|
1487
1500
|
throw new Error(`Invalid procedure ${filePath}: ${label}.${kind} sources require a path.`);
|
|
1488
1501
|
}
|
|
1489
1502
|
break;
|
|
@@ -1501,7 +1514,7 @@ function normalizeProcedureSource(record, label, filePath) {
|
|
|
1501
1514
|
}
|
|
1502
1515
|
return {
|
|
1503
1516
|
kind,
|
|
1504
|
-
...
|
|
1517
|
+
...path4 ? { path: path4 } : {},
|
|
1505
1518
|
...locator ? { locator } : {},
|
|
1506
1519
|
...sourceLabel ? { label: sourceLabel } : {}
|
|
1507
1520
|
};
|
|
@@ -3518,8 +3531,8 @@ import { fileURLToPath } from "url";
|
|
|
3518
3531
|
function isRecord2(value) {
|
|
3519
3532
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3520
3533
|
}
|
|
3521
|
-
function pushIssue(issues,
|
|
3522
|
-
issues.push({ path:
|
|
3534
|
+
function pushIssue(issues, path4, message) {
|
|
3535
|
+
issues.push({ path: path4, message });
|
|
3523
3536
|
}
|
|
3524
3537
|
function pushUnexpectedFields(value, allowedKeys, basePath, issues) {
|
|
3525
3538
|
for (const key of Object.keys(value)) {
|
|
@@ -3529,68 +3542,68 @@ function pushUnexpectedFields(value, allowedKeys, basePath, issues) {
|
|
|
3529
3542
|
pushIssue(issues, joinPath(basePath, key), "Unexpected field.");
|
|
3530
3543
|
}
|
|
3531
3544
|
}
|
|
3532
|
-
function parseRequiredTrimmedString(value,
|
|
3545
|
+
function parseRequiredTrimmedString(value, path4, issues, message = "Expected a non-empty string.") {
|
|
3533
3546
|
if (typeof value !== "string") {
|
|
3534
|
-
pushIssue(issues,
|
|
3547
|
+
pushIssue(issues, path4, message);
|
|
3535
3548
|
return void 0;
|
|
3536
3549
|
}
|
|
3537
3550
|
const normalized = value.trim();
|
|
3538
3551
|
if (normalized.length === 0) {
|
|
3539
|
-
pushIssue(issues,
|
|
3552
|
+
pushIssue(issues, path4, message);
|
|
3540
3553
|
return void 0;
|
|
3541
3554
|
}
|
|
3542
3555
|
return normalized;
|
|
3543
3556
|
}
|
|
3544
|
-
function parseOptionalTrimmedString(value,
|
|
3557
|
+
function parseOptionalTrimmedString(value, path4, issues, typeMessage = "Expected a string.", emptyMessage = "Expected a non-empty string.") {
|
|
3545
3558
|
if (value === void 0) {
|
|
3546
3559
|
return void 0;
|
|
3547
3560
|
}
|
|
3548
3561
|
if (typeof value !== "string") {
|
|
3549
|
-
pushIssue(issues,
|
|
3562
|
+
pushIssue(issues, path4, typeMessage);
|
|
3550
3563
|
return void 0;
|
|
3551
3564
|
}
|
|
3552
3565
|
const normalized = value.trim();
|
|
3553
3566
|
if (normalized.length === 0) {
|
|
3554
|
-
pushIssue(issues,
|
|
3567
|
+
pushIssue(issues, path4, emptyMessage);
|
|
3555
3568
|
return void 0;
|
|
3556
3569
|
}
|
|
3557
3570
|
return normalized;
|
|
3558
3571
|
}
|
|
3559
|
-
function parseOptionalBoolean(value,
|
|
3572
|
+
function parseOptionalBoolean(value, path4, issues, message = "Expected a boolean.") {
|
|
3560
3573
|
if (value === void 0) {
|
|
3561
3574
|
return void 0;
|
|
3562
3575
|
}
|
|
3563
3576
|
if (typeof value !== "boolean") {
|
|
3564
|
-
pushIssue(issues,
|
|
3577
|
+
pushIssue(issues, path4, message);
|
|
3565
3578
|
return void 0;
|
|
3566
3579
|
}
|
|
3567
3580
|
return value;
|
|
3568
3581
|
}
|
|
3569
|
-
function parseOptionalIntegerInRange(value,
|
|
3582
|
+
function parseOptionalIntegerInRange(value, path4, issues, bounds) {
|
|
3570
3583
|
if (value === void 0) {
|
|
3571
3584
|
return void 0;
|
|
3572
3585
|
}
|
|
3573
3586
|
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
|
|
3574
|
-
pushIssue(issues,
|
|
3587
|
+
pushIssue(issues, path4, integerRangeMessage(bounds));
|
|
3575
3588
|
return void 0;
|
|
3576
3589
|
}
|
|
3577
3590
|
if (bounds.min !== void 0 && value < bounds.min) {
|
|
3578
|
-
pushIssue(issues,
|
|
3591
|
+
pushIssue(issues, path4, integerRangeMessage(bounds));
|
|
3579
3592
|
return void 0;
|
|
3580
3593
|
}
|
|
3581
3594
|
if (bounds.max !== void 0 && value > bounds.max) {
|
|
3582
|
-
pushIssue(issues,
|
|
3595
|
+
pushIssue(issues, path4, integerRangeMessage(bounds));
|
|
3583
3596
|
return void 0;
|
|
3584
3597
|
}
|
|
3585
3598
|
return value;
|
|
3586
3599
|
}
|
|
3587
|
-
function parseOptionalTimestampString(value,
|
|
3588
|
-
const timestamp = parseOptionalTrimmedString(value,
|
|
3600
|
+
function parseOptionalTimestampString(value, path4, issues, message = "Expected a valid timestamp string.") {
|
|
3601
|
+
const timestamp = parseOptionalTrimmedString(value, path4, issues);
|
|
3589
3602
|
if (timestamp === void 0) {
|
|
3590
3603
|
return void 0;
|
|
3591
3604
|
}
|
|
3592
3605
|
if (Number.isNaN(Date.parse(timestamp))) {
|
|
3593
|
-
pushIssue(issues,
|
|
3606
|
+
pushIssue(issues, path4, message);
|
|
3594
3607
|
return void 0;
|
|
3595
3608
|
}
|
|
3596
3609
|
return timestamp;
|
|
@@ -3716,7 +3729,8 @@ function toAgenrConfigInput(config, options = {}) {
|
|
|
3716
3729
|
...config.extractionContext ? { extractionContext: config.extractionContext } : {},
|
|
3717
3730
|
...hasModelConfig(config.extractionModel) ? { extractionModel: config.extractionModel } : {},
|
|
3718
3731
|
...hasModelConfig(config.dedupModel) ? { dedupModel: config.dedupModel } : {},
|
|
3719
|
-
...hasModelConfig(config.episodeModel) ? { episodeModel: config.episodeModel } : {}
|
|
3732
|
+
...hasModelConfig(config.episodeModel) ? { episodeModel: config.episodeModel } : {},
|
|
3733
|
+
...hasModelConfig(config.crossEncoderModel) ? { crossEncoderModel: config.crossEncoderModel } : {}
|
|
3720
3734
|
};
|
|
3721
3735
|
const claimExtraction = toClaimExtractionInput(config.claimExtraction);
|
|
3722
3736
|
if (claimExtraction) {
|
|
@@ -3766,6 +3780,7 @@ function normalizeAgenrConfig(value, options) {
|
|
|
3766
3780
|
const extractionModel = parseModelConfig(value.extractionModel, "extractionModel", issues);
|
|
3767
3781
|
const dedupModel = parseModelConfig(value.dedupModel, "dedupModel", issues);
|
|
3768
3782
|
const episodeModel = parseModelConfig(value.episodeModel, "episodeModel", issues);
|
|
3783
|
+
const crossEncoderModel = parseModelConfig(value.crossEncoderModel, "crossEncoderModel", issues);
|
|
3769
3784
|
const claimExtraction = parseClaimExtractionConfig(value.claimExtraction, "claimExtraction", issues);
|
|
3770
3785
|
const surgeon = parseSurgeonConfig(value.surgeon, "surgeon", issues);
|
|
3771
3786
|
const dbPath = parseOptionalTrimmedString(value.dbPath, "dbPath", issues);
|
|
@@ -3792,6 +3807,7 @@ function normalizeAgenrConfig(value, options) {
|
|
|
3792
3807
|
...extractionModel ? { extractionModel } : {},
|
|
3793
3808
|
...dedupModel ? { dedupModel } : {},
|
|
3794
3809
|
...episodeModel ? { episodeModel } : {},
|
|
3810
|
+
...crossEncoderModel ? { crossEncoderModel } : {},
|
|
3795
3811
|
...claimExtraction.input ? { claimExtraction: claimExtraction.input } : {},
|
|
3796
3812
|
...surgeon.input ? { surgeon: surgeon.input } : {},
|
|
3797
3813
|
...dbPath ? { dbPath } : {},
|
|
@@ -3810,6 +3826,7 @@ function normalizeAgenrConfig(value, options) {
|
|
|
3810
3826
|
...extractionModel ? { extractionModel } : {},
|
|
3811
3827
|
...dedupModel ? { dedupModel } : {},
|
|
3812
3828
|
...episodeModel ? { episodeModel } : {},
|
|
3829
|
+
...crossEncoderModel ? { crossEncoderModel } : {},
|
|
3813
3830
|
claimExtraction: claimExtraction.resolved,
|
|
3814
3831
|
surgeon: surgeon.resolved,
|
|
3815
3832
|
dbPath: dbPath ?? options.defaultDbPath,
|
|
@@ -3828,6 +3845,7 @@ function pushTopLevelIssues(value, issues) {
|
|
|
3828
3845
|
"extractionModel",
|
|
3829
3846
|
"dedupModel",
|
|
3830
3847
|
"episodeModel",
|
|
3848
|
+
"crossEncoderModel",
|
|
3831
3849
|
"claimExtraction",
|
|
3832
3850
|
"surgeon",
|
|
3833
3851
|
"dbPath",
|
|
@@ -3843,41 +3861,41 @@ function pushTopLevelIssues(value, issues) {
|
|
|
3843
3861
|
pushIssue(issues, "embeddingApiKey", "Removed field. Move this value to credentials.openaiApiKey, then delete embeddingApiKey.");
|
|
3844
3862
|
}
|
|
3845
3863
|
}
|
|
3846
|
-
function parseAuth(value,
|
|
3847
|
-
const normalized = parseOptionalTrimmedString(value,
|
|
3864
|
+
function parseAuth(value, path4, issues) {
|
|
3865
|
+
const normalized = parseOptionalTrimmedString(value, path4, issues);
|
|
3848
3866
|
if (!normalized) {
|
|
3849
3867
|
return void 0;
|
|
3850
3868
|
}
|
|
3851
3869
|
if (!isAgenrAuthMethod(normalized)) {
|
|
3852
|
-
pushIssue(issues,
|
|
3870
|
+
pushIssue(issues, path4, "Expected a supported auth method.");
|
|
3853
3871
|
return void 0;
|
|
3854
3872
|
}
|
|
3855
3873
|
return normalized;
|
|
3856
3874
|
}
|
|
3857
|
-
function parseProvider(value,
|
|
3858
|
-
const normalized = parseOptionalTrimmedString(value,
|
|
3875
|
+
function parseProvider(value, path4, issues) {
|
|
3876
|
+
const normalized = parseOptionalTrimmedString(value, path4, issues);
|
|
3859
3877
|
if (!normalized) {
|
|
3860
3878
|
return void 0;
|
|
3861
3879
|
}
|
|
3862
3880
|
if (!isAgenrProvider(normalized)) {
|
|
3863
|
-
pushIssue(issues,
|
|
3881
|
+
pushIssue(issues, path4, "Expected a supported provider.");
|
|
3864
3882
|
return void 0;
|
|
3865
3883
|
}
|
|
3866
3884
|
return normalized;
|
|
3867
3885
|
}
|
|
3868
|
-
function parseCredentials(value,
|
|
3886
|
+
function parseCredentials(value, path4, issues) {
|
|
3869
3887
|
if (value === void 0) {
|
|
3870
3888
|
return void 0;
|
|
3871
3889
|
}
|
|
3872
3890
|
if (!isRecord2(value)) {
|
|
3873
|
-
pushIssue(issues,
|
|
3891
|
+
pushIssue(issues, path4, "Expected an object.");
|
|
3874
3892
|
return void 0;
|
|
3875
3893
|
}
|
|
3876
3894
|
const startIndex = issues.length;
|
|
3877
|
-
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["openaiApiKey", "anthropicApiKey", "anthropicOauthToken"]),
|
|
3878
|
-
const openaiApiKey = parseOptionalTrimmedString(value.openaiApiKey, `${
|
|
3879
|
-
const anthropicApiKey = parseOptionalTrimmedString(value.anthropicApiKey, `${
|
|
3880
|
-
const anthropicOauthToken = parseOptionalTrimmedString(value.anthropicOauthToken, `${
|
|
3895
|
+
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["openaiApiKey", "anthropicApiKey", "anthropicOauthToken"]), path4, issues);
|
|
3896
|
+
const openaiApiKey = parseOptionalTrimmedString(value.openaiApiKey, `${path4}.openaiApiKey`, issues);
|
|
3897
|
+
const anthropicApiKey = parseOptionalTrimmedString(value.anthropicApiKey, `${path4}.anthropicApiKey`, issues);
|
|
3898
|
+
const anthropicOauthToken = parseOptionalTrimmedString(value.anthropicOauthToken, `${path4}.anthropicOauthToken`, issues);
|
|
3881
3899
|
if (issues.length > startIndex) {
|
|
3882
3900
|
return void 0;
|
|
3883
3901
|
}
|
|
@@ -3888,20 +3906,20 @@ function parseCredentials(value, path3, issues) {
|
|
|
3888
3906
|
};
|
|
3889
3907
|
return hasStoredCredentials(credentials) ? credentials : void 0;
|
|
3890
3908
|
}
|
|
3891
|
-
function parseModelConfig(value,
|
|
3909
|
+
function parseModelConfig(value, path4, issues) {
|
|
3892
3910
|
if (value === void 0) {
|
|
3893
3911
|
return void 0;
|
|
3894
3912
|
}
|
|
3895
3913
|
if (!isRecord2(value)) {
|
|
3896
|
-
pushIssue(issues,
|
|
3914
|
+
pushIssue(issues, path4, "Expected an object.");
|
|
3897
3915
|
return void 0;
|
|
3898
3916
|
}
|
|
3899
3917
|
const startIndex = issues.length;
|
|
3900
|
-
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["provider", "model"]),
|
|
3901
|
-
const provider = parseProvider(value.provider, `${
|
|
3902
|
-
const model = parseOptionalTrimmedString(value.model, `${
|
|
3918
|
+
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["provider", "model"]), path4, issues);
|
|
3919
|
+
const provider = parseProvider(value.provider, `${path4}.provider`, issues);
|
|
3920
|
+
const model = parseOptionalTrimmedString(value.model, `${path4}.model`, issues);
|
|
3903
3921
|
if (!provider && !model) {
|
|
3904
|
-
pushIssue(issues,
|
|
3922
|
+
pushIssue(issues, path4, "Expected at least one of provider or model.");
|
|
3905
3923
|
}
|
|
3906
3924
|
if (issues.length > startIndex) {
|
|
3907
3925
|
return void 0;
|
|
@@ -3911,7 +3929,7 @@ function parseModelConfig(value, path3, issues) {
|
|
|
3911
3929
|
...model ? { model } : {}
|
|
3912
3930
|
};
|
|
3913
3931
|
}
|
|
3914
|
-
function parseClaimExtractionConfig(value,
|
|
3932
|
+
function parseClaimExtractionConfig(value, path4, issues) {
|
|
3915
3933
|
const defaults = createDefaultClaimExtractionConfig();
|
|
3916
3934
|
if (value === void 0) {
|
|
3917
3935
|
return {
|
|
@@ -3919,20 +3937,20 @@ function parseClaimExtractionConfig(value, path3, issues) {
|
|
|
3919
3937
|
};
|
|
3920
3938
|
}
|
|
3921
3939
|
if (!isRecord2(value)) {
|
|
3922
|
-
pushIssue(issues,
|
|
3940
|
+
pushIssue(issues, path4, "Expected an object.");
|
|
3923
3941
|
return {
|
|
3924
3942
|
resolved: defaults
|
|
3925
3943
|
};
|
|
3926
3944
|
}
|
|
3927
3945
|
const startIndex = issues.length;
|
|
3928
|
-
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["enabled", "confidenceThreshold", "eligibleTypes", "concurrency", "model"]),
|
|
3929
|
-
const enabled = parseOptionalBoolean(value.enabled, `${
|
|
3930
|
-
const confidenceThreshold = parseOptionalUnitInterval(value.confidenceThreshold, `${
|
|
3931
|
-
const eligibleTypes = parseEligibleTypes(value.eligibleTypes, `${
|
|
3932
|
-
const concurrency = parseOptionalIntegerInRange(value.concurrency, `${
|
|
3946
|
+
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["enabled", "confidenceThreshold", "eligibleTypes", "concurrency", "model"]), path4, issues);
|
|
3947
|
+
const enabled = parseOptionalBoolean(value.enabled, `${path4}.enabled`, issues);
|
|
3948
|
+
const confidenceThreshold = parseOptionalUnitInterval(value.confidenceThreshold, `${path4}.confidenceThreshold`, issues);
|
|
3949
|
+
const eligibleTypes = parseEligibleTypes(value.eligibleTypes, `${path4}.eligibleTypes`, issues);
|
|
3950
|
+
const concurrency = parseOptionalIntegerInRange(value.concurrency, `${path4}.concurrency`, issues, {
|
|
3933
3951
|
min: 1
|
|
3934
3952
|
});
|
|
3935
|
-
const model = parseModelConfig(value.model, `${
|
|
3953
|
+
const model = parseModelConfig(value.model, `${path4}.model`, issues);
|
|
3936
3954
|
if (issues.length > startIndex) {
|
|
3937
3955
|
return {
|
|
3938
3956
|
resolved: defaults
|
|
@@ -3956,7 +3974,7 @@ function parseClaimExtractionConfig(value, path3, issues) {
|
|
|
3956
3974
|
}
|
|
3957
3975
|
};
|
|
3958
3976
|
}
|
|
3959
|
-
function parseSurgeonConfig(value,
|
|
3977
|
+
function parseSurgeonConfig(value, path4, issues) {
|
|
3960
3978
|
const defaults = createDefaultSurgeonConfig();
|
|
3961
3979
|
if (value === void 0) {
|
|
3962
3980
|
return {
|
|
@@ -3964,19 +3982,19 @@ function parseSurgeonConfig(value, path3, issues) {
|
|
|
3964
3982
|
};
|
|
3965
3983
|
}
|
|
3966
3984
|
if (!isRecord2(value)) {
|
|
3967
|
-
pushIssue(issues,
|
|
3985
|
+
pushIssue(issues, path4, "Expected an object.");
|
|
3968
3986
|
return {
|
|
3969
3987
|
resolved: defaults
|
|
3970
3988
|
};
|
|
3971
3989
|
}
|
|
3972
3990
|
const startIndex = issues.length;
|
|
3973
|
-
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["model", "costCap", "dailyCostCap", "contextLimit", "customInstructions", "passes"]),
|
|
3974
|
-
const model = parseModelConfig(value.model, `${
|
|
3975
|
-
const costCap = parseOptionalPositiveNumber(value.costCap, `${
|
|
3976
|
-
const dailyCostCap = parseOptionalNonNegativeNumber(value.dailyCostCap, `${
|
|
3977
|
-
const contextLimit = parseOptionalIntegerInRange(value.contextLimit, `${
|
|
3978
|
-
const customInstructions = parseOptionalTrimmedString(value.customInstructions, `${
|
|
3979
|
-
const retirement = parseRetirementPassConfig(value.passes, `${
|
|
3991
|
+
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["model", "costCap", "dailyCostCap", "contextLimit", "customInstructions", "passes"]), path4, issues);
|
|
3992
|
+
const model = parseModelConfig(value.model, `${path4}.model`, issues);
|
|
3993
|
+
const costCap = parseOptionalPositiveNumber(value.costCap, `${path4}.costCap`, issues);
|
|
3994
|
+
const dailyCostCap = parseOptionalNonNegativeNumber(value.dailyCostCap, `${path4}.dailyCostCap`, issues);
|
|
3995
|
+
const contextLimit = parseOptionalIntegerInRange(value.contextLimit, `${path4}.contextLimit`, issues, { min: 0 });
|
|
3996
|
+
const customInstructions = parseOptionalTrimmedString(value.customInstructions, `${path4}.customInstructions`, issues);
|
|
3997
|
+
const retirement = parseRetirementPassConfig(value.passes, `${path4}.passes`, issues);
|
|
3980
3998
|
if (issues.length > startIndex) {
|
|
3981
3999
|
return {
|
|
3982
4000
|
resolved: defaults
|
|
@@ -4008,7 +4026,7 @@ function parseSurgeonConfig(value, path3, issues) {
|
|
|
4008
4026
|
}
|
|
4009
4027
|
};
|
|
4010
4028
|
}
|
|
4011
|
-
function parseRetirementPassConfig(value,
|
|
4029
|
+
function parseRetirementPassConfig(value, path4, issues) {
|
|
4012
4030
|
const defaults = createDefaultRetirementPassConfig();
|
|
4013
4031
|
if (value === void 0) {
|
|
4014
4032
|
return {
|
|
@@ -4016,36 +4034,36 @@ function parseRetirementPassConfig(value, path3, issues) {
|
|
|
4016
4034
|
};
|
|
4017
4035
|
}
|
|
4018
4036
|
if (!isRecord2(value)) {
|
|
4019
|
-
pushIssue(issues,
|
|
4037
|
+
pushIssue(issues, path4, "Expected an object.");
|
|
4020
4038
|
return {
|
|
4021
4039
|
resolved: defaults
|
|
4022
4040
|
};
|
|
4023
4041
|
}
|
|
4024
4042
|
const startIndex = issues.length;
|
|
4025
|
-
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["retirement"]),
|
|
4043
|
+
pushUnexpectedFields(value, /* @__PURE__ */ new Set(["retirement"]), path4, issues);
|
|
4026
4044
|
const retirement = value.retirement;
|
|
4027
4045
|
if (retirement === void 0) {
|
|
4028
4046
|
if (issues.length === startIndex) {
|
|
4029
|
-
pushIssue(issues,
|
|
4047
|
+
pushIssue(issues, path4, "Expected a retirement config when passes is provided.");
|
|
4030
4048
|
}
|
|
4031
4049
|
return {
|
|
4032
4050
|
resolved: defaults
|
|
4033
4051
|
};
|
|
4034
4052
|
}
|
|
4035
4053
|
if (!isRecord2(retirement)) {
|
|
4036
|
-
pushIssue(issues, `${
|
|
4054
|
+
pushIssue(issues, `${path4}.retirement`, "Expected an object.");
|
|
4037
4055
|
return {
|
|
4038
4056
|
resolved: defaults
|
|
4039
4057
|
};
|
|
4040
4058
|
}
|
|
4041
|
-
pushUnexpectedFields(retirement, /* @__PURE__ */ new Set(["protectRecalledDays", "protectMinImportance", "skipRecentlyEvaluatedDays"]), `${
|
|
4042
|
-
const protectRecalledDays = parseOptionalIntegerInRange(retirement.protectRecalledDays, `${
|
|
4059
|
+
pushUnexpectedFields(retirement, /* @__PURE__ */ new Set(["protectRecalledDays", "protectMinImportance", "skipRecentlyEvaluatedDays"]), `${path4}.retirement`, issues);
|
|
4060
|
+
const protectRecalledDays = parseOptionalIntegerInRange(retirement.protectRecalledDays, `${path4}.retirement.protectRecalledDays`, issues, {
|
|
4043
4061
|
min: 0
|
|
4044
4062
|
});
|
|
4045
|
-
const protectMinImportance = parseOptionalIntegerInRange(retirement.protectMinImportance, `${
|
|
4063
|
+
const protectMinImportance = parseOptionalIntegerInRange(retirement.protectMinImportance, `${path4}.retirement.protectMinImportance`, issues, {
|
|
4046
4064
|
min: 0
|
|
4047
4065
|
});
|
|
4048
|
-
const skipRecentlyEvaluatedDays = parseOptionalIntegerInRange(retirement.skipRecentlyEvaluatedDays, `${
|
|
4066
|
+
const skipRecentlyEvaluatedDays = parseOptionalIntegerInRange(retirement.skipRecentlyEvaluatedDays, `${path4}.retirement.skipRecentlyEvaluatedDays`, issues, {
|
|
4049
4067
|
min: 0
|
|
4050
4068
|
});
|
|
4051
4069
|
if (issues.length > startIndex) {
|
|
@@ -4067,54 +4085,54 @@ function parseRetirementPassConfig(value, path3, issues) {
|
|
|
4067
4085
|
}
|
|
4068
4086
|
};
|
|
4069
4087
|
}
|
|
4070
|
-
function parseOptionalUnitInterval(value,
|
|
4088
|
+
function parseOptionalUnitInterval(value, path4, issues) {
|
|
4071
4089
|
if (value === void 0) {
|
|
4072
4090
|
return void 0;
|
|
4073
4091
|
}
|
|
4074
4092
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
4075
|
-
pushIssue(issues,
|
|
4093
|
+
pushIssue(issues, path4, "Expected a number from 0 to 1.");
|
|
4076
4094
|
return void 0;
|
|
4077
4095
|
}
|
|
4078
4096
|
return value;
|
|
4079
4097
|
}
|
|
4080
|
-
function parseOptionalPositiveNumber(value,
|
|
4098
|
+
function parseOptionalPositiveNumber(value, path4, issues) {
|
|
4081
4099
|
if (value === void 0) {
|
|
4082
4100
|
return void 0;
|
|
4083
4101
|
}
|
|
4084
4102
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
4085
|
-
pushIssue(issues,
|
|
4103
|
+
pushIssue(issues, path4, "Expected a positive number.");
|
|
4086
4104
|
return void 0;
|
|
4087
4105
|
}
|
|
4088
4106
|
return value;
|
|
4089
4107
|
}
|
|
4090
|
-
function parseOptionalNonNegativeNumber(value,
|
|
4108
|
+
function parseOptionalNonNegativeNumber(value, path4, issues) {
|
|
4091
4109
|
if (value === void 0) {
|
|
4092
4110
|
return void 0;
|
|
4093
4111
|
}
|
|
4094
4112
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
4095
|
-
pushIssue(issues,
|
|
4113
|
+
pushIssue(issues, path4, "Expected a non-negative number.");
|
|
4096
4114
|
return void 0;
|
|
4097
4115
|
}
|
|
4098
4116
|
return value;
|
|
4099
4117
|
}
|
|
4100
|
-
function parseEligibleTypes(value,
|
|
4118
|
+
function parseEligibleTypes(value, path4, issues) {
|
|
4101
4119
|
if (value === void 0) {
|
|
4102
4120
|
return void 0;
|
|
4103
4121
|
}
|
|
4104
4122
|
if (!Array.isArray(value)) {
|
|
4105
|
-
pushIssue(issues,
|
|
4123
|
+
pushIssue(issues, path4, "Expected an array of entry types.");
|
|
4106
4124
|
return void 0;
|
|
4107
4125
|
}
|
|
4108
4126
|
const normalized = [];
|
|
4109
4127
|
const seen = /* @__PURE__ */ new Set();
|
|
4110
4128
|
for (const [index, item] of value.entries()) {
|
|
4111
4129
|
if (typeof item !== "string") {
|
|
4112
|
-
pushIssue(issues, `${
|
|
4130
|
+
pushIssue(issues, `${path4}.${index}`, "Expected a supported entry type.");
|
|
4113
4131
|
continue;
|
|
4114
4132
|
}
|
|
4115
4133
|
const trimmed = item.trim();
|
|
4116
4134
|
if (!isEntryType(trimmed)) {
|
|
4117
|
-
pushIssue(issues, `${
|
|
4135
|
+
pushIssue(issues, `${path4}.${index}`, "Expected a supported entry type.");
|
|
4118
4136
|
continue;
|
|
4119
4137
|
}
|
|
4120
4138
|
if (!seen.has(trimmed)) {
|
|
@@ -4123,7 +4141,7 @@ function parseEligibleTypes(value, path3, issues) {
|
|
|
4123
4141
|
}
|
|
4124
4142
|
}
|
|
4125
4143
|
if (normalized.length === 0) {
|
|
4126
|
-
pushIssue(issues,
|
|
4144
|
+
pushIssue(issues, path4, "Expected at least one supported entry type.");
|
|
4127
4145
|
return void 0;
|
|
4128
4146
|
}
|
|
4129
4147
|
return normalized;
|
|
@@ -4526,6 +4544,604 @@ async function sleep(durationMs) {
|
|
|
4526
4544
|
});
|
|
4527
4545
|
}
|
|
4528
4546
|
|
|
4547
|
+
// src/adapters/llm.ts
|
|
4548
|
+
import { createHash as createHash2 } from "crypto";
|
|
4549
|
+
import fs3 from "fs";
|
|
4550
|
+
import os2 from "os";
|
|
4551
|
+
import path3 from "path";
|
|
4552
|
+
import { createRequire } from "module";
|
|
4553
|
+
import { completeSimple, getEnvApiKey, getModel } from "@mariozechner/pi-ai";
|
|
4554
|
+
var DEFAULT_REASONING = "medium";
|
|
4555
|
+
var require2 = createRequire(import.meta.url);
|
|
4556
|
+
var getModelWithStrings = getModel;
|
|
4557
|
+
function probeLlmCredentials(params) {
|
|
4558
|
+
const candidate = resolveCredentialCandidate(params);
|
|
4559
|
+
if (!candidate) {
|
|
4560
|
+
return {
|
|
4561
|
+
available: false,
|
|
4562
|
+
guidance: credentialSetupGuidance(params.auth)
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
4565
|
+
return {
|
|
4566
|
+
available: true,
|
|
4567
|
+
source: candidate.source,
|
|
4568
|
+
guidance: "Credentials available.",
|
|
4569
|
+
credentials: {
|
|
4570
|
+
apiKey: candidate.token,
|
|
4571
|
+
source: candidate.source,
|
|
4572
|
+
auth: params.auth
|
|
4573
|
+
}
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
function resolveAuthCredentials(params) {
|
|
4577
|
+
const probe = probeLlmCredentials(params);
|
|
4578
|
+
if (!probe.available || !probe.credentials) {
|
|
4579
|
+
throw new Error(probe.guidance);
|
|
4580
|
+
}
|
|
4581
|
+
return probe.credentials;
|
|
4582
|
+
}
|
|
4583
|
+
function createLlmClient(provider, modelId, options = {}) {
|
|
4584
|
+
const model = getModelWithStrings(provider, modelId);
|
|
4585
|
+
const metadata = {
|
|
4586
|
+
model,
|
|
4587
|
+
contextWindowTokens: model.contextWindow,
|
|
4588
|
+
maxOutputTokens: model.maxTokens,
|
|
4589
|
+
supportsReasoning: model.reasoning,
|
|
4590
|
+
usage: createEmptyUsageStats()
|
|
4591
|
+
};
|
|
4592
|
+
const resolvedApiKey = normalizeOptionalString6(options.apiKey);
|
|
4593
|
+
const requestCompletion = async (systemPrompt, userMessage) => {
|
|
4594
|
+
const response = await completeSimple(
|
|
4595
|
+
model,
|
|
4596
|
+
{
|
|
4597
|
+
systemPrompt,
|
|
4598
|
+
messages: [
|
|
4599
|
+
{
|
|
4600
|
+
role: "user",
|
|
4601
|
+
content: userMessage,
|
|
4602
|
+
timestamp: Date.now()
|
|
4603
|
+
}
|
|
4604
|
+
]
|
|
4605
|
+
},
|
|
4606
|
+
{
|
|
4607
|
+
apiKey: resolvedApiKey,
|
|
4608
|
+
reasoning: metadata.supportsReasoning ? options.reasoning ?? DEFAULT_REASONING : void 0
|
|
4609
|
+
}
|
|
4610
|
+
);
|
|
4611
|
+
accumulateUsage(metadata.usage, response.usage);
|
|
4612
|
+
if (response.stopReason === "error") {
|
|
4613
|
+
throw new Error(response.errorMessage ?? `LLM completion failed for ${provider}/${modelId}.`);
|
|
4614
|
+
}
|
|
4615
|
+
return response;
|
|
4616
|
+
};
|
|
4617
|
+
const complete = async (systemPrompt, userMessage) => {
|
|
4618
|
+
const response = await requestCompletion(systemPrompt, userMessage);
|
|
4619
|
+
return extractText(response);
|
|
4620
|
+
};
|
|
4621
|
+
return {
|
|
4622
|
+
metadata,
|
|
4623
|
+
complete,
|
|
4624
|
+
completeJson: async (systemPrompt, userMessage) => {
|
|
4625
|
+
const response = await requestCompletion(systemPrompt, userMessage);
|
|
4626
|
+
const text = extractText(response);
|
|
4627
|
+
return JSON.parse(stripCodeFence(text));
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
function resolveModel(config, stage) {
|
|
4632
|
+
const override = resolveStageOverride(config, stage);
|
|
4633
|
+
return {
|
|
4634
|
+
provider: normalizeOptionalString6(override?.provider) ?? resolveStageDefaultProvider(config, stage),
|
|
4635
|
+
modelId: normalizeOptionalString6(override?.model) ?? resolveStageDefaultModel(config, stage)
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
function resolveStageOverride(config, stage) {
|
|
4639
|
+
switch (stage) {
|
|
4640
|
+
case "extraction":
|
|
4641
|
+
return config?.extractionModel;
|
|
4642
|
+
case "dedup":
|
|
4643
|
+
return config?.dedupModel;
|
|
4644
|
+
case "episode":
|
|
4645
|
+
return config?.episodeModel;
|
|
4646
|
+
case "claim":
|
|
4647
|
+
return config?.claimExtraction?.model ?? config?.extractionModel;
|
|
4648
|
+
case "cross_encoder":
|
|
4649
|
+
return config?.crossEncoderModel;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
function resolveStageDefaultProvider(config, stage) {
|
|
4653
|
+
if (stage === "cross_encoder") {
|
|
4654
|
+
const topLevel = normalizeOptionalString6(config?.provider);
|
|
4655
|
+
if (!topLevel || topLevel === "openai-codex") {
|
|
4656
|
+
return "openai";
|
|
4657
|
+
}
|
|
4658
|
+
return topLevel;
|
|
4659
|
+
}
|
|
4660
|
+
return normalizeOptionalString6(config?.provider) ?? "openai";
|
|
4661
|
+
}
|
|
4662
|
+
function resolveStageDefaultModel(config, stage) {
|
|
4663
|
+
if (stage === "cross_encoder") {
|
|
4664
|
+
return defaultModelForStage(stage);
|
|
4665
|
+
}
|
|
4666
|
+
return normalizeOptionalString6(config?.model) ?? defaultModelForStage(stage);
|
|
4667
|
+
}
|
|
4668
|
+
function resolveLlmCredentials(config, provider, env = process.env) {
|
|
4669
|
+
const normalizedProvider = normalizeOptionalString6(provider);
|
|
4670
|
+
if (!normalizedProvider) {
|
|
4671
|
+
throw new Error("Provider is required to resolve LLM credentials.");
|
|
4672
|
+
}
|
|
4673
|
+
const auth = normalizeAuthMethod(config?.auth);
|
|
4674
|
+
if (auth && authMethodToProvider(auth) === normalizedProvider) {
|
|
4675
|
+
return resolveAuthCredentials({
|
|
4676
|
+
auth,
|
|
4677
|
+
storedCredentials: config?.credentials,
|
|
4678
|
+
env
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4681
|
+
const fallback = resolveProviderCredentialCandidate(config, normalizedProvider, env);
|
|
4682
|
+
if (fallback) {
|
|
4683
|
+
return {
|
|
4684
|
+
apiKey: fallback.token,
|
|
4685
|
+
source: fallback.source
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4688
|
+
if (normalizedProvider === "openai-codex") {
|
|
4689
|
+
throw new Error("No OpenAI subscription credential found. Run `codex auth` or configure `auth` as `openai-api-key`.");
|
|
4690
|
+
}
|
|
4691
|
+
const exampleEnv = normalizedProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
4692
|
+
throw new Error(`No credential found for provider "${normalizedProvider}". Set the appropriate auth method in config or provide ${exampleEnv}.`);
|
|
4693
|
+
}
|
|
4694
|
+
function resolveLlmApiKey(config, provider, env = process.env) {
|
|
4695
|
+
return resolveLlmCredentials(config, provider, env).apiKey;
|
|
4696
|
+
}
|
|
4697
|
+
function stripCodeFence(text) {
|
|
4698
|
+
const trimmed = text.trim();
|
|
4699
|
+
const match = /^```(?:json)?\s*([\s\S]+?)\s*```$/i.exec(trimmed);
|
|
4700
|
+
return match?.[1]?.trim() ?? trimmed;
|
|
4701
|
+
}
|
|
4702
|
+
function defaultModelForStage(stage) {
|
|
4703
|
+
switch (stage) {
|
|
4704
|
+
case "extraction":
|
|
4705
|
+
case "episode":
|
|
4706
|
+
case "claim":
|
|
4707
|
+
return "gpt-5.4-mini";
|
|
4708
|
+
case "dedup":
|
|
4709
|
+
case "cross_encoder":
|
|
4710
|
+
return "gpt-5.4-nano";
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
function normalizeOptionalString6(value) {
|
|
4714
|
+
const trimmed = value?.trim();
|
|
4715
|
+
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
4716
|
+
}
|
|
4717
|
+
function normalizeAuthMethod(value) {
|
|
4718
|
+
const normalized = normalizeOptionalString6(value);
|
|
4719
|
+
return normalized && isAgenrAuthMethod(normalized) ? normalized : void 0;
|
|
4720
|
+
}
|
|
4721
|
+
function safeReadJson(filePath) {
|
|
4722
|
+
try {
|
|
4723
|
+
const raw = fs3.readFileSync(filePath, "utf8");
|
|
4724
|
+
return JSON.parse(raw);
|
|
4725
|
+
} catch {
|
|
4726
|
+
return null;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
function resolveHomeDir(env) {
|
|
4730
|
+
const home = normalizeOptionalString6(env.HOME);
|
|
4731
|
+
return home ? resolveUserPath(home) : os2.homedir();
|
|
4732
|
+
}
|
|
4733
|
+
function resolveCodexHome(env) {
|
|
4734
|
+
const configured = normalizeOptionalString6(env.CODEX_HOME) ?? "~/.codex";
|
|
4735
|
+
const resolved = resolveUserPath(configured);
|
|
4736
|
+
try {
|
|
4737
|
+
return fs3.realpathSync.native(resolved);
|
|
4738
|
+
} catch {
|
|
4739
|
+
return resolved;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
function resolveUserPath(value) {
|
|
4743
|
+
const trimmed = value.trim();
|
|
4744
|
+
if (trimmed === "~") {
|
|
4745
|
+
return os2.homedir();
|
|
4746
|
+
}
|
|
4747
|
+
if (trimmed.startsWith("~/")) {
|
|
4748
|
+
return path3.join(os2.homedir(), trimmed.slice(2));
|
|
4749
|
+
}
|
|
4750
|
+
if (trimmed.startsWith("~\\")) {
|
|
4751
|
+
return path3.join(os2.homedir(), trimmed.slice(2));
|
|
4752
|
+
}
|
|
4753
|
+
return path3.resolve(trimmed);
|
|
4754
|
+
}
|
|
4755
|
+
function parseCodexFromFile(env) {
|
|
4756
|
+
const authPath = path3.join(resolveCodexHome(env), "auth.json");
|
|
4757
|
+
const parsed = safeReadJson(authPath);
|
|
4758
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4759
|
+
return null;
|
|
4760
|
+
}
|
|
4761
|
+
const record = parsed;
|
|
4762
|
+
const tokens = record.tokens;
|
|
4763
|
+
const accessToken = tokens?.access_token;
|
|
4764
|
+
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
4765
|
+
return null;
|
|
4766
|
+
}
|
|
4767
|
+
return {
|
|
4768
|
+
token: accessToken.trim(),
|
|
4769
|
+
source: `file:${authPath}`
|
|
4770
|
+
};
|
|
4771
|
+
}
|
|
4772
|
+
function resolveCodexKeychainAccount(env) {
|
|
4773
|
+
const hash = createHash2("sha256").update(resolveCodexHome(env)).digest("hex");
|
|
4774
|
+
return `cli|${hash.slice(0, 16)}`;
|
|
4775
|
+
}
|
|
4776
|
+
function parseCodexFromKeychain(env) {
|
|
4777
|
+
if (process.platform !== "darwin") {
|
|
4778
|
+
return null;
|
|
4779
|
+
}
|
|
4780
|
+
try {
|
|
4781
|
+
const account = resolveCodexKeychainAccount(env);
|
|
4782
|
+
const { execSync } = require2("node:child_process");
|
|
4783
|
+
const raw = execSync(`security find-generic-password -s "Codex Auth" -a "${account}" -w`, {
|
|
4784
|
+
encoding: "utf8",
|
|
4785
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4786
|
+
timeout: 5e3
|
|
4787
|
+
}).trim();
|
|
4788
|
+
const parsed = JSON.parse(raw);
|
|
4789
|
+
const tokens = parsed.tokens;
|
|
4790
|
+
const accessToken = tokens?.access_token;
|
|
4791
|
+
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
4792
|
+
return null;
|
|
4793
|
+
}
|
|
4794
|
+
return {
|
|
4795
|
+
token: accessToken.trim(),
|
|
4796
|
+
source: "keychain:Codex Auth"
|
|
4797
|
+
};
|
|
4798
|
+
} catch {
|
|
4799
|
+
return null;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
function parseClaudeCredentialRecord(parsed, source) {
|
|
4803
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4804
|
+
return null;
|
|
4805
|
+
}
|
|
4806
|
+
const record = parsed;
|
|
4807
|
+
const claudeOauth = record.claudeAiOauth;
|
|
4808
|
+
const accessToken = claudeOauth?.accessToken;
|
|
4809
|
+
if (typeof accessToken !== "string" || !accessToken.trim()) {
|
|
4810
|
+
return null;
|
|
4811
|
+
}
|
|
4812
|
+
return {
|
|
4813
|
+
token: accessToken.trim(),
|
|
4814
|
+
source
|
|
4815
|
+
};
|
|
4816
|
+
}
|
|
4817
|
+
function parseClaudeFromFiles(env) {
|
|
4818
|
+
const homeDir = resolveHomeDir(env);
|
|
4819
|
+
const candidates = [path3.join(homeDir, ".claude", ".credentials.json"), path3.join(homeDir, ".claude", "credentials.json")];
|
|
4820
|
+
for (const candidate of candidates) {
|
|
4821
|
+
const parsed = safeReadJson(candidate);
|
|
4822
|
+
const resolved = parseClaudeCredentialRecord(parsed, `file:${candidate}`);
|
|
4823
|
+
if (resolved) {
|
|
4824
|
+
return resolved;
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
return null;
|
|
4828
|
+
}
|
|
4829
|
+
function parseClaudeFromKeychain() {
|
|
4830
|
+
if (process.platform !== "darwin") {
|
|
4831
|
+
return null;
|
|
4832
|
+
}
|
|
4833
|
+
try {
|
|
4834
|
+
const { execSync } = require2("node:child_process");
|
|
4835
|
+
const raw = execSync('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
4836
|
+
encoding: "utf8",
|
|
4837
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4838
|
+
timeout: 5e3
|
|
4839
|
+
}).trim();
|
|
4840
|
+
return parseClaudeCredentialRecord(JSON.parse(raw), "keychain:Claude Code-credentials");
|
|
4841
|
+
} catch {
|
|
4842
|
+
return null;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
function candidateFromToken(token, source) {
|
|
4846
|
+
const normalized = normalizeOptionalString6(token);
|
|
4847
|
+
if (!normalized) {
|
|
4848
|
+
return null;
|
|
4849
|
+
}
|
|
4850
|
+
return {
|
|
4851
|
+
token: normalized,
|
|
4852
|
+
source
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
function resolveOpenAIApiKeyCandidate(storedCredentials, env) {
|
|
4856
|
+
return candidateFromToken(env.OPENAI_API_KEY, "env:OPENAI_API_KEY") ?? candidateFromToken(storedCredentials?.openaiApiKey, "config:credentials.openaiApiKey");
|
|
4857
|
+
}
|
|
4858
|
+
function resolveAnthropicApiKeyCandidate(storedCredentials, env) {
|
|
4859
|
+
return candidateFromToken(env.ANTHROPIC_API_KEY, "env:ANTHROPIC_API_KEY") ?? candidateFromToken(storedCredentials?.anthropicApiKey, "config:credentials.anthropicApiKey");
|
|
4860
|
+
}
|
|
4861
|
+
function resolveAnthropicTokenCandidate(storedCredentials, env) {
|
|
4862
|
+
return candidateFromToken(env.ANTHROPIC_OAUTH_TOKEN, "env:ANTHROPIC_OAUTH_TOKEN") ?? candidateFromToken(storedCredentials?.anthropicOauthToken, "config:credentials.anthropicOauthToken");
|
|
4863
|
+
}
|
|
4864
|
+
function resolveAnthropicOauthCandidate(env) {
|
|
4865
|
+
return parseClaudeFromFiles(env) ?? parseClaudeFromKeychain();
|
|
4866
|
+
}
|
|
4867
|
+
function resolveOpenAiSubscriptionCandidate(env) {
|
|
4868
|
+
return parseCodexFromFile(env) ?? parseCodexFromKeychain(env);
|
|
4869
|
+
}
|
|
4870
|
+
function credentialSetupGuidance(auth) {
|
|
4871
|
+
switch (auth) {
|
|
4872
|
+
case "anthropic-oauth":
|
|
4873
|
+
return "Claude Code credentials not found. Install Claude Code CLI and sign in with `claude`.";
|
|
4874
|
+
case "anthropic-token":
|
|
4875
|
+
return "No Anthropic long-lived token found. Set ANTHROPIC_OAUTH_TOKEN or save credentials.anthropicOauthToken.";
|
|
4876
|
+
case "anthropic-api-key":
|
|
4877
|
+
return "No Anthropic API key found. Set ANTHROPIC_API_KEY or save credentials.anthropicApiKey.";
|
|
4878
|
+
case "openai-subscription":
|
|
4879
|
+
return "Codex CLI credentials not found or expired. Run `codex auth`.";
|
|
4880
|
+
case "openai-api-key":
|
|
4881
|
+
return "No OpenAI API key found. Set OPENAI_API_KEY or save credentials.openaiApiKey.";
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
function resolveCredentialCandidate(params) {
|
|
4885
|
+
const env = params.env ?? process.env;
|
|
4886
|
+
switch (params.auth) {
|
|
4887
|
+
case "anthropic-oauth":
|
|
4888
|
+
return resolveAnthropicOauthCandidate(env);
|
|
4889
|
+
case "anthropic-token":
|
|
4890
|
+
return resolveAnthropicTokenCandidate(params.storedCredentials, env);
|
|
4891
|
+
case "anthropic-api-key":
|
|
4892
|
+
return resolveAnthropicApiKeyCandidate(params.storedCredentials, env);
|
|
4893
|
+
case "openai-subscription":
|
|
4894
|
+
return resolveOpenAiSubscriptionCandidate(env);
|
|
4895
|
+
case "openai-api-key":
|
|
4896
|
+
return resolveOpenAIApiKeyCandidate(params.storedCredentials, env);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
function resolveProviderCredentialCandidate(config, provider, env) {
|
|
4900
|
+
if (provider === "openai") {
|
|
4901
|
+
return resolveOpenAIApiKeyCandidate(config?.credentials, env);
|
|
4902
|
+
}
|
|
4903
|
+
if (provider === "anthropic") {
|
|
4904
|
+
const auth = normalizeAuthMethod(config?.auth);
|
|
4905
|
+
if (auth && authMethodToProvider(auth) === "anthropic") {
|
|
4906
|
+
return resolveCredentialCandidate({
|
|
4907
|
+
auth,
|
|
4908
|
+
storedCredentials: config?.credentials,
|
|
4909
|
+
env
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
return resolveAnthropicApiKeyCandidate(config?.credentials, env);
|
|
4913
|
+
}
|
|
4914
|
+
const envApiKey = getEnvApiKey(provider) ?? getEnvApiKey(provider);
|
|
4915
|
+
return candidateFromToken(envApiKey, `env:${provider}`);
|
|
4916
|
+
}
|
|
4917
|
+
function createEmptyUsageStats() {
|
|
4918
|
+
return {
|
|
4919
|
+
calls: 0,
|
|
4920
|
+
inputTokens: 0,
|
|
4921
|
+
outputTokens: 0,
|
|
4922
|
+
cacheReadTokens: 0,
|
|
4923
|
+
cacheWriteTokens: 0,
|
|
4924
|
+
totalTokens: 0,
|
|
4925
|
+
totalCost: 0
|
|
4926
|
+
};
|
|
4927
|
+
}
|
|
4928
|
+
function accumulateUsage(target, usage) {
|
|
4929
|
+
target.calls += 1;
|
|
4930
|
+
target.inputTokens += usage.input;
|
|
4931
|
+
target.outputTokens += usage.output;
|
|
4932
|
+
target.cacheReadTokens += usage.cacheRead;
|
|
4933
|
+
target.cacheWriteTokens += usage.cacheWrite;
|
|
4934
|
+
target.totalTokens += usage.totalTokens;
|
|
4935
|
+
target.totalCost += usage.cost.total;
|
|
4936
|
+
}
|
|
4937
|
+
function extractText(response) {
|
|
4938
|
+
const blocks = [];
|
|
4939
|
+
for (const contentBlock of response.content) {
|
|
4940
|
+
if (contentBlock.type === "text") {
|
|
4941
|
+
blocks.push(contentBlock.text);
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
return blocks.join("");
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4947
|
+
// src/adapters/cross-encoder/openai-cross-encoder.ts
|
|
4948
|
+
var OPENAI_CHAT_COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions";
|
|
4949
|
+
var DEFAULT_MODEL = "gpt-5.4-nano";
|
|
4950
|
+
var DEFAULT_MAX_CONCURRENCY = 4;
|
|
4951
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
4952
|
+
var REQUEST_TIMEOUT_MS = 2e4;
|
|
4953
|
+
var SYSTEM_PROMPT = "You are an expert tasked with determining whether the passage is relevant to the query";
|
|
4954
|
+
function createOpenAICrossEncoder(options) {
|
|
4955
|
+
const apiKey = options.apiKey.trim();
|
|
4956
|
+
if (apiKey.length === 0) {
|
|
4957
|
+
throw new Error("OpenAI cross-encoder adapter requires a non-empty API key.");
|
|
4958
|
+
}
|
|
4959
|
+
const model = normalizeOptional(options.model) ?? DEFAULT_MODEL;
|
|
4960
|
+
const baseUrl = normalizeOptional(options.baseUrl) ?? OPENAI_CHAT_COMPLETIONS_URL;
|
|
4961
|
+
const maxConcurrency = clampPositiveInteger(options.maxConcurrency, DEFAULT_MAX_CONCURRENCY);
|
|
4962
|
+
const maxRetries = clampPositiveInteger(options.maxRetries, DEFAULT_MAX_RETRIES, { allowZero: true });
|
|
4963
|
+
const requestTimeoutMs = clampPositiveInteger(options.requestTimeoutMs, REQUEST_TIMEOUT_MS);
|
|
4964
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
4965
|
+
return {
|
|
4966
|
+
async rank(query, passages) {
|
|
4967
|
+
if (passages.length === 0) {
|
|
4968
|
+
return [];
|
|
4969
|
+
}
|
|
4970
|
+
const trimmedQuery = query.trim();
|
|
4971
|
+
if (trimmedQuery.length === 0) {
|
|
4972
|
+
return [];
|
|
4973
|
+
}
|
|
4974
|
+
const results = new Array(passages.length).fill(null);
|
|
4975
|
+
let nextIndex = 0;
|
|
4976
|
+
const workerCount = Math.max(1, Math.min(maxConcurrency, passages.length));
|
|
4977
|
+
await Promise.all(
|
|
4978
|
+
Array.from({ length: workerCount }, async () => {
|
|
4979
|
+
while (true) {
|
|
4980
|
+
const index = nextIndex;
|
|
4981
|
+
nextIndex += 1;
|
|
4982
|
+
if (index >= passages.length) {
|
|
4983
|
+
return;
|
|
4984
|
+
}
|
|
4985
|
+
const passage = passages[index];
|
|
4986
|
+
const score = await rankSinglePassage({
|
|
4987
|
+
apiKey,
|
|
4988
|
+
baseUrl,
|
|
4989
|
+
model,
|
|
4990
|
+
query: trimmedQuery,
|
|
4991
|
+
passage,
|
|
4992
|
+
fetchImpl,
|
|
4993
|
+
maxRetries,
|
|
4994
|
+
requestTimeoutMs
|
|
4995
|
+
});
|
|
4996
|
+
if (score !== null) {
|
|
4997
|
+
results[index] = { id: passage.id, score };
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
})
|
|
5001
|
+
);
|
|
5002
|
+
return results.filter((result) => result !== null);
|
|
5003
|
+
}
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
function resolveCrossEncoderApiKey(config) {
|
|
5007
|
+
const candidates = [config?.credentials?.openaiApiKey, process.env.OPENAI_API_KEY];
|
|
5008
|
+
for (const candidate of candidates) {
|
|
5009
|
+
const normalized = candidate?.trim();
|
|
5010
|
+
if (normalized && normalized.length > 0) {
|
|
5011
|
+
return normalized;
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
throw new Error("Cross-encoder API key is required. Set config.credentials.openaiApiKey or OPENAI_API_KEY.");
|
|
5015
|
+
}
|
|
5016
|
+
async function rankSinglePassage(params) {
|
|
5017
|
+
const body = JSON.stringify({
|
|
5018
|
+
model: params.model,
|
|
5019
|
+
temperature: 0,
|
|
5020
|
+
// gpt-5.4 chat completions reject `max_tokens=1` and may return an
|
|
5021
|
+
// empty completion when constrained below 4 completion tokens.
|
|
5022
|
+
max_completion_tokens: 4,
|
|
5023
|
+
logprobs: true,
|
|
5024
|
+
top_logprobs: 2,
|
|
5025
|
+
messages: [
|
|
5026
|
+
{
|
|
5027
|
+
role: "system",
|
|
5028
|
+
content: SYSTEM_PROMPT
|
|
5029
|
+
},
|
|
5030
|
+
{
|
|
5031
|
+
role: "user",
|
|
5032
|
+
content: buildUserPrompt(params.query, params.passage.text)
|
|
5033
|
+
}
|
|
5034
|
+
]
|
|
5035
|
+
});
|
|
5036
|
+
let attempt = 0;
|
|
5037
|
+
while (true) {
|
|
5038
|
+
const outcome = await performSingleRequest({
|
|
5039
|
+
...params,
|
|
5040
|
+
body
|
|
5041
|
+
});
|
|
5042
|
+
if (outcome.kind === "score") {
|
|
5043
|
+
return outcome.value;
|
|
5044
|
+
}
|
|
5045
|
+
if (outcome.kind === "fatal" || attempt >= params.maxRetries) {
|
|
5046
|
+
return null;
|
|
5047
|
+
}
|
|
5048
|
+
attempt += 1;
|
|
5049
|
+
await sleep2(backoffMs2(attempt));
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
async function performSingleRequest(params) {
|
|
5053
|
+
const controller = new AbortController();
|
|
5054
|
+
const timeout = setTimeout(() => controller.abort(), params.requestTimeoutMs);
|
|
5055
|
+
try {
|
|
5056
|
+
const response = await params.fetchImpl(params.baseUrl, {
|
|
5057
|
+
method: "POST",
|
|
5058
|
+
headers: {
|
|
5059
|
+
Authorization: `Bearer ${params.apiKey}`,
|
|
5060
|
+
"Content-Type": "application/json"
|
|
5061
|
+
},
|
|
5062
|
+
body: params.body,
|
|
5063
|
+
signal: controller.signal
|
|
5064
|
+
});
|
|
5065
|
+
const rawBody = await response.text();
|
|
5066
|
+
if (!response.ok) {
|
|
5067
|
+
return isRetryableStatus2(response.status) ? { kind: "retryable" } : { kind: "fatal" };
|
|
5068
|
+
}
|
|
5069
|
+
const score = parseRelevanceScore(rawBody);
|
|
5070
|
+
return score === null ? { kind: "fatal" } : { kind: "score", value: score };
|
|
5071
|
+
} catch {
|
|
5072
|
+
return { kind: "retryable" };
|
|
5073
|
+
} finally {
|
|
5074
|
+
clearTimeout(timeout);
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
function parseRelevanceScore(rawBody) {
|
|
5078
|
+
let parsed;
|
|
5079
|
+
try {
|
|
5080
|
+
parsed = JSON.parse(rawBody);
|
|
5081
|
+
} catch {
|
|
5082
|
+
return null;
|
|
5083
|
+
}
|
|
5084
|
+
const firstChoice = parsed.choices?.[0];
|
|
5085
|
+
const topLogprobs = firstChoice?.logprobs?.content?.[0]?.top_logprobs;
|
|
5086
|
+
if (!Array.isArray(topLogprobs) || topLogprobs.length === 0) {
|
|
5087
|
+
return null;
|
|
5088
|
+
}
|
|
5089
|
+
const top = topLogprobs[0];
|
|
5090
|
+
if (!top || typeof top.logprob !== "number" || !Number.isFinite(top.logprob) || typeof top.token !== "string") {
|
|
5091
|
+
return null;
|
|
5092
|
+
}
|
|
5093
|
+
const normalizedProb = Math.exp(top.logprob);
|
|
5094
|
+
const tokenFirstWord = top.token.trim().split(/\s+/)[0]?.toLowerCase();
|
|
5095
|
+
const score = tokenFirstWord === "true" ? normalizedProb : 1 - normalizedProb;
|
|
5096
|
+
if (!Number.isFinite(score)) {
|
|
5097
|
+
return null;
|
|
5098
|
+
}
|
|
5099
|
+
if (score <= 0) {
|
|
5100
|
+
return 0;
|
|
5101
|
+
}
|
|
5102
|
+
if (score >= 1) {
|
|
5103
|
+
return 1;
|
|
5104
|
+
}
|
|
5105
|
+
return score;
|
|
5106
|
+
}
|
|
5107
|
+
function buildUserPrompt(query, passage) {
|
|
5108
|
+
return `Respond with "True" if PASSAGE is relevant to QUERY and "False" otherwise.
|
|
5109
|
+
<PASSAGE>
|
|
5110
|
+
${passage}
|
|
5111
|
+
</PASSAGE>
|
|
5112
|
+
<QUERY>
|
|
5113
|
+
${query}
|
|
5114
|
+
</QUERY>`;
|
|
5115
|
+
}
|
|
5116
|
+
function isRetryableStatus2(status) {
|
|
5117
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500 && status < 600;
|
|
5118
|
+
}
|
|
5119
|
+
function clampPositiveInteger(value, fallback, opts) {
|
|
5120
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5121
|
+
return fallback;
|
|
5122
|
+
}
|
|
5123
|
+
const floored = Math.floor(value);
|
|
5124
|
+
if (floored < 0) {
|
|
5125
|
+
return fallback;
|
|
5126
|
+
}
|
|
5127
|
+
if (floored === 0) {
|
|
5128
|
+
return opts?.allowZero === true ? 0 : fallback;
|
|
5129
|
+
}
|
|
5130
|
+
return floored;
|
|
5131
|
+
}
|
|
5132
|
+
function normalizeOptional(value) {
|
|
5133
|
+
const trimmed = value?.trim();
|
|
5134
|
+
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
5135
|
+
}
|
|
5136
|
+
function backoffMs2(attempt) {
|
|
5137
|
+
return Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
5138
|
+
}
|
|
5139
|
+
async function sleep2(durationMs) {
|
|
5140
|
+
await new Promise((resolve) => {
|
|
5141
|
+
setTimeout(resolve, durationMs);
|
|
5142
|
+
});
|
|
5143
|
+
}
|
|
5144
|
+
|
|
4529
5145
|
// src/adapters/db/recall-adapter.ts
|
|
4530
5146
|
var RECALL_CANDIDATE_SELECT_COLUMNS = `
|
|
4531
5147
|
e.id,
|
|
@@ -4544,8 +5160,8 @@ var RECALL_CANDIDATE_SELECT_COLUMNS = `
|
|
|
4544
5160
|
e.created_at
|
|
4545
5161
|
`;
|
|
4546
5162
|
var FTS_TIERS2 = ["exact", "all_tokens", "any_tokens"];
|
|
4547
|
-
var
|
|
4548
|
-
var
|
|
5163
|
+
var NEIGHBORHOOD_DEFAULT_BUDGET_CAP = 40;
|
|
5164
|
+
var NEIGHBORHOOD_PER_SEED_BUDGET = 8;
|
|
4549
5165
|
function createRecallAdapter(executor, embeddingPort) {
|
|
4550
5166
|
return new LibsqlRecallAdapter(executor, embeddingPort);
|
|
4551
5167
|
}
|
|
@@ -4646,23 +5262,34 @@ var LibsqlRecallAdapter = class {
|
|
|
4646
5262
|
return Array.from(matches.values()).sort((left, right) => compareFtsCandidates(left, right)).slice(0, params.limit);
|
|
4647
5263
|
}
|
|
4648
5264
|
/**
|
|
4649
|
-
*
|
|
5265
|
+
* Expand a typed entry neighborhood around a seed set of candidate IDs.
|
|
4650
5266
|
*
|
|
4651
|
-
*
|
|
4652
|
-
*
|
|
4653
|
-
* a
|
|
5267
|
+
* Honors the requested `families` exactly. `supersession_chain` adds rows
|
|
5268
|
+
* that either supersede or are superseded by a seed. `claim_key_sibling`
|
|
5269
|
+
* adds rows sharing a claim key with any seed. `topic_family` adds rows
|
|
5270
|
+
* that share an exact subject with a seed and is the weakest fallback.
|
|
5271
|
+
* `includeRetired` is applied as a hard gate so the default ranking
|
|
5272
|
+
* profile never pulls retired rows into its candidate pool.
|
|
4654
5273
|
*/
|
|
4655
|
-
async
|
|
4656
|
-
const normalizedIds = normalizeStrings(
|
|
5274
|
+
async expandNeighborhood(request) {
|
|
5275
|
+
const normalizedIds = normalizeStrings(request.seedIds);
|
|
4657
5276
|
if (normalizedIds.length === 0) {
|
|
4658
5277
|
return [];
|
|
4659
5278
|
}
|
|
5279
|
+
const families = dedupeFamilies(request.families);
|
|
5280
|
+
if (families.length === 0) {
|
|
5281
|
+
return [];
|
|
5282
|
+
}
|
|
5283
|
+
const includeRetired = request.includeRetired === true;
|
|
5284
|
+
const budget = normalizeNeighborhoodBudget(request.budget, normalizedIds.length);
|
|
4660
5285
|
const placeholders = normalizedIds.map(() => "?").join(", ");
|
|
4661
|
-
const
|
|
5286
|
+
const retiredGate = includeRetired ? "" : "AND e.retired = 0";
|
|
5287
|
+
const priorityExpression = buildNeighborhoodPriorityExpression(families, includeRetired);
|
|
5288
|
+
const membershipExpression = buildNeighborhoodMembershipExpression(families);
|
|
4662
5289
|
const result = await this.executor.execute({
|
|
4663
5290
|
sql: `
|
|
4664
5291
|
WITH seed AS (
|
|
4665
|
-
SELECT id, subject, claim_key
|
|
5292
|
+
SELECT id, subject, claim_key, superseded_by
|
|
4666
5293
|
FROM entries
|
|
4667
5294
|
WHERE id IN (${placeholders})
|
|
4668
5295
|
),
|
|
@@ -4676,47 +5303,26 @@ var LibsqlRecallAdapter = class {
|
|
|
4676
5303
|
FROM seed
|
|
4677
5304
|
WHERE claim_key IS NOT NULL
|
|
4678
5305
|
),
|
|
4679
|
-
|
|
5306
|
+
seed_supersessions AS (
|
|
5307
|
+
SELECT DISTINCT superseded_by AS target_id
|
|
5308
|
+
FROM seed
|
|
5309
|
+
WHERE superseded_by IS NOT NULL
|
|
5310
|
+
),
|
|
5311
|
+
neighborhood AS (
|
|
4680
5312
|
SELECT
|
|
4681
5313
|
${RECALL_CANDIDATE_SELECT_COLUMNS},
|
|
4682
|
-
|
|
4683
|
-
WHEN e.superseded_by IN (SELECT id FROM seed) THEN 0
|
|
4684
|
-
WHEN e.claim_key IS NOT NULL
|
|
4685
|
-
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
4686
|
-
AND e.claim_key_status = 'trusted'
|
|
4687
|
-
AND (e.retired = 1 OR e.superseded_by IS NOT NULL) THEN 1
|
|
4688
|
-
WHEN e.claim_key IS NOT NULL
|
|
4689
|
-
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
4690
|
-
AND e.claim_key_status = 'trusted' THEN 2
|
|
4691
|
-
WHEN e.claim_key IS NOT NULL
|
|
4692
|
-
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
4693
|
-
AND (e.retired = 1 OR e.superseded_by IS NOT NULL) THEN 3
|
|
4694
|
-
WHEN e.claim_key IS NOT NULL
|
|
4695
|
-
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys) THEN 4
|
|
4696
|
-
WHEN e.retired = 1
|
|
4697
|
-
AND e.subject IN (SELECT subject FROM seed_subjects) THEN 5
|
|
4698
|
-
ELSE 6
|
|
4699
|
-
END AS lineage_priority
|
|
5314
|
+
${priorityExpression} AS family_priority
|
|
4700
5315
|
FROM entries AS e
|
|
4701
5316
|
WHERE e.id NOT IN (SELECT id FROM seed)
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
OR (
|
|
4705
|
-
e.claim_key IS NOT NULL
|
|
4706
|
-
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
4707
|
-
)
|
|
4708
|
-
OR (
|
|
4709
|
-
e.retired = 1
|
|
4710
|
-
AND e.subject IN (SELECT subject FROM seed_subjects)
|
|
4711
|
-
)
|
|
4712
|
-
)
|
|
5317
|
+
${retiredGate}
|
|
5318
|
+
AND (${membershipExpression})
|
|
4713
5319
|
)
|
|
4714
5320
|
SELECT *
|
|
4715
|
-
FROM
|
|
4716
|
-
ORDER BY
|
|
5321
|
+
FROM neighborhood
|
|
5322
|
+
ORDER BY family_priority ASC, created_at ASC, id ASC
|
|
4717
5323
|
LIMIT ?
|
|
4718
5324
|
`,
|
|
4719
|
-
args: [...normalizedIds,
|
|
5325
|
+
args: [...normalizedIds, budget]
|
|
4720
5326
|
});
|
|
4721
5327
|
return result.rows.map((row) => mapRecallCandidateRow(row));
|
|
4722
5328
|
}
|
|
@@ -4844,14 +5450,101 @@ function readOptionalClaimKeyStatus(row) {
|
|
|
4844
5450
|
}
|
|
4845
5451
|
return parsed;
|
|
4846
5452
|
}
|
|
4847
|
-
function
|
|
4848
|
-
|
|
5453
|
+
function normalizeNeighborhoodBudget(requestedBudget, seedCount) {
|
|
5454
|
+
const perSeedCap = seedCount * NEIGHBORHOOD_PER_SEED_BUDGET;
|
|
5455
|
+
const safeRequested = Number.isFinite(requestedBudget) && requestedBudget > 0 ? Math.floor(requestedBudget) : perSeedCap;
|
|
5456
|
+
return Math.max(1, Math.min(NEIGHBORHOOD_DEFAULT_BUDGET_CAP, perSeedCap, safeRequested));
|
|
5457
|
+
}
|
|
5458
|
+
function dedupeFamilies(families) {
|
|
5459
|
+
return Array.from(new Set(families));
|
|
5460
|
+
}
|
|
5461
|
+
function buildNeighborhoodPriorityExpression(families, includeRetired) {
|
|
5462
|
+
const branches = [];
|
|
5463
|
+
if (families.includes("supersession_chain")) {
|
|
5464
|
+
branches.push(`WHEN e.superseded_by IN (SELECT id FROM seed) THEN 0`);
|
|
5465
|
+
branches.push(`WHEN e.id IN (SELECT target_id FROM seed_supersessions) THEN 1`);
|
|
5466
|
+
}
|
|
5467
|
+
if (families.includes("claim_key_sibling")) {
|
|
5468
|
+
const retiredOrReplacedGuard = includeRetired ? "(e.retired = 1 OR e.superseded_by IS NOT NULL)" : "e.superseded_by IS NOT NULL";
|
|
5469
|
+
branches.push(
|
|
5470
|
+
`WHEN e.claim_key IS NOT NULL
|
|
5471
|
+
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
5472
|
+
AND e.claim_key_status = 'trusted'
|
|
5473
|
+
AND ${retiredOrReplacedGuard} THEN 2`
|
|
5474
|
+
);
|
|
5475
|
+
branches.push(
|
|
5476
|
+
`WHEN e.claim_key IS NOT NULL
|
|
5477
|
+
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
5478
|
+
AND e.claim_key_status = 'trusted' THEN 3`
|
|
5479
|
+
);
|
|
5480
|
+
branches.push(
|
|
5481
|
+
`WHEN e.claim_key IS NOT NULL
|
|
5482
|
+
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys)
|
|
5483
|
+
AND ${retiredOrReplacedGuard} THEN 4`
|
|
5484
|
+
);
|
|
5485
|
+
branches.push(
|
|
5486
|
+
`WHEN e.claim_key IS NOT NULL
|
|
5487
|
+
AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys) THEN 5`
|
|
5488
|
+
);
|
|
5489
|
+
}
|
|
5490
|
+
if (families.includes("topic_family")) {
|
|
5491
|
+
if (includeRetired) {
|
|
5492
|
+
branches.push(`WHEN e.retired = 1 AND e.subject IN (SELECT subject FROM seed_subjects) THEN 6`);
|
|
5493
|
+
} else {
|
|
5494
|
+
branches.push(`WHEN e.subject IN (SELECT subject FROM seed_subjects) THEN 6`);
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
return `CASE ${branches.join("\n ")} ELSE 9 END`;
|
|
5498
|
+
}
|
|
5499
|
+
function buildNeighborhoodMembershipExpression(families) {
|
|
5500
|
+
const clauses = [];
|
|
5501
|
+
if (families.includes("supersession_chain")) {
|
|
5502
|
+
clauses.push(`e.superseded_by IN (SELECT id FROM seed)`);
|
|
5503
|
+
clauses.push(`e.id IN (SELECT target_id FROM seed_supersessions)`);
|
|
5504
|
+
}
|
|
5505
|
+
if (families.includes("claim_key_sibling")) {
|
|
5506
|
+
clauses.push(`(e.claim_key IS NOT NULL AND e.claim_key IN (SELECT claim_key FROM seed_claim_keys))`);
|
|
5507
|
+
}
|
|
5508
|
+
if (families.includes("topic_family")) {
|
|
5509
|
+
clauses.push(`e.subject IN (SELECT subject FROM seed_subjects)`);
|
|
5510
|
+
}
|
|
5511
|
+
return clauses.length === 0 ? "0" : clauses.join("\n OR ");
|
|
4849
5512
|
}
|
|
4850
5513
|
function wrapVectorError(error) {
|
|
4851
5514
|
const message = error instanceof Error ? error.message : String(error);
|
|
4852
5515
|
return new Error(`Vector search is unavailable: ${message}`);
|
|
4853
5516
|
}
|
|
4854
5517
|
|
|
5518
|
+
// src/app/evals/recall/attach-cross-encoder.ts
|
|
5519
|
+
function attachCrossEncoderPort(ports, crossEncoder) {
|
|
5520
|
+
if (!crossEncoder) {
|
|
5521
|
+
return ports;
|
|
5522
|
+
}
|
|
5523
|
+
return {
|
|
5524
|
+
async embed(text) {
|
|
5525
|
+
return ports.embed(text);
|
|
5526
|
+
},
|
|
5527
|
+
async vectorSearch(params) {
|
|
5528
|
+
return ports.vectorSearch(params);
|
|
5529
|
+
},
|
|
5530
|
+
async ftsSearch(params) {
|
|
5531
|
+
return ports.ftsSearch(params);
|
|
5532
|
+
},
|
|
5533
|
+
...ports.expandNeighborhood ? {
|
|
5534
|
+
async expandNeighborhood(request) {
|
|
5535
|
+
return ports.expandNeighborhood(request);
|
|
5536
|
+
}
|
|
5537
|
+
} : {},
|
|
5538
|
+
crossEncoder,
|
|
5539
|
+
async hydrateEntries(ids) {
|
|
5540
|
+
return ports.hydrateEntries(ids);
|
|
5541
|
+
},
|
|
5542
|
+
async recordRecallEvents(params) {
|
|
5543
|
+
return ports.recordRecallEvents(params);
|
|
5544
|
+
}
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
|
|
4855
5548
|
// src/app/recall/claim-centric.ts
|
|
4856
5549
|
function projectClaimCentricRecallEntries(entries, options = {}) {
|
|
4857
5550
|
const families = /* @__PURE__ */ new Map();
|
|
@@ -4878,7 +5571,7 @@ function flattenClaimCentricRecallFamilies(families) {
|
|
|
4878
5571
|
}
|
|
4879
5572
|
function projectClaimCentricRecallEntry(recall2, options = {}) {
|
|
4880
5573
|
const entry = recall2.entry;
|
|
4881
|
-
const claimKey =
|
|
5574
|
+
const claimKey = normalizeOptionalString7(entry.claim_key);
|
|
4882
5575
|
const familyKey = claimKey ?? `entry:${entry.id}`;
|
|
4883
5576
|
const slotPolicy = resolveClaimSlotPolicy(claimKey, options.slotPolicyConfig).policy;
|
|
4884
5577
|
const asOfResolution = buildAsOfResolution(recall2, options.asOf);
|
|
@@ -4904,38 +5597,38 @@ function resolveMemoryState(recall2, asOfResolution) {
|
|
|
4904
5597
|
if (asOfResolution.relation === "active") {
|
|
4905
5598
|
return "current";
|
|
4906
5599
|
}
|
|
4907
|
-
return
|
|
5600
|
+
return normalizeOptionalString7(entry.superseded_by) ? "superseded" : "historical";
|
|
4908
5601
|
}
|
|
4909
5602
|
if (asOfResolution.relation === "observed_after" || asOfResolution.relation === "created_after") {
|
|
4910
5603
|
return "historical";
|
|
4911
5604
|
}
|
|
4912
|
-
if (
|
|
5605
|
+
if (normalizeOptionalString7(entry.superseded_by)) {
|
|
4913
5606
|
return "superseded";
|
|
4914
5607
|
}
|
|
4915
|
-
if (entry.retired ||
|
|
5608
|
+
if (entry.retired || normalizeOptionalString7(entry.valid_to)) {
|
|
4916
5609
|
return "historical";
|
|
4917
5610
|
}
|
|
4918
5611
|
return "current";
|
|
4919
5612
|
}
|
|
4920
|
-
if (
|
|
5613
|
+
if (normalizeOptionalString7(entry.superseded_by)) {
|
|
4921
5614
|
return "superseded";
|
|
4922
5615
|
}
|
|
4923
|
-
if (entry.retired ||
|
|
5616
|
+
if (entry.retired || normalizeOptionalString7(entry.valid_to)) {
|
|
4924
5617
|
return "historical";
|
|
4925
5618
|
}
|
|
4926
5619
|
return "current";
|
|
4927
5620
|
}
|
|
4928
5621
|
function resolveClaimStatus(recall2) {
|
|
4929
5622
|
const entry = recall2.entry;
|
|
4930
|
-
if (!
|
|
5623
|
+
if (!normalizeOptionalString7(entry.claim_key)) {
|
|
4931
5624
|
return "no_key";
|
|
4932
5625
|
}
|
|
4933
5626
|
return entry.claim_key_status ?? "legacy";
|
|
4934
5627
|
}
|
|
4935
5628
|
function buildFreshness(recall2, memoryState, asOfResolution) {
|
|
4936
5629
|
const entry = recall2.entry;
|
|
4937
|
-
const validFrom =
|
|
4938
|
-
const validTo =
|
|
5630
|
+
const validFrom = normalizeOptionalString7(entry.valid_from);
|
|
5631
|
+
const validTo = normalizeOptionalString7(entry.valid_to);
|
|
4939
5632
|
const createdAt = entry.created_at;
|
|
4940
5633
|
const labelParts = [`created ${createdAt}`];
|
|
4941
5634
|
if (validFrom || validTo) {
|
|
@@ -4963,12 +5656,12 @@ function buildFreshness(recall2, memoryState, asOfResolution) {
|
|
|
4963
5656
|
function buildProvenance(recall2) {
|
|
4964
5657
|
const entry = recall2.entry;
|
|
4965
5658
|
return {
|
|
4966
|
-
...
|
|
4967
|
-
...
|
|
4968
|
-
...
|
|
4969
|
-
...
|
|
4970
|
-
...
|
|
4971
|
-
...
|
|
5659
|
+
...normalizeOptionalString7(entry.superseded_by) ? { supersededById: entry.superseded_by } : {},
|
|
5660
|
+
...normalizeOptionalString7(entry.supersession_kind) ? { supersessionKind: entry.supersession_kind } : {},
|
|
5661
|
+
...normalizeOptionalString7(entry.supersession_reason) ? { supersessionReason: entry.supersession_reason } : {},
|
|
5662
|
+
...normalizeOptionalString7(entry.claim_support_source_kind) ? { supportSourceKind: entry.claim_support_source_kind } : {},
|
|
5663
|
+
...normalizeOptionalString7(entry.claim_support_locator) ? { supportLocator: entry.claim_support_locator } : {},
|
|
5664
|
+
...normalizeOptionalString7(entry.claim_support_observed_at) ? { supportObservedAt: entry.claim_support_observed_at } : {},
|
|
4972
5665
|
...entry.claim_support_mode ? { supportMode: entry.claim_support_mode } : {}
|
|
4973
5666
|
};
|
|
4974
5667
|
}
|
|
@@ -5004,12 +5697,12 @@ function buildWhySurfaced(recall2, asOfResolution) {
|
|
|
5004
5697
|
function formatScore(value) {
|
|
5005
5698
|
return value.toFixed(2);
|
|
5006
5699
|
}
|
|
5007
|
-
function
|
|
5700
|
+
function normalizeOptionalString7(value) {
|
|
5008
5701
|
const normalized = value?.trim();
|
|
5009
5702
|
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
5010
5703
|
}
|
|
5011
5704
|
function buildAsOfResolution(recall2, asOf) {
|
|
5012
|
-
const normalizedAsOf =
|
|
5705
|
+
const normalizedAsOf = normalizeOptionalString7(asOf);
|
|
5013
5706
|
if (!normalizedAsOf) {
|
|
5014
5707
|
return void 0;
|
|
5015
5708
|
}
|
|
@@ -5073,12 +5766,296 @@ function formatAsOfRelation(relation) {
|
|
|
5073
5766
|
}
|
|
5074
5767
|
}
|
|
5075
5768
|
function parseTimestamp(value) {
|
|
5076
|
-
const normalized =
|
|
5769
|
+
const normalized = normalizeOptionalString7(value);
|
|
5077
5770
|
if (!normalized) {
|
|
5078
5771
|
return void 0;
|
|
5079
5772
|
}
|
|
5080
|
-
const parsed = new Date(normalized);
|
|
5081
|
-
return Number.isFinite(parsed.getTime()) ? parsed : void 0;
|
|
5773
|
+
const parsed = new Date(normalized);
|
|
5774
|
+
return Number.isFinite(parsed.getTime()) ? parsed : void 0;
|
|
5775
|
+
}
|
|
5776
|
+
|
|
5777
|
+
// src/app/procedures/recall/service.ts
|
|
5778
|
+
var DEFAULT_LIMIT = 5;
|
|
5779
|
+
var DEFAULT_CANONICAL_THRESHOLD = 0.55;
|
|
5780
|
+
var DEFAULT_CANONICAL_MARGIN = 0.08;
|
|
5781
|
+
var LEXICAL_CANDIDATE_MULTIPLIER = 3;
|
|
5782
|
+
var VECTOR_CANDIDATE_MULTIPLIER = 4;
|
|
5783
|
+
var VECTOR_ONLY_CANONICAL_FLOOR = 0.6;
|
|
5784
|
+
var LEXICAL_ONLY_NOTICE = "Semantic procedure search unavailable - using lexical-only procedure ranking.";
|
|
5785
|
+
var LEXICAL_ONLY_FALLBACK_NOTICE = "Semantic procedure search failed during procedure recall - using lexical-only procedure ranking.";
|
|
5786
|
+
async function runProcedureRecall(input, deps) {
|
|
5787
|
+
const text = input.text.trim();
|
|
5788
|
+
const limit = normalizeLimit(input.limit);
|
|
5789
|
+
if (text.length === 0 || limit === 0) {
|
|
5790
|
+
return {
|
|
5791
|
+
candidates: [],
|
|
5792
|
+
notices: []
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
5795
|
+
const lexicalLimit = limit * LEXICAL_CANDIDATE_MULTIPLIER;
|
|
5796
|
+
const vectorLimit = limit * VECTOR_CANDIDATE_MULTIPLIER;
|
|
5797
|
+
const notices = [];
|
|
5798
|
+
const lexicalMatches = await deps.db.procedureFtsSearch({
|
|
5799
|
+
text,
|
|
5800
|
+
limit: lexicalLimit
|
|
5801
|
+
});
|
|
5802
|
+
const queryEmbedding = await maybeEmbedQuery(text, deps.embedQuery, notices);
|
|
5803
|
+
const vectorMatches = queryEmbedding.length > 0 ? await deps.db.procedureVectorSearch({
|
|
5804
|
+
embedding: queryEmbedding,
|
|
5805
|
+
limit: vectorLimit
|
|
5806
|
+
}).catch(() => {
|
|
5807
|
+
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5808
|
+
return [];
|
|
5809
|
+
}) : [];
|
|
5810
|
+
const diversified = applyProcedureMmrDiversification(rankProcedureCandidates(text, lexicalMatches, vectorMatches), queryEmbedding, input.mmr);
|
|
5811
|
+
const reranked = await applyProcedureCrossEncoderRerank(diversified, text, input.crossEncoder);
|
|
5812
|
+
const ranked = reranked.slice(0, limit);
|
|
5813
|
+
const canonicalProcedure = selectCanonicalProcedure(ranked, input.threshold);
|
|
5814
|
+
return {
|
|
5815
|
+
...canonicalProcedure ? { canonicalProcedure } : {},
|
|
5816
|
+
candidates: ranked,
|
|
5817
|
+
notices: dedupePreservingOrder(notices)
|
|
5818
|
+
};
|
|
5819
|
+
}
|
|
5820
|
+
async function applyProcedureCrossEncoderRerank(candidates, query, options) {
|
|
5821
|
+
if (!options || !options.enabled || candidates.length === 0) {
|
|
5822
|
+
return candidates;
|
|
5823
|
+
}
|
|
5824
|
+
const rerank = await applyCrossEncoderRerank({
|
|
5825
|
+
query,
|
|
5826
|
+
candidates: candidates.map((candidate) => ({
|
|
5827
|
+
id: candidate.procedure.id,
|
|
5828
|
+
text: buildProcedureCrossEncoderText(candidate.procedure),
|
|
5829
|
+
score: candidate.score,
|
|
5830
|
+
candidate
|
|
5831
|
+
})),
|
|
5832
|
+
port: options.port,
|
|
5833
|
+
topK: options.topK ?? DEFAULT_CROSS_ENCODER_TOP_K,
|
|
5834
|
+
alpha: options.alpha ?? DEFAULT_CROSS_ENCODER_ALPHA
|
|
5835
|
+
});
|
|
5836
|
+
return rerank.candidates.map((entry) => {
|
|
5837
|
+
const base = entry.candidate;
|
|
5838
|
+
const nextScore = entry.score;
|
|
5839
|
+
if (typeof entry.crossEncoderScore !== "number" && nextScore === base.score) {
|
|
5840
|
+
return base;
|
|
5841
|
+
}
|
|
5842
|
+
return {
|
|
5843
|
+
...base,
|
|
5844
|
+
score: nextScore,
|
|
5845
|
+
scores: {
|
|
5846
|
+
...base.scores,
|
|
5847
|
+
relevance: nextScore,
|
|
5848
|
+
rrf: nextScore,
|
|
5849
|
+
...typeof entry.crossEncoderScore === "number" ? { crossEncoder: entry.crossEncoderScore } : {}
|
|
5850
|
+
}
|
|
5851
|
+
};
|
|
5852
|
+
});
|
|
5853
|
+
}
|
|
5854
|
+
function buildProcedureCrossEncoderText(procedure) {
|
|
5855
|
+
const title = procedure.title?.trim() ?? "";
|
|
5856
|
+
const recallText = procedure.recall_text?.trim() ?? "";
|
|
5857
|
+
if (title.length === 0) {
|
|
5858
|
+
return recallText;
|
|
5859
|
+
}
|
|
5860
|
+
if (recallText.length === 0) {
|
|
5861
|
+
return title;
|
|
5862
|
+
}
|
|
5863
|
+
return `${title}
|
|
5864
|
+
|
|
5865
|
+
${recallText}`;
|
|
5866
|
+
}
|
|
5867
|
+
async function maybeEmbedQuery(text, embedQuery, notices) {
|
|
5868
|
+
if (!embedQuery) {
|
|
5869
|
+
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5870
|
+
return [];
|
|
5871
|
+
}
|
|
5872
|
+
try {
|
|
5873
|
+
const embedding = await embedQuery(text);
|
|
5874
|
+
if (embedding.length === 0) {
|
|
5875
|
+
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5876
|
+
return [];
|
|
5877
|
+
}
|
|
5878
|
+
return embedding;
|
|
5879
|
+
} catch {
|
|
5880
|
+
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5881
|
+
return [];
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
function rankProcedureCandidates(query, lexicalMatches, vectorMatches) {
|
|
5885
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5886
|
+
for (const match of lexicalMatches) {
|
|
5887
|
+
const lexical = computeProcedureLexicalScore(query, match.procedure);
|
|
5888
|
+
merged.set(match.procedure.id, {
|
|
5889
|
+
procedure: match.procedure,
|
|
5890
|
+
lexical,
|
|
5891
|
+
vector: 0
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
for (const match of vectorMatches) {
|
|
5895
|
+
const existing = merged.get(match.procedure.id);
|
|
5896
|
+
merged.set(match.procedure.id, {
|
|
5897
|
+
procedure: match.procedure,
|
|
5898
|
+
lexical: existing?.lexical ?? computeProcedureLexicalScore(query, match.procedure),
|
|
5899
|
+
vector: Math.max(existing?.vector ?? 0, match.vectorSim)
|
|
5900
|
+
});
|
|
5901
|
+
}
|
|
5902
|
+
const lexicalRanks = rankByDescending(merged, (signals) => signals.lexical);
|
|
5903
|
+
const vectorRanks = rankByDescending(merged, (signals) => signals.vector);
|
|
5904
|
+
const relevanceByProcedureId = rrfFuseVectorLexical(vectorRanks, lexicalRanks);
|
|
5905
|
+
const ranked = Array.from(merged.values()).map((candidate) => {
|
|
5906
|
+
const relevance = relevanceByProcedureId.get(candidate.procedure.id) ?? 0;
|
|
5907
|
+
return {
|
|
5908
|
+
procedure: candidate.procedure,
|
|
5909
|
+
score: relevance,
|
|
5910
|
+
scores: {
|
|
5911
|
+
relevance,
|
|
5912
|
+
rrf: relevance,
|
|
5913
|
+
lexical: candidate.lexical,
|
|
5914
|
+
vector: candidate.vector
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
}).filter((candidate) => candidate.score > 0);
|
|
5918
|
+
return applySeededProcedureRerank(ranked).sort(compareProcedureCandidates);
|
|
5919
|
+
}
|
|
5920
|
+
function applyProcedureMmrDiversification(candidates, queryEmbedding, options) {
|
|
5921
|
+
if (!options || !options.enabled || candidates.length < 2 || queryEmbedding.length === 0) {
|
|
5922
|
+
return candidates;
|
|
5923
|
+
}
|
|
5924
|
+
const reorder = maximalMarginalRelevance({
|
|
5925
|
+
queryVector: queryEmbedding,
|
|
5926
|
+
candidates: candidates.map((candidate) => ({
|
|
5927
|
+
id: candidate.procedure.id,
|
|
5928
|
+
relevance: candidate.score,
|
|
5929
|
+
...candidate.procedure.embedding ? { embedding: candidate.procedure.embedding } : {}
|
|
5930
|
+
})),
|
|
5931
|
+
lambda: resolveProcedureMmrLambda(options.lambda),
|
|
5932
|
+
...typeof options.minPoolSize === "number" ? { minPoolSize: options.minPoolSize } : {}
|
|
5933
|
+
});
|
|
5934
|
+
if (!reorder.applied) {
|
|
5935
|
+
return candidates;
|
|
5936
|
+
}
|
|
5937
|
+
const candidatesById = new Map(candidates.map((candidate) => [candidate.procedure.id, candidate]));
|
|
5938
|
+
return reorder.orderedIds.flatMap((id) => {
|
|
5939
|
+
const candidate = candidatesById.get(id);
|
|
5940
|
+
return candidate ? [candidate] : [];
|
|
5941
|
+
});
|
|
5942
|
+
}
|
|
5943
|
+
function resolveProcedureMmrLambda(value) {
|
|
5944
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5945
|
+
return DEFAULT_MMR_LAMBDA;
|
|
5946
|
+
}
|
|
5947
|
+
return Math.max(0, Math.min(1, value));
|
|
5948
|
+
}
|
|
5949
|
+
function applySeededProcedureRerank(candidates) {
|
|
5950
|
+
if (candidates.length === 0) {
|
|
5951
|
+
return candidates;
|
|
5952
|
+
}
|
|
5953
|
+
const seeds = selectStrongSeeds(
|
|
5954
|
+
candidates.map((candidate) => ({ id: candidate.procedure.id, score: candidate.score, procedure: candidate.procedure })),
|
|
5955
|
+
{
|
|
5956
|
+
topN: DEFAULT_STRONG_SEED_TOP_N,
|
|
5957
|
+
scoreGapFloor: DEFAULT_STRONG_SEED_SCORE_GAP
|
|
5958
|
+
}
|
|
5959
|
+
);
|
|
5960
|
+
if (seeds.length === 0) {
|
|
5961
|
+
return candidates;
|
|
5962
|
+
}
|
|
5963
|
+
const payloads = candidates.map((candidate) => ({
|
|
5964
|
+
id: candidate.procedure.id,
|
|
5965
|
+
score: candidate.score,
|
|
5966
|
+
procedure: candidate.procedure
|
|
5967
|
+
}));
|
|
5968
|
+
const reranked = seededRerank(payloads, seeds, (candidate, seed) => sharesProcedureLineage(candidate.procedure, seed.procedure), {
|
|
5969
|
+
weight: DEFAULT_SEEDED_RERANK_WEIGHT
|
|
5970
|
+
});
|
|
5971
|
+
const scoreById = new Map(reranked.candidates.map((candidate) => [candidate.id, candidate.score]));
|
|
5972
|
+
return candidates.map((candidate) => {
|
|
5973
|
+
const nextScore = scoreById.get(candidate.procedure.id);
|
|
5974
|
+
if (nextScore === void 0 || nextScore === candidate.score) {
|
|
5975
|
+
return candidate;
|
|
5976
|
+
}
|
|
5977
|
+
return {
|
|
5978
|
+
...candidate,
|
|
5979
|
+
score: nextScore,
|
|
5980
|
+
scores: {
|
|
5981
|
+
...candidate.scores,
|
|
5982
|
+
relevance: nextScore,
|
|
5983
|
+
rrf: nextScore
|
|
5984
|
+
}
|
|
5985
|
+
};
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
function computeProcedureLexicalScore(query, procedure) {
|
|
5989
|
+
return computeLexicalScore(query, procedure.title, procedure.recall_text);
|
|
5990
|
+
}
|
|
5991
|
+
function rankByDescending(merged, signalOf) {
|
|
5992
|
+
return Array.from(merged.values()).filter((signals) => signalOf(signals) > 0).sort((left, right) => {
|
|
5993
|
+
const delta = signalOf(right) - signalOf(left);
|
|
5994
|
+
if (delta !== 0) {
|
|
5995
|
+
return delta;
|
|
5996
|
+
}
|
|
5997
|
+
return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
|
|
5998
|
+
}).map((signals) => signals.procedure.id);
|
|
5999
|
+
}
|
|
6000
|
+
function selectCanonicalProcedure(ranked, threshold) {
|
|
6001
|
+
const leader = ranked[0];
|
|
6002
|
+
if (!leader) {
|
|
6003
|
+
return void 0;
|
|
6004
|
+
}
|
|
6005
|
+
const minimumScore = normalizeThreshold(threshold);
|
|
6006
|
+
if (leader.score < minimumScore) {
|
|
6007
|
+
return void 0;
|
|
6008
|
+
}
|
|
6009
|
+
if (leader.scores.lexical === 0 && leader.scores.vector < VECTOR_ONLY_CANONICAL_FLOOR) {
|
|
6010
|
+
return void 0;
|
|
6011
|
+
}
|
|
6012
|
+
const runnerUp = ranked[1];
|
|
6013
|
+
if (runnerUp && signalStrength(leader) - signalStrength(runnerUp) < DEFAULT_CANONICAL_MARGIN) {
|
|
6014
|
+
return void 0;
|
|
6015
|
+
}
|
|
6016
|
+
return leader.procedure;
|
|
6017
|
+
}
|
|
6018
|
+
function signalStrength(candidate) {
|
|
6019
|
+
return Math.max(candidate.scores.vector, candidate.scores.lexical);
|
|
6020
|
+
}
|
|
6021
|
+
function compareProcedureCandidates(left, right) {
|
|
6022
|
+
if (left.score !== right.score) {
|
|
6023
|
+
return right.score - left.score;
|
|
6024
|
+
}
|
|
6025
|
+
if (left.scores.lexical !== right.scores.lexical) {
|
|
6026
|
+
return right.scores.lexical - left.scores.lexical;
|
|
6027
|
+
}
|
|
6028
|
+
if (left.scores.vector !== right.scores.vector) {
|
|
6029
|
+
return right.scores.vector - left.scores.vector;
|
|
6030
|
+
}
|
|
6031
|
+
if (left.procedure.updated_at !== right.procedure.updated_at) {
|
|
6032
|
+
return right.procedure.updated_at.localeCompare(left.procedure.updated_at);
|
|
6033
|
+
}
|
|
6034
|
+
return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
|
|
6035
|
+
}
|
|
6036
|
+
function normalizeLimit(value) {
|
|
6037
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
6038
|
+
return DEFAULT_LIMIT;
|
|
6039
|
+
}
|
|
6040
|
+
return Math.max(0, Math.trunc(value));
|
|
6041
|
+
}
|
|
6042
|
+
function normalizeThreshold(value) {
|
|
6043
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
6044
|
+
return DEFAULT_CANONICAL_THRESHOLD;
|
|
6045
|
+
}
|
|
6046
|
+
return Math.min(1, Math.max(0, value));
|
|
6047
|
+
}
|
|
6048
|
+
function dedupePreservingOrder(values) {
|
|
6049
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6050
|
+
const deduped = [];
|
|
6051
|
+
for (const value of values) {
|
|
6052
|
+
if (seen.has(value)) {
|
|
6053
|
+
continue;
|
|
6054
|
+
}
|
|
6055
|
+
seen.add(value);
|
|
6056
|
+
deduped.push(value);
|
|
6057
|
+
}
|
|
6058
|
+
return deduped;
|
|
5082
6059
|
}
|
|
5083
6060
|
|
|
5084
6061
|
// src/core/episode/temporal-window.ts
|
|
@@ -5561,12 +6538,12 @@ function compareAscending(left, right) {
|
|
|
5561
6538
|
}
|
|
5562
6539
|
|
|
5563
6540
|
// src/core/episode/search.ts
|
|
5564
|
-
var
|
|
6541
|
+
var DEFAULT_LIMIT2 = 10;
|
|
5565
6542
|
var MIN_CANDIDATE_LIMIT = 25;
|
|
5566
6543
|
var MAX_CANDIDATE_LIMIT = 100;
|
|
5567
6544
|
var CANDIDATE_MULTIPLIER = 5;
|
|
5568
6545
|
async function searchEpisodes(query, database, now = /* @__PURE__ */ new Date()) {
|
|
5569
|
-
const limit =
|
|
6546
|
+
const limit = normalizeLimit2(query.limit);
|
|
5570
6547
|
if (limit === 0) {
|
|
5571
6548
|
return [];
|
|
5572
6549
|
}
|
|
@@ -5589,14 +6566,139 @@ async function searchEpisodes(query, database, now = /* @__PURE__ */ new Date())
|
|
|
5589
6566
|
return matches.map((match) => buildSemanticResult(match.episode, match.vectorSim, now)).sort(compareSemanticEpisodeResults).slice(0, limit);
|
|
5590
6567
|
}
|
|
5591
6568
|
const candidates = await database.listEpisodesByTimeWindow(query.timeWindow, computeCandidateLimit(limit));
|
|
5592
|
-
|
|
6569
|
+
const hybridResults = candidates.map((episode) => buildHybridResult(episode, normalizedEmbedding, bounds, now));
|
|
6570
|
+
const fused = fuseHybridResultsWithRrf(hybridResults);
|
|
6571
|
+
const diversified = applyEpisodeMmrDiversification(fused, normalizedEmbedding, query.mmr);
|
|
6572
|
+
const reranked = await applyEpisodeCrossEncoderRerank(diversified, query.text, query.crossEncoder);
|
|
6573
|
+
return reranked.slice(0, limit);
|
|
6574
|
+
}
|
|
6575
|
+
async function applyEpisodeCrossEncoderRerank(results, query, options) {
|
|
6576
|
+
if (!options || !options.enabled || results.length === 0) {
|
|
6577
|
+
return results;
|
|
6578
|
+
}
|
|
6579
|
+
const rerank = await applyCrossEncoderRerank({
|
|
6580
|
+
query,
|
|
6581
|
+
candidates: results.map((result) => ({
|
|
6582
|
+
id: result.episode.id,
|
|
6583
|
+
text: buildEpisodeCrossEncoderText(result.episode),
|
|
6584
|
+
score: result.score,
|
|
6585
|
+
candidate: result
|
|
6586
|
+
})),
|
|
6587
|
+
port: options.port,
|
|
6588
|
+
topK: options.topK ?? DEFAULT_CROSS_ENCODER_TOP_K,
|
|
6589
|
+
alpha: options.alpha ?? DEFAULT_CROSS_ENCODER_ALPHA
|
|
6590
|
+
});
|
|
6591
|
+
return rerank.candidates.map((entry) => {
|
|
6592
|
+
const base = entry.candidate;
|
|
6593
|
+
const nextScore = Number(entry.score.toFixed(6));
|
|
6594
|
+
if (typeof entry.crossEncoderScore !== "number" && nextScore === base.score) {
|
|
6595
|
+
return base;
|
|
6596
|
+
}
|
|
6597
|
+
return {
|
|
6598
|
+
...base,
|
|
6599
|
+
score: nextScore,
|
|
6600
|
+
scores: {
|
|
6601
|
+
...base.scores,
|
|
6602
|
+
...typeof entry.crossEncoderScore === "number" ? { crossEncoder: Number(entry.crossEncoderScore.toFixed(6)) } : {}
|
|
6603
|
+
}
|
|
6604
|
+
};
|
|
6605
|
+
});
|
|
5593
6606
|
}
|
|
5594
|
-
function
|
|
6607
|
+
function buildEpisodeCrossEncoderText(episode) {
|
|
6608
|
+
const summary = episode.summary?.trim() ?? "";
|
|
6609
|
+
if (summary.length > 0) {
|
|
6610
|
+
return summary;
|
|
6611
|
+
}
|
|
6612
|
+
const source = episode.source ?? "";
|
|
6613
|
+
const ref = episode.sourceRef ?? episode.sourceId ?? "";
|
|
6614
|
+
return `${source} ${ref}`.trim();
|
|
6615
|
+
}
|
|
6616
|
+
function applyEpisodeMmrDiversification(results, queryEmbedding, options) {
|
|
6617
|
+
if (!options || !options.enabled || results.length < 2 || queryEmbedding.length === 0) {
|
|
6618
|
+
return results;
|
|
6619
|
+
}
|
|
6620
|
+
const reorder = maximalMarginalRelevance({
|
|
6621
|
+
queryVector: queryEmbedding,
|
|
6622
|
+
candidates: results.map((result) => ({
|
|
6623
|
+
id: result.episode.id,
|
|
6624
|
+
relevance: result.score,
|
|
6625
|
+
...result.episode.embedding ? { embedding: result.episode.embedding } : {}
|
|
6626
|
+
})),
|
|
6627
|
+
lambda: resolveEpisodeMmrLambda(options.lambda),
|
|
6628
|
+
...typeof options.minPoolSize === "number" ? { minPoolSize: options.minPoolSize } : {}
|
|
6629
|
+
});
|
|
6630
|
+
if (!reorder.applied) {
|
|
6631
|
+
return results;
|
|
6632
|
+
}
|
|
6633
|
+
const resultsById = new Map(results.map((result) => [result.episode.id, result]));
|
|
6634
|
+
return reorder.orderedIds.flatMap((id) => {
|
|
6635
|
+
const result = resultsById.get(id);
|
|
6636
|
+
return result ? [result] : [];
|
|
6637
|
+
});
|
|
6638
|
+
}
|
|
6639
|
+
function resolveEpisodeMmrLambda(value) {
|
|
6640
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
6641
|
+
return DEFAULT_MMR_LAMBDA;
|
|
6642
|
+
}
|
|
6643
|
+
return Math.max(0, Math.min(1, value));
|
|
6644
|
+
}
|
|
6645
|
+
function fuseHybridResultsWithRrf(hybridResults) {
|
|
6646
|
+
if (hybridResults.length === 0) {
|
|
6647
|
+
return [];
|
|
6648
|
+
}
|
|
6649
|
+
const temporalRanks = [...hybridResults].sort((left, right) => compareDescending2(left.scores.temporal, right.scores.temporal) || compareAscending2(left.episode.id, right.episode.id)).map((result) => result.episode.id);
|
|
6650
|
+
const semanticRanks = [...hybridResults].filter((result) => result.scores.semantic > 0).sort((left, right) => compareDescending2(left.scores.semantic, right.scores.semantic) || compareAscending2(left.episode.id, right.episode.id)).map((result) => result.episode.id);
|
|
6651
|
+
const fusedScores = rrfFuse([temporalRanks, semanticRanks]);
|
|
6652
|
+
const fused = hybridResults.map((result) => ({
|
|
6653
|
+
...result,
|
|
6654
|
+
score: Number((fusedScores.get(result.episode.id) ?? 0).toFixed(6))
|
|
6655
|
+
}));
|
|
6656
|
+
const reranked = applySeededEpisodeRerank(fused);
|
|
6657
|
+
return reranked.sort(compareFusedEpisodeResults);
|
|
6658
|
+
}
|
|
6659
|
+
function applySeededEpisodeRerank(results) {
|
|
6660
|
+
if (results.length === 0) {
|
|
6661
|
+
return results;
|
|
6662
|
+
}
|
|
6663
|
+
const seeds = selectStrongSeeds(
|
|
6664
|
+
results.map((result) => ({ id: result.episode.id, score: result.score, episode: result.episode })),
|
|
6665
|
+
{
|
|
6666
|
+
topN: DEFAULT_STRONG_SEED_TOP_N,
|
|
6667
|
+
scoreGapFloor: DEFAULT_STRONG_SEED_SCORE_GAP
|
|
6668
|
+
}
|
|
6669
|
+
);
|
|
6670
|
+
if (seeds.length === 0) {
|
|
6671
|
+
return results;
|
|
6672
|
+
}
|
|
6673
|
+
const payloads = results.map((result) => ({
|
|
6674
|
+
id: result.episode.id,
|
|
6675
|
+
score: result.score,
|
|
6676
|
+
episode: result.episode
|
|
6677
|
+
}));
|
|
6678
|
+
const reranked = seededRerank(payloads, seeds, (candidate, seed) => sharesEpisodeLineage(candidate.episode, seed.episode), {
|
|
6679
|
+
weight: DEFAULT_SEEDED_RERANK_WEIGHT
|
|
6680
|
+
});
|
|
6681
|
+
const scoreById = new Map(reranked.candidates.map((candidate) => [candidate.id, candidate.score]));
|
|
6682
|
+
return results.map((result) => {
|
|
6683
|
+
const nextScore = scoreById.get(result.episode.id);
|
|
6684
|
+
if (nextScore === void 0 || nextScore === result.score) {
|
|
6685
|
+
return result;
|
|
6686
|
+
}
|
|
6687
|
+
return {
|
|
6688
|
+
...result,
|
|
6689
|
+
score: Number(nextScore.toFixed(6))
|
|
6690
|
+
};
|
|
6691
|
+
});
|
|
6692
|
+
}
|
|
6693
|
+
function compareFusedEpisodeResults(left, right) {
|
|
6694
|
+
return compareDescending2(left.score, right.score) || compareDescending2(left.scores.semantic, right.scores.semantic) || compareDescending2(left.scores.temporal, right.scores.temporal) || compareDescending2(left.scores.activity, right.scores.activity) || compareDescending2(left.scores.recency, right.scores.recency) || compareAscending2(left.episode.startedAt, right.episode.startedAt) || compareAscending2(left.episode.id, right.episode.id);
|
|
6695
|
+
}
|
|
6696
|
+
function normalizeLimit2(value) {
|
|
5595
6697
|
if (value === void 0) {
|
|
5596
|
-
return
|
|
6698
|
+
return DEFAULT_LIMIT2;
|
|
5597
6699
|
}
|
|
5598
6700
|
if (!Number.isFinite(value)) {
|
|
5599
|
-
return
|
|
6701
|
+
return DEFAULT_LIMIT2;
|
|
5600
6702
|
}
|
|
5601
6703
|
return Math.max(0, Math.trunc(value));
|
|
5602
6704
|
}
|
|
@@ -5653,156 +6755,6 @@ function compareAscending2(left, right) {
|
|
|
5653
6755
|
return left.localeCompare(right);
|
|
5654
6756
|
}
|
|
5655
6757
|
|
|
5656
|
-
// src/app/procedures/recall/service.ts
|
|
5657
|
-
var DEFAULT_LIMIT2 = 5;
|
|
5658
|
-
var DEFAULT_CANONICAL_THRESHOLD = 0.55;
|
|
5659
|
-
var DEFAULT_CANONICAL_MARGIN = 0.08;
|
|
5660
|
-
var LEXICAL_CANDIDATE_MULTIPLIER = 3;
|
|
5661
|
-
var VECTOR_CANDIDATE_MULTIPLIER = 4;
|
|
5662
|
-
var VECTOR_ONLY_CANONICAL_FLOOR = 0.6;
|
|
5663
|
-
var LEXICAL_ONLY_NOTICE = "Semantic procedure search unavailable - using lexical-only procedure ranking.";
|
|
5664
|
-
var LEXICAL_ONLY_FALLBACK_NOTICE = "Semantic procedure search failed during procedure recall - using lexical-only procedure ranking.";
|
|
5665
|
-
async function runProcedureRecall(input, deps) {
|
|
5666
|
-
const text = input.text.trim();
|
|
5667
|
-
const limit = normalizeLimit2(input.limit);
|
|
5668
|
-
if (text.length === 0 || limit === 0) {
|
|
5669
|
-
return {
|
|
5670
|
-
candidates: [],
|
|
5671
|
-
notices: []
|
|
5672
|
-
};
|
|
5673
|
-
}
|
|
5674
|
-
const lexicalLimit = limit * LEXICAL_CANDIDATE_MULTIPLIER;
|
|
5675
|
-
const vectorLimit = limit * VECTOR_CANDIDATE_MULTIPLIER;
|
|
5676
|
-
const notices = [];
|
|
5677
|
-
const lexicalMatches = await deps.db.procedureFtsSearch({
|
|
5678
|
-
text,
|
|
5679
|
-
limit: lexicalLimit
|
|
5680
|
-
});
|
|
5681
|
-
const queryEmbedding = await maybeEmbedQuery(text, deps.embedQuery, notices);
|
|
5682
|
-
const vectorMatches = queryEmbedding.length > 0 ? await deps.db.procedureVectorSearch({
|
|
5683
|
-
embedding: queryEmbedding,
|
|
5684
|
-
limit: vectorLimit
|
|
5685
|
-
}).catch(() => {
|
|
5686
|
-
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5687
|
-
return [];
|
|
5688
|
-
}) : [];
|
|
5689
|
-
const ranked = rankProcedureCandidates(text, lexicalMatches, vectorMatches).slice(0, limit);
|
|
5690
|
-
const canonicalProcedure = selectCanonicalProcedure(ranked, input.threshold);
|
|
5691
|
-
return {
|
|
5692
|
-
...canonicalProcedure ? { canonicalProcedure } : {},
|
|
5693
|
-
candidates: ranked,
|
|
5694
|
-
notices: dedupePreservingOrder(notices)
|
|
5695
|
-
};
|
|
5696
|
-
}
|
|
5697
|
-
async function maybeEmbedQuery(text, embedQuery, notices) {
|
|
5698
|
-
if (!embedQuery) {
|
|
5699
|
-
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5700
|
-
return [];
|
|
5701
|
-
}
|
|
5702
|
-
try {
|
|
5703
|
-
const embedding = await embedQuery(text);
|
|
5704
|
-
if (embedding.length === 0) {
|
|
5705
|
-
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5706
|
-
return [];
|
|
5707
|
-
}
|
|
5708
|
-
return embedding;
|
|
5709
|
-
} catch {
|
|
5710
|
-
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5711
|
-
return [];
|
|
5712
|
-
}
|
|
5713
|
-
}
|
|
5714
|
-
function rankProcedureCandidates(query, lexicalMatches, vectorMatches) {
|
|
5715
|
-
const merged = /* @__PURE__ */ new Map();
|
|
5716
|
-
for (const match of lexicalMatches) {
|
|
5717
|
-
const lexical = computeProcedureLexicalScore(query, match.procedure);
|
|
5718
|
-
merged.set(match.procedure.id, {
|
|
5719
|
-
procedure: match.procedure,
|
|
5720
|
-
lexical,
|
|
5721
|
-
vector: 0
|
|
5722
|
-
});
|
|
5723
|
-
}
|
|
5724
|
-
for (const match of vectorMatches) {
|
|
5725
|
-
const existing = merged.get(match.procedure.id);
|
|
5726
|
-
merged.set(match.procedure.id, {
|
|
5727
|
-
procedure: match.procedure,
|
|
5728
|
-
lexical: existing?.lexical ?? computeProcedureLexicalScore(query, match.procedure),
|
|
5729
|
-
vector: Math.max(existing?.vector ?? 0, match.vectorSim)
|
|
5730
|
-
});
|
|
5731
|
-
}
|
|
5732
|
-
return Array.from(merged.values()).map((candidate) => {
|
|
5733
|
-
const relevance = combinedRelevance(candidate.vector, candidate.lexical);
|
|
5734
|
-
return {
|
|
5735
|
-
procedure: candidate.procedure,
|
|
5736
|
-
score: relevance,
|
|
5737
|
-
scores: {
|
|
5738
|
-
relevance,
|
|
5739
|
-
lexical: candidate.lexical,
|
|
5740
|
-
vector: candidate.vector
|
|
5741
|
-
}
|
|
5742
|
-
};
|
|
5743
|
-
}).filter((candidate) => candidate.score > 0).sort(compareProcedureCandidates);
|
|
5744
|
-
}
|
|
5745
|
-
function computeProcedureLexicalScore(query, procedure) {
|
|
5746
|
-
return computeLexicalScore(query, procedure.title, procedure.recall_text);
|
|
5747
|
-
}
|
|
5748
|
-
function selectCanonicalProcedure(ranked, threshold) {
|
|
5749
|
-
const leader = ranked[0];
|
|
5750
|
-
if (!leader) {
|
|
5751
|
-
return void 0;
|
|
5752
|
-
}
|
|
5753
|
-
const minimumScore = normalizeThreshold(threshold);
|
|
5754
|
-
if (leader.score < minimumScore) {
|
|
5755
|
-
return void 0;
|
|
5756
|
-
}
|
|
5757
|
-
if (leader.scores.lexical === 0 && leader.scores.vector < VECTOR_ONLY_CANONICAL_FLOOR) {
|
|
5758
|
-
return void 0;
|
|
5759
|
-
}
|
|
5760
|
-
const runnerUp = ranked[1];
|
|
5761
|
-
if (runnerUp && leader.score - runnerUp.score < DEFAULT_CANONICAL_MARGIN) {
|
|
5762
|
-
return void 0;
|
|
5763
|
-
}
|
|
5764
|
-
return leader.procedure;
|
|
5765
|
-
}
|
|
5766
|
-
function compareProcedureCandidates(left, right) {
|
|
5767
|
-
if (left.score !== right.score) {
|
|
5768
|
-
return right.score - left.score;
|
|
5769
|
-
}
|
|
5770
|
-
if (left.scores.lexical !== right.scores.lexical) {
|
|
5771
|
-
return right.scores.lexical - left.scores.lexical;
|
|
5772
|
-
}
|
|
5773
|
-
if (left.scores.vector !== right.scores.vector) {
|
|
5774
|
-
return right.scores.vector - left.scores.vector;
|
|
5775
|
-
}
|
|
5776
|
-
if (left.procedure.updated_at !== right.procedure.updated_at) {
|
|
5777
|
-
return right.procedure.updated_at.localeCompare(left.procedure.updated_at);
|
|
5778
|
-
}
|
|
5779
|
-
return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
|
|
5780
|
-
}
|
|
5781
|
-
function normalizeLimit2(value) {
|
|
5782
|
-
if (value === void 0 || !Number.isFinite(value)) {
|
|
5783
|
-
return DEFAULT_LIMIT2;
|
|
5784
|
-
}
|
|
5785
|
-
return Math.max(0, Math.trunc(value));
|
|
5786
|
-
}
|
|
5787
|
-
function normalizeThreshold(value) {
|
|
5788
|
-
if (value === void 0 || !Number.isFinite(value)) {
|
|
5789
|
-
return DEFAULT_CANONICAL_THRESHOLD;
|
|
5790
|
-
}
|
|
5791
|
-
return Math.min(1, Math.max(0, value));
|
|
5792
|
-
}
|
|
5793
|
-
function dedupePreservingOrder(values) {
|
|
5794
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5795
|
-
const deduped = [];
|
|
5796
|
-
for (const value of values) {
|
|
5797
|
-
if (seen.has(value)) {
|
|
5798
|
-
continue;
|
|
5799
|
-
}
|
|
5800
|
-
seen.add(value);
|
|
5801
|
-
deduped.push(value);
|
|
5802
|
-
}
|
|
5803
|
-
return deduped;
|
|
5804
|
-
}
|
|
5805
|
-
|
|
5806
6758
|
// src/app/recall/transitions.ts
|
|
5807
6759
|
function buildClaimTransitionExplanations(params) {
|
|
5808
6760
|
if (params.families.length === 0 || params.detectedIntent === "temporal_narrative") {
|
|
@@ -5939,14 +6891,32 @@ var PROCEDURAL_REGEX_PATTERNS = [
|
|
|
5939
6891
|
/\b(?:checklist|playbook|runbook|procedure|process|instructions?|workflow|method)\b.*\b(?:for|to)\b/u,
|
|
5940
6892
|
/\bwhat(?:'s| is) the (?:best|recommended|right) way to\b/u
|
|
5941
6893
|
];
|
|
6894
|
+
var ENTITY_ATTRIBUTE_MAX_WORDS = 5;
|
|
6895
|
+
var ENTITY_ATTRIBUTE_CONTEXTUAL_PREFIX_RE = /^(?:on|in|at|for|about|during|after|before)\b/u;
|
|
6896
|
+
var ENTITY_ATTRIBUTE_CONTEXTUAL_TIME_RE = /\b(?:today|tomorrow|yesterday|tonight|currently|right now|this week|next week|last week|this month|next month|last month|this year|next year|last year)\b/u;
|
|
6897
|
+
var ENTITY_ATTRIBUTE_CONTEXTUAL_ACTIVITY_RE = /\b(?:on call|available|working|assigned|scheduled|responsible)\b/u;
|
|
6898
|
+
var ENTITY_ATTRIBUTE_GENERIC_ENTITY_RE = /^(?:it|this|that|these|those|they|them|he|she|someone|anyone|anything|everything)\b/u;
|
|
6899
|
+
var ENTITY_ATTRIBUTE_KIND_TOKENS = {
|
|
6900
|
+
identity: ["identity", "profile", "bio", "biography", "summary"],
|
|
6901
|
+
location: ["location", "live", "lives", "reside", "resides", "located", "home", "city"],
|
|
6902
|
+
email: ["email", "e-mail", "mail"],
|
|
6903
|
+
phone: ["phone", "number", "mobile", "cell", "telephone"],
|
|
6904
|
+
address: ["address", "street", "mailing"]
|
|
6905
|
+
};
|
|
5942
6906
|
async function runUnifiedRecall(input, deps) {
|
|
5943
6907
|
const now = deps.now ?? /* @__PURE__ */ new Date();
|
|
5944
6908
|
const requested = normalizeMode(input.mode);
|
|
5945
6909
|
const parsedTimeWindow = parseTemporalWindow(input.text, now);
|
|
5946
6910
|
const hasEntryFilters = hasEntryScopedFilters(input);
|
|
5947
6911
|
const topicAnchor = hasTopicAnchor(input.text, hasEntryFilters);
|
|
6912
|
+
const entityAttributeQuery = detectEntityAttributeQuery(input.text);
|
|
5948
6913
|
const historicalStatePattern = detectHistoricalStatePattern(input.text);
|
|
5949
6914
|
const proceduralPattern = detectProceduralPattern(input.text);
|
|
6915
|
+
if (entityAttributeQuery) {
|
|
6916
|
+
deps.debugLog?.(
|
|
6917
|
+
`[agenr] unified recall matched entity-attribute kind=${JSON.stringify(entityAttributeQuery.attributeKind)} entity=${JSON.stringify(entityAttributeQuery.entityText)} query=${JSON.stringify(input.text)}`
|
|
6918
|
+
);
|
|
6919
|
+
}
|
|
5950
6920
|
if (historicalStatePattern) {
|
|
5951
6921
|
deps.debugLog?.(`[agenr] unified recall matched historical-state pattern=${JSON.stringify(historicalStatePattern)} query=${JSON.stringify(input.text)}`);
|
|
5952
6922
|
}
|
|
@@ -5967,7 +6937,9 @@ async function runUnifiedRecall(input, deps) {
|
|
|
5967
6937
|
detectedIntent: routing.detectedIntent,
|
|
5968
6938
|
parsedTimeWindow,
|
|
5969
6939
|
topicAnchor,
|
|
5970
|
-
embedQuery: deps.embedQuery
|
|
6940
|
+
embedQuery: deps.embedQuery,
|
|
6941
|
+
rankingPolicy: deps.recallOptions?.rankingPolicy,
|
|
6942
|
+
crossEncoder: deps.recall.crossEncoder
|
|
5971
6943
|
}) : {
|
|
5972
6944
|
notices: []
|
|
5973
6945
|
};
|
|
@@ -5979,11 +6951,15 @@ async function runUnifiedRecall(input, deps) {
|
|
|
5979
6951
|
if (routing.queried.includes("episodes") && hasEntryScopedFilters(input)) {
|
|
5980
6952
|
notices.push(ENTRY_FILTER_NOTICE);
|
|
5981
6953
|
}
|
|
6954
|
+
const procedureMmr = resolveProcedureMmrOptions(deps.recallOptions?.rankingPolicy);
|
|
6955
|
+
const procedureCrossEncoder = resolveProcedureCrossEncoderOptions(deps.recallOptions?.rankingPolicy, deps.recall.crossEncoder);
|
|
5982
6956
|
const procedureResults = routing.queried.includes("procedures") ? await runProcedureRecall(
|
|
5983
6957
|
{
|
|
5984
6958
|
text: input.text,
|
|
5985
6959
|
...input.limit !== void 0 ? { limit: input.limit } : {},
|
|
5986
|
-
...input.threshold !== void 0 ? { threshold: input.threshold } : {}
|
|
6960
|
+
...input.threshold !== void 0 ? { threshold: input.threshold } : {},
|
|
6961
|
+
...procedureMmr ? { mmr: procedureMmr } : {},
|
|
6962
|
+
...procedureCrossEncoder ? { crossEncoder: procedureCrossEncoder } : {}
|
|
5987
6963
|
},
|
|
5988
6964
|
{
|
|
5989
6965
|
db: deps.procedures,
|
|
@@ -6044,13 +7020,14 @@ function routeRecall(params) {
|
|
|
6044
7020
|
const lower = params.text.trim().toLowerCase();
|
|
6045
7021
|
const factual = /^(when did|when was|what decision|what preference|what(?:'s| is) the default|which version|what threshold)\b/.test(lower);
|
|
6046
7022
|
const narrative = /\b(what happened|what were we doing|what was going on|summarize|catch me up)\b/.test(lower);
|
|
7023
|
+
const entityAttributeQuery = detectEntityAttributeQuery(params.text);
|
|
6047
7024
|
const historicalState = detectHistoricalStatePattern(params.text) !== void 0;
|
|
6048
7025
|
const procedural = detectProceduralPattern(params.text) !== void 0;
|
|
6049
7026
|
const topicAnchor = hasTopicAnchor(params.text, params.hasEntryFilters);
|
|
6050
7027
|
if (params.requested === "entries") {
|
|
6051
7028
|
return {
|
|
6052
7029
|
requested: params.requested,
|
|
6053
|
-
detectedIntent: historicalState ? "historical_state" : factual ? "factual" : params.parsedTimeWindow ? "mixed" : "factual",
|
|
7030
|
+
detectedIntent: entityAttributeQuery ? "entity_attribute" : historicalState ? "historical_state" : factual ? "factual" : params.parsedTimeWindow ? "mixed" : "factual",
|
|
6054
7031
|
queried: ["entries"],
|
|
6055
7032
|
reason: "Explicit mode=entries override."
|
|
6056
7033
|
};
|
|
@@ -6071,6 +7048,14 @@ function routeRecall(params) {
|
|
|
6071
7048
|
reason: "Explicit mode=procedures override."
|
|
6072
7049
|
};
|
|
6073
7050
|
}
|
|
7051
|
+
if (entityAttributeQuery) {
|
|
7052
|
+
return {
|
|
7053
|
+
requested: params.requested,
|
|
7054
|
+
detectedIntent: "entity_attribute",
|
|
7055
|
+
queried: ["entries"],
|
|
7056
|
+
reason: "The query asks for a specific entity attribute, so precision-first entry recall was used."
|
|
7057
|
+
};
|
|
7058
|
+
}
|
|
6074
7059
|
if (historicalState) {
|
|
6075
7060
|
return {
|
|
6076
7061
|
requested: params.requested,
|
|
@@ -6173,16 +7158,65 @@ async function buildEpisodeQueryPlan(params) {
|
|
|
6173
7158
|
notices
|
|
6174
7159
|
};
|
|
6175
7160
|
}
|
|
7161
|
+
const mmr = resolveEpisodeMmrOptions(params.detectedIntent, params.rankingPolicy);
|
|
7162
|
+
const crossEncoder = resolveEpisodeCrossEncoderOptions(params.rankingPolicy, params.crossEncoder);
|
|
6176
7163
|
return {
|
|
6177
7164
|
query: {
|
|
6178
7165
|
text: params.text,
|
|
6179
7166
|
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
6180
7167
|
...params.parsedTimeWindow ? { timeWindow: params.parsedTimeWindow.window } : {},
|
|
6181
|
-
...embedding ? { embedding } : {}
|
|
7168
|
+
...embedding ? { embedding } : {},
|
|
7169
|
+
...mmr ? { mmr } : {},
|
|
7170
|
+
...crossEncoder ? { crossEncoder } : {}
|
|
6182
7171
|
},
|
|
6183
7172
|
notices
|
|
6184
7173
|
};
|
|
6185
7174
|
}
|
|
7175
|
+
function resolveEpisodeMmrOptions(detectedIntent, rankingPolicy) {
|
|
7176
|
+
if (rankingPolicy?.mmr === "disabled") {
|
|
7177
|
+
return void 0;
|
|
7178
|
+
}
|
|
7179
|
+
if (detectedIntent !== "factual" && detectedIntent !== "mixed") {
|
|
7180
|
+
return void 0;
|
|
7181
|
+
}
|
|
7182
|
+
return {
|
|
7183
|
+
enabled: true,
|
|
7184
|
+
...typeof rankingPolicy?.mmrLambda === "number" ? { lambda: rankingPolicy.mmrLambda } : {},
|
|
7185
|
+
...typeof rankingPolicy?.mmrMinPoolSize === "number" ? { minPoolSize: rankingPolicy.mmrMinPoolSize } : {}
|
|
7186
|
+
};
|
|
7187
|
+
}
|
|
7188
|
+
function resolveProcedureMmrOptions(rankingPolicy) {
|
|
7189
|
+
if (rankingPolicy?.mmr === "disabled") {
|
|
7190
|
+
return void 0;
|
|
7191
|
+
}
|
|
7192
|
+
return {
|
|
7193
|
+
enabled: true,
|
|
7194
|
+
...typeof rankingPolicy?.mmrLambda === "number" ? { lambda: rankingPolicy.mmrLambda } : {},
|
|
7195
|
+
...typeof rankingPolicy?.mmrMinPoolSize === "number" ? { minPoolSize: rankingPolicy.mmrMinPoolSize } : {}
|
|
7196
|
+
};
|
|
7197
|
+
}
|
|
7198
|
+
function resolveEpisodeCrossEncoderOptions(rankingPolicy, crossEncoder) {
|
|
7199
|
+
if (!crossEncoder || rankingPolicy?.crossEncoder === "disabled") {
|
|
7200
|
+
return void 0;
|
|
7201
|
+
}
|
|
7202
|
+
return {
|
|
7203
|
+
enabled: true,
|
|
7204
|
+
port: crossEncoder,
|
|
7205
|
+
...typeof rankingPolicy?.crossEncoderTopK === "number" ? { topK: rankingPolicy.crossEncoderTopK } : {},
|
|
7206
|
+
...typeof rankingPolicy?.crossEncoderAlpha === "number" ? { alpha: rankingPolicy.crossEncoderAlpha } : {}
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
function resolveProcedureCrossEncoderOptions(rankingPolicy, crossEncoder) {
|
|
7210
|
+
if (!crossEncoder || rankingPolicy?.crossEncoder === "disabled") {
|
|
7211
|
+
return void 0;
|
|
7212
|
+
}
|
|
7213
|
+
return {
|
|
7214
|
+
enabled: true,
|
|
7215
|
+
port: crossEncoder,
|
|
7216
|
+
...typeof rankingPolicy?.crossEncoderTopK === "number" ? { topK: rankingPolicy.crossEncoderTopK } : {},
|
|
7217
|
+
...typeof rankingPolicy?.crossEncoderAlpha === "number" ? { alpha: rankingPolicy.crossEncoderAlpha } : {}
|
|
7218
|
+
};
|
|
7219
|
+
}
|
|
6186
7220
|
async function maybeRunEntryRecall(params) {
|
|
6187
7221
|
if (!params.routing.queried.includes("entries")) {
|
|
6188
7222
|
return {
|
|
@@ -6219,6 +7253,7 @@ function composeRecallTrace(upstream, onSummary) {
|
|
|
6219
7253
|
};
|
|
6220
7254
|
}
|
|
6221
7255
|
function buildEntryRecallInput(input, parsedTimeWindow, routing) {
|
|
7256
|
+
const entityAttributeQuery = routing.detectedIntent === "entity_attribute" ? detectEntityAttributeQuery(input.text) : void 0;
|
|
6222
7257
|
const request = {
|
|
6223
7258
|
text: input.text,
|
|
6224
7259
|
...input.limit !== void 0 ? { limit: input.limit } : {},
|
|
@@ -6227,7 +7262,9 @@ function buildEntryRecallInput(input, parsedTimeWindow, routing) {
|
|
|
6227
7262
|
...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
|
|
6228
7263
|
...input.sessionKey ? { sessionKey: input.sessionKey } : {},
|
|
6229
7264
|
...input.asOf ? { asOf: input.asOf } : {},
|
|
6230
|
-
...routing.detectedIntent === "historical_state" ? { rankingProfile: "historical_state" } : {}
|
|
7265
|
+
...routing.detectedIntent === "historical_state" ? { rankingProfile: "historical_state" } : {},
|
|
7266
|
+
...routing.detectedIntent === "entity_attribute" ? { rankingProfile: "entity_attribute" } : {},
|
|
7267
|
+
...entityAttributeQuery ? { queryShape: entityAttributeQuery } : {}
|
|
6231
7268
|
};
|
|
6232
7269
|
if (!parsedTimeWindow || input.asOf) {
|
|
6233
7270
|
return request;
|
|
@@ -6262,6 +7299,90 @@ function detectProceduralPattern(text) {
|
|
|
6262
7299
|
const regexPattern = PROCEDURAL_REGEX_PATTERNS.find((pattern) => pattern.test(lower));
|
|
6263
7300
|
return regexPattern?.source;
|
|
6264
7301
|
}
|
|
7302
|
+
function detectEntityAttributeQuery(text) {
|
|
7303
|
+
const normalizedText = normalizeEntityAttributeWhitespace(text);
|
|
7304
|
+
const whereDoesLive = /^where\s+does\s+(.+?)\s+live[?!.,]*$/iu.exec(normalizedText);
|
|
7305
|
+
if (whereDoesLive) {
|
|
7306
|
+
return buildEntityAttributeQueryShape(whereDoesLive[1], "location");
|
|
7307
|
+
}
|
|
7308
|
+
const whereIs = /^where\s+is\s+(.+?)[?!.,]*$/iu.exec(normalizedText);
|
|
7309
|
+
if (whereIs) {
|
|
7310
|
+
return buildEntityAttributeQueryShape(whereIs[1], "location");
|
|
7311
|
+
}
|
|
7312
|
+
const possessiveAttribute = /^(?:what\s+is|what's)\s+(.+?)'s\s+(.+?)[?!.,]*$/iu.exec(normalizedText);
|
|
7313
|
+
if (possessiveAttribute) {
|
|
7314
|
+
const attributeKind = resolveEntityAttributeKind(possessiveAttribute[2]);
|
|
7315
|
+
if (attributeKind) {
|
|
7316
|
+
return buildEntityAttributeQueryShape(possessiveAttribute[1], attributeKind);
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
const whoIs = /^(?:who\s+is|who's)\s+(.+?)(?:\s+again)?[?!.,]*$/iu.exec(normalizedText);
|
|
7320
|
+
if (whoIs) {
|
|
7321
|
+
return buildEntityAttributeQueryShape(whoIs[1], "identity");
|
|
7322
|
+
}
|
|
7323
|
+
const whatIs = /^what\s+is\s+(.+?)(?:\s+again)?[?!.,]*$/iu.exec(normalizedText);
|
|
7324
|
+
if (whatIs) {
|
|
7325
|
+
return buildEntityAttributeQueryShape(whatIs[1], "identity");
|
|
7326
|
+
}
|
|
7327
|
+
return void 0;
|
|
7328
|
+
}
|
|
7329
|
+
function buildEntityAttributeQueryShape(rawEntityText, attributeKind) {
|
|
7330
|
+
const entityText = normalizeEntityAttributeEntity(rawEntityText);
|
|
7331
|
+
if (!entityText) {
|
|
7332
|
+
return void 0;
|
|
7333
|
+
}
|
|
7334
|
+
const entityTokens = tokenize(entityText);
|
|
7335
|
+
if (entityTokens.length === 0) {
|
|
7336
|
+
return void 0;
|
|
7337
|
+
}
|
|
7338
|
+
return {
|
|
7339
|
+
kind: "entity_attribute",
|
|
7340
|
+
entityText,
|
|
7341
|
+
normalizedEntity: normalizeEntityAttributeText(entityText),
|
|
7342
|
+
entityTokens,
|
|
7343
|
+
attributeKind,
|
|
7344
|
+
attributeTokens: [...ENTITY_ATTRIBUTE_KIND_TOKENS[attributeKind]]
|
|
7345
|
+
};
|
|
7346
|
+
}
|
|
7347
|
+
function resolveEntityAttributeKind(rawAttributeText) {
|
|
7348
|
+
const tokens = tokenize(rawAttributeText ?? "");
|
|
7349
|
+
if (tokens.some((token) => token === "email" || token === "e-mail" || token === "mail")) {
|
|
7350
|
+
return "email";
|
|
7351
|
+
}
|
|
7352
|
+
if (tokens.some((token) => token === "phone" || token === "number" || token === "mobile" || token === "cell" || token === "telephone")) {
|
|
7353
|
+
return "phone";
|
|
7354
|
+
}
|
|
7355
|
+
if (tokens.some((token) => token === "address" || token === "street" || token === "mailing")) {
|
|
7356
|
+
return "address";
|
|
7357
|
+
}
|
|
7358
|
+
if (tokens.some(
|
|
7359
|
+
(token) => token === "location" || token === "live" || token === "lives" || token === "reside" || token === "resides" || token === "located" || token === "home" || token === "city"
|
|
7360
|
+
)) {
|
|
7361
|
+
return "location";
|
|
7362
|
+
}
|
|
7363
|
+
if (tokens.some((token) => ENTITY_ATTRIBUTE_KIND_TOKENS.identity.includes(token))) {
|
|
7364
|
+
return "identity";
|
|
7365
|
+
}
|
|
7366
|
+
return void 0;
|
|
7367
|
+
}
|
|
7368
|
+
function normalizeEntityAttributeEntity(rawEntityText) {
|
|
7369
|
+
const cleaned = rawEntityText ? normalizeEntityAttributeWhitespace(rawEntityText).replace(/^[("'`]+/u, "").replace(/[)"'`?!.,]+$/u, "").replace(/^(?:the|a|an)\s+/iu, "").trim() : "";
|
|
7370
|
+
if (cleaned.length === 0) {
|
|
7371
|
+
return void 0;
|
|
7372
|
+
}
|
|
7373
|
+
const normalized = normalizeEntityAttributeText(cleaned);
|
|
7374
|
+
const wordCount = cleaned.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
7375
|
+
if (wordCount === 0 || wordCount > ENTITY_ATTRIBUTE_MAX_WORDS || ENTITY_ATTRIBUTE_GENERIC_ENTITY_RE.test(normalized) || ENTITY_ATTRIBUTE_CONTEXTUAL_PREFIX_RE.test(normalized) || ENTITY_ATTRIBUTE_CONTEXTUAL_TIME_RE.test(normalized) || ENTITY_ATTRIBUTE_CONTEXTUAL_ACTIVITY_RE.test(normalized)) {
|
|
7376
|
+
return void 0;
|
|
7377
|
+
}
|
|
7378
|
+
return cleaned;
|
|
7379
|
+
}
|
|
7380
|
+
function normalizeEntityAttributeWhitespace(text) {
|
|
7381
|
+
return text.replace(/\s+/gu, " ").trim();
|
|
7382
|
+
}
|
|
7383
|
+
function normalizeEntityAttributeText(text) {
|
|
7384
|
+
return normalizeEntityAttributeWhitespace(text).normalize("NFKC").toLocaleLowerCase();
|
|
7385
|
+
}
|
|
6265
7386
|
function normalizeMode(value) {
|
|
6266
7387
|
return value === "entries" || value === "episodes" || value === "procedures" ? value : "auto";
|
|
6267
7388
|
}
|
|
@@ -6362,7 +7483,6 @@ export {
|
|
|
6362
7483
|
DEFAULT_SURGEON_RETIREMENT_PROTECT_MIN_IMPORTANCE,
|
|
6363
7484
|
DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS,
|
|
6364
7485
|
DEFAULT_CLAIM_EXTRACTION_CONCURRENCY,
|
|
6365
|
-
isAgenrAuthMethod,
|
|
6366
7486
|
authMethodToProvider,
|
|
6367
7487
|
getAuthMethodDefinition,
|
|
6368
7488
|
toAgenrConfigInput,
|
|
@@ -6378,7 +7498,16 @@ export {
|
|
|
6378
7498
|
createEmbeddingClient,
|
|
6379
7499
|
resolveEmbeddingApiKey,
|
|
6380
7500
|
resolveEmbeddingModel,
|
|
7501
|
+
probeLlmCredentials,
|
|
7502
|
+
createLlmClient,
|
|
7503
|
+
resolveModel,
|
|
7504
|
+
resolveLlmCredentials,
|
|
7505
|
+
resolveLlmApiKey,
|
|
7506
|
+
createOpenAICrossEncoder,
|
|
7507
|
+
resolveCrossEncoderApiKey,
|
|
6381
7508
|
createRecallAdapter,
|
|
7509
|
+
attachCrossEncoderPort,
|
|
6382
7510
|
projectClaimCentricRecallEntry,
|
|
7511
|
+
runProcedureRecall,
|
|
6383
7512
|
runUnifiedRecall
|
|
6384
7513
|
};
|