@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,439 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { canonicalizeIndexName } from "../app/index-name";
|
|
4
|
+
import {
|
|
5
|
+
contextCapsulePayloadV1Schema,
|
|
6
|
+
type ContextCapsulePayloadV1,
|
|
7
|
+
} from "./context-capsule-schema";
|
|
8
|
+
|
|
9
|
+
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
10
|
+
|
|
11
|
+
const compareCodeUnits = (left: string, right: string): number => {
|
|
12
|
+
if (left < right) return -1;
|
|
13
|
+
if (left > right) return 1;
|
|
14
|
+
return 0;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const canonicalizeJsonValue = (value: unknown): unknown => {
|
|
18
|
+
if (Array.isArray(value)) return value.map(canonicalizeJsonValue);
|
|
19
|
+
if (value !== null && typeof value === "object") {
|
|
20
|
+
const sorted: Record<string, unknown> = {};
|
|
21
|
+
for (const key of Object.keys(value).sort(compareCodeUnits)) {
|
|
22
|
+
const child = (value as Record<string, unknown>)[key];
|
|
23
|
+
if (child === undefined) {
|
|
24
|
+
throw new Error(`Canonical JSON rejects undefined at ${key}`);
|
|
25
|
+
}
|
|
26
|
+
sorted[key] = canonicalizeJsonValue(child);
|
|
27
|
+
}
|
|
28
|
+
return sorted;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
31
|
+
throw new Error("Canonical JSON rejects non-finite numbers");
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const canonicalJson = (value: unknown): string =>
|
|
37
|
+
JSON.stringify(canonicalizeJsonValue(value));
|
|
38
|
+
const utf8Bytes = (value: string): number =>
|
|
39
|
+
new TextEncoder().encode(value).byteLength;
|
|
40
|
+
const hashCanonical = (value: unknown): string =>
|
|
41
|
+
new Bun.CryptoHasher("sha256").update(canonicalJson(value)).digest("hex");
|
|
42
|
+
const normalizeText = (value: string): string =>
|
|
43
|
+
value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").normalize("NFC");
|
|
44
|
+
const normalizeDate = (value: string | null): string | null =>
|
|
45
|
+
value === null ? null : new Date(value).toISOString();
|
|
46
|
+
const normalizeDocumentDate = (value: string | null): string | null =>
|
|
47
|
+
value === null || /^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
48
|
+
? value
|
|
49
|
+
: new Date(value).toISOString();
|
|
50
|
+
const normalizeSet = (values: readonly string[]): string[] =>
|
|
51
|
+
[...new Set(values.map(normalizeText))].sort(compareCodeUnits);
|
|
52
|
+
|
|
53
|
+
const normalizePayload = (
|
|
54
|
+
value: ContextCapsulePayloadV1
|
|
55
|
+
): ContextCapsulePayloadV1 => ({
|
|
56
|
+
...value,
|
|
57
|
+
goal: normalizeText(value.goal),
|
|
58
|
+
query: normalizeText(value.query),
|
|
59
|
+
scope: {
|
|
60
|
+
...value.scope,
|
|
61
|
+
indexName: canonicalizeIndexName(value.scope.indexName),
|
|
62
|
+
collections: normalizeSet(value.scope.collections),
|
|
63
|
+
uriPrefix:
|
|
64
|
+
value.scope.uriPrefix === null
|
|
65
|
+
? null
|
|
66
|
+
: normalizeText(value.scope.uriPrefix),
|
|
67
|
+
tagsAll: normalizeSet(value.scope.tagsAll),
|
|
68
|
+
tagsAny: normalizeSet(value.scope.tagsAny),
|
|
69
|
+
categories: normalizeSet(value.scope.categories),
|
|
70
|
+
since: normalizeDate(value.scope.since),
|
|
71
|
+
until: normalizeDate(value.scope.until),
|
|
72
|
+
},
|
|
73
|
+
retrieval: {
|
|
74
|
+
...value.retrieval,
|
|
75
|
+
facets: normalizeSet(value.retrieval.facets),
|
|
76
|
+
queryVariants: value.retrieval.queryVariants.map(normalizeText),
|
|
77
|
+
request: {
|
|
78
|
+
...value.retrieval.request,
|
|
79
|
+
author:
|
|
80
|
+
value.retrieval.request.author === null
|
|
81
|
+
? null
|
|
82
|
+
: normalizeText(value.retrieval.request.author),
|
|
83
|
+
lang:
|
|
84
|
+
value.retrieval.request.lang === null
|
|
85
|
+
? null
|
|
86
|
+
: normalizeText(value.retrieval.request.lang),
|
|
87
|
+
queryModes: value.retrieval.request.queryModes.map((mode) => ({
|
|
88
|
+
...mode,
|
|
89
|
+
text: normalizeText(mode.text),
|
|
90
|
+
})),
|
|
91
|
+
},
|
|
92
|
+
capabilityStates: Object.fromEntries(
|
|
93
|
+
Object.entries(value.retrieval.capabilityStates).map(([key, state]) => [
|
|
94
|
+
key,
|
|
95
|
+
{ ...state, fallbackReasons: normalizeSet(state.fallbackReasons) },
|
|
96
|
+
])
|
|
97
|
+
) as typeof value.retrieval.capabilityStates,
|
|
98
|
+
},
|
|
99
|
+
fallbacks: [...value.fallbacks].sort((left, right) =>
|
|
100
|
+
compareCodeUnits(
|
|
101
|
+
`${left.code}\0${left.capability}`,
|
|
102
|
+
`${right.code}\0${right.capability}`
|
|
103
|
+
)
|
|
104
|
+
),
|
|
105
|
+
evidence: value.evidence.map((item) => ({
|
|
106
|
+
...item,
|
|
107
|
+
title: item.title === null ? null : normalizeText(item.title),
|
|
108
|
+
heading: item.heading === null ? null : normalizeText(item.heading),
|
|
109
|
+
modifiedAt: normalizeDate(item.modifiedAt),
|
|
110
|
+
documentDate: normalizeDocumentDate(item.documentDate),
|
|
111
|
+
observedAt: normalizeDate(item.observedAt),
|
|
112
|
+
contextIds: normalizeSet(item.contextIds),
|
|
113
|
+
facets: normalizeSet(item.facets),
|
|
114
|
+
})),
|
|
115
|
+
guidance: {
|
|
116
|
+
...value.guidance,
|
|
117
|
+
configuredContexts: [...value.guidance.configuredContexts]
|
|
118
|
+
.map((item) => ({
|
|
119
|
+
...item,
|
|
120
|
+
scopeKey: normalizeText(item.scopeKey),
|
|
121
|
+
text: normalizeText(item.text),
|
|
122
|
+
}))
|
|
123
|
+
.sort((left, right) => compareCodeUnits(left.contextId, right.contextId)),
|
|
124
|
+
},
|
|
125
|
+
coverage: {
|
|
126
|
+
...value.coverage,
|
|
127
|
+
requestedFacets: normalizeSet(value.coverage.requestedFacets),
|
|
128
|
+
coveredFacets: [...value.coverage.coveredFacets]
|
|
129
|
+
.map((item) => ({
|
|
130
|
+
facet: normalizeText(item.facet),
|
|
131
|
+
evidenceIds: normalizeSet(item.evidenceIds),
|
|
132
|
+
}))
|
|
133
|
+
.sort((left, right) => compareCodeUnits(left.facet, right.facet)),
|
|
134
|
+
unresolvedFacets: normalizeSet(value.coverage.unresolvedFacets),
|
|
135
|
+
gaps: [...value.coverage.gaps].sort((left, right) =>
|
|
136
|
+
compareCodeUnits(
|
|
137
|
+
`${left.facet}\0${left.code}`,
|
|
138
|
+
`${right.facet}\0${right.code}`
|
|
139
|
+
)
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
warnings: [...value.warnings].sort((left, right) =>
|
|
143
|
+
compareCodeUnits(left.code, right.code)
|
|
144
|
+
),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export type ContextCapsuleErrorCode =
|
|
148
|
+
| "identity_mismatch"
|
|
149
|
+
| "index_changed_during_compile"
|
|
150
|
+
| "invalid_budget"
|
|
151
|
+
| "invalid_input"
|
|
152
|
+
| "no_evidence"
|
|
153
|
+
| "tokenizer_unavailable";
|
|
154
|
+
|
|
155
|
+
export class ContextCapsuleContractError extends Error {
|
|
156
|
+
readonly code: ContextCapsuleErrorCode;
|
|
157
|
+
|
|
158
|
+
constructor(
|
|
159
|
+
code: ContextCapsuleErrorCode,
|
|
160
|
+
message: string,
|
|
161
|
+
options?: ErrorOptions
|
|
162
|
+
) {
|
|
163
|
+
super(message, options);
|
|
164
|
+
this.name = "ContextCapsuleContractError";
|
|
165
|
+
this.code = code;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const contractError = (
|
|
170
|
+
input: unknown,
|
|
171
|
+
error: unknown
|
|
172
|
+
): ContextCapsuleContractError => {
|
|
173
|
+
const record =
|
|
174
|
+
input && typeof input === "object"
|
|
175
|
+
? (input as Record<string, unknown>)
|
|
176
|
+
: {};
|
|
177
|
+
const evidence = record.evidence;
|
|
178
|
+
const retrieval = record.retrieval as Record<string, unknown> | undefined;
|
|
179
|
+
const snapshot = retrieval?.indexSnapshot as
|
|
180
|
+
| Record<string, unknown>
|
|
181
|
+
| undefined;
|
|
182
|
+
if (Array.isArray(evidence) && evidence.length === 0) {
|
|
183
|
+
return new ContextCapsuleContractError(
|
|
184
|
+
"no_evidence",
|
|
185
|
+
"Context Capsule requires evidence",
|
|
186
|
+
{
|
|
187
|
+
cause: error,
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (snapshot?.before !== snapshot?.after || snapshot?.stable === false) {
|
|
192
|
+
return new ContextCapsuleContractError(
|
|
193
|
+
"index_changed_during_compile",
|
|
194
|
+
"Index changed while Context Capsule compilation was running",
|
|
195
|
+
{ cause: error }
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (record.budget !== undefined) {
|
|
199
|
+
const budget = record.budget as Record<string, unknown>;
|
|
200
|
+
if (
|
|
201
|
+
typeof budget.usedBytes !== "number" ||
|
|
202
|
+
typeof budget.requestedBytes !== "number" ||
|
|
203
|
+
typeof budget.safetyMarginBytes !== "number" ||
|
|
204
|
+
typeof budget.usedTokens !== "number" ||
|
|
205
|
+
typeof budget.requestedTokens !== "number" ||
|
|
206
|
+
typeof budget.safetyMarginTokens !== "number" ||
|
|
207
|
+
budget.safetyMarginBytes < 0 ||
|
|
208
|
+
budget.safetyMarginTokens < 0 ||
|
|
209
|
+
budget.usedBytes > budget.requestedBytes ||
|
|
210
|
+
budget.usedTokens > budget.requestedTokens ||
|
|
211
|
+
budget.usedBytes + budget.safetyMarginBytes > budget.requestedBytes ||
|
|
212
|
+
budget.usedTokens + budget.safetyMarginTokens > budget.requestedTokens
|
|
213
|
+
) {
|
|
214
|
+
return new ContextCapsuleContractError(
|
|
215
|
+
"invalid_budget",
|
|
216
|
+
"Invalid Context Capsule budget",
|
|
217
|
+
{
|
|
218
|
+
cause: error,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return new ContextCapsuleContractError(
|
|
224
|
+
"invalid_input",
|
|
225
|
+
"Invalid Context Capsule payload",
|
|
226
|
+
{
|
|
227
|
+
cause: error,
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const parsePayload = (input: unknown): ContextCapsulePayloadV1 => {
|
|
233
|
+
try {
|
|
234
|
+
return contextCapsulePayloadV1Schema.parse(input);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
throw contractError(input, error);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export interface ContextCapsuleCreateOptions {
|
|
241
|
+
countTokens?: (accountingJson: string) => number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const identityPayload = (payload: ContextCapsulePayloadV1) => {
|
|
245
|
+
const {
|
|
246
|
+
usedBytes: _usedBytes,
|
|
247
|
+
usedTokens: _usedTokens,
|
|
248
|
+
...stableBudget
|
|
249
|
+
} = payload.budget;
|
|
250
|
+
return { ...payload, budget: stableBudget };
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const accountingPayload = <T extends ContextCapsulePayloadV1>(value: T) => {
|
|
254
|
+
const {
|
|
255
|
+
usedBytes: _usedBytes,
|
|
256
|
+
usedTokens: _usedTokens,
|
|
257
|
+
...budget
|
|
258
|
+
} = value.budget;
|
|
259
|
+
return { ...value, budget };
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const activeTokenCount = (
|
|
263
|
+
accountingJson: string,
|
|
264
|
+
options: ContextCapsuleCreateOptions
|
|
265
|
+
): number => {
|
|
266
|
+
const count = options.countTokens?.(accountingJson);
|
|
267
|
+
if (count === undefined || !Number.isSafeInteger(count) || count < 1) {
|
|
268
|
+
throw new ContextCapsuleContractError(
|
|
269
|
+
"invalid_budget",
|
|
270
|
+
"Active-tokenizer Capsules require a positive deterministic token counter"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return count;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const fixedPointCapsule = (
|
|
277
|
+
payloadInput: ContextCapsulePayloadV1,
|
|
278
|
+
options: ContextCapsuleCreateOptions
|
|
279
|
+
) => {
|
|
280
|
+
const capsuleId = hashCanonical(identityPayload(payloadInput));
|
|
281
|
+
const stableCapsule = { ...payloadInput, capsuleId };
|
|
282
|
+
const exactTokens =
|
|
283
|
+
payloadInput.budget.estimator === "active_tokenizer"
|
|
284
|
+
? activeTokenCount(
|
|
285
|
+
canonicalJson(accountingPayload(stableCapsule)),
|
|
286
|
+
options
|
|
287
|
+
)
|
|
288
|
+
: null;
|
|
289
|
+
let payload = {
|
|
290
|
+
...payloadInput,
|
|
291
|
+
budget: {
|
|
292
|
+
...payloadInput.budget,
|
|
293
|
+
usedBytes: 0,
|
|
294
|
+
usedTokens: exactTokens ?? 1,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
const visited = new Set<string>();
|
|
298
|
+
for (let iteration = 0; iteration < 32; iteration += 1) {
|
|
299
|
+
const capsule = { ...payload, capsuleId };
|
|
300
|
+
const canonical = canonicalJson(capsule);
|
|
301
|
+
const measuredBytes = utf8Bytes(canonical);
|
|
302
|
+
const tokenCount = exactTokens ?? measuredBytes;
|
|
303
|
+
if (
|
|
304
|
+
measuredBytes === payload.budget.usedBytes &&
|
|
305
|
+
tokenCount === payload.budget.usedTokens
|
|
306
|
+
) {
|
|
307
|
+
return capsule;
|
|
308
|
+
}
|
|
309
|
+
const state = `${measuredBytes}:${tokenCount}`;
|
|
310
|
+
if (visited.has(state)) break;
|
|
311
|
+
visited.add(state);
|
|
312
|
+
payload = {
|
|
313
|
+
...payload,
|
|
314
|
+
budget: {
|
|
315
|
+
...payload.budget,
|
|
316
|
+
usedBytes: measuredBytes,
|
|
317
|
+
usedTokens: tokenCount,
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
throw new ContextCapsuleContractError(
|
|
322
|
+
"invalid_budget",
|
|
323
|
+
"Canonical Context Capsule budget did not converge"
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const contextCapsuleBaseV1Schema = contextCapsulePayloadV1Schema.extend({
|
|
328
|
+
capsuleId: sha256Schema,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
export const contextCapsuleV1Schema = contextCapsuleBaseV1Schema.superRefine(
|
|
332
|
+
(value, context) => {
|
|
333
|
+
const { capsuleId, ...payload } = value;
|
|
334
|
+
if (hashCanonical(identityPayload(payload)) !== capsuleId) {
|
|
335
|
+
context.addIssue({
|
|
336
|
+
code: "custom",
|
|
337
|
+
message: "capsuleId does not match payload",
|
|
338
|
+
path: ["capsuleId"],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (utf8Bytes(canonicalJson(value)) !== value.budget.usedBytes) {
|
|
342
|
+
context.addIssue({
|
|
343
|
+
code: "custom",
|
|
344
|
+
message: "usedBytes must equal canonical Capsule JSON UTF-8 bytes",
|
|
345
|
+
path: ["budget", "usedBytes"],
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
349
|
+
value.budget.estimator === "unicode_conservative" &&
|
|
350
|
+
value.budget.usedTokens !== value.budget.usedBytes
|
|
351
|
+
) {
|
|
352
|
+
context.addIssue({
|
|
353
|
+
code: "custom",
|
|
354
|
+
message: "unicode-conservative usedTokens must equal final usedBytes",
|
|
355
|
+
path: ["budget", "usedTokens"],
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
export type ContextCapsuleV1 = z.infer<typeof contextCapsuleV1Schema>;
|
|
362
|
+
|
|
363
|
+
export const contextCapsuleId = (
|
|
364
|
+
input: unknown,
|
|
365
|
+
options: ContextCapsuleCreateOptions = {}
|
|
366
|
+
): string => createContextCapsuleV1(input, options).capsuleId;
|
|
367
|
+
|
|
368
|
+
export const createContextCapsuleV1 = (
|
|
369
|
+
input: unknown,
|
|
370
|
+
options: ContextCapsuleCreateOptions = {}
|
|
371
|
+
): ContextCapsuleV1 => {
|
|
372
|
+
const parsed = normalizePayload(parsePayload(input));
|
|
373
|
+
const capsule = fixedPointCapsule(parsed, options);
|
|
374
|
+
if (
|
|
375
|
+
capsule.budget.usedBytes + capsule.budget.safetyMarginBytes >
|
|
376
|
+
capsule.budget.requestedBytes ||
|
|
377
|
+
capsule.budget.usedTokens + capsule.budget.safetyMarginTokens >
|
|
378
|
+
capsule.budget.requestedTokens
|
|
379
|
+
) {
|
|
380
|
+
throw new ContextCapsuleContractError(
|
|
381
|
+
"invalid_budget",
|
|
382
|
+
"Canonical Context Capsule JSON exceeds its global budget"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return contextCapsuleV1Schema.parse(capsule);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export const parseContextCapsuleV1 = (
|
|
389
|
+
input: unknown,
|
|
390
|
+
options: ContextCapsuleCreateOptions = {}
|
|
391
|
+
): ContextCapsuleV1 => {
|
|
392
|
+
try {
|
|
393
|
+
const parsed = contextCapsuleBaseV1Schema.parse(input);
|
|
394
|
+
const { capsuleId, ...payloadInput } = parsed;
|
|
395
|
+
const payload = normalizePayload(payloadInput);
|
|
396
|
+
const canonical = { ...payload, capsuleId };
|
|
397
|
+
const result = contextCapsuleV1Schema.safeParse(canonical);
|
|
398
|
+
if (!result.success) {
|
|
399
|
+
throw new ContextCapsuleContractError(
|
|
400
|
+
result.error.issues.some((issue) => issue.path.includes("capsuleId"))
|
|
401
|
+
? "identity_mismatch"
|
|
402
|
+
: "invalid_budget",
|
|
403
|
+
"Context Capsule canonical identity or budget does not match its payload",
|
|
404
|
+
{ cause: result.error }
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
result.data.budget.estimator === "active_tokenizer" &&
|
|
409
|
+
options.countTokens !== undefined
|
|
410
|
+
) {
|
|
411
|
+
const recounted = activeTokenCount(
|
|
412
|
+
canonicalJson(accountingPayload(result.data)),
|
|
413
|
+
options
|
|
414
|
+
);
|
|
415
|
+
if (recounted !== result.data.budget.usedTokens) {
|
|
416
|
+
throw new ContextCapsuleContractError(
|
|
417
|
+
"invalid_budget",
|
|
418
|
+
"usedTokens does not match the active-tokenizer accounting projection"
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return result.data;
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error instanceof ContextCapsuleContractError) throw error;
|
|
425
|
+
throw contractError(input, error);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
export const canonicalContextCapsuleJson = (input: unknown): string =>
|
|
430
|
+
canonicalJson(parseContextCapsuleV1(input));
|
|
431
|
+
|
|
432
|
+
export const canonicalContextCapsuleAccountingJson = (input: unknown): string =>
|
|
433
|
+
canonicalJson(accountingPayload(parseContextCapsuleV1(input)));
|
|
434
|
+
|
|
435
|
+
export {
|
|
436
|
+
contextCapsuleVerificationEvidenceSchema,
|
|
437
|
+
contextCapsuleVerificationSchema,
|
|
438
|
+
type ContextCapsuleVerification,
|
|
439
|
+
} from "./context-capsule-verification";
|