From cf351ff2414603102949ef73245758eb9d5d1776 Mon Sep 17 00:00:00 2001 From: foxnne Date: Fri, 16 Oct 2026 09:29:45 -0500 Subject: [PATCH] chat: drop the Sample client from the chats list The Sample client was a scripted conversation partner, in Debug builds only, that answered with every kind of block the chat draws. It put a "Sample" section in the Chats sidebar and a `chat.sample` command in the palette. It has served its purpose, so it goes, with its embedded `sample.png`. The client mechanism it used (`chat.register`, `chat.post`, ...) stays: the agent client and other plugins register through it. --- plugin.zig | 2 - src/Sample.zig | 138 ------------------------------------------------- src/sample.png | Bin 7786 -> 0 bytes 3 files changed, 140 deletions(-) delete mode 100644 src/Sample.zig delete mode 100644 src/sample.png diff --git a/plugin.zig b/plugin.zig index 52de06d..e365d5f 100644 --- a/plugin.zig +++ b/plugin.zig @@ -15,7 +15,6 @@ const Chat = @import("src/Chat.zig"); const View = @import("src/View.zig"); const Commands = @import("src/Commands.zig"); const AcpClient = @import("src/AcpClient.zig"); -const Sample = @import("src/Sample.zig"); const Explorer = @import("src/Explorer.zig"); const Settings = @import("src/Settings.zig"); const DocHandle = sdk.DocHandle; @@ -88,7 +87,6 @@ pub fn register(host: *sdk.Host) !void { try Commands.register(host, &plugin); // Clients of the chat, through its commands like any other plugin. try AcpClient.register(host, &plugin); - if (@import("builtin").mode == .Debug) try Sample.register(host, &plugin); } fn drawExplorer(_: ?*anyopaque) anyerror!dvui.App.Result { diff --git a/src/Sample.zig b/src/Sample.zig deleted file mode 100644 index 4e57af6..0000000 --- a/src/Sample.zig +++ /dev/null @@ -1,138 +0,0 @@ -//! A scripted client, in Debug builds only: every kind of block the chat draws, answered through -//! the same commands any client uses (`api.zig`). What the chat looks like is checked against it -//! with no agent running (plans/CHAT_PLAN.md: the consumer that is not an agent). -//! -//! `chat.sample` opens a sample chat. Whatever the person sends, the reply shows thinking, -//! markdown, code, an image, a file link, a plan, and tool steps: read, an edit with its diff, a -//! run that failed, and one waiting on a question. -const std = @import("std"); -const sdk = @import("fizzy_sdk"); -const dvui = @import("dvui"); -const api = @import("api.zig"); - -pub const client_id = "sample"; -const png = @embedFile("sample.png"); - -pub fn register(host: *sdk.Host, owner: *sdk.Plugin) !void { - inline for (.{ - .{ "chat_send", api.Send, onSend }, - .{ "chat_answer", api.Answer, onAnswer }, - .{ "chat_new", api.NewChat, onNew }, - }) |entry| try host.registerCommand(.{ - .id = client_id ++ "." ++ entry[0], - .owner = owner, - .title = "Sample: " ++ entry[0], - .visibility = .internal, - .runWith = Handler(entry[1], entry[2]).run, - }); - try host.registerCommand(.{ - .id = "chat.sample", - .owner = owner, - .title = "Open a Sample Chat", - .run = runSample, - }); - _ = call("chat.register", api.Register{ .client = client_id, .title = "Sample" }); -} - -fn Handler(comptime Args: type, comptime f: fn (Args) void) type { - return struct { - fn run(_: *anyopaque, c: *sdk.Command.Call) anyerror!void { - f(api.parse(Args, c.arena, c.args) catch |err| return c.fail("{t}", .{err})); - } - }; -} - -fn call(comptime id: []const u8, args: anytype) ?[]const u8 { - const host = sdk.host(); - const arena = host.arena(); - const zon = api.write(arena, args) catch return null; - return switch (host.callCommand(id, zon, arena)) { - .ok => |r| r orelse "", - else => |o| { - dvui.log.warn("sample: {s} refused: {any}", .{ id, o }); - return null; - }, - }; -} - -fn runSample(_: *anyopaque) anyerror!void { - onNew(.{}); -} - -fn onNew(_: api.NewChat) void { - const r = call("chat.open", api.Open{ .client = client_id, .title = "Sample chat" }) orelse return; - const opened = api.parse(api.Opened, sdk.host().arena(), r) catch return; - _ = call("chat.post", api.Post{ .chat = opened.chat, .blocks = &.{ - .{ .text = "Hi! I'm the **sample** client. Send anything and I'll answer with every kind of block the chat draws." }, - } }); -} - -fn onAnswer(a: api.Answer) void { - const r = call("chat.post", api.Post{ .chat = a.chat, .author = .note, .blocks = &.{ - .{ .text = std.fmt.allocPrint(sdk.host().arena(), "Answered: {s}", .{a.option}) catch "Answered." }, - } }); - _ = r; -} - -fn onSend(a: api.Send) void { - const arena = sdk.host().arena(); - const m = blk: { - const r = call("chat.post", api.Post{ .chat = a.chat }) orelse return; - break :blk (api.parse(api.Posted, arena, r) catch return).message; - }; - const ap = struct { - fn text(chat: api.ChatId, msg: api.MessageId, kind: api.TextKind, t: []const u8) void { - _ = call("chat.append_text", api.AppendText{ .chat = chat, .message = msg, .kind = kind, .text = t }); - } - fn block(chat: api.ChatId, msg: api.MessageId, b: api.Block) void { - _ = call("chat.append", api.Append{ .chat = chat, .message = msg, .block = b }); - } - fn tool(chat: api.ChatId, msg: api.MessageId, t: api.Tool) ?api.ToolId { - const r = call("chat.tool", api.AddTool{ .chat = chat, .message = msg, .tool = t }) orelse return null; - return (api.parse(api.Added, sdk.host().arena(), r) catch return null).tool; - } - }; - ap.text(a.chat, m, .thought, "The person said something. Show them every block, in the order a real turn would use them."); - ap.text(a.chat, m, .text, std.fmt.allocPrint(arena, - \\You said: *{s}* - \\ - \\Here is **markdown**: `inline code`, a [link](https://fizzyed.it), and - \\ - \\- a list - \\- with a second item - \\ - \\> and a quote. - , .{a.text}) catch "You said something."); - _ = ap.tool(a.chat, m, .{ .kind = .read, .title = "Read build.zig", .status = .done, .content = &.{.{ .file = .{ .path = "build.zig", .line = 12 } }} }); - _ = ap.tool(a.chat, m, .{ .kind = .edit, .title = "Edit src/main.zig", .status = .done, .content = &.{.{ .diff = .{ - .path = "src/main.zig", - .old = "const std = @import(\"std\");\n\npub fn main() void {\n std.debug.print(\"hi\\n\", .{});\n}\n", - .new = "const std = @import(\"std\");\n\npub fn main() void {\n std.debug.print(\"hello, fizzy\\n\", .{});\n}\n", - } }} }); - _ = ap.tool(a.chat, m, .{ .kind = .execute, .title = "Run zig build test", .status = .failed, .content = &.{.{ .text = "test 1/3 a... OK\ntest 2/3 b... FAIL\n expected 2, found 3\ntest 3/3 c... OK\n1 failed" }} }); - if (ap.tool(a.chat, m, .{ .kind = .execute, .title = "Run zig build", .status = .pending })) |tid| { - _ = call("chat.ask", api.Ask{ .chat = a.chat, .message = m, .question = .{ .text = "", .tool = tid, .options = &.{ - .{ .id = "allow", .label = "Allow", .kind = .allow }, - .{ .id = "always", .label = "Always", .kind = .allow_always }, - .{ .id = "deny", .label = "Deny", .kind = .deny }, - } } }); - } - ap.block(a.chat, m, .{ .code = .{ .language = "zig", .text = "pub fn main() void {\n std.debug.print(\"hello, fizzy\\n\", .{});\n}" } }); - if (imagePath(arena)) |path| ap.block(a.chat, m, .{ .image = .{ .path = path, .mime = "image/png", .alt = "A gradient" } }); - _ = call("chat.set_plan", api.SetPlan{ .chat = a.chat, .message = m, .entries = &.{ - .{ .text = "Read the build", .status = .completed }, - .{ .text = "Change the greeting", .status = .in_progress }, - .{ .text = "Run the tests" }, - } }); - ap.text(a.chat, m, .text, "That's everything. **Allow** or **Deny** the last step to see a question answered."); -} - -/// The sample image, written where the chat can read it. -fn imagePath(arena: std.mem.Allocator) ?[]const u8 { - const plugins = sdk.host().plugins_dir orelse return null; - const dir = std.fs.path.join(arena, &.{ std.fs.path.dirname(plugins) orelse plugins, "chats", ".incoming" }) catch return null; - std.Io.Dir.cwd().createDirPath(dvui.io, dir) catch {}; - const path = std.fs.path.join(arena, &.{ dir, "sample.png" }) catch return null; - std.Io.Dir.cwd().writeFile(dvui.io, .{ .sub_path = path, .data = png }) catch return null; - return path; -} diff --git a/src/sample.png b/src/sample.png deleted file mode 100644 index 2dabee5389d2d7eac82b7d33215da0500385f051..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7786 zcmc(k`8(8G9LI;TGj=hEu~Vp-kuTYIN+U~)n`Eb(!f2s1GufBPzKmUQQ@1Xm+fCfc znq_RaZc!dH7(-o8geM~z_j~Uj@jcIZ&iDJ{dCqyiKJWKgvW}D7FcPW~C=?3g;qL4M z|AXLeFD3$i7tF}gD3k`?!`ZqGXgvVnE~`bx@kY$UHfb3 zBvYIA4bCq`Wi_3b$)7lV|FuAR6Y;r9he|cw23t+9`M;5tZFg3;ByFj@9!-Aa%DD+Q z8bw5mFd(ayj@4oshB{{wNOW=iEMlLTcp)=Cq}1{+W|7vgpyeL_kaDGy{-M5p}e&y_$QI@cXC zyk0D=jmX&WS2OJ7?KK6yV{rpGr&N`Q)PwbOIjtcJygoyBRPl|jJHaX2pX*NaN;j6| zoDRzhKY2R*_7(HVu$+rmC&Tk@N?UL;LrE5TW7tK-XY4}lQ^n7?>4ZO~z3&X!9MVW= zAf*^oSg$+goSKPR9~IqeyLERMvfJPY7sB9L~h!i=FSOjwanq9HJ7p&%91bz&Ho+sU2J>|1hS_r42{+Pd`5M7Q;r-dK)* zrRAm`-))pQYSH*gxJ4E+Utj55QMI1nlq>(#x#n7V&30*ZQ2FeO ztvj2|RCKc7l8(I{S_m7KDceJrjVQ)zJ7W#{g^GI1Ln?$kBHZ!A>gTzG`Qf+JbBtS< z5)U7>jEYo7w~R=4EVqmaWT?k6#Tz-6^HqY%0Y02;`S;VuxusV7{MsA)(RpvruR0e+ z21b1yQoA^_Ro7#1?(!BlBXAXBv&8E1P&-nxVfk8)Qbb3FcYmgAc$JERr+j4gd3oJ( ztv=)UhH?X^Qem=jYcFB{qt^bzYSFEI=2pwCuWd!u$-Q`ks=`HjyhK2(N5TFh0r5`d zXTID&zEGnoW9`$}rEsh!@lN0Vm?w#gDblq`6AP7^*LS%PP8WG`(_nFb10Ze;72OR$ zTpq;E6COv}F#fdTV=@6SRluTA4UlJQr-MafAkUQYHioAlupw7hE08QjaLx>N6(bI* z$lawYgeH5;dE$>H>k=Cn;kjn(VFRu9xh|!rBhpU1I~|#F?CE4g#@TO^i!`TP3vR0K zT2&nBmZYf>*(K}3CByjPy-L4M`_A8T`)rnQcaV7CNy5Ty^OdCW*`fo_lh&P9yeQ~7 z04V=}eM%C5Pw^p~7V=E#!9L}Ox+%02NyDW`$%QcFy6FVhRKxjRMo^)JuV)c%P|7Hx zTxY}@+iIL2TA)|zA6#6XFfYz2Vk6$)Ac`Ai6$h1e?~JOf^3Q2?u~NkJb0u*&d8 zg9_kqzdH}r86zKnC9q+XAz!(hFDdAK0K{E@#RYI8+TDig^pO|$Z#aeW0EkPapg#i; zcL!o?0`N!b4@?w*lh_EnW8RNEQ#&jctrU5t-osZY1^_~+mV*8O0Of6nT?+zbK4u9( z_Ep0d+j0ON;1-}d02Oovj@(-0D`<0