const std = @import("std");const paste_blocks = @import("../input/pasted_blocks.zig");const registered_entities = @import("../input/registered_entities.zig");const image_attachments = @import("../images/image_attachments.zig");const command_specs = @import("../slash_commands/command_specs.zig");const io_mod = @import("../shared/io.zig");const debug_trace = @import("../shared/debug_trace.zig");const entity_spans = @import("../shared/entity_spans.zig");const types = @import("../shared/types.zig");const input_queue_runtime = @import("input_queue_runtime.zig");const input_completion_runtime = @import("input_completion_runtime.zig");const input_limit_feedback = @import("input_limit_feedback.zig"); pub fn SubmitRuntime(comptime App: type) type { return struct { const queue_rt = input_queue_runtime.Runtime(App); const completion_rt = input_completion_runtime.CompletionRuntime(App); const AcceptedDraftProjection = struct { input: []const u8, pasted_blocks: std.ArrayList(paste_blocks.PastedBlock) = .empty, image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty, skill_tokens: std.ArrayList(registered_entities.SkillTokenSpan) = .empty, fn deinit(self: *AcceptedDraftProjection, alloc: std.mem.Allocator) void { self.pasted_blocks.deinit(alloc); self.image_tokens.deinit(alloc); self.skill_tokens.deinit(alloc); self.* = undefined; } }; pub fn submitInput(app: *App, max_prompt_history: usize) !void { try submit(app, max_prompt_history); } pub fn submit(app: *App, max_prompt_history: usize) !void { const expanded_len = paste_blocks.expandedLen( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, ) orelse std.math.maxInt(usize); const max_input_len = if (comptime @hasDecl(App, "input_limits")) App.input_limits.composer_bytes else if (comptime @hasDecl(App, "input_byte_limit")) App.input_byte_limit else std.math.maxInt(usize); if (expanded_len > max_input_len) { try input_limit_feedback.report( App, app, .composer, expanded_len, ); return; } const expanded = try paste_blocks.expand(app.alloc, app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items); defer if (expanded.owned) app.alloc.free(expanded.text); if (directCommand(expanded.text)) |command| { if (comptime @hasDecl(App, "submitDirectTerminal")) { try App.submitDirectTerminal(app, command); return; } } const left_trimmed = std.mem.trimStart(u8, expanded.text, " \t\r\n"); const resolved_slash_submission = resolvedSlashSubmission(app, left_trimmed); if (knownSlashCommand(app, resolved_slash_submission)) |command| { if (requiresPromptCredential(command, resolved_slash_submission) and !try preflightPrompt(app)) return; try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (shouldRouteUnknownSlashCommand(app, resolved_slash_submission)) { try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (pendingImagesPrefixEnd( app.input_runtime.edit_state.input.items, expanded.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, )) |command_start| { const command_text = expanded.text[command_start..]; if (knownSlashCommand(app, command_text)) |command| { if (requiresPromptCredential(command, command_text) and !try preflightPrompt(app)) return; try submitSlashCommand( app, command_text, expanded.text[0..command_start], max_prompt_history, ); return; } } const trimmed = std.mem.trim(u8, expanded.text, " \t\r\n"); if (trimmed.len == 0) { if (app.pending_images.items.len > 0) { if (!try preflightPrompt(app)) return; if (!try enqueuePromptForSubmit(app, "", &.{}, null)) return; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } return; } if (comptime @hasField(App, "queued_prompt_review")) { if (try queue_rt.submitPausedQueueUnchanged(app)) { beginIdleSubmittedPromptTransition(app); return; } } app.input_runtime.inputResetState().clearCurrent(app.alloc); app.shell.render_requests.request(.footer); return; } const first_image_id = try firstAvailableImageId(app.pending_images.items); var extracted = image_attachments.extractInlineImageAttachments( app.alloc, app.workspace_root, trimmed, first_image_id, ) catch |err| { const message = switch (err) { error.UnsupportedImageType => try app.alloc.dupe(u8, "unsupported image type"), error.FileNotFound => try app.alloc.dupe(u8, "image file not found"), error.ImageTooLarge => try app.alloc.dupe(u8, image_attachments.image_too_large_notice), else => try std.fmt.allocPrint(app.alloc, "failed to attach image: {s}", .{@errorName(err)}), }; defer app.alloc.free(message); try app.writeDomainNotice(.{ .topic = "images", .tone = .@"error", .body = message, }, true); app.shell.render_requests.request(.footer); return; }; var discard_extracted = true; defer if (discard_extracted) extracted.discard(app.alloc) else extracted.deinit(app.alloc); try assignStableExtractedImageIds(app, &extracted); if (try captureExtractedImages(app, extracted.images) == .rejected) { app.shell.render_requests.request(.footer); return; } const has_inline_images = extracted.images.len > 0; const effective_text = if (has_inline_images) extracted.text else expanded.text; if (!try preflightPrompt(app)) return; var staged_images = if (app.pending_images.items.len > 0 or extracted.images.len > 0) try stagePendingImages(app.alloc, app.pending_images.items, extracted.images) else null; defer if (staged_images) |*images| deinitOwnedImageList(app.alloc, images); var image_occurrences = try projectImageOccurrencesForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, trimmed, effective_text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, extracted.image_spans, extracted.images, has_inline_images, ); defer image_occurrences.deinit(app.alloc); // One mapping owns both placeholder IDs and attachment order. const visual_text = if (staged_images) |*images| try mapSubmittedImages( app.alloc, effective_text, images, image_occurrences.items, firstRemappedImageId(app), ) else VisualText{ .text = effective_text, .owned = false }; defer if (visual_text.owned) app.alloc.free(visual_text.text); const display_skill_tokens = try projectSkillTokensForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, trimmed, effective_text, visual_text.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.skill_tokens.items, has_inline_images, image_occurrences.items, ); defer if (display_skill_tokens.len > 0) app.alloc.free(display_skill_tokens); var accepted_draft = try prepareAcceptedDraftProjection( app, expanded.text, visual_text.text, display_skill_tokens, image_occurrences.items, has_inline_images, ); defer accepted_draft.deinit(app.alloc); const has_images_for_submit = if (staged_images) |*images| images.items.len > 0 else app.pending_images.items.len > 0; const queued = if (staged_images) |*images| try enqueuePromptWithStagedImages( app, visual_text.text, display_skill_tokens, &accepted_draft, images, ) else try enqueuePromptForSubmit( app, visual_text.text, display_skill_tokens, &accepted_draft, ); if (!queued) return; commitStableExtractedImageIds(app, extracted.images); commitRemappedImageIds(app, visual_text.next_image_id); if (visual_text.text.len > 0) { recordAcceptedPromptComposerHistory( app, max_prompt_history, &accepted_draft, ); } if (!has_images_for_submit and visual_text.text.len > 0) { recordAcceptedInput(app, visual_text.text); } discard_extracted = false; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } } fn acceptedPromptNeedsImmediateFooter(app: *const App) bool { if (comptime @hasField(App, "stream")) return app.stream.active; return true; } fn preflightPrompt(app: *App) !bool { if (comptime @hasDecl(App, "ensurePromptCredential")) { return App.ensurePromptCredential(app); } return true; } fn knownSlashCommand(app: *const App, text: []const u8) ?*const command_specs.SlashSpec { if (text.len == 0 or text[0] != '/') return null; var command_end: usize = 1; while (command_end < text.len and !std.ascii.isWhitespace(text[command_end])) : (command_end += 1) {} return app.slashRegistry().lookup(text[0..command_end]); } fn shouldRouteUnknownSlashCommand(app: *App, text: []const u8) bool { if (text.len == 0 or text[0] != '/') return false; const command_end = std.mem.indexOfAny(u8, text, " \t\r\n") orelse text.len; if (command_end != text.len) return false; if (std.mem.indexOfScalar(u8, text[1..command_end], '/') != null) return false; if (app.input_runtime.picker.isInlinePickerDismissed(.slash)) return false; if (app.input_runtime.picker.inlinePickerTriggerKind(&app.input_runtime.edit_state) != .slash) return false; return completion_rt.slashCompletionCandidateCount(app) == 0; } fn requiresPromptCredential(command: *const command_specs.SlashSpec, submission: []const u8) bool { if (!command.requires_prompt_credential) return false; if (command.kind != .model) return true; var command_end: usize = 1; while (command_end < submission.len and !std.ascii.isWhitespace(submission[command_end])) : (command_end += 1) {} return std.mem.trim(u8, submission[command_end..], " \t\r\n").len > 0; } fn pendingImagesPrefixEnd( raw_input: []const u8, expanded_text: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, ) ?usize { var index: usize = 0; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {} var saw_placeholder = false; while (projectedImageTokenAt( raw_input, pasted_blocks, image_tokens, images, index, )) |projected| { saw_placeholder = true; index = projected.end; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {} } if (!saw_placeholder) return null; return index; } fn projectedImageTokenAt( raw_input: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, expanded_start: usize, ) ?Span { for (image_tokens) |token| { if (!validRawImageToken(raw_input, token)) continue; if (image_attachments.findImageIndexById(images, token.id) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_start, ) orelse continue; if (start < expanded_start) continue; if (start > expanded_start) return null; const end = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_end, ) orelse return null; return .{ .start = start, .end = end }; } return null; } fn submitSlashCommand( app: *App, text: []const u8, retained_prefix: ?[]const u8, max_prompt_history: usize, ) !void { const command_text = resolvedSlashSubmission(app, text); const command_copy = try app.alloc.dupe(u8, command_text); defer app.alloc.free(command_copy); const prefix_copy: ?[]u8 = if (retained_prefix) |prefix| try app.alloc.dupe(u8, prefix) else null; defer if (prefix_copy) |prefix| app.alloc.free(prefix); var retained_image_tokens = if (prefix_copy) |prefix| try projectRetainedImageTokens(app, prefix.len) else std.ArrayList(entity_spans.ImageTokenSpan).empty; defer retained_image_tokens.deinit(app.alloc); if (prefix_copy) |prefix| { try app.input_runtime.edit_state.input.ensureTotalCapacity(app.alloc, prefix.len); try app.input_runtime.entities.image_tokens.ensureTotalCapacity( app.alloc, retained_image_tokens.items.len, ); } app.input_runtime.inputResetState().clearCurrent(app.alloc); if (prefix_copy) |prefix| { app.input_runtime.edit_state.insertSliceAssumeCapacity(prefix); app.input_runtime.entities.image_tokens.appendSliceAssumeCapacity( retained_image_tokens.items, ); } paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (retained_prefix == null and app.pending_images.items.len > 0) { debug_trace.logf( "input", "draft images dropped count={d} reason=local_command", .{app.pending_images.items.len}, ); app.clearPendingImages(); } app.shell.render_requests.request(.footer); defer if (retained_prefix != null) { if (app.pending_images.items.len == 0) { app.input_runtime.inputResetState().clearCurrent(app.alloc); } app.shell.render_requests.request(.footer); }; recordAcceptedSlashCommandHistory( app, max_prompt_history, command_copy, ); try App.handleCommand(app, command_copy); } fn projectRetainedImageTokens( app: *App, expanded_prefix_len: usize, ) !std.ArrayList(entity_spans.ImageTokenSpan) { var projected: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; errdefer projected.deinit(app.alloc); for (app.input_runtime.entities.image_tokens.items) |token| { if (!validRawImageToken(app.input_runtime.edit_state.input.items, token)) continue; if (image_attachments.findImageIndexById( app.pending_images.items, token.id, ) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_start, ) orelse continue; const end = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_end, ) orelse continue; if (end > expanded_prefix_len) continue; try projected.append(app.alloc, .{ .id = token.id, .span = .{ .raw_start = start, .raw_end = end }, }); } return projected; } fn resolvedSlashSubmission(app: *App, text: []const u8) []const u8 { if (completion_rt.visibleSlashCompletionCount(app) == 0) return text; return resolveSlashSubmission(app.slashRegistry(), text, app.input_runtime.picker.slash_completion_index); } fn enqueuePromptForSubmit( app: *App, prompt: []const u8, skill_tokens: []const registered_entities.SkillTokenSpan, accepted_draft: ?*const AcceptedDraftProjection, ) !bool { const resume_review = if (comptime @hasField(App, "queued_prompt_review")) app.queued_prompt_review.active() else false; if (comptime @hasField(App, "queued_prompt_review")) { switch (try queue_rt.promptAdmission( app, if (accepted_draft) |draft| draft.input else prompt, if (accepted_draft) |draft| draft.pasted_blocks.items else &.{}, if (accepted_draft) |draft| draft.image_tokens.items else &.{}, if (accepted_draft) |draft| draft.skill_tokens.items else skill_tokens, )) { .replaced => { beginIdleSubmittedPromptTransition(app); return true; }, .enqueue => {}, } } const accepted = if (comptime @hasDecl(App, "enqueuePromptWithReviewDraft")) blk: { if (accepted_draft) |draft| { break :blk try App.enqueuePromptWithReviewDraft( app, prompt, skill_tokens, draft.input, draft.pasted_blocks.items, draft.image_tokens.items, draft.skill_tokens.items, ); } break :blk try App.enqueuePromptWithSkillBindings( app, prompt, skill_tokens, ); } else if (comptime @hasDecl(App, "enqueuePromptWithSkillBindings")) try App.enqueuePromptWithSkillBindings(app, prompt, skill_tokens) else try App.enqueuePrompt(app, prompt); if (!accepted) return false; if (resume_review) { if (comptime @hasField(App, "queued_prompt_review")) { queue_rt.resumeAfterNewPrompt(app); } } beginIdleSubmittedPromptTransition(app); return true; } fn beginIdleSubmittedPromptTransition(app: *App) void {Lines 1–500 of 1510. Load more
const std = @import("std"); const paste_blocks = @import("../input/pasted_blocks.zig"); const registered_entities = @import("../input/registered_entities.zig"); const image_attachments = @import("../images/image_attachments.zig"); const command_specs = @import("../slash_commands/command_specs.zig"); const io_mod = @import("../shared/io.zig"); const debug_trace = @import("../shared/debug_trace.zig"); const entity_spans = @import("../shared/entity_spans.zig"); const types = @import("../shared/types.zig"); const input_queue_runtime = @import("input_queue_runtime.zig"); const input_completion_runtime = @import("input_completion_runtime.zig"); const input_limit_feedback = @import("input_limit_feedback.zig"); pub fn SubmitRuntime(comptime App: type) type { return struct { const queue_rt = input_queue_runtime.Runtime(App); const completion_rt = input_completion_runtime.CompletionRuntime(App); const AcceptedDraftProjection = struct { input: []const u8, pasted_blocks: std.ArrayList(paste_blocks.PastedBlock) = .empty, image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty, skill_tokens: std.ArrayList(registered_entities.SkillTokenSpan) = .empty, fn deinit(self: *AcceptedDraftProjection, alloc: std.mem.Allocator) void { self.pasted_blocks.deinit(alloc); self.image_tokens.deinit(alloc); self.skill_tokens.deinit(alloc); self.* = undefined; } }; pub fn submitInput(app: *App, max_prompt_history: usize) !void { try submit(app, max_prompt_history); } pub fn submit(app: *App, max_prompt_history: usize) !void { const expanded_len = paste_blocks.expandedLen( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, ) orelse std.math.maxInt(usize); const max_input_len = if (comptime @hasDecl(App, "input_limits")) App.input_limits.composer_bytes else if (comptime @hasDecl(App, "input_byte_limit")) App.input_byte_limit else std.math.maxInt(usize); if (expanded_len > max_input_len) { try input_limit_feedback.report( App, app, .composer, expanded_len, ); return; } const expanded = try paste_blocks.expand(app.alloc, app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items); defer if (expanded.owned) app.alloc.free(expanded.text); if (directCommand(expanded.text)) |command| { if (comptime @hasDecl(App, "submitDirectTerminal")) { try App.submitDirectTerminal(app, command); return; } } const left_trimmed = std.mem.trimStart(u8, expanded.text, " \t\r\n"); const resolved_slash_submission = resolvedSlashSubmission(app, left_trimmed); if (knownSlashCommand(app, resolved_slash_submission)) |command| { if (requiresPromptCredential(command, resolved_slash_submission) and !try preflightPrompt(app)) return; try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (shouldRouteUnknownSlashCommand(app, resolved_slash_submission)) { try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (pendingImagesPrefixEnd( app.input_runtime.edit_state.input.items, expanded.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, )) |command_start| { const command_text = expanded.text[command_start..]; if (knownSlashCommand(app, command_text)) |command| { if (requiresPromptCredential(command, command_text) and !try preflightPrompt(app)) return; try submitSlashCommand( app, command_text, expanded.text[0..command_start], max_prompt_history, ); return; } } const trimmed = std.mem.trim(u8, expanded.text, " \t\r\n"); if (trimmed.len == 0) { if (app.pending_images.items.len > 0) { if (!try preflightPrompt(app)) return; if (!try enqueuePromptForSubmit(app, "", &.{}, null)) return; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } return; } if (comptime @hasField(App, "queued_prompt_review")) { if (try queue_rt.submitPausedQueueUnchanged(app)) { beginIdleSubmittedPromptTransition(app); return; } } app.input_runtime.inputResetState().clearCurrent(app.alloc); app.shell.render_requests.request(.footer); return; } const first_image_id = try firstAvailableImageId(app.pending_images.items); var extracted = image_attachments.extractInlineImageAttachments( app.alloc, app.workspace_root, trimmed, first_image_id, ) catch |err| { const message = switch (err) { error.UnsupportedImageType => try app.alloc.dupe(u8, "unsupported image type"), error.FileNotFound => try app.alloc.dupe(u8, "image file not found"), error.ImageTooLarge => try app.alloc.dupe(u8, image_attachments.image_too_large_notice), else => try std.fmt.allocPrint(app.alloc, "failed to attach image: {s}", .{@errorName(err)}), }; defer app.alloc.free(message); try app.writeDomainNotice(.{ .topic = "images", .tone = .@"error", .body = message, }, true); app.shell.render_requests.request(.footer); return; }; var discard_extracted = true; defer if (discard_extracted) extracted.discard(app.alloc) else extracted.deinit(app.alloc); try assignStableExtractedImageIds(app, &extracted); if (try captureExtractedImages(app, extracted.images) == .rejected) { app.shell.render_requests.request(.footer); return; } const has_inline_images = extracted.images.len > 0; const effective_text = if (has_inline_images) extracted.text else expanded.text; if (!try preflightPrompt(app)) return; var staged_images = if (app.pending_images.items.len > 0 or extracted.images.len > 0) try stagePendingImages(app.alloc, app.pending_images.items, extracted.images) else null; defer if (staged_images) |*images| deinitOwnedImageList(app.alloc, images); var image_occurrences = try projectImageOccurrencesForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, trimmed, effective_text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, extracted.image_spans, extracted.images, has_inline_images, ); defer image_occurrences.deinit(app.alloc); // One mapping owns both placeholder IDs and attachment order. const visual_text = if (staged_images) |*images| try mapSubmittedImages( app.alloc, effective_text, images, image_occurrences.items, firstRemappedImageId(app), ) else VisualText{ .text = effective_text, .owned = false }; defer if (visual_text.owned) app.alloc.free(visual_text.text); const display_skill_tokens = try projectSkillTokensForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, trimmed, effective_text, visual_text.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.skill_tokens.items, has_inline_images, image_occurrences.items, ); defer if (display_skill_tokens.len > 0) app.alloc.free(display_skill_tokens); var accepted_draft = try prepareAcceptedDraftProjection( app, expanded.text, visual_text.text, display_skill_tokens, image_occurrences.items, has_inline_images, ); defer accepted_draft.deinit(app.alloc); const has_images_for_submit = if (staged_images) |*images| images.items.len > 0 else app.pending_images.items.len > 0; const queued = if (staged_images) |*images| try enqueuePromptWithStagedImages( app, visual_text.text, display_skill_tokens, &accepted_draft, images, ) else try enqueuePromptForSubmit( app, visual_text.text, display_skill_tokens, &accepted_draft, ); if (!queued) return; commitStableExtractedImageIds(app, extracted.images); commitRemappedImageIds(app, visual_text.next_image_id); if (visual_text.text.len > 0) { recordAcceptedPromptComposerHistory( app, max_prompt_history, &accepted_draft, ); } if (!has_images_for_submit and visual_text.text.len > 0) { recordAcceptedInput(app, visual_text.text); } discard_extracted = false; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } } fn acceptedPromptNeedsImmediateFooter(app: *const App) bool { if (comptime @hasField(App, "stream")) return app.stream.active; return true; } fn preflightPrompt(app: *App) !bool { if (comptime @hasDecl(App, "ensurePromptCredential")) { return App.ensurePromptCredential(app); } return true; } fn knownSlashCommand(app: *const App, text: []const u8) ?*const command_specs.SlashSpec { if (text.len == 0 or text[0] != '/') return null; var command_end: usize = 1; while (command_end < text.len and !std.ascii.isWhitespace(text[command_end])) : (command_end += 1) {} return app.slashRegistry().lookup(text[0..command_end]); } fn shouldRouteUnknownSlashCommand(app: *App, text: []const u8) bool { if (text.len == 0 or text[0] != '/') return false; const command_end = std.mem.indexOfAny(u8, text, " \t\r\n") orelse text.len; if (command_end != text.len) return false; if (std.mem.indexOfScalar(u8, text[1..command_end], '/') != null) return false; if (app.input_runtime.picker.isInlinePickerDismissed(.slash)) return false; if (app.input_runtime.picker.inlinePickerTriggerKind(&app.input_runtime.edit_state) != .slash) return false; return completion_rt.slashCompletionCandidateCount(app) == 0; } fn requiresPromptCredential(command: *const command_specs.SlashSpec, submission: []const u8) bool { if (!command.requires_prompt_credential) return false; if (command.kind != .model) return true; var command_end: usize = 1; while (command_end < submission.len and !std.ascii.isWhitespace(submission[command_end])) : (command_end += 1) {} return std.mem.trim(u8, submission[command_end..], " \t\r\n").len > 0; } fn pendingImagesPrefixEnd( raw_input: []const u8, expanded_text: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, ) ?usize { var index: usize = 0; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {} var saw_placeholder = false; while (projectedImageTokenAt( raw_input, pasted_blocks, image_tokens, images, index, )) |projected| { saw_placeholder = true; index = projected.end; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {} } if (!saw_placeholder) return null; return index; } fn projectedImageTokenAt( raw_input: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, expanded_start: usize, ) ?Span { for (image_tokens) |token| { if (!validRawImageToken(raw_input, token)) continue; if (image_attachments.findImageIndexById(images, token.id) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_start, ) orelse continue; if (start < expanded_start) continue; if (start > expanded_start) return null; const end = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_end, ) orelse return null; return .{ .start = start, .end = end }; } return null; } fn submitSlashCommand( app: *App, text: []const u8, retained_prefix: ?[]const u8, max_prompt_history: usize, ) !void { const command_text = resolvedSlashSubmission(app, text); const command_copy = try app.alloc.dupe(u8, command_text); defer app.alloc.free(command_copy); const prefix_copy: ?[]u8 = if (retained_prefix) |prefix| try app.alloc.dupe(u8, prefix) else null; defer if (prefix_copy) |prefix| app.alloc.free(prefix); var retained_image_tokens = if (prefix_copy) |prefix| try projectRetainedImageTokens(app, prefix.len) else std.ArrayList(entity_spans.ImageTokenSpan).empty; defer retained_image_tokens.deinit(app.alloc); if (prefix_copy) |prefix| { try app.input_runtime.edit_state.input.ensureTotalCapacity(app.alloc, prefix.len); try app.input_runtime.entities.image_tokens.ensureTotalCapacity( app.alloc, retained_image_tokens.items.len, ); } app.input_runtime.inputResetState().clearCurrent(app.alloc); if (prefix_copy) |prefix| { app.input_runtime.edit_state.insertSliceAssumeCapacity(prefix); app.input_runtime.entities.image_tokens.appendSliceAssumeCapacity( retained_image_tokens.items, ); } paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (retained_prefix == null and app.pending_images.items.len > 0) { debug_trace.logf( "input", "draft images dropped count={d} reason=local_command", .{app.pending_images.items.len}, ); app.clearPendingImages(); } app.shell.render_requests.request(.footer); defer if (retained_prefix != null) { if (app.pending_images.items.len == 0) { app.input_runtime.inputResetState().clearCurrent(app.alloc); } app.shell.render_requests.request(.footer); }; recordAcceptedSlashCommandHistory( app, max_prompt_history, command_copy, ); try App.handleCommand(app, command_copy); } fn projectRetainedImageTokens( app: *App, expanded_prefix_len: usize, ) !std.ArrayList(entity_spans.ImageTokenSpan) { var projected: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; errdefer projected.deinit(app.alloc); for (app.input_runtime.entities.image_tokens.items) |token| { if (!validRawImageToken(app.input_runtime.edit_state.input.items, token)) continue; if (image_attachments.findImageIndexById( app.pending_images.items, token.id, ) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_start, ) orelse continue; const end = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_end, ) orelse continue; if (end > expanded_prefix_len) continue; try projected.append(app.alloc, .{ .id = token.id, .span = .{ .raw_start = start, .raw_end = end }, }); } return projected; } fn resolvedSlashSubmission(app: *App, text: []const u8) []const u8 { if (completion_rt.visibleSlashCompletionCount(app) == 0) return text; return resolveSlashSubmission(app.slashRegistry(), text, app.input_runtime.picker.slash_completion_index); } fn enqueuePromptForSubmit( app: *App, prompt: []const u8, skill_tokens: []const registered_entities.SkillTokenSpan, accepted_draft: ?*const AcceptedDraftProjection, ) !bool { const resume_review = if (comptime @hasField(App, "queued_prompt_review")) app.queued_prompt_review.active() else false; if (comptime @hasField(App, "queued_prompt_review")) { switch (try queue_rt.promptAdmission( app, if (accepted_draft) |draft| draft.input else prompt, if (accepted_draft) |draft| draft.pasted_blocks.items else &.{}, if (accepted_draft) |draft| draft.image_tokens.items else &.{}, if (accepted_draft) |draft| draft.skill_tokens.items else skill_tokens, )) { .replaced => { beginIdleSubmittedPromptTransition(app); return true; }, .enqueue => {}, } } const accepted = if (comptime @hasDecl(App, "enqueuePromptWithReviewDraft")) blk: { if (accepted_draft) |draft| { break :blk try App.enqueuePromptWithReviewDraft( app, prompt, skill_tokens, draft.input, draft.pasted_blocks.items, draft.image_tokens.items, draft.skill_tokens.items, ); } break :blk try App.enqueuePromptWithSkillBindings( app, prompt, skill_tokens, ); } else if (comptime @hasDecl(App, "enqueuePromptWithSkillBindings")) try App.enqueuePromptWithSkillBindings(app, prompt, skill_tokens) else try App.enqueuePrompt(app, prompt); if (!accepted) return false; if (resume_review) { if (comptime @hasField(App, "queued_prompt_review")) { queue_rt.resumeAfterNewPrompt(app); } } beginIdleSubmittedPromptTransition(app); return true; } fn beginIdleSubmittedPromptTransition(app: *App) void {