From 7594b9938b7343717e7a57aa7aede5d40d547483 Mon Sep 17 00:00:00 2001 From: "Anderson A. Giacomolli" Date: Sun, 8 Nov 2026 09:50:31 -0300 Subject: [PATCH] Record the subagent name in the usage records A model request names the subagent that makes it, and the usage record of the call holds that name in the agent field. A call of the agent itself has no such field. The name goes only to the usage file, never to the endpoint, so the usage records show the cost of each subagent in the shared usage file of the agent. Issue: b1ac698 --- model.go | 13 +++++++++---- model_test.go | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/model.go b/model.go index 04b1556..f15be22 100644 --- a/model.go +++ b/model.go @@ -218,6 +218,7 @@ func (c *ModelClient) Create(ctx context.Context, request ModelRequest) (ModelRe } record.Turn = request.Turn + record.Agent = request.Agent response, err := c.send(httpRequest, &record) if err != nil { record.Error = err.Error() @@ -302,8 +303,9 @@ type ModelOutputItem struct { // ModelRequest is one model call. Input holds the input items in order, such as // modelUserMessage values or output items of an earlier response. Node names // the node that a compaction builds, or is nil. Turn names the request of a -// turn: first or next. Node and Turn go only to the usage file, never to the -// endpoint. +// turn: first or next. Agent names the subagent that makes the call, or is +// empty for a call of the agent itself. Node, Turn, and Agent go only to the +// usage file, never to the endpoint. type ModelRequest struct { Model string Effort string @@ -312,6 +314,7 @@ type ModelRequest struct { Input []any Node *NodeKey Turn string + Agent string } // ModelResponse is the result of a model call. Output holds the output items as @@ -342,14 +345,16 @@ type ModelReasoning struct { // ModelUsageRecord is one line of usage.jsonl. Date is the start of the call, // and Seconds its length. Node is the node [l, i] of a compaction. Status is // the HTTP status, or 0 when no response came. TextBytes is the size of the -// text of the reply. Turn names the request of a turn. Quota holds the -// x-codex-* headers of the response, without the turn state. +// text of the reply. Turn names the request of a turn. Agent names the subagent +// of the call. Quota holds the x-codex-* headers of the response, without the +// turn state. type ModelUsageRecord struct { Date string `json:"date"` Seconds float64 `json:"seconds"` Model string `json:"model"` Node *[2]int `json:"node,omitempty"` Turn string `json:"turn,omitempty"` + Agent string `json:"agent,omitempty"` Status int `json:"status"` InputTokens int `json:"input_tokens"` CachedTokens int `json:"cached_tokens"` diff --git a/model_test.go b/model_test.go index 84b767e..16946a0 100644 --- a/model_test.go +++ b/model_test.go @@ -145,10 +145,12 @@ func TestClientRecordsEachCall(t *testing.T) { client := newTestModelClient(t, server) request := modelTestRequest() request.Node = &NodeKey{2, 5} + request.Agent = "GreenOtter" _, err := client.Create(t.Context(), request) require.NoError(t, err) require.NotContains(t, server.Call(0).body, "node") require.NotContains(t, string(server.Call(0).raw), "[2,5]") + require.NotContains(t, string(server.Call(0).raw), "GreenOtter") _, err = client.Create(t.Context(), modelTestRequest()) require.ErrorContains(t, err, "HTTP 429") @@ -168,12 +170,14 @@ func TestClientRecordsEachCall(t *testing.T) { require.Equal(t, 7, records[0].OutputTokens) require.Equal(t, quota, records[0].Quota) require.Equal(t, &[2]int{2, 5}, records[0].Node) + require.Equal(t, "GreenOtter", records[0].Agent) require.Equal(t, len("ok"), records[0].TextBytes) require.Greater(t, records[0].Seconds, 0.0) require.Empty(t, records[0].Error) require.NotEmpty(t, records[0].Date) require.Nil(t, records[1].Node) + require.Empty(t, records[1].Agent) require.Zero(t, records[1].TextBytes) require.Equal(t, http.StatusTooManyRequests, records[1].Status) -- 2.53.0