@agentcontract/core 0.1.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/LICENSE +112 -0
- package/README.md +106 -0
- package/dist/audit-DNON4W2Q.mjs +7 -0
- package/dist/audit-DNON4W2Q.mjs.map +1 -0
- package/dist/chunk-BVUHPJDU.mjs +50 -0
- package/dist/chunk-BVUHPJDU.mjs.map +1 -0
- package/dist/chunk-UHNX2RBZ.mjs +526 -0
- package/dist/chunk-UHNX2RBZ.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +577 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +105 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +259 -0
- package/dist/index.d.ts +259 -0
- package/dist/index.js +654 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +29 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
30
|
+
var init_cjs_shims = __esm({
|
|
31
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// src/audit.ts
|
|
37
|
+
var init_audit = __esm({
|
|
38
|
+
"src/audit.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
init_cjs_shims();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// src/cli.ts
|
|
45
|
+
init_cjs_shims();
|
|
46
|
+
var import_fs2 = require("fs");
|
|
47
|
+
var import_commander = require("commander");
|
|
48
|
+
|
|
49
|
+
// src/loader.ts
|
|
50
|
+
init_cjs_shims();
|
|
51
|
+
var import_fs = require("fs");
|
|
52
|
+
var import_path = require("path");
|
|
53
|
+
var import_js_yaml = __toESM(require("js-yaml"));
|
|
54
|
+
|
|
55
|
+
// src/models.ts
|
|
56
|
+
init_cjs_shims();
|
|
57
|
+
var import_zod = require("zod");
|
|
58
|
+
var JudgeType = import_zod.z.enum(["deterministic", "llm"]).default("deterministic");
|
|
59
|
+
var ViolationAction = import_zod.z.enum(["warn", "block", "rollback", "halt_and_alert"]).default("block");
|
|
60
|
+
var AssertionType = import_zod.z.enum(["pattern", "schema", "llm", "cost", "latency", "custom"]);
|
|
61
|
+
var ClauseObject = import_zod.z.object({
|
|
62
|
+
text: import_zod.z.string().min(1),
|
|
63
|
+
judge: JudgeType,
|
|
64
|
+
description: import_zod.z.string().default("")
|
|
65
|
+
});
|
|
66
|
+
var Clause = import_zod.z.union([import_zod.z.string().min(1), ClauseObject]);
|
|
67
|
+
var PreconditionClause = import_zod.z.union([
|
|
68
|
+
import_zod.z.string().min(1),
|
|
69
|
+
import_zod.z.object({
|
|
70
|
+
text: import_zod.z.string().min(1),
|
|
71
|
+
judge: JudgeType,
|
|
72
|
+
on_fail: import_zod.z.enum(["block", "warn"]).default("block"),
|
|
73
|
+
description: import_zod.z.string().default("")
|
|
74
|
+
})
|
|
75
|
+
]);
|
|
76
|
+
var Assertion = import_zod.z.object({
|
|
77
|
+
name: import_zod.z.string().regex(/^[a-z][a-z0-9_]*$/),
|
|
78
|
+
type: AssertionType,
|
|
79
|
+
description: import_zod.z.string().default(""),
|
|
80
|
+
// pattern
|
|
81
|
+
must_not_match: import_zod.z.string().optional(),
|
|
82
|
+
must_match: import_zod.z.string().optional(),
|
|
83
|
+
// schema
|
|
84
|
+
schema: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
85
|
+
// llm
|
|
86
|
+
prompt: import_zod.z.string().optional(),
|
|
87
|
+
pass_when: import_zod.z.string().optional(),
|
|
88
|
+
model: import_zod.z.string().optional(),
|
|
89
|
+
// cost
|
|
90
|
+
max_usd: import_zod.z.number().nonnegative().optional(),
|
|
91
|
+
// latency
|
|
92
|
+
max_ms: import_zod.z.number().int().positive().optional(),
|
|
93
|
+
// custom
|
|
94
|
+
plugin: import_zod.z.string().optional()
|
|
95
|
+
}).passthrough();
|
|
96
|
+
var Limits = import_zod.z.object({
|
|
97
|
+
max_tokens: import_zod.z.number().int().positive().optional(),
|
|
98
|
+
max_input_tokens: import_zod.z.number().int().positive().optional(),
|
|
99
|
+
max_latency_ms: import_zod.z.number().int().positive().optional(),
|
|
100
|
+
max_cost_usd: import_zod.z.number().nonnegative().optional(),
|
|
101
|
+
max_tool_calls: import_zod.z.number().int().nonnegative().optional(),
|
|
102
|
+
max_steps: import_zod.z.number().int().positive().optional()
|
|
103
|
+
}).default({});
|
|
104
|
+
var OnViolation = import_zod.z.object({
|
|
105
|
+
default: ViolationAction
|
|
106
|
+
}).catchall(import_zod.z.string()).default({ default: "block" });
|
|
107
|
+
var Contract = import_zod.z.object({
|
|
108
|
+
agent: import_zod.z.string().min(1),
|
|
109
|
+
"spec-version": import_zod.z.string(),
|
|
110
|
+
version: import_zod.z.string(),
|
|
111
|
+
description: import_zod.z.string().default(""),
|
|
112
|
+
author: import_zod.z.string().default(""),
|
|
113
|
+
created: import_zod.z.string().default(""),
|
|
114
|
+
tags: import_zod.z.array(import_zod.z.string()).default([]),
|
|
115
|
+
extends: import_zod.z.string().optional(),
|
|
116
|
+
must: import_zod.z.array(Clause).default([]),
|
|
117
|
+
must_not: import_zod.z.array(Clause).default([]),
|
|
118
|
+
can: import_zod.z.array(import_zod.z.string()).default([]),
|
|
119
|
+
requires: import_zod.z.array(PreconditionClause).default([]),
|
|
120
|
+
ensures: import_zod.z.array(Clause).default([]),
|
|
121
|
+
invariant: import_zod.z.array(Clause).default([]),
|
|
122
|
+
assert: import_zod.z.array(Assertion).default([]),
|
|
123
|
+
limits: Limits,
|
|
124
|
+
on_violation: OnViolation
|
|
125
|
+
});
|
|
126
|
+
function getClauseText(clause) {
|
|
127
|
+
return typeof clause === "string" ? clause : clause.text;
|
|
128
|
+
}
|
|
129
|
+
function getClauseJudge(clause) {
|
|
130
|
+
return typeof clause === "string" ? "deterministic" : clause.judge;
|
|
131
|
+
}
|
|
132
|
+
function getViolationAction(onViolation, name) {
|
|
133
|
+
return onViolation[name] ?? onViolation.default;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/exceptions.ts
|
|
137
|
+
init_cjs_shims();
|
|
138
|
+
var ContractError = class extends Error {
|
|
139
|
+
constructor(message) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.name = "ContractError";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var ContractLoadError = class extends ContractError {
|
|
145
|
+
constructor(message) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.name = "ContractLoadError";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/loader.ts
|
|
152
|
+
function loadContract(filePath) {
|
|
153
|
+
const ext = (0, import_path.extname)(filePath).toLowerCase();
|
|
154
|
+
if (![".yaml", ".yml", ".json"].includes(ext)) {
|
|
155
|
+
throw new ContractLoadError(
|
|
156
|
+
`Unsupported file format: ${ext}. Use .contract.yaml or .contract.json`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
let raw;
|
|
160
|
+
try {
|
|
161
|
+
raw = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
162
|
+
} catch {
|
|
163
|
+
throw new ContractLoadError(`Contract file not found: ${filePath}`);
|
|
164
|
+
}
|
|
165
|
+
let data;
|
|
166
|
+
try {
|
|
167
|
+
data = ext === ".json" ? JSON.parse(raw) : import_js_yaml.default.load(raw);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
throw new ContractLoadError(`Failed to parse contract file: ${e}`);
|
|
170
|
+
}
|
|
171
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
172
|
+
throw new ContractLoadError("Contract file must be a YAML/JSON object at the root level.");
|
|
173
|
+
}
|
|
174
|
+
const result = Contract.safeParse(data);
|
|
175
|
+
if (!result.success) {
|
|
176
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
177
|
+
throw new ContractLoadError(`Contract schema validation failed:
|
|
178
|
+
${issues}`);
|
|
179
|
+
}
|
|
180
|
+
return result.data;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/runner.ts
|
|
184
|
+
init_cjs_shims();
|
|
185
|
+
var import_crypto = require("crypto");
|
|
186
|
+
|
|
187
|
+
// src/validators/pattern.ts
|
|
188
|
+
init_cjs_shims();
|
|
189
|
+
var PatternValidator = class {
|
|
190
|
+
constructor(name, mustNotMatch, mustMatch, description = "") {
|
|
191
|
+
this.name = name;
|
|
192
|
+
this.mustNotMatch = mustNotMatch;
|
|
193
|
+
this.mustMatch = mustMatch;
|
|
194
|
+
this.description = description;
|
|
195
|
+
}
|
|
196
|
+
validate(context) {
|
|
197
|
+
const output = context.output;
|
|
198
|
+
if (this.mustNotMatch) {
|
|
199
|
+
const re = new RegExp(this.mustNotMatch);
|
|
200
|
+
const match = re.exec(output);
|
|
201
|
+
if (match) {
|
|
202
|
+
return {
|
|
203
|
+
passed: false,
|
|
204
|
+
clauseName: this.name,
|
|
205
|
+
clauseText: this.description || `must_not_match: ${this.mustNotMatch}`,
|
|
206
|
+
clauseType: "assert",
|
|
207
|
+
judge: "deterministic",
|
|
208
|
+
details: `Forbidden pattern found: '${match[0].slice(0, 50)}'`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (this.mustMatch) {
|
|
213
|
+
const re = new RegExp(this.mustMatch);
|
|
214
|
+
if (!re.test(output)) {
|
|
215
|
+
return {
|
|
216
|
+
passed: false,
|
|
217
|
+
clauseName: this.name,
|
|
218
|
+
clauseText: this.description || `must_match: ${this.mustMatch}`,
|
|
219
|
+
clauseType: "assert",
|
|
220
|
+
judge: "deterministic",
|
|
221
|
+
details: "Required pattern not found in output."
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
passed: true,
|
|
227
|
+
clauseName: this.name,
|
|
228
|
+
clauseText: this.description || this.name,
|
|
229
|
+
clauseType: "assert",
|
|
230
|
+
judge: "deterministic",
|
|
231
|
+
details: ""
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/validators/cost.ts
|
|
237
|
+
init_cjs_shims();
|
|
238
|
+
var CostValidator = class {
|
|
239
|
+
constructor(name, maxUsd, description = "") {
|
|
240
|
+
this.name = name;
|
|
241
|
+
this.maxUsd = maxUsd;
|
|
242
|
+
this.description = description;
|
|
243
|
+
}
|
|
244
|
+
validate(context) {
|
|
245
|
+
const passed = context.costUsd <= this.maxUsd;
|
|
246
|
+
return {
|
|
247
|
+
passed,
|
|
248
|
+
clauseName: this.name,
|
|
249
|
+
clauseText: this.description || `cost must not exceed $${this.maxUsd.toFixed(4)} USD`,
|
|
250
|
+
clauseType: "assert",
|
|
251
|
+
judge: "deterministic",
|
|
252
|
+
details: passed ? "" : `Run cost $${context.costUsd.toFixed(4)} exceeded limit $${this.maxUsd.toFixed(4)}`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/validators/latency.ts
|
|
258
|
+
init_cjs_shims();
|
|
259
|
+
var LatencyValidator = class {
|
|
260
|
+
constructor(name, maxMs, description = "") {
|
|
261
|
+
this.name = name;
|
|
262
|
+
this.maxMs = maxMs;
|
|
263
|
+
this.description = description;
|
|
264
|
+
}
|
|
265
|
+
validate(context) {
|
|
266
|
+
const passed = context.durationMs <= this.maxMs;
|
|
267
|
+
return {
|
|
268
|
+
passed,
|
|
269
|
+
clauseName: this.name,
|
|
270
|
+
clauseText: this.description || `latency must not exceed ${this.maxMs}ms`,
|
|
271
|
+
clauseType: "assert",
|
|
272
|
+
judge: "deterministic",
|
|
273
|
+
details: passed ? "" : `Run took ${Math.round(context.durationMs)}ms, exceeded limit of ${this.maxMs}ms`
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/validators/llm.ts
|
|
279
|
+
init_cjs_shims();
|
|
280
|
+
var DEFAULT_JUDGE_MODEL = "claude-haiku-4-5-20251001";
|
|
281
|
+
var JUDGE_SYSTEM_PROMPT = "You are an impartial compliance judge evaluating an AI agent's behavior against a specific contract clause. Evaluate objectively based only on the evidence provided. Your response must be a single word: YES or NO, followed optionally by one sentence of reasoning.";
|
|
282
|
+
var LLMValidator = class {
|
|
283
|
+
constructor(name, clauseText, clauseType, prompt, passWhen = "NO", model = DEFAULT_JUDGE_MODEL, description = "") {
|
|
284
|
+
this.name = name;
|
|
285
|
+
this.clauseText = clauseText;
|
|
286
|
+
this.clauseType = clauseType;
|
|
287
|
+
this.prompt = prompt;
|
|
288
|
+
this.passWhen = passWhen;
|
|
289
|
+
this.model = model;
|
|
290
|
+
this.description = description;
|
|
291
|
+
}
|
|
292
|
+
async validate(context) {
|
|
293
|
+
let Anthropic;
|
|
294
|
+
try {
|
|
295
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
296
|
+
Anthropic = mod.default;
|
|
297
|
+
} catch {
|
|
298
|
+
return {
|
|
299
|
+
passed: false,
|
|
300
|
+
clauseName: this.name,
|
|
301
|
+
clauseText: this.clauseText,
|
|
302
|
+
clauseType: this.clauseType,
|
|
303
|
+
judge: "llm",
|
|
304
|
+
details: "@anthropic-ai/sdk not installed. Run: npm install @anthropic-ai/sdk"
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const client = new Anthropic();
|
|
308
|
+
const userPrompt = this.prompt ? `${this.prompt}
|
|
309
|
+
|
|
310
|
+
--- AGENT INPUT ---
|
|
311
|
+
${context.input}
|
|
312
|
+
|
|
313
|
+
--- AGENT OUTPUT ---
|
|
314
|
+
${context.output}` : `Contract clause: "${this.clauseText}"
|
|
315
|
+
|
|
316
|
+
--- AGENT INPUT ---
|
|
317
|
+
${context.input}
|
|
318
|
+
|
|
319
|
+
--- AGENT OUTPUT ---
|
|
320
|
+
${context.output}
|
|
321
|
+
|
|
322
|
+
Does the agent's output satisfy the contract clause? Answer YES if satisfied, NO if violated.`;
|
|
323
|
+
try {
|
|
324
|
+
const response = await client.messages.create({
|
|
325
|
+
model: this.model,
|
|
326
|
+
max_tokens: 64,
|
|
327
|
+
system: JUDGE_SYSTEM_PROMPT,
|
|
328
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
329
|
+
});
|
|
330
|
+
const raw = response.content[0].type === "text" ? response.content[0].text.trim() : "";
|
|
331
|
+
const firstWord = raw.split(/\s+/)[0]?.toUpperCase().replace(/[.,;:]$/, "") ?? "";
|
|
332
|
+
const passed = firstWord === this.passWhen.toUpperCase();
|
|
333
|
+
const reasoning = raw.slice(firstWord.length).trim();
|
|
334
|
+
return {
|
|
335
|
+
passed,
|
|
336
|
+
clauseName: this.name,
|
|
337
|
+
clauseText: this.clauseText,
|
|
338
|
+
clauseType: this.clauseType,
|
|
339
|
+
judge: "llm",
|
|
340
|
+
details: reasoning
|
|
341
|
+
};
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return {
|
|
344
|
+
passed: false,
|
|
345
|
+
clauseName: this.name,
|
|
346
|
+
clauseText: this.clauseText,
|
|
347
|
+
clauseType: this.clauseType,
|
|
348
|
+
judge: "llm",
|
|
349
|
+
details: `Judge model error: ${e}`
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/runner.ts
|
|
356
|
+
var ContractRunner = class {
|
|
357
|
+
constructor(contract) {
|
|
358
|
+
this.contract = contract;
|
|
359
|
+
}
|
|
360
|
+
async run(context, runId) {
|
|
361
|
+
const rid = runId ?? (0, import_crypto.randomUUID)();
|
|
362
|
+
const violations = [];
|
|
363
|
+
const c = this.contract;
|
|
364
|
+
const ov = c.on_violation;
|
|
365
|
+
violations.push(...this._checkLimits(context));
|
|
366
|
+
for (const assertion of c.assert) {
|
|
367
|
+
const result = await this._runAssertion(assertion, context);
|
|
368
|
+
if (!result.passed) {
|
|
369
|
+
const action = getViolationAction(ov, assertion.name);
|
|
370
|
+
violations.push({
|
|
371
|
+
clauseType: "assert",
|
|
372
|
+
clauseName: assertion.name,
|
|
373
|
+
clauseText: result.clauseText,
|
|
374
|
+
severity: action,
|
|
375
|
+
actionTaken: action,
|
|
376
|
+
judge: result.judge,
|
|
377
|
+
details: result.details
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const clause of c.must) {
|
|
382
|
+
const text = getClauseText(clause);
|
|
383
|
+
const judge = getClauseJudge(clause);
|
|
384
|
+
const result = await this._evaluateClause(text, "must", judge, context);
|
|
385
|
+
if (!result.passed) {
|
|
386
|
+
const action = getViolationAction(ov, `must:${text.slice(0, 30)}`);
|
|
387
|
+
violations.push({ clauseType: "must", clauseName: `must:${text.slice(0, 30)}`, clauseText: text, severity: action, actionTaken: action, judge, details: result.details });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
for (const clause of c.must_not) {
|
|
391
|
+
const text = getClauseText(clause);
|
|
392
|
+
const judge = getClauseJudge(clause);
|
|
393
|
+
const result = await this._evaluateClause(text, "must_not", judge, context);
|
|
394
|
+
if (!result.passed) {
|
|
395
|
+
const action = getViolationAction(ov, `must_not:${text.slice(0, 30)}`);
|
|
396
|
+
violations.push({ clauseType: "must_not", clauseName: `must_not:${text.slice(0, 30)}`, clauseText: text, severity: action, actionTaken: action, judge, details: result.details });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
for (const clause of c.ensures) {
|
|
400
|
+
const text = getClauseText(clause);
|
|
401
|
+
const judge = getClauseJudge(clause);
|
|
402
|
+
const result = await this._evaluateClause(text, "ensures", judge, context);
|
|
403
|
+
if (!result.passed) {
|
|
404
|
+
const action = getViolationAction(ov, `ensures:${text.slice(0, 30)}`);
|
|
405
|
+
violations.push({ clauseType: "ensures", clauseName: `ensures:${text.slice(0, 30)}`, clauseText: text, severity: action, actionTaken: action, judge, details: result.details });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const blocking = ["block", "rollback", "halt_and_alert"];
|
|
409
|
+
const passed = !violations.some((v) => blocking.includes(v.actionTaken));
|
|
410
|
+
return { passed, runId: rid, agent: c.agent, contractVersion: c.version, violations, context, outcome: passed ? "pass" : "violation" };
|
|
411
|
+
}
|
|
412
|
+
_checkLimits(context) {
|
|
413
|
+
const records = [];
|
|
414
|
+
const limits = this.contract.limits;
|
|
415
|
+
const ov = this.contract.on_violation;
|
|
416
|
+
if (limits.max_latency_ms != null) {
|
|
417
|
+
const r = new LatencyValidator("max_latency_ms", limits.max_latency_ms).validate(context);
|
|
418
|
+
if (!r.passed) {
|
|
419
|
+
const action = getViolationAction(ov, "max_latency_ms");
|
|
420
|
+
records.push({ clauseType: "limits", clauseName: "max_latency_ms", clauseText: r.clauseText, severity: action, actionTaken: action, judge: "deterministic", details: r.details });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (limits.max_cost_usd != null) {
|
|
424
|
+
const r = new CostValidator("max_cost_usd", limits.max_cost_usd).validate(context);
|
|
425
|
+
if (!r.passed) {
|
|
426
|
+
const action = getViolationAction(ov, "max_cost_usd");
|
|
427
|
+
records.push({ clauseType: "limits", clauseName: "max_cost_usd", clauseText: r.clauseText, severity: action, actionTaken: action, judge: "deterministic", details: r.details });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (limits.max_tokens != null && context.output) {
|
|
431
|
+
const estimated = Math.floor(context.output.length / 4);
|
|
432
|
+
if (estimated > limits.max_tokens) {
|
|
433
|
+
const action = getViolationAction(ov, "max_tokens");
|
|
434
|
+
records.push({ clauseType: "limits", clauseName: "max_tokens", clauseText: `output must not exceed ${limits.max_tokens} tokens`, severity: action, actionTaken: action, judge: "deterministic", details: `Estimated ${estimated} tokens exceeds limit of ${limits.max_tokens}` });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return records;
|
|
438
|
+
}
|
|
439
|
+
async _runAssertion(assertion, context) {
|
|
440
|
+
switch (assertion.type) {
|
|
441
|
+
case "pattern":
|
|
442
|
+
return new PatternValidator(assertion.name, assertion.must_not_match, assertion.must_match, assertion.description).validate(context);
|
|
443
|
+
case "cost":
|
|
444
|
+
return new CostValidator(assertion.name, assertion.max_usd ?? 0, assertion.description).validate(context);
|
|
445
|
+
case "latency":
|
|
446
|
+
return new LatencyValidator(assertion.name, assertion.max_ms ?? 0, assertion.description).validate(context);
|
|
447
|
+
case "llm":
|
|
448
|
+
return new LLMValidator(assertion.name, assertion.description || assertion.name, "assert", assertion.prompt, assertion.pass_when ?? "NO", assertion.model).validate(context);
|
|
449
|
+
default:
|
|
450
|
+
return { passed: false, clauseName: assertion.name, clauseText: assertion.description || assertion.name, clauseType: "assert", judge: "deterministic", details: `Unsupported assertion type: ${assertion.type}` };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async _evaluateClause(text, clauseType, judge, context) {
|
|
454
|
+
if (judge === "llm") {
|
|
455
|
+
return new LLMValidator(`${clauseType}:${text.slice(0, 30)}`, text, clauseType).validate(context);
|
|
456
|
+
}
|
|
457
|
+
return { passed: true, clauseName: `${clauseType}:${text.slice(0, 30)}`, clauseText: text, clauseType, judge: "deterministic", details: "" };
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/validators/base.ts
|
|
462
|
+
init_cjs_shims();
|
|
463
|
+
function makeContext(partial) {
|
|
464
|
+
return {
|
|
465
|
+
durationMs: 0,
|
|
466
|
+
costUsd: 0,
|
|
467
|
+
toolCalls: [],
|
|
468
|
+
steps: 0,
|
|
469
|
+
metadata: {},
|
|
470
|
+
...partial
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/index.ts
|
|
475
|
+
init_cjs_shims();
|
|
476
|
+
|
|
477
|
+
// src/enforce.ts
|
|
478
|
+
init_cjs_shims();
|
|
479
|
+
|
|
480
|
+
// src/index.ts
|
|
481
|
+
init_audit();
|
|
482
|
+
var VERSION = "0.1.0";
|
|
483
|
+
|
|
484
|
+
// src/cli.ts
|
|
485
|
+
import_commander.program.name("agentcontract").description("AgentContract \u2014 behavioral contracts for AI agents").version(VERSION);
|
|
486
|
+
import_commander.program.command("check <contract>").description("Validate a contract file against the AgentContract schema").action((contractFile) => {
|
|
487
|
+
try {
|
|
488
|
+
const contract = loadContract(contractFile);
|
|
489
|
+
console.log("\u2713 Contract is valid");
|
|
490
|
+
console.log(` Agent: ${contract.agent}`);
|
|
491
|
+
console.log(` Version: ${contract.version}`);
|
|
492
|
+
console.log(` Spec: ${contract["spec-version"]}`);
|
|
493
|
+
console.log(` Clauses: ${contract.must.length} must, ${contract.must_not.length} must_not, ${contract.assert.length} assertions`);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
console.error(`\u2717 Invalid contract: ${e instanceof Error ? e.message : e}`);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
import_commander.program.command("validate <contract> <runLog>").description("Validate a JSONL run log against a contract").option("--format <format>", "Output format: text or json", "text").action(async (contractFile, runLogFile, opts) => {
|
|
500
|
+
let contract;
|
|
501
|
+
try {
|
|
502
|
+
contract = loadContract(contractFile);
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.error(`\u2717 ${e instanceof Error ? e.message : e}`);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
const runner = new ContractRunner(contract);
|
|
508
|
+
const lines = (0, import_fs2.readFileSync)(runLogFile, "utf-8").split("\n").filter(Boolean);
|
|
509
|
+
const results = [];
|
|
510
|
+
let failed = 0;
|
|
511
|
+
for (const line of lines) {
|
|
512
|
+
try {
|
|
513
|
+
const entry = JSON.parse(line);
|
|
514
|
+
const ctx = makeContext({
|
|
515
|
+
input: entry.input ?? "",
|
|
516
|
+
output: entry.output ?? "",
|
|
517
|
+
durationMs: entry.duration_ms ?? 0,
|
|
518
|
+
costUsd: entry.cost_usd ?? 0
|
|
519
|
+
});
|
|
520
|
+
const result = await runner.run(ctx);
|
|
521
|
+
results.push(result);
|
|
522
|
+
if (!result.passed) failed++;
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (opts.format === "json") {
|
|
527
|
+
console.log(JSON.stringify(results.map((r) => ({
|
|
528
|
+
run_id: r.runId,
|
|
529
|
+
outcome: r.outcome,
|
|
530
|
+
violations: r.violations
|
|
531
|
+
})), null, 2));
|
|
532
|
+
} else {
|
|
533
|
+
const total = results.length;
|
|
534
|
+
const passed = total - failed;
|
|
535
|
+
console.log(`
|
|
536
|
+
AgentContract Validation Report`);
|
|
537
|
+
console.log(`Contract: ${contractFile} | Runs: ${total} | Passed: ${passed} | Failed: ${failed}`);
|
|
538
|
+
for (const r of results) {
|
|
539
|
+
if (r.violations.length > 0) {
|
|
540
|
+
console.log(`
|
|
541
|
+
Run ${r.runId.slice(0, 8)}... \u2014 VIOLATION`);
|
|
542
|
+
for (const v of r.violations) {
|
|
543
|
+
const icon = v.actionTaken !== "warn" ? "\u2717" : "\u26A0";
|
|
544
|
+
console.log(` ${icon} [${v.actionTaken.toUpperCase()}] ${v.clauseType}: "${v.clauseText}"`);
|
|
545
|
+
if (v.details) console.log(` ${v.details}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
551
|
+
});
|
|
552
|
+
import_commander.program.command("info <contract>").description("Display a summary of a contract file").action((contractFile) => {
|
|
553
|
+
try {
|
|
554
|
+
const c = loadContract(contractFile);
|
|
555
|
+
console.log(`
|
|
556
|
+
AgentContract \u2014 ${c.agent} v${c.version}`);
|
|
557
|
+
console.log(` Spec version : ${c["spec-version"]}`);
|
|
558
|
+
if (c.description) console.log(` Description : ${c.description}`);
|
|
559
|
+
if (c.author) console.log(` Author : ${c.author}`);
|
|
560
|
+
if (c.tags.length) console.log(` Tags : ${c.tags.join(", ")}`);
|
|
561
|
+
console.log(`
|
|
562
|
+
Clauses:`);
|
|
563
|
+
console.log(` must : ${c.must.length}`);
|
|
564
|
+
console.log(` must_not : ${c.must_not.length}`);
|
|
565
|
+
console.log(` can : ${c.can.length}`);
|
|
566
|
+
console.log(` requires : ${c.requires.length}`);
|
|
567
|
+
console.log(` ensures : ${c.ensures.length}`);
|
|
568
|
+
console.log(` assert : ${c.assert.length}`);
|
|
569
|
+
console.log(`
|
|
570
|
+
Violation default: ${c.on_violation.default}`);
|
|
571
|
+
} catch (e) {
|
|
572
|
+
console.error(`\u2717 ${e instanceof Error ? e.message : e}`);
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
import_commander.program.parse();
|
|
577
|
+
//# sourceMappingURL=cli.js.map
|