Hashline editing: content-hash anchors for edit_file stale-patch detection.
Pure-JS xxHash32, line hash computation, validation with HashlineMismatchError,
256-entry hash dictionary. 6 files in apps/coder/src/services/hashline/.
Audit hooks: emitHook('tool.execute.after') wired in frame-emitter.ts for
completed/failed tool results. emitHook('turn.end') wired at terminal points
in dispatcher.ts (all 5 run functions: native, external, opencode, warm ACP,
claude SDK). Fire-and-forget, non-blocking.
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
type BunHashRuntime = { hash: { xxHash32(data: string | Uint8Array, seed: number): number } }
|
|
|
|
const runtime = globalThis as typeof globalThis & { Bun?: BunHashRuntime }
|
|
const encoder = new TextEncoder()
|
|
|
|
const PRIME32_1 = 0x9e3779b1
|
|
const PRIME32_2 = 0x85ebca77
|
|
const PRIME32_3 = 0xc2b2ae3d
|
|
const PRIME32_4 = 0x27d4eb2f
|
|
const PRIME32_5 = 0x165667b1
|
|
|
|
function rotateLeft32(value: number, bits: number): number {
|
|
return ((value << bits) | (value >>> (32 - bits))) >>> 0
|
|
}
|
|
|
|
function readUint32LittleEndian(input: Uint8Array, offset: number): number {
|
|
return (
|
|
((input[offset] ?? 0) |
|
|
((input[offset + 1] ?? 0) << 8) |
|
|
((input[offset + 2] ?? 0) << 16) |
|
|
((input[offset + 3] ?? 0) << 24)) >>>
|
|
0
|
|
)
|
|
}
|
|
|
|
function round32(accumulator: number, value: number): number {
|
|
const added = (accumulator + Math.imul(value, PRIME32_2)) >>> 0
|
|
return Math.imul(rotateLeft32(added, 13), PRIME32_1) >>> 0
|
|
}
|
|
|
|
function xxHash32Js(input: Uint8Array, seed: number): number {
|
|
let offset = 0
|
|
const length = input.length
|
|
let hash: number
|
|
|
|
if (length >= 16) {
|
|
const limit = length - 16
|
|
let value1 = (seed + PRIME32_1 + PRIME32_2) >>> 0
|
|
let value2 = (seed + PRIME32_2) >>> 0
|
|
let value3 = seed >>> 0
|
|
let value4 = (seed - PRIME32_1) >>> 0
|
|
|
|
while (offset <= limit) {
|
|
value1 = round32(value1, readUint32LittleEndian(input, offset))
|
|
offset += 4
|
|
value2 = round32(value2, readUint32LittleEndian(input, offset))
|
|
offset += 4
|
|
value3 = round32(value3, readUint32LittleEndian(input, offset))
|
|
offset += 4
|
|
value4 = round32(value4, readUint32LittleEndian(input, offset))
|
|
offset += 4
|
|
}
|
|
|
|
hash = (rotateLeft32(value1, 1) + rotateLeft32(value2, 7)) >>> 0
|
|
hash = (hash + rotateLeft32(value3, 12)) >>> 0
|
|
hash = (hash + rotateLeft32(value4, 18)) >>> 0
|
|
} else {
|
|
hash = (seed + PRIME32_5) >>> 0
|
|
}
|
|
|
|
hash = (hash + length) >>> 0
|
|
|
|
while (offset + 4 <= length) {
|
|
hash = (hash + Math.imul(readUint32LittleEndian(input, offset), PRIME32_3)) >>> 0
|
|
hash = Math.imul(rotateLeft32(hash, 17), PRIME32_4) >>> 0
|
|
offset += 4
|
|
}
|
|
|
|
while (offset < length) {
|
|
hash = (hash + Math.imul(input[offset] ?? 0, PRIME32_5)) >>> 0
|
|
hash = Math.imul(rotateLeft32(hash, 11), PRIME32_1) >>> 0
|
|
offset += 1
|
|
}
|
|
|
|
hash = (hash ^ (hash >>> 15)) >>> 0
|
|
hash = Math.imul(hash, PRIME32_2) >>> 0
|
|
hash = (hash ^ (hash >>> 13)) >>> 0
|
|
hash = Math.imul(hash, PRIME32_3) >>> 0
|
|
|
|
return (hash ^ (hash >>> 16)) >>> 0
|
|
}
|
|
|
|
export function hashXxh32(input: string, seed: number): number {
|
|
const bun = runtime.Bun
|
|
if (bun !== undefined) {
|
|
return bun.hash.xxHash32(input, seed)
|
|
}
|
|
|
|
return xxHash32Js(encoder.encode(input), seed >>> 0)
|
|
}
|