summaryrefslogtreecommitdiff
path: root/libpanto-c/src/lib.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-19 14:53:03 -0600
committert <t@tjp.lol>2026-06-19 14:55:35 -0600
commit81ddb61dcfcc9bc85585f262bf303e1a9da507b0 (patch)
treeb38b39a937de047d35246a3e64df6fda5737850e /libpanto-c/src/lib.zig
parent270cd00129551647e7c764a13836e03e0b2dc4e2 (diff)
libpanto fixes
- ensuring stream.next() sequences are interruptible - Agent.run() takes any user message, not just chat text
Diffstat (limited to 'libpanto-c/src/lib.zig')
-rw-r--r--libpanto-c/src/lib.zig28
1 files changed, 24 insertions, 4 deletions
diff --git a/libpanto-c/src/lib.zig b/libpanto-c/src/lib.zig
index 9d584a0..7f61ae3 100644
--- a/libpanto-c/src/lib.zig
+++ b/libpanto-c/src/lib.zig
@@ -50,7 +50,7 @@ pub const PantoBlockStart = extern struct { block_type: PantoContentBlockTag, in
pub const PantoToolDetails = extern struct { index: usize, id: PantoSlice, name: PantoSlice };
pub const PantoContentDelta = extern struct { index: usize, delta: PantoSlice };
pub const PantoBlockComplete = extern struct { index: usize, block_type: PantoContentBlockTag };
-pub const PantoMessageComplete = extern struct { has_usage: bool, usage: PantoUsage };
+pub const PantoMessageComplete = extern struct { role: PantoMessageRole, message_index: usize, block_count: usize, has_usage: bool, usage: PantoUsage };
pub const PantoCompactionResult = extern struct { compacted: bool, kept_turns: usize, summarized_messages: usize };
pub const PantoProviderRetry = extern struct { attempt: usize, max_attempts: usize, delay_ms: u64, error_name: PantoSlice, has_status_code: bool, status_code: u16, has_retry_after_ms: bool, retry_after_ms: u64, message: PantoSlice, compaction: bool };
pub const PantoToolDispatchStart = extern struct { count: usize };
@@ -803,9 +803,21 @@ export fn panto_agent_compact(a: *PantoAgent, override_system_prompt: ?[*:0]cons
out.* = .{ .compacted = r.compacted, .kept_turns = r.kept_turns, .summarized_messages = r.summarized_messages };
return .ok;
}
-export fn panto_agent_run(a: *PantoAgent, text: ?[*:0]const u8, out: **PantoStream) PantoStatus {
+/// Open a turn from a user-role message built with the `PantoMessageBuilder`
+/// API — the same builder used to rebuild persisted messages. A plain chat
+/// turn is one `add_text` block; a turn that resumes after the embedder handled
+/// tool calls is one or more `add_tool_result` blocks. Consumes the builder
+/// (success or failure): `Agent.run` adopts the block bytes, so only the
+/// builder's list backing + shell are freed here, never the per-block bytes.
+export fn panto_agent_run(a: *PantoAgent, user_message: *PantoMessageBuilder, out: **PantoStream) PantoStatus {
const h: *AgentHandle = @ptrCast(@alignCast(a));
- const st = h.agent.run(.{ .text = cstr(text) }) catch |e| return setErr("{t}", .{e});
+ const b = unwrapBuilder(user_message);
+ defer {
+ b.blocks.deinit(allocator);
+ if (b.identity) |id| panto.freeWireIdentity(allocator, id);
+ allocator.destroy(b);
+ }
+ const st = h.agent.run(.{ .blocks = b.blocks.items }) catch |e| return setErr("{t}", .{e});
out.* = @ptrCast(st);
return .ok;
}
@@ -825,6 +837,14 @@ export fn panto_stream_next(s: *PantoStream, out: *PantoEvent) PantoNextStatus {
const ev = st.next() catch |e| return setErrNext("{t}", .{e});
if (ev) |e| {
out.* = marshalEvent(e) catch |err| return setErrNext("{t}", .{err});
+ // The just-completed assistant message is the conversation's current
+ // tail (no further message is committed within this `next()` call), so
+ // its index is `len - 1`. Stamp it so the embedder can fetch the full
+ // message via `panto_agent_conversation` rather than marshalling its
+ // blocks onto every event.
+ if (out.tag == .message_complete) {
+ out.data.message_complete.message_index = st._agent.conversation.messages.items.len - 1;
+ }
return .event;
}
return .done;
@@ -855,7 +875,7 @@ fn marshalEvent(ev: panto.Event) !PantoEvent {
.tool_details => |t| .{ .tag = .tool_details, .data = .{ .tool_details = .{ .index = t.index, .id = try dupOut(t.id), .name = try dupOut(t.name) } } },
.content_delta => |d| .{ .tag = .content_delta, .data = .{ .content_delta = .{ .index = d.index, .delta = try dupOut(d.delta) } } },
.block_complete => |b| .{ .tag = .block_complete, .data = .{ .block_complete = .{ .index = b.index, .block_type = @enumFromInt(@intFromEnum(b.block)) } } },
- .message_complete => |m| .{ .tag = .message_complete, .data = .{ .message_complete = .{ .has_usage = m.usage != null, .usage = if (m.usage) |u| usage(u) else .{ .input = 0, .output = 0, .cache_read = 0, .cache_write = 0, .reasoning = 0 } } } },
+ .message_complete => |m| .{ .tag = .message_complete, .data = .{ .message_complete = .{ .role = @enumFromInt(@intFromEnum(m.message.role)), .message_index = 0, .block_count = m.message.content.items.len, .has_usage = m.usage != null, .usage = if (m.usage) |u| usage(u) else .{ .input = 0, .output = 0, .cache_read = 0, .cache_write = 0, .reasoning = 0 } } } },
.provider_retry => |r| .{ .tag = .provider_retry, .data = .{ .provider_retry = .{ .attempt = r.attempt, .max_attempts = r.max_attempts, .delay_ms = r.delay_ms, .error_name = try dupOut(@errorName(r.err)), .has_status_code = r.status_code != null, .status_code = r.status_code orelse 0, .has_retry_after_ms = r.retry_after_ms != null, .retry_after_ms = r.retry_after_ms orelse 0, .message = try dupOut(r.message orelse ""), .compaction = r.compaction } } },
.tool_dispatch_start => |t| .{ .tag = .tool_dispatch_start, .data = .{ .tool_dispatch_start = .{ .count = t.count } } },
.tool_dispatch_result => .{ .tag = .tool_dispatch_result, .data = .{ .tool_dispatch_result = .{ .message_index = 0 } } },