Skip to content

Commit c5e1831

Browse files
sr status: show AWS Bedrock spend and rate limits (#67)
Append a compact Bedrock block (today/7d/30d/all-time spend + throttled(429) count) to the sr status output for a remote server. Extracts the bedrock-cost fetch from sr_spend.go into fetchBedrockSummary and reuses it; stays silent on servers with no Bedrock activity.
1 parent 4e8f733 commit c5e1831

3 files changed

Lines changed: 64 additions & 16 deletions

File tree

cmd/subrouter/sr_server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ func (r srRunner) serverStatus(ctx context.Context, store srServerStore, name st
523523
rows := usageRowsFromServerUsageStatuses(usage)
524524
fmt.Fprintf(r.out, "Server: %s (%s)\n", server.Name, server.URL)
525525
displayUsageRows(r.out, rows, false)
526+
r.printBedrockStatus(ctx, server)
526527
return nil
527528
}
528529
res, err := r.fetchServerAccountsResponse(ctx, server)

cmd/subrouter/sr_server_test.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,19 @@ func TestSRServerStatusSendsAdminToken(t *testing.T) {
9494
out: &out,
9595
errOut: &out,
9696
client: &http.Client{Transport: srRoundTripFunc(func(req *http.Request) (*http.Response, error) {
97-
if req.URL.Path != "/_subrouter/usage-status" {
98-
t.Fatalf("path = %s, want /_subrouter/usage-status", req.URL.Path)
99-
}
10097
if got := req.Header.Get("Authorization"); got != "Bearer secret-token" {
10198
t.Fatalf("Authorization = %q", got)
10299
}
100+
if req.URL.Path == "/_subrouter/bedrock-cost" {
101+
return &http.Response{
102+
StatusCode: http.StatusOK,
103+
Header: make(http.Header),
104+
Body: io.NopCloser(bytes.NewReader([]byte(`{"requests":0,"throttled":0}`))),
105+
}, nil
106+
}
107+
if req.URL.Path != "/_subrouter/usage-status" {
108+
t.Fatalf("path = %s, want /_subrouter/usage-status", req.URL.Path)
109+
}
103110
body, _ := json.Marshal([]remoteServerUsageStatus{{
104111
ID: "acct@example.com",
105112
Provider: accounts.ProviderCodex,
@@ -305,6 +312,15 @@ func TestSRDefaultOutputUsesDefaultRemoteServerStatus(t *testing.T) {
305312
out: &out,
306313
errOut: &out,
307314
client: &http.Client{Transport: srRoundTripFunc(func(req *http.Request) (*http.Response, error) {
315+
// status also queries bedrock-cost for the spend/rate-limit block;
316+
// return an empty summary so it stays silent here.
317+
if req.URL.Path == "/_subrouter/bedrock-cost" {
318+
return &http.Response{
319+
StatusCode: http.StatusOK,
320+
Header: make(http.Header),
321+
Body: io.NopCloser(bytes.NewReader([]byte(`{"requests":0,"throttled":0}`))),
322+
}, nil
323+
}
308324
if req.URL.Path != "/_subrouter/usage-status" {
309325
t.Fatalf("path = %s, want /_subrouter/usage-status", req.URL.Path)
310326
}

cmd/subrouter/sr_spend.go

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,12 @@ type bedrockCostSummaryView struct {
3131
ByModel map[string]bedrockModelAggView `json:"by_model"`
3232
}
3333

34-
// spend reports AWS Bedrock spend tracked by the default Subrouter server.
35-
func (r srRunner) spend(ctx context.Context) error {
36-
server, ok, err := r.defaultRemoteServer()
37-
if err != nil {
38-
return err
39-
}
40-
if !ok {
41-
return fmt.Errorf("sr spend needs a default Subrouter server; run '%s server use <name>'", r.programOrSubrouter())
42-
}
34+
// fetchBedrockSummary pulls the server's AWS Bedrock cost/throttle summary.
35+
func (r srRunner) fetchBedrockSummary(ctx context.Context, server srServerConfig) (bedrockCostSummaryView, error) {
36+
var summary bedrockCostSummaryView
4337
req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL+"/_subrouter/bedrock-cost", nil)
4438
if err != nil {
45-
return err
39+
return summary, err
4640
}
4741
addServerAdminAuth(req, server)
4842
client := r.client
@@ -51,14 +45,51 @@ func (r srRunner) spend(ctx context.Context) error {
5145
}
5246
res, err := client.Do(req)
5347
if err != nil {
54-
return err
48+
return summary, err
5549
}
5650
defer res.Body.Close()
5751
if res.StatusCode < 200 || res.StatusCode >= 300 {
58-
return fmt.Errorf("bedrock cost fetch failed: %s", res.Status)
52+
return summary, fmt.Errorf("bedrock cost fetch failed: %s", res.Status)
5953
}
60-
var summary bedrockCostSummaryView
6154
if err := json.NewDecoder(res.Body).Decode(&summary); err != nil {
55+
return summary, err
56+
}
57+
return summary, nil
58+
}
59+
60+
// printBedrockStatus appends a compact AWS Bedrock spend + rate-limit block to
61+
// `sr` status. It stays silent on servers with no Bedrock activity (or old
62+
// servers missing the endpoint) so it never clutters non-Bedrock status output.
63+
func (r srRunner) printBedrockStatus(ctx context.Context, server srServerConfig) {
64+
summary, err := r.fetchBedrockSummary(ctx, server)
65+
if err != nil {
66+
return
67+
}
68+
if summary.Requests == 0 && summary.Throttled == 0 {
69+
return
70+
}
71+
fmt.Fprintln(r.out)
72+
fmt.Fprintf(r.out, "AWS Bedrock spend today $%s · 7d $%s · 30d $%s · all-time $%s (%d req)\n",
73+
fmtUSD4(summary.TodayUSD), fmtUSD4(summary.Week7dUSD), fmtUSD4(summary.Month30dUSD),
74+
fmtUSD4(summary.TotalUSD), summary.Requests)
75+
limits := fmt.Sprintf("throttled(429) %d", summary.Throttled)
76+
if summary.Throttled > 0 && summary.LastThrottle != "" {
77+
limits += " · last " + summary.LastThrottle
78+
}
79+
fmt.Fprintf(r.out, "AWS Bedrock limits %s\n", limits)
80+
}
81+
82+
// spend reports AWS Bedrock spend tracked by the default Subrouter server.
83+
func (r srRunner) spend(ctx context.Context) error {
84+
server, ok, err := r.defaultRemoteServer()
85+
if err != nil {
86+
return err
87+
}
88+
if !ok {
89+
return fmt.Errorf("sr spend needs a default Subrouter server; run '%s server use <name>'", r.programOrSubrouter())
90+
}
91+
summary, err := r.fetchBedrockSummary(ctx, server)
92+
if err != nil {
6293
return err
6394
}
6495

0 commit comments

Comments
 (0)