From 0bb87330a7c2242a3065a22b0029dde0b08073b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 4 Jun 2026 13:07:06 +0200 Subject: [PATCH] agent: abort in-flight LLM requests on cancel Updates the zenai dependency and integrates `zenai.http.Interrupt` to immediately abort active LLM requests when cancellation is triggered. --- build.zig.zon | 4 ++-- src/agent/Agent.zig | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 2da214e3..c05b14bd 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -35,8 +35,8 @@ .hash = "sqlite3-3.51.0-DMxLWssOAABZ8cAvU_LfBIbp0kZjm824PU8sSLXpEDdr", }, .zenai = .{ - .url = "git+https://github.com/lightpanda-io/zenai.git#a84812e6d37beccdc43ee79f43b0af8c1ae2e5e8", - .hash = "zenai-0.0.0-iOY_VErUAwA06Ygd91R3fP6jmUhScXsvIfceno4lA9Po", + .url = "git+https://github.com/lightpanda-io/zenai.git#35bec07f40f1493362ba4b3dc3bb9b8e51a98fd0", + .hash = "zenai-0.0.0-iOY_VOvxAwDY9Gj7AsJhFb8CBf-QuQjJsJgUJb2mGjC_", }, .isocline = .{ .url = "git+https://github.com/arrufat/isocline#94433ba3afa8e1ebb0187af17838a8d853a3829c", diff --git a/src/agent/Agent.zig b/src/agent/Agent.zig index eb9d7ae8..1f9ec50c 100644 --- a/src/agent/Agent.zig +++ b/src/agent/Agent.zig @@ -113,6 +113,9 @@ script_file: ?[]const u8, one_shot_task: ?[]const u8, one_shot_attachments: ?[]const []const u8, cancel_requested: std.atomic.Value(bool) = .init(false), +/// Shuts down the in-flight LLM socket on Ctrl-C so an agent turn aborts +/// mid-request instead of blocking until the model's full response arrives. +http_interrupt: zenai.http.Interrupt = .{}, synthetic_tool_call_id: u32 = 0, /// Aggregate Anthropic/OpenAI/Gemini token usage across every model call /// this Agent has made. Printed as a structured `$usage ...` line on stderr @@ -249,6 +252,7 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent self.ai_client = if (llm) |l| try zenai.provider.Client.init(allocator, l, .{ .base_url = opts.base_url, .retry_policy = .long_running }) else null; errdefer if (self.ai_client) |c| c.deinit(allocator); + if (self.ai_client) |c| c.setInterrupt(&self.http_interrupt); if (will_repl) { self.terminal.attachCompleter(); @@ -305,6 +309,7 @@ fn globalTools() []const ProviderTool { /// touches from this context. pub fn requestCancel(self: *Agent) void { self.cancel_requested.store(true, .release); + self.http_interrupt.fire(); { self.script_runtime_mutex.lock(); defer self.script_runtime_mutex.unlock(); @@ -671,6 +676,7 @@ fn setProvider(self: *Agent, credentials: Credentials) !void { const new_model = try self.allocator.dupe(u8, zenai.provider.defaultModel(credentials.provider)); if (self.ai_client) |client| client.deinit(self.allocator); + new_client.setInterrupt(&self.http_interrupt); self.ai_client = new_client; self.model_credentials = credentials; self.model_completions = null;