const std = @import("std");const debug_trace = @import("debug_trace.zig");const display_width = @import("display_width.zig"); pub fn containsIgnoreCase(haystack: []const u8, needle: []const u8) bool { if (needle.len == 0) return true; if (needle.len > haystack.len) return false; const last_start = haystack.len - needle.len; var i: usize = 0; while (i <= last_start) : (i += 1) { if (std.ascii.eqlIgnoreCase(haystack[i .. i + needle.len], needle)) return true; } return false;} pub fn isModelSafeText(text: []const u8) bool { if (std.mem.findScalar(u8, text, 0) != null) return false; return std.unicode.utf8ValidateSlice(text);} pub fn utf8BackwardBoundary(text: []const u8, index: usize) usize { var len = @min(index, text.len); while (len > 0 and len < text.len and (text[len] & 0b1100_0000) == 0b1000_0000) { len -= 1; } return len;} pub fn utf8ForwardBoundary(text: []const u8, index: usize) usize { var len = @min(index, text.len); while (len < text.len and (text[len] & 0b1100_0000) == 0b1000_0000) { len += 1; } return len;} pub const HeadRounding = enum { down, up }; /// Writes text truncated to max_content_bytes as head + marker + tail with/// rune-safe cut points. The caller chooses how the retained budget splits/// between head and tail when it is odd.pub fn writeHeadTailBounded( writer: *std.Io.Writer, text: []const u8, max_content_bytes: usize, marker: []const u8, head_rounding: HeadRounding,) !void { if (text.len <= max_content_bytes) return writer.writeAll(text); if (max_content_bytes <= marker.len) { return writer.writeAll(marker[0..max_content_bytes]); } const retained_bytes = max_content_bytes - marker.len; const head_bytes = switch (head_rounding) { .down => retained_bytes / 2, .up => (retained_bytes + 1) / 2, }; const tail_bytes = retained_bytes - head_bytes; const head_end = utf8BackwardBoundary(text, head_bytes); const tail_start = utf8ForwardBoundary(text, text.len - tail_bytes); try writer.writeAll(text[0..head_end]); try writer.writeAll(marker); try writer.writeAll(text[tail_start..]);} pub fn utf8PrefixByBytes(text: []const u8, max_bytes: usize) []const u8 { return text[0..utf8BackwardBoundary(text, max_bytes)];} pub fn normalizeLineEndingsInPlace(bytes: []u8) []u8 { var read: usize = 0; var write: usize = 0; while (read < bytes.len) { if (bytes[read] == '\r') { bytes[write] = '\n'; write += 1; read += 1; if (read < bytes.len and bytes[read] == '\n') read += 1; continue; } bytes[write] = bytes[read]; write += 1; read += 1; } return bytes[0..write];} pub const EncodedText = struct { bytes: []u8, truncated: bool, pub fn deinit(self: *EncodedText, alloc: std.mem.Allocator) void { alloc.free(self.bytes); }}; /// Incrementally applies the same terminal-safe token policy as/// `encodeTerminalSafe`. It retains at most one unfinished UTF-8 token so a/// caller can process bounded file pages without exposing raw bytes.pub const IncrementalTerminalSafeEncoder = struct { pending: [4]u8 = undefined, pending_len: usize = 0, pub fn append( self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer, raw: []const u8, ) !void { var index: usize = 0; while (self.pending_len > 0) { if (try self.writePendingToken(writer)) continue; if (index == raw.len) return; self.pending[self.pending_len] = raw[index]; self.pending_len += 1; index += 1; } while (index < raw.len) { const sequence_len = std.unicode.utf8ByteSequenceLength(raw[index]) catch 1; if (raw.len - index < sequence_len) { const remaining = raw[index..]; @memcpy(self.pending[0..remaining.len], remaining); self.pending_len = remaining.len; return; } try writeTerminalSafeToken(writer, raw, index); var token_buf: [12]u8 = undefined; index += terminalSafeToken(raw, index, &token_buf).source_len; } } pub fn finish(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !void { while (self.pending_len > 0) { try self.emitPendingToken(writer); } } fn writePendingToken(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !bool { const sequence_len = std.unicode.utf8ByteSequenceLength(self.pending[0]) catch 1; if (self.pending_len < sequence_len) return false; try self.emitPendingToken(writer); return true; } fn emitPendingToken(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !void { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(self.pending[0..self.pending_len], 0, &token_buf); try writer.writeAll(token.bytes); const remaining = self.pending_len - token.source_len; if (remaining > 0) { std.mem.copyForwards( u8, self.pending[0..remaining], self.pending[token.source_len..self.pending_len], ); } self.pending_len = remaining; }}; /// Validates UTF-8 across arbitrary chunks and rejects an incomplete final sequence.pub const IncrementalUtf8Validator = struct { pending: [4]u8 = undefined, pending_len: usize = 0, pub fn append(self: *IncrementalUtf8Validator, bytes: []const u8) error{InvalidUtf8}!void { var index: usize = 0; if (self.pending_len > 0) { const sequence_len = std.unicode.utf8ByteSequenceLength(self.pending[0]) catch return error.InvalidUtf8; const copied = @min(sequence_len - self.pending_len, bytes.len); @memcpy(self.pending[self.pending_len .. self.pending_len + copied], bytes[0..copied]); self.pending_len += copied; index += copied; if (self.pending_len < sequence_len) return; _ = std.unicode.utf8Decode(self.pending[0..sequence_len]) catch return error.InvalidUtf8; self.pending_len = 0; } while (index < bytes.len) { const sequence_len = std.unicode.utf8ByteSequenceLength(bytes[index]) catch return error.InvalidUtf8; if (bytes.len - index < sequence_len) { const remaining = bytes[index..]; @memcpy(self.pending[0..remaining.len], remaining); self.pending_len = remaining.len; return; } _ = std.unicode.utf8Decode(bytes[index .. index + sequence_len]) catch return error.InvalidUtf8; index += sequence_len; } } pub fn finish(self: *IncrementalUtf8Validator) error{InvalidUtf8}!void { if (self.pending_len != 0) return error.InvalidUtf8; }}; pub const EncodedPathTail = struct { bytes: []u8, basename_start: usize, source_truncated: bool, pub fn deinit(self: *EncodedPathTail, alloc: std.mem.Allocator) void { alloc.free(self.bytes); }}; pub fn encodeTerminalSafePathTail( alloc: std.mem.Allocator, raw: []const u8, max_encoded_bytes: usize,) error{ OutOfMemory, PathBasenameTooLong }!EncodedPathTail { const basename = std.fs.path.basename(raw); if (basename.len == 0) return error.PathBasenameTooLong; const basename_source_start = raw.len - basename.len; var encoded: std.ArrayList(u8) = .empty; defer encoded.deinit(alloc); var boundaries: std.ArrayList(usize) = .empty; defer boundaries.deinit(alloc); try boundaries.append(alloc, 0); var encoded_basename_start: ?usize = null; var source_index: usize = 0; while (source_index < raw.len) { if (source_index == basename_source_start) { encoded_basename_start = encoded.items.len; } var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, source_index, &token_buf); try encoded.appendSlice(alloc, token.bytes); source_index += token.source_len; try boundaries.append(alloc, encoded.items.len); } const basename_start = encoded_basename_start orelse return error.PathBasenameTooLong; const basename_bytes = encoded.items.len - basename_start; if (basename_bytes > max_encoded_bytes) return error.PathBasenameTooLong; if (encoded.items.len <= max_encoded_bytes) { return .{ .bytes = try encoded.toOwnedSlice(alloc), .basename_start = basename_start, .source_truncated = false, }; } const marker = "…"; if (marker.len + basename_bytes > max_encoded_bytes) { return error.PathBasenameTooLong; } const suffix_budget = max_encoded_bytes - marker.len; var suffix_start: ?usize = null; for (boundaries.items) |boundary| { if (encoded.items.len - boundary <= suffix_budget) { suffix_start = boundary; break; } } const start = suffix_start orelse return error.PathBasenameTooLong; if (start > basename_start) return error.PathBasenameTooLong; const output = try alloc.alloc( u8, marker.len + encoded.items.len - start, ); @memcpy(output[0..marker.len], marker); @memcpy(output[marker.len..], encoded.items[start..]); return .{ .bytes = output, .basename_start = marker.len + basename_start - start, .source_truncated = true, };} pub fn suffixTerminalSafeByWidth( encoded: []const u8, max_width: usize,) []const u8 { var width = display_width.visibleWidth(encoded); if (width <= max_width) return encoded; var start: usize = 0; while (start < encoded.len and width > max_width) { const token_len = terminalSafeEncodedTokenLen(encoded[start..]); const token = encoded[start .. start + token_len]; width -|= display_width.visibleWidth(token); start += token_len; } return encoded[start..];} pub fn prefixTerminalSafeByWidth( encoded: []const u8, max_width: usize,) []const u8 { var width: usize = 0; var end: usize = 0; while (end < encoded.len) { const token_len = terminalSafeEncodedTokenLen(encoded[end..]); const token_end = @min(encoded.len, end + token_len); const token = encoded[end..token_end]; const token_width = display_width.visibleWidth(token); if (width + token_width > max_width) break; width += token_width; end = token_end; } return encoded[0..end];} fn terminalSafeEncodedTokenLen(encoded: []const u8) usize { if (encoded.len >= 4 and encoded[0] == '\\' and encoded[1] == 'x' and std.ascii.isHex(encoded[2]) and std.ascii.isHex(encoded[3])) { return 4; } if (encoded.len >= 5 and encoded[0] == '\\' and encoded[1] == 'u' and encoded[2] == '{') { var index: usize = 3; var saw_hex = false; while (index < encoded.len and index < 12) : (index += 1) { if (encoded[index] == '}') { if (saw_hex) return index + 1; break; } if (!std.ascii.isHex(encoded[index])) break; saw_hex = true; } } return @min( encoded.len, std.unicode.utf8ByteSequenceLength(encoded[0]) catch 1, );} pub fn encodeTerminalSafe( alloc: std.mem.Allocator, raw: []const u8, max_encoded_bytes: usize,) error{OutOfMemory}!EncodedText { const marker = "..."; const marker_len = @min(marker.len, max_encoded_bytes); const content_limit = max_encoded_bytes - marker_len; var bytes: std.ArrayList(u8) = .empty; errdefer bytes.deinit(alloc); var marker_boundary: usize = 0; var truncated = false; var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); const next_len = std.math.add(usize, bytes.items.len, token.bytes.len) catch { truncated = true; break; }; if (next_len > max_encoded_bytes) { truncated = true; break; } try bytes.appendSlice(alloc, token.bytes); index += token.source_len; if (next_len <= content_limit) marker_boundary = next_len; } if (truncated) { bytes.items.len = marker_boundary; try bytes.appendSlice(alloc, marker[0..marker_len]); } return .{ .bytes = try bytes.toOwnedSlice(alloc), .truncated = truncated, };} /// Returns true when `raw` is valid printable terminal text and/// `encodeTerminalSafe` would preserve it byte-for-byte.pub fn isTerminalSafe(raw: []const u8) bool { var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); if (token.source_len != token.bytes.len) return false; if (!std.mem.eql(u8, raw[index .. index + token.source_len], token.bytes)) return false; index += token.source_len; } return true;} pub fn terminalSafeVisibleWidth(raw: []const u8) usize { var width: usize = 0; var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); width += display_width.visibleWidth(token.bytes); index += token.source_len; } return width;} pub fn clippedLabel(buf: []u8, text: []const u8, max_len: usize) []const u8 { if (buf.len == 0 or max_len == 0) return ""; if (display_width.visibleWidth(text) <= max_len) return text; const keep_limit = if (max_len > 3) max_len - 3 else 0; const prefix = display_width.prefixByWidth(text, keep_limit); const keep = @min(prefix.len, buf.len); if (keep == 0) return display_width.prefixByWidth(text, max_len); @memcpy(buf[0..keep], prefix[0..keep]); if (keep + 3 <= buf.len) { @memcpy(buf[keep .. keep + 3], "..."); return buf[0 .. keep + 3]; } return buf[0..keep];} pub fn sanitizeModelText(arena: std.mem.Allocator, text: []const u8) ![]const u8 { if (isModelSafeText(text)) return text; return std.fmt.allocPrint(arena, "binary or non-utf8 tool output omitted ({d} bytes)", .{text.len});} pub fn maskSecrets(arena: std.mem.Allocator, text: []const u8) ![]const u8 { var out: std.Io.Writer.Allocating = .init(arena); defer out.deinit(); var modified = false; var i: usize = 0; while (i < text.len) { if (findSecretSpan(text, i)) |span| { modified = true; try out.writer.writeAll(text[i..span.prefix_end]); try out.writer.writeAll("[redacted]"); debug_trace.logf("core", "model-facing secret redacted kind={s} bytes={d}", .{ span.kind, span.value_len }); i = span.prefix_end + span.value_len; } else { try out.writer.writeByte(text[i]); i += 1; } } if (!modified) return text; return try out.toOwnedSlice();} pub fn redactUrlForDisplay(alloc: std.mem.Allocator, raw_url: []const u8) error{OutOfMemory}![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); const scheme_end = std.mem.find(u8, raw_url, "://"); var query_search_start: usize = 0; if (scheme_end) |end| { const authority_start = end + "://".len; const authority_end = firstDelimiter(raw_url, authority_start, "/?#"); if (std.mem.findScalar(u8, raw_url[authority_start..authority_end], '@')) |at| { out.writer.writeAll(raw_url[0..authority_start]) catch return error.OutOfMemory; out.writer.writeAll("[redacted]@") catch return error.OutOfMemory; out.writer.writeAll(raw_url[authority_start + at + 1 .. authority_end]) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[0..authority_end]) catch return error.OutOfMemory; } query_search_start = authority_end; } if (scheme_end == null) query_search_start = 0; const query_marker = std.mem.findScalarPos(u8, raw_url, query_search_start, '?') orelse { if (scheme_end == null) { out.writer.writeAll(raw_url) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[query_search_start..]) catch return error.OutOfMemory; } return out.toOwnedSlice() catch return error.OutOfMemory; }; if (scheme_end == null) { out.writer.writeAll(raw_url[0 .. query_marker + 1]) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[query_search_start .. query_marker + 1]) catch return error.OutOfMemory; } const query_end = std.mem.findScalarPos(u8, raw_url, query_marker + 1, '#') orelse raw_url.len; var segment_start = query_marker + 1; while (segment_start < query_end) { const segment_end = std.mem.findScalarPos(u8, raw_url, segment_start, '&') orelse query_end; const equals = std.mem.findScalarPos(u8, raw_url, segment_start, '=') orelse segment_end; out.writer.writeAll(raw_url[segment_start..equals]) catch return error.OutOfMemory; if (equals < segment_end) { out.writer.writeByte('=') catch return error.OutOfMemory; if (sensitiveUrlQueryKey(raw_url[segment_start..equals])) { out.writer.writeAll("[redacted]") catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[equals + 1 .. segment_end]) catch return error.OutOfMemory; } } if (segment_end < query_end) out.writer.writeByte('&') catch return error.OutOfMemory; segment_start = segment_end + 1;Lines 1–500 of 1286. Load more
const std = @import("std"); const debug_trace = @import("debug_trace.zig"); const display_width = @import("display_width.zig"); pub fn containsIgnoreCase(haystack: []const u8, needle: []const u8) bool { if (needle.len == 0) return true; if (needle.len > haystack.len) return false; const last_start = haystack.len - needle.len; var i: usize = 0; while (i <= last_start) : (i += 1) { if (std.ascii.eqlIgnoreCase(haystack[i .. i + needle.len], needle)) return true; } return false; } pub fn isModelSafeText(text: []const u8) bool { if (std.mem.findScalar(u8, text, 0) != null) return false; return std.unicode.utf8ValidateSlice(text); } pub fn utf8BackwardBoundary(text: []const u8, index: usize) usize { var len = @min(index, text.len); while (len > 0 and len < text.len and (text[len] & 0b1100_0000) == 0b1000_0000) { len -= 1; } return len; } pub fn utf8ForwardBoundary(text: []const u8, index: usize) usize { var len = @min(index, text.len); while (len < text.len and (text[len] & 0b1100_0000) == 0b1000_0000) { len += 1; } return len; } pub const HeadRounding = enum { down, up }; /// Writes text truncated to max_content_bytes as head + marker + tail with /// rune-safe cut points. The caller chooses how the retained budget splits /// between head and tail when it is odd. pub fn writeHeadTailBounded( writer: *std.Io.Writer, text: []const u8, max_content_bytes: usize, marker: []const u8, head_rounding: HeadRounding, ) !void { if (text.len <= max_content_bytes) return writer.writeAll(text); if (max_content_bytes <= marker.len) { return writer.writeAll(marker[0..max_content_bytes]); } const retained_bytes = max_content_bytes - marker.len; const head_bytes = switch (head_rounding) { .down => retained_bytes / 2, .up => (retained_bytes + 1) / 2, }; const tail_bytes = retained_bytes - head_bytes; const head_end = utf8BackwardBoundary(text, head_bytes); const tail_start = utf8ForwardBoundary(text, text.len - tail_bytes); try writer.writeAll(text[0..head_end]); try writer.writeAll(marker); try writer.writeAll(text[tail_start..]); } pub fn utf8PrefixByBytes(text: []const u8, max_bytes: usize) []const u8 { return text[0..utf8BackwardBoundary(text, max_bytes)]; } pub fn normalizeLineEndingsInPlace(bytes: []u8) []u8 { var read: usize = 0; var write: usize = 0; while (read < bytes.len) { if (bytes[read] == '\r') { bytes[write] = '\n'; write += 1; read += 1; if (read < bytes.len and bytes[read] == '\n') read += 1; continue; } bytes[write] = bytes[read]; write += 1; read += 1; } return bytes[0..write]; } pub const EncodedText = struct { bytes: []u8, truncated: bool, pub fn deinit(self: *EncodedText, alloc: std.mem.Allocator) void { alloc.free(self.bytes); } }; /// Incrementally applies the same terminal-safe token policy as /// `encodeTerminalSafe`. It retains at most one unfinished UTF-8 token so a /// caller can process bounded file pages without exposing raw bytes. pub const IncrementalTerminalSafeEncoder = struct { pending: [4]u8 = undefined, pending_len: usize = 0, pub fn append( self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer, raw: []const u8, ) !void { var index: usize = 0; while (self.pending_len > 0) { if (try self.writePendingToken(writer)) continue; if (index == raw.len) return; self.pending[self.pending_len] = raw[index]; self.pending_len += 1; index += 1; } while (index < raw.len) { const sequence_len = std.unicode.utf8ByteSequenceLength(raw[index]) catch 1; if (raw.len - index < sequence_len) { const remaining = raw[index..]; @memcpy(self.pending[0..remaining.len], remaining); self.pending_len = remaining.len; return; } try writeTerminalSafeToken(writer, raw, index); var token_buf: [12]u8 = undefined; index += terminalSafeToken(raw, index, &token_buf).source_len; } } pub fn finish(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !void { while (self.pending_len > 0) { try self.emitPendingToken(writer); } } fn writePendingToken(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !bool { const sequence_len = std.unicode.utf8ByteSequenceLength(self.pending[0]) catch 1; if (self.pending_len < sequence_len) return false; try self.emitPendingToken(writer); return true; } fn emitPendingToken(self: *IncrementalTerminalSafeEncoder, writer: *std.Io.Writer) !void { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(self.pending[0..self.pending_len], 0, &token_buf); try writer.writeAll(token.bytes); const remaining = self.pending_len - token.source_len; if (remaining > 0) { std.mem.copyForwards( u8, self.pending[0..remaining], self.pending[token.source_len..self.pending_len], ); } self.pending_len = remaining; } }; /// Validates UTF-8 across arbitrary chunks and rejects an incomplete final sequence. pub const IncrementalUtf8Validator = struct { pending: [4]u8 = undefined, pending_len: usize = 0, pub fn append(self: *IncrementalUtf8Validator, bytes: []const u8) error{InvalidUtf8}!void { var index: usize = 0; if (self.pending_len > 0) { const sequence_len = std.unicode.utf8ByteSequenceLength(self.pending[0]) catch return error.InvalidUtf8; const copied = @min(sequence_len - self.pending_len, bytes.len); @memcpy(self.pending[self.pending_len .. self.pending_len + copied], bytes[0..copied]); self.pending_len += copied; index += copied; if (self.pending_len < sequence_len) return; _ = std.unicode.utf8Decode(self.pending[0..sequence_len]) catch return error.InvalidUtf8; self.pending_len = 0; } while (index < bytes.len) { const sequence_len = std.unicode.utf8ByteSequenceLength(bytes[index]) catch return error.InvalidUtf8; if (bytes.len - index < sequence_len) { const remaining = bytes[index..]; @memcpy(self.pending[0..remaining.len], remaining); self.pending_len = remaining.len; return; } _ = std.unicode.utf8Decode(bytes[index .. index + sequence_len]) catch return error.InvalidUtf8; index += sequence_len; } } pub fn finish(self: *IncrementalUtf8Validator) error{InvalidUtf8}!void { if (self.pending_len != 0) return error.InvalidUtf8; } }; pub const EncodedPathTail = struct { bytes: []u8, basename_start: usize, source_truncated: bool, pub fn deinit(self: *EncodedPathTail, alloc: std.mem.Allocator) void { alloc.free(self.bytes); } }; pub fn encodeTerminalSafePathTail( alloc: std.mem.Allocator, raw: []const u8, max_encoded_bytes: usize, ) error{ OutOfMemory, PathBasenameTooLong }!EncodedPathTail { const basename = std.fs.path.basename(raw); if (basename.len == 0) return error.PathBasenameTooLong; const basename_source_start = raw.len - basename.len; var encoded: std.ArrayList(u8) = .empty; defer encoded.deinit(alloc); var boundaries: std.ArrayList(usize) = .empty; defer boundaries.deinit(alloc); try boundaries.append(alloc, 0); var encoded_basename_start: ?usize = null; var source_index: usize = 0; while (source_index < raw.len) { if (source_index == basename_source_start) { encoded_basename_start = encoded.items.len; } var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, source_index, &token_buf); try encoded.appendSlice(alloc, token.bytes); source_index += token.source_len; try boundaries.append(alloc, encoded.items.len); } const basename_start = encoded_basename_start orelse return error.PathBasenameTooLong; const basename_bytes = encoded.items.len - basename_start; if (basename_bytes > max_encoded_bytes) return error.PathBasenameTooLong; if (encoded.items.len <= max_encoded_bytes) { return .{ .bytes = try encoded.toOwnedSlice(alloc), .basename_start = basename_start, .source_truncated = false, }; } const marker = "…"; if (marker.len + basename_bytes > max_encoded_bytes) { return error.PathBasenameTooLong; } const suffix_budget = max_encoded_bytes - marker.len; var suffix_start: ?usize = null; for (boundaries.items) |boundary| { if (encoded.items.len - boundary <= suffix_budget) { suffix_start = boundary; break; } } const start = suffix_start orelse return error.PathBasenameTooLong; if (start > basename_start) return error.PathBasenameTooLong; const output = try alloc.alloc( u8, marker.len + encoded.items.len - start, ); @memcpy(output[0..marker.len], marker); @memcpy(output[marker.len..], encoded.items[start..]); return .{ .bytes = output, .basename_start = marker.len + basename_start - start, .source_truncated = true, }; } pub fn suffixTerminalSafeByWidth( encoded: []const u8, max_width: usize, ) []const u8 { var width = display_width.visibleWidth(encoded); if (width <= max_width) return encoded; var start: usize = 0; while (start < encoded.len and width > max_width) { const token_len = terminalSafeEncodedTokenLen(encoded[start..]); const token = encoded[start .. start + token_len]; width -|= display_width.visibleWidth(token); start += token_len; } return encoded[start..]; } pub fn prefixTerminalSafeByWidth( encoded: []const u8, max_width: usize, ) []const u8 { var width: usize = 0; var end: usize = 0; while (end < encoded.len) { const token_len = terminalSafeEncodedTokenLen(encoded[end..]); const token_end = @min(encoded.len, end + token_len); const token = encoded[end..token_end]; const token_width = display_width.visibleWidth(token); if (width + token_width > max_width) break; width += token_width; end = token_end; } return encoded[0..end]; } fn terminalSafeEncodedTokenLen(encoded: []const u8) usize { if (encoded.len >= 4 and encoded[0] == '\\' and encoded[1] == 'x' and std.ascii.isHex(encoded[2]) and std.ascii.isHex(encoded[3])) { return 4; } if (encoded.len >= 5 and encoded[0] == '\\' and encoded[1] == 'u' and encoded[2] == '{') { var index: usize = 3; var saw_hex = false; while (index < encoded.len and index < 12) : (index += 1) { if (encoded[index] == '}') { if (saw_hex) return index + 1; break; } if (!std.ascii.isHex(encoded[index])) break; saw_hex = true; } } return @min( encoded.len, std.unicode.utf8ByteSequenceLength(encoded[0]) catch 1, ); } pub fn encodeTerminalSafe( alloc: std.mem.Allocator, raw: []const u8, max_encoded_bytes: usize, ) error{OutOfMemory}!EncodedText { const marker = "..."; const marker_len = @min(marker.len, max_encoded_bytes); const content_limit = max_encoded_bytes - marker_len; var bytes: std.ArrayList(u8) = .empty; errdefer bytes.deinit(alloc); var marker_boundary: usize = 0; var truncated = false; var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); const next_len = std.math.add(usize, bytes.items.len, token.bytes.len) catch { truncated = true; break; }; if (next_len > max_encoded_bytes) { truncated = true; break; } try bytes.appendSlice(alloc, token.bytes); index += token.source_len; if (next_len <= content_limit) marker_boundary = next_len; } if (truncated) { bytes.items.len = marker_boundary; try bytes.appendSlice(alloc, marker[0..marker_len]); } return .{ .bytes = try bytes.toOwnedSlice(alloc), .truncated = truncated, }; } /// Returns true when `raw` is valid printable terminal text and /// `encodeTerminalSafe` would preserve it byte-for-byte. pub fn isTerminalSafe(raw: []const u8) bool { var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); if (token.source_len != token.bytes.len) return false; if (!std.mem.eql(u8, raw[index .. index + token.source_len], token.bytes)) return false; index += token.source_len; } return true; } pub fn terminalSafeVisibleWidth(raw: []const u8) usize { var width: usize = 0; var index: usize = 0; while (index < raw.len) { var token_buf: [12]u8 = undefined; const token = terminalSafeToken(raw, index, &token_buf); width += display_width.visibleWidth(token.bytes); index += token.source_len; } return width; } pub fn clippedLabel(buf: []u8, text: []const u8, max_len: usize) []const u8 { if (buf.len == 0 or max_len == 0) return ""; if (display_width.visibleWidth(text) <= max_len) return text; const keep_limit = if (max_len > 3) max_len - 3 else 0; const prefix = display_width.prefixByWidth(text, keep_limit); const keep = @min(prefix.len, buf.len); if (keep == 0) return display_width.prefixByWidth(text, max_len); @memcpy(buf[0..keep], prefix[0..keep]); if (keep + 3 <= buf.len) { @memcpy(buf[keep .. keep + 3], "..."); return buf[0 .. keep + 3]; } return buf[0..keep]; } pub fn sanitizeModelText(arena: std.mem.Allocator, text: []const u8) ![]const u8 { if (isModelSafeText(text)) return text; return std.fmt.allocPrint(arena, "binary or non-utf8 tool output omitted ({d} bytes)", .{text.len}); } pub fn maskSecrets(arena: std.mem.Allocator, text: []const u8) ![]const u8 { var out: std.Io.Writer.Allocating = .init(arena); defer out.deinit(); var modified = false; var i: usize = 0; while (i < text.len) { if (findSecretSpan(text, i)) |span| { modified = true; try out.writer.writeAll(text[i..span.prefix_end]); try out.writer.writeAll("[redacted]"); debug_trace.logf("core", "model-facing secret redacted kind={s} bytes={d}", .{ span.kind, span.value_len }); i = span.prefix_end + span.value_len; } else { try out.writer.writeByte(text[i]); i += 1; } } if (!modified) return text; return try out.toOwnedSlice(); } pub fn redactUrlForDisplay(alloc: std.mem.Allocator, raw_url: []const u8) error{OutOfMemory}![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); errdefer out.deinit(); const scheme_end = std.mem.find(u8, raw_url, "://"); var query_search_start: usize = 0; if (scheme_end) |end| { const authority_start = end + "://".len; const authority_end = firstDelimiter(raw_url, authority_start, "/?#"); if (std.mem.findScalar(u8, raw_url[authority_start..authority_end], '@')) |at| { out.writer.writeAll(raw_url[0..authority_start]) catch return error.OutOfMemory; out.writer.writeAll("[redacted]@") catch return error.OutOfMemory; out.writer.writeAll(raw_url[authority_start + at + 1 .. authority_end]) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[0..authority_end]) catch return error.OutOfMemory; } query_search_start = authority_end; } if (scheme_end == null) query_search_start = 0; const query_marker = std.mem.findScalarPos(u8, raw_url, query_search_start, '?') orelse { if (scheme_end == null) { out.writer.writeAll(raw_url) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[query_search_start..]) catch return error.OutOfMemory; } return out.toOwnedSlice() catch return error.OutOfMemory; }; if (scheme_end == null) { out.writer.writeAll(raw_url[0 .. query_marker + 1]) catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[query_search_start .. query_marker + 1]) catch return error.OutOfMemory; } const query_end = std.mem.findScalarPos(u8, raw_url, query_marker + 1, '#') orelse raw_url.len; var segment_start = query_marker + 1; while (segment_start < query_end) { const segment_end = std.mem.findScalarPos(u8, raw_url, segment_start, '&') orelse query_end; const equals = std.mem.findScalarPos(u8, raw_url, segment_start, '=') orelse segment_end; out.writer.writeAll(raw_url[segment_start..equals]) catch return error.OutOfMemory; if (equals < segment_end) { out.writer.writeByte('=') catch return error.OutOfMemory; if (sensitiveUrlQueryKey(raw_url[segment_start..equals])) { out.writer.writeAll("[redacted]") catch return error.OutOfMemory; } else { out.writer.writeAll(raw_url[equals + 1 .. segment_end]) catch return error.OutOfMemory; } } if (segment_end < query_end) out.writer.writeByte('&') catch return error.OutOfMemory; segment_start = segment_end + 1;