summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config_file.zig74
1 files changed, 73 insertions, 1 deletions
diff --git a/src/config_file.zig b/src/config_file.zig
index f5ed79c..1858520 100644
--- a/src/config_file.zig
+++ b/src/config_file.zig
@@ -25,7 +25,10 @@
//! by `api_key_env_var`. A provider whose only key source is an absent
//! env var is **silently dropped** — this is what lets the base layer
//! ship default OpenAI/Anthropic providers that simply don't appear when
-//! the user hasn't exported the corresponding key.
+//! the user hasn't exported the corresponding key. An `anthropic_messages`
+//! provider may also set `prompt_cache = <bool>` (default true) to control
+//! the advancing `cache_control` breakpoint; it is ignored for
+//! `openai_chat` providers.
//! - `defaults.model` is parsed as `<provider_name>:<model_alias>`.
//! - `tools` / `extensions` allow- and deny-lists are captured verbatim
//! (as glob pattern lists). A pattern appearing in both allow and deny
@@ -67,6 +70,10 @@ pub const Provider = struct {
style: APIStyle,
base_url: []const u8,
api_key: []const u8,
+ /// anthropic_messages only. Place one advancing `cache_control`
+ /// breakpoint on each request. Defaults to the library default (true);
+ /// ignored for `openai_chat` providers.
+ prompt_cache: bool = true,
pub fn deinit(self: Provider, alloc: Allocator) void {
alloc.free(self.name);
@@ -150,6 +157,7 @@ pub fn buildProviderConfig(
.effort = if (def_opt) |d| d.effort else .medium,
.thinking_budget_tokens = if (def_opt) |d| d.thinking_budget_tokens else 32_000,
.thinking_interleaved = if (def_opt) |d| d.thinking_interleaved else false,
+ .prompt_cache = prov.prompt_cache,
} },
}
}
@@ -545,11 +553,20 @@ fn resolveProvider(
return null;
};
+ // anthropic_messages only; absent => library default (true).
+ const prompt_cache: bool = blk: {
+ if (val.get("prompt_cache")) |pc| {
+ if (pc.asBool()) |b| break :blk b;
+ }
+ break :blk true;
+ };
+
return .{
.name = try allocator.dupe(u8, name),
.style = style,
.base_url = try allocator.dupe(u8, base_url),
.api_key = try allocator.dupe(u8, api_key),
+ .prompt_cache = prompt_cache,
};
}
@@ -732,6 +749,61 @@ test "resolve: env-backed provider resolves when env var is set" {
try testing.expectEqualStrings("sk-ant-xyz", p.api_key);
}
+test "resolve: prompt_cache defaults true and is parsed when set" {
+ const a = testing.allocator;
+ var env = emptyEnv(a);
+ defer env.deinit();
+
+ const src =
+ \\[providers.anthropic]
+ \\style = "anthropic_messages"
+ \\base_url = "https://api.anthropic.com"
+ \\api_key = "sk-ant"
+ \\
+ \\[providers.anthropic_nocache]
+ \\style = "anthropic_messages"
+ \\base_url = "https://api.anthropic.com"
+ \\api_key = "sk-ant"
+ \\prompt_cache = false
+ ;
+ const doc = try parseDoc(a, src);
+ defer doc.deinit();
+
+ var cfg = try resolve(a, &env, doc.root);
+ defer cfg.deinit();
+
+ // Absent => library default (true).
+ try testing.expectEqual(true, cfg.provider("anthropic").?.prompt_cache);
+ // Explicit false is honoured.
+ try testing.expectEqual(false, cfg.provider("anthropic_nocache").?.prompt_cache);
+}
+
+test "buildProviderConfig: prompt_cache flows from provider into anthropic config" {
+ const a = testing.allocator;
+ var env = emptyEnv(a);
+ defer env.deinit();
+
+ const src =
+ \\[providers.anthropic]
+ \\style = "anthropic_messages"
+ \\base_url = "https://api.anthropic.com"
+ \\api_key = "sk-ant"
+ \\prompt_cache = false
+ ;
+ const doc = try parseDoc(a, src);
+ defer doc.deinit();
+
+ var cfg = try resolve(a, &env, doc.root);
+ defer cfg.deinit();
+
+ var defs = models_toml.ModelRegistry.init(a);
+ defer defs.deinit();
+
+ const pc = try buildProviderConfig(&cfg, &defs, .{ .provider = "anthropic", .model = "sonnet" });
+ try testing.expectEqual(APIStyle.anthropic_messages, pc.style());
+ try testing.expectEqual(false, pc.anthropic_messages.prompt_cache);
+}
+
test "resolve: inline api_key wins over env var" {
const a = testing.allocator;
var env = emptyEnv(a);