PranitInitial commit439f83c3 months agoHistory
312 lines · 12.6 KB · Zig
RawDownload
const std = @import("std");const server_detection = @import("../../core/background/server_detection.zig");const io_mod = @import("../../core/shared/io.zig");const tool_dispatch = @import("../../core/tooling/tool_dispatch.zig");const pathing = @import("../../core/workspace/pathing.zig");const workspace_access = @import("../../core/workspace/workspace_access.zig"); const Allocator = std.mem.Allocator; /// Typed input for the core run_command tool.pub const Input = struct {    command: []u8,    cwd: ?[]u8 = null,    background: bool = false,     /// Frees owned command and optional cwd strings.    pub fn deinit(self: *Input, alloc: Allocator) void {        alloc.free(self.command);        if (self.cwd) |cwd| alloc.free(cwd);        self.* = .{ .command = &.{} };    }}; /// Decodes run_command JSON into an owned Input released by ToolInput.deinit.pub fn decode(ctx: tool_dispatch.DispatchContext, args_json: []const u8) tool_dispatch.DispatchError!tool_dispatch.DecodeResult {    var parsed = std.json.parseFromSlice(std.json.Value, ctx.allocator, args_json, .{}) catch {        return .{ .failure = try ctx.allocator.dupe(u8, "run_command arguments must be valid JSON") };    };    defer parsed.deinit();     if (parsed.value != .object) {        return .{ .failure = try ctx.allocator.dupe(u8, "run_command arguments must be an object") };    }     const command_value = parsed.value.object.get("command") orelse {        return .{ .failure = try ctx.allocator.dupe(u8, "run_command requires string field \"command\"") };    };    if (command_value != .string) {        return .{ .failure = try ctx.allocator.dupe(u8, "run_command field \"command\" must be a string") };    }     const cwd_raw: ?[]const u8 = if (parsed.value.object.get("cwd")) |cwd_value| blk: {        if (cwd_value != .string) {            return .{ .failure = try ctx.allocator.dupe(u8, "run_command field \"cwd\" must be a string") };        }        break :blk cwd_value.string;    } else null;     var run_in_background = false;    if (parsed.value.object.get("background")) |background_value| {        if (background_value != .bool) {            return .{ .failure = try ctx.allocator.dupe(u8, "run_command field \"background\" must be a bool") };        }        run_in_background = background_value.bool;    }     const command = try ctx.allocator.dupe(u8, command_value.string);    errdefer ctx.allocator.free(command);    const cwd: ?[]u8 = if (cwd_raw) |raw| try ctx.allocator.dupe(u8, raw) else null;    errdefer if (cwd) |owned| ctx.allocator.free(owned);    const input = try ctx.allocator.create(Input);    errdefer ctx.allocator.destroy(input);    input.* = .{        .command = command,        .cwd = cwd,        .background = run_in_background,    };     return .{ .input = .{ .ptr = input, .deinit_fn = inputDeinit } };} fn inputDeinit(ptr: *anyopaque, alloc: Allocator) void {    const input: *Input = @ptrCast(@alignCast(ptr));    input.deinit(alloc);    alloc.destroy(input);} /// Leaves the active run_command public contract to the runtime.pub fn validate(_: tool_dispatch.DispatchContext, _: tool_dispatch.ToolInput) tool_dispatch.DispatchError!?[]u8 {    return null;} /// Resolves the command cwd using the same rule as the runner and permission target.pub fn resolveCwd(arena: Allocator, scope: workspace_access.AccessScope, input: *const Input) ![]const u8 {    const cwd_input = input.cwd orelse return arena.dupe(u8, scope.primary_directory);    if (std.mem.eql(u8, cwd_input, ".")) return arena.dupe(u8, scope.primary_directory);    return pathing.resolveWorkspaceOrExternalPath(arena, scope.primary_directory, cwd_input);} /// Delegates validated input to the Core-owned command execution backend.pub fn call(ctx: tool_dispatch.DispatchContext, erased: tool_dispatch.ToolInput) tool_dispatch.DispatchError!tool_dispatch.ToolResult {    const backend = ctx.run_command_backend orelse return .{        .failure = try ctx.allocator.dupe(u8, "run_command execution backend is unavailable\n"),    };    const input = erased.as(Input);    const cwd = resolveCwd(        ctx.allocator,        ctx.access_scope orelse workspace_access.AccessScope.primaryOnly(ctx.workspace_root),        input,    ) catch |err| {        if (err == error.OutOfMemory) return error.OutOfMemory;        return .{ .failure = try std.fmt.allocPrint(ctx.allocator, "Unable to resolve command cwd: {s}", .{@errorName(err)}) };    };    defer ctx.allocator.free(@constCast(cwd));    return backend.execute(ctx, .{        .command = input.command,        .resolved_cwd = cwd,        .background = input.background,        .expects_url = server_detection.isLikelyServerCommand(input.command),    });} /// Reports that run_command may mutate process or filesystem state.pub fn readsOnly(_: tool_dispatch.ToolInput) bool {    return false;} /// Reports that run_command is not intrinsically irreversible.pub fn isIrreversible(_: tool_dispatch.ToolInput) bool {    return false;} fn noopInputDeinit(_: *anyopaque, _: Allocator) void {} fn stackInput(input: *Input) tool_dispatch.ToolInput {    return .{ .ptr = input, .deinit_fn = noopInputDeinit };} fn validateStackInput(alloc: Allocator, input: *Input) !?[]u8 {    return validate(.{ .allocator = alloc }, stackInput(input));} fn decodeInput(args_json: []const u8) !tool_dispatch.ToolInput {    const decoded = try decode(.{ .allocator = std.testing.allocator }, args_json);    return switch (decoded) {        .input => |input| input,        .failure => |body| {            defer std.testing.allocator.free(body);            return error.TestUnexpectedDecodeFailure;        },    };} fn expectDecodeFailure(args_json: []const u8, expected: []const u8) !void {    const decoded = try decode(.{ .allocator = std.testing.allocator }, args_json);    switch (decoded) {        .input => |input| {            input.deinit(std.testing.allocator);            return error.TestExpectedDecodeFailure;        },        .failure => |body| {            defer std.testing.allocator.free(body);            try std.testing.expectEqualStrings(expected, body);        },    }} test "decode accepts active fields and ignores unknown keys" {    const erased = try decodeInput("{\"command\":\"echo ok\",\"cwd\":\"src\",\"background\":true,\"timeout_ms\":500,\"extra\":1}");    defer erased.deinit(std.testing.allocator);    const input = erased.as(Input);    try std.testing.expectEqualStrings("echo ok", input.command);    try std.testing.expectEqualStrings("src", input.cwd.?);    try std.testing.expect(input.background);} test "decode frees valid cwd when background has invalid type" {    try expectDecodeFailure(        "{\"command\":\"echo ok\",\"cwd\":\"src\",\"background\":\"yes\"}",        "run_command field \"background\" must be a bool",    );} test "decode ignores unsigned timeout_ms type" {    const erased = try decodeInput("{\"command\":\"echo ok\",\"cwd\":\"src\",\"timeout_ms\":\"1000\"}");    defer erased.deinit(std.testing.allocator);    const input = erased.as(Input);    try std.testing.expectEqualStrings("echo ok", input.command);    try std.testing.expectEqualStrings("src", input.cwd.?);} test "validation preserves command bytes without public trimming" {    var empty = Input{ .command = try std.testing.allocator.dupe(u8, "") };    defer empty.deinit(std.testing.allocator);    try std.testing.expect((try validateStackInput(std.testing.allocator, &empty)) == null);    try std.testing.expectEqualStrings("", empty.command);     var whitespace_input = Input{ .command = try std.testing.allocator.dupe(u8, " \t\n ") };    defer whitespace_input.deinit(std.testing.allocator);    try std.testing.expect((try validateStackInput(std.testing.allocator, &whitespace_input)) == null);    try std.testing.expectEqualStrings(" \t\n ", whitespace_input.command);} test "cwd resolution handles default and non default" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    try tmp.dir.createDirPath(io_mod.getIo(), "workspace/src");    const workspace = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "workspace");    defer std.testing.allocator.free(workspace);    const expected = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "workspace/src");    defer std.testing.allocator.free(expected);     var default_input = Input{ .command = try std.testing.allocator.dupe(u8, "echo") };    defer default_input.deinit(std.testing.allocator);    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const scope = workspace_access.AccessScope.primaryOnly(workspace);    try std.testing.expectEqualStrings(workspace, try resolveCwd(arena_state.allocator(), scope, &default_input));     var src_input = Input{        .command = try std.testing.allocator.dupe(u8, "echo"),        .cwd = try std.testing.allocator.dupe(u8, "src"),    };    defer src_input.deinit(std.testing.allocator);    try std.testing.expectEqualStrings(expected, try resolveCwd(arena_state.allocator(), scope, &src_input));} test "cwd resolution accepts external absolute and workspace escape paths" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    try tmp.dir.createDirPath(io_mod.getIo(), "workspace");    try tmp.dir.createDirPath(io_mod.getIo(), "shared");    const workspace = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "workspace");    defer std.testing.allocator.free(workspace);    const shared = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "shared");    defer std.testing.allocator.free(shared);    const entries = [_]workspace_access.Entry{.{        .path = @constCast(shared),        .saved = true,        .command_line = false,        .available = true,        .active = true,    }};    const scope = workspace_access.AccessScope{        .primary_directory = workspace,        .additional_directories = &entries,    };    var input = Input{        .command = try std.testing.allocator.dupe(u8, "pwd"),        .cwd = try std.testing.allocator.dupe(u8, shared),    };    defer input.deinit(std.testing.allocator);    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();     try std.testing.expectEqualStrings(shared, try resolveCwd(arena_state.allocator(), scope, &input));    try std.testing.expectEqualStrings(        shared,        try resolveCwd(arena_state.allocator(), workspace_access.AccessScope.primaryOnly(workspace), &input),    );     var relative_input = Input{        .command = try std.testing.allocator.dupe(u8, "pwd"),        .cwd = try std.testing.allocator.dupe(u8, "../shared"),    };    defer relative_input.deinit(std.testing.allocator);    try std.testing.expectEqualStrings(        shared,        try resolveCwd(arena_state.allocator(), workspace_access.AccessScope.primaryOnly(workspace), &relative_input),    );} const FakeBackend = struct {    called: bool = false,    command: []const u8 = "",    expects_url: bool = false,     fn execute(        maybe_ctx: ?*anyopaque,        ctx: tool_dispatch.DispatchContext,        request: tool_dispatch.RunCommandRequest,    ) tool_dispatch.DispatchError!tool_dispatch.ToolResult {        const raw_ctx = maybe_ctx orelse unreachable;        const self: *FakeBackend = @ptrCast(@alignCast(raw_ctx));        self.called = true;        self.command = request.command;        self.expects_url = request.expects_url;        return .{ .success = try ctx.allocator.dupe(u8, "backend result") };    }}; test "registered callback delegates typed input to core backend" {    const erased = try decodeInput("{\"command\":\"npm run dev\"}");    defer erased.deinit(std.testing.allocator);    var backend = FakeBackend{};     var result = try call(.{        .allocator = std.testing.allocator,        .run_command_backend = .{            .ctx = &backend,            .execute_fn = FakeBackend.execute,        },    }, erased);    defer result.deinit(std.testing.allocator);     try std.testing.expect(backend.called);    try std.testing.expectEqualStrings("npm run dev", backend.command);    try std.testing.expect(backend.expects_url);    try std.testing.expectEqualStrings("backend result", result.success);} test "registered callback fails when core backend is unavailable" {    const erased = try decodeInput("{\"command\":\"printf unavailable\"}");    defer erased.deinit(std.testing.allocator);    var result = try call(.{ .allocator = std.testing.allocator }, erased);    defer result.deinit(std.testing.allocator);     try std.testing.expectEqualStrings(        "run_command execution backend is unavailable\n",        result.failure,    );}