167 lines · 5.3 KB · Zig
RawDownload
const std = @import("std");const agent_stream_provider = @import("../stream_provider.zig");const image_attachments = @import("../../images/image_attachments.zig");const model_capabilities = @import("../../config/model_capabilities.zig");const types = @import("../../shared/types.zig");const debug_trace = @import("../../shared/debug_trace.zig");const session_usage = @import("../../session/session_usage.zig");const runtime_gateway_step = @import("gateway_step.zig"); const Allocator = std.mem.Allocator;const ChatMessage = types.ChatMessage; const model = "google/gemini-2.5-flash"; pub const Request = struct {    stream_provider: agent_stream_provider.Provider,    api_key: []const u8,    gateway_team: ?[]const u8,    session_id: ?[]const u8 = null,    retry_count: usize,    chat_url: []const u8,    cancel_flag: *std.atomic.Value(bool),    usage: ?*session_usage.Usage = null,    usage_allocator: Allocator = std.heap.c_allocator,    trace_ctx: debug_trace.TraceContext,    capture_limit_bytes: usize,    response_format: agent_stream_provider.StructuredResponseFormat,}; pub const Result = struct {    text: []u8,    observed_bytes: usize,    usage: types.ToolUsage,     pub fn deinit(self: *Result, alloc: Allocator) void {        alloc.free(self.text);        self.* = undefined;    }}; pub fn inspect(    alloc: Allocator,    system_prompt: []const u8,    user_prompt: []const u8,    images: []const image_attachments.VerifiedSnapshot,    request: Request,) !Result {    const messages = [_]ChatMessage{        .{ .role = .system, .content = system_prompt },        .{ .role = .user, .content = user_prompt },    };    const provider_opts = model_capabilities.resolveProviderOptions(model, .auto, false);    const payload = try request.stream_provider.build(        alloc,        .{            .model = model,            .serialized_tools = "[]",            .messages = &messages,            .tool_choice = .none,            .provider_options = provider_opts,            .budget = .{ .cancel_flag = request.cancel_flag },            .verified_images = images,            .response_format = request.response_format,        },    );    defer alloc.free(payload);     var capture = StreamCapture{        .alloc = alloc,        .max_bytes = request.capture_limit_bytes,    };    defer capture.deinit();    var delivery = runtime_gateway_step.DeliveryCertainty.init();    var attempt_evidence: agent_stream_provider.AttemptEvidence = .{};    const streamed = try runtime_gateway_step.streamGatewayCompletion(        request.stream_provider,        alloc,        request.api_key,        request.gateway_team,        request.session_id,        model,        request.retry_count,        request.chat_url,        payload,        null,        &delivery,        &attempt_evidence,        @ptrCast(&capture),        onContentChunk,        null,        null,        null,        request.cancel_flag,        request.usage,        request.usage_allocator,        request.trace_ctx,        request.capture_limit_bytes,        .transport,    );     if (request.cancel_flag.load(.seq_cst)) return error.Cancelled;    if (streamed.status != .ok) return error.ImageProviderUnavailable;    if (capture.failed) return error.OutOfMemory;     const tool_usage = types.ToolUsage{        .input_tokens = streamed.completion.usage.input_tokens orelse 0,        .output_tokens = streamed.completion.usage.output_tokens orelse 0,    };    if (capture.saw_content) {        return .{            .text = try capture.takeText(),            .observed_bytes = capture.observed_bytes,            .usage = tool_usage,        };    }    if (streamed.completion.content) |content| {        const retained_len = @min(content.len, request.capture_limit_bytes);        return .{            .text = try alloc.dupe(u8, content[0..retained_len]),            .observed_bytes = content.len,            .usage = tool_usage,        };    }    return error.InvalidProviderResponse;} const StreamCapture = struct {    alloc: Allocator,    text: std.ArrayList(u8) = .empty,    failed: bool = false,    max_bytes: usize,    observed_bytes: usize = 0,    saw_content: bool = false,     fn deinit(self: *StreamCapture) void {        self.text.deinit(self.alloc);        self.text = .empty;    }     fn takeText(self: *StreamCapture) ![]u8 {        return self.text.toOwnedSlice(self.alloc);    }}; fn onContentChunk(raw: *anyopaque, chunk: []const u8) void {    const capture: *StreamCapture = @ptrCast(@alignCast(raw));    capture.saw_content = capture.saw_content or chunk.len > 0;    capture.observed_bytes +|= chunk.len;    const remaining = capture.max_bytes -| capture.text.items.len;    capture.text.appendSlice(capture.alloc, chunk[0..@min(chunk.len, remaining)]) catch {        capture.failed = true;    };} test "shared image provider capture counts all streamed bytes while retaining its bound" {    var capture = StreamCapture{        .alloc = std.testing.allocator,        .max_bytes = 4,    };    defer capture.deinit();     onContentChunk(@ptrCast(&capture), "abc");    onContentChunk(@ptrCast(&capture), "二xyz");     try std.testing.expectEqual(@as(usize, "abc二xyz".len), capture.observed_bytes);    try std.testing.expectEqualStrings("abc\xe4", capture.text.items);}