// Client sends model calls to a Responses endpoint for one agent. The ChatGPT
// backend and the OpenAI API take the same request body and differ only in the
// URL and the credentials. Every call sends the fixed cache key of the agent,
-// so the turns and the compactions of the agent share one prompt cache. Every
-// call is recorded in the usage file.
+// so the turns and the compactions of the agent share one prompt cache. The
+// ChatGPT backend caches only with the key in the session_id header, so a call
+// to it sends the key there and in the body. Every call is recorded in the
+// usage file.
//
// The request never holds max_output_tokens or metadata, because the ChatGPT
// backend refuses them. It sets store to false, so the client sends the whole
}
// Request is one model call. Input holds the input items in order, such as
-// userMessage values or output items of an earlier response.
+// userMessage values or output items of an earlier response. Node names the
+// node that a compaction builds, or is nil. It goes only to the usage file,
+// never to the endpoint.
type Request struct {
Model string
Effort string
Instructions string
Tools []Tool
Input []any
+ Node *nodeKey
}
// Tool is the definition of a function tool.
return Response{}, fmt.Errorf("authorize model call: %w", err)
}
+ _, chatgpt := c.auth.(*chatgptAuth)
+ if chatgpt {
+ httpRequest.Header.Set("session_id", c.cacheKey)
+ }
+
+ start := time.Now()
record := usageRecord{
- Date: time.Now().Format(time.RFC3339Nano),
+ Date: start.Format(time.RFC3339Nano),
Model: request.Model,
}
+ if request.Node != nil {
+ record.Node = &[2]int{request.Node.level, request.Node.index}
+ }
+
response, err := c.send(httpRequest, &record)
if err != nil {
record.Error = err.Error()
}
+ record.Seconds = time.Since(start).Seconds()
record.InputTokens = response.Usage.Input
record.CachedTokens = response.Usage.Cached
record.OutputTokens = response.Usage.Output
+ record.TextBytes = len(response.Text)
return response, errors.Join(err, c.usage.Write(record))
}
path string
}
-// usageRecord is one line of usage.jsonl. Status is the HTTP status, or 0 when
-// no response came. Quota holds the x-codex-* headers of the response, without
-// the turn state.
+// usageRecord 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. Quota holds the x-codex-* headers of the response, without the
+// turn state.
type usageRecord struct {
Date string `json:"date"`
+ Seconds float64 `json:"seconds"`
Model string `json:"model"`
+ Node *[2]int `json:"node,omitempty"`
Status int `json:"status"`
InputTokens int `json:"input_tokens"`
CachedTokens int `json:"cached_tokens"`
OutputTokens int `json:"output_tokens"`
+ TextBytes int `json:"text_bytes"`
Quota map[string]string `json:"quota,omitempty"`
Error string `json:"error,omitempty"`
}