2843 lines · 126.4 KB · Zig
RawDownload
const std = @import("std");const agent_runtime = @import("../agent/agent_runtime.zig");const agent_stream_provider = @import("../agent/stream_provider.zig");const command_admission = @import("../permissions/command_admission.zig");const permission_auto_classifier = @import("../permissions/auto_classifier.zig");const app_callbacks = @import("app_callbacks.zig");const runtime_profile = @import("../hosts/runtime_profile.zig");const app_permission_runtime = @import("app_permission_runtime.zig");const app_session_runtime = @import("app_session_runtime.zig");const app_worker_runtime = @import("app_worker_runtime.zig");const auth_runtime = @import("../auth/auth_runtime.zig");const credentials = @import("../auth/credentials.zig");const background_runtime = @import("../background/background_runtime.zig");const change_tracker = @import("../workspace/change_tracker.zig");const file_mutation_contract = @import("../tooling/file_mutation_contract.zig");const hooks = @import("../hooks/hooks.zig");const io_mod = @import("../shared/io.zig");const mcp_elicitation_interaction = @import("../mcp/elicitation_interaction.zig");const mcp_model_catalog = @import("../mcp/model_catalog.zig");const permission_gate = @import("../permissions/permission_gate.zig");const permissions = @import("../permissions/permissions.zig");const prompt_policy_contract = @import("../config/prompt_policy.zig");const sandbox = @import("../permissions/sandbox.zig");const session_runtime = @import("../session/session.zig");const session_child_store = @import("../session/session_child_store.zig");const skill_runtime = @import("../skills/skill_runtime.zig");const subagent_agent_adapter = @import("../subagent/agent_adapter.zig");const subagent_domain = @import("../subagent/domain.zig");const subagent_execution = @import("../subagent/execution.zig");const debug_trace = @import("../shared/debug_trace.zig");const text_utils = @import("../shared/text_utils.zig");const tool_args = @import("../tooling/tool_args.zig");const tool_admission = @import("../tooling/tool_admission.zig");const tool_advertisement = @import("../tooling/tool_advertisement.zig");const tool_dispatch = @import("../tooling/tool_dispatch.zig");const tool_mcp_runtime = @import("../tooling/tool_mcp_runtime.zig");const context_contract = @import("../workspace/context_contract.zig");const model_catalog = @import("../gateway/model_catalog.zig");const test_builtin_gateway = if (@import("builtin").is_test)    @import("../../builtins/gateway.zig")else    struct {};const test_builtin_tools = if (@import("builtin").is_test)    @import("../../builtins/tools.zig")else    struct {};const tool_presentation = @import("../tooling/tool_presentation.zig");const tool_runtime = @import("../tooling/tool_runtime.zig");const skill_invocation = @import("../skills/skill_invocation.zig");const web_fetch_runtime = @import("../tooling/web_fetch_runtime.zig");const web_search_runtime = @import("../tooling/web_search_runtime.zig");const types = @import("../shared/types.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const workspace_access = @import("../workspace/workspace_access.zig");const js_host_workspace = @import("../hosts/js_host_workspace.zig"); const Allocator = std.mem.Allocator;const ChatMessage = types.ChatMessage;const PermissionGrant = types.PermissionGrant;const PermissionMode = types.PermissionMode;const ToolCall = types.ToolCall;const ToolPermissionDecision = types.ToolPermissionDecision; pub fn Runtime(comptime App: type) type {    return struct {        const ToolAuthorityView = struct {            mode: PermissionMode,            sandbox_backend: sandbox.BackendKind,            grants: []const PermissionGrant,            rules: types.PermissionRuleSet,        };         fn appAccessScope(app: *const App) ?workspace_access.AccessScope {            if (comptime @hasDecl(App, "workspaceAccessScope")) {                return app.workspaceAccessScope();            }            return null;        }         fn appHostWorkspaceInfo(app: *const App) ?*const js_host_workspace.Info {            if (comptime @hasDecl(App, "workspaceHostInfo")) {                return app.workspaceHostInfo();            }            return null;        }         fn modelVisibleProjectContext(app: *App) []const u8 {            if (comptime @hasField(App, "context_enabled")) {                if (!app.context_enabled) return "";            }            if (app.worker.active_context_snapshot) |snapshot| return snapshot.modelVisibleBytes();            return app.context_snapshot.modelVisibleBytes();        }         fn childToolContext(root_context: tool_runtime.Context) tool_runtime.Context {            var child_context = root_context;            child_context.tracker = null;            return child_context;        }         pub fn toolContext(            app: *App,            ignored_list_entries: []const []const u8,            max_list_entries: usize,            max_read_file_bytes: usize,            max_read_file_lines: usize,            max_read_file_line_len: usize,            max_command_output_bytes: usize,            gateway_retry_count: usize,            gateway_chat_url: []const u8,        ) tool_runtime.Context {            return toolContextWithAuthority(                app,                ignored_list_entries,                max_list_entries,                max_read_file_bytes,                max_read_file_lines,                max_read_file_line_len,                max_command_output_bytes,                gateway_retry_count,                gateway_chat_url,                null,            );        }         pub fn toolContextForSubagent(            app: *App,            ignored_list_entries: []const []const u8,            max_list_entries: usize,            max_read_file_bytes: usize,            max_read_file_lines: usize,            max_read_file_line_len: usize,            max_command_output_bytes: usize,            gateway_retry_count: usize,            gateway_chat_url: []const u8,            admission: subagent_domain.AdmissionSnapshot,        ) tool_runtime.Context {            return toolContextWithAuthority(                app,                ignored_list_entries,                max_list_entries,                max_read_file_bytes,                max_read_file_lines,                max_read_file_line_len,                max_command_output_bytes,                gateway_retry_count,                gateway_chat_url,                .{                    .mode = admission.permission_mode,                    .sandbox_backend = admission.sandbox_backend,                    .grants = admission.grants,                    .rules = admission.rules,                },            );        }         fn toolContextWithAuthority(            app: *App,            ignored_list_entries: []const []const u8,            max_list_entries: usize,            max_read_file_bytes: usize,            max_read_file_lines: usize,            max_read_file_line_len: usize,            max_command_output_bytes: usize,            gateway_retry_count: usize,            gateway_chat_url: []const u8,            authority: ?ToolAuthorityView,        ) tool_runtime.Context {            const host_workspace = appHostWorkspaceInfo(app);            const workspace_root = if (host_workspace) |info|                info.root()            else                app.workspace_root;            const agent_settings = app.worker.effectiveAgentTurnSettings();            const permission_snapshot = if (authority) |snapshot|                worker_runtime.PermissionSnapshot{                    .mode = snapshot.mode,                    .sandbox_backend = snapshot.sandbox_backend,                }            else                app.worker.effectivePermissionSnapshot(                    app_permission_runtime.Runtime(App).livePermissionSnapshot(app),                );            const permission_grants = if (authority) |snapshot|                snapshot.grants            else                app.permission_engine.grants.items;            const permission_rules = if (authority) |snapshot|                snapshot.rules            else                app.permission_engine.rules;            const child_capability =                if (comptime @hasField(App, "session_persistence"))                    app_session_runtime.Runtime(App).childCapability(app)                else                    null;            var ctx: tool_runtime.Context = .{                .workspace_root = workspace_root,                .access_scope = if (host_workspace != null)                    workspace_access.AccessScope.primaryOnly(workspace_root)                else                    appAccessScope(app),                .ignored_list_entries = ignored_list_entries,                .max_list_entries = max_list_entries,                .max_read_file_bytes = max_read_file_bytes,                .max_read_file_lines = max_read_file_lines,                .max_read_file_line_len = max_read_file_line_len,                .max_command_output_bytes = max_command_output_bytes,                .max_tool_result_bytes = agent_settings.max_tool_result_bytes,                .api_key = app.auth.apiKey() orelse "",                .agent_stream_provider = if (comptime @hasDecl(App, "agentStreamProvider"))                    app.agentStreamProvider()                else                    agent_stream_provider.unavailable_provider,                .gateway_team = app.auth.gatewayTeam(),                .credential_source = app.auth.credentialSource(),                .oauth_transport = app.auth.oauthTransport(),                .model = app.selected_model.items,                .gateway_retry_count = gateway_retry_count,                .gateway_chat_url = gateway_chat_url,                .gateway_models_path = if (comptime @hasField(App, "web_search_models_path")) app.web_search_models_path else "/v1/models",                .agent_step_limit = app.agent_step_limit,                .fast_mode = agent_settings.fast_mode,                .effort = agent_settings.effort,                .first_call_tool_choice = agent_settings.first_call_tool_choice,                .tool_registry = if (comptime @hasDecl(App, "toolRegistry")) app.toolRegistry() else .{},                .subagent_host = if (comptime @hasField(App, "session_persistence"))                    app_session_runtime.Runtime(App).subagentHost(app)                else                    null,                .subagent_caller_id = if (comptime @hasField(App, "session_persistence"))                    app_session_runtime.Runtime(App).activeSessionId(app)                else                    null,                .permission_mode = permission_snapshot.mode,                .permission_grants = permission_grants,                .permission_rules = permission_rules,                .worker = &app.worker,                .permission_prompter = tool_admission.workerPrompter(&app.worker),                .cancel_flag = &app.worker.worker_cancel_requested,                .background = &app.background,                .session_child_capability = child_capability,                .terminal_client = if (comptime @hasField(App, "terminal_client"))                    &app.terminal_client                else                    null,                .session = &app.session,                .session_allocator = app.alloc,                .skills_dir = app.skills.dir,                .context_limits = if (comptime @hasField(App, "context_limits")) app.context_limits else .{},                .context_enabled = if (comptime @hasField(App, "context_enabled")) app.context_enabled else true,                .context_registry = app.contextRegistry(),                .output_chunk_ctx = @ptrCast(app),                .on_output_chunk = app_callbacks.Bindings(App).onCommandOutputChunk,                .background_url_ctx = @ptrCast(app),                .on_background_url_ready = app_callbacks.Bindings(App).onBackgroundUrlReady,                .sandbox_backend = permission_snapshot.sandbox_backend,                .devbox_provider = if (comptime @hasDecl(App, "devboxProvider")) app.devboxProvider() else null,                .workspace_executor = if (comptime @hasDecl(App, "workspaceExecutor")) app.workspaceExecutor() else null,                .host_sandbox_default = if (host_workspace) |info| switch (info.permission) {                    .allow_sandboxed => .allow_sandboxed,                    .prompt => .prompt,                } else .none,                .permission_reviewer_provider = if (comptime @hasDecl(App, "permissionReviewerProvider")) app.permissionReviewerProvider() else null,                .tracker = &app.change_tracker,                .mcp_ctx = @ptrCast(app),                .mcp_has_tool = if (comptime runtime_profile.allows(App, .mcp)) mcpHasTool else null,                .mcp_validate_tool = if (comptime runtime_profile.allows(App, .mcp)) validateMcpTool else null,                .mcp_call_tool = if (comptime runtime_profile.allows(App, .mcp)) callMcpTool else null,                .mcp_search_tools = if (comptime runtime_profile.allows(App, .mcp)) searchMcpTools else null,                .mcp_tool_schema = if (comptime runtime_profile.allows(App, .mcp)) mcpToolSchemaJson else null,                .mcp_call_feature = if (comptime runtime_profile.allows(App, .mcp)) callMcpFeature else null,                .mcp_progress_ctx = @ptrCast(app),                .on_mcp_progress = app_callbacks.Bindings(App).onMcpProgress,                .lifecycle_view = app.lifecycle_view,                .lifecycle_scope = lifecycleContext(app).scope,            };            if (comptime @hasField(App, "web_fetch_runtime")) {                ctx.web_fetch_runtime = &app.web_fetch_runtime;                ctx.web_fetch_artifact_store = app.session.webFetchArtifactStore();                ctx.web_fetch_artifact_error = app.session.webFetchArtifactError();                ctx.web_fetch_progress_ctx = @ptrCast(app);                ctx.on_web_fetch_progress = app_callbacks.Bindings(App).onWebFetchProgress;            }            if (comptime @hasField(App, "web_search_runtime")) {                app.web_search_runtime.configure(.{                    .api_key = app.auth.apiKey() orelse "",                    .gateway_team = app.auth.gatewayTeam(),                    .worker_model = app.selected_model.items,                    .gateway_retry_count = gateway_retry_count,                    .gateway_chat_url = gateway_chat_url,                    .usage = &app.session.usage,                    .usage_allocator = app.alloc,                });                ctx.web_search_runtime_ready = false;                ctx.web_search_backend = app.web_search_runtime.dispatchBackend();                ctx.web_search_progress_ctx = @ptrCast(app);                ctx.on_web_search_progress = app_callbacks.Bindings(App).onWebSearchProgress;            }            if (comptime runtime_profile.allows(App, .mcp) and                @hasDecl(@TypeOf(app.worker), "requestMcpElicitationAnswerBlocking") and                @hasDecl(App, "urlOpener"))            {                ctx.mcp_input_responder = .{                    .context = @ptrCast(app),                    .capabilities = .{ .form = true, .url = true },                    .legacy_url_manual_completion = true,                    .callback = respondToMcpInput,                };            }            ctx.model_capability_resolver = app_callbacks.Bindings(App).modelCapabilityResolver(app);            return ctx;        }         fn respondToMcpInput(            raw_ctx: *anyopaque,            alloc: Allocator,            origin: tool_mcp_runtime.InputOrigin,            required: tool_mcp_runtime.InputRequired,        ) anyerror![]const u8 {            return mcp_elicitation_interaction.respond(alloc, origin, required, .{                .questioner = .{ .context = raw_ctx, .ask_fn = askMcpQuestion },                .browser = .{ .context = raw_ctx, .open_fn = openMcpUrl },                .capabilities = .{ .form = true, .url = true },            });        }         fn askMcpQuestion(            raw_ctx: *anyopaque,            alloc: Allocator,            entries: []const types.QuestionBatchEntry,            deadline_ms: i64,            lifecycle_cancel_flag: ?*const std.atomic.Value(bool),        ) anyerror!?[][]u8 {            const app: *App = @ptrCast(@alignCast(raw_ctx));            var watch = DeadlineWatch{                .app = app,                .deadline_ms = deadline_ms,                .lifecycle_cancel_flag = lifecycle_cancel_flag,            };            const watcher = try std.Thread.spawn(.{}, DeadlineWatch.run, .{&watch});            defer {                watch.done.store(true, .release);                watcher.join();            }             const c_alloc = std.heap.c_allocator;            const answers = try app.worker.requestMcpElicitationAnswerBlocking(c_alloc, entries) orelse {                if (watch.timed_out.load(.acquire)) return error.McpInputTimedOut;                return null;            };            defer freeMcpAnswers(c_alloc, answers);            const copy = try alloc.alloc([]u8, answers.len);            errdefer alloc.free(copy);            var copied: usize = 0;            errdefer for (copy[0..copied]) |answer| alloc.free(answer);            while (copied < answers.len) : (copied += 1) {                copy[copied] = try alloc.dupe(u8, answers[copied]);            }            return copy;        }         fn openMcpUrl(            raw_ctx: ?*anyopaque,            alloc: Allocator,            url: []const u8,        ) anyerror!bool {            const app: *App = @ptrCast(@alignCast(raw_ctx.?));            return app.urlOpener().open(alloc, url);        }         fn freeMcpAnswers(alloc: Allocator, answers: [][]u8) void {            for (answers) |answer| alloc.free(answer);            alloc.free(answers);        }         const DeadlineWatch = struct {            app: *App,            deadline_ms: i64,            lifecycle_cancel_flag: ?*const std.atomic.Value(bool),            done: std.atomic.Value(bool) = .init(false),            timed_out: std.atomic.Value(bool) = .init(false),             fn run(self: *DeadlineWatch) void {                while (!self.done.load(.acquire)) {                    const cancelled = self.app.worker.worker_cancel_requested.load(.acquire);                    const lifecycle_cancelled = if (self.lifecycle_cancel_flag) |flag|                        flag.load(.acquire)                    else                        false;                    const timed_out = currentAwakeMillis() >= self.deadline_ms;                    if (cancelled or lifecycle_cancelled or timed_out) {                        if (timed_out and !cancelled and !lifecycle_cancelled) {                            self.timed_out.store(true, .release);                        }                        if (self.app.worker.cancelPendingQuestionBatch()) return;                    }                    io_mod.sleep(20 * std.time.ns_per_ms);                }            }        };         fn currentAwakeMillis() i64 {            const now = std.Io.Clock.Timestamp.now(io_mod.getIo(), .awake);            const milliseconds = @divFloor(now.raw.nanoseconds, std.time.ns_per_ms);            return std.math.cast(i64, milliseconds) orelse if (milliseconds < 0)                std.math.minInt(i64)            else                std.math.maxInt(i64);        }         fn mcpHasTool(raw_ctx: *anyopaque, name: []const u8, access: tool_mcp_runtime.Access) bool {            const app: *App = @ptrCast(@alignCast(raw_ctx));            return app.hasMcpTool(name, access);        }         fn validateMcpTool(raw_ctx: *anyopaque, arena: Allocator, name: []const u8, arguments_json: []const u8, access: tool_mcp_runtime.Access) anyerror!tool_mcp_runtime.ValidationResult {            const app: *App = @ptrCast(@alignCast(raw_ctx));            if (comptime @hasDecl(App, "validateMcpTool")) {                return app.validateMcpTool(arena, name, arguments_json, access);            }            return .not_available;        }         fn callMcpTool(raw_ctx: *anyopaque, arena: Allocator, name: []const u8, arguments_json: []const u8, max_tool_result_bytes: usize, options: tool_mcp_runtime.CallOptions) anyerror!?tool_mcp_runtime.CallResult {            const app: *App = @ptrCast(@alignCast(raw_ctx));            return app.callMcpTool(arena, name, arguments_json, max_tool_result_bytes, options);        }         fn searchMcpTools(raw_ctx: *anyopaque, arena: Allocator, query: []const u8, limit: usize, permission_rules: types.PermissionRuleSet, _: @import("../config/context_limits.zig").Values, access: tool_mcp_runtime.Access) anyerror!tool_mcp_runtime.SearchResult {            const app: *App = @ptrCast(@alignCast(raw_ctx));            if (comptime @hasDecl(App, "searchMcpTools")) {                return app.searchMcpTools(arena, query, limit, permission_rules, access);            }            return .{ .model_output = try arena.dupe(u8, "{\"tools\":[],\"count\":0}") };        }         fn mcpToolSchemaJson(raw_ctx: *anyopaque, arena: Allocator, name: []const u8, permission_rules: types.PermissionRuleSet, _: @import("../config/context_limits.zig").Values, access: tool_mcp_runtime.Access) anyerror!?tool_mcp_runtime.ToolSchemaResult {            const app: *App = @ptrCast(@alignCast(raw_ctx));            if (comptime @hasDecl(App, "mcpToolSchemaJson")) {                return app.mcpToolSchemaJson(arena, name, permission_rules, access);            }            return null;        }         fn callMcpFeature(            raw_ctx: *anyopaque,            arena: Allocator,            request: tool_mcp_runtime.FeatureRequest,            options: tool_mcp_runtime.FeatureCallOptions,        ) anyerror!tool_mcp_runtime.FeatureResult {            if (comptime !@hasDecl(App, "acquireMcpRuntime")) {                return error.McpRuntimeUnavailable;            }            const app: *App = @ptrCast(@alignCast(raw_ctx));            var lease = app.acquireMcpRuntime() orelse return error.McpRuntimeUnavailable;            defer lease.deinit();            return lease.runtime.callFeatureForModel(arena, request, options);        }         pub fn describeToolAction(            app: *App,            arena: Allocator,            call: ToolCall,            file_display_path: ?[]const u8,            advertised_dynamic_tool_names: []const []const u8,            ignored_list_entries: []const []const u8,            max_list_entries: usize,            max_read_file_bytes: usize,            max_read_file_lines: usize,            max_read_file_line_len: usize,            max_command_output_bytes: usize,            gateway_retry_count: usize,            gateway_chat_url: []const u8,        ) ![]const u8 {            const ctx = tool_runtime.withAdvertisedDynamicToolNames(toolContext(app, ignored_list_entries, max_list_entries, max_read_file_bytes, max_read_file_lines, max_read_file_line_len, max_command_output_bytes, gateway_retry_count, gateway_chat_url), advertised_dynamic_tool_names);            return formatToolAction(ctx, arena, call, file_display_path, .active, null);        }         pub fn describeToolActionCompleted(            app: *App,            arena: Allocator,            call: ToolCall,            file_display_path: ?[]const u8,            advertised_dynamic_tool_names: []const []const u8,            ignored_list_entries: []const []const u8,            max_list_entries: usize,            max_read_file_bytes: usize,            max_read_file_lines: usize,            max_read_file_line_len: usize,            max_command_output_bytes: usize,            gateway_retry_count: usize,            gateway_chat_url: []const u8,        ) ![]const u8 {            const ctx = tool_runtime.withAdvertisedDynamicToolNames(toolContext(app, ignored_list_entries, max_list_entries, max_read_file_bytes, max_read_file_lines, max_read_file_line_len, max_command_output_bytes, gateway_retry_count, gateway_chat_url), advertised_dynamic_tool_names);            return formatToolAction(ctx, arena, call, file_display_path, .completed, null);        }         pub fn describeToolActionDenied(            app: *App,

Lines 1–500 of 2843.