Skip to content

feat(providers): add AWS Bedrock as a built-in provider with native SigV4 auth - #705

Open
cru-Luis-Rodriguez wants to merge 5 commits into
alibaba:mainfrom
cru-Luis-Rodriguez:feat/bedrock-provider
Open

feat(providers): add AWS Bedrock as a built-in provider with native SigV4 auth#705
cru-Luis-Rodriguez wants to merge 5 commits into
alibaba:mainfrom
cru-Luis-Rodriguez:feat/bedrock-provider

Conversation

@cru-Luis-Rodriguez

@cru-Luis-Rodriguez cru-Luis-Rodriguez commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes #659. Adds bedrock as a built-in provider with native SigV4 auth, taking option (1) direct dependency as you decided in that issue.

Two commits: the provider itself, then the config/CLI surface it turned out to need. The second one exists because registering a provider was not sufficient to make it configurable — details under Q1–Q3 below.

Sample config

{
  "provider": "bedrock",
  "model": "us.anthropic.claude-sonnet-4-6",
  "providers": {
    "bedrock": {
      "aws_region": "us-west-2",
      "aws_profile": "example-profile"
    }
  }
}

Both AWS fields are optional; without them the standard credential chain decides, as with any other AWS tool. There is no api_key line, and none is accepted as a substitute for a signature.

Answers to the five questions from #659

1. Provider entry shape. api_key is optional and ignored, not forbidden. Rejecting a stray key would punish someone who pasted one out of habit, and it cannot leak into a request: the client deletes Authorization and X-Api-Key before the signing middleware runs. The resolver's gate is now apiKey == "" && apiKeyCmd == "" && !ambientAuth, where ambientAuth follows the protocol actually in force rather than the preset flag — an entry that overrides providers.bedrock.protocol to a non-signing protocol needs a token again, and an api_key_cmd from #605 satisfies the requirement exactly as a static key does. Every key-based provider keeps its requirement — TestNonAmbientProviderStillRequiresAPIKey pins that.

aws_region and aws_profile are real fields on the provider entry, JSON-serializable under providers.bedrock, not read purely from the env chain. Pinning them is what makes a run reproducible without exporting AWS_PROFILE first, which matters most on CI runners that carry a different default.

While wiring that up I found a bug worth fixing here rather than later: the fields were readable by the resolver but absent from the ProviderEntry struct the CLI marshals. Config is unmarshalled into that struct and written back on every config command, so a hand-written aws_region was silently deleted the first time the user ran ocr config model or ocr config set — no error, and nothing to explain why the next review reached a different region. TestConfigRoundTripKeepsAWSSettings is the regression test.

2. Interactive wizard. Keyed off AmbientAuth, never off a provider name. For an ambient provider the model step is the last one — the wizard confirms there instead of advancing to an API-key prompt that has to be left blank, which reads as a step the user failed to complete. Before this, bedrock was unreachable through ocr config provider entirely: applyOfficialProviderConfig rejected the empty key. That gate is now a named checkAPIKeyRequirement so the ambient case is explicit and testable, and apiKeyStepCanConfirm also accepts an empty value for an ambient provider, which is the state reachable when an existing config is edited.

Yes, it still runs the connection test at the end — that is exactly where an expired SSO session surfaces, and the cheapest moment to catch it.

What is not in this PR: collecting region and profile as two optional inputs in that step. It needs a real design decision about the wizard's shape (placeholders showing what the AWS chain currently resolves, so blank reads as "inherit us-west-2" rather than "unset"), and I would rather agree on it with you than guess. Until then the wizard configures bedrock and leaves both to the AWS chain, and ocr config set covers pinning them. Happy to add it as a follow-up commit here.

3. Non-interactive config. Yes, both need to be settable, and they now are:

ocr config set providers.bedrock.aws_region us-west-2
ocr config set providers.bedrock.aws_profile example-profile

One applyProviderField case covers providers.* and custom_providers.*, since setProviderValue and setCustomProviderField both funnel there. Values are trimmed; whitespace inside one is rejected. Region names are not validated against a fixed list — AWS adds regions faster than an embedded list stays correct, and a wrong region already fails at request time with a clear message (see Q5). Setting either field on a provider that authenticates by api_key is an error rather than stored dead config, which catches providers.anthropic.aws_region typos; for a custom provider it is accepted once the entry declares the bedrock protocol.

The unknown-config-key message is pinned byte-for-byte by an existing test, so it is updated for the two new fields and for anthropic-bedrock as a protocol value.

4. Model list semantics. The preset ships a Models list, but it should be read as a starting point, not a closed set, and free-form entry has to keep working — the existing custom-model path in the model picker already provides it. Three reasons Bedrock cannot be validated like a hosted API:

  • Inference profile IDs differ per account and per region.
  • An application inference profile ARN is account-specific and long, and is the right value when usage has to be attributed for cost allocation.
  • Suffix conventions vary per family, so IDs cannot be derived. us.anthropic.claude-sonnet-5 is correct while us.anthropic.claude-sonnet-5-v1:0 is rejected outright. The listed IDs are taken verbatim from aws bedrock list-inference-profiles on a live account rather than inferred, and global.* variants are listed beside us.* since either is a valid routing target.

So an ID missing from the preset must not be rejected. That is now implemented rather than merely argued: preset.Models still gates a --model override for key-based providers — a typo against a hosted API is worth catching locally — but not for an ambient-auth preset, where the list is a picker for ocr config model and nothing more. Before that change, --model with an application inference profile ARN failed locally with "is not available for provider", which contradicted this very section.

5. Connection-test messaging. Bedrock's own wording sends people after the wrong problem, so failures are classified before falling through to a generic error. Real output from this branch:

$ ocr llm test
Source: provider:bedrock
Region: us-east-1
Profile: example-profile
Model:  claude-sonnet-5
✓ Connection test successful

The URL line is replaced by region and profile, because bedrock has no configured URL — the region decides the host, and a request that reached the wrong one otherwise fails as though the model ID were malformed.

Failure What the user sees
Unresolvable profile / no AWS config "bedrock uses the standard AWS credential chain — set AWS_PROFILE, or run aws sso login --profile NAME"
Expired session (ExpiredToken, refresh failure) Names the profile in the aws sso login suggestion
AccessDeniedException: You don't have access to the model… Model access is granted per account and per region in the console; an IAM policy alone does not enable it
AccessDeniedException naming bedrock:InvokeModel "credentials resolved, so this is an authorization gap" — the IAM side
Invalid model identifier Points at aws bedrock list-inference-profiles --region <r> and calls out the -v1:0 suffix trap
Anything else (ValidationException on max_tokens, a reset, a throttle) Keeps the service's own wording, with the region and profile appended
Invalid API Key format Explains that no api_key applies to bedrock and that a bearer token reached the request; if AWS_BEARER_TOKEN_BEDROCK is set, names that variable specifically

Two notes on that last row. It is checked first, because it occurs with otherwise-valid credentials and a later "expired"/"denied" branch would mislabel it. And the SDK reports the pre-middleware /v1/messages path in its error text, which makes correct path rewriting look broken — the surrounding message now says which region and profile were actually used, so the URL is not the only context available.

Every other protocol shares this client type, so the translation is gated on the bedrock flag and returns other errors untouched (TestExplainErrorLeavesNonBedrockErrorsAlone).

On the SDK bug

Per your note, the workaround stays in this patch: bedrock.WithConfig prefers bearer auth whenever cfg.BearerAuthTokenProvider is non-nil, and LoadDefaultConfig populates it from the SSO token cache — so an SSO-authenticated caller silently sends its OIDC access token and gets 403 Invalid API Key format. The provider is therefore cleared before WithConfig runs.

There is a second, smaller SDK problem worth knowing about, which I hit while reviewing my own patch. WithConfig's doc comment says the environment variable takes precedence:

Authentication is determined as follows: if the AWS_BEARER_TOKEN_BEDROCK environment variable is set, it is used for bearer token authentication. Otherwise, if cfg.BearerAuthTokenProvider is set, it is used.

The code does the opposite — it consults the variable only if cfg.BearerAuthTokenProvider == nil. My first version of this patch trusted the comment and cleared the provider only when the variable was unset, which meant an SSO user who deliberately set a Bedrock API key still sent the SSO token. Clearing unconditionally is what actually delivers the documented precedence, so that is what this does. Glad to open the anthropic-sdk-go issue for both points separately; say the word if you would rather this patch shrink once they land.

bedrock.WithLoadDefaultConfig also panics when AWS config cannot be loaded, so the config is loaded directly and the failure deferred to the first request as a sentence naming the likely fix — a CLI should not answer an expired session with a stack trace.

Verification

  • Full suite passes under -race; go vet clean and gofmt -s clean.
  • New tests: protocol registration, preset shape, resolution with no api_key, AWS settings reaching the client, the api_key requirement surviving for non-ambient providers, deferred-panic behaviour, config round-trip preserving the AWS fields, config set accept/reject/trim cases, the wizard skipping the key step, and each error classification.
  • End-to-end against a live Bedrock account: reviews complete using SigV4 credentials from an SSO profile with no AWS variables in the environment; a -v1:0 model ID and an unresolvable profile each produce their intended message rather than a bare 400 or 403.

Rebased on current main, so it carries the finalizeResolvedEndpoint refactor, the per-run provider/model overrides from #687, and the api_key_cmd / auth_token_cmd resolution from #605. That last one overlaps this patch — both change the credential gate — and the overlap surfaced two defects a clean textual merge would have shipped:

  • The site that runs api_key_cmd assumed an empty api_key implied a command was set, which held while api_key was mandatory. With ambient auth both are legitimately empty, so bedrock ran an empty command and failed with api_key_cmd for provider "bedrock" produced empty output. It is now gated on a non-empty command.
  • cloneProviderEntry did not copy aws_region / aws_profile, so editing a provider through the wizard silently dropped a pinned region or profile. The reflect-based TestCloneProviderEntry_CopiesEveryField now on main caught it; the clone copies both fields and the fixture sets them.

The resolver change itself is down to one completeness condition and one gate.

@cru-Luis-Rodriguez

Copy link
Copy Markdown
Contributor Author

Pushed a third commit (0da3406) after reviewing the first two adversarially. Four defects, each verified by execution or against SDK source rather than inferred — flagging them here so the delta is visible rather than buried in a force-push. The description above is updated to match.

  1. AWS_BEARER_TOKEN_BEDROCK was unreachable for exactly the users it should serve. I had cleared cfg.BearerAuthTokenProvider only when the variable was unset, trusting WithConfig's doc comment that the variable takes precedence. The code does the opposite — it reads the variable only if cfg.BearerAuthTokenProvider == nil. So an SSO profile plus a deliberately set Bedrock API key still sent the SSO OIDC token, and the error message then blamed a token that never left the machine. Cleared unconditionally now, which is what actually delivers the documented precedence. Details in the SDK section of the description.

  2. A model the account has not enabled was reported as an IAM problem. Bedrock returns AccessDeniedException for both, but You don't have access to the model with the specified model ID is fixed by enabling model access in the console, per account and per region — no IAM policy provides it. The specific wording is now matched ahead of the generic code; my clause for it had been stranded in an unreachable branch.

  3. A bare ValidationException match over-triggered. Input is too long for requested model was answered with "go list your inference profiles". Only the model-identifier wording is matched now; everything else keeps the service's own message. Same treatment for the credential-expiry arm, which had matched a bare expired and so claimed x509: certificate has expired was an SSO problem.

  4. --model rejected identifiers absent from the preset list, which contradicted the Q4 answer in this very description. A preset list cannot be an allowlist for Bedrock: IDs are account- and region-scoped, and an application inference profile ARN can never appear in a list compiled upstream. The list no longer gates an override for an ambient-auth provider; key-based providers keep the check.

Also dropped a cfg.URL normalization block that could not have had any effect (WithConfig is appended last and installs its own base URL), pinned AWS_CONFIG_FILE in the one test that was reading the developer's real ~/.aws/config, and fixed a column alignment in ocr llm test.

New tests cover each: the two AccessDeniedException shapes with their verbatim service wording, a request-shape ValidationException falling through to the generic message, the TLS-certificate case, and --model accepting an ARN for bedrock while still rejecting a typo for a key-based provider. Suite, go vet and gofmt clean; ocr llm test re-verified against a live account, and an identifier the preset does not list now reaches Bedrock and gets Bedrock's own verdict instead of a local rejection.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 3 issue(s) in this PR.

  • ✅ Successfully posted inline: 2 comment(s)
  • ❌ Failed to post inline: 1 comment(s)

[documentation · low]

📄 internal/llm/protocol.go (L60-L61)

⚠️ GitHub could not post this as an inline comment: Unprocessable Entity: "Line could not be resolved" - https://docs.github.com/rest/pulls/reviews#create-a-review-for-a-pull-request

The comment says "accepts the three canonical protocol names" but there are now four supported protocols (including ProtocolAnthropicBedrock). Update the comment to reflect the new count.

💡 Suggested Change

Before:

// ValidateProtocol accepts the three canonical protocol names and rejects
// everything else.

After:

// ValidateProtocol accepts the four canonical protocol names and rejects
// everything else.

Comment thread cmd/opencodereview/config_cmd.go
Comment thread internal/llm/client.go Outdated
@cru-Luis-Rodriguez

Copy link
Copy Markdown
Contributor Author

Addressed all three findings from the OCR run above in 68d250c, with replies in the two inline threads.

The third one could not be posted inline ("Line could not be resolved"), so for the record: it was right. ValidateProtocol said "the three canonical protocol names" while accepting four. Fixed, and the internal/llm package comment now lists anthropic-bedrock among the supported protocols too — it had the same omission, one the bot did not flag.

Worth noting on the second finding (ValidationException too broad): that run reviewed fdd1ad2, one commit behind, and 0da3406 had already narrowed it. So the finding was accurate against the commit it saw. The first finding — ambient auth read off the preset while the protocol can be overridden per entry — was live and is the substantive fix here; it also existed on the resolver side, where it let an overridden entry resolve with no credentials at all.

Suite, go vet and gofmt clean; ocr llm test re-verified against a live Bedrock account after the change.

@lizhengfeng101

Copy link
Copy Markdown
Collaborator

CI / test (pull_request)
CI / test (pull_request)Failing after 39s

@cru-Luis-Rodriguez

@cru-Luis-Rodriguez

Copy link
Copy Markdown
Contributor Author

Thanks for the ping — fixed in 273357a.

The failure was the govulncheck step, not a test. GO-2026-5764 affects github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3, and the trace runs through this PR's own new dependency (llm.initbedrock.initeventstream.init), so it's on me to clear it.

aws-sdk-go-v2/config is the only direct AWS import here, so bumping it from v1.27.27 to v1.32.34 pulls the tree forward and resolves eventstream to v1.7.16, past the v1.7.8 fix. smithy-go moves to v1.27.6 as its runtime companion; internal/ini drops out and internal/v4a / service/signin come in, both internal restructuring within aws-sdk-go-v2 itself. No non-AWS dependency moves — the diff is go.mod and go.sum only.

On why the core module jumps so many minors: there's no supported way to move eventstream past v1.7.8 while holding the 2024-era pins, since MVS resolves it from the direct dependency. This is the minimal coherent bump.

Verified locally against the CI configuration (go1.26.5): go build ./..., gofmt -s, go vet, and go mod tidy are all clean, go test -race -count=1 ./... passes across all packages, and govulncheck ./... reports 0 affecting vulnerabilities.

One note for a separate PR: govulncheck still reports GO-2026-5970 (golang.org/x/text v0.37.0) and GO-2026-5942 (golang.org/x/net v0.55.0) as imported-but-not-called. Neither affects the exit code and both pre-date this PR, so I left them out to keep this diff scoped to the AWS tree — happy to open a follow-up if you'd like them bumped.

@lizhengfeng101

lizhengfeng101 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@cru-Luis-Rodriguez CI is failing due to missing SPDX license headers on the two new test files:

cmd/opencodereview/bedrock_config_test.go (missing SPDX identifier)
internal/llm/bedrock_test.go (missing SPDX identifier)

The merge conflict has been resolved on our side. Could you rebase on main and run make license-add to fix the headers?

@yq314

yq314 commented Aug 14, 2026

Copy link
Copy Markdown

Just checking in, when can we get this merged?

@cru-Luis-Rodriguez

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (0c8c067) — conflicts resolved, all five commits preserved.

On make license-add: already done, back in the 8/7 rebase. Both new test files carry the SPDX identifier and copyright line, make license-check reports "All source files have valid license headers," and the license step is green in CI. Nothing outstanding there.

The conflicts this round came entirely from the retry work that landed since — retry_codes (#818), the session-key prompt-cache affinity (#332), and the retry report (#790). Those added RetryCodes, SessionKey and retryCollector to ClientConfig and moved NewLLMClient to take a collector. Most of the merge was mechanical.

One part was not, and it's the piece worth your eye. NewAnthropicBedrockClient built its own client and applied ExtraHeaders at construction time, so it would have silently skipped all three of those features — a Bedrock endpoint would have ignored retry_codes and gone missing from the retry report. It now defaults cfg.SessionKey and installs retryCodesMiddleware and newRetryObserver as middlewares, with bedrock.WithConfig still appended last so SigV4 signing wraps them. That's new behavior rather than a conflict resolution, so I'd rather name it here than have it read as a mechanical rebase.

Green across the board — test, CodeQL, and all five cross-compile targets.

@lizhengfeng101 lizhengfeng101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge conflicts

cru-Luis-Rodriguez and others added 5 commits August 17, 2026 14:09
Bedrock serves the same Messages API as api.anthropic.com, so this reuses
AnthropicClient wholesale and lets the official SDK's bedrock middleware
handle what differs: SigV4 signing, moving the model from the body into the
URL path, injecting anthropic_version, and deriving the host from the region.
No new protocol implementation, no AWS request plumbing.

Configuration is an empty provider entry — there is no api_key to set:

  {
    "provider": "bedrock",
    "model": "us.anthropic.claude-sonnet-4-6",
    "providers": { "bedrock": { "aws_profile": "...", "aws_region": "..." } }
  }

aws_profile and aws_region are optional; without them the standard AWS chain
decides, as with any other AWS tool. Setting them makes a run reproducible
without exporting AWS_PROFILE first. Model accepts a foundation model ID, an
inference profile ID, or an application inference profile ARN when usage has
to be attributed for cost allocation.

Four things this needed beyond registering a provider, each found by running
it rather than reading it:

  - The resolver required a non-empty api_key, and separately required both
    URL and Token to consider an endpoint complete. Bedrock has none of the
    three, so a correct config fell through every strategy and reported "no
    valid LLM endpoint configured" — the error for having configured nothing.
    Both gates now recognise ambient authentication, via an AmbientAuth flag
    on Provider and ResolvedEndpoint. Providers that do use api_key are
    unaffected, which TestNonAmbientProviderStillRequiresAPIKey pins.

  - bedrock.WithConfig prefers bearer auth over SigV4 whenever
    cfg.BearerAuthTokenProvider is non-nil, and LoadDefaultConfig populates
    that provider from the SSO token cache. An SSO-authenticated caller —
    most enterprise setups — therefore sent its OIDC access token and got
    403 "Invalid API Key format: Must start with pre-defined prefix". The
    provider is cleared unless AWS_BEARER_TOKEN_BEDROCK was set deliberately,
    which restores SigV4 while leaving an explicit bearer token working.

  - The SDK would also attach an API-key header of its own, which Bedrock
    rejects even when empty. Authorization and X-Api-Key are removed before
    the signing middleware runs.

  - bedrock.WithLoadDefaultConfig panics when AWS config cannot be loaded.
    A CLI should not answer an expired session with a stack trace, so the
    config is loaded directly and the failure deferred to the first request
    as a sentence naming the likely fix.

The preset's Models list is taken verbatim from `aws bedrock list-inference-profiles`
on a live account rather than inferred: suffix conventions vary per family, so
us.anthropic.claude-sonnet-5 is correct while us.anthropic.claude-sonnet-5-v1:0 is
rejected with 400 "The provided model identifier is invalid." The global.* cross-region
variants are listed alongside us.* since either is a valid routing target. That list
only gates --model overrides; an application inference profile ARN still works via the
model field.

Two existing tests needed updating: the provider-order list gains "bedrock",
and TestProviders_AllProtocolsCanonical now delegates to ValidateProtocol
instead of re-listing the canonical names, so the next protocol added cannot
silently leave it behind.

Verified end-to-end against a live Bedrock account: reviews complete and
return findings using SigV4 credentials from an SSO profile, with no AWS
variables in the environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e CLI

Registering the provider was not enough to make it usable: every config-related
path still assumed an api_key, and Bedrock's own error wording sends users after
the wrong problem.

  - ProviderEntry gains aws_profile and aws_region. They were readable by the
    resolver but absent from the struct the CLI marshals, and config is
    unmarshalled into it and written back on every config command — so a
    hand-written aws_region was silently deleted the first time the user ran
    `ocr config model`, with no error and nothing to suggest why the next review
    reached a different region.

  - `ocr config set providers.<name>.aws_region|aws_profile` now works, for both
    the providers and custom_providers paths. Values are trimmed; whitespace
    inside one is rejected. Region names are deliberately not validated against
    a fixed list — AWS adds regions faster than an embedded list stays correct,
    and a wrong region already fails at request time. Setting either field on a
    provider that authenticates by api_key is an error rather than dead config
    that reads as applied.

  - The provider wizard treats the model step as final for an ambient provider
    instead of demanding a key. An API-key prompt that has to be left blank reads
    as a step the user failed to complete, and applyOfficialProviderConfig
    rejected the empty value anyway, so bedrock was unreachable through
    `ocr config provider` entirely. The gate is now a named check keyed off
    AmbientAuth, so key-based providers keep the requirement.

  - `ocr llm test` prints the resolved region and profile in place of the URL,
    which is empty for bedrock because the region decides the host. A request
    that reached the wrong region otherwise fails as though the model ID were
    malformed.

  - Bedrock rejections are translated into the action that fixes them, since two
    of them are actively misleading as the service words them: "Invalid API Key
    format" names a credential no bedrock user can configure (it means a bearer
    token reached the request), and a model merely absent from the region comes
    back as "The provided model identifier is invalid." Expired credentials point
    at `aws sso login` with the profile filled in; AccessDenied is named as an
    IAM gap on bedrock:InvokeModel rather than a bad credential; a rejected model
    points at `aws bedrock list-inference-profiles` and the -v1:0 suffix trap.
    Every other protocol shares this client type, so the translation is gated on
    the bedrock flag and returns other errors untouched.

The unknown-config-key message is pinned byte-for-byte by an existing test; it is
updated for the two new provider fields and for anthropic-bedrock as a protocol
value.

Verified against a live Bedrock account: `ocr llm test` reports region and
profile and completes over SigV4; a -v1:0 model ID and an unresolvable profile
each produce their intended message rather than a bare 400 or 403.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l gating

Four defects found reviewing the two commits before this one. Each was verified
by execution or against SDK source, not inferred.

  - AWS_BEARER_TOKEN_BEDROCK was unreachable for exactly the users it was meant
    to serve. The provider was cleared only when the variable was unset, on the
    strength of WithConfig's doc comment ("if the AWS_BEARER_TOKEN_BEDROCK
    environment variable is set, it is used"). The code disagrees with that
    comment: bedrock.go consults the variable only `if
    cfg.BearerAuthTokenProvider == nil`. So an SSO profile plus a deliberately
    configured Bedrock API key sent the SSO OIDC token instead of the key — the
    same silent substitution this patch exists to prevent, and explainError then
    blamed a token that never left the machine. Cleared unconditionally now,
    which is what gives the variable the precedence it documents.

  - A model that the account has not enabled was reported as an IAM problem.
    Bedrock answers both authorization failures with AccessDeniedException, and
    the fixes have nothing in common: "You don't have access to the model with
    the specified model ID" needs model access granted in the console, per
    account and per region, which no IAM policy provides. The specific wording
    is now matched ahead of the generic code, and the clause for it is no longer
    stranded in an unreachable branch.

  - A bare ValidationException match claimed every request-shape rejection was a
    model-ID problem: "Input is too long for requested model" sent the user off
    to list inference profiles. Only the model-identifier wording is matched now;
    everything else keeps the service's own message, which is the whole point of
    the function. The credential-expiry arm likewise no longer matches a bare
    "expired", which caught `x509: certificate has expired`.

  - --model rejected any Bedrock identifier absent from the preset's Models list,
    contradicting both the preset's own comment and this PR's description. A
    preset list cannot be an allowlist here: identifiers are scoped to an account
    and a region, and an application inference profile ARN — the value to use
    when spend has to be attributed — can never appear in a list compiled
    upstream. The list stays a picker for `ocr config model`; it no longer gates
    an override for an ambient-auth provider. Key-based providers keep the
    check, so a typo against a hosted API is still caught locally.

Also: dropped a cfg.URL normalization block that could not have any effect,
since WithConfig is appended last and installs its own base URL — the comment
claimed a purpose the code did not have. Pinned AWS_CONFIG_FILE in the test that
constructs a client, which was reading the developer's real ~/.aws/config. Fixed
the column alignment of the region line in `ocr llm test`.

Verified: `ocr llm test` still completes over SigV4 against a live account; an
identifier the preset does not list now reaches Bedrock and returns Bedrock's own
verdict rather than a local rejection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OCR's own review of this PR found that ambient auth was read off the preset while
the protocol could be overridden per entry, which left two ways to configure
something that reads as applied and cannot work.

`providers.bedrock.protocol = openai` resolved with no api_key and no URL: the
key requirement was skipped because the preset declares AmbientAuth, but the
endpoint then spoke a protocol with no SigV4 signing and carried nothing to
authenticate with. Ambient auth is now derived from the protocol actually in
force, after the override is applied, so such an entry needs a token again — and
conversely an entry that selects the bedrock protocol explicitly signs its
requests whatever preset it sits under. The same value gates the --model
allowlist, which had the same preset-only assumption.

`ocr config set providers.bedrock.aws_region` accepted AWS settings on that same
overridden entry. The check now lets the entry's protocol decide whenever it sets
one, falling back to the preset's flag only when the entry is silent.

Also corrects two stale doc comments the review flagged: ValidateProtocol accepts
four protocol names, not three, and the package comment now lists
anthropic-bedrock among the supported protocols.

The third finding in that review — a bare ValidationException match in
explainError — was already fixed in the preceding commit; the bot reviewed the
commit before it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
govulncheck fails the CI test job because the pinned AWS SDK tree pulls
in github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3, which
is affected by GO-2026-5764 (fixed in v1.7.8). Upgrading the direct
dependency aws-sdk-go-v2/config to current resolves eventstream to
v1.7.16, past the fixed version.

The diff is scoped to the AWS module tree (plus smithy-go, its runtime
companion); no other dependencies move. The Bedrock provider's behavior
is unchanged: the newer config module still populates
BearerAuthTokenProvider from the SSO token cache, so the unconditional
clearing in NewAnthropicBedrockClient remains necessary and correct,
and it still does not consult AWS_BEARER_TOKEN_BEDROCK itself, so the
anthropic-sdk-go re-read of that variable keeps its documented
precedence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cru-Luis-Rodriguez

cru-Luis-Rodriguez commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@lizhengfeng101
Rebased onto current main (533f736) — the branch went conflicting once #605, #687 and the two new provider presets landed. What the conflicts were and how they were resolved:

  • Credential gate (internal/llm/resolver.go) — feat(config): resolve api_key/auth_token from a command (#236) #605 added api_key_cmd to the same gate this patch relaxes for ambient auth. It now reads apiKey == "" && apiKeyCmd == "" && !ambientAuth, with your error wording kept verbatim.
  • Completeness check — the ambient-auth condition carried over to the new finalizeResolvedEndpoint(name, ep, env) signature.
  • checkAPIKeyRequirement — now takes apiKeyCmd, so a configured command satisfies the wizard the same way a static key does. That was feat(config): resolve api_key/auth_token from a command (#236) #605's behaviour at the call site this patch replaced, so it had to move into the function rather than be dropped.
  • Provider list and the two byte-pinned help strings — unions, not either/or: api_key_cmd alongside aws_region, aws_profile, and anthropic-bedrock alongside the existing protocol values.

Two defects came out of the overlap with #605 rather than out of the text conflict, and both are fixed here with tests:

  • The site that runs api_key_cmd assumed an empty api_key implied a command was set — true while an api_key was mandatory. Under ambient auth both are legitimately empty, so bedrock ran an empty command and failed with api_key_cmd for provider "bedrock" produced empty output. Now gated on a non-empty command.
  • cloneProviderEntry did not copy aws_region / aws_profile, so editing a provider through the wizard silently dropped a pinned region or profile. Your reflect-based TestCloneProviderEntry_CopiesEveryField caught it — a good test; it found this before I did.

Verified on the rebased branch: go test -race -count=1 ./..., go vet, gofmt -s, go mod tidy (no diff), line endings, and the license / action-pin / english-only scripts all pass.

The description is updated for the new gate and for what the rebase carries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scoping a first-class AWS Bedrock provider with native SigV4 auth (follow-up to #50)

3 participants