summaryrefslogtreecommitdiff
path: root/src/config_file.zig
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-15 17:08:00 -0600
committert <t@tjp.lol>2026-06-15 17:18:20 -0600
commit9f8719929dbd1326d9b327885c4da319c6d2673c (patch)
treecb7cd72f0be46c099e9831364ba7d5cbedebeb09 /src/config_file.zig
parentd9d4131cb321a9ce29b000cd7aed8ced9a18efc1 (diff)
anthropic credentials in oauth providers (eg copilot)
Diffstat (limited to 'src/config_file.zig')
-rw-r--r--src/config_file.zig34
1 files changed, 30 insertions, 4 deletions
diff --git a/src/config_file.zig b/src/config_file.zig
index a70a832..6d4b705 100644
--- a/src/config_file.zig
+++ b/src/config_file.zig
@@ -178,16 +178,16 @@ pub fn buildProviderConfig(
) ResolveError!panto.ProviderConfig {
const prov = cfg.provider(ref.provider) orelse return error.UnknownProvider;
+ const auth_cfg = cfg.auth(prov.auth_name) orelse unreachable;
+
// The provider's credential comes from its named auth session. For
// `api_key` sessions this is resolved eagerly at load; for OAuth it is
// null here and patched in at turn time by the embedder's auth manager
// (which also supplies a dynamic base_url and auth-derived headers). An
// empty string is a deliberate placeholder: an unresolved session yields
// a clear auth error on its first request rather than a silent drop.
- const api_key: []const u8 = blk: {
- const a = cfg.auth(prov.auth_name) orelse break :blk "";
- break :blk a.resolved_api_key orelse "";
- };
+ const api_key: []const u8 = auth_cfg.resolved_api_key orelse "";
+ const anthropic_use_bearer_auth = auth_cfg.config == .oauth_device;
const def_opt = defs.get(ref.provider, ref.model);
const wire_model: []const u8 = if (def_opt) |d| d.model else ref.model;
@@ -214,6 +214,7 @@ pub fn buildProviderConfig(
.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,
+ .use_bearer_auth = anthropic_use_bearer_auth,
.extra_headers = prov.extra_headers,
} },
.openai_responses => return .{ .openai_responses = .{
@@ -1362,6 +1363,31 @@ test "resolve: prompt_cache defaults true and is parsed when set" {
try testing.expectEqual(false, cfg.provider("anthropic_nocache").?.prompt_cache);
}
+test "buildProviderConfig: anthropic oauth_device uses bearer auth" {
+ const a = testing.allocator;
+ var env = emptyEnv(a);
+ defer env.deinit();
+ const src =
+ \\[providers.copilot_anthropic]
+ \\style = "anthropic_messages"
+ \\base_url = "https://api.individual.githubcopilot.com"
+ \\auth = "github_copilot"
+ \\
+ \\[auth.github_copilot]
+ \\type = "oauth_device"
+ \\client_id = "Iv1.x"
+ \\device_code_url = "https://github.com/login/device/code"
+ \\token_url = "https://github.com/login/oauth/access_token"
+ ;
+ var cfg = try loadFromString(a, &env, src);
+ defer cfg.deinit();
+ var defs = models_toml.ModelRegistry.init(a);
+ defer defs.deinit();
+ const pc = try buildProviderConfig(&cfg, &defs, .{ .provider = "copilot_anthropic", .model = "claude-sonnet-4-5" });
+ try testing.expectEqual(APIStyle.anthropic_messages, pc.style());
+ try testing.expect(pc.anthropic_messages.use_bearer_auth);
+}
+
test "buildProviderConfig: prompt_cache flows from provider into anthropic config" {
const a = testing.allocator;
var env = emptyEnv(a);