@fitzdoud/valun 0.0.0-stage → 0.3.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/src/sha256.js ADDED
@@ -0,0 +1,52 @@
1
+ // SHA-256 (FIPS 180-4), synchronous and dependency-free, so terms hashes and statements can be built the same way in
2
+ // every runtime (Web Crypto's digest is async-only).
3
+
4
+ const K = new Uint32Array([
5
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
6
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
7
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
8
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
9
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
10
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
11
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
12
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
13
+ ]);
14
+
15
+ /** SHA-256 digest (32 bytes) of a byte array. */
16
+ export function sha256(data) {
17
+ const len = data.length;
18
+ const padded = new Uint8Array(((len + 9 + 63) >> 6) << 6);
19
+ padded.set(data);
20
+ padded[len] = 0x80;
21
+ const bits = len * 8;
22
+ const dv = new DataView(padded.buffer);
23
+ dv.setUint32(padded.length - 8, Math.floor(bits / 0x100000000));
24
+ dv.setUint32(padded.length - 4, bits >>> 0);
25
+
26
+ const H = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
27
+ const W = new Uint32Array(64);
28
+ for (let off = 0; off < padded.length; off += 64) {
29
+ for (let i = 0; i < 16; i++) W[i] = dv.getUint32(off + 4 * i);
30
+ for (let i = 16; i < 64; i++) {
31
+ const w15 = W[i - 15], w2 = W[i - 2];
32
+ const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);
33
+ const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);
34
+ W[i] = W[i - 16] + s0 + W[i - 7] + s1;
35
+ }
36
+ let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
37
+ for (let i = 0; i < 64; i++) {
38
+ const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
39
+ const ch = (e & f) ^ (~e & g);
40
+ const t1 = (h + S1 + ch + K[i] + W[i]) | 0;
41
+ const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
42
+ const maj = (a & b) ^ (a & c) ^ (b & c);
43
+ const t2 = (S0 + maj) | 0;
44
+ h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
45
+ }
46
+ H[0] += a; H[1] += b; H[2] += c; H[3] += d; H[4] += e; H[5] += f; H[6] += g; H[7] += h;
47
+ }
48
+ const out = new Uint8Array(32);
49
+ const ov = new DataView(out.buffer);
50
+ for (let i = 0; i < 8; i++) ov.setUint32(4 * i, H[i]);
51
+ return out;
52
+ }
package/src/tools.js ADDED
@@ -0,0 +1,112 @@
1
+ // The nine Valun tools in the shape most JavaScript agent frameworks accept: {name, description, parameters (JSON
2
+ // Schema), execute(args)}, with the owner's approval asked before every commitment (ValunAgent.isCommitment: an offer,
3
+ // an acceptance, a counter-offer, accepting a delivery, loosening a limit).
4
+ //
5
+ // Approvals fail closed: without an approve hook every commitment is refused (approval_unavailable) unless the caller
6
+ // passes approvals: 'none' explicitly. The Valun server enforces the owner's mandate in every mode.
7
+
8
+ import {APPROVAL_TEXT, errorPayload} from './agent.js';
9
+ import {ValunError} from './errors.js';
10
+ import {ALL} from './schemas.js';
11
+
12
+ /** What the model is told about approvals when no approve hook is given (valun_status shows it). */
13
+ export const NO_APPROVAL_STEP = 'Commitments cannot be signed here: this app has no way to ask your owner, so every offer, '
14
+ + 'counter-offer, acceptance, credit note and loosening of a limit is refused and nothing is signed. Reading, declining, '
15
+ + 'withdrawing, delivering and tightening limits still work.';
16
+
17
+ function approvalUnavailable(message) {
18
+ return {error: 'approval_unavailable', message,
19
+ what_this_means: 'Nothing was signed. Tell your owner that this app cannot ask them to approve Valun commitments; '
20
+ + 'it needs an approval step, or an explicit choice to act without asking within the owner\'s mandate.'};
21
+ }
22
+
23
+ const NO_HOOK = 'Not signed. This app asks the owner before every commitment, but it has no approval step (no approve hook was given).';
24
+
25
+ function deepFreeze(o) {
26
+ if (o && typeof o === 'object') {
27
+ for (const v of Object.values(o)) deepFreeze(v);
28
+ Object.freeze(o);
29
+ }
30
+ return o;
31
+ }
32
+
33
+ /** Tool arguments as plain JSON data: an object, a JSON string of one (raw tool-call arguments), or nothing. */
34
+ function plainArgs(raw) {
35
+ if (raw === undefined || raw === null || raw === '') return {};
36
+ const v = typeof raw === 'string' ? JSON.parse(raw) : JSON.parse(JSON.stringify(raw));
37
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) throw new TypeError('not a JSON object');
38
+ return v;
39
+ }
40
+
41
+ async function run(agent, name, raw, mode, approve) {
42
+ let args;
43
+ try {
44
+ args = plainArgs(raw);
45
+ } catch (e) {
46
+ return {error: 'bad_arguments', message: `arguments must be a JSON object: ${e?.message || e}`};
47
+ }
48
+ if (mode !== 'none') {
49
+ let commits;
50
+ try {
51
+ commits = await agent.isCommitment(name, args);
52
+ } catch {
53
+ commits = true; // when in doubt, it is a commitment
54
+ }
55
+ if (commits) {
56
+ if (mode !== 'ask') return approvalUnavailable(NO_HOOK);
57
+ try {
58
+ args = plainArgs(await agent.bind(name, args)); // the approval and the signature cover the same exact terms
59
+ } catch {
60
+ // binding is best effort (as in Python): the call itself reports real problems
61
+ }
62
+ let text;
63
+ try {
64
+ text = await agent.describeCommitment(name, args);
65
+ } catch {
66
+ text = `Valun: sign a commitment (${name}).`;
67
+ }
68
+ let answer;
69
+ try {
70
+ answer = await approve(text, name, deepFreeze(plainArgs(args)));
71
+ } catch (e) {
72
+ return approvalUnavailable(`Not signed: the approval step failed (${e?.message || String(e)}).`);
73
+ }
74
+ if (answer !== true) {
75
+ return errorPayload(new ValunError('owner_declined', 'your owner did not approve this commitment; nothing was signed'), agent.setupHint);
76
+ }
77
+ }
78
+ }
79
+ return agent.callSafe(name, args);
80
+ }
81
+
82
+ /**
83
+ * The nine tools of `agent` (a ValunAgent) as [{name, description, parameters, execute(args)}]. execute() takes the
84
+ * arguments as an object or as a JSON string, and always resolves to a plain object (an error payload on failure).
85
+ *
86
+ * options:
87
+ * approve async (commitmentText, name, args) => boolean, called before every commitment. The arguments are first
88
+ * bound (ValunAgent.bind: an acceptance pinned to the current terms hash or deliverable hash); the hook gets
89
+ * the text of describeCommitment() for them and a frozen copy of them, and exactly they are executed. Only
90
+ * `true` approves. Otherwise the call is refused with owner_declined; if the hook throws, with
91
+ * approval_unavailable.
92
+ * approvals 'none' to sign commitments without asking anyone (the server still enforces the owner's mandate). Without
93
+ * an approve hook and without 'none', every commitment is refused with approval_unavailable (fail closed).
94
+ * If agent.approvals (the sentence valun_status shows the model) is empty, it is set to match the mode chosen here.
95
+ */
96
+ export function valunTools(agent, options = {}) {
97
+ for (const m of ['callSafe', 'isCommitment', 'describeCommitment', 'bind']) {
98
+ if (typeof agent?.[m] !== 'function') throw new TypeError('valunTools needs a ValunAgent');
99
+ }
100
+ const {approve = null, approvals = null} = options;
101
+ if (approve !== null && typeof approve !== 'function') throw new TypeError('approve must be a function');
102
+ if (approvals !== null && approvals !== 'ask' && approvals !== 'none') throw new TypeError("approvals must be 'ask' or 'none'");
103
+ if (approvals === 'none' && approve) throw new TypeError("approvals: 'none' and an approve hook contradict each other");
104
+ const mode = approvals === 'none' ? 'none' : approve ? 'ask' : 'unavailable';
105
+ if (!agent.approvals) agent.approvals = mode === 'none' ? APPROVAL_TEXT.none : mode === 'ask' ? APPROVAL_TEXT.ask : NO_APPROVAL_STEP;
106
+ return ALL.map(def => ({
107
+ name: def.name,
108
+ description: def.description,
109
+ parameters: JSON.parse(JSON.stringify(def.parameters)),
110
+ execute: args => run(agent, def.name, args, mode, approve),
111
+ }));
112
+ }