ThinkWatch Core 0.55.0
This release adds AWS Bedrock upstreams. Requests to them are converted to Converse and signed per request, and prompt-cache breakpoints and Claude's thinking now survive that conversion. Each request is priced by the model it was actually sent as, long-context prices follow the thresholds the price data gives, and an error an upstream reports partway through a stream now counts as a failure.
Upgrade notes
- The control-plane protocol version (
CONTROL_API_VERSION) is now 28. ThinkWatch Lite connects only to a core with the same protocol version, and has to regenerate its protocol types from this release to use it. ThinkWatch Lite 2026.9.23 includes 0.54.0 (protocol 27) and does not connect to 0.55.0: a server used with it stays on 0.54.0 until the app is updated to a release that includes 0.55.0.sudo twcore upgrade --version 0.54.0 --restartswitches a server back to 0.54.0. - The request store's schema is unchanged (22): the request history is kept.
- Changed in the protocol:
Protocolhasbedrock.ProviderInput.awsandProviderView.aws(AwsKeys): the access keys, or the name of an AWS profile, and the region to sign for.ProviderView.regionandProviderPreview.region: the region of a Bedrock upstream.AttemptView.model: the model an attempt sent when a routing rule rewrote it.
- New in the configuration: the protocol
bedrockandproviders[].aws. The 200K long-context threshold of a price sheet'sinput_above_200kandoutput_above_200know counts cache reads and writes (see Priced by the model that was sent). - New message codes:
config.credential.:bedrock_oauth,key_and_aws,bedrock_no_credential,aws_not_bedrock,aws_empty,aws_profile_and_keys,aws_empty_profile,aws_signed_header,aws_no_region,aws_bad_region,aws_region_mismatchconfig.aws_profile.:no_home,unreadable,not_found,unsupported,no_keysgw.upstream.:aws_token_expired,aws_profile_expired,bedrock_refused,bedrock_refused_unnamed,sign_failed,eventstream_broken,stream_exception,stream_errorgw.probe.aws_token_expired,gw.probe.bedrock_list_denied,gw.count_tokens.bedrock_model,gw.count_tokens.bedrock_upstream,control.replay_bedrock,l3.sign_failed
AWS Bedrock upstreams. An upstream at https://bedrock-runtime.<region>.amazonaws.com is recognized as bedrock. It authenticates in one of three ways:
- A Bedrock API key in
key, sent asAuthorization: Bearer. - AWS access keys in
aws:access_key_id,secret_access_keyand, for temporary credentials,session_token, each of which can be${VAR}. Every request is signed with them (SigV4) once its body is final. aws.profile: the access keys of a profile in the AWS credential files (~/.aws/credentials,~/.aws/config, or the filesAWS_SHARED_CREDENTIALS_FILEandAWS_CONFIG_FILEname) on the machine core runs on. The files are read again when they change, so a tool that refreshes temporary keys there needs no restart. Nothing is run to obtain a credential: a profile that signs in through IAM Identity Center, runs acredential_processor assumes a role is refused, naming the setting.
Requests in every client format are converted to Converse and ConverseStream. The binary eventstream is turned into server-sent events as it arrives, so usage, the first token, output redaction and tool-call inspection work as they do for any other upstream. The anthropic-beta values Bedrock accepts for Claude are carried into the request body. The model list comes from the region's control plane: the foundation models that can be invoked on demand, the inference profiles AWS defines (us.anthropic.claude-…, global.…), and the account's application inference profiles, listed by the ARN they are invoked by. Checking the connection and the inference speed test go through the same signing. For a VPC endpoint or a proxy, write its address in base_url and the region in aws.region.
- AWS's text for a refused credential (HTTP 401 or 403) names the account and the IAM identity. The client receives the gateway's own sentence instead, and AWS's text is not recorded as the request's error.
- An expired session token is named as such; for a profile, the message says to refresh the profile.
- An exception AWS sends inside a stream ends the request as a failure.
count_tokensfor a model that only Bedrock upstreams serve is answered with 501not_supported, the answer Claude Code's guidance for gateways names; Claude Code then counts with a one-token request. Bedrock's own CountTokens covers only some older Claude models onbedrock-runtime.- A replay to a Bedrock upstream is refused: a replay sends the recorded request as it was, and Bedrock takes only the Converse format, which no client sends.
The code that speaks to Bedrock (signing, the eventstream, the endpoints and the model catalog) is the new layer-one crate tw-bedrock, which ThinkWatch Enterprise shares.
Converse keeps cache breakpoints and thinking. The conversion to Converse used to drop cache_control, so a client that cached its prompt never hit the cache through Bedrock. Cache breakpoints now become cachePoint blocks (at most four, as Bedrock allows), with the one-hour TTL where the client asked for it, and cache reads and writes, one-hour writes included, are read back. Claude's thinking and effort are sent in additionalModelRequestFields, and its reasoning comes back as reasoning content.
Priced by the model that was sent. A routing rule can send a request as another model, and the upstream charges for the model it received. Each attempt now records the model it sent when a rule rewrote it (AttemptView.model), and a request is priced by the model of the attempt that served it. The row still names the model the client asked for. The list of unpriced models names the model a price has to be set for.
- A Bedrock model the price data does not list borrows a price, always marked estimated: an inference profile from the model it is named after, and an Anthropic model on Bedrock from Anthropic's own name for it.
- Long-context tiers are read with the threshold the price data gives (200K for Claude Sonnet 4 and 4.5, 272K for GPT-5.4 and later), including their cache prices. A request's input counts its cache reads and writes when the tier is chosen. The 272K tier was not recognized before, and a request with a warm cache was priced as a short one.
- A price the data does not give is filled in, and the cost is marked estimated whenever it is used: a one-hour cache write costs twice the input price (it used to fall back to the five-minute price), and the missing cache prices of a long-context tier grow with its input price.
Errors partway through a stream. An upstream can answer 200, stream part of an answer and then report an error in the stream: Anthropic's overloaded_error, Responses' response.failed, an error in a Chat or Gemini frame. Such a request was recorded as finished; it is now a failure, with the usage the upstream reported before the error.
Listed models. GET /v1/models and GET /v1/models/:model no longer stamp each model with the time of the request. created and created_at are now the Unix epoch, since the gateway does not know when a model was released.
Downloads
| Platform | Binary | Archive for server installation |
|---|---|---|
| Linux, x86_64 | twcore-x86_64-unknown-linux-gnu |
twcore-x86_64-unknown-linux-gnu.tar.gz |
| Linux, aarch64 | twcore-aarch64-unknown-linux-gnu |
twcore-aarch64-unknown-linux-gnu.tar.gz |
| macOS, Apple silicon | twcore-aarch64-apple-darwin |
— |
| Windows, x64 | twcore-x86_64-pc-windows-msvc.exe |
— |
| Windows, ARM64 | twcore-aarch64-pc-windows-msvc.exe |
— |
Each file is published with a .sha256 file beside it. A Linux archive contains twcore, the systemd unit twcore.service and LICENSE. ThinkWatch Lite includes its own copy of twcore; the files here are for running core separately, such as on a server.
Server installation
On Linux (x86_64 or aarch64), the install script sets up twcore as a systemd service. This installs 0.55.0:
curl -fsSL https://raw.githubusercontent.com/ThinkWatchProject/ThinkWatch-Core/main/scripts/install.sh | sudo sh -s -- --version 0.55.0An installation made with the script switches to 0.55.0 with:
sudo twcore upgrade --version 0.55.0 --restartConfiguration, the remote control port and connecting ThinkWatch Lite are described in docs/server.md.
Verifying a download
A .sha256 file holds the SHA-256 of the file followed by its name. With both files in the current directory, on Linux:
sha256sum -c twcore-x86_64-unknown-linux-gnu.tar.gz.sha256On macOS:
shasum -a 256 -c twcore-aarch64-apple-darwin.sha256On Windows, in PowerShell, the following prints True when the binary matches:
(Get-FileHash .\twcore-x86_64-pc-windows-msvc.exe).Hash -eq (Get-Content .\twcore-x86_64-pc-windows-msvc.exe.sha256).Split()[0]The install script and twcore upgrade check the SHA-256 themselves.
What's Changed
- fix(gateway): stop stamping listed models with the request time by @fylorn in #222
- feat(bedrock): move Bedrock's wire code into a shared layer-one crate by @fylorn in #223
- feat(dialect): carry prompt-cache breakpoints and Claude's thinking into Converse by @fylorn in #224
- test(gateway): reload only once the in-flight request has reached its upstream by @fylorn in #225
- feat(gateway): AWS Bedrock upstreams by @fylorn in #226
- feat(pricing): price what was sent, borrow Bedrock prices, count cache toward long context by @fylorn in #227
- fix(gateway): answer count_tokens for a Bedrock-only model with 501 not_supported by @fylorn in #229
- fix(gateway): record an error the upstream reports partway through a stream as a failure by @fylorn in #228
- feat(config): read a Bedrock upstream's keys from an AWS profile by @fylorn in #230
- fix(gateway): say a refusal without an exception name in its own sentence by @fylorn in #231
- chore: v0.55.0 by @fylorn in #232
Full Changelog: v0.54.0...v0.55.0