From 9f8719929dbd1326d9b327885c4da319c6d2673c Mon Sep 17 00:00:00 2001 From: t Date: Mon, 15 Jun 2026 17:08:00 -0600 Subject: anthropic credentials in oauth providers (eg copilot) --- src/config_file.zig | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) (limited to 'src/config_file.zig') 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); -- cgit v1.3