summaryrefslogtreecommitdiff
path: root/libpanto/src/provider.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/provider.zig')
-rw-r--r--libpanto/src/provider.zig45
1 files changed, 45 insertions, 0 deletions
diff --git a/libpanto/src/provider.zig b/libpanto/src/provider.zig
index e83012d..43c882a 100644
--- a/libpanto/src/provider.zig
+++ b/libpanto/src/provider.zig
@@ -26,6 +26,24 @@ pub const ContentBlockType = enum {
/// Markers (case-sensitive substrings, as the wire emits them):
/// - OpenAI: `context_length_exceeded`, `maximum context length`
/// - Anthropic: `prompt is too long`
+/// Merge a provider's built-in request headers with caller-supplied
+/// `extra_headers` (config `Header`s) into one `[]std.http.Header`, allocated
+/// with `alloc`. The caller owns the result and must free it once the request
+/// has been sent. `base` comes first; `extra` is appended in order, so an
+/// `extra` header with the same name as a base header is sent as a second
+/// occurrence (provider-identity headers in practice never collide with the
+/// fixed content-type/accept/authorization set).
+pub fn mergeHeaders(
+ alloc: std.mem.Allocator,
+ base: []const std.http.Header,
+ extra: []const config_mod.Header,
+) ![]std.http.Header {
+ const out = try alloc.alloc(std.http.Header, base.len + extra.len);
+ @memcpy(out[0..base.len], base);
+ for (extra, 0..) |h, i| out[base.len + i] = .{ .name = h.name, .value = h.value };
+ return out;
+}
+
pub fn isContextOverflowBody(body: []const u8) bool {
const markers = [_][]const u8{
"context_length_exceeded",
@@ -311,6 +329,33 @@ pub const OpenStreamFn = *const fn (
diag: ?*ProviderDiagnostic,
) anyerror!ProviderStream;
+test "mergeHeaders - base first, extra appended, converted to http.Header" {
+ const t2 = std.testing;
+ const base = [_]std.http.Header{
+ .{ .name = "content-type", .value = "application/json" },
+ .{ .name = "authorization", .value = "Bearer x" },
+ };
+ const extra = [_]config_mod.Header{
+ .{ .name = "Copilot-Integration-Id", .value = "vscode-chat" },
+ .{ .name = "X-Initiator", .value = "user" },
+ };
+ const merged = try mergeHeaders(t2.allocator, &base, &extra);
+ defer t2.allocator.free(merged);
+ try t2.expectEqual(@as(usize, 4), merged.len);
+ try t2.expectEqualStrings("content-type", merged[0].name);
+ try t2.expectEqualStrings("Copilot-Integration-Id", merged[2].name);
+ try t2.expectEqualStrings("user", merged[3].value);
+}
+
+test "mergeHeaders - empty extra yields a copy of base" {
+ const t2 = std.testing;
+ const base = [_]std.http.Header{.{ .name = "accept", .value = "text/event-stream" }};
+ const merged = try mergeHeaders(t2.allocator, &base, &.{});
+ defer t2.allocator.free(merged);
+ try t2.expectEqual(@as(usize, 1), merged.len);
+ try t2.expectEqualStrings("accept", merged[0].name);
+}
+
test "isContextOverflowBody - matches known markers, rejects others" {
const t2 = std.testing;
try t2.expect(isContextOverflowBody("{\"error\":{\"code\":\"context_length_exceeded\"}}"));