Skip to content

Add AWS Bedrock as an alternative API provider#112

Open
shivamtiwari3 wants to merge 3 commits into
ankitvgupta:mainfrom
shivamtiwari3:feature/aws-bedrock-support
Open

Add AWS Bedrock as an alternative API provider#112
shivamtiwari3 wants to merge 3 commits into
ankitvgupta:mainfrom
shivamtiwari3:feature/aws-bedrock-support

Conversation

@shivamtiwari3
Copy link
Copy Markdown
Contributor

Summary

Adds AWS Bedrock as an optional API provider, allowing users who have AWS Bedrock access (but no direct Anthropic API key) to use all AI features in Exo.

  • Users can switch between Anthropic (direct API key) and AWS Bedrock (IAM credentials) in Settings
  • All AI features work with both providers: email analysis, draft generation, draft refinement, archive-ready analysis, sender lookup, memory learning
  • Bedrock credentials are validated on save with a live test call; on success the app switches provider immediately

Changes

New Infrastructure

  • src/main/lib/anthropic-client.ts (new): Central factory that returns either Anthropic or AnthropicBedrock based on config. Uses a structural type { messages: Pick<Anthropic["messages"], "create" | "stream"> } to satisfy both clients (they are siblings extending BaseAnthropic, not parent-child).
  • package.json: Added @anthropic-ai/bedrock-sdk@^0.29.1 dependency

Shared Types (src/shared/types.ts)

  • Added BEDROCK_MODEL_TIER_IDS mapping tiers to Bedrock cross-region inference profile IDs (us.anthropic.claude-*-v1:0)
  • Added BedrockConfigSchema/BedrockConfig (region, accessKeyId, secretAccessKey, sessionToken)
  • Added apiProvider and bedrock fields to ConfigSchema

Settings & IPC

  • src/main/ipc/settings.ipc.ts: getModelIdForFeature() now returns Bedrock model IDs when Bedrock is active; added settings:validate-bedrock-credentials IPC handler; resets services on provider change
  • src/preload/index.ts: Exposes validateBedrockCredentials to renderer

Services (constructor injection)

All services now accept an injected AnthropicClient instead of calling new Anthropic() internally:

  • src/main/services/email-analyzer.ts
  • src/main/services/draft-generator.ts
  • src/main/services/calendaring-agent.ts
  • src/main/services/archive-ready-analyzer.ts

IPC Handlers

Updated to create clients via createAnthropicClientFromConfig:

  • src/main/ipc/analysis.ipc.ts
  • src/main/ipc/archive-ready.ipc.ts
  • src/main/ipc/drafts.ipc.ts
  • src/main/ipc/memory.ipc.ts
  • src/main/services/draft-pipeline.ts
  • src/main/services/draft-edit-learner.ts
  • src/main/services/analysis-edit-learner.ts

Extensions

  • src/extensions/mail-ext-web-search/src/web-search-provider.ts: Accepts getClient: () => AnthropicClient callback
  • src/extensions/mail-ext-web-search/src/index.ts: Passes factory callback

UI (src/renderer/components/SettingsPanel.tsx)

  • New API Provider section with toggle between Anthropic and Bedrock
  • Bedrock credential fields: region, access key ID, secret access key, optional session token
  • Validates credentials on save (live test call) and shows success/error feedback
  • Note that Managed Agents (Claude Agent SDK) is not available on Bedrock

Notes

  • AnthropicBedrock and Anthropic are sibling classes (both extend BaseAnthropic) — not a subclass relationship. Using a structural type avoids union-type inference issues in TypeScript.
  • Pre-existing unit test failures in archive-ready.spec.ts and email-analyzer.spec.ts (Playwright/Vitest runner conflict) are unrelated to these changes and exist on main.

…nkitvgupta#24)

Root cause: the blocklist regex /[;&|`$><\n\r]/ missed several shell bypass
vectors — subshells via (), brace expansion via {}, line continuation via \,
history expansion via !, and command substitution via $() — making it possible
to chain commands past the filter.

Fix: replace the blocklist with SAFE_COMMAND_PATTERN, an allowlist that only
permits characters safe in shell arguments (letters, digits, spaces, common
path/option chars). Anything not explicitly permitted is denied, which closes
the entire class of bypass rather than playing whack-a-mole with metacharacters.

The hook logic is extracted into bash-hook.ts so it can be imported and tested
without triggering the Electron-specific require.resolve IIFE in the provider.
25 unit tests are added covering all bypass vectors from the issue plus
legitimate CLI tool invocations.
Users without a direct Anthropic API key can now use AWS Bedrock to
power all AI features (analysis, drafts, refinement, archive-ready,
memory learning). The provider is selectable in Settings → Agent →
Authentication with full credential management UI.

Key changes:
- New `src/main/lib/anthropic-client.ts` factory that returns an
  `AnthropicClient` (structural type satisfying both `Anthropic` and
  `AnthropicBedrock`) based on stored config
- `BedrockConfigSchema` + `BEDROCK_MODEL_TIER_IDS` added to shared types
- `apiProvider` and `bedrock` fields added to `ConfigSchema`
- `getModelIdForFeature` is now provider-aware, returning Bedrock cross-
  region inference profile IDs when Bedrock is active
- All service classes (EmailAnalyzer, DraftGenerator, CalendaringAgent,
  ArchiveReadyAnalyzer) accept an injected `AnthropicClient` instead of
  constructing one internally
- All inline `new Anthropic()` calls in IPC handlers and learner services
  replaced with `createAnthropicClientFromConfig(getConfig())`
- `settings:validate-bedrock-credentials` IPC handler for live credential
  validation before saving
- SettingsPanel UI: provider toggle (Anthropic / AWS Bedrock) with region,
  access key, secret key, optional session token, and validate-and-save flow
- Note in UI that the AI agent feature requires a direct Anthropic API key
  and is not available via Bedrock (Managed Agents is first-party only)
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 1, 2026

Greptile Summary

This PR adds AWS Bedrock as an optional API provider alongside the existing direct Anthropic API key path. All AI services now accept an injected AnthropicClient (a structural type satisfied by both SDKs), the bash-hook security check is upgraded from a blocklist to an allowlist, and the web-search enrichment correctly skips itself when Bedrock is active.

  • P1 — provider switch broken: When Bedrock is the active provider and the user clicks the "Anthropic API" toggle, the "Switch to Anthropic" button (inside the now-hidden Bedrock section) disappears. Clicking "Save" on the API key form then calls handleSaveApiKey, which only writes anthropicApiKey — it never sets apiProvider: \"anthropic\" — leaving the app still routing to Bedrock.

Confidence Score: 4/5

Safe to merge after fixing the provider-toggle persistence bug in SettingsPanel.

One P1 bug: users on Bedrock who click the Anthropic API toggle are left with no path to complete the provider switch via the Save button since handleSaveApiKey does not write apiProvider. All other changes are well-structured.

src/renderer/components/SettingsPanel.tsx — handleSaveApiKey must also persist apiProvider when the local selection differs from the saved config.

Important Files Changed

Filename Overview
src/renderer/components/SettingsPanel.tsx New Bedrock credential UI with provider toggle; P1 bug: clicking "Anthropic API" tab hides the "Switch to Anthropic" button and the "Save" button for the API key does not update apiProvider, leaving the user stuck on Bedrock.
src/main/lib/anthropic-client.ts New central factory; correctly uses structural AnthropicClient type to satisfy both SDK clients. Falls back to Anthropic when bedrock config is absent.
src/main/ipc/settings.ipc.ts getModelIdForFeature dispatches to Bedrock model IDs when provider is bedrock; validate-bedrock-credentials handler uses HTTP status codes for error bucketing.
src/shared/types.ts Adds BedrockConfig, resolveBedrockModelId with region-prefix derivation, and apiProvider field to ConfigSchema. Region prefix logic handles eu/ap/us but silently falls back to "us" for unsupported regions.
src/extensions/mail-ext-web-search/src/web-search-provider.ts canEnrich now returns false when Bedrock is active, correctly guarding the web_search_20250305 Anthropic-only tool. Client is now injected via callback.
src/main/agents/providers/bash-hook.ts Extracted and upgraded from claude-agent-provider.ts; switches from blocklist to allowlist pattern closing bypass vectors like subshells, brace expansion, and backslash continuation. Tests added.
src/main/services/draft-edit-learner.ts Hardcoded Anthropic model IDs replaced with getModelIdForFeature; client injection via createAnthropicClientFromConfig. Extended thinking stream call should work on Bedrock for supported models.

Sequence Diagram

sequenceDiagram
    participant UI as SettingsPanel (renderer)
    participant Preload as preload/index.ts
    participant IPC as settings.ipc.ts
    participant Factory as anthropic-client.ts
    participant Bedrock as AnthropicBedrock SDK
    participant Anthropic as Anthropic SDK

    UI->>Preload: validateBedrockCredentials(region, keys)
    Preload->>IPC: settings:validate-bedrock-credentials
    IPC->>Bedrock: new AnthropicBedrock + messages.create(haiku, max_tokens:1)
    Bedrock-->>IPC: success / HTTP error
    IPC-->>UI: { success, error? }

    UI->>Preload: settings.set({ apiProvider, bedrock })
    Preload->>IPC: settings:set
    IPC->>IPC: resetAnalyzer / resetArchiveReady / prefetchService.reset()

    Note over IPC,Factory: On next AI call
    IPC->>Factory: createAnthropicClientFromConfig(config)
    alt apiProvider === bedrock and config.bedrock exists
        Factory->>Bedrock: new AnthropicBedrock(region, keys)
        Bedrock-->>Factory: AnthropicClient
    else apiProvider === anthropic or no bedrock config
        Factory->>Anthropic: new Anthropic()
        Anthropic-->>Factory: AnthropicClient
    end
    Factory-->>IPC: AnthropicClient (structural type)
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/renderer/components/SettingsPanel.tsx:2079
**Provider toggle doesn't persist when switching from Bedrock → Anthropic**

Clicking "Anthropic API" only updates local React state (`setApiProvider("anthropic")`). When Bedrock is the currently-saved provider, this hides the Bedrock section — including the "Switch to Anthropic" button. The Anthropic API key form is revealed with its "Save" button, but `handleSaveApiKey` at line 480 only persists `anthropicApiKey`; it never writes `apiProvider: "anthropic"` to config. After clicking "Save", `generalConfig.apiProvider` stays `"bedrock"` and all AI calls keep going to Bedrock.

The simplest fix: have `handleSaveApiKey` also include the provider when the user's local selection differs from the saved one. Or, make the "Anthropic API" toggle button invoke `handleSwitchToAnthropic` directly (which already calls `window.api.settings.set({ apiProvider: "anthropic" })`) rather than only updating local state.

### Issue 2 of 2
src/renderer/components/SettingsPanel.tsx:530-534
**`handleSwitchToAnthropic` has no error handling**

If `window.api.settings.set` rejects, the error is swallowed silently — local state shows "Anthropic" as active but the saved config hasn't changed. A try/catch keeps UI state consistent with what was actually persisted.

Reviews (2): Last reviewed commit: "Address Greptile review: Bedrock web sea..." | Re-trigger Greptile

Comment thread src/shared/types.ts Outdated
Comment thread src/main/ipc/settings.ipc.ts Outdated
… IDs, typed error handling

- Skip sender lookup enrichment when Bedrock is active (web_search_20250305 is Anthropic-only)
- Replace static BEDROCK_MODEL_TIER_IDS with resolveBedrockModelId(tier, region) that derives
  the cross-region inference profile prefix (us/eu/ap) from the configured AWS region
- Use HTTP status codes (401/403 → bad creds, 404 → model unavailable) in the Bedrock
  credential validator instead of fragile substring matching on error messages
@shivamtiwari3
Copy link
Copy Markdown
Contributor Author

Addressed all three Greptile review findings in commit be603e6:

Fixed (P1) — Sender lookup broken on Bedrock
Added isBedrockActive: () => boolean parameter to createWebSearchProvider. canEnrich() now returns false when Bedrock is active, so the extension silently opts out rather than throwing a 400. The web_search_20250305 tool is Anthropic-only and cannot be made to work on Bedrock.

Fixed (P2) — US-only cross-region inference profile IDs
Replaced the static BEDROCK_MODEL_TIER_IDS map with resolveBedrockModelId(tier, region) which derives the cross-region inference profile prefix (us. / eu. / ap.) from the configured AWS region string. All call sites (model resolution, credential validation, and the Settings UI display) now pass the configured region so the correct profile ID is used for non-US deployments.

Fixed (P3) — Fragile substring matching on error messages
The credential validator now checks the HTTP status code (.status property on the API error, which is stable) first: 401/403 → bad credentials, 404 → model unavailable. String matching is only used as a fallback for pre-HTTP errors (credential resolution failures before a request is made), where the message patterns are fewer and more specific ("Could not load credentials", "resolve credentials").

</h5>
<div className="flex gap-3">
<button
onClick={() => setApiProvider("anthropic")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Provider toggle doesn't persist when switching from Bedrock → Anthropic

Clicking "Anthropic API" only updates local React state (setApiProvider("anthropic")). When Bedrock is the currently-saved provider, this hides the Bedrock section — including the "Switch to Anthropic" button. The Anthropic API key form is revealed with its "Save" button, but handleSaveApiKey at line 480 only persists anthropicApiKey; it never writes apiProvider: "anthropic" to config. After clicking "Save", generalConfig.apiProvider stays "bedrock" and all AI calls keep going to Bedrock.

The simplest fix: have handleSaveApiKey also include the provider when the user's local selection differs from the saved one. Or, make the "Anthropic API" toggle button invoke handleSwitchToAnthropic directly (which already calls window.api.settings.set({ apiProvider: "anthropic" })) rather than only updating local state.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/components/SettingsPanel.tsx
Line: 2079

Comment:
**Provider toggle doesn't persist when switching from Bedrock → Anthropic**

Clicking "Anthropic API" only updates local React state (`setApiProvider("anthropic")`). When Bedrock is the currently-saved provider, this hides the Bedrock section — including the "Switch to Anthropic" button. The Anthropic API key form is revealed with its "Save" button, but `handleSaveApiKey` at line 480 only persists `anthropicApiKey`; it never writes `apiProvider: "anthropic"` to config. After clicking "Save", `generalConfig.apiProvider` stays `"bedrock"` and all AI calls keep going to Bedrock.

The simplest fix: have `handleSaveApiKey` also include the provider when the user's local selection differs from the saved one. Or, make the "Anthropic API" toggle button invoke `handleSwitchToAnthropic` directly (which already calls `window.api.settings.set({ apiProvider: "anthropic" })`) rather than only updating local state.

How can I resolve this? If you propose a fix, please make it concise.

@ankitvgupta
Copy link
Copy Markdown
Owner

let's hold off on my ollama PR first bc that cleans up a lot of the code to have multiple backends

@shivamtiwari3
Copy link
Copy Markdown
Contributor Author

Sounds good, @ankitvgupta — I'll hold this and rebase on top of the Ollama PR once it lands so the backend abstraction is aligned. Ping me whenever you'd like me to pick it up again.

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.

2 participants