const std = @import("std"); const ws = @import("../../net/ws/client.zig"); const protocol = @import("../../net/protocol.zig"); const crypto = @import("../../utils/crypto.zig"); const Config = @import("../../config.zig").Config; const core = @import("../../core.zig"); const history = @import("../../utils/history.zig"); /// Job submission configuration pub const SubmitConfig = struct { job_names: []const []const u8, commit_id: ?[]const u8, priority: u8, snapshot_id: ?[]const u8, snapshot_sha256: ?[]const u8, args_override: ?[]const u8, note_override: ?[]const u8, cpu: u8, memory: u8, gpu: u8, gpu_memory: ?[]const u8, dry_run: bool, force: bool, runner_args: []const []const u8, pub fn estimateTotalJobs(self: SubmitConfig) usize { return self.job_names.len; } }; /// Submission result pub const SubmitResult = struct { success: bool, job_count: usize, errors: std.ArrayList([]const u8), pub fn init() SubmitResult { return .{ .success = true, .job_count = 0, .errors = .empty, }; } pub fn deinit(self: *SubmitResult, allocator: std.mem.Allocator) void { for (self.errors.items) |err| { allocator.free(err); } self.errors.deinit(allocator); } }; /// Submit jobs to the server pub fn submitJobs( allocator: std.mem.Allocator, config: Config, submit_config: SubmitConfig, json: bool, ) !SubmitResult { var result = SubmitResult.init(allocator); errdefer result.deinit(allocator); // Dry run mode - just print what would be submitted if (submit_config.dry_run) { if (json) { std.debug.print("{{\"success\":true,\"command\":\"queue.submit\",\"dry_run\":true,\"jobs\":[", .{}); for (submit_config.job_names, 0..) |name, i| { if (i > 0) std.debug.print(",", .{}); std.debug.print("\"{s}\"", .{name}); } std.debug.print("],\"total\":{d}}}}}\n", .{submit_config.job_names.len}); } else { std.debug.print("[DRY RUN] Would submit {d} jobs:\n", .{submit_config.job_names.len}); for (submit_config.job_names) |name| { std.debug.print(" - {s}\n", .{name}); } } result.job_count = submit_config.job_names.len; return result; } // Get WebSocket URL const ws_url = try config.getWebSocketUrl(allocator); defer allocator.free(ws_url); // Hash API key const api_key_hash = try crypto.hashApiKey(allocator, config.api_key); defer allocator.free(api_key_hash); // Connect to server var client = ws.Client.connect(allocator, ws_url, config.api_key) catch |err| { const msg = try std.fmt.allocPrint(allocator, "Failed to connect: {}", .{err}); result.addError(msg); result.success = false; return result; }; defer client.close(); // Submit each job for (submit_config.job_names) |job_name| { submitSingleJob( allocator, &client, api_key_hash, job_name, submit_config, &result, ) catch |err| { const msg = try std.fmt.allocPrint(allocator, "Failed to submit {s}: {}", .{ job_name, err }); result.addError(msg); result.success = false; }; } // Save to history if successful if (result.success and result.job_count > 0) { if (submit_config.commit_id) |commit_id| { for (submit_config.job_names) |job_name| { history.saveEntry(allocator, job_name, commit_id) catch {}; } } } return result; } /// Submit a single job fn submitSingleJob( allocator: std.mem.Allocator, client: *ws.Client, _: []const u8, job_name: []const u8, submit_config: SubmitConfig, result: *SubmitResult, ) !void { // Build job submission payload var payload = std.ArrayList(u8).init(allocator); defer payload.deinit(); const writer = payload.writer(); try writer.print( "{{\"job_name\":\"{s}\",\"priority\":{d},\"resources\":{{\"cpu\":{d},\"memory\":{d},\"gpu\":{d}", .{ job_name, submit_config.priority, submit_config.cpu, submit_config.memory, submit_config.gpu }, ); if (submit_config.gpu_memory) |gm| { try writer.print(",\"gpu_memory\":\"{s}\"", .{gm}); } try writer.print("}}", .{}); if (submit_config.commit_id) |cid| { try writer.print(",\"commit_id\":\"{s}\"", .{cid}); } if (submit_config.snapshot_id) |sid| { try writer.print(",\"snapshot_id\":\"{s}\"", .{sid}); } if (submit_config.note_override) |note| { try writer.print(",\"note\":\"{s}\"", .{note}); } try writer.print("}}", .{}); // Send job submission client.sendMessage(payload.items) catch |err| { return err; }; result.job_count += 1; } /// Print submission results pub fn printResults(result: SubmitResult, json: bool) void { if (json) { const status = if (result.success) "true" else "false"; std.debug.print("{{\"success\":{s},\"command\":\"queue.submit\",\"data\":{{\"submitted\":{d}", .{ status, result.job_count }); if (result.errors.items.len > 0) { std.debug.print(",\"errors\":[", .{}); for (result.errors.items, 0..) |err, i| { if (i > 0) std.debug.print(",", .{}); std.debug.print("\"{s}\"", .{err}); } std.debug.print("]", .{}); } std.debug.print("}}}}\n", .{}); } else { if (result.success) { std.debug.print("Successfully submitted {d} jobs\n", .{result.job_count}); } else { std.debug.print("Failed to submit jobs ({d} errors)\n", .{result.errors.items.len}); for (result.errors.items) |err| { std.debug.print(" Error: {s}\n", .{err}); } } } }