5134 lines · 196.1 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const io_mod = @import("../shared/io.zig");const app_lifecycle = @import("../app/app_lifecycle.zig");const background_record_liveness = @import("../background/background_record_liveness.zig");const background_store = @import("../background/background_store.zig");const acp_runner = @import("acp_runner.zig");const cli_ask = @import("cli_ask.zig");const cli_replay = @import("cli_replay.zig");const command_specs = @import("../slash_commands/command_specs.zig");const collections = @import("../shared/collections.zig");const config_runtime = @import("../config/config_runtime.zig");const devbox_executor = @import("../execution/devbox_executor.zig");const doctor_runtime = @import("doctor_runtime.zig");const gateway_provider = @import("../gateway/gateway_provider.zig");const model_catalog = @import("../gateway/model_catalog.zig");const background_process_provider = @import(    "../execution/background_process_provider.zig",);const github_publish = @import("../github/github_publish.zig");const github_workflows = @import("../github/github_workflows.zig");const host = @import("../hosts/host.zig");const login_flow = @import("../auth/login_flow.zig");const oauth_transport = @import("../auth/oauth_transport.zig");const secret = @import("../auth/secret.zig");const output_contracts = @import("../output/output_contracts.zig");const permission_auto_classifier = @import("../permissions/auto_classifier.zig");const prompt_policy = @import("../config/prompt_policy.zig");const sandbox = @import("../permissions/sandbox.zig");const session_store = @import("../session/session_store.zig");const usage_report = @import("../session/usage_report.zig");const skill_contract = @import("../skills/skill_contract.zig");const types = @import("../shared/types.zig");const update_target = @import("../upgrade/update_target.zig");const test_builtin_gateway = if (builtin.is_test)    @import("../../builtins/gateway.zig")else    struct {};const context_contract = @import("../workspace/context_contract.zig");const mode_registry = @import("../modes/mode_registry.zig");const mcp_contract = @import("../mcp/mcp_contract.zig");const mcp_runtime = @import("../mcp/mcp_runtime.zig");const tool_set_contract = @import("../tooling/tool_set.zig");const workspace_access = @import("../workspace/workspace_access.zig");const workspace_commands = @import("../workspace/workspace_commands.zig");const usage_cli_runtime = @import("usage_cli_runtime.zig"); const Allocator = std.mem.Allocator;const CommandCatalog = command_specs.TopLevelRegistry;const TopLevelKind = command_specs.TopLevelKind; pub const Command = union(enum) {    interactive,    help,    ask: []const [:0]const u8,    acp: []const [:0]const u8,    pr: []const [:0]const u8,    issue: []const [:0]const u8,    login: []const [:0]const u8,    logout: []const [:0]const u8,    setup: []const [:0]const u8,    status: []const [:0]const u8,    permissions: []const [:0]const u8,    models: []const [:0]const u8,    doctor: []const [:0]const u8,    background: []const [:0]const u8,    teams: []const [:0]const u8,    session: []const [:0]const u8,    sessions: []const [:0]const u8,    resume_session: ResumeInvocation,    credits: []const [:0]const u8,    usage: []const [:0]const u8,    upgrade: []const [:0]const u8,    replay: []const [:0]const u8,    workspace: []const [:0]const u8,    unknown: []const u8,}; const ResumeInvocation = struct {    args: []const [:0]const u8,    top_level_alias: bool = false,}; const resume_id_alias_prefix = "--resume-";pub const upgrade_relaunch_arg = "--upgrade-relaunch"; // The one resume alias that asks which session to open. Every other spelling// names its target, so it resumes without a prompt.const resume_picker_alias = "-r"; pub const ResumeTarget = union(enum) {    pick,    last,    id: []u8,     pub fn deinit(self: *ResumeTarget, alloc: Allocator) void {        switch (self.*) {            .pick, .last => {},            .id => |value| alloc.free(value),        }        self.* = undefined;    }}; pub const LaunchModifiers = struct {    context_limit_overrides: []config_runtime.context_limits.Override = &.{},    additional_directories: [][]u8 = &.{},    saved_directories_suppressed: bool = false,     pub fn deinit(self: *LaunchModifiers, alloc: Allocator) void {        if (self.context_limit_overrides.len > 0) alloc.free(self.context_limit_overrides);        for (self.additional_directories) |path| alloc.free(path);        if (self.additional_directories.len > 0) alloc.free(self.additional_directories);        self.* = .{};    }     pub fn hasWorkspaceModifiers(self: LaunchModifiers) bool {        return self.additional_directories.len > 0 or self.saved_directories_suppressed;    }}; pub const InteractiveLaunch = struct {    requested_resume: ?ResumeTarget = null,    upgrade_relaunch: bool = false,    record_requested: bool = false,    modifiers: LaunchModifiers = .{},     pub fn deinit(self: *InteractiveLaunch, alloc: Allocator) void {        if (self.requested_resume) |*target| target.deinit(alloc);        self.modifiers.deinit(alloc);        self.* = undefined;    }}; pub const RunResult = union(enum) {    interactive: InteractiveLaunch,    handled_success,    handled_failure,    handled_exit: u8,}; pub const record_modifier_usage = "usage: fx --record is only supported for interactive startup\n";const version_usage = "usage: fx --version\n"; pub fn recordRequested(args: []const [:0]const u8) error{RecordModifierRequiresInteractive}!bool {    var count: usize = 0;    for (args) |arg| {        if (std.mem.eql(u8, arg, "--record")) count += 1;    }    if (count == 0) return false;    if (count != 1) return error.RecordModifierRequiresInteractive;    if (args.len == 1 and std.mem.eql(u8, args[0], "--record")) return true;    if (args.len >= 2 and        (std.mem.eql(u8, args[0], "resume") or std.mem.eql(u8, args[0], "--resume")) and        std.mem.eql(u8, args[args.len - 1], "--record")) return true;    return error.RecordModifierRequiresInteractive;} pub const Config = struct {    version: []const u8 = "",    revision: []const u8 = "",    build_channel: update_target.Channel = .stable,    command_catalog: CommandCatalog,    default_model: []const u8,    default_agent_step_limit: usize,    models_path: []const u8,    gateway_retry_count: usize,    gateway_chat_url: []const u8,    gateway_provider: gateway_provider.Provider,    background_process_provider: background_process_provider.Provider =        background_process_provider.unavailable_provider,    url_opener: host.UrlOpener,    secret_store: host.SecretStore,    prompt_policy: prompt_policy.Policy,    skill_root_policy: skill_contract.RootPolicy,    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,    max_tool_result_bytes: usize,    max_history_turns: usize,    context_registry: context_contract.Registry,    mode_registry: mode_registry.Registry,    tool_set: tool_set_contract.ToolSet,    inspect_mcp_profile_config: mcp_contract.InspectProfileConfigFn,    load_mcp_runtime: mcp_runtime.LoadRuntimeFn,    acp_runner: acp_runner.Runner,    devbox_provider: ?devbox_executor.Provider = null,    permission_reviewer_provider: ?permission_auto_classifier.Provider = null,}; const LocalSurfaceOptions = struct {    format: output_contracts.OutputFormat = .text,}; const UpgradeOptions = struct {    format: output_contracts.OutputFormat = .text,    channel: ?update_target.Channel = null,}; const SessionListOptions = struct {    format: output_contracts.OutputFormat = .text,    scope: session_store.SessionListScope = .current_workspace,    limit: usize = session_store.session_list_default_limit,    continuation: ?session_store.ResumableSessionContinuation = null,}; const UsageOptions = struct {    format: output_contracts.OutputFormat = .text,    scope: usage_report.Scope = .days_30,}; const WorkspaceOptions = struct {    format: output_contracts.OutputFormat = .text,    action: ?workspace_commands.Action = null,}; const PersistedRecordTarget = union(enum) {    last,    id: u64,}; const PersistedRecordOptions = struct {    format: output_contracts.OutputFormat = .text,    target: ?PersistedRecordTarget = null,}; // `fx session` reads one saved session, so it names its target and never// reaches for the picker that `ResumeTarget` carries.const SessionDetailTarget = union(enum) {    last,    id: []u8,     fn deinit(self: *SessionDetailTarget, alloc: Allocator) void {        switch (self.*) {            .last => {},            .id => |value| alloc.free(value),        }        self.* = undefined;    }}; const SessionDetailOptions = struct {    format: output_contracts.OutputFormat = .text,    target: ?SessionDetailTarget = null,     fn deinit(self: *SessionDetailOptions, alloc: Allocator) void {        if (self.target) |*target| target.deinit(alloc);        self.* = undefined;    }}; const SessionMigrationOptions = struct {    format: output_contracts.OutputFormat = .text,    session_id: []u8,    allow_large: bool = false,     fn deinit(self: *SessionMigrationOptions, alloc: Allocator) void {        alloc.free(self.session_id);        self.* = undefined;    }}; const SessionRecoveryOptions = struct {    format: output_contracts.OutputFormat = .text,    session_id: []u8,     fn deinit(self: *SessionRecoveryOptions, alloc: Allocator) void {        alloc.free(self.session_id);        self.* = undefined;    }}; const AcpOptions = struct {    model: ?[]const u8 = null,    log_file: ?[]const u8 = null,}; const WorkflowOptions = struct {    auto_permission: bool,    create: bool,    context: []u8,     fn deinit(self: WorkflowOptions, alloc: Allocator) void {        alloc.free(self.context);    }}; const WriteFn = *const fn (?*anyopaque, []const u8) anyerror!void;const LoadStartupStateFn = *const fn (Allocator, oauth_transport.Provider, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadCatalogStartupStateFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadStartupStateWithoutCredentialsFn = *const fn (Allocator, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadStartupStatusFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupStatus;const GetenvFn = *const fn (?*anyopaque, []const u8) ?[]const u8;const EnvironMapFn = *const fn (?*anyopaque) ?*const std.process.Environ.Map;const SelfExePathFn = *const fn (?*anyopaque, Allocator) anyerror![]u8;const ReadMaskedKeyFn = *const fn (?*anyopaque, Allocator, WriteFn, ?*anyopaque) anyerror![]u8;const SetupTerminalAvailableFn = *const fn (?*anyopaque) bool;const RunDeps = struct {    stdout_ctx: ?*anyopaque = null,    stderr_ctx: ?*anyopaque = null,    env_ctx: ?*anyopaque = null,    self_exe_ctx: ?*anyopaque = null,    setup_ctx: ?*anyopaque = null,    write_stdout: WriteFn = writeRealStdout,    write_stderr: WriteFn = writeRealStderr,    load_startup_state: LoadStartupStateFn = app_lifecycle.loadStartupState,    load_catalog_startup_state: LoadCatalogStartupStateFn = app_lifecycle.loadCatalogStartupState,    load_startup_state_without_credentials: LoadStartupStateWithoutCredentialsFn = app_lifecycle.loadStartupStateWithoutCredentials,    load_startup_status: LoadStartupStatusFn = app_lifecycle.loadStartupStatus,    getenv: GetenvFn = getenvDefault,    environ_map: EnvironMapFn = environMapDefault,    self_exe_path: SelfExePathFn = selfExePathDefault,    read_masked_key: ReadMaskedKeyFn = readMaskedKeyDefault,    setup_terminal_available: SetupTerminalAvailableFn = setupTerminalAvailableDefault,}; const GlobalLaunchArgs = struct {    remaining: []const [:0]const u8,    modifiers: LaunchModifiers = .{},     fn deinit(self: *GlobalLaunchArgs, alloc: Allocator) void {        self.modifiers.deinit(alloc);        self.* = undefined;    }     fn takeModifiers(self: *GlobalLaunchArgs) LaunchModifiers {        const result = self.modifiers;        self.modifiers = .{};        return result;    }}; fn parseGlobalLaunchArgs(    alloc: Allocator,    args: []const [:0]const u8,) !GlobalLaunchArgs {    var overrides: std.ArrayList(config_runtime.context_limits.Override) = .empty;    errdefer overrides.deinit(alloc);    var directories: std.ArrayList([]u8) = .empty;    errdefer {        for (directories.items) |path| alloc.free(path);        directories.deinit(alloc);    }    var suppress_saved = false;     var index: usize = 0;    while (index < args.len) {        const arg = args[index];        if (std.mem.eql(u8, arg, "--context-limit")) {            index += 1;            if (index >= args.len) return error.MissingContextLimitValue;            try overrides.append(alloc, try config_runtime.context_limits.parseOverride(args[index]));        } else if (std.mem.startsWith(u8, arg, "--context-limit=")) {            try overrides.append(alloc, try config_runtime.context_limits.parseOverride(arg["--context-limit=".len..]));        } else if (std.mem.eql(u8, arg, "--add-dir")) {            index += 1;            if (index >= args.len or args[index].len == 0) return error.MissingAddDirectoryValue;            try directories.append(alloc, try alloc.dupe(u8, args[index]));        } else if (std.mem.startsWith(u8, arg, "--add-dir=")) {            const value = arg["--add-dir=".len..];            if (value.len == 0) return error.MissingAddDirectoryValue;            try directories.append(alloc, try alloc.dupe(u8, value));        } else if (std.mem.eql(u8, arg, "--no-additional-dirs")) {            if (suppress_saved) return error.DuplicateAdditionalDirectorySuppression;            suppress_saved = true;        } else {            break;        }        index += 1;    }     const override_slice = try overrides.toOwnedSlice(alloc);    errdefer if (override_slice.len > 0) alloc.free(override_slice);    const directory_slice = try directories.toOwnedSlice(alloc);    return .{        .remaining = args[index..],        .modifiers = .{            .context_limit_overrides = override_slice,            .additional_directories = directory_slice,            .saved_directories_suppressed = suppress_saved,        },    };} /// Returns the command that follows the supported global launch modifiers./// Startup uses this same surface to select the full runtime configuration/// before the allocating parser runs.pub fn commandAfterGlobalLaunchArgs(args: []const [:0]const u8) ?[]const u8 {    var index: usize = 0;    while (index < args.len) {        const arg = args[index];        if (std.mem.eql(u8, arg, "--context-limit") or std.mem.eql(u8, arg, "--add-dir")) {            index += 1;            if (index >= args.len) return null;        } else if (!std.mem.startsWith(u8, arg, "--context-limit=") and            !std.mem.startsWith(u8, arg, "--add-dir=") and            !std.mem.eql(u8, arg, "--no-additional-dirs"))        {            return arg;        }        index += 1;    }    return null;} pub fn parse(command_catalog: CommandCatalog, args: []const [:0]const u8) Command {    @setRuntimeSafety(false);    if (args.len == 0) return .interactive;     const command = args[0];    if (command.len == 0) return .{ .unknown = command };    switch (command[0]) {        '-', 'h' => {            if (command_specs.matchesTopLevel(command_catalog, command, .help)) return .help;            if (command_specs.matchesTopLevel(command_catalog, command, .@"resume") or                std.mem.startsWith(u8, command, resume_id_alias_prefix))            {                return .{ .resume_session = .{                    .args = args,                    .top_level_alias = true,                } };            }        },        'a' => {            if (command_specs.matchesTopLevel(command_catalog, command, .ask)) return .{ .ask = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .acp)) return .{ .acp = args[1..] };        },        'b' => {            if (command_specs.matchesTopLevel(command_catalog, command, .background)) return .{ .background = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] };        },        'c' => {            if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] };        },        'd' => {            if (command_specs.matchesTopLevel(command_catalog, command, .doctor)) return .{ .doctor = args[1..] };        },        'i' => {            if (command_specs.matchesTopLevel(command_catalog, command, .issue)) return .{ .issue = args[1..] };        },        'l' => {            if (command_specs.matchesTopLevel(command_catalog, command, .login)) return .{ .login = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .logout)) return .{ .logout = args[1..] };        },        'm' => {            if (command_specs.matchesTopLevel(command_catalog, command, .models)) return .{ .models = args[1..] };        },        'p' => {            if (command_specs.matchesTopLevel(command_catalog, command, .pr)) return .{ .pr = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .permissions)) return .{ .permissions = args[1..] };        },        'r' => {            if (command_specs.matchesTopLevel(command_catalog, command, .@"resume")) return .{ .resume_session = .{ .args = args[1..] } };            if (command_specs.matchesTopLevel(command_catalog, command, .replay)) return .{ .replay = args[1..] };        },        's' => {            if (command_specs.matchesTopLevel(command_catalog, command, .setup)) return .{ .setup = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .status)) return .{ .status = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .sessions)) return .{ .sessions = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .session)) return .{ .session = args[1..] };        },        't' => {            if (command_specs.matchesTopLevel(command_catalog, command, .teams)) return .{ .teams = args[1..] };        },        'u' => {            if (command_specs.matchesTopLevel(command_catalog, command, .usage)) return .{ .usage = args[1..] };            if (command_specs.matchesTopLevel(command_catalog, command, .upgrade)) return .{ .upgrade = args[1..] };        },        'w' => {            if (command_specs.matchesTopLevel(command_catalog, command, .workspace)) return .{ .workspace = args[1..] };        },        else => {},    }    return .{ .unknown = command };} pub const NonInteractiveLaunch = struct {    global_args: GlobalLaunchArgs,    effective_args: []const [:0]const u8,    command: Command,     pub fn deinit(self: *NonInteractiveLaunch, alloc: Allocator) void {        self.global_args.deinit(alloc);        self.* = undefined;    }}; pub const InteractiveLaunchParseResult = union(enum) {    interactive: InteractiveLaunch,    noninteractive: NonInteractiveLaunch,}; /// Parses the shared interactive launch language without dispatching commands./// Returned launch values own their allocations and must be deinitialized.pub fn parseInteractiveLaunch(    alloc: Allocator,    args: []const [:0]const u8,

Lines 1–500 of 5134.