@gmickel/gno 1.16.0 → 1.17.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/README.md +20 -16
- package/assets/skill/SKILL.md +8 -5
- package/package.json +1 -1
- package/src/app/context-agent-projection.ts +303 -0
- package/src/app/context-format.ts +249 -0
- package/src/app/context-runtime-contract.ts +325 -0
- package/src/app/context-runtime-input.ts +362 -0
- package/src/app/context-runtime-types.ts +65 -0
- package/src/app/context-runtime.ts +170 -0
- package/src/app/context-surface.ts +145 -0
- package/src/cli/commands/context-build.ts +149 -0
- package/src/cli/commands/context-verify.ts +90 -0
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +178 -0
- package/src/core/context-budget.ts +461 -0
- package/src/core/context-capsule-index-schema.ts +15 -0
- package/src/core/context-capsule-retrieval-schema.ts +81 -0
- package/src/core/context-capsule-schema.ts +473 -0
- package/src/core/context-capsule-validation.ts +416 -0
- package/src/core/context-capsule-verification.ts +218 -0
- package/src/core/context-capsule.ts +439 -0
- package/src/core/context-compiler.ts +513 -0
- package/src/core/context-evidence-metadata.ts +33 -0
- package/src/core/context-evidence.ts +495 -0
- package/src/core/context-facets.ts +163 -0
- package/src/core/context-guidance.ts +69 -0
- package/src/core/context-scope.ts +32 -0
- package/src/core/context-verifier-canonical.ts +90 -0
- package/src/core/context-verifier-input.ts +66 -0
- package/src/core/context-verifier.ts +447 -0
- package/src/core/sections.ts +63 -0
- package/src/mcp/server.ts +10 -4
- package/src/mcp/tools/context.ts +229 -0
- package/src/mcp/tools/index.ts +27 -0
- package/src/pipeline/chunk-lookup.ts +33 -0
- package/src/pipeline/hybrid.ts +79 -57
- package/src/pipeline/types.ts +14 -0
- package/src/sdk/client.ts +68 -6
- package/src/sdk/index.ts +21 -0
- package/src/sdk/types.ts +24 -0
- package/src/serve/background-runtime.ts +1 -0
- package/src/serve/context-capsule.ts +136 -0
- package/src/serve/context.ts +10 -1
- package/src/serve/routes/api.ts +2 -0
- package/src/serve/server.ts +23 -0
- package/src/store/sqlite/adapter.ts +38 -20
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Config } from "../config/types";
|
|
2
|
+
import type { ContextCapsuleV1 } from "../core/context-capsule";
|
|
3
|
+
import type { ContextEvidenceCompilerDeps } from "../core/context-evidence";
|
|
4
|
+
import type { ContextVerifierDeps } from "../core/context-verifier";
|
|
5
|
+
import type { EmbeddingPort, RerankPort } from "../llm/types";
|
|
6
|
+
import type { QueryModeInput } from "../pipeline/types";
|
|
7
|
+
import type { StorePort } from "../store/types";
|
|
8
|
+
import type { VectorIndexPort } from "../store/vector";
|
|
9
|
+
|
|
10
|
+
export type ContextDepthPolicy = "fast" | "balanced" | "thorough";
|
|
11
|
+
|
|
12
|
+
export interface ContextCapsuleBuildInput {
|
|
13
|
+
goal: string;
|
|
14
|
+
query?: string;
|
|
15
|
+
indexName?: string;
|
|
16
|
+
collections?: string[];
|
|
17
|
+
uriPrefix?: string | null;
|
|
18
|
+
queryModes?: QueryModeInput[];
|
|
19
|
+
tagsAll?: string[];
|
|
20
|
+
tagsAny?: string[];
|
|
21
|
+
categories?: string[];
|
|
22
|
+
author?: string;
|
|
23
|
+
lang?: string;
|
|
24
|
+
since?: string;
|
|
25
|
+
until?: string;
|
|
26
|
+
graph?: boolean;
|
|
27
|
+
limit?: number;
|
|
28
|
+
candidateLimit?: number;
|
|
29
|
+
budgetTokens: number;
|
|
30
|
+
budgetBytes?: number;
|
|
31
|
+
safetyMarginTokens?: number;
|
|
32
|
+
safetyMarginBytes?: number;
|
|
33
|
+
depthPolicy?: ContextDepthPolicy;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ContextCapsuleRuntimeDeps {
|
|
37
|
+
store: StorePort &
|
|
38
|
+
ContextEvidenceCompilerDeps<ContextCapsuleV1>["store"] &
|
|
39
|
+
ContextVerifierDeps["store"];
|
|
40
|
+
config: Config;
|
|
41
|
+
indexName?: string;
|
|
42
|
+
vectorIndex?: VectorIndexPort | null;
|
|
43
|
+
embedPort?: EmbeddingPort | null;
|
|
44
|
+
rerankPort?: RerankPort | null;
|
|
45
|
+
countTokens?: (accountingJson: string) => number;
|
|
46
|
+
tokenizerFingerprint?: string | null;
|
|
47
|
+
resolveCurrentRanks?: ContextVerifierDeps["resolveCurrentRanks"];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ContextRuntimeErrorCode =
|
|
51
|
+
| "invalid_goal"
|
|
52
|
+
| "invalid_budget"
|
|
53
|
+
| "invalid_filter"
|
|
54
|
+
| "invalid_uri"
|
|
55
|
+
| "retrieval_failed";
|
|
56
|
+
|
|
57
|
+
export class ContextRuntimeError extends Error {
|
|
58
|
+
readonly code: ContextRuntimeErrorCode;
|
|
59
|
+
|
|
60
|
+
constructor(code: ContextRuntimeErrorCode, message: string, cause?: unknown) {
|
|
61
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
62
|
+
this.name = "ContextRuntimeError";
|
|
63
|
+
this.code = code;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Shared application boundary for Context Capsule build and verification. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ContextCapsuleV1,
|
|
5
|
+
ContextCapsuleVerification,
|
|
6
|
+
} from "../core/context-capsule";
|
|
7
|
+
import type {
|
|
8
|
+
ContextCapsuleBuildInput,
|
|
9
|
+
ContextCapsuleRuntimeDeps,
|
|
10
|
+
} from "./context-runtime-types";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
canonicalContextCapsuleJson,
|
|
14
|
+
ContextCapsuleContractError,
|
|
15
|
+
} from "../core/context-capsule";
|
|
16
|
+
import { compileContextEvidence } from "../core/context-evidence";
|
|
17
|
+
import {
|
|
18
|
+
canonicalContextCapsuleVerificationJson,
|
|
19
|
+
parseCanonicalContextCapsuleForVerification,
|
|
20
|
+
verifyContextCapsule,
|
|
21
|
+
} from "../core/context-verifier";
|
|
22
|
+
import { searchHybrid } from "../pipeline/hybrid";
|
|
23
|
+
import {
|
|
24
|
+
currentContextFingerprints,
|
|
25
|
+
projectContextCapsule,
|
|
26
|
+
} from "./context-runtime-contract";
|
|
27
|
+
import { normalizeContextBuildInput } from "./context-runtime-input";
|
|
28
|
+
import { ContextRuntimeError } from "./context-runtime-types";
|
|
29
|
+
import { canonicalizeIndexName } from "./index-name";
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
ContextCapsuleBuildInput,
|
|
33
|
+
ContextCapsuleRuntimeDeps,
|
|
34
|
+
ContextDepthPolicy,
|
|
35
|
+
ContextRuntimeErrorCode,
|
|
36
|
+
} from "./context-runtime-types";
|
|
37
|
+
export { ContextRuntimeError } from "./context-runtime-types";
|
|
38
|
+
|
|
39
|
+
/** Build one strict Capsule through the shared compiler composition. */
|
|
40
|
+
export const buildContextCapsule = async (
|
|
41
|
+
input: ContextCapsuleBuildInput,
|
|
42
|
+
deps: ContextCapsuleRuntimeDeps
|
|
43
|
+
): Promise<ContextCapsuleV1> => {
|
|
44
|
+
const now = new Date();
|
|
45
|
+
const normalized = normalizeContextBuildInput(
|
|
46
|
+
input,
|
|
47
|
+
deps.indexName,
|
|
48
|
+
now,
|
|
49
|
+
deps.config.collections.map((collection) => collection.name)
|
|
50
|
+
);
|
|
51
|
+
const noRerank = normalized.depthPolicy === "fast";
|
|
52
|
+
const plan = await compileContextEvidence<ContextCapsuleV1>(
|
|
53
|
+
{
|
|
54
|
+
goal: normalized.goal,
|
|
55
|
+
query: normalized.query,
|
|
56
|
+
indexName: normalized.indexName,
|
|
57
|
+
collections: normalized.collections,
|
|
58
|
+
uriPrefix: normalized.uriPrefix,
|
|
59
|
+
queryModes: normalized.queryModes,
|
|
60
|
+
tagsAll: normalized.tagsAll,
|
|
61
|
+
tagsAny: normalized.tagsAny,
|
|
62
|
+
categories: normalized.categories,
|
|
63
|
+
author: normalized.author ?? undefined,
|
|
64
|
+
lang: normalized.lang ?? undefined,
|
|
65
|
+
since: normalized.since,
|
|
66
|
+
until: normalized.until,
|
|
67
|
+
graph: normalized.graph,
|
|
68
|
+
limit: normalized.limit,
|
|
69
|
+
candidateLimit: normalized.candidateLimit,
|
|
70
|
+
temporalNow: now,
|
|
71
|
+
limits: {
|
|
72
|
+
requestedBytes: normalized.budgetBytes,
|
|
73
|
+
requestedTokens: normalized.budgetTokens,
|
|
74
|
+
safetyMarginBytes: normalized.safetyMarginBytes,
|
|
75
|
+
safetyMarginTokens: normalized.safetyMarginTokens,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
store: deps.store,
|
|
80
|
+
retrieve: async (request) => {
|
|
81
|
+
const requestNoRerank = noRerank || request.noRerank === true;
|
|
82
|
+
const result = await searchHybrid(
|
|
83
|
+
{
|
|
84
|
+
store: deps.store,
|
|
85
|
+
config: deps.config,
|
|
86
|
+
vectorIndex: deps.vectorIndex ?? null,
|
|
87
|
+
embedPort: deps.embedPort ?? null,
|
|
88
|
+
expandPort: null,
|
|
89
|
+
rerankPort: requestNoRerank ? null : (deps.rerankPort ?? null),
|
|
90
|
+
},
|
|
91
|
+
request.query,
|
|
92
|
+
{ ...request, noRerank: requestNoRerank }
|
|
93
|
+
);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
throw new ContextRuntimeError(
|
|
96
|
+
"retrieval_failed",
|
|
97
|
+
result.error.message,
|
|
98
|
+
result.error.cause
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return result.value;
|
|
102
|
+
},
|
|
103
|
+
projectCanonical: (draft, snapshots) =>
|
|
104
|
+
projectContextCapsule(draft, snapshots, normalized, deps),
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
if (!plan.projection) {
|
|
108
|
+
const budgetExhausted = plan.omissions.some(
|
|
109
|
+
(item) => item.reason === "global_budget"
|
|
110
|
+
);
|
|
111
|
+
throw new ContextCapsuleContractError(
|
|
112
|
+
budgetExhausted ? "invalid_budget" : "no_evidence",
|
|
113
|
+
budgetExhausted
|
|
114
|
+
? "No evidence fit the requested Context Capsule budget"
|
|
115
|
+
: "No in-scope evidence was available for the Context Capsule"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return plan.projection.value;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** Verify one Capsule through the same runtime fingerprint boundary. */
|
|
122
|
+
export const verifyContextCapsuleRuntime = async (
|
|
123
|
+
input: unknown,
|
|
124
|
+
deps: ContextCapsuleRuntimeDeps
|
|
125
|
+
): Promise<ContextCapsuleVerification> => {
|
|
126
|
+
// Parse before any store access. verifyContextCapsule repeats this guard to
|
|
127
|
+
// retain its standalone fail-closed contract.
|
|
128
|
+
const capsule = parseCanonicalContextCapsuleForVerification(input, {
|
|
129
|
+
countTokens: deps.countTokens,
|
|
130
|
+
tokenizerFingerprint: deps.tokenizerFingerprint,
|
|
131
|
+
});
|
|
132
|
+
if (
|
|
133
|
+
deps.indexName !== undefined &&
|
|
134
|
+
canonicalizeIndexName(deps.indexName) !== capsule.scope.indexName
|
|
135
|
+
) {
|
|
136
|
+
throw new ContextRuntimeError(
|
|
137
|
+
"invalid_filter",
|
|
138
|
+
`Context Capsule index ${capsule.scope.indexName} does not match runtime index ${deps.indexName}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return verifyContextCapsule(input, {
|
|
142
|
+
store: deps.store,
|
|
143
|
+
currentFingerprints: currentContextFingerprints(capsule, deps),
|
|
144
|
+
resolveCurrentRanks: deps.resolveCurrentRanks,
|
|
145
|
+
countTokens: deps.countTokens,
|
|
146
|
+
tokenizerFingerprint: deps.tokenizerFingerprint,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const canonicalBuiltContextCapsuleJson = (
|
|
151
|
+
capsule: ContextCapsuleV1
|
|
152
|
+
): string => canonicalContextCapsuleJson(capsule);
|
|
153
|
+
|
|
154
|
+
export const canonicalVerifiedContextCapsuleJson = (
|
|
155
|
+
receipt: ContextCapsuleVerification
|
|
156
|
+
): string => canonicalContextCapsuleVerificationJson(receipt);
|
|
157
|
+
|
|
158
|
+
/** Pure validation used by CLI before opening the selected store. */
|
|
159
|
+
export const validateContextCapsuleBuildInput = (
|
|
160
|
+
input: ContextCapsuleBuildInput,
|
|
161
|
+
defaultIndexName?: string,
|
|
162
|
+
configuredCollectionNames?: readonly string[]
|
|
163
|
+
): void => {
|
|
164
|
+
normalizeContextBuildInput(
|
|
165
|
+
input,
|
|
166
|
+
defaultIndexName,
|
|
167
|
+
new Date(),
|
|
168
|
+
configuredCollectionNames
|
|
169
|
+
);
|
|
170
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/** Closed wire contracts shared by Context Capsule REST and MCP surfaces. */
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import type { ContextCapsuleBuildInput } from "./context-runtime-types";
|
|
6
|
+
|
|
7
|
+
import { ContextCapsuleContractError } from "../core/context-capsule";
|
|
8
|
+
|
|
9
|
+
const queryModeSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
mode: z.enum(["term", "intent", "hyde"]),
|
|
12
|
+
text: z.string(),
|
|
13
|
+
})
|
|
14
|
+
.strict();
|
|
15
|
+
|
|
16
|
+
const stringList = z.array(z.string());
|
|
17
|
+
const positiveInteger = z.number().int().positive();
|
|
18
|
+
const nonnegativeInteger = z.number().int().nonnegative();
|
|
19
|
+
|
|
20
|
+
/** Host-owned index identity is deliberately absent from this public input. */
|
|
21
|
+
export const contextBuildSurfaceSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
goal: z.string(),
|
|
24
|
+
query: z.string().optional(),
|
|
25
|
+
collections: stringList.optional(),
|
|
26
|
+
uriPrefix: z.string().nullable().optional(),
|
|
27
|
+
queryModes: z.array(queryModeSchema).optional(),
|
|
28
|
+
tagsAll: stringList.optional(),
|
|
29
|
+
tagsAny: stringList.optional(),
|
|
30
|
+
categories: stringList.optional(),
|
|
31
|
+
author: z.string().optional(),
|
|
32
|
+
lang: z.string().optional(),
|
|
33
|
+
since: z.string().optional(),
|
|
34
|
+
until: z.string().optional(),
|
|
35
|
+
graph: z.boolean().optional(),
|
|
36
|
+
limit: positiveInteger.optional(),
|
|
37
|
+
candidateLimit: positiveInteger.optional(),
|
|
38
|
+
budgetTokens: positiveInteger,
|
|
39
|
+
budgetBytes: positiveInteger.optional(),
|
|
40
|
+
safetyMarginTokens: nonnegativeInteger.optional(),
|
|
41
|
+
safetyMarginBytes: nonnegativeInteger.optional(),
|
|
42
|
+
depthPolicy: z.enum(["fast", "balanced", "thorough"]).optional(),
|
|
43
|
+
format: z.enum(["json", "md"]).optional(),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
|
|
47
|
+
export const contextVerifySurfaceSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
capsule: z.record(z.string(), z.unknown()),
|
|
50
|
+
format: z.enum(["json", "md"]).optional(),
|
|
51
|
+
})
|
|
52
|
+
.strict();
|
|
53
|
+
|
|
54
|
+
export type ContextSurfaceFormat = "json" | "md";
|
|
55
|
+
|
|
56
|
+
export interface ParsedContextBuildSurfaceInput {
|
|
57
|
+
input: ContextCapsuleBuildInput;
|
|
58
|
+
format: ContextSurfaceFormat;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ParsedContextVerifySurfaceInput {
|
|
62
|
+
capsule: Record<string, unknown>;
|
|
63
|
+
format: ContextSurfaceFormat;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const CONTEXT_SURFACE_ERROR_MESSAGES = {
|
|
67
|
+
capsule_mutated_during_verify:
|
|
68
|
+
"The Context Capsule changed during verification.",
|
|
69
|
+
chunk_coordinate_mismatch:
|
|
70
|
+
"Indexed evidence coordinates do not match the source.",
|
|
71
|
+
chunk_load_failed: "Context Capsule evidence chunks could not be loaded.",
|
|
72
|
+
collection_load_failed: "Context Capsule collections could not be loaded.",
|
|
73
|
+
content_load_failed: "Context Capsule source content could not be loaded.",
|
|
74
|
+
context_changed_during_compile:
|
|
75
|
+
"Configured context changed during compilation.",
|
|
76
|
+
context_changed_during_verify:
|
|
77
|
+
"Configured context changed during verification.",
|
|
78
|
+
context_load_failed: "Configured context could not be loaded.",
|
|
79
|
+
document_load_failed: "Context Capsule documents could not be loaded.",
|
|
80
|
+
identity_mismatch: "The Context Capsule identity does not match its content.",
|
|
81
|
+
index_changed_during_compile: "The index changed during compilation.",
|
|
82
|
+
index_changed_during_verify: "The index changed during verification.",
|
|
83
|
+
index_snapshot_failed: "The index snapshot could not be loaded.",
|
|
84
|
+
index_snapshot_mismatch:
|
|
85
|
+
"Indexed evidence does not match the captured snapshot.",
|
|
86
|
+
invalid_budget: "The Context Capsule budget is invalid.",
|
|
87
|
+
invalid_filter: "A Context Capsule filter is invalid.",
|
|
88
|
+
invalid_goal: "The Context Capsule goal is invalid.",
|
|
89
|
+
invalid_input: "The Context Capsule request is invalid.",
|
|
90
|
+
invalid_uri: "The Context Capsule URI is invalid.",
|
|
91
|
+
no_evidence: "No in-scope evidence was available for the Context Capsule.",
|
|
92
|
+
retrieval_failed: "Context Capsule retrieval failed.",
|
|
93
|
+
runtime_error: "The Context Capsule request failed.",
|
|
94
|
+
stored_provenance_mismatch:
|
|
95
|
+
"Stored evidence provenance does not match the source.",
|
|
96
|
+
tokenizer_unavailable: "The required tokenizer is unavailable.",
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
export type ContextSurfaceErrorCode =
|
|
100
|
+
keyof typeof CONTEXT_SURFACE_ERROR_MESSAGES;
|
|
101
|
+
|
|
102
|
+
const invalidInput = (error: z.ZodError): ContextCapsuleContractError =>
|
|
103
|
+
new ContextCapsuleContractError(
|
|
104
|
+
"invalid_input",
|
|
105
|
+
error.issues
|
|
106
|
+
.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
|
|
107
|
+
.join("; ")
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const parseContextBuildSurfaceInput = (
|
|
111
|
+
value: unknown,
|
|
112
|
+
indexName: string
|
|
113
|
+
): ParsedContextBuildSurfaceInput => {
|
|
114
|
+
const parsed = contextBuildSurfaceSchema.safeParse(value);
|
|
115
|
+
if (!parsed.success) throw invalidInput(parsed.error);
|
|
116
|
+
const { format = "json", ...input } = parsed.data;
|
|
117
|
+
return { input: { ...input, indexName }, format };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const parseContextVerifySurfaceInput = (
|
|
121
|
+
value: unknown
|
|
122
|
+
): ParsedContextVerifySurfaceInput => {
|
|
123
|
+
const parsed = contextVerifySurfaceSchema.safeParse(value);
|
|
124
|
+
if (!parsed.success) throw invalidInput(parsed.error);
|
|
125
|
+
return {
|
|
126
|
+
capsule: parsed.data.capsule,
|
|
127
|
+
format: parsed.data.format ?? "json",
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const contextSurfaceError = (
|
|
132
|
+
error: unknown
|
|
133
|
+
): { code: ContextSurfaceErrorCode; message: string } => {
|
|
134
|
+
const candidate =
|
|
135
|
+
error !== null &&
|
|
136
|
+
typeof error === "object" &&
|
|
137
|
+
"code" in error &&
|
|
138
|
+
typeof error.code === "string"
|
|
139
|
+
? error.code
|
|
140
|
+
: "runtime_error";
|
|
141
|
+
const code = Object.hasOwn(CONTEXT_SURFACE_ERROR_MESSAGES, candidate)
|
|
142
|
+
? (candidate as ContextSurfaceErrorCode)
|
|
143
|
+
: "runtime_error";
|
|
144
|
+
return { code, message: CONTEXT_SURFACE_ERROR_MESSAGES[code] };
|
|
145
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Context Capsule build command over the shared application runtime. */
|
|
2
|
+
|
|
3
|
+
import type { ContextCapsuleBuildInput } from "../../app/context-runtime";
|
|
4
|
+
import type { EmbeddingPort, RerankPort } from "../../llm/types";
|
|
5
|
+
import type { VectorIndexPort } from "../../store/vector";
|
|
6
|
+
|
|
7
|
+
import { formatContextCapsuleMarkdown } from "../../app/context-format";
|
|
8
|
+
import {
|
|
9
|
+
buildContextCapsule,
|
|
10
|
+
canonicalBuiltContextCapsuleJson,
|
|
11
|
+
validateContextCapsuleBuildInput,
|
|
12
|
+
} from "../../app/context-runtime";
|
|
13
|
+
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
14
|
+
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
15
|
+
import { resolveModelUri } from "../../llm/registry";
|
|
16
|
+
import { createVectorIndexPort } from "../../store/vector";
|
|
17
|
+
import { CliError } from "../errors";
|
|
18
|
+
import { getGlobals } from "../program";
|
|
19
|
+
import {
|
|
20
|
+
createProgressRenderer,
|
|
21
|
+
createThrottledProgressRenderer,
|
|
22
|
+
} from "../progress";
|
|
23
|
+
import { initStore } from "./shared";
|
|
24
|
+
|
|
25
|
+
export interface ContextBuildCommandOptions extends Omit<
|
|
26
|
+
ContextCapsuleBuildInput,
|
|
27
|
+
"goal"
|
|
28
|
+
> {
|
|
29
|
+
configPath?: string;
|
|
30
|
+
format: "json" | "md";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const validationCodes = new Set([
|
|
34
|
+
"identity_mismatch",
|
|
35
|
+
"invalid_budget",
|
|
36
|
+
"invalid_filter",
|
|
37
|
+
"invalid_goal",
|
|
38
|
+
"invalid_input",
|
|
39
|
+
"invalid_uri",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
export const contextCliError = (error: unknown): CliError => {
|
|
43
|
+
const contextCode =
|
|
44
|
+
error !== null &&
|
|
45
|
+
typeof error === "object" &&
|
|
46
|
+
"code" in error &&
|
|
47
|
+
typeof error.code === "string"
|
|
48
|
+
? error.code
|
|
49
|
+
: "runtime_error";
|
|
50
|
+
return new CliError(
|
|
51
|
+
validationCodes.has(contextCode) ? "VALIDATION" : "RUNTIME",
|
|
52
|
+
error instanceof Error ? error.message : String(error),
|
|
53
|
+
{ details: { contextCode } }
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// oxlint-disable-next-line max-lines-per-function -- owns one bounded model/store lifecycle
|
|
58
|
+
export const contextBuild = async (
|
|
59
|
+
goal: string,
|
|
60
|
+
options: ContextBuildCommandOptions
|
|
61
|
+
): Promise<string> => {
|
|
62
|
+
try {
|
|
63
|
+
validateContextCapsuleBuildInput({ goal, ...options }, options.indexName);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw contextCliError(error);
|
|
66
|
+
}
|
|
67
|
+
const initResult = await initStore({
|
|
68
|
+
configPath: options.configPath,
|
|
69
|
+
indexName: options.indexName,
|
|
70
|
+
syncConfig: true,
|
|
71
|
+
});
|
|
72
|
+
if (!initResult.ok) {
|
|
73
|
+
throw new CliError("RUNTIME", initResult.error);
|
|
74
|
+
}
|
|
75
|
+
const { config, store } = initResult;
|
|
76
|
+
const llm = new LlmAdapter(config);
|
|
77
|
+
let embedPort: EmbeddingPort | null = null;
|
|
78
|
+
let rerankPort: RerankPort | null = null;
|
|
79
|
+
let vectorIndex: VectorIndexPort | null = null;
|
|
80
|
+
try {
|
|
81
|
+
validateContextCapsuleBuildInput(
|
|
82
|
+
{ goal, ...options },
|
|
83
|
+
options.indexName,
|
|
84
|
+
config.collections.map((collection) => collection.name)
|
|
85
|
+
);
|
|
86
|
+
if (options.depthPolicy !== "fast") {
|
|
87
|
+
const globals = getGlobals();
|
|
88
|
+
const policy = resolveDownloadPolicy(process.env, {
|
|
89
|
+
offline: globals.offline,
|
|
90
|
+
});
|
|
91
|
+
const showProgress = process.stderr.isTTY && !globals.quiet;
|
|
92
|
+
const progress = showProgress
|
|
93
|
+
? createThrottledProgressRenderer(createProgressRenderer())
|
|
94
|
+
: undefined;
|
|
95
|
+
const collection =
|
|
96
|
+
options.collections?.length === 1 ? options.collections[0] : undefined;
|
|
97
|
+
const embedUri = resolveModelUri(config, "embed", undefined, collection);
|
|
98
|
+
const embedResult = await llm.createEmbeddingPort(embedUri, {
|
|
99
|
+
policy,
|
|
100
|
+
onProgress: progress ? (value) => progress("embed", value) : undefined,
|
|
101
|
+
});
|
|
102
|
+
if (embedResult.ok) {
|
|
103
|
+
embedPort = embedResult.value;
|
|
104
|
+
const initialized = await embedPort.init();
|
|
105
|
+
if (initialized.ok) {
|
|
106
|
+
const vectorResult = await createVectorIndexPort(store.getRawDb(), {
|
|
107
|
+
model: embedUri,
|
|
108
|
+
dimensions: embedPort.dimensions(),
|
|
109
|
+
});
|
|
110
|
+
if (vectorResult.ok) vectorIndex = vectorResult.value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const rerankUri = resolveModelUri(
|
|
114
|
+
config,
|
|
115
|
+
"rerank",
|
|
116
|
+
undefined,
|
|
117
|
+
collection
|
|
118
|
+
);
|
|
119
|
+
const rerankResult = await llm.createRerankPort(rerankUri, {
|
|
120
|
+
policy,
|
|
121
|
+
onProgress: progress ? (value) => progress("rerank", value) : undefined,
|
|
122
|
+
});
|
|
123
|
+
if (rerankResult.ok) rerankPort = rerankResult.value;
|
|
124
|
+
if (showProgress && progress) process.stderr.write("\n");
|
|
125
|
+
}
|
|
126
|
+
const capsule = await buildContextCapsule(
|
|
127
|
+
{ goal, ...options },
|
|
128
|
+
{
|
|
129
|
+
store,
|
|
130
|
+
config,
|
|
131
|
+
indexName: options.indexName,
|
|
132
|
+
vectorIndex,
|
|
133
|
+
embedPort,
|
|
134
|
+
rerankPort,
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
return options.format === "md"
|
|
138
|
+
? formatContextCapsuleMarkdown(capsule)
|
|
139
|
+
: canonicalBuiltContextCapsuleJson(capsule);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof CliError) throw error;
|
|
142
|
+
throw contextCliError(error);
|
|
143
|
+
} finally {
|
|
144
|
+
await embedPort?.dispose();
|
|
145
|
+
await rerankPort?.dispose();
|
|
146
|
+
await llm.dispose();
|
|
147
|
+
await store.close();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** Context Capsule verification command over the shared application runtime. */
|
|
2
|
+
|
|
3
|
+
import { formatContextCapsuleVerificationMarkdown } from "../../app/context-format";
|
|
4
|
+
import {
|
|
5
|
+
canonicalVerifiedContextCapsuleJson,
|
|
6
|
+
verifyContextCapsuleRuntime,
|
|
7
|
+
} from "../../app/context-runtime";
|
|
8
|
+
import { canonicalizeIndexName } from "../../app/index-name";
|
|
9
|
+
import { parseCanonicalContextCapsuleForVerification } from "../../core/context-verifier";
|
|
10
|
+
import { CliError } from "../errors";
|
|
11
|
+
import { contextCliError } from "./context-build";
|
|
12
|
+
import { initStore } from "./shared";
|
|
13
|
+
|
|
14
|
+
export interface ContextVerifyCommandOptions {
|
|
15
|
+
configPath?: string;
|
|
16
|
+
indexName?: string;
|
|
17
|
+
format: "json" | "md";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const readCapsule = async (source: string): Promise<unknown> => {
|
|
21
|
+
let raw: string;
|
|
22
|
+
try {
|
|
23
|
+
raw =
|
|
24
|
+
source === "-" ? await Bun.stdin.text() : await Bun.file(source).text();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new CliError(
|
|
27
|
+
"RUNTIME",
|
|
28
|
+
`Failed to read Context Capsule: ${
|
|
29
|
+
error instanceof Error ? error.message : String(error)
|
|
30
|
+
}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(raw) as unknown;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new CliError("VALIDATION", "Context Capsule must be valid JSON", {
|
|
37
|
+
details: {
|
|
38
|
+
contextCode: "invalid_input",
|
|
39
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const contextVerify = async (
|
|
46
|
+
source: string,
|
|
47
|
+
options: ContextVerifyCommandOptions
|
|
48
|
+
): Promise<string> => {
|
|
49
|
+
const input = await readCapsule(source);
|
|
50
|
+
let capsule: ReturnType<typeof parseCanonicalContextCapsuleForVerification>;
|
|
51
|
+
try {
|
|
52
|
+
capsule = parseCanonicalContextCapsuleForVerification(input);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw contextCliError(error);
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
options.indexName !== undefined &&
|
|
58
|
+
canonicalizeIndexName(options.indexName) !== capsule.scope.indexName
|
|
59
|
+
) {
|
|
60
|
+
throw new CliError(
|
|
61
|
+
"VALIDATION",
|
|
62
|
+
`Context Capsule index ${capsule.scope.indexName} does not match --index ${options.indexName}`,
|
|
63
|
+
{ details: { contextCode: "invalid_filter" } }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const initResult = await initStore({
|
|
67
|
+
configPath: options.configPath,
|
|
68
|
+
indexName: capsule.scope.indexName,
|
|
69
|
+
syncConfig: true,
|
|
70
|
+
});
|
|
71
|
+
if (!initResult.ok) {
|
|
72
|
+
throw new CliError("RUNTIME", initResult.error);
|
|
73
|
+
}
|
|
74
|
+
const { config, store } = initResult;
|
|
75
|
+
try {
|
|
76
|
+
const receipt = await verifyContextCapsuleRuntime(capsule, {
|
|
77
|
+
store,
|
|
78
|
+
config,
|
|
79
|
+
indexName: capsule.scope.indexName,
|
|
80
|
+
});
|
|
81
|
+
return options.format === "md"
|
|
82
|
+
? formatContextCapsuleVerificationMarkdown(receipt)
|
|
83
|
+
: canonicalVerifiedContextCapsuleJson(receipt);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof CliError) throw error;
|
|
86
|
+
throw contextCliError(error);
|
|
87
|
+
} finally {
|
|
88
|
+
await store.close();
|
|
89
|
+
}
|
|
90
|
+
};
|
package/src/cli/options.ts
CHANGED
|
@@ -33,6 +33,8 @@ export const CMD = {
|
|
|
33
33
|
collectionList: "collection.list",
|
|
34
34
|
contextList: "context.list",
|
|
35
35
|
contextCheck: "context.check",
|
|
36
|
+
contextBuild: "context.build",
|
|
37
|
+
contextVerify: "context.verify",
|
|
36
38
|
modelsList: "models.list",
|
|
37
39
|
tagsList: "tags.list",
|
|
38
40
|
linksList: "links.list",
|
|
@@ -60,6 +62,8 @@ const FORMAT_SUPPORT: Record<CommandId, OutputFormat[]> = {
|
|
|
60
62
|
[CMD.collectionList]: ["terminal", "json", "md"],
|
|
61
63
|
[CMD.contextList]: ["terminal", "json", "md"],
|
|
62
64
|
[CMD.contextCheck]: ["terminal", "json", "md"],
|
|
65
|
+
[CMD.contextBuild]: ["terminal", "json", "md"],
|
|
66
|
+
[CMD.contextVerify]: ["terminal", "json", "md"],
|
|
63
67
|
[CMD.modelsList]: ["terminal", "json"],
|
|
64
68
|
[CMD.tagsList]: ["terminal", "json", "md"],
|
|
65
69
|
[CMD.linksList]: ["terminal", "json", "md"],
|