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. Load more
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,