@tangle-network/agent-interface 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/certified-context.d.ts +118 -0
- package/dist/certified-context.js +228 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Sha256Digest } from "./agent-candidate.js";
|
|
3
|
+
export type CertifiedContextKind = "prompt" | "skill" | "instructions";
|
|
4
|
+
export type CertifiedContextDelivery = {
|
|
5
|
+
readonly kind: "inline";
|
|
6
|
+
readonly content: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "file";
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly content: string;
|
|
11
|
+
};
|
|
12
|
+
export interface CertifiedContextProvenance {
|
|
13
|
+
/** SHA-256 of the entry id, kind, name, and delivery. */
|
|
14
|
+
readonly contentHash: Sha256Digest;
|
|
15
|
+
/** Positive release number, or null when the source has no released version. */
|
|
16
|
+
readonly version: number | null;
|
|
17
|
+
readonly promotedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CertifiedContextEntry {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly kind: CertifiedContextKind;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly delivery: CertifiedContextDelivery;
|
|
24
|
+
readonly provenance: CertifiedContextProvenance;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Tenant-bound context delivered by Intelligence.
|
|
28
|
+
*
|
|
29
|
+
* This contract intentionally excludes tools, credentials, executable files,
|
|
30
|
+
* profile patches, MCP servers, and arbitrary network requests.
|
|
31
|
+
*/
|
|
32
|
+
export interface CertifiedContext {
|
|
33
|
+
readonly tenantId: string;
|
|
34
|
+
readonly target: string;
|
|
35
|
+
readonly state: "active" | "revoked";
|
|
36
|
+
/** Monotonic decimal revision for this tenant and target. */
|
|
37
|
+
readonly revision: string;
|
|
38
|
+
readonly generatedAt: string;
|
|
39
|
+
readonly expiresAt: string;
|
|
40
|
+
readonly entries: readonly CertifiedContextEntry[];
|
|
41
|
+
/** SHA-256 of tenantId, target, state, revision, and entries. */
|
|
42
|
+
readonly contentHash: Sha256Digest;
|
|
43
|
+
}
|
|
44
|
+
export declare const certifiedContextDeliverySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
45
|
+
kind: z.ZodLiteral<"inline">;
|
|
46
|
+
content: z.ZodString;
|
|
47
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
48
|
+
kind: z.ZodLiteral<"file">;
|
|
49
|
+
path: z.ZodString;
|
|
50
|
+
content: z.ZodString;
|
|
51
|
+
}, z.core.$strict>], "kind">;
|
|
52
|
+
export declare const certifiedContextProvenanceSchema: z.ZodObject<{
|
|
53
|
+
contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
|
|
54
|
+
version: z.ZodNullable<z.ZodNumber>;
|
|
55
|
+
promotedAt: z.ZodISODateTime;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
/** Compute the exact digest stored in an entry's provenance. */
|
|
58
|
+
export declare function certifiedContextEntryContentHash(entry: Pick<CertifiedContextEntry, "id" | "kind" | "name" | "delivery">): Sha256Digest;
|
|
59
|
+
export declare const certifiedContextEntrySchema: z.ZodObject<{
|
|
60
|
+
id: z.ZodString;
|
|
61
|
+
kind: z.ZodEnum<{
|
|
62
|
+
prompt: "prompt";
|
|
63
|
+
instructions: "instructions";
|
|
64
|
+
skill: "skill";
|
|
65
|
+
}>;
|
|
66
|
+
name: z.ZodString;
|
|
67
|
+
delivery: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
68
|
+
kind: z.ZodLiteral<"inline">;
|
|
69
|
+
content: z.ZodString;
|
|
70
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"file">;
|
|
72
|
+
path: z.ZodString;
|
|
73
|
+
content: z.ZodString;
|
|
74
|
+
}, z.core.$strict>], "kind">;
|
|
75
|
+
provenance: z.ZodObject<{
|
|
76
|
+
contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
|
|
77
|
+
version: z.ZodNullable<z.ZodNumber>;
|
|
78
|
+
promotedAt: z.ZodISODateTime;
|
|
79
|
+
}, z.core.$strict>;
|
|
80
|
+
}, z.core.$strict>;
|
|
81
|
+
/** Compute the stable hash for one context revision. */
|
|
82
|
+
export declare function certifiedContextContentHash(context: Pick<CertifiedContext, "tenantId" | "target" | "state" | "revision" | "entries">): Sha256Digest;
|
|
83
|
+
export declare const certifiedContextSchema: z.ZodObject<{
|
|
84
|
+
tenantId: z.ZodString;
|
|
85
|
+
target: z.ZodString;
|
|
86
|
+
state: z.ZodEnum<{
|
|
87
|
+
active: "active";
|
|
88
|
+
revoked: "revoked";
|
|
89
|
+
}>;
|
|
90
|
+
revision: z.ZodString;
|
|
91
|
+
generatedAt: z.ZodISODateTime;
|
|
92
|
+
expiresAt: z.ZodISODateTime;
|
|
93
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
94
|
+
id: z.ZodString;
|
|
95
|
+
kind: z.ZodEnum<{
|
|
96
|
+
prompt: "prompt";
|
|
97
|
+
instructions: "instructions";
|
|
98
|
+
skill: "skill";
|
|
99
|
+
}>;
|
|
100
|
+
name: z.ZodString;
|
|
101
|
+
delivery: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
102
|
+
kind: z.ZodLiteral<"inline">;
|
|
103
|
+
content: z.ZodString;
|
|
104
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
105
|
+
kind: z.ZodLiteral<"file">;
|
|
106
|
+
path: z.ZodString;
|
|
107
|
+
content: z.ZodString;
|
|
108
|
+
}, z.core.$strict>], "kind">;
|
|
109
|
+
provenance: z.ZodObject<{
|
|
110
|
+
contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
|
|
111
|
+
version: z.ZodNullable<z.ZodNumber>;
|
|
112
|
+
promotedAt: z.ZodISODateTime;
|
|
113
|
+
}, z.core.$strict>;
|
|
114
|
+
}, z.core.$strict>>;
|
|
115
|
+
contentHash: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
|
|
116
|
+
}, z.core.$strict>;
|
|
117
|
+
/** Parse, clone, and recursively freeze one untrusted context response. */
|
|
118
|
+
export declare function parseCertifiedContext(value: unknown): CertifiedContext;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { canonicalCandidateDigest, isSafeRelativePath, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
|
|
3
|
+
const MAX_INLINE_CONTEXT_BYTES = 65_536;
|
|
4
|
+
const MAX_TOTAL_INLINE_CONTEXT_BYTES = 131_072;
|
|
5
|
+
const MAX_FILE_CONTEXT_BYTES = 1_048_576;
|
|
6
|
+
const MAX_CERTIFIED_CONTEXT_BYTES = 16_777_216;
|
|
7
|
+
const MAX_CERTIFIED_CONTEXT_LIFETIME_MS = 900_000;
|
|
8
|
+
const revisionPattern = /^(0|[1-9]\d{0,18})$/;
|
|
9
|
+
const nonBlankStringSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.refine((value) => value.trim().length > 0, "value cannot be blank");
|
|
12
|
+
const identifierSchema = nonBlankStringSchema.max(256);
|
|
13
|
+
const relativePathSchema = nonBlankStringSchema
|
|
14
|
+
.max(1_024)
|
|
15
|
+
.refine((value) => isSafeRelativePath(value, false), "value must be a canonical relative path");
|
|
16
|
+
const inlineContentSchema = z
|
|
17
|
+
.string()
|
|
18
|
+
.max(MAX_INLINE_CONTEXT_BYTES)
|
|
19
|
+
.refine((value) => new TextEncoder().encode(value).byteLength <= MAX_INLINE_CONTEXT_BYTES, `inline content exceeds ${MAX_INLINE_CONTEXT_BYTES} UTF-8 bytes`);
|
|
20
|
+
const fileContentSchema = z
|
|
21
|
+
.string()
|
|
22
|
+
.max(MAX_FILE_CONTEXT_BYTES)
|
|
23
|
+
.refine((value) => new TextEncoder().encode(value).byteLength <= MAX_FILE_CONTEXT_BYTES, `file content exceeds ${MAX_FILE_CONTEXT_BYTES} UTF-8 bytes`);
|
|
24
|
+
export const certifiedContextDeliverySchema = z.discriminatedUnion("kind", [
|
|
25
|
+
z.strictObject({
|
|
26
|
+
kind: z.literal("inline"),
|
|
27
|
+
content: inlineContentSchema,
|
|
28
|
+
}),
|
|
29
|
+
z.strictObject({
|
|
30
|
+
kind: z.literal("file"),
|
|
31
|
+
path: relativePathSchema,
|
|
32
|
+
content: fileContentSchema,
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
35
|
+
export const certifiedContextProvenanceSchema = z.strictObject({
|
|
36
|
+
contentHash: sha256DigestSchema,
|
|
37
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
38
|
+
promotedAt: z.iso.datetime(),
|
|
39
|
+
});
|
|
40
|
+
function jsonMaterial(value) {
|
|
41
|
+
const serialized = JSON.stringify(value);
|
|
42
|
+
if (serialized === undefined) {
|
|
43
|
+
throw new Error("certified context material must be JSON serializable");
|
|
44
|
+
}
|
|
45
|
+
return JSON.parse(serialized);
|
|
46
|
+
}
|
|
47
|
+
/** Compute the exact digest stored in an entry's provenance. */
|
|
48
|
+
export function certifiedContextEntryContentHash(entry) {
|
|
49
|
+
return canonicalCandidateDigest(jsonMaterial({
|
|
50
|
+
id: entry.id,
|
|
51
|
+
kind: entry.kind,
|
|
52
|
+
name: entry.name,
|
|
53
|
+
delivery: entry.delivery,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
export const certifiedContextEntrySchema = z
|
|
57
|
+
.strictObject({
|
|
58
|
+
id: identifierSchema,
|
|
59
|
+
kind: z.enum(["prompt", "skill", "instructions"]),
|
|
60
|
+
name: identifierSchema,
|
|
61
|
+
delivery: certifiedContextDeliverySchema,
|
|
62
|
+
provenance: certifiedContextProvenanceSchema,
|
|
63
|
+
})
|
|
64
|
+
.superRefine((entry, context) => {
|
|
65
|
+
if (entry.kind === "skill" && entry.delivery.kind !== "file") {
|
|
66
|
+
context.addIssue({
|
|
67
|
+
code: "custom",
|
|
68
|
+
path: ["delivery", "kind"],
|
|
69
|
+
message: "skills must be delivered as files",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (entry.kind !== "skill" && entry.delivery.kind !== "inline") {
|
|
73
|
+
context.addIssue({
|
|
74
|
+
code: "custom",
|
|
75
|
+
path: ["delivery", "kind"],
|
|
76
|
+
message: "prompts and instructions must be delivered inline",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (entry.delivery.kind === "inline" &&
|
|
80
|
+
entry.delivery.content.trim().length === 0) {
|
|
81
|
+
context.addIssue({
|
|
82
|
+
code: "custom",
|
|
83
|
+
path: ["delivery", "content"],
|
|
84
|
+
message: "inline context cannot be blank",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (entry.provenance.contentHash !== certifiedContextEntryContentHash(entry)) {
|
|
88
|
+
context.addIssue({
|
|
89
|
+
code: "custom",
|
|
90
|
+
path: ["provenance", "contentHash"],
|
|
91
|
+
message: "entry content hash does not match the delivered context",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
/** Compute the stable hash for one context revision. */
|
|
96
|
+
export function certifiedContextContentHash(context) {
|
|
97
|
+
return canonicalCandidateDigest(jsonMaterial(context));
|
|
98
|
+
}
|
|
99
|
+
export const certifiedContextSchema = z
|
|
100
|
+
.strictObject({
|
|
101
|
+
tenantId: identifierSchema,
|
|
102
|
+
target: identifierSchema,
|
|
103
|
+
state: z.enum(["active", "revoked"]),
|
|
104
|
+
revision: z
|
|
105
|
+
.string()
|
|
106
|
+
.regex(revisionPattern)
|
|
107
|
+
.refine((value) => !revisionPattern.test(value) ||
|
|
108
|
+
BigInt(value) <= 9223372036854775807n, "revision exceeds signed 64-bit range"),
|
|
109
|
+
generatedAt: z.iso.datetime(),
|
|
110
|
+
expiresAt: z.iso.datetime(),
|
|
111
|
+
entries: z.array(certifiedContextEntrySchema).max(128),
|
|
112
|
+
contentHash: sha256DigestSchema,
|
|
113
|
+
})
|
|
114
|
+
.superRefine((context, refinement) => {
|
|
115
|
+
const generatedAt = Date.parse(context.generatedAt);
|
|
116
|
+
const expiresAt = Date.parse(context.expiresAt);
|
|
117
|
+
if (expiresAt <= generatedAt) {
|
|
118
|
+
refinement.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
path: ["expiresAt"],
|
|
121
|
+
message: "expiresAt must be after generatedAt",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else if (expiresAt - generatedAt >
|
|
125
|
+
MAX_CERTIFIED_CONTEXT_LIFETIME_MS) {
|
|
126
|
+
refinement.addIssue({
|
|
127
|
+
code: "custom",
|
|
128
|
+
path: ["expiresAt"],
|
|
129
|
+
message: "certified context cannot live longer than 15 minutes",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const serialized = JSON.stringify(context);
|
|
133
|
+
if (new TextEncoder().encode(serialized).byteLength >
|
|
134
|
+
MAX_CERTIFIED_CONTEXT_BYTES) {
|
|
135
|
+
refinement.addIssue({
|
|
136
|
+
code: "too_big",
|
|
137
|
+
maximum: MAX_CERTIFIED_CONTEXT_BYTES,
|
|
138
|
+
origin: "string",
|
|
139
|
+
inclusive: true,
|
|
140
|
+
message: `serialized context exceeds ${MAX_CERTIFIED_CONTEXT_BYTES} UTF-8 bytes`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const ids = new Set();
|
|
144
|
+
const filePaths = new Set();
|
|
145
|
+
let inlineBytes = 0;
|
|
146
|
+
for (const [index, entry] of context.entries.entries()) {
|
|
147
|
+
if (ids.has(entry.id)) {
|
|
148
|
+
refinement.addIssue({
|
|
149
|
+
code: "custom",
|
|
150
|
+
path: ["entries", index, "id"],
|
|
151
|
+
message: `duplicate context id: ${entry.id}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
ids.add(entry.id);
|
|
155
|
+
if (entry.delivery.kind === "file") {
|
|
156
|
+
if (filePaths.has(entry.delivery.path)) {
|
|
157
|
+
refinement.addIssue({
|
|
158
|
+
code: "custom",
|
|
159
|
+
path: ["entries", index, "delivery", "path"],
|
|
160
|
+
message: `duplicate file path: ${entry.delivery.path}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
filePaths.add(entry.delivery.path);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
inlineBytes += new TextEncoder().encode(entry.delivery.content).byteLength;
|
|
167
|
+
}
|
|
168
|
+
if (Date.parse(entry.provenance.promotedAt) > generatedAt) {
|
|
169
|
+
refinement.addIssue({
|
|
170
|
+
code: "custom",
|
|
171
|
+
path: ["entries", index, "provenance", "promotedAt"],
|
|
172
|
+
message: "context cannot be promoted after bundle generation",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (inlineBytes > MAX_TOTAL_INLINE_CONTEXT_BYTES) {
|
|
177
|
+
refinement.addIssue({
|
|
178
|
+
code: "custom",
|
|
179
|
+
path: ["entries"],
|
|
180
|
+
message: `inline context exceeds ${MAX_TOTAL_INLINE_CONTEXT_BYTES} UTF-8 bytes`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if ((context.state === "active" && context.entries.length === 0) ||
|
|
184
|
+
(context.state === "revoked" && context.entries.length !== 0)) {
|
|
185
|
+
refinement.addIssue({
|
|
186
|
+
code: "custom",
|
|
187
|
+
path: ["entries"],
|
|
188
|
+
message: "active context requires entries and revoked context requires none",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const material = {
|
|
192
|
+
tenantId: context.tenantId,
|
|
193
|
+
target: context.target,
|
|
194
|
+
state: context.state,
|
|
195
|
+
revision: context.revision,
|
|
196
|
+
entries: context.entries,
|
|
197
|
+
};
|
|
198
|
+
if (context.contentHash !== certifiedContextContentHash(material)) {
|
|
199
|
+
refinement.addIssue({
|
|
200
|
+
code: "custom",
|
|
201
|
+
path: ["contentHash"],
|
|
202
|
+
message: "context content hash does not match the delivered context",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
function deepFreeze(value) {
|
|
207
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
208
|
+
for (const child of Object.values(value)) {
|
|
209
|
+
deepFreeze(child);
|
|
210
|
+
}
|
|
211
|
+
Object.freeze(value);
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
const _certifiedContextDeliverySchemaMatches = true;
|
|
216
|
+
const _certifiedContextProvenanceSchemaMatches = true;
|
|
217
|
+
const _certifiedContextEntrySchemaMatches = true;
|
|
218
|
+
const _certifiedContextSchemaMatches = true;
|
|
219
|
+
void [
|
|
220
|
+
_certifiedContextDeliverySchemaMatches,
|
|
221
|
+
_certifiedContextProvenanceSchemaMatches,
|
|
222
|
+
_certifiedContextEntrySchemaMatches,
|
|
223
|
+
_certifiedContextSchemaMatches,
|
|
224
|
+
];
|
|
225
|
+
/** Parse, clone, and recursively freeze one untrusted context response. */
|
|
226
|
+
export function parseCertifiedContext(value) {
|
|
227
|
+
return deepFreeze(certifiedContextSchema.parse(value));
|
|
228
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -615,6 +615,7 @@ export * from "./agent-candidate.js";
|
|
|
615
615
|
export * from "./agent-candidate-schema.js";
|
|
616
616
|
export * from "./agent-candidate-promotion-schema.js";
|
|
617
617
|
export * from "./agent-profile.js";
|
|
618
|
+
export * from "./certified-context.js";
|
|
618
619
|
export * from "./profile-diff.js";
|
|
619
620
|
export * from "./harness.js";
|
|
620
621
|
export * from "./harness-capabilities.js";
|
package/dist/index.js
CHANGED
|
@@ -128,6 +128,7 @@ export * from "./agent-candidate.js";
|
|
|
128
128
|
export * from "./agent-candidate-schema.js";
|
|
129
129
|
export * from "./agent-candidate-promotion-schema.js";
|
|
130
130
|
export * from "./agent-profile.js";
|
|
131
|
+
export * from "./certified-context.js";
|
|
131
132
|
export * from "./profile-diff.js";
|
|
132
133
|
export * from "./harness.js";
|
|
133
134
|
export * from "./harness-capabilities.js";
|