@clawdbuilder/jig-bedrock 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -56
- package/package.json +1 -1
- package/src/provider.ts +6 -4
- package/src/sso-check.ts +7 -1
- package/src/statistics.ts +13 -17
- package/src/usage.ts +2 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# jig-bedrock: Claude on AWS Bedrock for Pi
|
|
2
2
|
|
|
3
|
-
A Pi provider for Claude on AWS Bedrock. It
|
|
4
|
-
Anthropic Messages route
|
|
3
|
+
A Pi provider for Claude on AWS Bedrock. It discovers the application inference profiles your account can use, sends
|
|
4
|
+
Claude through Bedrock's Anthropic Messages route where possible, and falls back to Converse per model.
|
|
5
5
|
|
|
6
6
|
## The gap it fills
|
|
7
7
|
|
|
8
|
-
Pi's built-in Bedrock provider uses Converse only, has a fixed model list and no profile discovery, SSO repair or
|
|
9
|
-
footer. This package adds the Messages route with SigV4, discovery by an AWS tag, repair through `aws sso login`,
|
|
10
|
-
optional spend line. It works in plain Pi; when [Jig](https://github.com/ClawdBuilder/jig) is installed it also
|
|
11
|
-
records and footer, but Jig is not required.
|
|
8
|
+
Pi's built-in Bedrock provider uses Converse only, has a fixed model list, and has no profile discovery, SSO repair or
|
|
9
|
+
budget footer. This package adds the Messages route with SigV4, discovery by an AWS tag, repair through `aws sso login`,
|
|
10
|
+
and an optional spend line. It works in plain Pi; when [Jig](https://github.com/ClawdBuilder/jig) is installed it also
|
|
11
|
+
uses Jig's records and footer, but Jig is not required.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -16,17 +16,24 @@ records and footer, but Jig is not required.
|
|
|
16
16
|
pi install npm:@clawdbuilder/jig-bedrock
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Requirements: Node 22.19 or later, Pi 1.5 or later, AWS CLI v2
|
|
20
|
-
Linux, WSL2 and macOS are supported; Windows is not.
|
|
19
|
+
Requirements: Node 22.19 or later, Pi 1.5 or later, and the AWS CLI v2 for discovery. You need an AWS profile the CLI can
|
|
20
|
+
use; **SSO is one option, not a requirement**. Linux, WSL2 and macOS are supported; Windows is not, for the reason below.
|
|
21
|
+
|
|
22
|
+
Run `/bedrock-doctor` after installing; it checks Node, the AWS CLI, `bash`, `jq` and the optional usage program without
|
|
23
|
+
calling AWS.
|
|
21
24
|
|
|
22
25
|
## Minimum settings
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
With Jig, add a `bedrock` section to `jig.json` (or use the environment):
|
|
25
28
|
|
|
26
29
|
```json
|
|
27
30
|
{ "bedrock": { "team": "my-team" } }
|
|
28
31
|
```
|
|
29
32
|
|
|
33
|
+
A plain Pi install has no `jig.json`: put the same keys in `~/.pi/agent/bedrock.json` (or in a `bedrock` object in that
|
|
34
|
+
file). With Jig, that file is read first and Jig's `bedrock` section is merged over it. A project's `jig.json` may set the
|
|
35
|
+
catalog keys, but `ssoLoginCommand`, `usageDir` and `browser` are read only from the user's file or the environment.
|
|
36
|
+
|
|
30
37
|
`team` is the value of the AWS tag on your inference profiles. The package discovers active profiles with that value.
|
|
31
38
|
|
|
32
39
|
| Setting | Default | Meaning |
|
|
@@ -44,15 +51,97 @@ Add a `bedrock` section to `jig.json` (or the same keys through the environment)
|
|
|
44
51
|
| `browser` | WSL's Windows browser | Program that opens the sign-in address |
|
|
45
52
|
| `api` | `messages` | `messages` (cache-friendly) or `converse` |
|
|
46
53
|
|
|
47
|
-
Plain Pi reads the same keys from `~/.pi/agent/bedrock.json` (or a `bedrock` object in it). With Jig, that file is read
|
|
48
|
-
first and Jig's `bedrock` section is merged on top; Jig must load before the provider, as in Jig's normal order. A project's `jig.json` may set
|
|
49
|
-
the catalog keys, but `ssoLoginCommand`, `usageDir` and `browser` are ignored there: programs run only from the user's file or the
|
|
50
|
-
environment.
|
|
51
|
-
|
|
52
54
|
Environment: `JIG_BEDROCK_TEAM`, `JIG_BEDROCK_PROFILE`, `JIG_BEDROCK_REGIONS`, `JIG_BEDROCK_USAGE_DIR`,
|
|
53
55
|
`JIG_BEDROCK_TAG_KEY`, `JIG_BEDROCK_TAG_VALUE`. `PI_HOST_DIR` points at the Pi install when it is not beside `node`.
|
|
54
56
|
`PI_CACHE_RETENTION` sets Pi's own cache retention; the provider does not change it.
|
|
55
57
|
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
`jig-bedrock` reads the `bedrock` settings once at Pi start. Discovery and requests happen later: on the first request,
|
|
61
|
+
when Pi starts an SSO check, or when you run a command.
|
|
62
|
+
|
|
63
|
+
### Discovery
|
|
64
|
+
|
|
65
|
+
Discovery runs the AWS CLI with the configured `profile` and `regions`:
|
|
66
|
+
|
|
67
|
+
- `aws sts get-caller-identity` learns the 12-digit account.
|
|
68
|
+
- `aws resourcegroupstaggingapi get-resources --resource-type-filters bedrock:application-inference-profile` asks for the
|
|
69
|
+
profiles in each region. `--tag-filters` keeps the profile's `Team` tag (or `tagKey`) and its `Active=true` tag (or
|
|
70
|
+
`activeTagKey`/`activeTagValue`).
|
|
71
|
+
- `aws bedrock get-inference-profile --inference-profile-identifier <arn>` reads each profile's name, status and model.
|
|
72
|
+
|
|
73
|
+
Only `ACTIVE` profiles are listed. Their foundation model is read from the profile's model ARN; the provider does not call
|
|
74
|
+
`GetFoundationModel`. A successful catalog is cached for **24 hours** in `~/.cache/jig-bedrock/catalog.json`, with private
|
|
75
|
+
permissions and an atomic rename. A changed account or settings discards it; `/bedrock-refresh` forces a new run.
|
|
76
|
+
|
|
77
|
+
### Routes
|
|
78
|
+
|
|
79
|
+
There are two routes to the same Claude models:
|
|
80
|
+
|
|
81
|
+
- **Messages.** Pi's Anthropic Messages client calls
|
|
82
|
+
`https://bedrock-runtime.<region>.amazonaws.com/anthropic/v1/messages`. `bedrock-fetch.ts` removes the client's API-key
|
|
83
|
+
headers and signs each request with SigV4 (`sigv4.ts`). The signature uses the credentials the AWS SDK resolves for the
|
|
84
|
+
configured profile.
|
|
85
|
+
- **Converse.** Pi's Bedrock provider uses the AWS SDK's `ConverseStream` operation at the bedrock-runtime endpoint.
|
|
86
|
+
|
|
87
|
+
A profile is put on Messages only when `api` is `messages` (the default) and its foundation model is an Anthropic model.
|
|
88
|
+
Every other model uses Converse. `api: "converse"` puts every model on Converse. Both routes stream. On Messages the tools array stays
|
|
89
|
+
stable, so the prompt cache survives a tool change; on Converse a changed tool list rewrites it. The footer and usage
|
|
90
|
+
figures are the same.
|
|
91
|
+
|
|
92
|
+
A child agent (a Pi session with `PI_SUBAGENT_ID`, or the older `PI_SUBAGENT_CHILD=1`) skips the catalog refresh, the
|
|
93
|
+
usage program and its events, and the sign-in at start. The parent does those; the child still sends requests and reads
|
|
94
|
+
the parent's cached catalog.
|
|
95
|
+
|
|
96
|
+
## Credentials
|
|
97
|
+
|
|
98
|
+
Requests use the AWS SDK's default credential chain, loaded from Pi's install (`pi-host.ts` loads
|
|
99
|
+
`@aws-sdk/credential-provider-node`). Discovery is different: it runs the AWS CLI with `--profile`, so the configured
|
|
100
|
+
profile must be usable by the CLI as well as by the SDK.
|
|
101
|
+
|
|
102
|
+
| What you have | Requests | Discovery | Sign-in repair |
|
|
103
|
+
| -------------------------------------------- | -------------- | -------------------------------------- | ---------------------------------------------------- |
|
|
104
|
+
| SSO / IAM Identity Center | yes, SDK chain | yes, `--profile` | yes: checked at start, one retry before any response |
|
|
105
|
+
| Access keys in a profile | yes | yes, `--profile` | no |
|
|
106
|
+
| Assume role in a profile | yes | yes, `--profile` | no |
|
|
107
|
+
| `credential_process` in a profile | yes | yes, `--profile` | no |
|
|
108
|
+
| Web identity or instance role | yes | only through a profile the CLI can use | no |
|
|
109
|
+
| Bedrock API key (`AWS_BEARER_TOKEN_BEDROCK`) | not supported | no | no |
|
|
110
|
+
|
|
111
|
+
Only the SSO path has been tested against real AWS. The other rows are supported by the SDK and CLI, but not by a real
|
|
112
|
+
run in this package.
|
|
113
|
+
|
|
114
|
+
For a Bedrock API key: `sso.ts`'s `profileFor` declines to choose an SSO profile when the bearer variable is set, so no
|
|
115
|
+
repair is attempted. With a configured profile, the request path clears that variable and refuses an explicit API key or
|
|
116
|
+
bearer token with `Unapproved Bedrock authentication scope`. The package does not fall back to API-key auth.
|
|
117
|
+
|
|
118
|
+
## AWS permissions
|
|
119
|
+
|
|
120
|
+
The profile used for discovery needs these actions:
|
|
121
|
+
|
|
122
|
+
- `sts:GetCallerIdentity` for the account id;
|
|
123
|
+
- `tag:GetResources` for the tagged inference profiles;
|
|
124
|
+
- `bedrock:GetInferenceProfile` for each profile.
|
|
125
|
+
|
|
126
|
+
The provider always streams: Messages uses Bedrock's Anthropic Messages route, and Converse uses the SDK's
|
|
127
|
+
`ConverseStream` operation. The request therefore needs:
|
|
128
|
+
|
|
129
|
+
- `bedrock:InvokeModelWithResponseStream` for both routes. The SDK's `ConverseStream` is authorized by this action, not by
|
|
130
|
+
an action named after Converse.
|
|
131
|
+
|
|
132
|
+
`bedrock:InvokeModel` is not needed: this provider has no non-streaming call. If a future call adds one, it needs that
|
|
133
|
+
action too.
|
|
134
|
+
|
|
135
|
+
For an application inference profile, the policy must allow the action on the profile's ARN **and** on every foundation
|
|
136
|
+
model ARN the profile routes to.
|
|
137
|
+
|
|
138
|
+
No `List` or `GetFoundationModel` permission is needed: the code gets the foundation model from the profile's ARN.
|
|
139
|
+
|
|
140
|
+
## Why not Windows
|
|
141
|
+
|
|
142
|
+
The SSO lock needs a POSIX owner and private `0700` directory (`ssoRuntime` in `src/login.ts` checks `process.getuid` and
|
|
143
|
+
mode bits), and the no-browser fallback is the WSL-only `bin/windows-browser` shell script. Native Windows has neither.
|
|
144
|
+
|
|
56
145
|
## Commands
|
|
57
146
|
|
|
58
147
|
| Command | What it does |
|
|
@@ -64,48 +153,17 @@ Environment: `JIG_BEDROCK_TEAM`, `JIG_BEDROCK_PROFILE`, `JIG_BEDROCK_REGIONS`, `
|
|
|
64
153
|
| `/aws-login` | Start the owning AWS CLI's `sso login` for the configured profile |
|
|
65
154
|
| `/bedrock-doctor` | Probe Node, AWS CLI, bash, jq and the optional usage program; it does not call AWS |
|
|
66
155
|
|
|
67
|
-
The footer
|
|
68
|
-
`
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```json
|
|
72
|
-
{
|
|
73
|
-
"schema_version": 1,
|
|
74
|
-
"command": "usage",
|
|
75
|
-
"user": "me",
|
|
76
|
-
"period": "Current Month",
|
|
77
|
-
"summary": {
|
|
78
|
-
"total_requests": 2,
|
|
79
|
-
"total_input_tokens_fresh": 100,
|
|
80
|
-
"total_cache_read_tokens": 20,
|
|
81
|
-
"total_cache_write_tokens": 10,
|
|
82
|
-
"total_input_tokens": 130,
|
|
83
|
-
"total_output_tokens": 40,
|
|
84
|
-
"total_estimated_cost_usd": 0.42
|
|
85
|
-
},
|
|
86
|
-
"profiles": [
|
|
87
|
-
{
|
|
88
|
-
"model_slug": "claude-sonnet-5-5",
|
|
89
|
-
"profile_name": "my-profile",
|
|
90
|
-
"requests": 2,
|
|
91
|
-
"input_tokens_fresh": 100,
|
|
92
|
-
"cache_read_tokens": 20,
|
|
93
|
-
"cache_write_tokens": 10,
|
|
94
|
-
"total_input_tokens": 130,
|
|
95
|
-
"output_tokens": 40,
|
|
96
|
-
"estimated_cost_usd": 0.42
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The 2.x shape may omit `schema_version`/`command` and each row's `user`; extra keys are ignored. A missing or unpriced row
|
|
103
|
-
leaves the spend unknown, not zero.
|
|
156
|
+
The footer and `/bedrock-usage` read an executable named `usage` in `usageDir`; it is called with `--daily`,
|
|
157
|
+
`--weekly` or `--monthly` and `--output json`, and prints the spend as JSON. It receives `AWS_PROFILE` and
|
|
158
|
+
`BEDROCK_REGIONS` in its environment.
|
|
104
159
|
|
|
105
|
-
|
|
160
|
+
If your usage CLI's own command is `usage-cli`, put this two-line wrapper at
|
|
161
|
+
`~/.local/share/usage-tool/usage` and make it executable:
|
|
106
162
|
|
|
107
|
-
|
|
108
|
-
|
|
163
|
+
```sh
|
|
164
|
+
#!/bin/sh
|
|
165
|
+
exec usage-cli usage "$@"
|
|
166
|
+
```
|
|
109
167
|
|
|
110
168
|
## Tests
|
|
111
169
|
|
|
@@ -117,8 +175,7 @@ npm run scan
|
|
|
117
175
|
|
|
118
176
|
WSL uses `bin/windows-browser` when `browser` is unset, so the sign-in opens on Windows.
|
|
119
177
|
|
|
120
|
-
|
|
121
|
-
CLI.
|
|
178
|
+
Real-AWS tests are manual and need the user's approval; unit tests use recorded shapes and a fake CLI.
|
|
122
179
|
|
|
123
180
|
## Licence
|
|
124
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawdbuilder/jig-bedrock",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Claude on AWS Bedrock for Pi: inference-profile discovery, the Messages route with cache-friendly tools, Converse fallback, SSO repair and a budget footer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/provider.ts
CHANGED
|
@@ -149,11 +149,13 @@ export function createJigBedrock(
|
|
|
149
149
|
);
|
|
150
150
|
}
|
|
151
151
|
if (!config) {
|
|
152
|
+
const file = path.join(agentFolder(options.env), "bedrock.json");
|
|
153
|
+
const where =
|
|
154
|
+
options.api === undefined
|
|
155
|
+
? `Add "team" to ${file}, then /reload.`
|
|
156
|
+
: `Set it in bedrock in jig.json or in ${file}, then /reload.`;
|
|
152
157
|
pi.on("session_start", (_event, ctx) =>
|
|
153
|
-
ctx.ui.notify(
|
|
154
|
-
`Bedrock provider disabled: ${resolved.error} Edit bedrock in jig.json, then /reload.`,
|
|
155
|
-
"warning",
|
|
156
|
-
),
|
|
158
|
+
ctx.ui.notify(`Bedrock provider disabled: ${resolved.error} ${where}`, "warning"),
|
|
157
159
|
);
|
|
158
160
|
return undefined;
|
|
159
161
|
}
|
package/src/sso-check.ts
CHANGED
|
@@ -100,12 +100,18 @@ export async function checkSso(
|
|
|
100
100
|
): Promise<SsoState> {
|
|
101
101
|
// The fast path: a cached token that stays valid needs no network check and no SDK.
|
|
102
102
|
if (cachedTokenFresh(profile, deps)) return "ok";
|
|
103
|
-
|
|
103
|
+
// `AbortSignal.timeout` uses an unreferenced timer. With a stalled SDK and nothing else in the event loop, the process
|
|
104
|
+
// would exit before the check could return an error. A referenced timer keeps the check alive for its own timeout.
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const limit = controller.signal;
|
|
107
|
+
const timer = setTimeout(() => controller.abort(), deps.timeoutMs);
|
|
104
108
|
try {
|
|
105
109
|
// The SDK's answer is dropped: only whether it resolved matters.
|
|
106
110
|
await until(deps.credentialProvider()({ profile })(), signal ? AbortSignal.any([signal, limit]) : limit);
|
|
107
111
|
return "ok";
|
|
108
112
|
} catch (error) {
|
|
109
113
|
return classify(error);
|
|
114
|
+
} finally {
|
|
115
|
+
clearTimeout(timer);
|
|
110
116
|
}
|
|
111
117
|
}
|
package/src/statistics.ts
CHANGED
|
@@ -250,24 +250,20 @@ export function createStatistics(
|
|
|
250
250
|
previous = load(period);
|
|
251
251
|
if (!options.force && previous && !previous.stale) return previous;
|
|
252
252
|
try {
|
|
253
|
-
const output = await runner(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
AWS_PAGER: "",
|
|
266
|
-
NO_COLOR: "1",
|
|
267
|
-
PAT_SKIP_UPDATE_CHECK: "1",
|
|
268
|
-
},
|
|
253
|
+
const output = await runner(usageExecutable(config), [`--${period}`, "--output", "json"], {
|
|
254
|
+
timeout: 120_000,
|
|
255
|
+
maxBuffer: 2_097_152,
|
|
256
|
+
signal: options.signal,
|
|
257
|
+
env: {
|
|
258
|
+
...process.env,
|
|
259
|
+
BEDROCK_REGIONS: config.regions.join(","),
|
|
260
|
+
AWS_PROFILE: config.profile,
|
|
261
|
+
AWS_CLI_AUTO_PROMPT: "off",
|
|
262
|
+
AWS_PAGER: "",
|
|
263
|
+
NO_COLOR: "1",
|
|
264
|
+
PAT_SKIP_UPDATE_CHECK: "1",
|
|
269
265
|
},
|
|
270
|
-
);
|
|
266
|
+
});
|
|
271
267
|
if (options.signal?.aborted) throw new UsageError("cancelled");
|
|
272
268
|
const observedAt = now();
|
|
273
269
|
// Legacy reports have no explicit range, so a UTC rollover cannot be safely relabelled.
|
package/src/usage.ts
CHANGED
|
@@ -113,8 +113,9 @@ export const runUsage: UsageRunner = (program, args, options) =>
|
|
|
113
113
|
);
|
|
114
114
|
child.on("close", (code) => finish(code === 0 ? undefined : new UsageError(classify(stderr))));
|
|
115
115
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
116
|
+
// A referenced timer keeps a stalled program's timeout alive when nothing else holds the event loop. It is
|
|
117
|
+
// cleared in finish(), so a program that answers does not delay exit.
|
|
116
118
|
timer = setTimeout(() => finish(new UsageError("timeout")), options.timeout);
|
|
117
|
-
timer.unref();
|
|
118
119
|
if (options.signal?.aborted) abort();
|
|
119
120
|
});
|
|
120
121
|
export const usageExecutable = (config: ResolvedConfig): string =>
|