mirror of
https://github.com/githubhjs/CLIProxyAPIPlus.git
synced 2026-07-12 01:25:13 +00:00
Merge branch 'router-for-me:main' into master
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
name: pr-test-build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
go build -o test-output ./cmd/server
|
||||||
|
rm -f test-output
|
||||||
@@ -832,6 +832,17 @@ func GetGitHubCopilotModels() []*ModelInfo {
|
|||||||
ContextLength: 128000,
|
ContextLength: 128000,
|
||||||
MaxCompletionTokens: 16384,
|
MaxCompletionTokens: 16384,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "gpt-5.2",
|
||||||
|
Object: "model",
|
||||||
|
Created: now,
|
||||||
|
OwnedBy: "github-copilot",
|
||||||
|
Type: "github-copilot",
|
||||||
|
DisplayName: "GPT-5.2",
|
||||||
|
Description: "OpenAI GPT-5.2 via GitHub Copilot",
|
||||||
|
ContextLength: 200000,
|
||||||
|
MaxCompletionTokens: 32768,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "claude-haiku-4.5",
|
ID: "claude-haiku-4.5",
|
||||||
Object: "model",
|
Object: "model",
|
||||||
|
|||||||
@@ -33,15 +33,16 @@ import (
|
|||||||
const (
|
const (
|
||||||
antigravityBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
antigravityBaseURLDaily = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
||||||
// antigravityBaseURLAutopush = "https://autopush-cloudcode-pa.sandbox.googleapis.com"
|
// antigravityBaseURLAutopush = "https://autopush-cloudcode-pa.sandbox.googleapis.com"
|
||||||
antigravityBaseURLProd = "https://cloudcode-pa.googleapis.com"
|
antigravityBaseURLProd = "https://cloudcode-pa.googleapis.com"
|
||||||
antigravityStreamPath = "/v1internal:streamGenerateContent"
|
antigravityCountTokensPath = "/v1internal:countTokens"
|
||||||
antigravityGeneratePath = "/v1internal:generateContent"
|
antigravityStreamPath = "/v1internal:streamGenerateContent"
|
||||||
antigravityModelsPath = "/v1internal:fetchAvailableModels"
|
antigravityGeneratePath = "/v1internal:generateContent"
|
||||||
antigravityClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
|
antigravityModelsPath = "/v1internal:fetchAvailableModels"
|
||||||
antigravityClientSecret = "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"
|
antigravityClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
|
||||||
defaultAntigravityAgent = "antigravity/1.11.5 windows/amd64"
|
antigravityClientSecret = "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"
|
||||||
antigravityAuthType = "antigravity"
|
defaultAntigravityAgent = "antigravity/1.11.5 windows/amd64"
|
||||||
refreshSkew = 3000 * time.Second
|
antigravityAuthType = "antigravity"
|
||||||
|
refreshSkew = 3000 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -650,9 +651,131 @@ func (e *AntigravityExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountTokens counts tokens for the given request (not supported for Antigravity).
|
// CountTokens counts tokens for the given request using the Antigravity API.
|
||||||
func (e *AntigravityExecutor) CountTokens(context.Context, *cliproxyauth.Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func (e *AntigravityExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: http.StatusNotImplemented, msg: "count tokens not supported"}
|
token, updatedAuth, errToken := e.ensureAccessToken(ctx, auth)
|
||||||
|
if errToken != nil {
|
||||||
|
return cliproxyexecutor.Response{}, errToken
|
||||||
|
}
|
||||||
|
if updatedAuth != nil {
|
||||||
|
auth = updatedAuth
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(token) == "" {
|
||||||
|
return cliproxyexecutor.Response{}, statusErr{code: http.StatusUnauthorized, msg: "missing access token"}
|
||||||
|
}
|
||||||
|
|
||||||
|
from := opts.SourceFormat
|
||||||
|
to := sdktranslator.FromString("antigravity")
|
||||||
|
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
||||||
|
|
||||||
|
baseURLs := antigravityBaseURLFallbackOrder(auth)
|
||||||
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
|
|
||||||
|
var authID, authLabel, authType, authValue string
|
||||||
|
if auth != nil {
|
||||||
|
authID = auth.ID
|
||||||
|
authLabel = auth.Label
|
||||||
|
authType, authValue = auth.AccountInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastStatus int
|
||||||
|
var lastBody []byte
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
for idx, baseURL := range baseURLs {
|
||||||
|
payload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||||
|
payload = applyThinkingMetadataCLI(payload, req.Metadata, req.Model)
|
||||||
|
payload = util.ApplyDefaultThinkingIfNeededCLI(req.Model, payload)
|
||||||
|
payload = normalizeAntigravityThinking(req.Model, payload)
|
||||||
|
payload = deleteJSONField(payload, "project")
|
||||||
|
payload = deleteJSONField(payload, "model")
|
||||||
|
payload = deleteJSONField(payload, "request.safetySettings")
|
||||||
|
|
||||||
|
base := strings.TrimSuffix(baseURL, "/")
|
||||||
|
if base == "" {
|
||||||
|
base = buildBaseURL(auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestURL strings.Builder
|
||||||
|
requestURL.WriteString(base)
|
||||||
|
requestURL.WriteString(antigravityCountTokensPath)
|
||||||
|
if opts.Alt != "" {
|
||||||
|
requestURL.WriteString("?$alt=")
|
||||||
|
requestURL.WriteString(url.QueryEscape(opts.Alt))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodPost, requestURL.String(), bytes.NewReader(payload))
|
||||||
|
if errReq != nil {
|
||||||
|
return cliproxyexecutor.Response{}, errReq
|
||||||
|
}
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
httpReq.Header.Set("User-Agent", resolveUserAgent(auth))
|
||||||
|
httpReq.Header.Set("Accept", "application/json")
|
||||||
|
if host := resolveHost(base); host != "" {
|
||||||
|
httpReq.Host = host
|
||||||
|
}
|
||||||
|
|
||||||
|
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
||||||
|
URL: requestURL.String(),
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Headers: httpReq.Header.Clone(),
|
||||||
|
Body: payload,
|
||||||
|
Provider: e.Identifier(),
|
||||||
|
AuthID: authID,
|
||||||
|
AuthLabel: authLabel,
|
||||||
|
AuthType: authType,
|
||||||
|
AuthValue: authValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
httpResp, errDo := httpClient.Do(httpReq)
|
||||||
|
if errDo != nil {
|
||||||
|
recordAPIResponseError(ctx, e.cfg, errDo)
|
||||||
|
lastStatus = 0
|
||||||
|
lastBody = nil
|
||||||
|
lastErr = errDo
|
||||||
|
if idx+1 < len(baseURLs) {
|
||||||
|
log.Debugf("antigravity executor: request error on base url %s, retrying with fallback base url: %s", baseURL, baseURLs[idx+1])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return cliproxyexecutor.Response{}, errDo
|
||||||
|
}
|
||||||
|
|
||||||
|
recordAPIResponseMetadata(ctx, e.cfg, httpResp.StatusCode, httpResp.Header.Clone())
|
||||||
|
bodyBytes, errRead := io.ReadAll(httpResp.Body)
|
||||||
|
if errClose := httpResp.Body.Close(); errClose != nil {
|
||||||
|
log.Errorf("antigravity executor: close response body error: %v", errClose)
|
||||||
|
}
|
||||||
|
if errRead != nil {
|
||||||
|
recordAPIResponseError(ctx, e.cfg, errRead)
|
||||||
|
return cliproxyexecutor.Response{}, errRead
|
||||||
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, bodyBytes)
|
||||||
|
|
||||||
|
if httpResp.StatusCode >= http.StatusOK && httpResp.StatusCode < http.StatusMultipleChoices {
|
||||||
|
count := gjson.GetBytes(bodyBytes, "totalTokens").Int()
|
||||||
|
translated := sdktranslator.TranslateTokenCount(respCtx, to, from, count, bodyBytes)
|
||||||
|
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastStatus = httpResp.StatusCode
|
||||||
|
lastBody = append([]byte(nil), bodyBytes...)
|
||||||
|
lastErr = nil
|
||||||
|
if httpResp.StatusCode == http.StatusTooManyRequests && idx+1 < len(baseURLs) {
|
||||||
|
log.Debugf("antigravity executor: rate limited on base url %s, retrying with fallback base url: %s", baseURL, baseURLs[idx+1])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return cliproxyexecutor.Response{}, statusErr{code: httpResp.StatusCode, msg: string(bodyBytes)}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case lastStatus != 0:
|
||||||
|
return cliproxyexecutor.Response{}, statusErr{code: lastStatus, msg: string(lastBody)}
|
||||||
|
case lastErr != nil:
|
||||||
|
return cliproxyexecutor.Response{}, lastErr
|
||||||
|
default:
|
||||||
|
return cliproxyexecutor.Response{}, statusErr{code: http.StatusServiceUnavailable, msg: "antigravity executor: no base url available"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAntigravityModels retrieves available models using the supplied auth.
|
// FetchAntigravityModels retrieves available models using the supplied auth.
|
||||||
@@ -1122,6 +1245,8 @@ func modelName2Alias(modelName string) string {
|
|||||||
return "gemini-3-pro-image-preview"
|
return "gemini-3-pro-image-preview"
|
||||||
case "gemini-3-pro-high":
|
case "gemini-3-pro-high":
|
||||||
return "gemini-3-pro-preview"
|
return "gemini-3-pro-preview"
|
||||||
|
case "gemini-3-flash":
|
||||||
|
return "gemini-3-flash-preview"
|
||||||
case "claude-sonnet-4-5":
|
case "claude-sonnet-4-5":
|
||||||
return "gemini-claude-sonnet-4-5"
|
return "gemini-claude-sonnet-4-5"
|
||||||
case "claude-sonnet-4-5-thinking":
|
case "claude-sonnet-4-5-thinking":
|
||||||
@@ -1143,6 +1268,8 @@ func alias2ModelName(modelName string) string {
|
|||||||
return "gemini-3-pro-image"
|
return "gemini-3-pro-image"
|
||||||
case "gemini-3-pro-preview":
|
case "gemini-3-pro-preview":
|
||||||
return "gemini-3-pro-high"
|
return "gemini-3-pro-high"
|
||||||
|
case "gemini-3-flash-preview":
|
||||||
|
return "gemini-3-flash"
|
||||||
case "gemini-claude-sonnet-4-5":
|
case "gemini-claude-sonnet-4-5":
|
||||||
return "claude-sonnet-4-5"
|
return "claude-sonnet-4-5"
|
||||||
case "gemini-claude-sonnet-4-5-thinking":
|
case "gemini-claude-sonnet-4-5-thinking":
|
||||||
|
|||||||
@@ -73,10 +73,12 @@ func tokenizerForModel(model string) (*TokenizerWrapper, error) {
|
|||||||
switch {
|
switch {
|
||||||
case sanitized == "":
|
case sanitized == "":
|
||||||
enc, err = tokenizer.Get(tokenizer.Cl100kBase)
|
enc, err = tokenizer.Get(tokenizer.Cl100kBase)
|
||||||
case strings.HasPrefix(sanitized, "gpt-5"):
|
case strings.HasPrefix(sanitized, "gpt-5.2"):
|
||||||
enc, err = tokenizer.ForModel(tokenizer.GPT5)
|
enc, err = tokenizer.ForModel(tokenizer.GPT5)
|
||||||
case strings.HasPrefix(sanitized, "gpt-5.1"):
|
case strings.HasPrefix(sanitized, "gpt-5.1"):
|
||||||
enc, err = tokenizer.ForModel(tokenizer.GPT5)
|
enc, err = tokenizer.ForModel(tokenizer.GPT5)
|
||||||
|
case strings.HasPrefix(sanitized, "gpt-5"):
|
||||||
|
enc, err = tokenizer.ForModel(tokenizer.GPT5)
|
||||||
case strings.HasPrefix(sanitized, "gpt-4.1"):
|
case strings.HasPrefix(sanitized, "gpt-4.1"):
|
||||||
enc, err = tokenizer.ForModel(tokenizer.GPT41)
|
enc, err = tokenizer.ForModel(tokenizer.GPT41)
|
||||||
case strings.HasPrefix(sanitized, "gpt-4o"):
|
case strings.HasPrefix(sanitized, "gpt-4o"):
|
||||||
@@ -154,10 +156,10 @@ func countClaudeChatTokens(enc *TokenizerWrapper, payload []byte) (int64, error)
|
|||||||
|
|
||||||
// Collect system prompt (can be string or array of content blocks)
|
// Collect system prompt (can be string or array of content blocks)
|
||||||
collectClaudeSystem(root.Get("system"), &segments)
|
collectClaudeSystem(root.Get("system"), &segments)
|
||||||
|
|
||||||
// Collect messages
|
// Collect messages
|
||||||
collectClaudeMessages(root.Get("messages"), &segments)
|
collectClaudeMessages(root.Get("messages"), &segments)
|
||||||
|
|
||||||
// Collect tools
|
// Collect tools
|
||||||
collectClaudeTools(root.Get("tools"), &segments)
|
collectClaudeTools(root.Get("tools"), &segments)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user