UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

128 lines 4.13 kB
// JCS (RFC 8785) canonicalization — TypeScript port of the Go reference at // agentic-sandbox-conformance/internal/spec/jcs.go. // // Sufficient for the AgentCard shape: objects, strings, integers, booleans, // null, arrays, and JSON numbers without exponential form. Numbers are emitted // in a form that matches serde_json output for integers and short decimals // (the only forms `agent_card::sign_agent_card` produces today). const encoder = new TextEncoder(); export function canonicalizeJsonBytes(input) { const text = typeof input === 'string' ? input : new TextDecoder('utf-8').decode(input); const value = JSON.parse(text); return canonicalizeJson(value); } export function canonicalizeJson(value) { const chunks = []; writeCanonical(chunks, value); return encoder.encode(chunks.join('')); } export function canonicalizeJsonString(value) { const chunks = []; writeCanonical(chunks, value); return chunks.join(''); } function writeCanonical(out, value) { if (value === null) { out.push('null'); return; } if (typeof value === 'boolean') { out.push(value ? 'true' : 'false'); return; } if (typeof value === 'string') { writeCanonicalString(out, value); return; } if (typeof value === 'number') { if (!Number.isFinite(value)) { throw new Error(`JCS: non-finite number ${value}`); } if (Number.isInteger(value)) { out.push(value.toString(10)); } else { // Shortest round-trip representation; matches Go strconv -1 precision // for the common cases in the AgentCard shape (it has no exotic doubles). out.push(String(value)); } return; } if (Array.isArray(value)) { out.push('['); for (let i = 0; i < value.length; i++) { if (i > 0) out.push(','); writeCanonical(out, value[i]); } out.push(']'); return; } if (typeof value === 'object') { const keys = Object.keys(value).sort(compareUtf16); out.push('{'); for (let i = 0; i < keys.length; i++) { if (i > 0) out.push(','); const k = keys[i]; writeCanonicalString(out, k); out.push(':'); writeCanonical(out, value[k]); } out.push('}'); return; } throw new Error(`JCS: unsupported type ${typeof value}`); } // Strings per RFC 8785 §3.2.2.2 / RFC 8259. Escape " \ and control chars (\u00xx). // Slash is NOT escaped. Non-ASCII characters are emitted as raw UTF-8. function writeCanonicalString(out, s) { out.push('"'); for (let i = 0; i < s.length; i++) { const ch = s.charCodeAt(i); switch (ch) { case 0x22: // " out.push('\\"'); break; case 0x5c: // \ out.push('\\\\'); break; case 0x08: out.push('\\b'); break; case 0x0c: out.push('\\f'); break; case 0x0a: out.push('\\n'); break; case 0x0d: out.push('\\r'); break; case 0x09: out.push('\\t'); break; default: if (ch < 0x20) { out.push('\\u' + ch.toString(16).padStart(4, '0')); } else { // Emit raw character; JS strings are already UTF-16 and TextEncoder // will produce UTF-8 bytes on encode. out.push(s[i]); } } } out.push('"'); } // Compare two strings by their UTF-16 code-unit sequences (RFC 8785 §3.2.3). // JS string comparison is already lexicographic over UTF-16 code units, so a // plain compare suffices. function compareUtf16(a, b) { if (a < b) return -1; if (a > b) return 1; return 0; } //# sourceMappingURL=jcs.js.map