@@ -11,6 +11,165 @@ target.
1111
1212## [ Unreleased]
1313
14+ ## [ 2.1.0] — 2026-09-30
15+
16+ Amazon Bedrock becomes a provider you can run from the console. It
17+ authenticates with a Bedrock API key as well as access keys or the
18+ instance role, lists its models for import and for the route editor,
19+ and has a working Test Connection. Requests converted for Bedrock now
20+ keep their prompt-cache breakpoints and Claude's thinking settings. The
21+ route editor takes any upstream model name, and a route the provider
22+ refused can be created anyway. ThinkWatch-Core moves to v0.55.0, whose
23+ Bedrock layer this edition now shares with the desktop gateway. No
24+ database, setting, environment variable or Helm value changes.
25+
26+ ### Read before upgrading
27+
28+ - ** Test Connection with a saved provider's secrets needs
29+ ` providers:update ` .** A test that names a saved provider
30+ (` provider_id ` , which is what the Edit dialog sends) used to take only
31+ ` providers:create ` . A custom role that has ` providers:create ` without
32+ ` providers:update ` can no longer test existing providers; the built-in
33+ admin role has both. A test with values typed into the request still
34+ takes ` providers:create ` . See * Security* below.
35+ - ** Refusing a route the provider does not serve answers
36+ ` model_not_served ` .** ` POST /api/admin/models/{model_id}/routes ` still
37+ answers ` 400 ` when the import probe found no API the provider serves
38+ the model on, but ` error.type ` is now ` model_not_served ` , where it was
39+ ` bad_request ` . Scripts that matched on ` bad_request ` for this case need
40+ the new value. The request takes a new optional ` "force": true ` to
41+ create the route anyway.
42+ - ** ` error_type ` in ` gateway_logs ` for failed requests is always the
43+ error's tag.** An upstream HTTP error used to log its debug text, such
44+ as ` ProviderHttpError { status: 502, message: "…" } ` , and an upstream
45+ rate limit a cut-off ` UpstreamRateLimited { retry_after_secs: Some ` .
46+ They now log ` ProviderHttpError ` and ` UpstreamRateLimited ` , as the
47+ metric labels and streamed requests already did. Dashboards or log
48+ forwarder queries that grouped by those strings see them merge into
49+ one value each.
50+ - ** The server log now carries the upstream's reply to a 401 or 403** ,
51+ as it already did for other upstream errors. For Bedrock that reply
52+ names the AWS account and the IAM principal. Callers still see only
53+ "Authentication failed with upstream", and failover, breakers and
54+ ` gateway_logs ` treat these errors as before.
55+ - ** Prompt caching now works on Bedrock routes, and is billed at cache
56+ prices.** Requests converted for Bedrock used to lose their
57+ ` cache_control ` breakpoints, so Claude on Bedrock re-read the whole
58+ prompt at full price every turn. Breakpoints now go out as Converse
59+ ` cachePoint ` blocks, to Claude and Nova models only (others reject
60+ them), and the cache reads and writes Bedrock reports come back into
61+ usage, one-hour writes included. Traffic with repeated prompts, such as
62+ Claude Code, costs much less on Bedrock routes than it did; writes cost
63+ a little more, at ` cache_write_weight ` / ` cache_write_1h_weight ` .
64+ - ** Claude's thinking reaches Bedrock.** A request converted for Claude
65+ on Bedrock now carries its thinking and effort settings, in the form
66+ Anthropic's API uses, along with the sampling limits thinking imposes
67+ (` temperature ` 1, no ` top_k ` , ` top_p ` at least 0.95). Thinking used to
68+ be dropped on the way to Bedrock. Other Bedrock models still get no
69+ thinking.
70+
71+ ### Added
72+
73+ - ** Bedrock providers can authenticate with a Bedrock API key.** Pick
74+ * Bedrock API Key* as the authentication mode and paste the key AWS
75+ generated. It is kept like any other provider's API key, as an
76+ encrypted ` Authorization: Bearer ` header, and replaced in the
77+ provider's Edit dialog. A Bedrock provider that sends an
78+ ` Authorization ` header is not SigV4-signed; one without it is signed
79+ as before, with access keys or the instance role. Use a long-term key:
80+ a short-term one expires within 12 hours.
81+ - ** Bedrock models can be imported, and are suggested in the route
82+ editor.** A Bedrock provider now lists what it can be routed to: the
83+ region's foundation models that can be invoked on demand and answer in
84+ text, and the inference profiles AWS defines, such as
85+ ` us.anthropic.claude-sonnet-4-5-20250929-v1:0 ` . A model that is only
86+ served through an inference profile, as most current models are, is
87+ listed under its profiles' ids and not its own. Whether the account
88+ may call a model is still checked one model at a time when it is
89+ imported. The provider's credential needs
90+ ` bedrock:ListFoundationModels ` and ` bedrock:ListInferenceProfiles ` ;
91+ the ` AmazonBedrockLimitedAccess ` policy a long-term API key is created
92+ with allows both.
93+ - ** Test Connection for Bedrock providers** , however they authenticate:
94+ with an API key, access keys or the instance role. It lists the models
95+ above, so a wrong credential or a missing permission shows up before
96+ any traffic does.
97+ - ** Create anyway, for a route the provider refused.** When the route
98+ editor's save is refused because the provider does not serve the
99+ model, the error now offers * Create anyway* : the refusal can be
100+ stale, or be about the probe's request rather than the model. The
101+ route is created, and its ` model_route.created ` audit row records the
102+ refusal it overrode in a new ` refusal_overridden ` key.
103+
104+ ### Changed
105+
106+ - ** The route editor's upstream model field takes any model name.** For
107+ a provider that lists its models, the field used to turn into a list
108+ to pick from, so a model the listing leaves out could not be routed to
109+ from the console. It now suggests the provider's models as you type,
110+ and takes whatever is typed.
111+ - ** Bedrock instance-role credentials are cached.** They took three
112+ IMDSv2 round trips per request; they are now kept until five minutes
113+ before they expire, and a burst of requests at expiry makes one trip.
114+ - ** Chat Completions requests can switch reasoning with ` thinking ` .**
115+ When a request is converted for another format, ` thinking.type `
116+ (` enabled ` / ` disabled ` , as DeepSeek, GLM and Kimi write it) now turns
117+ reasoning on or off; ` disabled ` wins over ` reasoning_effort ` .
118+ - ** Core crates at ThinkWatch-Core v0.55.0.** ` tw-dialect ` , ` tw-guard `
119+ and ` tw-breaker ` move from v0.43.0, and ` tw-bedrock ` joins them:
120+ SigV4 signing, eventstream unframing, Bedrock's addresses, region
121+ checks and model catalog now come from core. Where credentials come
122+ from (provider keys, the instance role) stays in this repository.
123+ ` tw-breaker ` is unchanged.
124+
125+ ### Fixed
126+
127+ - ** Bedrock providers could not be created or edited in the console.**
128+ The region was checked as a URL, so saving failed with
129+ ` 400 Invalid URL ` . A Bedrock provider's ` base_url ` is now checked as an
130+ AWS region such as ` us-east-1 ` , and anything else is refused, since the
131+ host is built from it. A provider saved with a URL there never reached
132+ Bedrock; set its region in the Edit dialog.
133+ - ** Bedrock models the account may not call were imported anyway.**
134+ Bedrock refuses such a model (model access not granted, or an IAM or
135+ organization policy that denies it) with a 403, and the import probe
136+ read every 403 as a credential problem that says nothing about the
137+ model. The route was created and failed on first use. Now, when the
138+ region's control plane accepts the same credential, the refusal is
139+ recorded as the model's, with AWS's reason, and the model is skipped
140+ on import like any other refused one. Once access is granted,
141+ re-check the provider's models.
142+ - ** A Bedrock provider whose stored secret key would not decrypt signed
143+ with an empty secret** , and every request failed with AWS's
144+ ` SignatureDoesNotMatch ` . It now refuses its requests with the reason
145+ until the keys are saved again, rather than falling back to the
146+ instance role, which would call AWS as a different identity. An
147+ access key ID saved without a secret is refused the same way.
148+ - ** A Bedrock model id that is an ARN could not be routed.** Its ` / `
149+ went into the request path unescaped, adding a path segment AWS could
150+ not route. It is now escaped.
151+ - ** A base URL ending in its version segment doubled it.** A provider
152+ written as ` https://api.openai.com/v1 ` sent requests, and the model
153+ listing, to ` /v1/v1/… ` and got 404s. A trailing version segment
154+ (` v1 ` , ` v1beta ` , …) that the request path starts with is now written
155+ once. Base URLs without one are unchanged.
156+ - ** Typing into a provider's API key field and clearing it again wiped
157+ the saved key on save.** The field sent ` Bearer ` with nothing after
158+ it. A cleared field now keeps the saved key, as a blank one always did.
159+
160+ ### Security
161+
162+ - ** Testing a connection with a saved provider's secrets takes
163+ ` providers:update ` .** The test fills each header left blank with the
164+ saved provider's secret and sends it to the URL in the request, and
165+ ` providers:create ` was enough to ask for it. So a user allowed only to
166+ create providers could send any saved API key to a server of their
167+ own. A test that names a saved provider (` provider_id ` ) now takes
168+ ` providers:update ` , which lets a user point that provider elsewhere
169+ anyway; a test without one still takes ` providers:create ` . A user with
170+ ` providers:update ` alone can now use Test Connection in the Edit
171+ dialog, which used to answer ` 403 ` .
172+
14173## [ 2.0.0] — 2026-09-24
15174
16175Callers now get errors in their own API's format, and an upstream that
@@ -557,7 +716,8 @@ unreleased builds should: stop the gateway, run `db/schema.sql`
557716against PostgreSQL, restart against this tag. The schema is
558717idempotent end-to-end, so the apply is safe to repeat.
559718
560- [ Unreleased ] : https://github.com/ThinkWatchProject/ThinkWatch/compare/v2.0.0...HEAD
719+ [ Unreleased ] : https://github.com/ThinkWatchProject/ThinkWatch/compare/v2.1.0...HEAD
720+ [ 2.1.0 ] : https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v2.1.0
561721[ 2.0.0 ] : https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v2.0.0
562722[ 1.1.0 ] : https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v1.1.0
563723[ 1.0.2 ] : https://github.com/ThinkWatchProject/ThinkWatch/releases/tag/v1.0.2
0 commit comments