diff options
Diffstat (limited to 'libpanto-c')
| -rw-r--r-- | libpanto-c/include/panto.h | 13 | ||||
| -rw-r--r-- | libpanto-c/src/lib.zig | 28 |
2 files changed, 35 insertions, 6 deletions
diff --git a/libpanto-c/include/panto.h b/libpanto-c/include/panto.h index 3b4a22a..c79e77c 100644 --- a/libpanto-c/include/panto.h +++ b/libpanto-c/include/panto.h @@ -50,7 +50,11 @@ typedef struct { PantoContentBlockTag block_type; size_t index; } PantoBlockStar typedef struct { size_t index; PantoSlice id, name; } PantoToolDetails; typedef struct { size_t index; PantoSlice delta; } PantoContentDelta; typedef struct { size_t index; PantoContentBlockTag block_type; } PantoBlockComplete; -typedef struct { bool has_usage; PantoUsage usage; } PantoMessageComplete; +/* One assistant message finished streaming. `message_index` indexes the + * agent's live conversation (`panto_agent_conversation`), whose tail is this + * message when the event is delivered; fetch its `block_count` blocks from + * there for full content. `usage` is the wire-reported token usage. */ +typedef struct { PantoMessageRole role; size_t message_index; size_t block_count; bool has_usage; PantoUsage usage; } PantoMessageComplete; typedef struct { bool compacted; size_t kept_turns; size_t summarized_messages; } PantoCompactionResult; typedef struct { size_t attempt, max_attempts; uint64_t delay_ms; PantoSlice error_name; bool has_status_code; uint16_t status_code; bool has_retry_after_ms; uint64_t retry_after_ms; PantoSlice message; bool compaction; } PantoProviderRetry; typedef struct { size_t count; } PantoToolDispatchStart; @@ -181,7 +185,12 @@ PANTO_EXPORT PantoStatus panto_agent_set_config(PantoAgent *agent, const PantoCo PANTO_EXPORT PantoStatus panto_agent_add_system_message(PantoAgent *agent, const char *text); PANTO_EXPORT PantoStatus panto_agent_set_system_prompt(PantoAgent *agent, const char *text); PANTO_EXPORT PantoStatus panto_agent_compact(PantoAgent *agent, const char *override_system_prompt, const char *extra_instructions, PantoCompactionResult *out); -PANTO_EXPORT PantoStatus panto_agent_run(PantoAgent *agent, const char *user_text, PantoStream **out); +/* Open a turn from a user-role message built with the PantoMessageBuilder API + * (the same builder used to rebuild persisted messages): one `add_text` block + * for a plain chat turn, or `add_tool_result` blocks to resume after the + * embedder handled tool calls itself. Consumes `user_message` (the builder is + * destroyed); do not reuse or destroy it afterwards. */ +PANTO_EXPORT PantoStatus panto_agent_run(PantoAgent *agent, PantoMessageBuilder *user_message, PantoStream **out); /* Reset a failed stream back to its turn-open boundary so the caller can * resume panto_stream_next() after changing the agent config (e.g. catch a * terminal bad-request, swap the provider config, and retry the SAME turn 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 } } }, |
