github.com/anthropics/anthropic-sdk-go@v1.70.1/option/requestoption.go (about) 1 // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 3 package option 4 5 import ( 6 "bytes" 7 "context" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "os" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/anthropics/anthropic-sdk-go/config" 18 "github.com/anthropics/anthropic-sdk-go/internal/auth" 19 "github.com/anthropics/anthropic-sdk-go/internal/requestconfig" 20 "github.com/tidwall/sjson" 21 ) 22 23 // IdentityTokenFunc returns a fresh JWT identity token (e.g. from SPIFFE/SPIRE, 24 // the GCP metadata server, or any other OIDC-compatible provider). It is 25 // invoked once per federation token exchange; implementations should be safe 26 // for concurrent use. 27 type IdentityTokenFunc func(ctx context.Context) (string, error) 28 29 // FederationOptions configures a federation-based token exchange. 30 type FederationOptions struct { 31 // FederationRuleID identifies the OidcFederationRule governing this 32 // exchange. Required; must be a tagged ID with the "fdrl_" prefix. 33 FederationRuleID string 34 // OrganizationID is the UUID of the Anthropic organization the 35 // federation rule belongs to. Required. 36 OrganizationID string 37 // ServiceAccountID is an optional expected-target check for federation 38 // rules with target_type=SERVICE_ACCOUNT. Must be a tagged ID with the 39 // "svac_" prefix. Omit for target_type=USER rules, where the principal 40 // is derived from the JWT claims. 41 ServiceAccountID string 42 // WorkspaceID is an optional `wrkspc_*` tagged ID, or the literal 43 // "default" to scope the token to the organization's default workspace. 44 // When omitted the server picks the rule's sole enabled workspace, else 45 // the org default if the rule covers it. Required when the rule enables 46 // more than one non-default workspace, or to target a specific workspace 47 // other than the one the server would pick. The minted token is 48 // workspace-scoped: per-request workspace selection (the 49 // anthropic-workspace-id header) is not supported for federation 50 // tokens — switching workspaces requires a new token exchange with a 51 // different WorkspaceID. 52 WorkspaceID string 53 } 54 55 // identityTokenFuncAdapter adapts an [IdentityTokenFunc] to the internal 56 // [auth.IdentityTokenProvider] interface without leaking the internal type. 57 type identityTokenFuncAdapter struct { 58 fn IdentityTokenFunc 59 } 60 61 func (a identityTokenFuncAdapter) GetIdentityToken(ctx context.Context) (string, error) { 62 if a.fn == nil { 63 return "", fmt.Errorf("option: IdentityTokenFunc is nil") 64 } 65 return a.fn(ctx) 66 } 67 68 // errOption returns a [RequestOption] whose Apply unconditionally returns 69 // err. Used to defer validation errors from option constructors so they 70 // surface on the first request rather than at option-construction time. 71 func errOption(err error) RequestOption { 72 return requestconfig.RequestOptionFunc(func(*requestconfig.RequestConfig) error { 73 return err 74 }) 75 } 76 77 // IdentityTokenFile returns an [IdentityTokenFunc] that reads a JWT from path 78 // on each invocation. Use it for Kubernetes projected service account tokens 79 // and similar rotating on-disk credentials — the file is re-read on every 80 // federation exchange so rotations are picked up automatically. Surrounding 81 // whitespace is trimmed; an empty file is treated as an error. 82 // 83 // Passing this as the provider to [WithFederationTokenProvider] takes 84 // precedence over the ANTHROPIC_IDENTITY_TOKEN_FILE environment variable: 85 // explicit options always beat env-var auto-discovery. The env var only 86 // applies when the client is constructed without a [WithFederationTokenProvider] 87 // option (or equivalent). 88 // 89 // The returned function is safe for concurrent use. 90 // 91 // Typical usage: 92 // 93 // client := anthropic.NewClient( 94 // option.WithFederationTokenProvider( 95 // option.IdentityTokenFile("/var/run/secrets/anthropic.com/token"), 96 // option.FederationOptions{ 97 // FederationRuleID: "fdrl_...", 98 // OrganizationID: os.Getenv("ANTHROPIC_ORGANIZATION_ID"), 99 // }, 100 // ), 101 // ) 102 func IdentityTokenFile(path string) IdentityTokenFunc { 103 return (&auth.IdentityTokenFile{Path: path}).GetIdentityToken 104 } 105 106 // WithFederationTokenProvider returns a [RequestOption] that authenticates 107 // requests using workload identity federation, exchanging a caller-supplied 108 // identity token for a short-lived Anthropic access token. Use this to 109 // integrate custom OIDC token sources (SPIFFE/SPIRE, cloud provider SDKs, 110 // etc.) without staging the token through a file. 111 // 112 // provider is called on each token exchange to fetch a fresh JWT. 113 // opts.FederationRuleID and opts.OrganizationID are required. 114 // 115 // The [auth.TokenCache] built by [auth.WithAuthMiddleware] is shared across 116 // requests, so a token is only re-exchanged when it enters the refresh window. 117 func WithFederationTokenProvider(provider IdentityTokenFunc, opts FederationOptions) RequestOption { 118 switch { 119 case provider == nil: 120 return errOption(fmt.Errorf("option: WithFederationTokenProvider: provider is nil")) 121 case opts.FederationRuleID == "": 122 return errOption(fmt.Errorf("option: WithFederationTokenProvider: FederationRuleID is required")) 123 case opts.OrganizationID == "": 124 return errOption(fmt.Errorf("option: WithFederationTokenProvider: OrganizationID is required")) 125 } 126 tokenProvider := auth.NewOIDCFederationCredentials(auth.OIDCFederationConfig{ 127 IdentityProvider: identityTokenFuncAdapter{fn: provider}, 128 FederationRuleID: opts.FederationRuleID, 129 OrganizationID: opts.OrganizationID, 130 ServiceAccountID: opts.ServiceAccountID, 131 WorkspaceID: opts.WorkspaceID, 132 }) 133 return auth.WithAuthMiddleware(tokenProvider) 134 } 135 136 // RequestOption is an option for the requests made by the anthropic API Client 137 // which can be supplied to clients, services, and methods. You can read more about this functional 138 // options pattern in our [README]. 139 // 140 // [README]: https://pkg.go.dev/github.com/anthropics/anthropic-sdk-go#readme-requestoptions 141 type RequestOption = requestconfig.RequestOption 142 143 // WithBaseURL returns a RequestOption that sets the BaseURL for the client. 144 // 145 // For security reasons, ensure that the base URL is trusted. 146 func WithBaseURL(base string) RequestOption { 147 u, err := url.Parse(base) 148 if err == nil && u.Path != "" && !strings.HasSuffix(u.Path, "/") { 149 u.Path += "/" 150 } 151 152 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 153 if err != nil { 154 return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s", err) 155 } 156 157 r.BaseURL = u 158 return nil 159 }) 160 } 161 162 // HTTPClient is primarily used to describe an [*http.Client], but also 163 // supports custom implementations. 164 // 165 // For bespoke implementations, prefer using an [*http.Client] with a 166 // custom transport. See [http.RoundTripper] for further information. 167 type HTTPClient interface { 168 Do(*http.Request) (*http.Response, error) 169 } 170 171 // WithHTTPClient returns a RequestOption that changes the underlying http client used to make this 172 // request, which by default is [http.DefaultClient]. 173 // 174 // For custom uses cases, it is recommended to provide an [*http.Client] with a custom 175 // [http.RoundTripper] as its transport, rather than directly implementing [HTTPClient]. 176 func WithHTTPClient(client HTTPClient) RequestOption { 177 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 178 if client == nil { 179 return fmt.Errorf("requestoption: custom http client cannot be nil") 180 } 181 182 if c, ok := client.(*http.Client); ok { 183 // Prefer the native client if possible. 184 r.HTTPClient = c 185 r.CustomHTTPDoer = nil 186 } else { 187 r.CustomHTTPDoer = client 188 } 189 190 return nil 191 }) 192 } 193 194 // MiddlewareNext is a function which is called by a middleware to pass an HTTP request 195 // to the next stage in the middleware chain. 196 type MiddlewareNext = func(*http.Request) (*http.Response, error) 197 198 // Middleware is a function which intercepts HTTP requests, processing or modifying 199 // them, and then passing the request to the next middleware or handler 200 // in the chain by calling the provided MiddlewareNext function. 201 type Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error) 202 203 // WithMiddleware returns a RequestOption that applies the given middleware 204 // to the requests made. Each middleware will execute in the order they were given. 205 func WithMiddleware(middlewares ...Middleware) RequestOption { 206 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 207 r.Middlewares = append(r.Middlewares, middlewares...) 208 return nil 209 }) 210 } 211 212 // WithMaxRetries returns a RequestOption that sets the maximum number of retries that the client 213 // attempts to make. When given 0, the client only makes one request. By 214 // default, the client retries two times. 215 // 216 // WithMaxRetries panics when retries is negative. 217 func WithMaxRetries(retries int) RequestOption { 218 if retries < 0 { 219 panic("option: cannot have fewer than 0 retries") 220 } 221 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 222 r.MaxRetries = retries 223 return nil 224 }) 225 } 226 227 // WithHeader returns a RequestOption that sets the header value to the associated key. It overwrites 228 // any value if there was one already present. 229 func WithHeader(key, value string) RequestOption { 230 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 231 r.Request.Header.Set(key, value) 232 return nil 233 }) 234 } 235 236 // WithHeaderAdd returns a RequestOption that adds the header value to the associated key. It appends 237 // onto any existing values. 238 func WithHeaderAdd(key, value string) RequestOption { 239 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 240 r.Request.Header.Add(key, value) 241 return nil 242 }) 243 } 244 245 // WithHeaderDel returns a RequestOption that deletes the header value(s) associated with the given key. 246 func WithHeaderDel(key string) RequestOption { 247 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 248 r.Request.Header.Del(key) 249 return nil 250 }) 251 } 252 253 // WithQuery returns a RequestOption that sets the query value to the associated key. It overwrites 254 // any value if there was one already present. 255 func WithQuery(key, value string) RequestOption { 256 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 257 query := r.Request.URL.Query() 258 query.Set(key, value) 259 r.Request.URL.RawQuery = query.Encode() 260 return nil 261 }) 262 } 263 264 // WithQueryAdd returns a RequestOption that adds the query value to the associated key. It appends 265 // onto any existing values. 266 func WithQueryAdd(key, value string) RequestOption { 267 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 268 query := r.Request.URL.Query() 269 query.Add(key, value) 270 r.Request.URL.RawQuery = query.Encode() 271 return nil 272 }) 273 } 274 275 // WithQueryDel returns a RequestOption that deletes the query value(s) associated with the key. 276 func WithQueryDel(key string) RequestOption { 277 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 278 query := r.Request.URL.Query() 279 query.Del(key) 280 r.Request.URL.RawQuery = query.Encode() 281 return nil 282 }) 283 } 284 285 // WithJSONSet returns a RequestOption that sets the body's JSON value associated with the key. 286 // The key accepts a string as defined by the [sjson format]. 287 // 288 // [sjson format]: https://github.com/tidwall/sjson 289 func WithJSONSet(key string, value any) RequestOption { 290 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) { 291 var b []byte 292 293 if r.Body == nil { 294 b, err = sjson.SetBytes(nil, key, value) 295 if err != nil { 296 return err 297 } 298 } else if buffer, ok := r.Body.(*bytes.Buffer); ok { 299 b = buffer.Bytes() 300 b, err = sjson.SetBytes(b, key, value) 301 if err != nil { 302 return err 303 } 304 } else { 305 return fmt.Errorf("cannot use WithJSONSet on a body that is not serialized as *bytes.Buffer") 306 } 307 308 r.Body = bytes.NewBuffer(b) 309 return nil 310 }) 311 } 312 313 // WithJSONDel returns a RequestOption that deletes the body's JSON value associated with the key. 314 // The key accepts a string as defined by the [sjson format]. 315 // 316 // [sjson format]: https://github.com/tidwall/sjson 317 func WithJSONDel(key string) RequestOption { 318 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) { 319 if buffer, ok := r.Body.(*bytes.Buffer); ok { 320 b := buffer.Bytes() 321 b, err = sjson.DeleteBytes(b, key) 322 if err != nil { 323 return err 324 } 325 r.Body = bytes.NewBuffer(b) 326 return nil 327 } 328 329 return fmt.Errorf("cannot use WithJSONDel on a body that is not serialized as *bytes.Buffer") 330 }) 331 } 332 333 // WithResponseBodyInto returns a RequestOption that overwrites the deserialization target with 334 // the given destination. If provided, we don't deserialize into the default struct. 335 func WithResponseBodyInto(dst any) RequestOption { 336 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 337 r.ResponseBodyInto = dst 338 return nil 339 }) 340 } 341 342 // WithResponseInto returns a RequestOption that copies the [*http.Response] into the given address. 343 func WithResponseInto(dst **http.Response) RequestOption { 344 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 345 r.ResponseInto = dst 346 return nil 347 }) 348 } 349 350 // WithRequestBody returns a RequestOption that provides a custom serialized body with the given 351 // content type. 352 // 353 // body accepts an io.Reader or raw []bytes. 354 func WithRequestBody(contentType string, body any) RequestOption { 355 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 356 if reader, ok := body.(io.Reader); ok { 357 r.Body = reader 358 return r.Apply(WithHeader("Content-Type", contentType)) 359 } 360 361 if b, ok := body.([]byte); ok { 362 r.Body = bytes.NewBuffer(b) 363 return r.Apply(WithHeader("Content-Type", contentType)) 364 } 365 366 return fmt.Errorf("body must be a byte slice or implement io.Reader") 367 }) 368 } 369 370 // WithRequestTimeout returns a RequestOption that sets the timeout for 371 // each request attempt. This should be smaller than the timeout defined in 372 // the context, which spans all retries. 373 // 374 // An attempt that exceeds this timeout is retried (see [WithMaxRetries]) for as 375 // long as the context passed to the method is not done, so a call can take up to 376 // roughly (retries + 1) * dur plus backoff before it fails with context.DeadlineExceeded. 377 func WithRequestTimeout(dur time.Duration) RequestOption { 378 // we need this to be a PreRequestOptionFunc so that it can be applied at the endpoint level 379 // see: CalculateNonStreamingTimeout 380 return requestconfig.PreRequestOptionFunc(func(r *requestconfig.RequestConfig) error { 381 r.RequestTimeout = dur 382 return nil 383 }) 384 } 385 386 // withConfigClientKey mirrors auth.clientKey: one auth.TokenCache per transport. 387 type withConfigClientKey struct { 388 httpClient *http.Client 389 customDoer requestconfig.HTTPDoer 390 } 391 392 // WithConfig returns a [RequestOption] that authenticates requests using 393 // the given [config.Config]. 394 // 395 // Credential resolution is deferred to a request-time middleware so a 396 // later [WithAPIKey] / [WithAuthToken] (tier-1 explicit-credential 397 // precedence) can preempt the profile — even when the profile's 398 // credentials are missing or broken. The same escape-hatch shape as 399 // [explicitProfileErrorOption] applies: if the final RequestConfig has a 400 // static APIKey/AuthToken, or the request already carries an 401 // Authorization / X-Api-Key header, the profile is treated as shadowed 402 // and resolution is skipped entirely. 403 // 404 // The config's non-credential fields (BaseURL, WorkspaceID) apply at 405 // option-Apply time and do not depend on credential resolution. BaseURL 406 // only takes effect when no explicit [WithBaseURL] is present. 407 // 408 // When a static credential shadows the profile, a one-shot warning is 409 // emitted so users migrating from API keys can tell why their profile is 410 // being ignored. Use [WithConfigQuiet] to suppress that warning when the 411 // caller has its own, richer diagnostic. 412 func WithConfig(cfg *config.Config) RequestOption { 413 return withConfig(cfg, false) 414 } 415 416 // WithConfigQuiet is identical to [WithConfig] but suppresses the one-shot 417 // "static credential shadows profile" warning. Intended for callers (such as 418 // the CLI) that detect the same condition themselves and emit a more 419 // specific multi-auth diagnostic, so users don't see two warnings for the 420 // same thing. 421 func WithConfigQuiet(cfg *config.Config) RequestOption { 422 return withConfig(cfg, true) 423 } 424 425 // WithProfile returns a [RequestOption] that loads the named profile from 426 // the default config directory (see [config.DefaultDir]) and authenticates 427 // requests using it — equivalent to setting ANTHROPIC_PROFILE=name and 428 // constructing a zero-config client. Shorthand for: 429 // 430 // cfg, err := config.LoadProfile(config.DefaultDir(), name) 431 // ... option.WithConfig(cfg) 432 // 433 // If the profile cannot be loaded, the error is deferred to the first 434 // request and is preempted by a static credential ([WithAPIKey] / 435 // [WithAuthToken]) — the same escape hatch as the ANTHROPIC_PROFILE env 436 // path. As with [WithConfig], a static credential shadows the profile at 437 // request time and a one-shot warning is emitted. 438 func WithProfile(name string) RequestOption { 439 if name == "" { 440 return errOption(fmt.Errorf("option: WithProfile: profile name is empty")) 441 } 442 cfg, err := config.LoadProfile(config.DefaultDir(), name) 443 if err != nil { 444 loadErr := fmt.Errorf("option: WithProfile(%q): %w", name, err) 445 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 446 rc := r 447 check := func(req *http.Request, next func(*http.Request) (*http.Response, error)) (*http.Response, error) { 448 if rc.APIKey != "" || rc.AuthToken != "" { 449 return next(req) 450 } 451 if req.Header.Get("Authorization") != "" || req.Header.Get("X-Api-Key") != "" { 452 return next(req) 453 } 454 return nil, loadErr 455 } 456 r.Middlewares = append(r.Middlewares, check) 457 return nil 458 }) 459 } 460 return withConfig(cfg, false) 461 } 462 463 // joinedOption is the [RequestOption] returned by [Join]. It is a distinct 464 // type (rather than a RequestOptionFunc closure) so that 465 // [HasWithoutEnvironmentDefaults] can look inside it. 466 type joinedOption []RequestOption 467 468 func (opts joinedOption) Apply(r *requestconfig.RequestConfig) error { 469 return r.Apply(opts...) 470 } 471 472 // Join returns a single [RequestOption] that applies opts in order, at the 473 // position the joined option occupies in the caller's option list. It is 474 // equivalent to passing opts individually at that position, and lets a 475 // function that configures several aspects of a client return one option: 476 // 477 // func WithPlatform(cfg Config) option.RequestOption { 478 // return option.Join( 479 // option.WithoutEnvironmentDefaults(), 480 // option.WithBaseURL(cfg.URL), 481 // option.WithMiddleware(cfg.middleware()), 482 // ) 483 // } 484 // 485 // A [WithoutEnvironmentDefaults] marker nested inside a Join (at any depth) 486 // is still recognized by anthropic.NewClient. 487 func Join(opts ...RequestOption) RequestOption { 488 return joinedOption(opts) 489 } 490 491 // withoutEnvironmentDefaultsOption is the marker type returned by 492 // [WithoutEnvironmentDefaults]. Its Apply is a no-op; it is detected by 493 // [HasWithoutEnvironmentDefaults] before options are applied. 494 type withoutEnvironmentDefaultsOption struct{} 495 496 func (withoutEnvironmentDefaultsOption) Apply(*requestconfig.RequestConfig) error { return nil } 497 498 // WithoutEnvironmentDefaults returns a marker [RequestOption] that, when 499 // passed to anthropic.NewClient, causes it to skip the environment-based 500 // credential autoload performed by anthropic.DefaultClientOptions 501 // (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_PROFILE, env 502 // federation, fallback profile, ANTHROPIC_BASE_URL). The hardcoded 503 // production base-URL default is still applied so callers that supply only 504 // credentials get a working client. 505 // 506 // Intended for callers that perform their own credential resolution (such 507 // as the CLI) and want full control over which auth source is used, without 508 // the SDK's autoloader contributing a second [WithConfig] that would emit a 509 // duplicate shadow-warning. 510 func WithoutEnvironmentDefaults() RequestOption { 511 return withoutEnvironmentDefaultsOption{} 512 } 513 514 // HasWithoutEnvironmentDefaults reports whether opts contains a 515 // [WithoutEnvironmentDefaults] marker, looking inside options combined with 516 // [Join] at any depth. Used by anthropic.NewClient to decide whether to 517 // prepend anthropic.DefaultClientOptions. 518 func HasWithoutEnvironmentDefaults(opts []RequestOption) bool { 519 for _, o := range opts { 520 switch o := o.(type) { 521 case withoutEnvironmentDefaultsOption: 522 return true 523 case joinedOption: 524 if HasWithoutEnvironmentDefaults(o) { 525 return true 526 } 527 } 528 } 529 return false 530 } 531 532 func withConfig(cfg *config.Config, quiet bool) RequestOption { 533 var ( 534 shadowOnce sync.Once 535 resolveOnce sync.Once 536 provider auth.TokenProvider 537 resolveErr error 538 539 cacheMu sync.Mutex 540 cacheBy = map[withConfigClientKey]*auth.TokenCache{} 541 ) 542 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 543 // Non-credential config — applied unconditionally so a profile's 544 // base_url / workspace_id are honored even when its credentials 545 // are shadowed by a later WithAPIKey or fail to resolve. 546 if cfg.BaseURL != "" && r.BaseURL == nil { 547 if err := WithBaseURL(cfg.BaseURL).Apply(r); err != nil { 548 return err 549 } 550 } 551 // For federation profiles workspace_id is sent in the jwt-bearer 552 // exchange body, not as a request header (the minted token is 553 // already workspace-scoped, so the header would be ignored). 554 isFederation := cfg.AuthenticationInfo != nil && 555 cfg.AuthenticationInfo.Type == config.AuthenticationTypeOIDCFederation 556 if cfg.WorkspaceID != "" && !isFederation { 557 if err := WithHeader("anthropic-workspace-id", cfg.WorkspaceID).Apply(r); err != nil { 558 return err 559 } 560 } 561 562 // Both checks below are request-time middlewares so they observe 563 // the RequestConfig after ALL options have applied — order of 564 // WithConfig vs WithAPIKey/WithAuthToken in the caller's option 565 // list doesn't matter. 566 rc := r 567 if !quiet { 568 shadowCheck := func(req *http.Request, next func(*http.Request) (*http.Response, error)) (*http.Response, error) { 569 shadowOnce.Do(func() { 570 switch { 571 case rc.APIKey != "": 572 auth.WarnConfigShadowed("ANTHROPIC_API_KEY", detectShadowSource("ANTHROPIC_API_KEY")) 573 case rc.AuthToken != "": 574 auth.WarnConfigShadowed("ANTHROPIC_AUTH_TOKEN", detectShadowSource("ANTHROPIC_AUTH_TOKEN")) 575 } 576 }) 577 return next(req) 578 } 579 r.Middlewares = append(r.Middlewares, shadowCheck) 580 } 581 582 credCheck := func(req *http.Request, next func(*http.Request) (*http.Response, error)) (*http.Response, error) { 583 if rc.APIKey != "" || rc.AuthToken != "" { 584 return next(req) 585 } 586 if req.Header.Get("Authorization") != "" || req.Header.Get("X-Api-Key") != "" { 587 return next(req) 588 } 589 resolveOnce.Do(func() { 590 result, err := auth.ResolveCredentials(cfg) 591 if err != nil { 592 resolveErr = err 593 return 594 } 595 provider = result.Provider 596 }) 597 if resolveErr != nil { 598 return nil, resolveErr 599 } 600 601 handler := rc.HTTPClient.Do 602 if rc.CustomHTTPDoer != nil { 603 handler = rc.CustomHTTPDoer.Do 604 } 605 key := withConfigClientKey{httpClient: rc.HTTPClient, customDoer: rc.CustomHTTPDoer} 606 cacheMu.Lock() 607 cache, ok := cacheBy[key] 608 if !ok { 609 cache = auth.NewTokenCache(provider, handler) 610 cacheBy[key] = cache 611 } 612 cacheMu.Unlock() 613 return auth.NewMiddleware(cache, rc)(req, next) 614 } 615 r.Middlewares = append(r.Middlewares, credCheck) 616 return nil 617 }) 618 } 619 620 // detectShadowSource classifies where the shadowing static credential 621 // came from by inspecting whether the named env var is set. When the env 622 // var has a non-empty value the source is [auth.ConfigShadowFromEnv]; 623 // otherwise the credential must have come from an explicit option call. 624 func detectShadowSource(envVar string) auth.ConfigShadowSource { 625 if v, ok := os.LookupEnv(envVar); ok && v != "" { 626 return auth.ConfigShadowFromEnv 627 } 628 return auth.ConfigShadowFromOption 629 } 630 631 // WithEnvironmentProduction returns a RequestOption that sets the current 632 // environment to be the "production" environment. An environment specifies which base URL 633 // to use by default. 634 func WithEnvironmentProduction() RequestOption { 635 return requestconfig.WithDefaultBaseURL("https://api.anthropic.com/") 636 } 637 638 // WithAPIKey returns a RequestOption that sets the client setting "api_key". 639 func WithAPIKey(value string) RequestOption { 640 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 641 r.APIKey = value 642 return r.Apply(WithHeader("X-Api-Key", r.APIKey)) 643 }) 644 } 645 646 // WithAuthToken returns a RequestOption that sets the client setting "auth_token". 647 func WithAuthToken(value string) RequestOption { 648 return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { 649 r.AuthToken = value 650 return r.Apply(WithHeader("authorization", fmt.Sprintf("Bearer %s", r.AuthToken))) 651 }) 652 } 653 654 // WithWebhookKey returns a RequestOption that sets the client setting "webhook_key". 655 func WithWebhookKey(value string) RequestOption { 656 return requestconfig.PreRequestOptionFunc(func(r *requestconfig.RequestConfig) error { 657 r.WebhookKey = value 658 return nil 659 }) 660 }