Add Amazon Bedrock provider for the LLM settings (Converse + OpenAI)#14
Open
aws-scottm wants to merge 1 commit intoagent0ai:mainfrom
Open
Add Amazon Bedrock provider for the LLM settings (Converse + OpenAI)#14aws-scottm wants to merge 1 commit intoagent0ai:mainfrom
aws-scottm wants to merge 1 commit intoagent0ai:mainfrom
Conversation
Adds first-class Amazon Bedrock as an LLM provider alongside the
existing generic API and local huggingface options. Works with the
unchanged streaming reader in the browser because the server-side
Bedrock code re-emits OpenAI-shaped JSON / SSE.
## What the user sees
New "Bedrock" tab in both the Admin Agent and the Onscreen Agent
settings dialogs (the dashboard's "Set LLM API key" pill and the
admin view open the same tab).
Bedrock tab contents:
- Server credentials panel — live-populated from a new
GET /api/bedrock/config; shows auth mode (AWS profile SigV4 vs.
Bedrock API key), profile name, region, and which server env vars
are driving it.
- Credential mode radio — "Use server credentials" (Node signs with
the configured AWS profile or server-side Bedrock key; works for
all models) or "Paste a Bedrock API key" (key forwarded as
Authorization: Bearer; works for the OpenAI-compatible route only,
since Anthropic Bedrock requires SigV4).
- Model dropdown with six presets: Claude Sonnet 4.6, Opus 4.7,
Opus 4.6, Haiku 4.5, OpenAI gpt-oss-20b, gpt-oss-120b.
- Endpoint auto-routed from model family — openai.* IDs hit
/api/bedrock/openai/v1/chat/completions, everything else hits
/api/bedrock/converse/v1/chat/completions. The user never types a
URL.
The existing API and Local tabs are untouched. Default provider is
still API. No existing user's settings are migrated or changed.
## Server — /api/bedrock/**
Three endpoints (all auth-gated via the same ensureAuthenticatedOrRespond
as /api/proxy):
- GET /api/bedrock/config
→ { mode: "sigv4"|"apikey", profile, region, hasApiKey }
- POST /api/bedrock/openai/v1/chat/completions
Signed pass-through to Bedrock's native OpenAI route. Honors a
client-side Authorization: Bearer header so the "Paste a Bedrock
API key" UI mode round-trips the key. Falls back to SigV4 with the
configured AWS profile otherwise.
- POST /api/bedrock/converse/v1/chat/completions
OpenAI-chat → Bedrock Converse adapter. Translates
messages/system/inferenceConfig, calls /converse or
/converse-stream, emits a single OpenAI chat.completion (non-stream)
or a stream of chat.completion.chunk frames + data: [DONE].
Server config is env-driven; no new config files:
| Env var | Purpose |
|------------------------------|-----------------------------------------------|
| SPACE_BEDROCK_MODE | "apikey" or "sigv4"; auto-detected if unset |
| SPACE_BEDROCK_REGION | Bedrock region; falls back to AWS_REGION |
| SPACE_BEDROCK_API_KEY | Long-term Bedrock API key (apikey mode) |
| SPACE_BEDROCK_AWS_PROFILE | AWS profile for SigV4; falls back to AWS_PROFILE |
Standard AWS_* variables (AWS_ACCESS_KEY_ID etc.) also work. Profiles
using credential_process are supported natively.
## Why the Converse adapter
Anthropic models on Bedrock do not respond to the OpenAI-compatible
/openai/v1/... route. They expose the Converse API at
/model/{id}/converse[-stream]. The adapter is what makes the browser
client talk to Claude on Bedrock without any client-side changes.
Notes:
- Claude 4.5+ models deprecate `temperature` and `top_p`; the adapter
strips both so requests don't 400 out. Older models still get them.
- Streaming path decodes Bedrock's binary eventstream on the server
(server/lib/bedrock/eventstream.js) and re-emits plain
text/event-stream chunks so the existing browser SSE reader works.
## New files
- server/lib/bedrock/sigv4.js (242 lines) — SigV4 signer with
env / shared-ini / credential_process credential resolution.
- server/lib/bedrock/proxy.js (164 lines) — request router.
- server/lib/bedrock/converse.js (371 lines) — OpenAI↔Converse
adapter.
- server/lib/bedrock/eventstream.js (115 lines) — AWS eventstream
binary decoder.
- app/L0/_all/mod/_core/llm_settings/panel.html (157 lines) — shared
settings component. Matches the existing
huggingface/config-sidebar.html pattern of a single component with
a mode="admin|onscreen" attribute, so the admin and onscreen
dialogs render identical UX from one file.
## Modified files
Admin + onscreen config/store/storage/panel.html:
- New BEDROCK enum value in ADMIN_CHAT_LLM_PROVIDER /
ONSCREEN_AGENT_LLM_PROVIDER.
- bedrockCredMode, bedrockModel, bedrockApiKey added to draft / saved
settings with safe defaults.
- New getters (isSettingsDraftUsingBedrockProvider,
isSettingsDraftUsingBedrockClientKey, bedrockServerConfig,
bedrockModelPresets).
- New actions (setSettingsBedrockCredMode, setSettingsBedrockModel,
loadBedrockServerConfig).
- saveSettingsFromDialog projects the Bedrock draft onto
apiEndpoint + apiKey + model at persist time so the existing
streamer code path in api.js works unchanged.
- panel.html: the inline provider block becomes a single
`<x-component path="/mod/_core/llm_settings/panel.html" mode="…">`.
server/router/router.js:
- Wires /api/bedrock and /api/bedrock/** into the auth-gated router
the same way /api/proxy is wired.
## file_read `allowMissing` — side fix (opt-in only)
First-run dashboard / agent loads trigger /api/file_read 404s for
~/conf/dashboard.yaml and ~/hist/{admin-chat,onscreen-agent}.json
that don't yet exist. The calling code already handles the missing
case; only DevTools noise remains.
This PR adds an opt-in `allowMissing: true` flag on the object form
of runtime.api.fileRead(…) / fileDelete(…). When set and the path is
missing, the server returns 200 { exists: false, content: "" } instead
of 404. When unset, behavior is identical to before.
Callers opted in:
- dashboard_welcome/dashboard-prefs.js loadDashboardPrefs
- admin/views/agent/storage.js loadAdminChatConfig + loadAdminChatHistory
- onscreen_agent/storage.js loadOnscreenAgentConfig + loadOnscreenAgentHistory
The queueFileRead() coalescing batcher strips unknown body fields when
it rebuilds the outgoing request; the PR bypasses the batcher when
allowMissing is set so the flag survives to the server.
This fix is orthogonal to the Bedrock work but shares the allowMissing
plumbing used by the new /api/bedrock/config call. Happy to split into
a separate PR if you prefer.
## Backward compatibility
- Provider defaults unchanged (API). New BEDROCK enum value has no
effect unless the user selects it.
- No existing config files are read, written, renamed, or migrated.
- No existing public API changed. createFileReadRequest /
createFileDeleteRequest are additive.
- Server boots and works without any SPACE_BEDROCK_* env vars set;
/api/bedrock/** is simply unavailable in that case.
- Zero new runtime dependencies. Pure Node built-ins (crypto,
child_process, stream, global fetch).
## Live test evidence
All calls against bedrock-runtime.us-west-2.amazonaws.com:
- POST /api/bedrock/openai/v1/chat/completions, model
openai.gpt-oss-20b-1:0 → 200, usage returned.
- POST /api/bedrock/converse/v1/chat/completions (non-stream), model
us.anthropic.claude-sonnet-4-6 → "pong", usage returned.
- POST /api/bedrock/converse/v1/chat/completions (non-stream), model
us.anthropic.claude-opus-4-6-v1 → "pong", usage returned.
- POST /api/bedrock/converse/v1/chat/completions (non-stream), model
us.anthropic.claude-opus-4-7 → "pong", usage returned.
- POST /api/bedrock/converse/v1/chat/completions (stream=true), model
us.anthropic.claude-sonnet-4-6 → 4 SSE chunks + data: [DONE],
finish_reason: stop.
- POST /api/bedrock/converse/v1/chat/completions, model
us.anthropic.claude-opus-4-7, temperature:0.2 → 200 (adapter strips
deprecated temperature for Claude 4.5+, previously 400'd).
- POST /api/file_read {path:"~/conf/dashboard.yaml", allowMissing:true}
→ 200 {exists:false, content:""} (previously 404).
- Server boot with no SPACE_BEDROCK_* env vars → works; /api/bedrock/config
still 200s but with no useful config; /api/bedrock/** calls 500 with
a readable error. Existing OpenRouter / API-tab path unaffected.
## Non-goals / known limitations
- Converse adapter only translates text parts. Tool calls, image
parts, and documents are not translated yet — happy to follow up.
- Bedrock's /openai/v1/ route only serves the OpenAI open-weights
models hosted on Bedrock (gpt-oss-20b, gpt-oss-120b). That is an
AWS-side limitation, not a client bug.
- The model preset list is hand-curated. A maintainer-preferred way
to resolve available models dynamically (e.g. list-inference-profiles)
would be a nice follow-up; the text field still works for any ID.
74c8a34 to
64a9644
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Amazon Bedrock as a first-class LLM provider in the settings UI, alongside the existing generic API and Local options. Works with Claude on Bedrock (via the Converse API) and the OpenAI open-weights models hosted on Bedrock (via the native
/openai/v1/route). The existing browser streaming reader is unchanged — the server re-emits OpenAI-shaped JSON / SSE for both routes.Zero new runtime dependencies (pure Node built-ins). No existing code path or default behavior changes.
UX changes
A new Bedrock tab appears in both settings dialogs (the admin view and the dashboard's "Set LLM API key" pill open the same tab).
Bedrock tab contents
GET /api/bedrock/config. Bulleted summary of the server's current auth mode (AWS profile SigV4 vs. Bedrock API key), profile name, region, and which env vars are driving it.Authorization: Bearer. Works for the OpenAI-compatible route only (Anthropic Bedrock requires SigV4).openai.*→/api/bedrock/openai/v1/chat/completions, everything else →/api/bedrock/converse/v1/chat/completions. The user never types a URL.The API and Local tabs are untouched. Default provider is still API. No existing user's settings are migrated.
House-style note: the new
app/L0/_all/mod/_core/llm_settings/panel.htmlfollows the existinghuggingface/config-sidebar.htmlpattern — a single<x-component>with amode="admin|onscreen"attribute — so the admin and onscreen dialogs render identical UX from one file.Server —
/api/bedrock/**Three endpoints, all auth-gated via the same
ensureAuthenticatedOrRespondas/api/proxy:/api/bedrock/config{ mode: "sigv4" | "apikey", profile, region, hasApiKey }/api/bedrock/openai/v1/chat/completionsAuthorization: Bearerheader so the Paste a Bedrock API key UI mode round-trips the key. Falls back to SigV4 with the configured AWS profile otherwise./api/bedrock/converse/v1/chat/completionsmessages/system/inferenceConfig, calls/converseor/converse-stream, emits a single OpenAIchat.completion(non-stream) or a stream ofchat.completion.chunkframes +data: [DONE].Env-driven config; no new config files.
SPACE_BEDROCK_MODEapikeyorsigv4; auto-detected if unsetSPACE_BEDROCK_REGIONAWS_REGIONSPACE_BEDROCK_API_KEYSPACE_BEDROCK_AWS_PROFILEAWS_PROFILEStandard
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENenv vars also work. AWS profiles usingcredential_processare supported natively.Why the Converse adapter
Anthropic models on Bedrock do not respond to the OpenAI-compatible
/openai/v1/route — they expose the Converse API at/model/{id}/converse[-stream]. The adapter is what makes the existing browser client talk to Claude on Bedrock with no client-side changes.Two behavioral notes baked into the adapter:
temperatureandtop_pon Converse; the adapter strips both so requests don't 400 out. Older Claude models still get them.application/vnd.amazon.eventstreamframing on the server (newserver/lib/bedrock/eventstream.js) and re-emits plaintext/event-streamchunks so the existing browser SSE reader works unchanged.Files changed (17 total, +1461 / −105)
New Bedrock server code (5 files, +892 lines)
server/lib/bedrock/sigv4.js(+242) — SigV4 signer with env / shared-ini /credential_processcredential resolution.server/lib/bedrock/proxy.js(+164) — request router for/api/bedrock/**.server/lib/bedrock/converse.js(+371) — OpenAI ↔ Converse adapter.server/lib/bedrock/eventstream.js(+115) — AWS eventstream binary decoder.server/router/router.js(+10) — wires the auth gate.New shared UI component (1 file, +157 lines)
app/L0/_all/mod/_core/llm_settings/panel.html(+157) — single settings component used by both dialogs viamode="…"attribute.Admin agent wiring (4 files)
admin/views/agent/config.js(+80) — enum, presets, route resolver.admin/views/agent/store.js(+98) — getters, actions, save-time projection.admin/views/agent/storage.js(+16/−5) —allowMissingopt-in.admin/views/agent/panel.html(+9/−29) — replaced inline block with<x-component>.Onscreen agent wiring (4 files)
onscreen_agent/config.js,store.js,storage.js,panel.html— parallel changes to the above.allowMissingplumbing (3 files)server/api/file_read.js(+49/−6) — honorsallowMissing; returns 200{exists:false}on missing.app/L0/_all/mod/_core/framework/js/api-client.js(+22) — threads flag through request builders and bypasses the queue batcher when set.dashboard_welcome/dashboard-prefs.js(+5/−1) — opts in.See the commit message for the per-file breakdown.
Side fix:
/api/file_read404 noise (opt-in)First-run dashboard / agent loads trigger
/api/file_read404s for~/conf/dashboard.yamland~/hist/{admin-chat,onscreen-agent}.jsonthat don't yet exist. The calling code already handles the missing case; only DevTools noise remains.This PR adds an opt-in
allowMissing: trueflag on the object form ofruntime.api.fileRead(…)andruntime.api.fileDelete(…). When set and the path is missing, the server returns200 { exists: false, content: "" }instead of 404. When unset, behavior is identical to before.Shares the plumbing with the new
GET /api/bedrock/config. Happy to split into a separate PR if you prefer — let me know.Backward compatibility
BEDROCKenum value has no effect unless the user selects it.createFileReadRequest/createFileDeleteRequestand the file-op callers are additive.SPACE_BEDROCK_*env vars set;/api/bedrock/**is simply unavailable in that case, and every other path behaves as before.crypto,child_process,stream, globalfetch).Live test evidence
All calls against
bedrock-runtime.us-west-2.amazonaws.com:/api/bedrock/openai/v1/chat/completionsopenai.gpt-oss-20b-1:0/api/bedrock/converse/v1/chat/completionsus.anthropic.claude-sonnet-4-6"pong"/api/bedrock/converse/v1/chat/completionsus.anthropic.claude-opus-4-6-v1"pong"/api/bedrock/converse/v1/chat/completionsus.anthropic.claude-opus-4-7"pong"/api/bedrock/converse/v1/chat/completionsus.anthropic.claude-sonnet-4-6data: [DONE],finish_reason: "stop"/api/bedrock/converse/v1/chat/completionsus.anthropic.claude-opus-4-7+temperature: 0.2/api/file_read{path:"~/conf/dashboard.yaml", allowMissing:true}{exists:false, content:""}(previously 404)SPACE_BEDROCK_*env vars setWhat I did not verify:
node space serve+ browser on macOS.Non-goals / known limitations
/openai/v1/route only serves OpenAI open-weights models on Bedrock (gpt-oss-20b,gpt-oss-120b). That's an AWS-side limitation, not a client bug — hence the route split.list-inference-profiles) would be a nice follow-up; any ID can still be typed.How to try it
Open
http://127.0.0.1:3000, open the agent settings, pick the Bedrock tab, choose a Claude model, save, chat.