@agent-surface/compiler 0.18.0 → 0.19.1
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/chunk-BNIUE5RA.js +746 -0
- package/dist/chunk-BNIUE5RA.js.map +1 -0
- package/dist/compile-RPDITMVH.js +7 -0
- package/dist/compile-RPDITMVH.js.map +1 -0
- package/dist/index.js +12 -664
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
// src/compile.ts
|
|
2
|
+
import { resolve as resolve3 } from "path";
|
|
3
|
+
import { build } from "vite";
|
|
4
|
+
|
|
5
|
+
// src/plugin.ts
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
|
+
import { dirname, isAbsolute, join, relative as relative2, resolve as resolve2, sep } from "path";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_LIMITS,
|
|
10
|
+
parseCapabilityId,
|
|
11
|
+
validateJsonSchemaDocument
|
|
12
|
+
} from "@agent-surface/core";
|
|
13
|
+
|
|
14
|
+
// src/canonical.ts
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
function normalize(value) {
|
|
17
|
+
if (Array.isArray(value)) return value.map(normalize);
|
|
18
|
+
if (typeof value !== "object" || value === null) return value;
|
|
19
|
+
return Object.fromEntries(
|
|
20
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, normalize(entry)])
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function canonicalJson(value, pretty = false) {
|
|
24
|
+
return `${JSON.stringify(normalize(value), null, pretty ? 2 : void 0)}
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
function sha256(value) {
|
|
28
|
+
return createHash("sha256").update(value).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
function manifestPayload(manifest) {
|
|
31
|
+
const { hash: _hash, ...payload } = manifest;
|
|
32
|
+
return payload;
|
|
33
|
+
}
|
|
34
|
+
function computeManifestHash(manifest) {
|
|
35
|
+
return sha256(canonicalJson(manifestPayload(manifest)));
|
|
36
|
+
}
|
|
37
|
+
function canonicalManifestJson(manifest) {
|
|
38
|
+
return canonicalJson(manifest, true);
|
|
39
|
+
}
|
|
40
|
+
function verifyManifest(manifest) {
|
|
41
|
+
if (computeManifestHash(manifest) !== manifest.hash) {
|
|
42
|
+
throw new Error(`capability contract hash mismatch: ${manifest.hash}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/extract.ts
|
|
47
|
+
import { posix, relative, resolve } from "path";
|
|
48
|
+
import ts from "typescript";
|
|
49
|
+
var CORE = "@agent-surface/core";
|
|
50
|
+
var COMPONENT_MACRO = "defineAgentComponentContract";
|
|
51
|
+
var PROCEDURE_MACRO = "defineAgentProcedureContract";
|
|
52
|
+
var EXTERNAL_MACRO = "defineExternalAgentToolContract";
|
|
53
|
+
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
54
|
+
"observationContract",
|
|
55
|
+
"actionContract",
|
|
56
|
+
"fromJsonSchema",
|
|
57
|
+
"defineAgentComponentContract",
|
|
58
|
+
"defineAgentProcedureContract",
|
|
59
|
+
"defineExternalAgentToolContract"
|
|
60
|
+
]);
|
|
61
|
+
function sourceKind(id) {
|
|
62
|
+
if (/\.tsx(?:\?|$)/.test(id)) return ts.ScriptKind.TSX;
|
|
63
|
+
if (/\.jsx(?:\?|$)/.test(id)) return ts.ScriptKind.JSX;
|
|
64
|
+
if (/\.js(?:\?|$)/.test(id)) return ts.ScriptKind.JS;
|
|
65
|
+
return ts.ScriptKind.TS;
|
|
66
|
+
}
|
|
67
|
+
function unwrap(node) {
|
|
68
|
+
while (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node)) {
|
|
69
|
+
node = node.expression;
|
|
70
|
+
}
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
function propertyName(node) {
|
|
74
|
+
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
|
75
|
+
return node.text;
|
|
76
|
+
}
|
|
77
|
+
if (ts.isComputedPropertyName(node) && ts.isStringLiteral(unwrap(node.expression))) {
|
|
78
|
+
return unwrap(node.expression).text;
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
function calleeName(node) {
|
|
83
|
+
node = unwrap(node);
|
|
84
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
85
|
+
if (ts.isPropertyAccessExpression(node)) return node.name.text;
|
|
86
|
+
if (ts.isElementAccessExpression(node) && node.argumentExpression && ts.isStringLiteral(unwrap(node.argumentExpression))) {
|
|
87
|
+
return unwrap(node.argumentExpression).text;
|
|
88
|
+
}
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
function normalizedOrigin(root, id) {
|
|
92
|
+
const clean = id.replace(/^\0/, "virtual:").split("?")[0];
|
|
93
|
+
if (clean.startsWith("virtual:")) return clean;
|
|
94
|
+
const path = clean.startsWith("/@fs/") ? clean.slice(4) : clean;
|
|
95
|
+
return posix.normalize(relative(root, resolve(path)).split("\\").join("/"));
|
|
96
|
+
}
|
|
97
|
+
var StaticEvaluator = class {
|
|
98
|
+
constructor(file) {
|
|
99
|
+
this.file = file;
|
|
100
|
+
const visit = (node) => {
|
|
101
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
102
|
+
this.constants.set(node.name.text, node.initializer);
|
|
103
|
+
}
|
|
104
|
+
ts.forEachChild(node, visit);
|
|
105
|
+
};
|
|
106
|
+
visit(file);
|
|
107
|
+
}
|
|
108
|
+
file;
|
|
109
|
+
constants = /* @__PURE__ */ new Map();
|
|
110
|
+
active = /* @__PURE__ */ new Set();
|
|
111
|
+
evaluate(input, where) {
|
|
112
|
+
const node = unwrap(input);
|
|
113
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
114
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
115
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
116
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
117
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
118
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken) {
|
|
119
|
+
const value = this.evaluate(node.operand, where);
|
|
120
|
+
if (typeof value === "number") return -value;
|
|
121
|
+
}
|
|
122
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
123
|
+
return node.elements.map((entry, index) => {
|
|
124
|
+
if (ts.isSpreadElement(entry)) {
|
|
125
|
+
const spread = this.evaluate(entry.expression, `${where}[${index}]`);
|
|
126
|
+
if (!Array.isArray(spread)) throw this.failure(entry, `${where}: array spread is not static`);
|
|
127
|
+
return spread;
|
|
128
|
+
}
|
|
129
|
+
return this.evaluate(entry, `${where}[${index}]`);
|
|
130
|
+
}).flat();
|
|
131
|
+
}
|
|
132
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const property of node.properties) {
|
|
135
|
+
if (ts.isSpreadAssignment(property)) {
|
|
136
|
+
const spread = this.evaluate(property.expression, `${where} spread`);
|
|
137
|
+
if (typeof spread !== "object" || spread === null || Array.isArray(spread)) {
|
|
138
|
+
throw this.failure(property, `${where}: object spread is not static`);
|
|
139
|
+
}
|
|
140
|
+
Object.assign(result, spread);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
144
|
+
result[property.name.text] = this.evaluate(property.name, `${where}.${property.name.text}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
148
|
+
throw this.failure(property, `${where}: methods/accessors are runtime code`);
|
|
149
|
+
}
|
|
150
|
+
const name = propertyName(property.name);
|
|
151
|
+
if (name === void 0) throw this.failure(property.name, `${where}: computed key is not static`);
|
|
152
|
+
result[name] = this.evaluate(property.initializer, `${where}.${name}`);
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
if (ts.isIdentifier(node)) {
|
|
157
|
+
if (node.text === "undefined") throw this.failure(node, `${where}: undefined is not serializable`);
|
|
158
|
+
if (node.text === "emptyObjectSchema") {
|
|
159
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
160
|
+
}
|
|
161
|
+
const value = this.constants.get(node.text);
|
|
162
|
+
if (!value) throw this.failure(node, `${where}: imported/dynamic value "${node.text}" is not static`);
|
|
163
|
+
if (this.active.has(node.text)) throw this.failure(node, `${where}: cyclic static value "${node.text}"`);
|
|
164
|
+
this.active.add(node.text);
|
|
165
|
+
try {
|
|
166
|
+
return this.evaluate(value, where);
|
|
167
|
+
} finally {
|
|
168
|
+
this.active.delete(node.text);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (ts.isCallExpression(node)) {
|
|
172
|
+
const name = calleeName(node.expression);
|
|
173
|
+
if (name && WRAPPERS.has(name) && node.arguments[0]) {
|
|
174
|
+
return this.evaluate(node.arguments[0], where);
|
|
175
|
+
}
|
|
176
|
+
throw this.failure(node, `${where}: call "${name ?? "<computed>"}" is not a compiler macro`);
|
|
177
|
+
}
|
|
178
|
+
if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
|
|
179
|
+
const owner = this.evaluate(node.expression, where);
|
|
180
|
+
const name = ts.isPropertyAccessExpression(node) ? node.name.text : node.argumentExpression && ts.isStringLiteral(unwrap(node.argumentExpression)) ? unwrap(node.argumentExpression).text : void 0;
|
|
181
|
+
if (name !== void 0 && typeof owner === "object" && owner !== null && !Array.isArray(owner)) {
|
|
182
|
+
const value = owner[name];
|
|
183
|
+
if (value !== void 0) return value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
throw this.failure(node, `${where}: expression is dynamic or non-serializable`);
|
|
187
|
+
}
|
|
188
|
+
failure(node, message) {
|
|
189
|
+
const position = this.file.getLineAndCharacterOfPosition(node.getStart(this.file));
|
|
190
|
+
return new Error(`${this.file.fileName}:${position.line + 1}:${position.character + 1}: ${message}`);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function record(value, where) {
|
|
194
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
195
|
+
throw new Error(`${where} must be a static object`);
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
function string(value, where) {
|
|
200
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${where} must be a string`);
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function policies(value) {
|
|
204
|
+
if (value === void 0) return void 0;
|
|
205
|
+
if (!Array.isArray(value)) throw new Error("policies must be a static array");
|
|
206
|
+
return value.map((entry) => record(entry, "policy"));
|
|
207
|
+
}
|
|
208
|
+
function combine(inherited, local) {
|
|
209
|
+
const values = [...inherited ?? [], ...local ?? []];
|
|
210
|
+
if (values.length === 0) return void 0;
|
|
211
|
+
const unique = new Map(values.map((value) => [canonicalJson(value), value]));
|
|
212
|
+
return [...unique.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, value]) => value);
|
|
213
|
+
}
|
|
214
|
+
function entryHash(entry) {
|
|
215
|
+
return sha256(canonicalJson(entry));
|
|
216
|
+
}
|
|
217
|
+
function declarationVariable(call) {
|
|
218
|
+
let node = call;
|
|
219
|
+
while (node.parent && (ts.isParenthesizedExpression(node.parent) || ts.isAsExpression(node.parent) || ts.isSatisfiesExpression(node.parent))) {
|
|
220
|
+
node = node.parent;
|
|
221
|
+
}
|
|
222
|
+
return node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name) ? node.parent.name.text : void 0;
|
|
223
|
+
}
|
|
224
|
+
function isMacroCall(call, names, namespaces, aliases) {
|
|
225
|
+
const expression = unwrap(call.expression);
|
|
226
|
+
let imported;
|
|
227
|
+
if (ts.isIdentifier(expression)) imported = aliases.get(expression.text);
|
|
228
|
+
else if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
229
|
+
if (namespaces.has(expression.expression.text)) imported = expression.name.text;
|
|
230
|
+
} else if (ts.isElementAccessExpression(expression) && ts.isIdentifier(expression.expression) && namespaces.has(expression.expression.text) && expression.argumentExpression && ts.isStringLiteral(unwrap(expression.argumentExpression))) {
|
|
231
|
+
imported = unwrap(expression.argumentExpression).text;
|
|
232
|
+
}
|
|
233
|
+
if (imported === COMPONENT_MACRO) return "component";
|
|
234
|
+
if (imported === PROCEDURE_MACRO) return "procedure";
|
|
235
|
+
if (imported === EXTERNAL_MACRO) return "external";
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
function extractModule(options) {
|
|
239
|
+
const file = ts.createSourceFile(options.id, options.code, ts.ScriptTarget.Latest, true, sourceKind(options.id));
|
|
240
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
241
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const statement of file.statements) {
|
|
243
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
244
|
+
if (statement.moduleSpecifier.text !== CORE || !statement.importClause) continue;
|
|
245
|
+
const bindings = statement.importClause.namedBindings;
|
|
246
|
+
if (bindings && ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text);
|
|
247
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
248
|
+
for (const binding of bindings.elements) {
|
|
249
|
+
aliases.set(binding.name.text, binding.propertyName?.text ?? binding.name.text);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
let changed = true;
|
|
254
|
+
while (changed) {
|
|
255
|
+
changed = false;
|
|
256
|
+
const visitAlias = (node) => {
|
|
257
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
258
|
+
const init = unwrap(node.initializer);
|
|
259
|
+
let source;
|
|
260
|
+
if (ts.isIdentifier(init)) source = aliases.get(init.text);
|
|
261
|
+
else if (ts.isPropertyAccessExpression(init) && ts.isIdentifier(init.expression) && namespaces.has(init.expression.text)) {
|
|
262
|
+
source = init.name.text;
|
|
263
|
+
}
|
|
264
|
+
if (source && !aliases.has(node.name.text)) {
|
|
265
|
+
aliases.set(node.name.text, source);
|
|
266
|
+
changed = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
ts.forEachChild(node, visitAlias);
|
|
270
|
+
};
|
|
271
|
+
visitAlias(file);
|
|
272
|
+
}
|
|
273
|
+
const macroNames = /* @__PURE__ */ new Set([COMPONENT_MACRO, PROCEDURE_MACRO, EXTERNAL_MACRO]);
|
|
274
|
+
if (![...aliases.values()].some((name) => macroNames.has(name)) && namespaces.size === 0) {
|
|
275
|
+
return { code: options.code, entries: [] };
|
|
276
|
+
}
|
|
277
|
+
const evaluator = new StaticEvaluator(file);
|
|
278
|
+
const entries = [];
|
|
279
|
+
const injections = [];
|
|
280
|
+
const origin = normalizedOrigin(options.root, options.id);
|
|
281
|
+
const visit = (node) => {
|
|
282
|
+
if (ts.isCallExpression(node)) {
|
|
283
|
+
const macro = isMacroCall(node, macroNames, namespaces, aliases);
|
|
284
|
+
if (macro) {
|
|
285
|
+
if (!node.arguments[0]) throw new Error(`${origin}: contract declaration needs an argument`);
|
|
286
|
+
const variable = declarationVariable(node);
|
|
287
|
+
if (!variable) throw new Error(`${origin}: contract declaration must initialize a named module constant`);
|
|
288
|
+
const declarationId = `${origin}#${variable}`;
|
|
289
|
+
const definition = record(evaluator.evaluate(node.arguments[0], declarationId), declarationId);
|
|
290
|
+
const emitted = [];
|
|
291
|
+
if (macro === "component") {
|
|
292
|
+
const type = string(definition.type, `${declarationId}.type`);
|
|
293
|
+
const inheritedPolicies = policies(definition.policies);
|
|
294
|
+
const inheritedTags = definition.tags;
|
|
295
|
+
const groups = [
|
|
296
|
+
["observations", "observation", "read"],
|
|
297
|
+
["actions", "action", void 0]
|
|
298
|
+
];
|
|
299
|
+
for (const [groupName, kind, defaultEffect] of groups) {
|
|
300
|
+
const groupValue = definition[groupName];
|
|
301
|
+
if (groupValue === void 0) continue;
|
|
302
|
+
const group = record(groupValue, `${declarationId}.${groupName}`);
|
|
303
|
+
for (const [name, rawMember] of Object.entries(group)) {
|
|
304
|
+
const member = record(rawMember, `${declarationId}.${groupName}.${name}`);
|
|
305
|
+
const capabilityPolicies = combine(inheritedPolicies, policies(member.policies));
|
|
306
|
+
const capabilityTags = combine(inheritedTags, member.tags);
|
|
307
|
+
const base = {
|
|
308
|
+
declarationId,
|
|
309
|
+
capabilityId: `view:${type}.${name}`,
|
|
310
|
+
kind,
|
|
311
|
+
description: string(member.description, `${declarationId}.${name}.description`),
|
|
312
|
+
effect: string(member.effect ?? defaultEffect, `${declarationId}.${name}.effect`),
|
|
313
|
+
...member.input ? { inputSchema: member.input } : {},
|
|
314
|
+
...member.output ? { outputSchema: member.output } : {},
|
|
315
|
+
...member.confirmation ? { confirmation: member.confirmation } : {},
|
|
316
|
+
...capabilityPolicies ? { policies: capabilityPolicies } : {},
|
|
317
|
+
...capabilityTags ? { tags: capabilityTags } : {},
|
|
318
|
+
origin
|
|
319
|
+
};
|
|
320
|
+
emitted.push({ ...base, contractHash: entryHash(base), targets: [options.target] });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
const id = string(definition.id, `${declarationId}.id`);
|
|
325
|
+
const base = {
|
|
326
|
+
declarationId,
|
|
327
|
+
capabilityId: id,
|
|
328
|
+
kind: macro === "procedure" ? "procedure" : "external",
|
|
329
|
+
description: string(definition.description, `${declarationId}.description`),
|
|
330
|
+
effect: string(definition.effect, `${declarationId}.effect`),
|
|
331
|
+
...definition.input ? { inputSchema: definition.input } : {},
|
|
332
|
+
...definition.output ? { outputSchema: definition.output } : {},
|
|
333
|
+
...definition.confirmation ? { confirmation: definition.confirmation } : {},
|
|
334
|
+
...policies(definition.policies) ? { policies: policies(definition.policies) } : {},
|
|
335
|
+
...definition.tags ? { tags: definition.tags } : {},
|
|
336
|
+
origin
|
|
337
|
+
};
|
|
338
|
+
emitted.push({ ...base, contractHash: entryHash(base), targets: [options.target] });
|
|
339
|
+
}
|
|
340
|
+
entries.push(...emitted);
|
|
341
|
+
if (node.arguments.length < 2) {
|
|
342
|
+
if (macro === "component") {
|
|
343
|
+
const capabilities = {};
|
|
344
|
+
for (const entry of emitted) {
|
|
345
|
+
capabilities[entry.capabilityId] = {
|
|
346
|
+
manifestHash: options.placeholder,
|
|
347
|
+
declarationId: entry.declarationId,
|
|
348
|
+
capabilityId: entry.capabilityId,
|
|
349
|
+
contractHash: entry.contractHash
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const provenance = {
|
|
353
|
+
manifestHash: options.placeholder,
|
|
354
|
+
declarationId,
|
|
355
|
+
capabilities
|
|
356
|
+
};
|
|
357
|
+
injections.push({ position: node.arguments.end, text: `, ${JSON.stringify(provenance)}` });
|
|
358
|
+
} else {
|
|
359
|
+
const entry = emitted[0];
|
|
360
|
+
const token = {
|
|
361
|
+
manifestHash: options.placeholder,
|
|
362
|
+
declarationId,
|
|
363
|
+
capabilityId: entry.capabilityId,
|
|
364
|
+
contractHash: entry.contractHash
|
|
365
|
+
};
|
|
366
|
+
injections.push({ position: node.arguments.end, text: `, ${JSON.stringify(token)}` });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
ts.forEachChild(node, visit);
|
|
372
|
+
};
|
|
373
|
+
visit(file);
|
|
374
|
+
let code = options.code;
|
|
375
|
+
for (const injection of injections.sort((a, b) => b.position - a.position)) {
|
|
376
|
+
code = `${code.slice(0, injection.position)}${injection.text}${code.slice(injection.position)}`;
|
|
377
|
+
}
|
|
378
|
+
return { code, entries };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/plugin.ts
|
|
382
|
+
var COMPILER_VERSION = "0.17.0";
|
|
383
|
+
var CONTRACT_FILE = "agent-surface.contract.json";
|
|
384
|
+
var VIRTUAL_CONTRACT_ID = "virtual:agent-surface-contract";
|
|
385
|
+
var RESOLVED_VIRTUAL_CONTRACT_ID = `\0${VIRTUAL_CONTRACT_ID}`;
|
|
386
|
+
function mergeEntries(entries) {
|
|
387
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
const key = `${entry.declarationId}\0${entry.capabilityId}`;
|
|
390
|
+
const current = byKey.get(key);
|
|
391
|
+
if (!current) {
|
|
392
|
+
byKey.set(key, { ...entry, targets: [...entry.targets] });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (current.contractHash !== entry.contractHash) {
|
|
396
|
+
throw new Error(`contract "${entry.declarationId}" / "${entry.capabilityId}" differs across build targets`);
|
|
397
|
+
}
|
|
398
|
+
current.targets = [.../* @__PURE__ */ new Set([...current.targets, ...entry.targets])].sort();
|
|
399
|
+
}
|
|
400
|
+
return [...byKey.values()].sort(
|
|
401
|
+
(a, b) => `${a.declarationId}\0${a.capabilityId}`.localeCompare(`${b.declarationId}\0${b.capabilityId}`)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
function validateEntries(entries) {
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
if (entry.kind !== "external" && !parseCapabilityId(entry.capabilityId)) {
|
|
407
|
+
throw new Error(`invalid compiled capability id "${entry.capabilityId}"`);
|
|
408
|
+
}
|
|
409
|
+
for (const [label, schema] of [["input", entry.inputSchema], ["output", entry.outputSchema]]) {
|
|
410
|
+
if (!schema) continue;
|
|
411
|
+
const result = validateJsonSchemaDocument(schema, DEFAULT_LIMITS);
|
|
412
|
+
if (!result.ok) {
|
|
413
|
+
throw new Error(`${entry.declarationId} ${label} schema: ${result.reason}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function nearestPackage(id) {
|
|
419
|
+
if (id.startsWith("\0") || !isAbsolute(id)) return void 0;
|
|
420
|
+
let dir = dirname(id.split("?")[0]);
|
|
421
|
+
while (dir !== dirname(dir)) {
|
|
422
|
+
const packagePath = join(dir, "package.json");
|
|
423
|
+
if (existsSync(packagePath)) {
|
|
424
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
425
|
+
if (!pkg.name) return void 0;
|
|
426
|
+
return {
|
|
427
|
+
name: pkg.name,
|
|
428
|
+
dir,
|
|
429
|
+
packagePath,
|
|
430
|
+
...pkg.agentSurface?.contract ? { sidecar: resolve2(dir, pkg.agentSurface.contract) } : {}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
dir = dirname(dir);
|
|
434
|
+
}
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
function isDependencyModule(id) {
|
|
438
|
+
return id.split("?")[0].includes(`${sep}node_modules${sep}`);
|
|
439
|
+
}
|
|
440
|
+
function readSidecar(path) {
|
|
441
|
+
const bytes = readFileSync(path);
|
|
442
|
+
const manifest = JSON.parse(bytes.toString("utf8"));
|
|
443
|
+
verifyManifest(manifest);
|
|
444
|
+
return { manifest, digest: sha256(bytes) };
|
|
445
|
+
}
|
|
446
|
+
function authorizeContributions(contributions, policy) {
|
|
447
|
+
const allow = new Map((policy?.allow ?? []).map((entry) => [entry.package, entry]));
|
|
448
|
+
const unauthorized = [];
|
|
449
|
+
const mismatched = [];
|
|
450
|
+
const attributions = [];
|
|
451
|
+
for (const contribution of contributions) {
|
|
452
|
+
const approved = allow.get(contribution.package);
|
|
453
|
+
if (!approved) {
|
|
454
|
+
unauthorized.push(
|
|
455
|
+
` ${contribution.package} (${contribution.route}: ${contribution.source})
|
|
456
|
+
{ "package": "${contribution.package}", "digest": "${contribution.contractDigest}" }`
|
|
457
|
+
);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (approved.digest !== contribution.contractDigest) {
|
|
461
|
+
mismatched.push(
|
|
462
|
+
` ${contribution.package}: approved ${approved.digest}, this build computed ${contribution.contractDigest}`
|
|
463
|
+
);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
attributions.push({
|
|
467
|
+
package: contribution.package,
|
|
468
|
+
source: contribution.source,
|
|
469
|
+
route: contribution.route,
|
|
470
|
+
contractDigest: contribution.contractDigest,
|
|
471
|
+
authorization: { mode: "pinned", expectedDigest: approved.digest }
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (unauthorized.length > 0) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`unauthorized external capability contract(s). A dependency cannot contribute capabilities until the consumer approves it. Add to externalContracts.allow:
|
|
477
|
+
${unauthorized.join("\n")}`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (mismatched.length > 0) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`external capability contract digest mismatch. The dependency changed what it contributes; review the change before updating the approved digest:
|
|
483
|
+
${mismatched.join("\n")}`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return attributions.sort((a, b) => a.package.localeCompare(b.package));
|
|
487
|
+
}
|
|
488
|
+
var serveManifests = /* @__PURE__ */ new Map();
|
|
489
|
+
function compileServeManifest(options) {
|
|
490
|
+
const key = `${options.root}\0${options.target}`;
|
|
491
|
+
const cached = serveManifests.get(key);
|
|
492
|
+
if (cached) return cached;
|
|
493
|
+
const pending = import("./compile-RPDITMVH.js").then(
|
|
494
|
+
({ compileCapabilityContract: compileCapabilityContract2 }) => compileCapabilityContract2({
|
|
495
|
+
root: options.root,
|
|
496
|
+
target: options.target,
|
|
497
|
+
...options.externalContracts ? { externalContracts: options.externalContracts } : {},
|
|
498
|
+
vite: {
|
|
499
|
+
...options.configFile === false ? { configFile: false } : {},
|
|
500
|
+
...options.alias ? { resolve: { alias: options.alias } } : {}
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
serveManifests.set(key, pending);
|
|
505
|
+
void pending.catch(() => serveManifests.delete(key));
|
|
506
|
+
return pending;
|
|
507
|
+
}
|
|
508
|
+
function agentSurface(options = {}) {
|
|
509
|
+
const target = options.target ?? "web-production";
|
|
510
|
+
const placeholder = `__AGENT_SURFACE_MANIFEST_HASH_${Math.random().toString(36).slice(2)}__`;
|
|
511
|
+
const manifestPlaceholder = `__AGENT_SURFACE_MANIFEST_${Math.random().toString(36).slice(2)}__`;
|
|
512
|
+
let root = process.cwd();
|
|
513
|
+
let entries = [];
|
|
514
|
+
let sourceEntries = /* @__PURE__ */ new Map();
|
|
515
|
+
let manifest;
|
|
516
|
+
let serve = false;
|
|
517
|
+
let serveAlias;
|
|
518
|
+
let serveConfigFile;
|
|
519
|
+
const servedManifest = () => {
|
|
520
|
+
if (!manifest) throw new Error("agent-surface: contract not compiled for this dev/test run");
|
|
521
|
+
return manifest;
|
|
522
|
+
};
|
|
523
|
+
const manifestHashToken = () => serve ? servedManifest().hash : placeholder;
|
|
524
|
+
return {
|
|
525
|
+
name: "agent-surface:compiler",
|
|
526
|
+
enforce: "pre",
|
|
527
|
+
configResolved(config) {
|
|
528
|
+
root = config.root;
|
|
529
|
+
serve = config.command === "serve";
|
|
530
|
+
serveAlias = config.resolve?.alias;
|
|
531
|
+
serveConfigFile = config.configFile === void 0 ? false : config.configFile;
|
|
532
|
+
},
|
|
533
|
+
resolveId(id) {
|
|
534
|
+
return id === VIRTUAL_CONTRACT_ID ? RESOLVED_VIRTUAL_CONTRACT_ID : null;
|
|
535
|
+
},
|
|
536
|
+
load(id) {
|
|
537
|
+
if (id !== RESOLVED_VIRTUAL_CONTRACT_ID) return null;
|
|
538
|
+
const inlined = serve ? JSON.stringify(servedManifest()) : manifestPlaceholder;
|
|
539
|
+
return `import { createCapabilityAuthority } from "@agent-surface/core";
|
|
540
|
+
const manifest = ${inlined};
|
|
541
|
+
export { manifest };
|
|
542
|
+
export default createCapabilityAuthority(manifest);`;
|
|
543
|
+
},
|
|
544
|
+
async buildStart() {
|
|
545
|
+
entries = [];
|
|
546
|
+
sourceEntries = /* @__PURE__ */ new Map();
|
|
547
|
+
manifest = void 0;
|
|
548
|
+
if (!serve) return;
|
|
549
|
+
manifest = await compileServeManifest({
|
|
550
|
+
root,
|
|
551
|
+
target,
|
|
552
|
+
alias: serveAlias,
|
|
553
|
+
configFile: serveConfigFile,
|
|
554
|
+
...options.externalContracts ? { externalContracts: options.externalContracts } : {}
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
transform(code, id) {
|
|
558
|
+
if (id.includes("/node_modules/@agent-surface/") || /\/packages\/(?:core|react|orpc|testing|webmcp|cli|compiler)\//.test(id) || // Vite's optimized-dependency chunks, which exist only under serve.
|
|
559
|
+
// They are bundled OUTPUT — several dependencies concatenated, this
|
|
560
|
+
// package among them — so the guards below would read our own
|
|
561
|
+
// `registry.register({` back out of a vendor chunk and refuse to serve
|
|
562
|
+
// the app. A dependency's real contracts are picked up from its actual
|
|
563
|
+
// module, via the sidecar and source routes, not from optimizer output.
|
|
564
|
+
id.includes("/node_modules/.vite/")) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
if (/\buseAgentComponent\s*\(\s*\{/.test(code)) {
|
|
568
|
+
throw new Error(`${id}: inline useAgentComponent registration is unsupported; bind a compiled contract`);
|
|
569
|
+
}
|
|
570
|
+
if (/\b(?:useAgentAction|useAgentObservation)\s*\(/.test(code)) {
|
|
571
|
+
throw new Error(`${id}: granular runtime capability construction is unsupported by the compiled contract`);
|
|
572
|
+
}
|
|
573
|
+
if (/\bdefineAgentComponent\s*\(/.test(code) || /\bregistry\s*\.\s*register\s*\(\s*\{/.test(code)) {
|
|
574
|
+
throw new Error(`${id}: raw capability registration bypasses the compiled exposure ceiling`);
|
|
575
|
+
}
|
|
576
|
+
if (!code.includes("defineAgentComponentContract") && !code.includes("defineAgentProcedureContract") && !code.includes("defineExternalAgentToolContract")) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
const extracted = extractModule({ code, id, root, target, placeholder: manifestHashToken() });
|
|
580
|
+
const owner = isDependencyModule(id) ? nearestPackage(id) : void 0;
|
|
581
|
+
if (owner) {
|
|
582
|
+
const existing = sourceEntries.get(owner.name) ?? [];
|
|
583
|
+
existing.push(...extracted.entries);
|
|
584
|
+
sourceEntries.set(owner.name, existing);
|
|
585
|
+
} else {
|
|
586
|
+
entries.push(...extracted.entries);
|
|
587
|
+
}
|
|
588
|
+
return extracted.code === code ? null : { code: extracted.code, map: null };
|
|
589
|
+
},
|
|
590
|
+
buildEnd(error) {
|
|
591
|
+
if (error || serve) return;
|
|
592
|
+
const contributions = [];
|
|
593
|
+
const seenSidecars = /* @__PURE__ */ new Set();
|
|
594
|
+
for (const id of this.getModuleIds()) {
|
|
595
|
+
if (!isDependencyModule(id)) continue;
|
|
596
|
+
const owner = nearestPackage(id);
|
|
597
|
+
if (!owner?.sidecar || seenSidecars.has(owner.sidecar)) continue;
|
|
598
|
+
seenSidecars.add(owner.sidecar);
|
|
599
|
+
const { manifest: external, digest } = readSidecar(owner.sidecar);
|
|
600
|
+
contributions.push({
|
|
601
|
+
package: owner.name,
|
|
602
|
+
source: relative2(root, owner.sidecar).split(sep).join("/"),
|
|
603
|
+
route: "sidecar",
|
|
604
|
+
contractDigest: digest,
|
|
605
|
+
entries: external.capabilities
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
for (const allowed of options.externalContracts?.allow ?? []) {
|
|
609
|
+
if (!allowed.path) continue;
|
|
610
|
+
const path = isAbsolute(allowed.path) ? allowed.path : resolve2(root, allowed.path);
|
|
611
|
+
if (seenSidecars.has(path)) continue;
|
|
612
|
+
seenSidecars.add(path);
|
|
613
|
+
const owner = nearestPackage(path);
|
|
614
|
+
if (owner && owner.name !== allowed.package) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
`external contract "${allowed.path}" belongs to "${owner.name}", not the approved "${allowed.package}"`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const { manifest: external, digest } = readSidecar(path);
|
|
620
|
+
contributions.push({
|
|
621
|
+
package: owner?.name ?? allowed.package,
|
|
622
|
+
source: allowed.path,
|
|
623
|
+
route: "sidecar",
|
|
624
|
+
contractDigest: digest,
|
|
625
|
+
entries: external.capabilities
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
for (const [name, extracted] of [...sourceEntries].sort(([a], [b]) => a.localeCompare(b))) {
|
|
629
|
+
if (extracted.length === 0) continue;
|
|
630
|
+
const sorted = mergeEntries(extracted);
|
|
631
|
+
contributions.push({
|
|
632
|
+
package: name,
|
|
633
|
+
source: sorted[0]?.origin ?? name,
|
|
634
|
+
route: "source",
|
|
635
|
+
contractDigest: sha256(canonicalJson(sorted)),
|
|
636
|
+
entries: sorted
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
const externalContracts = authorizeContributions(contributions, options.externalContracts);
|
|
640
|
+
const externalEntries = contributions.flatMap((contribution) => contribution.entries);
|
|
641
|
+
const capabilities = mergeEntries([...entries, ...externalEntries]);
|
|
642
|
+
validateEntries(capabilities);
|
|
643
|
+
const targets = [...new Set(capabilities.flatMap((entry) => entry.targets))].sort();
|
|
644
|
+
const payload = {
|
|
645
|
+
formatVersion: 5,
|
|
646
|
+
compilerVersion: COMPILER_VERSION,
|
|
647
|
+
targets,
|
|
648
|
+
capabilities,
|
|
649
|
+
externalContracts,
|
|
650
|
+
completeness: { status: "proven" }
|
|
651
|
+
};
|
|
652
|
+
manifest = { ...payload, hash: computeManifestHash(payload) };
|
|
653
|
+
options.onManifest?.(manifest);
|
|
654
|
+
},
|
|
655
|
+
renderChunk(code) {
|
|
656
|
+
if (!manifest || !code.includes(placeholder) && !code.includes(manifestPlaceholder)) return null;
|
|
657
|
+
return {
|
|
658
|
+
code: code.replaceAll(placeholder, manifest.hash).replaceAll(manifestPlaceholder, JSON.stringify(manifest)),
|
|
659
|
+
map: null
|
|
660
|
+
};
|
|
661
|
+
},
|
|
662
|
+
generateBundle() {
|
|
663
|
+
if (!manifest || options.emit === false) return;
|
|
664
|
+
this.emitFile({
|
|
665
|
+
type: "asset",
|
|
666
|
+
fileName: options.fileName ?? CONTRACT_FILE,
|
|
667
|
+
source: canonicalManifestJson(manifest)
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
async handleHotUpdate(ctx) {
|
|
671
|
+
if (!serve) return;
|
|
672
|
+
const code = await ctx.read();
|
|
673
|
+
if (!code.includes("defineAgentComponentContract") && !code.includes("defineAgentProcedureContract") && !code.includes("defineExternalAgentToolContract")) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
serveManifests.delete(`${root}\0${target}`);
|
|
677
|
+
manifest = await compileServeManifest({
|
|
678
|
+
root,
|
|
679
|
+
target,
|
|
680
|
+
alias: serveAlias,
|
|
681
|
+
configFile: serveConfigFile,
|
|
682
|
+
...options.externalContracts ? { externalContracts: options.externalContracts } : {}
|
|
683
|
+
});
|
|
684
|
+
const virtual = ctx.server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CONTRACT_ID);
|
|
685
|
+
if (virtual) ctx.server.moduleGraph.invalidateModule(virtual);
|
|
686
|
+
ctx.server.ws.send({ type: "full-reload" });
|
|
687
|
+
return [];
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/compile.ts
|
|
693
|
+
async function compileCapabilityContract(options = {}) {
|
|
694
|
+
let manifest;
|
|
695
|
+
const root = resolve3(options.root ?? process.cwd());
|
|
696
|
+
const result = await build({
|
|
697
|
+
...options.vite ?? {},
|
|
698
|
+
root,
|
|
699
|
+
...options.configFile ? { configFile: resolve3(root, options.configFile) } : {},
|
|
700
|
+
logLevel: options.vite?.logLevel ?? "silent",
|
|
701
|
+
build: {
|
|
702
|
+
...options.vite?.build ?? {},
|
|
703
|
+
write: false
|
|
704
|
+
},
|
|
705
|
+
plugins: [
|
|
706
|
+
...options.vite?.plugins ?? [],
|
|
707
|
+
agentSurface({
|
|
708
|
+
target: options.target,
|
|
709
|
+
emit: false,
|
|
710
|
+
...options.externalContracts ? { externalContracts: options.externalContracts } : {},
|
|
711
|
+
onManifest: (value) => {
|
|
712
|
+
manifest = value;
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
]
|
|
716
|
+
});
|
|
717
|
+
const outputs = Array.isArray(result) ? result : [result];
|
|
718
|
+
for (const output of outputs) {
|
|
719
|
+
if (!("output" in output)) continue;
|
|
720
|
+
const asset = output.output.find(
|
|
721
|
+
(entry) => entry.type === "asset" && entry.fileName === "agent-surface.contract.json"
|
|
722
|
+
);
|
|
723
|
+
if (asset?.type === "asset") {
|
|
724
|
+
const source = typeof asset.source === "string" ? asset.source : new TextDecoder().decode(asset.source);
|
|
725
|
+
manifest = JSON.parse(source);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (!manifest) throw new Error("compiler did not produce a capability contract");
|
|
730
|
+
return manifest;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export {
|
|
734
|
+
canonicalJson,
|
|
735
|
+
sha256,
|
|
736
|
+
manifestPayload,
|
|
737
|
+
computeManifestHash,
|
|
738
|
+
canonicalManifestJson,
|
|
739
|
+
verifyManifest,
|
|
740
|
+
compileCapabilityContract,
|
|
741
|
+
COMPILER_VERSION,
|
|
742
|
+
CONTRACT_FILE,
|
|
743
|
+
VIRTUAL_CONTRACT_ID,
|
|
744
|
+
agentSurface
|
|
745
|
+
};
|
|
746
|
+
//# sourceMappingURL=chunk-BNIUE5RA.js.map
|