Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237const std = @import("std");const types = @import("types.zig");
/// Role assigned to a request message.pub const Role = enum { system, user, assistant, tool,};
/// Tool call emitted by the gateway and replayed in assistant messages.pub const ToolCall = types.ToolCall;
/// Content carried by a request message.pub const Content = union(enum) { text: []const u8,
/// Returns the borrowed text for the active content variant. pub fn asText(self: Content) []const u8 { return switch (self) { .text => |value| value, }; }};
/// One role-tagged request message. Explicit flags track ownership because/// `ToolCall` byte slices do not encode it at the type level.pub const Message = struct { role: Role, content: ?Content = null, images: []const types.ImageAttachment = &.{}, tool_calls: []const ToolCall = &.{}, tool_call_id: ?[]const u8 = null, tool_name: ?[]const u8 = null, tool_result_status: ?types.PersistedToolStatus = null, tool_result_memory: ?types.ToolResultMemory = null, permission_feedback: bool = false, owns_content: bool = false, owns_tool_calls: bool = false,
/// Builds a borrowed system message. pub fn systemText(text: []const u8) Message { return .{ .role = .system, .content = .{ .text = text }, }; }
/// Builds an owned system message; caller transfers ownership of text to the Message; Message.deinit will free it. pub fn systemOwned(text: []u8) Message { return .{ .role = .system, .content = .{ .text = text }, .owns_content = true, }; }
/// Builds a borrowed user message. pub fn userText(text: []const u8) Message { return .{ .role = .user, .content = .{ .text = text }, }; }
/// Builds an owned user message; caller transfers ownership of text to the Message. pub fn userOwned(text: []u8) Message { return .{ .role = .user, .content = .{ .text = text }, .owns_content = true, }; }
/// Builds an assistant message that owns content and tool-call slices. pub fn assistantOwned(content: ?[]const u8, tool_calls: []const ToolCall) Message { return .{ .role = .assistant, .content = if (content) |text| .{ .text = text } else null, .tool_calls = tool_calls, .owns_content = content != null, .owns_tool_calls = tool_calls.len > 0, }; }
/// Builds an assistant message that borrows content and tool-call slices. pub fn assistantBorrowed(content: ?[]const u8, tool_calls: []const ToolCall) Message { return .{ .role = .assistant, .content = if (content) |text| .{ .text = text } else null, .tool_calls = tool_calls, }; }
/// Builds a tool-result message that owns the result text. pub fn toolOwned( content: []u8, tool_call_id: []const u8, tool_name: []const u8, status: types.PersistedToolStatus, ) Message { return .{ .role = .tool, .content = .{ .text = content }, .tool_call_id = tool_call_id, .tool_name = tool_name, .tool_result_status = status, .owns_content = true, }; }
/// Frees owned message fields and leaves borrowed fields untouched. pub fn deinit(self: *Message, alloc: std.mem.Allocator) void { if (self.owns_content) { if (self.content) |content| alloc.free(content.asText()); } if (self.owns_tool_calls) freeToolCalls(alloc, self.tool_calls); self.* = .{ .role = .user }; }};
/// Full message list consumed by one streamed gateway request.pub const Request = struct { messages: []const Message, tool_choice: types.ToolChoice = .auto,
/// Wraps a borrowed conversation slice owned by the caller. pub fn init(messages: []const Message) Request { return .{ .messages = messages }; }
pub fn initWithToolChoice(messages: []const Message, tool_choice: types.ToolChoice) Request { return .{ .messages = messages, .tool_choice = tool_choice }; }};
/// Frees an owned tool-call slice returned by the gateway.pub fn freeToolCalls(alloc: std.mem.Allocator, tool_calls: []const ToolCall) void { for (tool_calls) |call| { alloc.free(call.id); alloc.free(call.name); alloc.free(call.arguments_json); if (call.provisional_id) |provisional_id| alloc.free(provisional_id); if (call.provider_result) |provider_result| alloc.free(provider_result); } if (tool_calls.len > 0) alloc.free(tool_calls);}
test "Message constructors preserve borrowed prompts" { const system = Message.systemText("rules"); const user = Message.userText("hello");
try std.testing.expectEqual(.system, system.role); try std.testing.expectEqualStrings("rules", system.content.?.asText()); try std.testing.expect(!system.owns_content); try std.testing.expectEqual(.user, user.role); try std.testing.expectEqualStrings("hello", user.content.?.asText());}
test "Message.assistantOwned frees content and tool calls" { const alloc = std.testing.allocator; const content = try alloc.dupe(u8, "assistant"); const calls = try alloc.alloc(ToolCall, 1); calls[0] = .{ .id = try alloc.dupe(u8, "call_1"), .name = try alloc.dupe(u8, "read_file"), .arguments_json = try alloc.dupe(u8, "{}"), .provider_result = try alloc.dupe(u8, "provider"), };
var msg = Message.assistantOwned(content, calls); try std.testing.expect(msg.owns_content); try std.testing.expect(msg.owns_tool_calls); try std.testing.expectEqualStrings("assistant", msg.content.?.asText()); try std.testing.expectEqualStrings("read_file", msg.tool_calls[0].name);
msg.deinit(alloc);}
test "Message.systemOwned frees owned content" { const alloc = std.testing.allocator; const text = try alloc.dupe(u8, "system"); var msg = Message.systemOwned(text); try std.testing.expect(msg.owns_content); try std.testing.expectEqualStrings("system", msg.content.?.asText()); msg.deinit(alloc);}
test "Message.userOwned frees owned content" { const alloc = std.testing.allocator; const text = try alloc.dupe(u8, "user"); var msg = Message.userOwned(text); try std.testing.expect(msg.owns_content); try std.testing.expectEqualStrings("user", msg.content.?.asText()); msg.deinit(alloc);}
test "assistantBorrowed constructor and deinit leave borrowed content alive" { const borrowed = "assistant"; var msg = Message.assistantBorrowed(borrowed, &.{}); try std.testing.expect(!msg.owns_content); try std.testing.expectEqualStrings(borrowed, msg.content.?.asText()); msg.deinit(std.testing.allocator); try std.testing.expectEqualStrings("assistant", borrowed);}
test "Message.toolOwned frees result text without owning borrowed call labels" { const alloc = std.testing.allocator; const body = try alloc.dupe(u8, "result"); var msg = Message.toolOwned(body, "call_1", "read_file", .success);
try std.testing.expectEqual(.tool, msg.role); try std.testing.expectEqualStrings("result", msg.content.?.asText()); try std.testing.expectEqualStrings("call_1", msg.tool_call_id.?); try std.testing.expectEqualStrings("read_file", msg.tool_name.?); try std.testing.expectEqual(types.PersistedToolStatus.success, msg.tool_result_status.?);
msg.deinit(alloc);}
test "Request.init wraps borrowed message slice" { const messages = [_]Message{ Message.userText("hello"), }; const request = Request.init(&messages);
try std.testing.expectEqual(@as(usize, 1), request.messages.len); try std.testing.expectEqualStrings("hello", request.messages[0].content.?.asText());}
test "Content.asText returns borrowed text" { const content = Content{ .text = "borrowed" };
try std.testing.expectEqualStrings("borrowed", content.asText());}