@airprompter/agent-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.
Files changed (129) hide show
  1. package/README.md +28 -0
  2. package/dist/cjs/bundle/apbundle.js +98 -0
  3. package/dist/cjs/bundle/apbundle.js.map +1 -0
  4. package/dist/cjs/bundle/hpke.js +120 -0
  5. package/dist/cjs/bundle/hpke.js.map +1 -0
  6. package/dist/cjs/checks/index.js +450 -0
  7. package/dist/cjs/checks/index.js.map +1 -0
  8. package/dist/cjs/control/client.js +85 -0
  9. package/dist/cjs/control/client.js.map +1 -0
  10. package/dist/cjs/golden/index.js +138 -0
  11. package/dist/cjs/golden/index.js.map +1 -0
  12. package/dist/cjs/index.js +125 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/judge/index.js +134 -0
  15. package/dist/cjs/judge/index.js.map +1 -0
  16. package/dist/cjs/package.json +1 -0
  17. package/dist/cjs/ports/node.js +30 -0
  18. package/dist/cjs/ports/node.js.map +1 -0
  19. package/dist/cjs/protocol/assignment.js +158 -0
  20. package/dist/cjs/protocol/assignment.js.map +1 -0
  21. package/dist/cjs/protocol/canonicalJson.js +72 -0
  22. package/dist/cjs/protocol/canonicalJson.js.map +1 -0
  23. package/dist/cjs/protocol/errors.js +22 -0
  24. package/dist/cjs/protocol/errors.js.map +1 -0
  25. package/dist/cjs/protocol/ports.js +19 -0
  26. package/dist/cjs/protocol/ports.js.map +1 -0
  27. package/dist/cjs/protocol/trust.js +225 -0
  28. package/dist/cjs/protocol/trust.js.map +1 -0
  29. package/dist/cjs/protocol/types.js +15 -0
  30. package/dist/cjs/protocol/types.js.map +1 -0
  31. package/dist/cjs/protocol/version.js +8 -0
  32. package/dist/cjs/protocol/version.js.map +1 -0
  33. package/dist/cjs/release/bundleRelease.js +61 -0
  34. package/dist/cjs/release/bundleRelease.js.map +1 -0
  35. package/dist/cjs/release/reader.js +12 -0
  36. package/dist/cjs/release/reader.js.map +1 -0
  37. package/dist/cjs/render/runRef.js +33 -0
  38. package/dist/cjs/render/runRef.js.map +1 -0
  39. package/dist/cjs/render/template.js +71 -0
  40. package/dist/cjs/render/template.js.map +1 -0
  41. package/dist/cjs/telemetry/feedback.js +81 -0
  42. package/dist/cjs/telemetry/feedback.js.map +1 -0
  43. package/dist/cjs/telemetry/rows.js +28 -0
  44. package/dist/cjs/telemetry/rows.js.map +1 -0
  45. package/dist/cjs/telemetry/uploadSink.js +13 -0
  46. package/dist/cjs/telemetry/uploadSink.js.map +1 -0
  47. package/dist/cjs/testing/controlPlane.js +381 -0
  48. package/dist/cjs/testing/controlPlane.js.map +1 -0
  49. package/dist/cjs/testing/index.js +45 -0
  50. package/dist/cjs/testing/index.js.map +1 -0
  51. package/dist/cjs/testing/memoryFs.js +236 -0
  52. package/dist/cjs/testing/memoryFs.js.map +1 -0
  53. package/dist/esm/.tsbuildinfo +1 -0
  54. package/dist/esm/bundle/apbundle.d.ts +33 -0
  55. package/dist/esm/bundle/apbundle.js +89 -0
  56. package/dist/esm/bundle/apbundle.js.map +1 -0
  57. package/dist/esm/bundle/hpke.d.ts +48 -0
  58. package/dist/esm/bundle/hpke.js +107 -0
  59. package/dist/esm/bundle/hpke.js.map +1 -0
  60. package/dist/esm/checks/index.d.ts +85 -0
  61. package/dist/esm/checks/index.js +436 -0
  62. package/dist/esm/checks/index.js.map +1 -0
  63. package/dist/esm/control/client.d.ts +78 -0
  64. package/dist/esm/control/client.js +81 -0
  65. package/dist/esm/control/client.js.map +1 -0
  66. package/dist/esm/golden/index.d.ts +97 -0
  67. package/dist/esm/golden/index.js +130 -0
  68. package/dist/esm/golden/index.js.map +1 -0
  69. package/dist/esm/index.d.ts +41 -0
  70. package/dist/esm/index.js +31 -0
  71. package/dist/esm/index.js.map +1 -0
  72. package/dist/esm/judge/index.d.ts +51 -0
  73. package/dist/esm/judge/index.js +127 -0
  74. package/dist/esm/judge/index.js.map +1 -0
  75. package/dist/esm/ports/node.d.ts +8 -0
  76. package/dist/esm/ports/node.js +27 -0
  77. package/dist/esm/ports/node.js.map +1 -0
  78. package/dist/esm/protocol/assignment.d.ts +49 -0
  79. package/dist/esm/protocol/assignment.js +143 -0
  80. package/dist/esm/protocol/assignment.js.map +1 -0
  81. package/dist/esm/protocol/canonicalJson.d.ts +10 -0
  82. package/dist/esm/protocol/canonicalJson.js +65 -0
  83. package/dist/esm/protocol/canonicalJson.js.map +1 -0
  84. package/dist/esm/protocol/errors.d.ts +15 -0
  85. package/dist/esm/protocol/errors.js +19 -0
  86. package/dist/esm/protocol/errors.js.map +1 -0
  87. package/dist/esm/protocol/ports.d.ts +65 -0
  88. package/dist/esm/protocol/ports.js +16 -0
  89. package/dist/esm/protocol/ports.js.map +1 -0
  90. package/dist/esm/protocol/trust.d.ts +60 -0
  91. package/dist/esm/protocol/trust.js +211 -0
  92. package/dist/esm/protocol/trust.js.map +1 -0
  93. package/dist/esm/protocol/types.d.ts +235 -0
  94. package/dist/esm/protocol/types.js +12 -0
  95. package/dist/esm/protocol/types.js.map +1 -0
  96. package/dist/esm/protocol/version.d.ts +4 -0
  97. package/dist/esm/protocol/version.js +5 -0
  98. package/dist/esm/protocol/version.js.map +1 -0
  99. package/dist/esm/release/bundleRelease.d.ts +45 -0
  100. package/dist/esm/release/bundleRelease.js +57 -0
  101. package/dist/esm/release/bundleRelease.js.map +1 -0
  102. package/dist/esm/release/reader.d.ts +30 -0
  103. package/dist/esm/release/reader.js +11 -0
  104. package/dist/esm/release/reader.js.map +1 -0
  105. package/dist/esm/render/runRef.d.ts +17 -0
  106. package/dist/esm/render/runRef.js +29 -0
  107. package/dist/esm/render/runRef.js.map +1 -0
  108. package/dist/esm/render/template.d.ts +38 -0
  109. package/dist/esm/render/template.js +65 -0
  110. package/dist/esm/render/template.js.map +1 -0
  111. package/dist/esm/telemetry/feedback.d.ts +21 -0
  112. package/dist/esm/telemetry/feedback.js +77 -0
  113. package/dist/esm/telemetry/feedback.js.map +1 -0
  114. package/dist/esm/telemetry/rows.d.ts +85 -0
  115. package/dist/esm/telemetry/rows.js +22 -0
  116. package/dist/esm/telemetry/rows.js.map +1 -0
  117. package/dist/esm/telemetry/uploadSink.d.ts +52 -0
  118. package/dist/esm/telemetry/uploadSink.js +12 -0
  119. package/dist/esm/telemetry/uploadSink.js.map +1 -0
  120. package/dist/esm/testing/controlPlane.d.ts +116 -0
  121. package/dist/esm/testing/controlPlane.js +341 -0
  122. package/dist/esm/testing/controlPlane.js.map +1 -0
  123. package/dist/esm/testing/index.d.ts +25 -0
  124. package/dist/esm/testing/index.js +35 -0
  125. package/dist/esm/testing/index.js.map +1 -0
  126. package/dist/esm/testing/memoryFs.d.ts +64 -0
  127. package/dist/esm/testing/memoryFs.js +232 -0
  128. package/dist/esm/testing/memoryFs.js.map +1 -0
  129. package/package.json +45 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * HPKE base mode (RFC 9180) with DHKEM(X25519, HKDF-SHA256), HKDF-SHA256 and
3
+ * AES-GCM, on node:crypto alone. The offline bundle (`.apbundle`) is sealed
4
+ * to the target's X25519 distribution key with AES-256-GCM; AES-128-GCM is
5
+ * here only so the implementation can be checked against RFC 9180's A.1
6
+ * test vector, which uses that AEAD with the same KEM and KDF.
7
+ */
8
+ import { createCipheriv, createDecipheriv, createHmac, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync } from "node:crypto";
9
+ const KEM_X25519 = 0x0020;
10
+ const KDF_HKDF_SHA256 = 0x0001;
11
+ export const AEAD_AES_128_GCM = 0x0001;
12
+ export const AEAD_AES_256_GCM = 0x0002;
13
+ const NSECRET = 32;
14
+ const NN = 12;
15
+ const utf8 = (text) => Buffer.from(text, "utf8");
16
+ const i2osp2 = (value) => Buffer.from([(value >> 8) & 0xff, value & 0xff]);
17
+ const kemSuiteId = Buffer.concat([utf8("KEM"), i2osp2(KEM_X25519)]);
18
+ const hpkeSuiteId = (aead) => Buffer.concat([utf8("HPKE"), i2osp2(KEM_X25519), i2osp2(KDF_HKDF_SHA256), i2osp2(aead)]);
19
+ function hkdfExtract(salt, ikm) {
20
+ return createHmac("sha256", salt.length ? salt : Buffer.alloc(32)).update(ikm).digest();
21
+ }
22
+ function hkdfExpand(prk, info, length) {
23
+ const blocks = [];
24
+ let previous = Buffer.alloc(0);
25
+ for (let counter = 1; Buffer.concat(blocks).length < length; counter += 1) {
26
+ previous = createHmac("sha256", prk).update(Buffer.concat([previous, info, Buffer.from([counter])])).digest();
27
+ blocks.push(previous);
28
+ }
29
+ return Buffer.concat(blocks).subarray(0, length);
30
+ }
31
+ function labeledExtract(suiteId, salt, label, ikm) {
32
+ return hkdfExtract(salt, Buffer.concat([utf8("HPKE-v1"), suiteId, utf8(label), ikm]));
33
+ }
34
+ function labeledExpand(suiteId, prk, label, info, length) {
35
+ return hkdfExpand(prk, Buffer.concat([i2osp2(length), utf8("HPKE-v1"), suiteId, utf8(label), info]), length);
36
+ }
37
+ function rawPublic(key) {
38
+ return Buffer.from(key.export({ format: "jwk" }).x, "base64url");
39
+ }
40
+ export function x25519PublicKeyFromRaw(raw) {
41
+ return createPublicKey({ key: { kty: "OKP", crv: "X25519", x: Buffer.from(raw).toString("base64url") }, format: "jwk" });
42
+ }
43
+ export function x25519PrivateKeyFromRaw(raw, publicRaw) {
44
+ return createPrivateKey({ key: { kty: "OKP", crv: "X25519", x: Buffer.from(publicRaw).toString("base64url"), d: Buffer.from(raw).toString("base64url") }, format: "jwk" });
45
+ }
46
+ export function generateX25519KeyPair() {
47
+ const pair = generateKeyPairSync("x25519");
48
+ return { privateKey: pair.privateKey, publicKey: pair.publicKey, publicRaw: rawPublic(pair.publicKey) };
49
+ }
50
+ function extractAndExpand(dh, kemContext) {
51
+ const eaePrk = labeledExtract(kemSuiteId, Buffer.alloc(0), "eae_prk", dh);
52
+ return labeledExpand(kemSuiteId, eaePrk, "shared_secret", kemContext, NSECRET);
53
+ }
54
+ export function encap(recipientPublicRaw, ephemeral) {
55
+ const e = ephemeral ?? generateX25519KeyPair();
56
+ const dh = diffieHellman({ privateKey: e.privateKey, publicKey: x25519PublicKeyFromRaw(recipientPublicRaw) });
57
+ const enc = e.publicRaw;
58
+ return { sharedSecret: extractAndExpand(dh, Buffer.concat([enc, Buffer.from(recipientPublicRaw)])), enc };
59
+ }
60
+ export function decap(enc, recipientPrivateKey, recipientPublicRaw) {
61
+ const dh = diffieHellman({ privateKey: recipientPrivateKey, publicKey: x25519PublicKeyFromRaw(enc) });
62
+ return extractAndExpand(dh, Buffer.concat([Buffer.from(enc), Buffer.from(recipientPublicRaw)]));
63
+ }
64
+ export function keySchedule(sharedSecret, info, aead) {
65
+ const suite = hpkeSuiteId(aead);
66
+ const pskIdHash = labeledExtract(suite, Buffer.alloc(0), "psk_id_hash", Buffer.alloc(0));
67
+ const infoHash = labeledExtract(suite, Buffer.alloc(0), "info_hash", info);
68
+ const context = Buffer.concat([Buffer.from([0x00]), pskIdHash, infoHash]);
69
+ const secret = labeledExtract(suite, sharedSecret, "secret", Buffer.alloc(0));
70
+ const nk = aead === AEAD_AES_256_GCM ? 32 : 16;
71
+ return { key: labeledExpand(suite, secret, "key", context, nk), baseNonce: labeledExpand(suite, secret, "base_nonce", context, NN), aead };
72
+ }
73
+ function nonceFor(context, seq) {
74
+ const nonce = Buffer.from(context.baseNonce);
75
+ // XOR the sequence number into the low-order bytes (big-endian).
76
+ let remaining = seq;
77
+ for (let index = nonce.length - 1; index >= 0 && remaining > 0; index -= 1) {
78
+ nonce[index] = nonce[index] ^ (remaining & 0xff);
79
+ remaining = Math.floor(remaining / 256);
80
+ }
81
+ return nonce;
82
+ }
83
+ /** ct ‖ tag(16), as HPKE's AEAD.Seal returns it. */
84
+ export function seal(context, aad, plaintext, seq = 0) {
85
+ const algorithm = context.aead === AEAD_AES_256_GCM ? "aes-256-gcm" : "aes-128-gcm";
86
+ const cipher = createCipheriv(algorithm, context.key, nonceFor(context, seq));
87
+ cipher.setAAD(aad);
88
+ return Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()]);
89
+ }
90
+ export function open(context, aad, ciphertext, seq = 0) {
91
+ const algorithm = context.aead === AEAD_AES_256_GCM ? "aes-256-gcm" : "aes-128-gcm";
92
+ const buffer = Buffer.from(ciphertext);
93
+ const decipher = createDecipheriv(algorithm, context.key, nonceFor(context, seq));
94
+ decipher.setAAD(aad);
95
+ decipher.setAuthTag(buffer.subarray(buffer.length - 16));
96
+ return Buffer.concat([decipher.update(buffer.subarray(0, buffer.length - 16)), decipher.final()]);
97
+ }
98
+ /** Single-shot base-mode seal: returns `enc` and the ciphertext. */
99
+ export function sealTo(recipientPublicRaw, info, aad, plaintext, aead = AEAD_AES_256_GCM) {
100
+ const { sharedSecret, enc } = encap(recipientPublicRaw);
101
+ return { enc, ciphertext: seal(keySchedule(sharedSecret, Buffer.from(info), aead), aad, plaintext) };
102
+ }
103
+ export function openFrom(input) {
104
+ const sharedSecret = decap(input.enc, input.recipientPrivateKey, input.recipientPublicRaw);
105
+ return open(keySchedule(sharedSecret, Buffer.from(input.info), input.aead ?? AEAD_AES_256_GCM), input.aad, input.ciphertext);
106
+ }
107
+ //# sourceMappingURL=hpke.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hpke.js","sourceRoot":"","sources":["../../../src/bundle/hpke.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Declared output checks (T29 / 5-E, checks.md): deterministic, on the host,
3
+ * on the output the SDK already sees; the result is two counters on the
4
+ * window (`checks.passed` / `checks.failed`) and never leaves the process.
5
+ * A port of `conformance/checks.mjs`; `vectors/checks.json` pins the two.
6
+ *
7
+ * Kinds: `json_schema` (a documented JSON Schema subset), `enum` (the string
8
+ * at a dotted path is one of the allowed values), `length` (an output-token
9
+ * band — the provider's count when reported, else ceil(UTF-8 bytes / 4)),
10
+ * `must_match` / `must_not_match` (a pattern that must / must not occur).
11
+ *
12
+ * Regex safety rule: RE2-class syntax only — no backreferences, no
13
+ * lookaround, no atomic or possessive groups, no quantified group whose body
14
+ * is itself quantified — patterns of at most 256 characters, and an output
15
+ * over 64 KiB fails a pattern check closed rather than being scanned in
16
+ * part. A refused pattern is refused at declaration and fails closed here.
17
+ */
18
+ import type { OutputCheck } from "../protocol/types.js";
19
+ export declare const CHECK_BOUNDS: Readonly<{
20
+ maxChecks: 8;
21
+ nameMaxLength: 64;
22
+ patternMaxLength: 256;
23
+ subjectMaxBytes: 65536;
24
+ schemaMaxBytes: 16384;
25
+ enumMaxValues: 64;
26
+ enumValueMaxLength: 128;
27
+ pathMaxLength: 128;
28
+ maxTokens: 1000000;
29
+ }>;
30
+ /** A declared check as the console stores it: the pin projection plus `enabled`. */
31
+ export type DeclaredCheck = OutputCheck & {
32
+ enabled?: boolean;
33
+ };
34
+ export type CheckResult = {
35
+ name: string;
36
+ kind: OutputCheck["kind"];
37
+ verdict: "pass" | "fail";
38
+ reason?: string;
39
+ };
40
+ export type CheckOutcome = {
41
+ passed: number;
42
+ failed: number;
43
+ results: CheckResult[];
44
+ };
45
+ export declare function utf8Bytes(text: string): number;
46
+ /** ceil(bytes / 4): the estimate when the provider reported no output tokens. */
47
+ export declare function estimateTokens(text: string): number;
48
+ /** Why a pattern is refused, or null when it is RE2-class and inside the cap. */
49
+ export declare function patternRefusal(pattern: unknown): string | null;
50
+ /** Why a declared check is refused, or null when it is well-formed. */
51
+ export declare function checkRefusal(check: unknown): string | null;
52
+ /** Every refusal of a declared list; empty when all pass. */
53
+ export declare function checksRefusals(checks: unknown): Array<{
54
+ name: string | null;
55
+ reason: string;
56
+ }>;
57
+ /** The JSON Schema subset checks.md names; unknown keywords are ignored. */
58
+ export declare function validateJsonSchema(schema: unknown, value: unknown): boolean;
59
+ /** A dotted path into parsed JSON; "" is the whole value. */
60
+ export declare function valueAtPath(value: unknown, path: string): {
61
+ found: true;
62
+ value: unknown;
63
+ } | {
64
+ found: false;
65
+ };
66
+ /** One check on one output; never throws. `outputTokens` null = estimate. */
67
+ export declare function evaluateCheck(check: OutputCheck, input: {
68
+ text: string;
69
+ outputTokens: number | null;
70
+ }): CheckResult;
71
+ /** Every enabled check on one output: the window's counters and the per-check results. */
72
+ export declare function evaluateChecks(checks: readonly DeclaredCheck[] | undefined, input: {
73
+ text: string;
74
+ outputTokens: number | null;
75
+ }): CheckOutcome;
76
+ /** The pin / manifest projection: enabled checks only, sorted by name, the kind's own members only. */
77
+ export declare function projectChecks(checks: readonly DeclaredCheck[] | undefined): OutputCheck[];
78
+ /**
79
+ * The output text of whatever the provider answered, for the checks: a
80
+ * plain string; OpenAI `choices[0].message.content` (a string or content
81
+ * parts); Anthropic `content[].text`; Bedrock Converse
82
+ * `output.message.content[].text`. Null when nothing recognisable — then the
83
+ * checks are not run (never a failure invented from an unknown shape).
84
+ */
85
+ export declare function outputTextOf(result: unknown): string | null;
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Declared output checks (T29 / 5-E, checks.md): deterministic, on the host,
3
+ * on the output the SDK already sees; the result is two counters on the
4
+ * window (`checks.passed` / `checks.failed`) and never leaves the process.
5
+ * A port of `conformance/checks.mjs`; `vectors/checks.json` pins the two.
6
+ *
7
+ * Kinds: `json_schema` (a documented JSON Schema subset), `enum` (the string
8
+ * at a dotted path is one of the allowed values), `length` (an output-token
9
+ * band — the provider's count when reported, else ceil(UTF-8 bytes / 4)),
10
+ * `must_match` / `must_not_match` (a pattern that must / must not occur).
11
+ *
12
+ * Regex safety rule: RE2-class syntax only — no backreferences, no
13
+ * lookaround, no atomic or possessive groups, no quantified group whose body
14
+ * is itself quantified — patterns of at most 256 characters, and an output
15
+ * over 64 KiB fails a pattern check closed rather than being scanned in
16
+ * part. A refused pattern is refused at declaration and fails closed here.
17
+ */
18
+ export const CHECK_BOUNDS = Object.freeze({
19
+ maxChecks: 8,
20
+ nameMaxLength: 64,
21
+ patternMaxLength: 256,
22
+ subjectMaxBytes: 65536,
23
+ schemaMaxBytes: 16384,
24
+ enumMaxValues: 64,
25
+ enumValueMaxLength: 128,
26
+ pathMaxLength: 128,
27
+ maxTokens: 1_000_000,
28
+ });
29
+ const NAME = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
30
+ const encoder = new TextEncoder();
31
+ export function utf8Bytes(text) {
32
+ return encoder.encode(text).length;
33
+ }
34
+ /** ceil(bytes / 4): the estimate when the provider reported no output tokens. */
35
+ export function estimateTokens(text) {
36
+ return Math.ceil(utf8Bytes(text) / 4);
37
+ }
38
+ /** Why a pattern is refused, or null when it is RE2-class and inside the cap. */
39
+ export function patternRefusal(pattern) {
40
+ if (typeof pattern !== "string" || pattern.length === 0)
41
+ return "empty";
42
+ if (pattern.length > CHECK_BOUNDS.patternMaxLength)
43
+ return "too_long";
44
+ if (/\\[1-9]/.test(pattern) || /\\k</.test(pattern))
45
+ return "backreference";
46
+ if (/\(\?<?[=!]/.test(pattern))
47
+ return "lookaround";
48
+ if (/\(\?>/.test(pattern) || /[*+?}]\+/.test(pattern))
49
+ return "possessive_or_atomic";
50
+ let depth = 0;
51
+ const bodies = [];
52
+ for (let i = 0; i < pattern.length; i += 1) {
53
+ const ch = pattern[i];
54
+ if (ch === "\\") {
55
+ i += 1;
56
+ continue;
57
+ }
58
+ if (ch === "[") {
59
+ let j = i + 1;
60
+ if (pattern[j] === "^")
61
+ j += 1;
62
+ if (pattern[j] === "]")
63
+ j += 1;
64
+ while (j < pattern.length && pattern[j] !== "]") {
65
+ if (pattern[j] === "\\")
66
+ j += 1;
67
+ j += 1;
68
+ }
69
+ i = j;
70
+ continue;
71
+ }
72
+ if (ch === "(") {
73
+ depth += 1;
74
+ bodies.push({ quantified: false });
75
+ continue;
76
+ }
77
+ if (ch === ")") {
78
+ const body = bodies.pop();
79
+ depth -= 1;
80
+ const groupQuantified = /^(?:[*+?]|\{\d+(?:,\d*)?\})/.test(pattern.slice(i + 1));
81
+ if (groupQuantified && body?.quantified)
82
+ return "nested_quantifier";
83
+ if (groupQuantified && bodies.length > 0)
84
+ bodies[bodies.length - 1].quantified = true;
85
+ continue;
86
+ }
87
+ if (ch === "{") {
88
+ const counted = /^\{\d+(?:,\d*)?\}/.exec(pattern.slice(i));
89
+ if (!counted)
90
+ return "invalid";
91
+ if (bodies.length > 0)
92
+ bodies[bodies.length - 1].quantified = true;
93
+ i += counted[0].length - 1;
94
+ continue;
95
+ }
96
+ if (ch === "}")
97
+ return "invalid";
98
+ if ((ch === "*" || ch === "+") && bodies.length > 0)
99
+ bodies[bodies.length - 1].quantified = true;
100
+ }
101
+ if (depth !== 0)
102
+ return "unbalanced";
103
+ try {
104
+ new RegExp(pattern, "u");
105
+ }
106
+ catch {
107
+ return "invalid";
108
+ }
109
+ return null;
110
+ }
111
+ function schemaPatternRefusal(schema) {
112
+ if (typeof schema !== "object" || schema === null)
113
+ return null;
114
+ if (Array.isArray(schema)) {
115
+ for (const entry of schema) {
116
+ const bad = schemaPatternRefusal(entry);
117
+ if (bad)
118
+ return bad;
119
+ }
120
+ return null;
121
+ }
122
+ for (const [key, value] of Object.entries(schema)) {
123
+ if (key === "pattern" && typeof value === "string") {
124
+ const bad = patternRefusal(value);
125
+ if (bad)
126
+ return bad;
127
+ continue;
128
+ }
129
+ const bad = schemaPatternRefusal(value);
130
+ if (bad)
131
+ return bad;
132
+ }
133
+ return null;
134
+ }
135
+ /** Why a declared check is refused, or null when it is well-formed. */
136
+ export function checkRefusal(check) {
137
+ if (typeof check !== "object" || check === null)
138
+ return "not_an_object";
139
+ const c = check;
140
+ if (typeof c.name !== "string" || !NAME.test(c.name) || c.name.length > CHECK_BOUNDS.nameMaxLength)
141
+ return "bad_name";
142
+ switch (c.kind) {
143
+ case "json_schema": {
144
+ if (typeof c.schema !== "object" || c.schema === null || Array.isArray(c.schema))
145
+ return "schema_not_an_object";
146
+ if (utf8Bytes(JSON.stringify(c.schema)) > CHECK_BOUNDS.schemaMaxBytes)
147
+ return "schema_too_large";
148
+ const bad = schemaPatternRefusal(c.schema);
149
+ return bad ? `schema_pattern_${bad}` : null;
150
+ }
151
+ case "enum": {
152
+ if (typeof c.path !== "string" || c.path.length > CHECK_BOUNDS.pathMaxLength)
153
+ return "bad_path";
154
+ const values = c.values;
155
+ if (!Array.isArray(values) || values.length === 0 || values.length > CHECK_BOUNDS.enumMaxValues)
156
+ return "bad_values";
157
+ if (!values.every((v) => typeof v === "string" && v.length > 0 && v.length <= CHECK_BOUNDS.enumValueMaxLength))
158
+ return "bad_values";
159
+ if (new Set(values).size !== values.length)
160
+ return "duplicate_values";
161
+ return null;
162
+ }
163
+ case "length": {
164
+ const okInt = (v) => Number.isInteger(v) && v >= 0 && v <= CHECK_BOUNDS.maxTokens;
165
+ if (c.minTokens === undefined && c.maxTokens === undefined)
166
+ return "no_bound";
167
+ if (c.minTokens !== undefined && !okInt(c.minTokens))
168
+ return "bad_bound";
169
+ if (c.maxTokens !== undefined && !okInt(c.maxTokens))
170
+ return "bad_bound";
171
+ if (c.minTokens !== undefined && c.maxTokens !== undefined && c.minTokens > c.maxTokens)
172
+ return "inverted_bounds";
173
+ return null;
174
+ }
175
+ case "must_match":
176
+ case "must_not_match": {
177
+ const refusal = patternRefusal(c.pattern);
178
+ if (refusal)
179
+ return `pattern_${refusal}`;
180
+ if (c.flags !== undefined && c.flags !== "i")
181
+ return "bad_flags";
182
+ return null;
183
+ }
184
+ default:
185
+ return "unknown_kind";
186
+ }
187
+ }
188
+ /** Every refusal of a declared list; empty when all pass. */
189
+ export function checksRefusals(checks) {
190
+ const refusals = [];
191
+ if (!Array.isArray(checks))
192
+ return [{ name: null, reason: "not_a_list" }];
193
+ if (checks.length > CHECK_BOUNDS.maxChecks)
194
+ refusals.push({ name: null, reason: "too_many" });
195
+ const names = new Set();
196
+ for (const check of checks) {
197
+ const reason = checkRefusal(check);
198
+ const name = typeof check?.name === "string" ? (check.name) : null;
199
+ if (reason)
200
+ refusals.push({ name, reason });
201
+ if (name !== null) {
202
+ if (names.has(name))
203
+ refusals.push({ name, reason: "duplicate_name" });
204
+ names.add(name);
205
+ }
206
+ }
207
+ return refusals;
208
+ }
209
+ function parseJson(text) {
210
+ try {
211
+ return { ok: true, value: JSON.parse(text) };
212
+ }
213
+ catch {
214
+ const starts = ["{", "["].map((c) => text.indexOf(c)).filter((i) => i >= 0);
215
+ const start = starts.length ? Math.min(...starts) : -1;
216
+ const end = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
217
+ if (start < 0 || end <= start)
218
+ return { ok: false };
219
+ try {
220
+ return { ok: true, value: JSON.parse(text.slice(start, end + 1)) };
221
+ }
222
+ catch {
223
+ return { ok: false };
224
+ }
225
+ }
226
+ }
227
+ function typeOf(value) {
228
+ if (value === null)
229
+ return "null";
230
+ if (Array.isArray(value))
231
+ return "array";
232
+ if (typeof value === "number")
233
+ return Number.isInteger(value) ? "integer" : "number";
234
+ return typeof value;
235
+ }
236
+ /** The JSON Schema subset checks.md names; unknown keywords are ignored. */
237
+ export function validateJsonSchema(schema, value) {
238
+ if (typeof schema !== "object" || schema === null)
239
+ return true;
240
+ const s = schema;
241
+ if (s.type !== undefined) {
242
+ const types = Array.isArray(s.type) ? s.type : [s.type];
243
+ const actual = typeOf(value);
244
+ if (!types.some((t) => t === actual || (t === "number" && actual === "integer")))
245
+ return false;
246
+ }
247
+ if (Array.isArray(s.enum) && !s.enum.some((v) => JSON.stringify(v) === JSON.stringify(value)))
248
+ return false;
249
+ if (s.const !== undefined && JSON.stringify(s.const) !== JSON.stringify(value))
250
+ return false;
251
+ if (Array.isArray(s.anyOf) && !s.anyOf.some((sub) => validateJsonSchema(sub, value)))
252
+ return false;
253
+ if (typeof value === "string") {
254
+ const length = [...value].length;
255
+ if (typeof s.minLength === "number" && length < s.minLength)
256
+ return false;
257
+ if (typeof s.maxLength === "number" && length > s.maxLength)
258
+ return false;
259
+ if (typeof s.pattern === "string") {
260
+ if (patternRefusal(s.pattern) || utf8Bytes(value) > CHECK_BOUNDS.subjectMaxBytes)
261
+ return false;
262
+ if (!new RegExp(s.pattern, "u").test(value))
263
+ return false;
264
+ }
265
+ }
266
+ if (typeof value === "number") {
267
+ if (typeof s.minimum === "number" && value < s.minimum)
268
+ return false;
269
+ if (typeof s.maximum === "number" && value > s.maximum)
270
+ return false;
271
+ }
272
+ if (Array.isArray(value)) {
273
+ if (typeof s.minItems === "number" && value.length < s.minItems)
274
+ return false;
275
+ if (typeof s.maxItems === "number" && value.length > s.maxItems)
276
+ return false;
277
+ if (s.items !== undefined && !value.every((item) => validateJsonSchema(s.items, item)))
278
+ return false;
279
+ }
280
+ if (typeOf(value) === "object") {
281
+ const record = value;
282
+ const properties = typeof s.properties === "object" && s.properties !== null ? s.properties : {};
283
+ for (const key of Array.isArray(s.required) ? s.required : [])
284
+ if (!(key in record))
285
+ return false;
286
+ for (const [key, sub] of Object.entries(properties))
287
+ if (key in record && !validateJsonSchema(sub, record[key]))
288
+ return false;
289
+ if (s.additionalProperties === false)
290
+ for (const key of Object.keys(record))
291
+ if (!(key in properties))
292
+ return false;
293
+ }
294
+ return true;
295
+ }
296
+ /** A dotted path into parsed JSON; "" is the whole value. */
297
+ export function valueAtPath(value, path) {
298
+ if (path === "")
299
+ return { found: true, value };
300
+ let current = value;
301
+ for (const part of path.split(".")) {
302
+ if (Array.isArray(current) && /^\d+$/.test(part))
303
+ current = current[Number(part)];
304
+ else if (typeof current === "object" && current !== null && part in current)
305
+ current = current[part];
306
+ else
307
+ return { found: false };
308
+ if (current === undefined)
309
+ return { found: false };
310
+ }
311
+ return { found: true, value: current };
312
+ }
313
+ /** One check on one output; never throws. `outputTokens` null = estimate. */
314
+ export function evaluateCheck(check, input) {
315
+ const text = typeof input.text === "string" ? input.text : "";
316
+ const fail = (reason) => ({ name: check.name, kind: check.kind, verdict: "fail", reason });
317
+ const pass = () => ({ name: check.name, kind: check.kind, verdict: "pass" });
318
+ switch (check.kind) {
319
+ case "json_schema": {
320
+ const parsed = parseJson(text);
321
+ if (!parsed.ok)
322
+ return fail("not_json");
323
+ return validateJsonSchema(check.schema, parsed.value) ? pass() : fail("schema_mismatch");
324
+ }
325
+ case "enum": {
326
+ let candidate;
327
+ if (check.path === "") {
328
+ const trimmed = text.trim();
329
+ const parsed = parseJson(trimmed);
330
+ candidate = parsed.ok && typeof parsed.value === "string" ? parsed.value : trimmed;
331
+ }
332
+ else {
333
+ const parsed = parseJson(text);
334
+ if (!parsed.ok)
335
+ return fail("not_json");
336
+ const at = valueAtPath(parsed.value, check.path);
337
+ if (!at.found)
338
+ return fail("path_missing");
339
+ candidate = at.value;
340
+ }
341
+ if (typeof candidate !== "string")
342
+ return fail("not_a_string");
343
+ return check.values.includes(candidate) ? pass() : fail("not_in_enum");
344
+ }
345
+ case "length": {
346
+ const tokens = Number.isInteger(input.outputTokens) && input.outputTokens >= 0 ? input.outputTokens : estimateTokens(text);
347
+ if (check.minTokens !== undefined && tokens < check.minTokens)
348
+ return fail("too_short");
349
+ if (check.maxTokens !== undefined && tokens > check.maxTokens)
350
+ return fail("too_long");
351
+ return pass();
352
+ }
353
+ case "must_match":
354
+ case "must_not_match": {
355
+ if (patternRefusal(check.pattern))
356
+ return fail("pattern_refused");
357
+ if (utf8Bytes(text) > CHECK_BOUNDS.subjectMaxBytes)
358
+ return fail("subject_too_long");
359
+ const matched = new RegExp(check.pattern, check.flags === "i" ? "iu" : "u").test(text);
360
+ if (check.kind === "must_match")
361
+ return matched ? pass() : fail("no_match");
362
+ return matched ? fail("matched") : pass();
363
+ }
364
+ default:
365
+ return fail("unknown_kind");
366
+ }
367
+ }
368
+ /** Every enabled check on one output: the window's counters and the per-check results. */
369
+ export function evaluateChecks(checks, input) {
370
+ const results = [];
371
+ for (const check of checks ?? []) {
372
+ if (check && check.enabled === false)
373
+ continue;
374
+ results.push(evaluateCheck(check, input));
375
+ }
376
+ return { passed: results.filter((r) => r.verdict === "pass").length, failed: results.filter((r) => r.verdict === "fail").length, results };
377
+ }
378
+ /** The pin / manifest projection: enabled checks only, sorted by name, the kind's own members only. */
379
+ export function projectChecks(checks) {
380
+ return (checks ?? [])
381
+ .filter((check) => check && check.enabled !== false)
382
+ .map((check) => {
383
+ switch (check.kind) {
384
+ case "json_schema":
385
+ return { kind: "json_schema", name: check.name, schema: check.schema };
386
+ case "enum":
387
+ return { kind: "enum", name: check.name, path: check.path, values: [...check.values] };
388
+ case "length":
389
+ return { kind: "length", name: check.name, ...(check.minTokens !== undefined ? { minTokens: check.minTokens } : {}), ...(check.maxTokens !== undefined ? { maxTokens: check.maxTokens } : {}) };
390
+ default:
391
+ return { kind: check.kind, name: check.name, pattern: check.pattern, ...(check.flags ? { flags: check.flags } : {}) };
392
+ }
393
+ })
394
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
395
+ }
396
+ /**
397
+ * The output text of whatever the provider answered, for the checks: a
398
+ * plain string; OpenAI `choices[0].message.content` (a string or content
399
+ * parts); Anthropic `content[].text`; Bedrock Converse
400
+ * `output.message.content[].text`. Null when nothing recognisable — then the
401
+ * checks are not run (never a failure invented from an unknown shape).
402
+ */
403
+ export function outputTextOf(result) {
404
+ if (typeof result === "string")
405
+ return result;
406
+ if (typeof result !== "object" || result === null)
407
+ return null;
408
+ const root = result;
409
+ const partsText = (parts) => {
410
+ if (typeof parts === "string")
411
+ return parts;
412
+ if (!Array.isArray(parts))
413
+ return null;
414
+ const texts = parts.map((part) => (typeof part === "object" && part !== null && typeof part.text === "string" ? part.text : null)).filter((t) => t !== null);
415
+ return texts.length ? texts.join("") : null;
416
+ };
417
+ const choice = Array.isArray(root.choices) ? root.choices[0] : undefined;
418
+ const message = choice && typeof choice.message === "object" && choice.message !== null ? choice.message : null;
419
+ if (message)
420
+ return partsText(message.content);
421
+ if (root.content !== undefined)
422
+ return partsText(root.content);
423
+ const output = typeof root.output === "object" && root.output !== null ? root.output : null;
424
+ const outMessage = output && typeof output.message === "object" && output.message !== null ? output.message : null;
425
+ if (outMessage)
426
+ return partsText(outMessage.content);
427
+ if (typeof root.output_text === "string")
428
+ return root.output_text;
429
+ // Responses API: output items (messages) whose content parts carry `text`.
430
+ if (Array.isArray(root.output)) {
431
+ const texts = root.output.map((item) => partsText(item?.content)).filter((t) => t !== null);
432
+ return texts.length ? texts.join("") : null;
433
+ }
434
+ return null;
435
+ }
436
+ //# sourceMappingURL=index.js.map