summaryrefslogtreecommitdiff
path: root/libpanto/src/config.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/config.zig')
-rw-r--r--libpanto/src/config.zig84
1 files changed, 81 insertions, 3 deletions
diff --git a/libpanto/src/config.zig b/libpanto/src/config.zig
index 5e01bb1..587f00a 100644
--- a/libpanto/src/config.zig
+++ b/libpanto/src/config.zig
@@ -41,6 +41,28 @@ pub const ReasoningEffort = enum {
high,
};
+/// Anthropic extended-thinking mode.
+pub const Thinking = enum {
+ /// Do not request extended thinking.
+ disabled,
+ /// Manual extended thinking with an explicit token budget (`thinking_budget_tokens`).
+ /// Supported on all current models including Haiku 4.5. Default.
+ enabled,
+ /// Adaptive thinking: Claude decides when and how much to think.
+ /// Requires Opus 4.6 / Sonnet 4.6 or newer; automatic on Opus 4.7+.
+ adaptive,
+};
+
+/// Effort level sent with Anthropic adaptive thinking (`thinking = .adaptive`).
+/// Ignored when `thinking` is `.enabled` or `.disabled`.
+pub const Effort = enum {
+ low,
+ medium,
+ high,
+ xhigh,
+ max,
+};
+
pub const OpenAIChatConfig = struct {
api_key: []const u8,
base_url: []const u8,
@@ -57,6 +79,22 @@ pub const AnthropicMessagesConfig = struct {
api_version: []const u8 = "2023-06-01",
/// Required by Anthropic's Messages API.
max_tokens: u32 = 64_000,
+ /// Extended-thinking mode. `.enabled` sends a manual thinking block with
+ /// `thinking_budget_tokens`; `.adaptive` lets Claude decide and uses
+ /// `effort` instead. `.disabled` omits thinking entirely.
+ thinking: Thinking = .enabled,
+ /// Effort level for adaptive thinking. Only emitted on the wire when
+ /// `thinking == .adaptive`; ignored otherwise.
+ effort: Effort = .medium,
+ /// Maximum tokens Claude may spend on internal reasoning when
+ /// `thinking == .enabled`. `null` falls back to `max_tokens - 1`.
+ /// Ignored when `thinking == .adaptive` or `.disabled`.
+ thinking_budget_tokens: ?u32 = 32_000,
+ /// When true and `thinking == .enabled`, sends the
+ /// `interleaved-thinking-2025-05-14` beta header so Claude can think
+ /// between tool calls. Ignored when `thinking == .adaptive` (interleaving
+ /// is automatic there) or `.disabled`.
+ thinking_interleaved: bool = false,
};
/// Per-provider transport/auth/model configuration. Tagged by `APIStyle`.
@@ -70,8 +108,8 @@ pub const ProviderConfig = union(APIStyle) {
/// The wire-format provider identity for this config: the ground-truth
/// `{api_style, base_url, model, reasoning}` that a turn is sent with.
- /// Anthropic has no reasoning-effort knob, so it reports `.default`.
- /// Borrowed slices; valid as long as the config is.
+ /// Anthropic carries thinking/effort/budget/interleaved instead of
+ /// reasoning. Borrowed slices; valid as long as the config is.
pub fn wireIdentity(self: ProviderConfig) WireIdentity {
return switch (self) {
.openai_chat => |c| .{
@@ -84,7 +122,10 @@ pub const ProviderConfig = union(APIStyle) {
.api_style = .anthropic_messages,
.base_url = c.base_url,
.model = c.model,
- .reasoning = .default,
+ .thinking = c.thinking,
+ .effort = c.effort,
+ .thinking_budget_tokens = c.thinking_budget_tokens,
+ .thinking_interleaved = c.thinking_interleaved,
},
};
}
@@ -93,11 +134,24 @@ pub const ProviderConfig = union(APIStyle) {
/// Wire-format provider identity (see `ProviderConfig.wireIdentity`). This
/// is the same shape as `session_store.WireIdentity`; defined here to avoid
/// a module cycle (config must not import session_store).
+///
+/// OpenAI uses `reasoning`; Anthropic uses `thinking`/`effort`/
+/// `thinking_budget_tokens`/`thinking_interleaved`. Fields unused by the
+/// active provider carry their zero/default values.
pub const WireIdentity = struct {
api_style: APIStyle,
base_url: []const u8,
model: []const u8,
+ /// OpenAI only.
reasoning: ReasoningEffort = .default,
+ /// Anthropic only.
+ thinking: Thinking = .enabled,
+ /// Anthropic only; only meaningful when `thinking == .adaptive`.
+ effort: Effort = .medium,
+ /// Anthropic only; only meaningful when `thinking == .enabled`.
+ thinking_budget_tokens: ?u32 = 32_000,
+ /// Anthropic only; only meaningful when `thinking == .enabled`.
+ thinking_interleaved: bool = false,
};
/// Compaction settings the agent consults when summarizing old history.
@@ -220,6 +274,30 @@ test "ProviderConfig - anthropic_messages variant" {
try t.expectEqual(APIStyle.anthropic_messages, cfg.style());
try t.expectEqualStrings("2023-06-01", cfg.anthropic_messages.api_version);
try t.expectEqual(@as(u32, 64_000), cfg.anthropic_messages.max_tokens);
+ try t.expectEqual(Thinking.enabled, cfg.anthropic_messages.thinking);
+ try t.expectEqual(Effort.medium, cfg.anthropic_messages.effort);
+ try t.expectEqual(@as(?u32, 32_000), cfg.anthropic_messages.thinking_budget_tokens);
+ try t.expectEqual(false, cfg.anthropic_messages.thinking_interleaved);
+}
+
+test "ProviderConfig - anthropic_messages wireIdentity carries thinking fields" {
+ const cfg: ProviderConfig = .{ .anthropic_messages = .{
+ .api_key = "k",
+ .base_url = "https://api.anthropic.com",
+ .model = "claude-opus-4-8",
+ .thinking = .adaptive,
+ .effort = .high,
+ .thinking_budget_tokens = null,
+ .thinking_interleaved = false,
+ } };
+ const id = cfg.wireIdentity();
+ try t.expectEqual(APIStyle.anthropic_messages, id.api_style);
+ try t.expectEqual(Thinking.adaptive, id.thinking);
+ try t.expectEqual(Effort.high, id.effort);
+ try t.expectEqual(@as(?u32, null), id.thinking_budget_tokens);
+ try t.expectEqual(false, id.thinking_interleaved);
+ // reasoning field carries its zero default; not meaningful for Anthropic
+ try t.expectEqual(ReasoningEffort.default, id.reasoning);
}
test "ProviderConfig - openai_chat reasoning defaults to .default" {