6697 lines · 276.0 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const builtin_context = @import("../../../../builtins/context.zig");const builtin_tools = @import("../../../../builtins/tools.zig");const types = @import("../../../shared/types.zig");const worker_runtime = @import("../../worker_runtime.zig");const session_runtime = @import("../../../session/session.zig");const session_codec = @import("../../../session/session_codec.zig");const session_child_store = @import("../../../session/session_child_store.zig");const result_store = @import("../../../session/result_store.zig");const debug_trace = @import("../../../shared/debug_trace.zig");const image_attachments = @import("../../../images/image_attachments.zig");const io_mod = @import("../../../shared/io.zig");const diff = @import("../../../output/diff.zig");const file_mutation = @import("../../../tooling/file_mutation.zig");const command_result_mapping = @import("../../../tooling/command_result_mapping.zig");const tool_dispatch = @import("../../../tooling/tool_dispatch.zig");const tool_specs = @import("../../../tooling/tool_specs.zig");const tool_result_errors = @import("../../../tooling/tool_result_errors.zig");const context_contract = @import("../../../workspace/context_contract.zig");const lifecycle_hooks = @import("../../../hooks/hooks.zig");const runtime_parallel_execution = @import("../parallel_execution.zig");const runtime_config = @import("../config.zig");const runtime_lifecycle = @import("../lifecycle.zig");const runtime_deps = @import("../deps.zig");const runtime_orchestrator = @import("../orchestrator.zig");const runtime_tool_admission = @import("../tool_admission.zig");const runtime_tool_batch = @import("../tool_batch.zig");const runtime_tool_contracts = @import("../tool_contracts.zig");const runtime_tool_presentation = @import("../tool_presentation.zig");const runtime_telemetry = @import("../telemetry.zig");const command_admission = @import("../../../permissions/command_admission.zig");const permission_auto_classifier = @import("../../../permissions/auto_classifier.zig");const auto_classifier_context = @import("../../../permissions/auto_classifier_context.zig"); const test_support = @import("support.zig"); const ChatMessage = types.ChatMessage;const PermissionGrant = types.PermissionGrant;const ToolCall = types.ToolCall; const FakeCompletion = test_support.FakeCompletion;const FakeGateway = test_support.FakeGateway;const FakeAgentRuntimeDeps = test_support.FakeAgentRuntimeDeps;const PreToolUseTestHandler = test_support.PreToolUseTestHandler;const PromptFixture = test_support.PromptFixture; const runFakePrompt = test_support.runFakePrompt;const runFakePromptWithLifecycle = test_support.runFakePromptWithLifecycle;const registerPreToolUseTestHandler = test_support.registerPreToolUseTestHandler;const testLifecycleContext = test_support.testLifecycleContext;const prepareToolCallForLifecycle = runtime_lifecycle.prepareToolCallForLifecycle;const expectBodyContains = test_support.expectBodyContains;const expectBodyNotContains = test_support.expectBodyNotContains;const expectBodyContainsInOrder = test_support.expectBodyContainsInOrder;const countText = test_support.countText;const countNeedle = test_support.countNeedle;const readTraceFile = test_support.readTraceFile;const logIndex = test_support.logIndex;const textContains = test_support.textContains;const toolCall = test_support.toolCall; const vision_agent_test_tools = test_support.vision_agent_test_tools;const VisionAgentToolRuntime = test_support.VisionAgentToolRuntime; fn makeOwnedVisionCatalog(    alloc: std.mem.Allocator,    dir: std.Io.Dir,    image_id: usize,) ![]types.ImageAttachment {    try dir.writeFile(io_mod.getIo(), .{ .sub_path = "vision.png", .data = "\x89PNG\r\n\x1a\nimage bytes" });    const catalog = try alloc.alloc(types.ImageAttachment, 1);    errdefer alloc.free(catalog);    const path = try io_mod.dirRealpathAlloc(alloc, dir, "vision.png");    var path_owned = true;    errdefer if (path_owned) alloc.free(path);    const media_type = try alloc.dupe(u8, "image/png");    var media_type_owned = true;    errdefer if (media_type_owned) alloc.free(media_type);    catalog[0] = .{ .id = image_id, .path = path, .media_type = media_type };    path_owned = false;    media_type_owned = false;    errdefer types.freeImageAttachment(alloc, catalog[0]);    const root = try io_mod.dirRealpathAlloc(alloc, dir, ".");    defer alloc.free(root);    const snapshot_dir = try std.fs.path.join(alloc, &.{ root, "snapshots" });    defer alloc.free(snapshot_dir);    try image_attachments.captureImageSnapshot(alloc, &catalog[0], snapshot_dir);    return catalog;} const BlockingPromptRun = struct {    gateway: *FakeGateway,    hooks: *FakeAgentRuntimeDeps,    config: runtime_config.Config,    job: worker_runtime.QueuedPrompt,    finished: *std.atomic.Value(bool),    failure: ?anyerror = null,     fn run(self: *@This()) void {        runFakePrompt(self.gateway, self.hooks, self.config, self.job) catch |err| {            self.failure = err;        };        self.finished.store(true, .seq_cst);    }}; fn waitForPermissionBarrier(    waiting: *const std.atomic.Value(bool),    finished: *const std.atomic.Value(bool),) bool {    while (!waiting.load(.seq_cst) and !finished.load(.seq_cst)) {        std.Thread.yield() catch std.atomic.spinLoopHint();    }    return waiting.load(.seq_cst);} fn executedToolCount(hooks: *FakeAgentRuntimeDeps) usize {    hooks.execute_mutex.lockUncancelable(io_mod.getIo());    defer hooks.execute_mutex.unlock(io_mod.getIo());    return hooks.executed_names.items.len;} fn restrictedCommandFingerprint(    command: []const u8,    cwd: []const u8,) command_admission.AdmissionFingerprint {    return .{        .command = command,        .resolved_cwd = cwd,        .background = false,        .resolved_backend = .none,        .target_os = builtin.os.tag,        .scope = .restricted,    };} fn expectGrantListsEqual(    expected: []const PermissionGrant,    actual: []const PermissionGrant,) !void {    try std.testing.expectEqual(expected.len, actual.len);    for (expected, actual) |expected_grant, actual_grant| {        try std.testing.expectEqualStrings(            expected_grant.tool_name,            actual_grant.tool_name,        );        try std.testing.expectEqualStrings(            expected_grant.target_path,            actual_grant.target_path,        );    }} fn expectNoGrantPermission(    grants: []const PermissionGrant,    permission: []const u8,) !void {    for (grants) |grant| {        try std.testing.expect(!std.mem.eql(            u8,            grant.tool_name,            permission,        ));    }} fn logContains(hooks: *const FakeAgentRuntimeDeps, needle: []const u8) bool {    for (hooks.log.items) |entry| {        if (std.mem.find(u8, entry, needle) != null) return true;    }    return false;} const FailingApplicableContext = struct {    const Failure = enum {        out_of_memory,        no_space_left,        write_failed,    };     var failure: Failure = .out_of_memory;    var expected_target: []const u8 = "";    var select_calls: usize = 0;    var saw_expected_input = false;     fn reset(next_failure: Failure, target: []const u8) void {        failure = next_failure;        expected_target = target;        select_calls = 0;        saw_expected_input = false;    }     fn gather(        _: std.mem.Allocator,        _: context_contract.InitialContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        return .{};    }     fn select(        _: std.mem.Allocator,        input: context_contract.LaterContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        select_calls += 1;        saw_expected_input = input.targets.len == 1 and            input.targets[0].kind == .file and            std.mem.eql(u8, input.targets[0].path, expected_target) and            input.delivered_sources.len == 0 and            input.evaluated_endpoints.len == 0;        return switch (failure) {            .out_of_memory => error.OutOfMemory,            .no_space_left => error.NoSpaceLeft,            .write_failed => error.WriteFailed,        };    }     fn appendStatic(        _: context_contract.StaticContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     fn appendTransient(        _: context_contract.TransientContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     const registry = context_contract.Registry{ .default_provider = .{        .id = "test.failing_applicable_context",        .gather_project_context_fn = gather,        .select_applicable_project_context_fn = select,        .append_static_fn = appendStatic,        .append_transient_fn = appendTransient,    } };}; const ApplicableContextDelta = struct {    const content = "SCOPED_CONTEXT_DELTA";    const source = "/test/scoped/AGENTS.md";    const endpoint = "/test/scoped";     var expected_target: []const u8 = "";    var cancel_flag: ?*std.atomic.Value(bool) = null;    var select_calls: usize = 0;    var saw_expected_input = false;     fn reset(target: []const u8, maybe_cancel_flag: ?*std.atomic.Value(bool)) void {        expected_target = target;        cancel_flag = maybe_cancel_flag;        select_calls = 0;        saw_expected_input = false;    }     fn gather(        _: std.mem.Allocator,        _: context_contract.InitialContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        return .{};    }     fn select(        alloc: std.mem.Allocator,        input: context_contract.LaterContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        select_calls += 1;        saw_expected_input = input.targets.len == 1 and            input.targets[0].kind == .file and            std.mem.eql(u8, input.targets[0].path, expected_target) and            input.delivered_sources.len == 0 and            input.evaluated_endpoints.len == 0;        if (cancel_flag) |flag| flag.store(true, .seq_cst);         var selected: context_contract.ProviderContext = .{};        errdefer selected.deinit(alloc);        selected.content = try alloc.dupe(u8, content);        selected.delivered_sources = try dupeStrings(alloc, &.{source});        selected.evaluated_endpoints = try dupeStrings(alloc, &.{endpoint});        return selected;    }     fn appendStatic(        _: context_contract.StaticContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     fn appendTransient(        _: context_contract.TransientContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     fn dupeStrings(        alloc: std.mem.Allocator,        strings: []const []const u8,    ) std.mem.Allocator.Error![][]u8 {        if (strings.len == 0) return &.{};        const owned = try alloc.alloc([]u8, strings.len);        var initialized: usize = 0;        errdefer {            for (owned[0..initialized]) |string| alloc.free(string);            alloc.free(owned);        }        for (strings, 0..) |string, index| {            owned[index] = try alloc.dupe(u8, string);            initialized += 1;        }        return owned;    }     const registry = context_contract.Registry{ .default_provider = .{        .id = "test.applicable_context_delta",        .gather_project_context_fn = gather,        .select_applicable_project_context_fn = select,        .append_static_fn = appendStatic,        .append_transient_fn = appendTransient,    } };}; const EmptyApplicableContext = struct {    var select_calls: usize = 0;    var last_target_count: usize = 0;     fn reset() void {        select_calls = 0;        last_target_count = 0;    }     fn gather(        _: std.mem.Allocator,        _: context_contract.InitialContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        return .{};    }     fn select(        _: std.mem.Allocator,        input: context_contract.LaterContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        select_calls += 1;        last_target_count = input.targets.len;        return .{};    }     fn appendStatic(        _: context_contract.StaticContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     fn appendTransient(        _: context_contract.TransientContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     const registry = context_contract.Registry{ .default_provider = .{        .id = "test.empty_applicable_context",        .gather_project_context_fn = gather,        .select_applicable_project_context_fn = select,        .append_static_fn = appendStatic,        .append_transient_fn = appendTransient,    } };}; const FreshnessApplicableContext = struct {    const content = "NEW_SCOPED_CONTEXT";    const source = "/test/new/AGENTS.md";    const endpoint = "/test/new";     var old_target: []const u8 = "";    var new_target: []const u8 = "";    var select_calls: usize = 0;    var first_saw_old_target = false;    var first_saw_new_target = false;    var reissue_saw_new_target = false;    var execution_reissue_saw_new_target = false;     fn reset(old: []const u8, new: []const u8) void {        old_target = old;        new_target = new;        select_calls = 0;        first_saw_old_target = false;        first_saw_new_target = false;        reissue_saw_new_target = false;        execution_reissue_saw_new_target = false;    }     fn gather(        _: std.mem.Allocator,        _: context_contract.InitialContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        return .{};    }     fn select(        alloc: std.mem.Allocator,        input: context_contract.LaterContextInput,    ) context_contract.ProviderError!context_contract.ProviderContext {        select_calls += 1;        for (input.targets) |target| {            if (select_calls == 1 and std.mem.eql(u8, target.path, old_target)) {                first_saw_old_target = true;            }            if (select_calls == 1 and std.mem.eql(u8, target.path, new_target)) {                first_saw_new_target = true;            }            if (select_calls == 2 and std.mem.eql(u8, target.path, new_target)) {                reissue_saw_new_target = true;            }            if (select_calls == 3 and std.mem.eql(u8, target.path, new_target)) {                execution_reissue_saw_new_target = true;            }        }        if (select_calls == 2 and reissue_saw_new_target) {            var selected: context_contract.ProviderContext = .{};            errdefer selected.deinit(alloc);            selected.content = try alloc.dupe(u8, content);            selected.delivered_sources = try ApplicableContextDelta.dupeStrings(alloc, &.{source});            selected.evaluated_endpoints = try ApplicableContextDelta.dupeStrings(alloc, &.{endpoint});            return selected;        }        return .{};    }     fn appendStatic(        _: context_contract.StaticContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     fn appendTransient(        _: context_contract.TransientContextInput,        _: std.mem.Allocator,        _: *std.ArrayList(ChatMessage),    ) context_contract.ProviderError!void {}     const registry = context_contract.Registry{ .default_provider = .{        .id = "test.freshness_applicable_context",        .gather_project_context_fn = gather,        .select_applicable_project_context_fn = select,        .append_static_fn = appendStatic,        .append_transient_fn = appendTransient,    } };}; fn expectPermissionDeniedToolResult(gateway: *const FakeGateway, index: usize, tool_name: []const u8, reason: types.ToolPermissionDenialReason) !void {    const alloc = std.testing.allocator;    try std.testing.expect(index < gateway.request_bodies.items.len);     var parsed = try std.json.parseFromSlice(std.json.Value, alloc, gateway.request_bodies.items[index], .{});    defer parsed.deinit();     const prompt = parsed.value.object.get("prompt").?.array.items;    for (prompt) |entry| {        if (entry != .object) continue;        const role = entry.object.get("role") orelse continue;        if (role != .string or !std.mem.eql(u8, role.string, "tool")) continue;        const content = entry.object.get("content") orelse continue;        if (content != .array) continue;        for (content.array.items) |part| {            if (part != .object) continue;            const part_tool_name = part.object.get("toolName") orelse continue;            if (part_tool_name != .string or !std.mem.eql(u8, part_tool_name.string, tool_name)) continue;            const output = part.object.get("output") orelse continue;            if (output != .object) continue;            const value = output.object.get("value") orelse continue;            if (value != .string) continue;             try std.testing.expect(tool_result_errors.isToolPermissionDeniedOutput(value.string));            var payload = try std.json.parseFromSlice(std.json.Value, alloc, value.string, .{});            defer payload.deinit();            const error_obj = payload.value.object.get("error").?.object;            try std.testing.expectEqualStrings("tool_permission_denied", error_obj.get("type").?.string);            try std.testing.expectEqualStrings(tool_name, error_obj.get("tool_name").?.string);            if (expectedPermissionDeniedMessage(reason)) |message| {                try std.testing.expectEqualStrings(message, error_obj.get("message").?.string);            }            try std.testing.expectEqualStrings(@tagName(reason), error_obj.get("reason").?.string);            try std.testing.expect(error_obj.get("denied").?.bool);            try std.testing.expect(error_obj.get("suggestion") != null);            return;        }    }     return error.TestExpectedToolResultMissing;} fn expectedPermissionDeniedMessage(reason: types.ToolPermissionDenialReason) ?[]const u8 {    return switch (reason) {        .user_denied => "Permission denied by user",        .auto_denied => "Permission denied by auto mode classifier",        .policy_denied, .permission_required => null,    };} fn expectMalformedArgumentToolPair(    gateway: *const FakeGateway,

Lines 1–500 of 6697.