History

feat(usage): implement usage tracking infrastructure across executors

- Added `LoggerPlugin` to log usage metrics for observability. - Introduced a new `Manager` to handle usage record queuing and plugin registration. - Integrated new usage reporter and detailed metrics parsing into executors, covering providers like OpenAI, Codex, Claude, and Gemini. - Improved token usage breakdown across streaming and non-streaming responses.

Luis Pater committed 1 year ago

Commit 3ade03f1 parent 5090d9813 files · +706 −133Browse files

Showing the changes to internal/runtime/executor/codex_executor.go (1 of 13 files). Show all

Unified · Split

internal/runtime/executor/codex_executor.go+38−3
@@ -18,12 +18,15 @@ import ( · 17 unmodified lines
18 cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"18 cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
19 sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"19 sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
20 log "github.com/sirupsen/logrus"20 log "github.com/sirupsen/logrus"
21 "github.com/tidwall/gjson"
21 "github.com/tidwall/sjson"22 "github.com/tidwall/sjson"
22 23
23 "github.com/gin-gonic/gin"24 "github.com/gin-gonic/gin"
24 "github.com/google/uuid"25 "github.com/google/uuid"
25)26)
26 27
28var dataTag = []byte("data:")
29
27// CodexExecutor is a stateless executor for Codex (OpenAI Responses API entrypoint).30// CodexExecutor is a stateless executor for Codex (OpenAI Responses API entrypoint).
28// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.31// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.
29type CodexExecutor struct {32type CodexExecutor struct {
@@ -44,6 +47,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re · 14 unmodified lines
44 if baseURL == "" {47 if baseURL == "" {
45 baseURL = "https://chatgpt.com/backend-api/codex"48 baseURL = "https://chatgpt.com/backend-api/codex"
46 }49 }
50 reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
47 51
48 from := opts.SourceFormat52 from := opts.SourceFormat
49 to := sdktranslator.FromString("codex")53 to := sdktranslator.FromString("codex")
@@ -75,6 +79,8 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re · 25 unmodified lines
75 }79 }
76 }80 }
77 81
82 body, _ = sjson.SetBytes(body, "stream", true)
83
78 url := strings.TrimSuffix(baseURL, "/") + "/responses"84 url := strings.TrimSuffix(baseURL, "/") + "/responses"
79 recordAPIRequest(ctx, e.cfg, body)85 recordAPIRequest(ctx, e.cfg, body)
80 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))86 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
@@ -103,9 +109,27 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re · 22 unmodified lines
103 return cliproxyexecutor.Response{}, err109 return cliproxyexecutor.Response{}, err
104 }110 }
105 appendAPIResponseChunk(ctx, e.cfg, data)111 appendAPIResponseChunk(ctx, e.cfg, data)
106 var param any112
107 out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, &param)113 lines := bytes.Split(data, []byte("\n"))
108 return cliproxyexecutor.Response{Payload: []byte(out)}, nil114 for _, line := range lines {
115 if !bytes.HasPrefix(line, dataTag) {
116 continue
117 }
118
119 line = bytes.TrimSpace(line[5:])
120 if gjson.GetBytes(line, "type").String() != "response.completed" {
121 continue
122 }
123
124 if detail, ok := parseCodexUsage(line); ok {
125 reporter.publish(ctx, detail)
126 }
127
128 var param any
129 out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, line, &param)
130 return cliproxyexecutor.Response{Payload: []byte(out)}, nil
131 }
132 return cliproxyexecutor.Response{}, statusErr{code: 408, msg: "stream error: stream disconnected before completion: stream closed before response.completed"}
109}133}
110 134
111func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (<-chan cliproxyexecutor.StreamChunk, error) {135func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (<-chan cliproxyexecutor.StreamChunk, error) {
@@ -116,6 +140,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au · 4 unmodified lines
116 if baseURL == "" {140 if baseURL == "" {
117 baseURL = "https://chatgpt.com/backend-api/codex"141 baseURL = "https://chatgpt.com/backend-api/codex"
118 }142 }
143 reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
119 144
120 from := opts.SourceFormat145 from := opts.SourceFormat
121 to := sdktranslator.FromString("codex")146 to := sdktranslator.FromString("codex")
@@ -181,6 +206,16 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au · 59 unmodified lines
181 for scanner.Scan() {206 for scanner.Scan() {
182 line := scanner.Bytes()207 line := scanner.Bytes()
183 appendAPIResponseChunk(ctx, e.cfg, line)208 appendAPIResponseChunk(ctx, e.cfg, line)
209
210 if bytes.HasPrefix(line, dataTag) {
211 data := bytes.TrimSpace(line[5:])
212 if gjson.GetBytes(data, "type").String() == "response.completed" {
213 if detail, ok := parseCodexUsage(data); ok {
214 reporter.publish(ctx, detail)
215 }
216 }
217 }
218
184 chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), &param)219 chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), &param)
185 for i := range chunks {220 for i := range chunks {
186 out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}221 out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t