nexus-ai-pro 2.2.0 → 2.4.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/API_STABILITY.md +225 -0
- package/CHANGELOG.md +325 -1
- package/README.md +20 -6
- package/SECURITY.md +140 -0
- package/dist/adapter-kit/contracts.d.ts +87 -0
- package/dist/adapter-kit/contracts.js +237 -0
- package/dist/adapter-kit/errors.d.ts +39 -0
- package/dist/adapter-kit/errors.js +52 -0
- package/dist/adapter-kit/index.d.ts +144 -0
- package/dist/adapter-kit/index.js +178 -0
- package/dist/adapter-kit/semver.d.ts +12 -0
- package/dist/adapter-kit/semver.js +55 -0
- package/dist/agent/capabilities.d.ts +39 -0
- package/dist/agent/capabilities.js +36 -0
- package/dist/agent/create-agent.d.ts +61 -11
- package/dist/agent/create-agent.js +136 -22
- package/dist/agent/index.d.ts +3 -2
- package/dist/agent/index.js +1 -1
- package/dist/agent/middleware/approval.d.ts +26 -0
- package/dist/agent/middleware/approval.js +23 -0
- package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist/agent/middleware/context-editor.d.ts +52 -0
- package/dist/agent/middleware/context-editor.js +66 -0
- package/dist/agent/middleware/filesystem.d.ts +44 -0
- package/dist/agent/middleware/filesystem.js +83 -0
- package/dist/agent/middleware/index.d.ts +19 -0
- package/dist/agent/middleware/index.js +9 -0
- package/dist/agent/middleware/limits.d.ts +42 -0
- package/dist/agent/middleware/limits.js +52 -0
- package/dist/agent/middleware/models.d.ts +48 -0
- package/dist/agent/middleware/models.js +44 -0
- package/dist/agent/middleware/pii.d.ts +67 -0
- package/dist/agent/middleware/pii.js +142 -0
- package/dist/agent/middleware/retry.d.ts +66 -0
- package/dist/agent/middleware/retry.js +59 -0
- package/dist/agent/middleware/shared.d.ts +41 -0
- package/dist/agent/middleware/shared.js +73 -0
- package/dist/agent/middleware/tool-selector.d.ts +64 -0
- package/dist/agent/middleware/tool-selector.js +106 -0
- package/dist/agent/permissions.d.ts +113 -0
- package/dist/agent/permissions.js +180 -0
- package/dist/agent/sandbox.d.ts +151 -0
- package/dist/agent/sandbox.js +257 -0
- package/dist/agent/tool.d.ts +8 -3
- package/dist/agent/tool.js +14 -3
- package/dist/core/secure-stream.js +4 -3
- package/dist/deep-agent/index.d.ts +109 -0
- package/dist/deep-agent/index.js +216 -0
- package/dist/embeddings/adapters.js +2 -1
- package/dist/evals/judge.js +2 -5
- package/dist/evaluate/cache.d.ts +0 -14
- package/dist/evaluate/cache.js +0 -30
- package/dist/evaluate/file-cache.d.ts +15 -0
- package/dist/evaluate/file-cache.js +30 -0
- package/dist/evaluate/index.d.ts +2 -1
- package/dist/evaluate/index.js +2 -1
- package/dist/evaluate/review.js +2 -2
- package/dist/evaluate/run.js +2 -2
- package/dist/evaluate/version.js +3 -3
- package/dist/graph/functional.d.ts +9 -0
- package/dist/graph/functional.js +20 -3
- package/dist/graph/graph.js +27 -6
- package/dist/graph/lint.d.ts +4 -1
- package/dist/graph/lint.js +35 -0
- package/dist/graph/node-cache.js +3 -3
- package/dist/hallucination/rag.d.ts +4 -1
- package/dist/hallucination/rag.js +6 -1
- package/dist/images/asset-stores.js +2 -3
- package/dist/images/asset-support.js +2 -2
- package/dist/images/assets.js +1 -2
- package/dist/images/evals.js +4 -4
- package/dist/insights/proposals.js +1 -2
- package/dist/insights/regressions.d.ts +26 -2
- package/dist/insights/regressions.js +129 -3
- package/dist/loaders/git.js +8 -1
- package/dist/loaders/web.js +7 -1
- package/dist/mcp/client.d.ts +14 -0
- package/dist/mcp/client.js +59 -1
- package/dist/mcp/registry.d.ts +6 -0
- package/dist/mcp/registry.js +7 -0
- package/dist/postgres/fulltext.d.ts +67 -0
- package/dist/postgres/fulltext.js +91 -0
- package/dist/prompts/file.js +28 -11
- package/dist/protocols/a2a.d.ts +235 -0
- package/dist/protocols/a2a.js +386 -0
- package/dist/protocols/acp.d.ts +87 -0
- package/dist/protocols/acp.js +322 -0
- package/dist/protocols/ag-ui.d.ts +120 -0
- package/dist/protocols/ag-ui.js +180 -0
- package/dist/protocols/shared.d.ts +80 -0
- package/dist/protocols/shared.js +49 -0
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/cohere.js +2 -2
- package/dist/providers/google.js +2 -11
- package/dist/providers/openai.js +1 -0
- package/dist/rag/elasticsearch.d.ts +71 -0
- package/dist/rag/elasticsearch.js +130 -0
- package/dist/rag/file-ingestion.js +15 -9
- package/dist/rag/pipeline.d.ts +140 -0
- package/dist/rag/pipeline.js +187 -0
- package/dist/rag/rerankers.d.ts +92 -0
- package/dist/rag/rerankers.js +97 -0
- package/dist/rag/retrievers.d.ts +12 -1
- package/dist/runtime/detect.d.ts +17 -0
- package/dist/runtime/detect.js +16 -0
- package/dist/runtime/index.d.ts +19 -0
- package/dist/runtime/index.js +7 -0
- package/dist/security/injection-detector.d.ts +2 -0
- package/dist/security/injection-detector.js +7 -1
- package/dist/security/input-guard.js +5 -2
- package/dist/security/output-guard.js +8 -4
- package/dist/security/pii-detector.js +22 -6
- package/dist/security/prompt-hardening.js +6 -2
- package/dist/security/semantic-injection-classifier.d.ts +2 -1
- package/dist/security/semantic-injection-classifier.js +31 -35
- package/dist/security/upload-scanner.d.ts +8 -0
- package/dist/security/upload-scanner.js +51 -9
- package/dist/server/assistant.d.ts +5 -1
- package/dist/server/assistant.js +4 -2
- package/dist/server/auth.d.ts +250 -0
- package/dist/server/auth.js +392 -0
- package/dist/server/deployments.d.ts +63 -1
- package/dist/server/deployments.js +75 -2
- package/dist/server/runs.d.ts +9 -0
- package/dist/server/runs.js +20 -0
- package/dist/server/server.js +5 -0
- package/dist/store/memory.d.ts +10 -0
- package/dist/store/memory.js +10 -0
- package/dist/testing/record.js +2 -2
- package/dist/tracing/stores.js +2 -1
- package/dist/tracing/tracer.js +2 -2
- package/dist/types/config.d.ts +20 -0
- package/dist/types/graph.d.ts +38 -0
- package/dist/types/messages.d.ts +36 -2
- package/dist/types/principal.d.ts +21 -0
- package/dist/types/principal.js +1 -0
- package/dist/types/server.d.ts +8 -9
- package/dist/utils/base64.d.ts +4 -0
- package/dist/utils/base64.js +21 -0
- package/dist/utils/checksums.d.ts +22 -0
- package/dist/utils/checksums.js +39 -0
- package/dist/utils/ids.d.ts +8 -0
- package/dist/utils/ids.js +26 -2
- package/dist/utils/sha256.d.ts +22 -0
- package/dist/utils/sha256.js +120 -0
- package/dist/utils/tenant.d.ts +11 -0
- package/dist/utils/tenant.js +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
- package/dist-cjs/adapter-kit/contracts.js +243 -0
- package/dist-cjs/adapter-kit/errors.d.ts +39 -0
- package/dist-cjs/adapter-kit/errors.js +57 -0
- package/dist-cjs/adapter-kit/index.d.ts +144 -0
- package/dist-cjs/adapter-kit/index.js +194 -0
- package/dist-cjs/adapter-kit/semver.d.ts +12 -0
- package/dist-cjs/adapter-kit/semver.js +59 -0
- package/dist-cjs/agent/capabilities.d.ts +39 -0
- package/dist-cjs/agent/capabilities.js +41 -0
- package/dist-cjs/agent/create-agent.d.ts +61 -11
- package/dist-cjs/agent/create-agent.js +136 -22
- package/dist-cjs/agent/index.d.ts +3 -2
- package/dist-cjs/agent/index.js +4 -4
- package/dist-cjs/agent/middleware/approval.d.ts +26 -0
- package/dist-cjs/agent/middleware/approval.js +26 -0
- package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
- package/dist-cjs/agent/middleware/context-editor.js +70 -0
- package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
- package/dist-cjs/agent/middleware/filesystem.js +86 -0
- package/dist-cjs/agent/middleware/index.d.ts +19 -0
- package/dist-cjs/agent/middleware/index.js +28 -0
- package/dist-cjs/agent/middleware/limits.d.ts +42 -0
- package/dist-cjs/agent/middleware/limits.js +57 -0
- package/dist-cjs/agent/middleware/models.d.ts +48 -0
- package/dist-cjs/agent/middleware/models.js +48 -0
- package/dist-cjs/agent/middleware/pii.d.ts +67 -0
- package/dist-cjs/agent/middleware/pii.js +147 -0
- package/dist-cjs/agent/middleware/retry.d.ts +66 -0
- package/dist-cjs/agent/middleware/retry.js +63 -0
- package/dist-cjs/agent/middleware/shared.d.ts +41 -0
- package/dist-cjs/agent/middleware/shared.js +83 -0
- package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
- package/dist-cjs/agent/middleware/tool-selector.js +109 -0
- package/dist-cjs/agent/permissions.d.ts +113 -0
- package/dist-cjs/agent/permissions.js +189 -0
- package/dist-cjs/agent/sandbox.d.ts +151 -0
- package/dist-cjs/agent/sandbox.js +297 -0
- package/dist-cjs/agent/tool.d.ts +8 -3
- package/dist-cjs/agent/tool.js +14 -3
- package/dist-cjs/core/secure-stream.js +4 -3
- package/dist-cjs/deep-agent/index.d.ts +109 -0
- package/dist-cjs/deep-agent/index.js +219 -0
- package/dist-cjs/embeddings/adapters.js +2 -1
- package/dist-cjs/evals/judge.js +2 -5
- package/dist-cjs/evaluate/cache.d.ts +0 -14
- package/dist-cjs/evaluate/cache.js +1 -65
- package/dist-cjs/evaluate/file-cache.d.ts +15 -0
- package/dist-cjs/evaluate/file-cache.js +67 -0
- package/dist-cjs/evaluate/index.d.ts +2 -1
- package/dist-cjs/evaluate/index.js +3 -2
- package/dist-cjs/evaluate/review.js +2 -2
- package/dist-cjs/evaluate/run.js +2 -2
- package/dist-cjs/evaluate/version.js +3 -3
- package/dist-cjs/graph/functional.d.ts +9 -0
- package/dist-cjs/graph/functional.js +20 -3
- package/dist-cjs/graph/graph.js +27 -6
- package/dist-cjs/graph/lint.d.ts +4 -1
- package/dist-cjs/graph/lint.js +35 -0
- package/dist-cjs/graph/node-cache.js +3 -3
- package/dist-cjs/hallucination/rag.d.ts +4 -1
- package/dist-cjs/hallucination/rag.js +6 -1
- package/dist-cjs/images/asset-stores.js +2 -3
- package/dist-cjs/images/asset-support.js +2 -2
- package/dist-cjs/images/assets.js +1 -2
- package/dist-cjs/images/evals.js +4 -4
- package/dist-cjs/insights/proposals.js +1 -2
- package/dist-cjs/insights/regressions.d.ts +26 -2
- package/dist-cjs/insights/regressions.js +129 -3
- package/dist-cjs/loaders/git.js +8 -1
- package/dist-cjs/loaders/web.js +7 -1
- package/dist-cjs/mcp/client.d.ts +14 -0
- package/dist-cjs/mcp/client.js +60 -1
- package/dist-cjs/mcp/registry.d.ts +6 -0
- package/dist-cjs/mcp/registry.js +7 -0
- package/dist-cjs/postgres/fulltext.d.ts +67 -0
- package/dist-cjs/postgres/fulltext.js +129 -0
- package/dist-cjs/prompts/file.js +28 -11
- package/dist-cjs/protocols/a2a.d.ts +235 -0
- package/dist-cjs/protocols/a2a.js +393 -0
- package/dist-cjs/protocols/acp.d.ts +87 -0
- package/dist-cjs/protocols/acp.js +326 -0
- package/dist-cjs/protocols/ag-ui.d.ts +120 -0
- package/dist-cjs/protocols/ag-ui.js +186 -0
- package/dist-cjs/protocols/shared.d.ts +80 -0
- package/dist-cjs/protocols/shared.js +59 -0
- package/dist-cjs/providers/anthropic.js +1 -0
- package/dist-cjs/providers/cohere.js +2 -2
- package/dist-cjs/providers/google.js +3 -12
- package/dist-cjs/providers/openai.js +1 -0
- package/dist-cjs/rag/elasticsearch.d.ts +71 -0
- package/dist-cjs/rag/elasticsearch.js +135 -0
- package/dist-cjs/rag/file-ingestion.js +14 -8
- package/dist-cjs/rag/pipeline.d.ts +140 -0
- package/dist-cjs/rag/pipeline.js +191 -0
- package/dist-cjs/rag/rerankers.d.ts +92 -0
- package/dist-cjs/rag/rerankers.js +107 -0
- package/dist-cjs/rag/retrievers.d.ts +12 -1
- package/dist-cjs/runtime/detect.d.ts +17 -0
- package/dist-cjs/runtime/detect.js +19 -0
- package/dist-cjs/runtime/index.d.ts +19 -0
- package/dist-cjs/runtime/index.js +31 -0
- package/dist-cjs/security/injection-detector.d.ts +2 -0
- package/dist-cjs/security/injection-detector.js +7 -1
- package/dist-cjs/security/input-guard.js +5 -2
- package/dist-cjs/security/output-guard.js +8 -4
- package/dist-cjs/security/pii-detector.js +22 -6
- package/dist-cjs/security/prompt-hardening.js +6 -2
- package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
- package/dist-cjs/security/semantic-injection-classifier.js +31 -35
- package/dist-cjs/security/upload-scanner.d.ts +8 -0
- package/dist-cjs/security/upload-scanner.js +53 -9
- package/dist-cjs/server/assistant.d.ts +5 -1
- package/dist-cjs/server/assistant.js +4 -2
- package/dist-cjs/server/auth.d.ts +250 -0
- package/dist-cjs/server/auth.js +402 -0
- package/dist-cjs/server/deployments.d.ts +63 -1
- package/dist-cjs/server/deployments.js +75 -2
- package/dist-cjs/server/runs.d.ts +9 -0
- package/dist-cjs/server/runs.js +20 -0
- package/dist-cjs/server/server.js +5 -0
- package/dist-cjs/store/memory.d.ts +10 -0
- package/dist-cjs/store/memory.js +10 -0
- package/dist-cjs/testing/record.js +2 -2
- package/dist-cjs/tracing/stores.js +2 -1
- package/dist-cjs/tracing/tracer.js +2 -2
- package/dist-cjs/types/config.d.ts +20 -0
- package/dist-cjs/types/graph.d.ts +38 -0
- package/dist-cjs/types/messages.d.ts +36 -2
- package/dist-cjs/types/principal.d.ts +21 -0
- package/dist-cjs/types/principal.js +2 -0
- package/dist-cjs/types/server.d.ts +8 -9
- package/dist-cjs/utils/base64.d.ts +4 -0
- package/dist-cjs/utils/base64.js +25 -0
- package/dist-cjs/utils/checksums.d.ts +22 -0
- package/dist-cjs/utils/checksums.js +46 -0
- package/dist-cjs/utils/ids.d.ts +8 -0
- package/dist-cjs/utils/ids.js +28 -2
- package/dist-cjs/utils/sha256.d.ts +22 -0
- package/dist-cjs/utils/sha256.js +125 -0
- package/dist-cjs/utils/tenant.d.ts +11 -0
- package/dist-cjs/utils/tenant.js +15 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +7 -3
- package/package.json +193 -6
|
@@ -33,7 +33,10 @@ export interface CompareRunsOptions {
|
|
|
33
33
|
metrics?: readonly RegressionMetric[];
|
|
34
34
|
/** Feedback keys whose mean score is compared, such as `helpfulness`. */
|
|
35
35
|
feedback?: readonly string[];
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Runs each side needs before it is judged; for a feedback score, runs that have one. Defaults to
|
|
38
|
+
* 20. A run counts once per feedback key, however many times it was rated.
|
|
39
|
+
*/
|
|
37
40
|
minRuns?: number;
|
|
38
41
|
/** A rise in p95 latency, relative, that counts. Defaults to 0.25, a quarter slower. */
|
|
39
42
|
latencyIncrease?: number;
|
|
@@ -44,6 +47,22 @@ export interface CompareRunsOptions {
|
|
|
44
47
|
minLatencyChangeMs?: number;
|
|
45
48
|
/** A rise in mean cost, relative, that counts. Defaults to 0.25. */
|
|
46
49
|
costIncrease?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Judges every metric the way an evaluation comparison does, at this confidence, such as 0.95. Each
|
|
52
|
+
* metric is resampled by bootstrap, and it regresses only when two things hold: the change's
|
|
53
|
+
* interval does not reach zero, and the change is at least that metric's minimum effect. Noise and
|
|
54
|
+
* trivia both stay below the bar. Without it, the error rate uses a z-test at 95%, and latency and
|
|
55
|
+
* cost use their margins alone.
|
|
56
|
+
*/
|
|
57
|
+
confidence?: number;
|
|
58
|
+
/** The smallest rise in the error rate, absolute, that counts under `confidence`. Defaults to 0.02. */
|
|
59
|
+
minErrorRateIncrease?: number;
|
|
60
|
+
/** The smallest fall in a feedback score's mean, absolute, that counts under `confidence`. Defaults to 0.05. */
|
|
61
|
+
minFeedbackDrop?: number;
|
|
62
|
+
/** Bootstrap resamples under `confidence`. Defaults to 1,000. */
|
|
63
|
+
resamples?: number;
|
|
64
|
+
/** Seeds the resampling, so the same runs always get the same verdict. Defaults to 1. */
|
|
65
|
+
seed?: number;
|
|
47
66
|
}
|
|
48
67
|
/** Options for `detectRegressions()`. */
|
|
49
68
|
export interface DetectRegressionsOptions {
|
|
@@ -61,7 +80,10 @@ export interface DetectRegressionsOptions {
|
|
|
61
80
|
metrics?: readonly RegressionMetric[];
|
|
62
81
|
/** Feedback keys whose mean score is compared, such as `helpfulness`. */
|
|
63
82
|
feedback?: readonly string[];
|
|
64
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Runs each window needs before a group is judged; for a feedback score, runs that have one.
|
|
85
|
+
* Defaults to 20.
|
|
86
|
+
*/
|
|
65
87
|
minRuns?: number;
|
|
66
88
|
/** A rise in p95 latency, relative, that counts. Defaults to 0.25, a quarter slower. */
|
|
67
89
|
latencyIncrease?: number;
|
|
@@ -96,6 +118,8 @@ export interface Regression {
|
|
|
96
118
|
};
|
|
97
119
|
/** One readable line. */
|
|
98
120
|
summary: string;
|
|
121
|
+
/** The change's interval, in the units of `change`, when the comparison was judged with `confidence`. */
|
|
122
|
+
interval?: [number, number];
|
|
99
123
|
}
|
|
100
124
|
/**
|
|
101
125
|
* Compares a current window of traces with a baseline window and reports what got worse, per run
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
function runScores(runs, key) {
|
|
2
|
+
return runs.flatMap((run) => {
|
|
3
|
+
const given = (run.feedback ?? [])
|
|
4
|
+
.filter((item) => item.key === key)
|
|
5
|
+
.map((item) => item.score)
|
|
6
|
+
.filter(isNumber);
|
|
7
|
+
return given.length ? [given.reduce((sum, score) => sum + score, 0) / given.length] : [];
|
|
8
|
+
});
|
|
9
|
+
}
|
|
1
10
|
export async function detectRegressions(options) {
|
|
2
11
|
const read = async (window) => {
|
|
3
12
|
const runs = await options.store.query({
|
|
@@ -27,6 +36,8 @@ export async function detectRegressions(options) {
|
|
|
27
36
|
return regressions;
|
|
28
37
|
}
|
|
29
38
|
export function compareRuns(baseline, current, options = {}) {
|
|
39
|
+
if (options.confidence !== undefined)
|
|
40
|
+
return judgedRegressions(baseline, current, options, options.confidence);
|
|
30
41
|
const name = options.group ?? 'runs';
|
|
31
42
|
const minRuns = options.minRuns ?? 20;
|
|
32
43
|
const metrics = new Set(options.metrics ?? ['error-rate', 'latency', 'cost']);
|
|
@@ -90,9 +101,7 @@ export function compareRuns(baseline, current, options = {}) {
|
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
for (const feedbackKey of options.feedback ?? []) {
|
|
93
|
-
const scores = (runs) => runs
|
|
94
|
-
.flatMap((run) => (run.feedback ?? []).filter((item) => item.key === feedbackKey).map((item) => item.score))
|
|
95
|
-
.filter(isNumber);
|
|
104
|
+
const scores = (runs) => runScores(runs, feedbackKey);
|
|
96
105
|
const before = scores(baseline);
|
|
97
106
|
const after = scores(current);
|
|
98
107
|
if (before.length < minRuns || after.length < minRuns)
|
|
@@ -114,6 +123,123 @@ export function compareRuns(baseline, current, options = {}) {
|
|
|
114
123
|
}
|
|
115
124
|
return regressions;
|
|
116
125
|
}
|
|
126
|
+
function judgedRegressions(baseline, current, options, confidence) {
|
|
127
|
+
if (!(confidence > 0 && confidence < 1))
|
|
128
|
+
throw new RangeError('confidence is between 0 and 1, such as 0.95');
|
|
129
|
+
const name = options.group ?? 'runs';
|
|
130
|
+
const minRuns = options.minRuns ?? 20;
|
|
131
|
+
const metrics = new Set(options.metrics ?? ['error-rate', 'latency', 'cost']);
|
|
132
|
+
const judge = (before, after, shape) => before.length >= minRuns && after.length >= minRuns
|
|
133
|
+
? metricVerdict(before, after, {
|
|
134
|
+
...shape,
|
|
135
|
+
confidence,
|
|
136
|
+
resamples: options.resamples ?? 1_000,
|
|
137
|
+
seed: options.seed ?? 1,
|
|
138
|
+
})
|
|
139
|
+
: undefined;
|
|
140
|
+
const regressions = [];
|
|
141
|
+
const add = (metric, verdict, counts, line) => {
|
|
142
|
+
if (!verdict?.regressed)
|
|
143
|
+
return;
|
|
144
|
+
regressions.push({
|
|
145
|
+
group: name,
|
|
146
|
+
metric,
|
|
147
|
+
baseline: verdict.baseline,
|
|
148
|
+
current: verdict.current,
|
|
149
|
+
change: verdict.change,
|
|
150
|
+
samples: counts,
|
|
151
|
+
interval: verdict.interval,
|
|
152
|
+
summary: `${name}: ${line} (${Math.round(confidence * 100)}% interval ${formatInterval(verdict.interval, metric)})`,
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const counts = { baseline: baseline.length, current: current.length };
|
|
156
|
+
if (metrics.has('error-rate')) {
|
|
157
|
+
const errors = (runs) => runs.map((run) => (run.status === 'error' ? 1 : 0));
|
|
158
|
+
const verdict = judge(errors(baseline), errors(current), {
|
|
159
|
+
statistic: meanOf,
|
|
160
|
+
relative: false,
|
|
161
|
+
worse: 'rise',
|
|
162
|
+
minEffect: options.minErrorRateIncrease ?? 0.02,
|
|
163
|
+
});
|
|
164
|
+
add('error-rate', verdict, counts, `error rate rose from ${percent(verdict?.baseline ?? 0)} to ${percent(verdict?.current ?? 0)}`);
|
|
165
|
+
}
|
|
166
|
+
if (metrics.has('latency')) {
|
|
167
|
+
const latencies = (runs) => runs.map((run) => run.latencyMs).filter(isNumber);
|
|
168
|
+
const before = latencies(baseline);
|
|
169
|
+
const after = latencies(current);
|
|
170
|
+
const verdict = judge(before, after, {
|
|
171
|
+
statistic: (values) => percentile(values, 0.95) ?? 0,
|
|
172
|
+
relative: true,
|
|
173
|
+
worse: 'rise',
|
|
174
|
+
minEffect: options.latencyIncrease ?? 0.25,
|
|
175
|
+
});
|
|
176
|
+
const large = verdict && verdict.current - verdict.baseline >= (options.minLatencyChangeMs ?? 0);
|
|
177
|
+
add('latency-p95', large ? verdict : undefined, { baseline: before.length, current: after.length }, `p95 latency rose from ${Math.round(verdict?.baseline ?? 0)}ms to ${Math.round(verdict?.current ?? 0)}ms`);
|
|
178
|
+
}
|
|
179
|
+
if (metrics.has('cost')) {
|
|
180
|
+
const costs = (runs) => runs.map((run) => run.cost).filter(isNumber);
|
|
181
|
+
const before = costs(baseline);
|
|
182
|
+
const after = costs(current);
|
|
183
|
+
const verdict = judge(before, after, {
|
|
184
|
+
statistic: meanOf,
|
|
185
|
+
relative: true,
|
|
186
|
+
worse: 'rise',
|
|
187
|
+
minEffect: options.costIncrease ?? 0.25,
|
|
188
|
+
});
|
|
189
|
+
add('cost', verdict, { baseline: before.length, current: after.length }, `mean cost rose from $${(verdict?.baseline ?? 0).toFixed(4)} to $${(verdict?.current ?? 0).toFixed(4)}`);
|
|
190
|
+
}
|
|
191
|
+
for (const feedbackKey of options.feedback ?? []) {
|
|
192
|
+
const scores = (runs) => runScores(runs, feedbackKey);
|
|
193
|
+
const before = scores(baseline);
|
|
194
|
+
const after = scores(current);
|
|
195
|
+
const verdict = judge(before, after, {
|
|
196
|
+
statistic: meanOf,
|
|
197
|
+
relative: false,
|
|
198
|
+
worse: 'fall',
|
|
199
|
+
minEffect: options.minFeedbackDrop ?? 0.05,
|
|
200
|
+
});
|
|
201
|
+
add(`feedback:${feedbackKey}`, verdict, { baseline: before.length, current: after.length }, `${feedbackKey} fell from ${(verdict?.baseline ?? 0).toFixed(2)} to ${(verdict?.current ?? 0).toFixed(2)}`);
|
|
202
|
+
}
|
|
203
|
+
return regressions;
|
|
204
|
+
}
|
|
205
|
+
function metricVerdict(baseline, current, options) {
|
|
206
|
+
const change = (before, after) => options.relative ? (before === 0 ? 0 : after / before - 1) : after - before;
|
|
207
|
+
const before = options.statistic(baseline);
|
|
208
|
+
const after = options.statistic(current);
|
|
209
|
+
const random = seeded(options.seed);
|
|
210
|
+
const resample = (values) => Array.from({ length: values.length }, () => values[Math.floor(random() * values.length)]);
|
|
211
|
+
const draws = [];
|
|
212
|
+
for (let index = 0; index < options.resamples; index += 1) {
|
|
213
|
+
draws.push(change(options.statistic(resample(baseline)), options.statistic(resample(current))));
|
|
214
|
+
}
|
|
215
|
+
draws.sort((a, b) => a - b);
|
|
216
|
+
const tail = (1 - options.confidence) / 2;
|
|
217
|
+
const at = (quantile) => draws[Math.min(draws.length - 1, Math.max(0, Math.floor(quantile * draws.length)))];
|
|
218
|
+
const interval = [at(tail), at(1 - tail)];
|
|
219
|
+
const observed = change(before, after);
|
|
220
|
+
const regressed = options.worse === 'rise'
|
|
221
|
+
? observed >= options.minEffect && interval[0] > 0
|
|
222
|
+
: -observed >= options.minEffect && interval[1] < 0;
|
|
223
|
+
return { baseline: before, current: after, change: observed, interval, regressed };
|
|
224
|
+
}
|
|
225
|
+
function seeded(seed) {
|
|
226
|
+
let state = seed >>> 0;
|
|
227
|
+
return () => {
|
|
228
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
229
|
+
let value = state;
|
|
230
|
+
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
231
|
+
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
232
|
+
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function meanOf(values) {
|
|
236
|
+
return mean(values) ?? 0;
|
|
237
|
+
}
|
|
238
|
+
function formatInterval(interval, metric) {
|
|
239
|
+
const relative = metric === 'latency-p95' || metric === 'cost';
|
|
240
|
+
const show = (value) => (relative ? `${value >= 0 ? '+' : ''}${Math.round(value * 100)}%` : value.toFixed(3));
|
|
241
|
+
return `${show(interval[0])} to ${show(interval[1])}`;
|
|
242
|
+
}
|
|
117
243
|
function isNumber(value) {
|
|
118
244
|
return typeof value === 'number' && Number.isFinite(value);
|
|
119
245
|
}
|
package/dist/loaders/git.js
CHANGED
|
@@ -32,6 +32,8 @@ export async function* loadGitRepository(repository, options = {}) {
|
|
|
32
32
|
const files = (await git(['ls-files', '-z'], directory, options)).split('\0').filter(Boolean).sort();
|
|
33
33
|
const extensions = options.extensions && new Set(options.extensions.map((extension) => extension.toLowerCase()));
|
|
34
34
|
const maxFileBytes = options.maxFileBytes ?? 1_000_000;
|
|
35
|
+
const { lstat, realpath } = await import('node:fs/promises');
|
|
36
|
+
const checkout = await realpath(directory);
|
|
35
37
|
for (const file of files) {
|
|
36
38
|
const extension = path.extname(file).toLowerCase();
|
|
37
39
|
if (extensions && !extensions.has(extension))
|
|
@@ -40,7 +42,12 @@ export async function* loadGitRepository(repository, options = {}) {
|
|
|
40
42
|
continue;
|
|
41
43
|
let bytes;
|
|
42
44
|
try {
|
|
43
|
-
|
|
45
|
+
const full = path.join(directory, file);
|
|
46
|
+
if ((await lstat(full)).isSymbolicLink())
|
|
47
|
+
continue;
|
|
48
|
+
if (!(await realpath(full)).startsWith(checkout + path.sep))
|
|
49
|
+
continue;
|
|
50
|
+
bytes = new Uint8Array(await readFile(full));
|
|
44
51
|
}
|
|
45
52
|
catch {
|
|
46
53
|
continue;
|
package/dist/loaders/web.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseUrl, safeFetch } from '../utils/safe-fetch.js';
|
|
2
2
|
import { htmlToText } from './html.js';
|
|
3
|
+
const MAX_SITEMAP_BYTES = 50 * 1024 * 1024;
|
|
3
4
|
export class WebLoaderError extends Error {
|
|
4
5
|
url;
|
|
5
6
|
status;
|
|
@@ -49,7 +50,12 @@ export async function sitemapUrls(url, options = {}) {
|
|
|
49
50
|
let xml;
|
|
50
51
|
if (bytes[0] === 0x1f && bytes[1] === 0x8b) {
|
|
51
52
|
const { gunzipSync } = await import('node:zlib');
|
|
52
|
-
|
|
53
|
+
try {
|
|
54
|
+
xml = gunzipSync(bytes, { maxOutputLength: MAX_SITEMAP_BYTES }).toString('utf8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new WebLoaderError(`${sitemap} does not decompress to a sitemap of at most 50 MB: ${error instanceof Error ? error.message : String(error)}`, sitemap);
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
else
|
|
55
61
|
xml = new TextDecoder().decode(bytes);
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -122,7 +122,21 @@ export interface StdioClientOptions {
|
|
|
122
122
|
env?: Record<string, string>;
|
|
123
123
|
/** Working directory for the process. */
|
|
124
124
|
cwd?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Which of this process's environment variables the server inherits, besides `env`. By default,
|
|
127
|
+
* only what a process needs to start: the path, the home and temporary directories, the user, the
|
|
128
|
+
* locale and terminal, the Windows system variables, proxies, and certificate settings. A server is
|
|
129
|
+
* often third-party code, and must not see this process's API keys or database URLs. A list adds
|
|
130
|
+
* the variables it names, `true` passes the whole environment, as releases before 2.4 did, and
|
|
131
|
+
* `false` passes nothing but `env`.
|
|
132
|
+
*/
|
|
133
|
+
inheritEnv?: boolean | readonly string[];
|
|
125
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The environment a server process starts with. Names compare without case, as Windows compares
|
|
137
|
+
* them, and `LC_` locale settings are kept with the rest of the locale.
|
|
138
|
+
*/
|
|
139
|
+
export declare function serverEnvironment(source: Record<string, string | undefined>, options?: Pick<StdioClientOptions, 'env' | 'inheritEnv'>): Record<string, string>;
|
|
126
140
|
/**
|
|
127
141
|
* Runs an MCP server as a child process and speaks to it over its stdin and stdout.
|
|
128
142
|
*
|
package/dist/mcp/client.js
CHANGED
|
@@ -131,6 +131,64 @@ export class McpClient {
|
|
|
131
131
|
this.settle(id, undefined, error);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
const INHERITED_ENV = new Set([
|
|
135
|
+
'PATH',
|
|
136
|
+
'PATHEXT',
|
|
137
|
+
'HOME',
|
|
138
|
+
'USER',
|
|
139
|
+
'USERNAME',
|
|
140
|
+
'LOGNAME',
|
|
141
|
+
'SHELL',
|
|
142
|
+
'TERM',
|
|
143
|
+
'LANG',
|
|
144
|
+
'LANGUAGE',
|
|
145
|
+
'TZ',
|
|
146
|
+
'TMPDIR',
|
|
147
|
+
'TEMP',
|
|
148
|
+
'TMP',
|
|
149
|
+
'XDG_CONFIG_HOME',
|
|
150
|
+
'XDG_CACHE_HOME',
|
|
151
|
+
'XDG_DATA_HOME',
|
|
152
|
+
'XDG_RUNTIME_DIR',
|
|
153
|
+
'SYSTEMROOT',
|
|
154
|
+
'SYSTEMDRIVE',
|
|
155
|
+
'WINDIR',
|
|
156
|
+
'COMSPEC',
|
|
157
|
+
'APPDATA',
|
|
158
|
+
'LOCALAPPDATA',
|
|
159
|
+
'USERPROFILE',
|
|
160
|
+
'HOMEDRIVE',
|
|
161
|
+
'HOMEPATH',
|
|
162
|
+
'PROGRAMDATA',
|
|
163
|
+
'PROGRAMFILES',
|
|
164
|
+
'PROGRAMFILES(X86)',
|
|
165
|
+
'COMMONPROGRAMFILES',
|
|
166
|
+
'PROCESSOR_ARCHITECTURE',
|
|
167
|
+
'NUMBER_OF_PROCESSORS',
|
|
168
|
+
'OS',
|
|
169
|
+
'HTTP_PROXY',
|
|
170
|
+
'HTTPS_PROXY',
|
|
171
|
+
'NO_PROXY',
|
|
172
|
+
'ALL_PROXY',
|
|
173
|
+
'NODE_EXTRA_CA_CERTS',
|
|
174
|
+
'SSL_CERT_FILE',
|
|
175
|
+
'SSL_CERT_DIR',
|
|
176
|
+
]);
|
|
177
|
+
export function serverEnvironment(source, options = {}) {
|
|
178
|
+
if (options.inheritEnv === false)
|
|
179
|
+
return { ...options.env };
|
|
180
|
+
const named = new Set((Array.isArray(options.inheritEnv) ? options.inheritEnv : []).map((name) => name.toUpperCase()));
|
|
181
|
+
const inherited = {};
|
|
182
|
+
for (const [name, value] of Object.entries(source)) {
|
|
183
|
+
if (value === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
const upper = name.toUpperCase();
|
|
186
|
+
if (options.inheritEnv === true || INHERITED_ENV.has(upper) || upper.startsWith('LC_') || named.has(upper)) {
|
|
187
|
+
inherited[name] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { ...inherited, ...options.env };
|
|
191
|
+
}
|
|
134
192
|
export function createStdioTransport(options) {
|
|
135
193
|
let child;
|
|
136
194
|
const decoder = new LineDecoder();
|
|
@@ -141,7 +199,7 @@ export function createStdioTransport(options) {
|
|
|
141
199
|
const { spawn } = await import('node:child_process');
|
|
142
200
|
const spawned = spawn(options.command, options.args ?? [], {
|
|
143
201
|
cwd: options.cwd,
|
|
144
|
-
env:
|
|
202
|
+
env: serverEnvironment(process.env, options),
|
|
145
203
|
stdio: ['pipe', 'pipe', 'inherit'],
|
|
146
204
|
});
|
|
147
205
|
spawned.stdout.setEncoding('utf8');
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface McpServerConfig {
|
|
|
15
15
|
env?: Record<string, string>;
|
|
16
16
|
/** Working directory for the process. */
|
|
17
17
|
cwd?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Which of this process's own environment variables a local server inherits, besides `env`. By
|
|
20
|
+
* default, only what a process needs to start, never an API key; a list adds variables by name,
|
|
21
|
+
* `true` passes the whole environment, and `false` nothing but `env`.
|
|
22
|
+
*/
|
|
23
|
+
inheritEnv?: boolean | string[];
|
|
18
24
|
/** The endpoint of a remote server, reached over HTTP. */
|
|
19
25
|
url?: string;
|
|
20
26
|
/** Headers sent to a remote server, such as authorization. */
|
package/dist/mcp/registry.js
CHANGED
|
@@ -59,6 +59,7 @@ export class McpRegistry {
|
|
|
59
59
|
...(resolved.args ? { args: resolved.args } : {}),
|
|
60
60
|
...(resolved.env ? { env: resolved.env } : {}),
|
|
61
61
|
...(resolved.cwd ? { cwd: resolved.cwd } : {}),
|
|
62
|
+
...(resolved.inheritEnv !== undefined ? { inheritEnv: resolved.inheritEnv } : {}),
|
|
62
63
|
})
|
|
63
64
|
: createHttpTransport({
|
|
64
65
|
url: resolved.url,
|
|
@@ -163,6 +164,12 @@ export function validateMcpConfig(value) {
|
|
|
163
164
|
throw new McpRegistryError(`Server "${name}": "${field}" is a list of strings`, name);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
167
|
+
const inherit = server.inheritEnv;
|
|
168
|
+
if (inherit !== undefined &&
|
|
169
|
+
typeof inherit !== 'boolean' &&
|
|
170
|
+
!(Array.isArray(inherit) && inherit.every((item) => typeof item === 'string'))) {
|
|
171
|
+
throw new McpRegistryError(`Server "${name}": "inheritEnv" is true, false, or a list of names`, name);
|
|
172
|
+
}
|
|
166
173
|
for (const field of ['env', 'headers']) {
|
|
167
174
|
const map = server[field];
|
|
168
175
|
if (map !== undefined &&
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RagChunk } from '../hallucination/rag.js';
|
|
2
|
+
import type { VectorSearchResult } from '../hallucination/retrieval.js';
|
|
3
|
+
import type { RetrieveOptions, SparseRetriever } from '../rag/retrievers.js';
|
|
4
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
5
|
+
import { type PostgresLikeClient } from './client.js';
|
|
6
|
+
import type { PostgresMigrateOptions } from './migrations.js';
|
|
7
|
+
/** Options for `PostgresKeywordIndex`. */
|
|
8
|
+
export interface PostgresKeywordIndexOptions {
|
|
9
|
+
/** Table name, optionally schema-qualified. Defaults to `nexus_keywords`. */
|
|
10
|
+
table?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Searches the table of a `PostgresVectorStore` instead of keeping chunks of its own. `migrate()`
|
|
13
|
+
* adds a generated search column and its index to that table, the store's writes keep it current,
|
|
14
|
+
* and `add()` and `delete()` leave writing to the store. One copy of every chunk, and keyword and
|
|
15
|
+
* vector search can never disagree about what exists.
|
|
16
|
+
*/
|
|
17
|
+
shared?: boolean;
|
|
18
|
+
/** The text search configuration: `english`, `simple`, `german`, and so on. Defaults to `english`. */
|
|
19
|
+
language?: string;
|
|
20
|
+
/**
|
|
21
|
+
* `any` (the default) finds chunks with any of the query's terms, ranked by how many and how close,
|
|
22
|
+
* as keyword search for retrieval should. `all` needs every term, read as a web search query, with
|
|
23
|
+
* quoted phrases and `-` for exclusion.
|
|
24
|
+
*/
|
|
25
|
+
match?: 'any' | 'all';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The versioned schema, which `migrate()` applies. Version 1 creates the table with a generated
|
|
29
|
+
* `tsvector` column and a GIN index, or, `shared`, adds the column and the index to a vector table.
|
|
30
|
+
*/
|
|
31
|
+
export declare function keywordIndexMigrations(options?: PostgresKeywordIndexOptions): SchemaMigration[];
|
|
32
|
+
/**
|
|
33
|
+
* Keyword search in Postgres, with its full-text search: stemmed terms, a GIN index, and
|
|
34
|
+
* `ts_rank_cd` ranking, so only the best matches leave the database however large the table.
|
|
35
|
+
*
|
|
36
|
+
* A `SparseRetriever`, so `hybridRetriever()` fuses it with `PostgresVectorStore`. With `shared`, it
|
|
37
|
+
* searches the vector store's own table, and the hybrid search runs over one copy of the data. A
|
|
38
|
+
* metadata filter runs in SQL, with the same meaning as the vector store's.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const vectors = new PostgresVectorStore(client, { dimensions: 1536, embed });
|
|
42
|
+
* const keywords = new PostgresKeywordIndex(client, { table: 'nexus_vectors', shared: true });
|
|
43
|
+
* await vectors.migrate();
|
|
44
|
+
* await keywords.migrate();
|
|
45
|
+
* const retriever = hybridRetriever([vectorRetriever(vectors), keywords]);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare class PostgresKeywordIndex implements SparseRetriever {
|
|
49
|
+
private readonly client;
|
|
50
|
+
private readonly options;
|
|
51
|
+
private readonly table;
|
|
52
|
+
private readonly language;
|
|
53
|
+
constructor(client: PostgresLikeClient, options?: PostgresKeywordIndexOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Applies this index's pending migrations, recorded in `nexus_schema_migrations`, under a lock.
|
|
56
|
+
* Never runs implicitly. With `shared`, run the vector store's `migrate()` first.
|
|
57
|
+
*/
|
|
58
|
+
migrate(options?: PostgresMigrateOptions): Promise<MigrationResult>;
|
|
59
|
+
/** Adds chunks, or replaces those whose id exists. With `shared`, does nothing: the vector store writes. */
|
|
60
|
+
add(chunks: readonly RagChunk[]): Promise<void>;
|
|
61
|
+
/** Removes chunks by id. With `shared`, does nothing: the vector store deletes. */
|
|
62
|
+
delete(ids: readonly string[]): Promise<void>;
|
|
63
|
+
/** The chunks that best match a query's terms, best first, scored from 0 to 1. */
|
|
64
|
+
retrieve(query: string, options?: RetrieveOptions): Promise<VectorSearchResult[]>;
|
|
65
|
+
/** Chunks held. */
|
|
66
|
+
size(): Promise<number>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { fromJson, quoteDerived, quoteTable } from './client.js';
|
|
2
|
+
import { SqlParams, textArray } from './sql.js';
|
|
3
|
+
const DEFAULT_TABLE = 'nexus_keywords';
|
|
4
|
+
function languageOf(options) {
|
|
5
|
+
const language = options.language ?? 'english';
|
|
6
|
+
if (!/^[a-z_]{1,63}$/.test(language))
|
|
7
|
+
throw new RangeError(`"${language}" is not a text search configuration name`);
|
|
8
|
+
return language;
|
|
9
|
+
}
|
|
10
|
+
export function keywordIndexMigrations(options = {}) {
|
|
11
|
+
const name = options.table ?? DEFAULT_TABLE;
|
|
12
|
+
const table = quoteTable(name);
|
|
13
|
+
const generated = `fts tsvector GENERATED ALWAYS AS (to_tsvector('${languageOf(options)}'::regconfig, content)) STORED`;
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
component: `keywords:${name}`,
|
|
17
|
+
version: 1,
|
|
18
|
+
name: options.shared ? 'add full-text search to a vector table' : 'create the keyword table',
|
|
19
|
+
statements: [
|
|
20
|
+
options.shared
|
|
21
|
+
? `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${generated}`
|
|
22
|
+
: `CREATE TABLE IF NOT EXISTS ${table} (
|
|
23
|
+
id text PRIMARY KEY,
|
|
24
|
+
content text NOT NULL,
|
|
25
|
+
source text,
|
|
26
|
+
metadata jsonb,
|
|
27
|
+
${generated}
|
|
28
|
+
)`,
|
|
29
|
+
`CREATE INDEX IF NOT EXISTS ${quoteDerived(name, 'fts')} ON ${table} USING gin (fts)`,
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
export class PostgresKeywordIndex {
|
|
35
|
+
client;
|
|
36
|
+
options;
|
|
37
|
+
table;
|
|
38
|
+
language;
|
|
39
|
+
constructor(client, options = {}) {
|
|
40
|
+
this.client = client;
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.table = quoteTable(options.table ?? DEFAULT_TABLE);
|
|
43
|
+
this.language = languageOf(options);
|
|
44
|
+
}
|
|
45
|
+
async migrate(options = {}) {
|
|
46
|
+
const { applyPostgresMigrations } = await import('./migrations.js');
|
|
47
|
+
return applyPostgresMigrations(this.client, keywordIndexMigrations(this.options), options);
|
|
48
|
+
}
|
|
49
|
+
async add(chunks) {
|
|
50
|
+
if (this.options.shared || chunks.length === 0)
|
|
51
|
+
return;
|
|
52
|
+
const params = new SqlParams();
|
|
53
|
+
const rows = chunks.map((chunk) => `(${params.add(chunk.id)}, ${params.add(chunk.content)}, ${params.add(chunk.source ?? null)}, ${params.add(chunk.metadata === undefined ? null : JSON.stringify(chunk.metadata))}::jsonb)`);
|
|
54
|
+
await this.client.query(`INSERT INTO ${this.table} (id, content, source, metadata) VALUES ${rows.join(', ')}
|
|
55
|
+
ON CONFLICT (id) DO UPDATE SET content = EXCLUDED.content, source = EXCLUDED.source, metadata = EXCLUDED.metadata`, params.values);
|
|
56
|
+
}
|
|
57
|
+
async delete(ids) {
|
|
58
|
+
if (this.options.shared || ids.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
await this.client.query(`DELETE FROM ${this.table} WHERE id = ANY($1::text[])`, [textArray(ids)]);
|
|
61
|
+
}
|
|
62
|
+
async retrieve(query, options = {}) {
|
|
63
|
+
options.signal?.throwIfAborted();
|
|
64
|
+
const params = new SqlParams();
|
|
65
|
+
const text = params.add(query);
|
|
66
|
+
const language = `'${this.language}'::regconfig`;
|
|
67
|
+
const tsquery = this.options.match === 'all'
|
|
68
|
+
? `websearch_to_tsquery(${language}, ${text})`
|
|
69
|
+
: `NULLIF(replace(plainto_tsquery(${language}, ${text})::text, ' & ', ' | '), '')::tsquery`;
|
|
70
|
+
const conditions = ['fts @@ q.query'];
|
|
71
|
+
if (options.filter && Object.keys(options.filter).length > 0) {
|
|
72
|
+
conditions.push(`metadata @> ${params.add(JSON.stringify(options.filter))}::jsonb`);
|
|
73
|
+
}
|
|
74
|
+
const limit = params.add(options.topK ?? 5);
|
|
75
|
+
const { rows } = await this.client.query(`SELECT id, content, source, metadata::text AS metadata, ts_rank_cd(fts, q.query, 32) AS score
|
|
76
|
+
FROM ${this.table}, (SELECT ${tsquery} AS query) AS q
|
|
77
|
+
WHERE ${conditions.join(' AND ')}
|
|
78
|
+
ORDER BY score DESC, id LIMIT ${limit}`, params.values);
|
|
79
|
+
return rows.map((row) => ({
|
|
80
|
+
id: row.id,
|
|
81
|
+
content: row.content,
|
|
82
|
+
...(row.source === null ? {} : { source: row.source }),
|
|
83
|
+
...(row.metadata === null ? {} : { metadata: fromJson(row.metadata) }),
|
|
84
|
+
score: Number(row.score),
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
async size() {
|
|
88
|
+
const { rows } = await this.client.query(`SELECT count(*)::int AS count FROM ${this.table}`);
|
|
89
|
+
return Number(rows[0]?.count ?? 0);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/prompts/file.js
CHANGED
|
@@ -38,20 +38,24 @@ export class FilePromptStore {
|
|
|
38
38
|
}
|
|
39
39
|
async setLabel(label, expected) {
|
|
40
40
|
const file = this.labelFile(label.name, label.label);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
return serialized(file, async () => {
|
|
42
|
+
if (expected !== undefined) {
|
|
43
|
+
const current = await readJson(file);
|
|
44
|
+
if (expected === null ? current !== undefined : current?.version !== expected)
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
await writeJson(file, label);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
48
50
|
}
|
|
49
51
|
async deleteLabel(name, label) {
|
|
50
52
|
const file = this.labelFile(name, label);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
return serialized(file, async () => {
|
|
54
|
+
if ((await readJson(file)) === undefined)
|
|
55
|
+
return false;
|
|
56
|
+
await rm(file, { force: true });
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
55
59
|
}
|
|
56
60
|
async appendHistory(entry) {
|
|
57
61
|
await mkdir(this.promptDir(entry.name), { recursive: true });
|
|
@@ -109,6 +113,19 @@ async function readJson(file) {
|
|
|
109
113
|
return undefined;
|
|
110
114
|
}
|
|
111
115
|
}
|
|
116
|
+
const labelWrites = new Map();
|
|
117
|
+
async function serialized(file, work) {
|
|
118
|
+
const key = path.resolve(file);
|
|
119
|
+
const next = (labelWrites.get(key) ?? Promise.resolve()).catch(() => undefined).then(work);
|
|
120
|
+
labelWrites.set(key, next);
|
|
121
|
+
try {
|
|
122
|
+
return await next;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (labelWrites.get(key) === next)
|
|
126
|
+
labelWrites.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
112
129
|
async function writeJson(file, value) {
|
|
113
130
|
await mkdir(path.dirname(file), { recursive: true });
|
|
114
131
|
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|