]> git.giacomolli.uk - onechat/commitdiff
Record the subagent name in the usage records
authorAnderson A. Giacomolli <agiacomolli@gmail.com>
Sun, 8 Nov 2026 12:50:31 +0000 (09:50 -0300)
committerAnderson A. Giacomolli <agiacomolli@gmail.com>
Sun, 8 Nov 2026 12:50:31 +0000 (09:50 -0300)
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
model_test.go

index 04b15563304af5720269f7a72272a41382952c95..f15be221e2d494e977595d3437d274029eb85552 100644 (file)
--- 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.Turn = request.Turn
+       record.Agent = request.Agent
        response, err := c.send(httpRequest, &record)
        if err != nil {
                record.Error = err.Error()
        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
 // 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
 type ModelRequest struct {
        Model        string
        Effort       string
@@ -312,6 +314,7 @@ type ModelRequest struct {
        Input        []any
        Node         *NodeKey
        Turn         string
        Input        []any
        Node         *NodeKey
        Turn         string
+       Agent        string
 }
 
 // ModelResponse is the result of a model call. Output holds the output items as
 }
 
 // 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
 // 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"`
 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"`
        Status       int               `json:"status"`
        InputTokens  int               `json:"input_tokens"`
        CachedTokens int               `json:"cached_tokens"`
index 84b767eb451ded852d25f6468c026adfb615c587..16946a061dfe25447e34bded0128e079732d0523 100644 (file)
@@ -145,10 +145,12 @@ func TestClientRecordsEachCall(t *testing.T) {
        client := newTestModelClient(t, server)
        request := modelTestRequest()
        request.Node = &NodeKey{2, 5}
        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]")
        _, 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")
 
        _, 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, 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.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)
        require.Zero(t, records[1].TextBytes)
 
        require.Equal(t, http.StatusTooManyRequests, records[1].Status)