X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/6f7eda727e519ae43d7d35195cbe412a2725abd7..d647f33b3e70f9419f6c0740e1d01b210439a519:/responses.go diff --git a/responses.go b/responses.go index cdc1558..fddec73 100644 --- a/responses.go +++ b/responses.go @@ -48,8 +48,10 @@ type apiKey string // 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 @@ -64,13 +66,16 @@ type Client struct { } // 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. @@ -217,19 +222,31 @@ func (c *Client) Create(ctx context.Context, request Request) (Response, error) 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)) } @@ -265,16 +282,21 @@ type usageLog struct { 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"` }