@tangle-network/agent-interface 5.8.1 → 5.10.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 +2 -0
- package/dist/agent-profile-canonical.d.ts +14 -0
- package/dist/agent-profile-canonical.js +52 -51
- package/dist/agent-profile-snapshot.js +1 -1
- package/dist/contract-limits.d.ts +6 -13
- package/dist/contract-limits.js +62 -208
- package/dist/harness-capabilities.d.ts +1 -1
- package/dist/harness-capabilities.js +26 -20
- package/dist/own-property-record.d.ts +4 -1
- package/dist/own-property-record.js +44 -15
- package/dist/profile-schema.js +56 -31
- package/dist/proxy-secrets.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -424,3 +424,5 @@ must validate source ownership and active generation bindings for its own runtim
|
|
|
424
424
|
before admitting a knowledge tool. A profile copied to another owner does not
|
|
425
425
|
confer access. Hosts unable to resolve the saved pins must fail closed rather than
|
|
426
426
|
inline JSON into instructions, substitute a newer generation or silently omit them.
|
|
427
|
+
|
|
428
|
+
Profile collection admission uses the shared [contract limits](src/contract-limits.ts) before parsing elements or encoding record keys. Training receipts and knowledge references retain their own typed limits in [the profile schema](src/profile-schema.ts). Opaque metadata is admitted and detached by value as bounded portable data; accessors are refused and cross-key object identity is not preserved. Snapshot validation precedes the final detached copy, preserving explicit undefined fields, canonical hashes and nested isolation. The generic serialized-JSON byte bound is not a whole-profile transport limit.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { AgentCandidateJsonValue } from "./agent-candidate.js";
|
|
2
|
+
type ProfilePath = readonly (string | number)[];
|
|
3
|
+
/** Why a value cannot be AgentProfile JSON, with the path of the offending entry. */
|
|
4
|
+
export declare class AgentProfileValueError extends Error {
|
|
5
|
+
readonly path: ProfilePath;
|
|
6
|
+
constructor(path: ProfilePath, reason: string);
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Normalize profile-shaped data into the canonical JSON domain used for public
|
|
4
10
|
* profile identity. Undefined object entries are omitted; unsupported values,
|
|
@@ -8,5 +14,13 @@ import type { AgentCandidateJsonValue } from "./agent-candidate.js";
|
|
|
8
14
|
* profile-level operations that own validation, identity, or diff semantics.
|
|
9
15
|
*/
|
|
10
16
|
export declare function canonicalAgentProfileValue(value: unknown): AgentCandidateJsonValue | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Throw the path-specific reason a value refused by the bounded admission walker is
|
|
19
|
+
* not profile JSON. Reads no accessor and throws at the contract node and depth
|
|
20
|
+
* bounds, so hostile input stays cheap; returns only when the refusal was a bound
|
|
21
|
+
* this walker does not model, such as string or entry length.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertAgentProfileValue(value: unknown, path: ProfilePath): void;
|
|
11
24
|
/** Canonical serialization after applying AgentProfile normalization rules. */
|
|
12
25
|
export declare function canonicalAgentProfileJson(value: unknown): string | undefined;
|
|
26
|
+
export {};
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { canonicalCandidateJson, isWellFormedUnicode, } from "./agent-candidate-schema-common.js";
|
|
2
|
+
import { CONTRACT_MAX_JSON_DEPTH, CONTRACT_MAX_JSON_NODES } from "./contract-limits.js";
|
|
3
|
+
/** Why a value cannot be AgentProfile JSON, with the path of the offending entry. */
|
|
4
|
+
export class AgentProfileValueError extends Error {
|
|
5
|
+
path;
|
|
6
|
+
constructor(path, reason) {
|
|
7
|
+
super(`AgentProfile ${renderPath(path)} ${reason}`);
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.name = "AgentProfileValueError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* Normalize profile-shaped data into the canonical JSON domain used for public
|
|
4
14
|
* profile identity. Undefined object entries are omitted; unsupported values,
|
|
@@ -10,66 +20,57 @@ import { canonicalCandidateJson, isWellFormedUnicode, } from "./agent-candidate-
|
|
|
10
20
|
export function canonicalAgentProfileValue(value) {
|
|
11
21
|
return normalizeAgentProfileValue(value, [], new Set());
|
|
12
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Throw the path-specific reason a value refused by the bounded admission walker is
|
|
25
|
+
* not profile JSON. Reads no accessor and throws at the contract node and depth
|
|
26
|
+
* bounds, so hostile input stays cheap; returns only when the refusal was a bound
|
|
27
|
+
* this walker does not model, such as string or entry length.
|
|
28
|
+
*/
|
|
29
|
+
export function assertAgentProfileValue(value, path) {
|
|
30
|
+
normalizeAgentProfileValue(value, path, new Set(), { nodes: CONTRACT_MAX_JSON_NODES, base: path.length });
|
|
31
|
+
}
|
|
13
32
|
/** Canonical serialization after applying AgentProfile normalization rules. */
|
|
14
33
|
export function canonicalAgentProfileJson(value) {
|
|
15
34
|
const normalized = canonicalAgentProfileValue(value);
|
|
16
|
-
return normalized === undefined
|
|
17
|
-
? undefined
|
|
18
|
-
: canonicalCandidateJson(normalized);
|
|
35
|
+
return normalized === undefined ? undefined : canonicalCandidateJson(normalized);
|
|
19
36
|
}
|
|
20
|
-
function normalizeAgentProfileValue(value, path, ancestors) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
typeof value === "string") {
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
if (typeof value === "number") {
|
|
29
|
-
if (!Number.isFinite(value)) {
|
|
30
|
-
throw new Error(`AgentProfile ${renderPath(path)} must be finite`);
|
|
31
|
-
}
|
|
37
|
+
function normalizeAgentProfileValue(value, path, ancestors, bounds) {
|
|
38
|
+
const refuse = (reason, at = path) => { throw new AgentProfileValueError(at, reason); };
|
|
39
|
+
if (bounds && --bounds.nodes < 0)
|
|
40
|
+
refuse("exceeds the contract node bound");
|
|
41
|
+
if (value === undefined || value === null || typeof value === "boolean" || typeof value === "string")
|
|
32
42
|
return value;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (ancestors.has(value))
|
|
38
|
-
|
|
39
|
-
}
|
|
43
|
+
if (typeof value === "number")
|
|
44
|
+
return Number.isFinite(value) ? value : refuse("must be finite");
|
|
45
|
+
if (typeof value !== "object")
|
|
46
|
+
return refuse("is not JSON serializable");
|
|
47
|
+
if (ancestors.has(value))
|
|
48
|
+
refuse("must be acyclic");
|
|
40
49
|
const prototype = Object.getPrototypeOf(value);
|
|
41
|
-
if (!Array.isArray(value) &&
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
50
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null)
|
|
51
|
+
refuse("must be a plain JSON object");
|
|
52
|
+
if (bounds && path.length - bounds.base >= CONTRACT_MAX_JSON_DEPTH)
|
|
53
|
+
refuse("exceeds the contract depth");
|
|
46
54
|
const nextAncestors = new Set(ancestors).add(value);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
}
|
|
55
|
+
// Entries are read through own data descriptors so refusing a value never runs an accessor.
|
|
56
|
+
const entry = (key) => {
|
|
57
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
58
|
+
if (!descriptor)
|
|
59
|
+
return refuse("cannot be a sparse array hole", [...path, key]);
|
|
60
|
+
if (!("value" in descriptor))
|
|
61
|
+
return refuse("must be a data property", [...path, key]);
|
|
62
|
+
const normalized = normalizeAgentProfileValue(descriptor.value, [...path, key], nextAncestors, bounds);
|
|
63
|
+
return normalized === undefined && typeof key === "number" ? refuse("cannot be undefined", [...path, key]) : normalized;
|
|
64
|
+
};
|
|
65
|
+
if (Array.isArray(value))
|
|
66
|
+
return Array.from({ length: value.length }, (_, index) => entry(index));
|
|
60
67
|
const material = Object.create(null);
|
|
61
|
-
for (const
|
|
62
|
-
if (!isWellFormedUnicode(key))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const normalized = normalizeAgentProfileValue(entry, [...path, key], nextAncestors);
|
|
68
|
+
for (const key of Object.keys(value)) {
|
|
69
|
+
if (!isWellFormedUnicode(key))
|
|
70
|
+
refuse("has a record key that is not valid Unicode");
|
|
71
|
+
const normalized = entry(key);
|
|
66
72
|
if (normalized !== undefined) {
|
|
67
|
-
Object.defineProperty(material, key, {
|
|
68
|
-
value: normalized,
|
|
69
|
-
enumerable: true,
|
|
70
|
-
configurable: true,
|
|
71
|
-
writable: true,
|
|
72
|
-
});
|
|
73
|
+
Object.defineProperty(material, key, { value: normalized, enumerable: true, configurable: true, writable: true });
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
return material;
|
|
@@ -9,7 +9,7 @@ import { agentProfileSchema } from "./profile-schema.js";
|
|
|
9
9
|
* existing canonical profile JSON domain fail instead of becoming mutable state.
|
|
10
10
|
*/
|
|
11
11
|
export function snapshotAgentProfile(value) {
|
|
12
|
-
const parsed = agentProfileSchema.parse(
|
|
12
|
+
const parsed = structuredClone(agentProfileSchema.parse(value));
|
|
13
13
|
canonicalAgentProfileValue(parsed);
|
|
14
14
|
return deepFreeze(parsed);
|
|
15
15
|
}
|
|
@@ -12,23 +12,16 @@ export declare const CONTRACT_MAX_JSON_NODES = 8192;
|
|
|
12
12
|
export declare const boundedStringSchema: z.ZodString;
|
|
13
13
|
export declare const confidentialAttestationQuoteSchema: z.ZodString;
|
|
14
14
|
export declare const boundedIdentifierSchema: z.ZodString;
|
|
15
|
-
/**
|
|
16
|
-
* Validate JSON without asking Zod to recursively copy an attacker-sized
|
|
17
|
-
* value. This is intentionally iterative so depth and collection limits are
|
|
18
|
-
* checked before canonical serialization.
|
|
19
|
-
*/
|
|
15
|
+
/** Admit ordinary JSON before recursive parsing, copying or hashing. */
|
|
20
16
|
export declare function isBoundedJsonValue(value: unknown): boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Validate provider event and terminal content. Unlike ordinary metadata,
|
|
23
|
-
* content may contain a single large transcript or tool result. It remains
|
|
24
|
-
* finite, plain JSON with the normal structural limits, and its complete JSON
|
|
25
|
-
* representation is capped exactly by UTF-8 bytes.
|
|
26
|
-
*/
|
|
17
|
+
/** Event content permits large strings within the exact serialized UTF-8 budget. */
|
|
27
18
|
export declare function isBoundedEventContentJson(value: unknown, { omitUndefinedObjectFields }?: {
|
|
28
19
|
omitUndefinedObjectFields?: boolean;
|
|
29
20
|
}): boolean;
|
|
30
|
-
/**
|
|
31
|
-
export declare function isBoundedJsonMaterial(value: unknown
|
|
21
|
+
/** Digest material omits undefined object fields; portable profile values require data properties. */
|
|
22
|
+
export declare function isBoundedJsonMaterial(value: unknown, { dataPropertiesOnly }?: {
|
|
23
|
+
dataPropertiesOnly?: boolean;
|
|
24
|
+
}): boolean;
|
|
32
25
|
export declare const boundedJsonSchema: z.ZodCustom<unknown, unknown>;
|
|
33
26
|
export declare const boundedJsonRecordSchema: z.ZodCustom<Record<string, unknown>, Record<string, unknown>>;
|
|
34
27
|
/** One provider event payload or terminal response, bounded as serialized UTF-8 JSON. */
|
package/dist/contract-limits.js
CHANGED
|
@@ -17,72 +17,9 @@ export const boundedIdentifierSchema = boundedStringSchema
|
|
|
17
17
|
.min(1)
|
|
18
18
|
.max(CONTRACT_MAX_IDENTIFIER_LENGTH)
|
|
19
19
|
.refine((value) => value.trim() === value, "identifier cannot have outer whitespace");
|
|
20
|
-
/**
|
|
21
|
-
* Validate JSON without asking Zod to recursively copy an attacker-sized
|
|
22
|
-
* value. This is intentionally iterative so depth and collection limits are
|
|
23
|
-
* checked before canonical serialization.
|
|
24
|
-
*/
|
|
20
|
+
/** Admit ordinary JSON before recursive parsing, copying or hashing. */
|
|
25
21
|
export function isBoundedJsonValue(value) {
|
|
26
|
-
|
|
27
|
-
const ancestors = new Set();
|
|
28
|
-
let nodes = 0;
|
|
29
|
-
while (pending.length > 0) {
|
|
30
|
-
const item = pending.pop();
|
|
31
|
-
if (!item)
|
|
32
|
-
continue;
|
|
33
|
-
nodes += 1;
|
|
34
|
-
if (nodes > CONTRACT_MAX_JSON_NODES)
|
|
35
|
-
return false;
|
|
36
|
-
const current = item.value;
|
|
37
|
-
if (item.leave) {
|
|
38
|
-
ancestors.delete(current);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (current === undefined)
|
|
42
|
-
return false;
|
|
43
|
-
if (current === null || typeof current === "boolean")
|
|
44
|
-
continue;
|
|
45
|
-
if (typeof current === "string") {
|
|
46
|
-
if (current.length > CONTRACT_MAX_STRING_LENGTH)
|
|
47
|
-
return false;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (typeof current === "number") {
|
|
51
|
-
if (!Number.isFinite(current))
|
|
52
|
-
return false;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
if (ancestors.has(current))
|
|
59
|
-
return false;
|
|
60
|
-
ancestors.add(current);
|
|
61
|
-
pending.push({ value: current, depth: item.depth, leave: true });
|
|
62
|
-
if (Array.isArray(current)) {
|
|
63
|
-
if (current.length > CONTRACT_MAX_ARRAY_LENGTH)
|
|
64
|
-
return false;
|
|
65
|
-
for (const entry of current) {
|
|
66
|
-
pending.push({ value: entry, depth: item.depth + 1 });
|
|
67
|
-
}
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const prototype = Object.getPrototypeOf(current);
|
|
71
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
72
|
-
return false;
|
|
73
|
-
const keys = Object.keys(current);
|
|
74
|
-
if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
|
|
75
|
-
return false;
|
|
76
|
-
for (const key of keys) {
|
|
77
|
-
if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
|
|
78
|
-
return false;
|
|
79
|
-
pending.push({
|
|
80
|
-
value: current[key],
|
|
81
|
-
depth: item.depth + 1,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return true;
|
|
22
|
+
return boundedJson(value);
|
|
86
23
|
}
|
|
87
24
|
/** Exact serialized JSON UTF-8 byte count for one string scalar. */
|
|
88
25
|
function serializedJsonStringBytes(value) {
|
|
@@ -122,187 +59,104 @@ function serializedJsonStringBytes(value) {
|
|
|
122
59
|
}
|
|
123
60
|
return bytes;
|
|
124
61
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Validate provider event and terminal content. Unlike ordinary metadata,
|
|
127
|
-
* content may contain a single large transcript or tool result. It remains
|
|
128
|
-
* finite, plain JSON with the normal structural limits, and its complete JSON
|
|
129
|
-
* representation is capped exactly by UTF-8 bytes.
|
|
130
|
-
*/
|
|
62
|
+
/** Event content permits large strings within the exact serialized UTF-8 budget. */
|
|
131
63
|
export function isBoundedEventContentJson(value, { omitUndefinedObjectFields = false } = {}) {
|
|
64
|
+
return boundedJson(value, { content: true, omitUndefinedObjectFields });
|
|
65
|
+
}
|
|
66
|
+
/** Digest material omits undefined object fields; portable profile values require data properties. */
|
|
67
|
+
export function isBoundedJsonMaterial(value, { dataPropertiesOnly = false } = {}) {
|
|
68
|
+
return boundedJson(value, { material: true, omitUndefinedObjectFields: true, dataPropertiesOnly });
|
|
69
|
+
}
|
|
70
|
+
/** One iterative walker owns structural admission; typed domains keep their own schema limits. */
|
|
71
|
+
function boundedJson(value, options = {}) {
|
|
132
72
|
const pending = [
|
|
133
73
|
{ value, depth: 0 },
|
|
134
74
|
];
|
|
135
75
|
const ancestors = new Set();
|
|
136
76
|
let nodes = 0;
|
|
137
77
|
let bytes = 0;
|
|
138
|
-
const addBytes = (
|
|
139
|
-
|
|
140
|
-
return bytes <= CONTRACT_MAX_JSON_BYTES;
|
|
141
|
-
};
|
|
142
|
-
while (pending.length > 0) {
|
|
78
|
+
const addBytes = (count) => !options.content || (bytes += count) <= CONTRACT_MAX_JSON_BYTES;
|
|
79
|
+
while (pending.length) {
|
|
143
80
|
const item = pending.pop();
|
|
144
|
-
if (!item)
|
|
145
|
-
continue;
|
|
146
81
|
const current = item.value;
|
|
147
|
-
if (
|
|
148
|
-
continue;
|
|
149
|
-
nodes += 1;
|
|
150
|
-
if (nodes > CONTRACT_MAX_JSON_NODES)
|
|
82
|
+
if ((!item.leave || !options.material) && ++nodes > CONTRACT_MAX_JSON_NODES)
|
|
151
83
|
return false;
|
|
84
|
+
if (current === undefined && item.omitUndefined)
|
|
85
|
+
continue;
|
|
152
86
|
if (item.leave) {
|
|
153
87
|
ancestors.delete(current);
|
|
154
88
|
continue;
|
|
155
89
|
}
|
|
156
|
-
if (current === null) {
|
|
157
|
-
if (!addBytes(4))
|
|
158
|
-
return false;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (typeof current === "boolean") {
|
|
162
|
-
if (!addBytes(current ? 4 : 5))
|
|
90
|
+
if (current === null || typeof current === "boolean") {
|
|
91
|
+
if (!addBytes(current === null ? 4 : current ? 4 : 5))
|
|
163
92
|
return false;
|
|
164
93
|
continue;
|
|
165
94
|
}
|
|
166
95
|
if (typeof current === "string") {
|
|
167
|
-
if (!addBytes(serializedJsonStringBytes(current)))
|
|
96
|
+
if (options.content ? !addBytes(serializedJsonStringBytes(current)) : current.length > CONTRACT_MAX_STRING_LENGTH)
|
|
168
97
|
return false;
|
|
169
98
|
continue;
|
|
170
99
|
}
|
|
171
100
|
if (typeof current === "number") {
|
|
172
|
-
if (!Number.isFinite(current))
|
|
173
|
-
return false;
|
|
174
|
-
if (!addBytes(JSON.stringify(current).length))
|
|
101
|
+
if (!Number.isFinite(current) || !addBytes(JSON.stringify(current).length))
|
|
175
102
|
return false;
|
|
176
103
|
continue;
|
|
177
104
|
}
|
|
178
|
-
if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH)
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
if (ancestors.has(current))
|
|
105
|
+
if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH || ancestors.has(current))
|
|
182
106
|
return false;
|
|
183
107
|
ancestors.add(current);
|
|
184
108
|
pending.push({ value: current, depth: item.depth, leave: true });
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const keys = Reflect.ownKeys(current);
|
|
194
|
-
|
|
195
|
-
if (entryKeys.length !== current.length)
|
|
196
|
-
return false;
|
|
197
|
-
if (entryKeys.some((key) => {
|
|
198
|
-
if (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key))
|
|
199
|
-
return true;
|
|
200
|
-
const index = Number(key);
|
|
201
|
-
return !Number.isSafeInteger(index) || index < 0 || index >= current.length || index >= 4_294_967_295;
|
|
202
|
-
})) {
|
|
109
|
+
const array = Array.isArray(current);
|
|
110
|
+
if (array && current.length > CONTRACT_MAX_ARRAY_LENGTH)
|
|
111
|
+
return false;
|
|
112
|
+
const prototype = Object.getPrototypeOf(current);
|
|
113
|
+
if ((!array && prototype !== Object.prototype && prototype !== null) ||
|
|
114
|
+
(array && options.content && prototype !== Array.prototype && prototype !== null))
|
|
115
|
+
return false;
|
|
116
|
+
if (options.content || options.dataPropertiesOnly) {
|
|
117
|
+
const keys = Reflect.ownKeys(current).filter((key) => !array || key !== "length");
|
|
118
|
+
if (array ? keys.length !== current.length : keys.length > CONTRACT_MAX_MAP_ENTRIES)
|
|
203
119
|
return false;
|
|
204
|
-
|
|
205
|
-
for (const key of
|
|
120
|
+
let entries = 0;
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
if (typeof key !== "string" || (!array && key.length > CONTRACT_MAX_IDENTIFIER_LENGTH))
|
|
123
|
+
return false;
|
|
124
|
+
if (array && (!/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= current.length || Number(key) >= 4_294_967_295))
|
|
125
|
+
return false;
|
|
206
126
|
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
207
|
-
if (
|
|
127
|
+
if (!descriptor?.enumerable || !("value" in descriptor))
|
|
128
|
+
return false;
|
|
129
|
+
if (!array && descriptor.value === undefined && options.omitUndefinedObjectFields) {
|
|
130
|
+
if (++nodes > CONTRACT_MAX_JSON_NODES)
|
|
131
|
+
return false;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (++entries > (array ? CONTRACT_MAX_ARRAY_LENGTH : CONTRACT_MAX_MAP_ENTRIES))
|
|
135
|
+
return false;
|
|
136
|
+
if (!array && !addBytes(serializedJsonStringBytes(key) + 1))
|
|
208
137
|
return false;
|
|
209
138
|
pending.push({ value: descriptor.value, depth: item.depth + 1 });
|
|
210
139
|
}
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
const prototype = Object.getPrototypeOf(current);
|
|
214
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
215
|
-
return false;
|
|
216
|
-
const entries = [];
|
|
217
|
-
for (const key of Reflect.ownKeys(current)) {
|
|
218
|
-
if (typeof key !== "string" || key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
|
|
219
|
-
return false;
|
|
220
|
-
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
221
|
-
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
|
|
222
|
-
return false;
|
|
223
|
-
if (descriptor.value === undefined && omitUndefinedObjectFields)
|
|
224
|
-
continue;
|
|
225
|
-
entries.push([key, descriptor]);
|
|
226
|
-
if (entries.length > CONTRACT_MAX_MAP_ENTRIES)
|
|
140
|
+
if (!addBytes(2 + Math.max(entries - 1, 0)))
|
|
227
141
|
return false;
|
|
228
142
|
}
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
for (const [key, descriptor] of entries) {
|
|
232
|
-
if (!addBytes(serializedJsonStringBytes(key) + 1))
|
|
233
|
-
return false;
|
|
234
|
-
pending.push({
|
|
235
|
-
value: descriptor.value,
|
|
236
|
-
depth: item.depth + 1,
|
|
237
|
-
omitUndefined: omitUndefinedObjectFields,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
/** Validate digest input while matching JSON's omission of undefined object fields. */
|
|
244
|
-
export function isBoundedJsonMaterial(value) {
|
|
245
|
-
const pending = [{ value, depth: 0 }];
|
|
246
|
-
const ancestors = new Set();
|
|
247
|
-
let nodes = 0;
|
|
248
|
-
while (pending.length > 0) {
|
|
249
|
-
const item = pending.pop();
|
|
250
|
-
if (!item)
|
|
251
|
-
continue;
|
|
252
|
-
if (item.leave) {
|
|
253
|
-
ancestors.delete(item.value);
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (item.value === undefined) {
|
|
257
|
-
if (item.omitUndefined === true)
|
|
258
|
-
continue;
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
nodes += 1;
|
|
262
|
-
if (nodes > CONTRACT_MAX_JSON_NODES)
|
|
263
|
-
return false;
|
|
264
|
-
const current = item.value;
|
|
265
|
-
if (current === null || typeof current === "boolean")
|
|
266
|
-
continue;
|
|
267
|
-
if (typeof current === "string") {
|
|
268
|
-
if (current.length > CONTRACT_MAX_STRING_LENGTH)
|
|
269
|
-
return false;
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (typeof current === "number") {
|
|
273
|
-
if (!Number.isFinite(current))
|
|
274
|
-
return false;
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
if (ancestors.has(current))
|
|
281
|
-
return false;
|
|
282
|
-
ancestors.add(current);
|
|
283
|
-
pending.push({ value: current, depth: item.depth, leave: true });
|
|
284
|
-
if (Array.isArray(current)) {
|
|
285
|
-
if (current.length > CONTRACT_MAX_ARRAY_LENGTH)
|
|
286
|
-
return false;
|
|
287
|
-
for (const entry of current) {
|
|
143
|
+
else if (array) {
|
|
144
|
+
for (const entry of current)
|
|
288
145
|
pending.push({ value: entry, depth: item.depth + 1 });
|
|
289
|
-
}
|
|
290
|
-
continue;
|
|
291
146
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const keys = Object.keys(current);
|
|
296
|
-
if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
|
|
297
|
-
return false;
|
|
298
|
-
for (const key of keys) {
|
|
299
|
-
if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
|
|
147
|
+
else {
|
|
148
|
+
const keys = Object.keys(current);
|
|
149
|
+
if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
|
|
300
150
|
return false;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
151
|
+
for (const key of keys) {
|
|
152
|
+
if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
|
|
153
|
+
return false;
|
|
154
|
+
pending.push({
|
|
155
|
+
value: current[key],
|
|
156
|
+
depth: item.depth + 1,
|
|
157
|
+
omitUndefined: options.omitUndefinedObjectFields,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
306
160
|
}
|
|
307
161
|
}
|
|
308
162
|
return true;
|
|
@@ -130,7 +130,7 @@ export declare function reasoningEffortsFor(harness: HarnessType, model?: ModelR
|
|
|
130
130
|
* that verifies the receipt read this function, so a CLI that renames a rung moves both at once
|
|
131
131
|
* instead of turning into a refused run.
|
|
132
132
|
*/
|
|
133
|
-
export declare function nativeReasoningControl(harness: HarnessType, effort: ReasoningEffort | null): string | null;
|
|
133
|
+
export declare function nativeReasoningControl(harness: HarnessType, effort: ReasoningEffort | null, model?: string): string | null;
|
|
134
134
|
/** Whether the harness's runner honors a per-turn MODEL override (vs. picking the model itself). */
|
|
135
135
|
export declare function harnessHonorsModel(harness: HarnessType): boolean;
|
|
136
136
|
/** Whether the harness's runner honors a reasoning-EFFORT override (vs. dropping it). */
|
|
@@ -253,8 +253,8 @@ export function snapHarnessToModel(harness, modelId) {
|
|
|
253
253
|
* - claude-code: `--effort` accepts `low|medium|high|xhigh|max`; canonical `ultracode` maps to
|
|
254
254
|
* native `max`. It cannot express `none` or `minimal`, and an unsupported value is warned about
|
|
255
255
|
* and silently replaced with the default rather than rejected — so the set must not overstate.
|
|
256
|
-
* - pi: `--thinking` accepts `off|minimal|low|medium|high|xhigh
|
|
257
|
-
* `
|
|
256
|
+
* - pi: `--thinking` accepts `off|minimal|low|medium|high|xhigh|max` (pi 1.4.0
|
|
257
|
+
* `cli/args.js`); canonical `none` maps to `off` and `ultracode` to `max`.
|
|
258
258
|
* - prime: the prime fork of the pi line accepts the same `--thinking` set
|
|
259
259
|
* (`off|minimal|low|medium|high|xhigh|max`); canonical `none` maps to `off` and `ultracode` to
|
|
260
260
|
* `max`.
|
|
@@ -316,19 +316,26 @@ export function reasoningEffortsFor(harness, model) {
|
|
|
316
316
|
* `low`, and `ultracode` becomes its ceiling `max`.
|
|
317
317
|
* - codex — `-c model_reasoning_effort="<value>"`; it takes the canonical rungs directly and
|
|
318
318
|
* names its ceiling `ultra`.
|
|
319
|
-
* - pi — `--thinking <value>`; `none` becomes `off` and `ultracode`
|
|
320
|
-
*
|
|
321
|
-
* - prime — `--thinking <value>`; the fork carries `max` above `xhigh`, so `ultracode` reaches
|
|
322
|
-
* `max`. This is the one rung where prime and pi differ.
|
|
319
|
+
* - pi, prime and openclaw — `--thinking <value>`; `none` becomes `off` and `ultracode` reaches
|
|
320
|
+
* `max` (pi 1.4.0 `cli/args.js`, the prime fork's `cli/args.ts`, openclaw `agent --help`).
|
|
323
321
|
* - kimi-code — the control is the FLAG itself, `--thinking` or `--no-thinking`, because kimi's
|
|
324
322
|
* thinking switch is binary. `medium` is its default and passes no flag at all.
|
|
325
|
-
* - opencode —
|
|
323
|
+
* - opencode — `--variant <value>`, forwarded verbatim as the provider's reasoning effort; `none`
|
|
324
|
+
* omits the flag, as the sandbox adapter does. No
|
|
325
|
+
* provider spells `ultracode`, so it becomes the model's ceiling: `max` for GLM (Z.ai answers
|
|
326
|
+
* 400 code 1210 for anything outside `none…max`) and `xhigh` for every other model (GPT-6
|
|
327
|
+
* answers `max` with 400). Pass `model` so this rung resolves; without it `xhigh` is sent.
|
|
326
328
|
*
|
|
327
329
|
* A harness with no entry applies NO native control: either it derives thinking from the model
|
|
328
330
|
* (gemini's `--thinking-budget`) or it plumbs no thinking flag at all (see
|
|
329
331
|
* {@link harnessHonorsEffort}). Both answer `null`, which is what their receipts carry — so an
|
|
330
332
|
* unknown harness is never asserted to have applied a control it cannot apply.
|
|
331
333
|
*/
|
|
334
|
+
const thinkingRung = (effort) => {
|
|
335
|
+
if (effort === "none")
|
|
336
|
+
return "off";
|
|
337
|
+
return effort === "ultracode" ? "max" : effort;
|
|
338
|
+
};
|
|
332
339
|
const harnessNativeReasoningControl = {
|
|
333
340
|
"claude-code": (effort) => {
|
|
334
341
|
if (effort === "none" || effort === "minimal")
|
|
@@ -336,16 +343,9 @@ const harnessNativeReasoningControl = {
|
|
|
336
343
|
return effort === "ultracode" ? "max" : effort;
|
|
337
344
|
},
|
|
338
345
|
codex: (effort) => (effort === "ultracode" ? "ultra" : effort),
|
|
339
|
-
pi:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return effort === "ultracode" ? "xhigh" : effort;
|
|
343
|
-
},
|
|
344
|
-
prime: (effort) => {
|
|
345
|
-
if (effort === "none")
|
|
346
|
-
return "off";
|
|
347
|
-
return effort === "ultracode" ? "max" : effort;
|
|
348
|
-
},
|
|
346
|
+
pi: thinkingRung,
|
|
347
|
+
prime: thinkingRung,
|
|
348
|
+
openclaw: thinkingRung,
|
|
349
349
|
"kimi-code": (effort) => {
|
|
350
350
|
if (effort === "medium")
|
|
351
351
|
return null;
|
|
@@ -353,7 +353,13 @@ const harnessNativeReasoningControl = {
|
|
|
353
353
|
? "--no-thinking"
|
|
354
354
|
: "--thinking";
|
|
355
355
|
},
|
|
356
|
-
opencode: (effort) =>
|
|
356
|
+
opencode: (effort, model) => {
|
|
357
|
+
if (effort === "none")
|
|
358
|
+
return null;
|
|
359
|
+
if (effort !== "ultracode")
|
|
360
|
+
return effort;
|
|
361
|
+
return /(?:^|\/)glm-/iu.test(model ?? "") ? "max" : "xhigh";
|
|
362
|
+
},
|
|
357
363
|
};
|
|
358
364
|
/**
|
|
359
365
|
* The native control token a harness applies for one canonical effort, or `null` when it applies
|
|
@@ -363,10 +369,10 @@ const harnessNativeReasoningControl = {
|
|
|
363
369
|
* that verifies the receipt read this function, so a CLI that renames a rung moves both at once
|
|
364
370
|
* instead of turning into a refused run.
|
|
365
371
|
*/
|
|
366
|
-
export function nativeReasoningControl(harness, effort) {
|
|
372
|
+
export function nativeReasoningControl(harness, effort, model) {
|
|
367
373
|
if (effort === null)
|
|
368
374
|
return null;
|
|
369
|
-
return harnessNativeReasoningControl[harness]?.(effort) ?? null;
|
|
375
|
+
return harnessNativeReasoningControl[harness]?.(effort, model) ?? null;
|
|
370
376
|
}
|
|
371
377
|
// ── Per-turn selector support (does the harness honor the chat pickers?) ──────
|
|
372
378
|
/**
|
|
@@ -6,4 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
* sees it, then restore the exact UTF-16 key with defineProperty so all string
|
|
7
7
|
* keys allowed by `Record<string, ...>` remain data rather than object syntax.
|
|
8
8
|
*/
|
|
9
|
-
export declare function ownPropertyRecordSchema<ValueSchema extends z.ZodType>(valueSchema: ValueSchema
|
|
9
|
+
export declare function ownPropertyRecordSchema<ValueSchema extends z.ZodType>(valueSchema: ValueSchema, { maxEntries, prepareValue }?: {
|
|
10
|
+
maxEntries?: number;
|
|
11
|
+
prepareValue?: (key: string, value: unknown) => unknown;
|
|
12
|
+
}): z.ZodType<Record<string, z.output<ValueSchema>>>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { CONTRACT_MAX_IDENTIFIER_LENGTH, CONTRACT_MAX_MAP_ENTRIES } from "./contract-limits.js";
|
|
2
3
|
import { isWellFormedUnicode } from "./agent-candidate-schema-common.js";
|
|
4
|
+
import { AgentProfileValueError } from "./agent-profile-canonical.js";
|
|
3
5
|
/**
|
|
4
6
|
* Zod records currently assign parsed values onto an ordinary object. The
|
|
5
7
|
* special own key `__proto__` therefore invokes the legacy prototype setter
|
|
@@ -7,8 +9,48 @@ import { isWellFormedUnicode } from "./agent-candidate-schema-common.js";
|
|
|
7
9
|
* sees it, then restore the exact UTF-16 key with defineProperty so all string
|
|
8
10
|
* keys allowed by `Record<string, ...>` remain data rather than object syntax.
|
|
9
11
|
*/
|
|
10
|
-
export function ownPropertyRecordSchema(valueSchema) {
|
|
11
|
-
return z.preprocess(
|
|
12
|
+
export function ownPropertyRecordSchema(valueSchema, { maxEntries = CONTRACT_MAX_MAP_ENTRIES, prepareValue } = {}) {
|
|
13
|
+
return z.preprocess((value, context) => {
|
|
14
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
15
|
+
return value;
|
|
16
|
+
const prototype = Object.getPrototypeOf(value);
|
|
17
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
18
|
+
return [];
|
|
19
|
+
let entries = 0;
|
|
20
|
+
for (const key in value) {
|
|
21
|
+
if (!Object.hasOwn(value, key))
|
|
22
|
+
continue;
|
|
23
|
+
if (++entries > maxEntries || key.length > CONTRACT_MAX_IDENTIFIER_LENGTH) {
|
|
24
|
+
context.addIssue({ code: "custom", message: "record exceeds the contract bounds" });
|
|
25
|
+
return z.NEVER;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const encoded = Object.create(null);
|
|
29
|
+
for (const key of Object.keys(value)) {
|
|
30
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
31
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
32
|
+
context.addIssue({ code: "custom", message: "record entries must be data properties" });
|
|
33
|
+
return z.NEVER;
|
|
34
|
+
}
|
|
35
|
+
let entry;
|
|
36
|
+
try {
|
|
37
|
+
entry = prepareValue ? prepareValue(key, descriptor.value) : descriptor.value;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// Only a profile-value refusal is a validation issue; any other throw is a defect.
|
|
41
|
+
if (!(error instanceof AgentProfileValueError))
|
|
42
|
+
throw error;
|
|
43
|
+
context.addIssue({ code: "custom", path: [...error.path], message: error.message });
|
|
44
|
+
return z.NEVER;
|
|
45
|
+
}
|
|
46
|
+
if (entry === z.NEVER) {
|
|
47
|
+
context.addIssue({ code: "custom", message: "record value exceeds the contract bounds or is not portable JSON" });
|
|
48
|
+
return z.NEVER;
|
|
49
|
+
}
|
|
50
|
+
encoded[encodeRecordKey(key)] = entry;
|
|
51
|
+
}
|
|
52
|
+
return encoded;
|
|
53
|
+
}, z.record(z
|
|
12
54
|
.string()
|
|
13
55
|
.regex(/^u(?:[0-9a-f]{4})*$/, "record keys must contain valid Unicode"), valueSchema).transform((record) => {
|
|
14
56
|
const restored = {};
|
|
@@ -23,19 +65,6 @@ export function ownPropertyRecordSchema(valueSchema) {
|
|
|
23
65
|
return restored;
|
|
24
66
|
}));
|
|
25
67
|
}
|
|
26
|
-
function encodeOwnRecordKeys(value) {
|
|
27
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
const prototype = Object.getPrototypeOf(value);
|
|
31
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
32
|
-
return [];
|
|
33
|
-
const encoded = Object.create(null);
|
|
34
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
35
|
-
encoded[encodeRecordKey(key)] = entry;
|
|
36
|
-
}
|
|
37
|
-
return encoded;
|
|
38
|
-
}
|
|
39
68
|
function encodeRecordKey(value) {
|
|
40
69
|
let encoded = isWellFormedUnicode(value) ? "u" : "i";
|
|
41
70
|
for (let index = 0; index < value.length; index += 1) {
|
package/dist/profile-schema.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { agentCandidateSecretRefSchema } from "./agent-candidate-schema-common.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { CONTRACT_MAX_ARRAY_LENGTH, isBoundedJsonMaterial } from "./contract-limits.js";
|
|
3
4
|
import { ownPropertyRecordSchema } from "./own-property-record.js";
|
|
4
5
|
import { ProxySecretsSchema } from "./proxy-secrets.js";
|
|
5
6
|
import { agentProfileKnowledgeRefsSchema } from "./profile-knowledge.js";
|
|
@@ -7,9 +8,24 @@ export { agentProfileKnowledgeRefSchema, agentProfileKnowledgeRefsSchema, } from
|
|
|
7
8
|
import { REASONING_EFFORTS, TRAINED_MODEL_PREFIX, trainedModelIdForArtifact, } from "./agent-profile.js";
|
|
8
9
|
import { canonicalCandidateDigest, sha256DigestSchema, environmentNameSchema, headerNameSchema, isSafeExecutable, isSafeRelativePath, isWellFormedUnicode, looksLikeCredential, } from "./agent-candidate-schema-common.js";
|
|
9
10
|
import { harnessTypeSchema } from "./harness.js";
|
|
10
|
-
import { canonicalAgentProfileJson } from "./agent-profile-canonical.js";
|
|
11
|
+
import { AgentProfileValueError, assertAgentProfileValue, canonicalAgentProfileJson } from "./agent-profile-canonical.js";
|
|
11
12
|
import { harnessHonorsEffort, harnessHonorsModel, harnessModelSupport, harnessReasoningEfforts, harnessSystemPromptIntents, resolveModelProvider, } from "./harness-capabilities.js";
|
|
12
13
|
import { SANDBOX_SIZE_PRESET_NAMES, } from "./sandbox-size.js";
|
|
14
|
+
const profileOpaqueValueSchema = z.unknown().transform((value, context) => {
|
|
15
|
+
if (value === undefined || isBoundedJsonMaterial(value, { dataPropertiesOnly: true }))
|
|
16
|
+
return structuredClone(value);
|
|
17
|
+
try {
|
|
18
|
+
assertAgentProfileValue(value, []);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (!(error instanceof AgentProfileValueError))
|
|
22
|
+
throw error;
|
|
23
|
+
context.addIssue({ code: "custom", path: [...error.path], message: error.message });
|
|
24
|
+
return z.NEVER;
|
|
25
|
+
}
|
|
26
|
+
context.addIssue({ code: "custom", message: "profile metadata exceeds contract bounds or is not portable JSON" });
|
|
27
|
+
return z.NEVER;
|
|
28
|
+
});
|
|
13
29
|
export const agentProfilePermissionValueSchema = z.enum([
|
|
14
30
|
"allow",
|
|
15
31
|
"deny",
|
|
@@ -41,11 +57,11 @@ export const agentProfileFileMountSchema = z.strictObject({
|
|
|
41
57
|
});
|
|
42
58
|
export const agentProfileResourcesSchema = z.strictObject({
|
|
43
59
|
knowledge: agentProfileKnowledgeRefsSchema.optional(),
|
|
44
|
-
files: z.array(agentProfileFileMountSchema).optional(),
|
|
45
|
-
tools: z.array(agentProfileResourceRefSchema).optional(),
|
|
46
|
-
skills: z.array(agentProfileResourceRefSchema).optional(),
|
|
47
|
-
agents: z.array(agentProfileResourceRefSchema).optional(),
|
|
48
|
-
commands: z.array(agentProfileResourceRefSchema).optional(),
|
|
60
|
+
files: z.array(agentProfileFileMountSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
61
|
+
tools: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
62
|
+
skills: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
63
|
+
agents: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
64
|
+
commands: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
49
65
|
instructions: z
|
|
50
66
|
.union([z.string(), agentProfileResourceRefSchema])
|
|
51
67
|
.optional(),
|
|
@@ -88,7 +104,7 @@ export const agentProfileModelHintsBaseSchema = z.strictObject({
|
|
|
88
104
|
maxVisibleOutputTokens: z.number().int().positive().optional(),
|
|
89
105
|
maxReasoningTokens: z.number().int().positive().optional(),
|
|
90
106
|
maxTotalOutputTokens: z.number().int().positive().optional(),
|
|
91
|
-
metadata: ownPropertyRecordSchema(
|
|
107
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
92
108
|
});
|
|
93
109
|
export const agentProfileModelHintsSchema = agentProfileModelHintsBaseSchema.superRefine(enforceModelTokenBounds);
|
|
94
110
|
/** Ordered account selection policy, without credentials or ambient model defaults. */
|
|
@@ -113,7 +129,7 @@ export const agentProfileSeatSchema = z.strictObject({
|
|
|
113
129
|
export const agentProfilePromptSchema = z.strictObject({
|
|
114
130
|
systemPrompt: z.string().optional(),
|
|
115
131
|
appendSystemPrompt: z.string().optional(),
|
|
116
|
-
instructions: z.array(z.string()).optional(),
|
|
132
|
+
instructions: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
117
133
|
});
|
|
118
134
|
const controlCharacterPattern = /[\u0000-\u001f\u007f]/;
|
|
119
135
|
const secretCapableNamePattern = /(?:^|[_-])(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password|credentials?|authorization|cookie|database[_-]?url|dsn|pat)(?:[_-]|$)/i;
|
|
@@ -166,7 +182,7 @@ export const agentSubagentProfileSchema = z.strictObject({
|
|
|
166
182
|
tools: ownPropertyRecordSchema(z.boolean()).optional(),
|
|
167
183
|
permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
|
|
168
184
|
maxSteps: z.number().optional(),
|
|
169
|
-
metadata: ownPropertyRecordSchema(
|
|
185
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
170
186
|
});
|
|
171
187
|
export const agentProfileHookCommandSchema = z.strictObject({
|
|
172
188
|
command: z.string(),
|
|
@@ -181,7 +197,7 @@ export const agentProfileModeSchema = z.strictObject({
|
|
|
181
197
|
prompt: z.string().optional(),
|
|
182
198
|
tools: ownPropertyRecordSchema(z.boolean()).optional(),
|
|
183
199
|
permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
|
|
184
|
-
metadata: ownPropertyRecordSchema(
|
|
200
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
185
201
|
});
|
|
186
202
|
export const agentProfileConfidentialSchema = z.strictObject({
|
|
187
203
|
tee: z.string().optional(),
|
|
@@ -204,7 +220,7 @@ const remoteMcpUrlSchema = nonBlankMcpValueSchema.refine((value) => {
|
|
|
204
220
|
const agentProfileLocalMcpServerSchema = z.strictObject({
|
|
205
221
|
transport: z.literal("stdio").optional(),
|
|
206
222
|
command: nonBlankMcpValueSchema.refine(isSafeExecutable, "local MCP command must be a canonical public executable"),
|
|
207
|
-
args: z.array(agentProfileConfigValueSchema).optional(),
|
|
223
|
+
args: z.array(agentProfileConfigValueSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
208
224
|
env: agentProfileEnvironmentSchema.optional(),
|
|
209
225
|
cwd: z
|
|
210
226
|
.string()
|
|
@@ -213,7 +229,7 @@ const agentProfileLocalMcpServerSchema = z.strictObject({
|
|
|
213
229
|
url: z.undefined().optional(),
|
|
214
230
|
headers: z.undefined().optional(),
|
|
215
231
|
enabled: z.literal(true).optional(),
|
|
216
|
-
metadata: ownPropertyRecordSchema(
|
|
232
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
217
233
|
});
|
|
218
234
|
const agentProfileRemoteMcpServerSchema = z.strictObject({
|
|
219
235
|
transport: z.enum(["sse", "http"]).optional(),
|
|
@@ -224,7 +240,7 @@ const agentProfileRemoteMcpServerSchema = z.strictObject({
|
|
|
224
240
|
url: remoteMcpUrlSchema,
|
|
225
241
|
headers: agentProfileHeaderSchema.optional(),
|
|
226
242
|
enabled: z.literal(true).optional(),
|
|
227
|
-
metadata: ownPropertyRecordSchema(
|
|
243
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
228
244
|
});
|
|
229
245
|
const agentProfileDisabledMcpServerSchema = z.strictObject({
|
|
230
246
|
enabled: z.literal(false),
|
|
@@ -235,7 +251,7 @@ const agentProfileDisabledMcpServerSchema = z.strictObject({
|
|
|
235
251
|
cwd: z.undefined().optional(),
|
|
236
252
|
url: z.undefined().optional(),
|
|
237
253
|
headers: z.undefined().optional(),
|
|
238
|
-
metadata: ownPropertyRecordSchema(
|
|
254
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
239
255
|
});
|
|
240
256
|
export const agentProfileMcpServerSchema = z.union([
|
|
241
257
|
agentProfileLocalMcpServerSchema,
|
|
@@ -244,10 +260,10 @@ export const agentProfileMcpServerSchema = z.union([
|
|
|
244
260
|
]);
|
|
245
261
|
export const agentProfileConnectionSchema = z.strictObject({
|
|
246
262
|
connectionId: z.string().min(1),
|
|
247
|
-
capabilities: z.array(z.string().min(1)).min(1),
|
|
263
|
+
capabilities: z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH).min(1),
|
|
248
264
|
alias: z.string().min(1).optional(),
|
|
249
265
|
});
|
|
250
|
-
const removeListSchema = z.union([z.literal(true), z.array(z.string().min(1))]);
|
|
266
|
+
const removeListSchema = z.union([z.literal(true), z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH)]);
|
|
251
267
|
export const agentProfilePromptRemovalSchema = z.strictObject({
|
|
252
268
|
systemPrompt: z.literal(true).optional(),
|
|
253
269
|
appendSystemPrompt: z.literal(true).optional(),
|
|
@@ -311,10 +327,7 @@ export const agentTrainingDatasetIdentitySchema = z.strictObject({
|
|
|
311
327
|
context.addIssue({ code: "custom", path: ["taskSetDigest"], message: "training task inventory digest mismatch" });
|
|
312
328
|
}
|
|
313
329
|
});
|
|
314
|
-
export const agentTrainingParametersSchema = ownPropertyRecordSchema(z.union([z.string().max(2_048).pipe(publicProfileConfigStringSchema), z.number().finite(), z.boolean(), z.null()])).superRefine((parameters, context) => {
|
|
315
|
-
if (Object.keys(parameters).length > 100) {
|
|
316
|
-
context.addIssue({ code: "custom", message: "at most 100 public training parameters are allowed" });
|
|
317
|
-
}
|
|
330
|
+
export const agentTrainingParametersSchema = ownPropertyRecordSchema(z.union([z.string().max(2_048).pipe(publicProfileConfigStringSchema), z.number().finite(), z.boolean(), z.null()]), { maxEntries: 100 }).superRefine((parameters, context) => {
|
|
318
331
|
for (const name of Object.keys(parameters)) {
|
|
319
332
|
if (!trainingIdentitySchema.safeParse(name).success || isCredentialBearingProfileConfigName(name)) {
|
|
320
333
|
context.addIssue({ code: "custom", path: [name], message: "training parameter name must be public and non-credential" });
|
|
@@ -359,13 +372,25 @@ export const agentProfileTrainingSchema = z.strictObject({
|
|
|
359
372
|
}
|
|
360
373
|
}
|
|
361
374
|
});
|
|
362
|
-
export const agentProfileMetadataSchema = ownPropertyRecordSchema(z.unknown()
|
|
375
|
+
export const agentProfileMetadataSchema = ownPropertyRecordSchema(z.unknown(), {
|
|
376
|
+
prepareValue: (key, value) => {
|
|
377
|
+
if (key === "training")
|
|
378
|
+
return value;
|
|
379
|
+
if (value === undefined || isBoundedJsonMaterial(value, { dataPropertiesOnly: true }))
|
|
380
|
+
return structuredClone(value);
|
|
381
|
+
assertAgentProfileValue(value, [key]);
|
|
382
|
+
return z.NEVER;
|
|
383
|
+
},
|
|
384
|
+
}).superRefine((metadata, context) => {
|
|
363
385
|
if (!Object.hasOwn(metadata, "training"))
|
|
364
386
|
return;
|
|
365
387
|
const result = agentProfileTrainingSchema.safeParse(metadata.training);
|
|
366
|
-
if (
|
|
388
|
+
if (result.success)
|
|
389
|
+
metadata.training = result.data;
|
|
390
|
+
else {
|
|
391
|
+
delete metadata.training;
|
|
367
392
|
for (const issue of result.error.issues) {
|
|
368
|
-
context.addIssue({ code: "custom", path: ["training", ...issue.path], message: issue.message });
|
|
393
|
+
context.addIssue({ code: "custom", path: ["training", ...issue.path], message: issue.message, continue: false });
|
|
369
394
|
}
|
|
370
395
|
}
|
|
371
396
|
});
|
|
@@ -379,23 +404,23 @@ export const agentProfileSchema = z
|
|
|
379
404
|
name: z.string().optional(),
|
|
380
405
|
description: z.string().optional(),
|
|
381
406
|
version: z.string().optional(),
|
|
382
|
-
tags: z.array(z.string()).optional(),
|
|
407
|
+
tags: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
383
408
|
prompt: agentProfilePromptSchema.optional(),
|
|
384
409
|
model: agentProfileModelHintsSchema.optional(),
|
|
385
410
|
harness: harnessTypeSchema.optional(),
|
|
386
|
-
seats: z.array(agentProfileSeatSchema).nonempty().optional(),
|
|
411
|
+
seats: z.array(agentProfileSeatSchema).max(CONTRACT_MAX_ARRAY_LENGTH).nonempty().optional(),
|
|
387
412
|
proxySecrets: ProxySecretsSchema.optional(),
|
|
388
413
|
permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
|
|
389
414
|
tools: ownPropertyRecordSchema(z.boolean()).optional(),
|
|
390
415
|
mcp: ownPropertyRecordSchema(agentProfileMcpServerSchema).optional(),
|
|
391
|
-
connections: z.array(agentProfileConnectionSchema).optional(),
|
|
416
|
+
connections: z.array(agentProfileConnectionSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
392
417
|
subagents: ownPropertyRecordSchema(agentSubagentProfileSchema).optional(),
|
|
393
418
|
resources: agentProfileResourcesSchema.optional(),
|
|
394
|
-
hooks: ownPropertyRecordSchema(z.array(agentProfileHookCommandSchema)).optional(),
|
|
419
|
+
hooks: ownPropertyRecordSchema(z.array(agentProfileHookCommandSchema).max(CONTRACT_MAX_ARRAY_LENGTH)).optional(),
|
|
395
420
|
modes: ownPropertyRecordSchema(agentProfileModeSchema).optional(),
|
|
396
421
|
confidential: agentProfileConfidentialSchema.optional(),
|
|
397
422
|
metadata: agentProfileMetadataSchema.optional(),
|
|
398
|
-
extensions: ownPropertyRecordSchema(z.union([ownPropertyRecordSchema(
|
|
423
|
+
extensions: ownPropertyRecordSchema(z.union([ownPropertyRecordSchema(profileOpaqueValueSchema), z.undefined()])).optional(),
|
|
399
424
|
})
|
|
400
425
|
.superRefine((profile, context) => {
|
|
401
426
|
validateNestedRecordKeys(profile, context, [], new Set());
|
|
@@ -533,13 +558,13 @@ export const agentProfileDiffSchema = z.strictObject({
|
|
|
533
558
|
"optimizer",
|
|
534
559
|
"compound",
|
|
535
560
|
]),
|
|
536
|
-
artifacts: z.array(z.string().min(1)).optional(),
|
|
537
|
-
notes: z.array(z.string()).optional(),
|
|
561
|
+
artifacts: z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
562
|
+
notes: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
|
|
538
563
|
})
|
|
539
564
|
.optional(),
|
|
540
565
|
set: agentProfileSchema.optional(),
|
|
541
566
|
remove: agentProfileDiffRemovalSchema.optional(),
|
|
542
|
-
metadata: ownPropertyRecordSchema(
|
|
567
|
+
metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
|
|
543
568
|
});
|
|
544
569
|
const _agentProfileSchemaMatchesInterface = true;
|
|
545
570
|
void _agentProfileSchemaMatchesInterface;
|
package/dist/proxy-secrets.js
CHANGED
|
@@ -57,9 +57,8 @@ export const ProxySecretsSchema = z.array(ProxySecretBindingSchema).max(CONTRACT
|
|
|
57
57
|
* Strings select an API-verified parent sandbox; null explicitly selects independent issuance.
|
|
58
58
|
* When present, every destination in the final effective selection must have exactly one key.
|
|
59
59
|
*/
|
|
60
|
-
export const ProxySecretParentsSchema = ownPropertyRecordSchema(boundedIdentifierSchema.nullable())
|
|
61
|
-
.refine((parents) => Object.keys(parents).
|
|
62
|
-
Object.keys(parents).every((name) => envNameSchema.safeParse(name).success), "invalid parent selection keys");
|
|
60
|
+
export const ProxySecretParentsSchema = ownPropertyRecordSchema(boundedIdentifierSchema.nullable(), { maxEntries: CONTRACT_MAX_ARRAY_LENGTH })
|
|
61
|
+
.refine((parents) => Object.keys(parents).every((name) => envNameSchema.safeParse(name).success), "invalid parent selection keys");
|
|
63
62
|
/** Shape and totality only; the API verifies issuer authority and durable immutable pins. */
|
|
64
63
|
export function isProxySecretParentsTotal(bindings, parents) {
|
|
65
64
|
const selection = ProxySecretsSchema.safeParse(bindings);
|