mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-06 15:50:49 +08:00
- Introduced `MetaExecutor` to support native Meta (Muse Code) API operations, including `Execute`, `ExecuteStream`, and `CountTokens`. - Added logic to handle Meta-specific `responses` endpoint, including request preparation, enriched authentication, header management, and response translation. - Implemented token counting functionality with custom `CountTokens` logic. - Updated `meta_executor` to process streamed and non-streamed responses, maintaining compatibility with the Codex schema. - Added utility functions to handle events, errors, and enriched metadata common to Meta API requests. - Extended `models.json` to include Meta's updated configuration with additional thinking levels like "minimal" and "max." - Introduced comprehensive tests to validate Meta-specific execution, streaming, header assignments, and response formatting.
142 lines
5.0 KiB
Go
142 lines
5.0 KiB
Go
package executor
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
|
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func (e *MetaExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (_ *cliproxyexecutor.StreamResult, err error) {
|
|
ctx = helps.EnsureSessionContext(ctx, opts, req.Payload)
|
|
if opts.Alt == "responses/compact" {
|
|
return nil, statusErr{code: http.StatusNotImplemented, msg: "/responses/compact not supported"}
|
|
}
|
|
|
|
enriched, errAuth := e.ensureAuth(ctx, auth)
|
|
if errAuth != nil {
|
|
return nil, errAuth
|
|
}
|
|
|
|
prepared, errPrepare := e.prepareResponsesRequest(ctx, req, opts, true)
|
|
if errPrepare != nil {
|
|
return nil, errPrepare
|
|
}
|
|
|
|
reporter := helps.NewExecutorUsageReporter(ctx, e, prepared.baseModel, enriched)
|
|
defer reporter.TrackFailure(ctx, &err)
|
|
reporter.SetTranslatedReasoningEffort(prepared.body, prepared.to.String())
|
|
|
|
baseURL, token := metaCreds(enriched)
|
|
if strings.TrimSpace(baseURL) == "" {
|
|
return nil, statusErr{code: http.StatusUnauthorized, msg: "meta executor: missing provider baseURL"}
|
|
}
|
|
|
|
url := strings.TrimSuffix(baseURL, "/") + "/responses"
|
|
httpReq, errRequest := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(prepared.body))
|
|
if errRequest != nil {
|
|
return nil, errRequest
|
|
}
|
|
applyMetaAPIHeaders(httpReq, enriched, token, true, opts.Headers)
|
|
e.recordMetaRequest(ctx, enriched, url, httpReq.Header.Clone(), prepared.body)
|
|
|
|
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, enriched, 0)
|
|
httpClient = reporter.TrackHTTPClientRoundTripOnly(httpClient)
|
|
httpResp, errDo := httpClient.Do(httpReq)
|
|
if errDo != nil {
|
|
helps.RecordAPIResponseError(ctx, e.cfg, errDo)
|
|
return nil, errDo
|
|
}
|
|
helps.RecordAPIResponseMetadata(ctx, e.cfg, httpResp.StatusCode, httpResp.Header.Clone())
|
|
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
|
|
data, errRead := io.ReadAll(httpResp.Body)
|
|
if errClose := httpResp.Body.Close(); errClose != nil {
|
|
log.Errorf("meta executor: close response body error: %v", errClose)
|
|
}
|
|
if errRead != nil {
|
|
helps.RecordAPIResponseError(ctx, e.cfg, errRead)
|
|
return nil, errRead
|
|
}
|
|
helps.AppendAPIResponseChunk(ctx, e.cfg, data)
|
|
helps.LogWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, helps.SummarizeErrorBody(httpResp.Header.Get("Content-Type"), data))
|
|
return nil, wrapMetaUpstreamError(httpResp.StatusCode, data)
|
|
}
|
|
|
|
out := make(chan cliproxyexecutor.StreamChunk)
|
|
go func() {
|
|
defer close(out)
|
|
defer func() {
|
|
if errClose := httpResp.Body.Close(); errClose != nil {
|
|
log.Errorf("meta executor: close response body error: %v", errClose)
|
|
}
|
|
}()
|
|
scanner := bufio.NewScanner(httpResp.Body)
|
|
scanner.Buffer(nil, 52_428_800)
|
|
claudeInputTokens := helps.NewClaudeInputTokenState(prepared.from, prepared.to, prepared.responseFormat, prepared.originalPayload)
|
|
var param any
|
|
outputItemsByIndex := make(map[int64][]byte)
|
|
var outputItemsFallback [][]byte
|
|
emitTranslatedLine := func(translatedLine []byte) bool {
|
|
chunks := helps.TranslateStreamWithClaudeInputTokens(ctx, prepared.to, prepared.responseFormat, req.Model, prepared.originalPayload, prepared.body, translatedLine, ¶m, claudeInputTokens)
|
|
for i := range chunks {
|
|
select {
|
|
case out <- cliproxyexecutor.StreamChunk{Payload: chunks[i]}:
|
|
case <-ctx.Done():
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
helps.AppendAPIResponseChunk(ctx, e.cfg, line)
|
|
if !bytes.HasPrefix(line, dataTag) {
|
|
if !emitTranslatedLine(bytes.Clone(line)) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
eventData := bytes.TrimSpace(line[len(dataTag):])
|
|
if errEvent := metaStreamEventError(eventData); errEvent != nil {
|
|
helps.RecordAPIResponseError(ctx, e.cfg, errEvent)
|
|
reporter.PublishFailure(ctx, errEvent)
|
|
select {
|
|
case out <- cliproxyexecutor.StreamChunk{Err: errEvent}:
|
|
case <-ctx.Done():
|
|
}
|
|
return
|
|
}
|
|
eventType := gjson.GetBytes(eventData, "type").String()
|
|
switch eventType {
|
|
case "response.output_item.done":
|
|
xaiCollectOutputItemDone(eventData, outputItemsByIndex, &outputItemsFallback)
|
|
case "response.completed", "response.incomplete":
|
|
if detail, ok := helps.ParseCodexUsage(eventData); ok {
|
|
reporter.Publish(ctx, detail)
|
|
}
|
|
eventData = patchCodexCompletedOutput(eventData, outputItemsByIndex, outputItemsFallback)
|
|
}
|
|
if !emitTranslatedLine(append([]byte("data: "), eventData...)) {
|
|
return
|
|
}
|
|
}
|
|
if errScan := scanner.Err(); errScan != nil {
|
|
helps.RecordAPIResponseError(ctx, e.cfg, errScan)
|
|
reporter.PublishFailure(ctx, errScan)
|
|
select {
|
|
case out <- cliproxyexecutor.StreamChunk{Err: errScan}:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
}()
|
|
return &cliproxyexecutor.StreamResult{Headers: httpResp.Header.Clone(), Chunks: out}, nil
|
|
}
|