Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/pr-test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ jobs:
run: |
go build -o test-output ./cmd/server
rm -f test-output
- name: Test
run: go test ./... -count=1
- name: Focused race tests
run: go test -race ./sdk/cliproxy ./sdk/translator ./internal/runtime/executor -count=1
- name: Vet
run: go vet ./...
39 changes: 5 additions & 34 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ jobs:
} > "$updated_notes_file"

if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release edit "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file "$updated_notes_file"
else
gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file "$updated_notes_file"
echo "release already exists for immutable tag $GITHUB_REF_NAME" >&2
exit 1
fi
gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file "$updated_notes_file"

build-hosted:
name: build ${{ matrix.target }}
Expand Down Expand Up @@ -201,36 +201,7 @@ jobs:
printf '%s\n' "${assets[@]}" >&2
exit 1
fi
gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber
- name: Refresh release checksums
continue-on-error: true
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
gh release download "$GITHUB_REF_NAME" \
--pattern 'CLIProxyAPI_*.tar.gz' \
--pattern 'CLIProxyAPI_*.zip' \
--dir "$tmp_dir" \
--clobber
(
cd "$tmp_dir"
shopt -s nullglob
archives=(CLIProxyAPI_*.tar.gz CLIProxyAPI_*.zip)
if [[ ${#archives[@]} -eq 0 ]]; then
echo "No release archives found"
exit 0
fi
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${archives[@]}" | sort -k2 > checksums.txt
else
shasum -a 256 "${archives[@]}" | sort -k2 > checksums.txt
fi
)
gh release upload "$GITHUB_REF_NAME" "$tmp_dir/checksums.txt" --clobber
gh release upload "$GITHUB_REF_NAME" "${assets[@]}"

build-linux-glibc:
name: build linux-${{ matrix.goarch }} glibc
Expand Down Expand Up @@ -667,4 +638,4 @@ jobs:
exit 0
fi
sha256sum "${archives[@]}" | sort -k2 > checksums.txt
gh release upload "$GITHUB_REF_NAME" checksums.txt --clobber
gh release upload "$GITHUB_REF_NAME" checksums.txt
32 changes: 32 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
stages:
- verify

variables:
GOPROXY: https://goproxy.cn,direct
GOFLAGS: -buildvcs=false

default:
image: docker.m.daocloud.io/library/golang:1.26-bookworm
cache:
key: go-mod-${CI_COMMIT_REF_SLUG}
paths:
- .cache/go-build
- .cache/go-mod
before_script:
- export GOCACHE="$CI_PROJECT_DIR/.cache/go-build"
- export GOMODCACHE="$CI_PROJECT_DIR/.cache/go-mod"

test:
stage: verify
script:
- go test ./... -count=1

race-focused:
stage: verify
script:
- go test -race ./sdk/cliproxy ./sdk/translator ./internal/runtime/executor -count=1

vet:
stage: verify
script:
- go vet ./...
15 changes: 11 additions & 4 deletions internal/runtime/executor/antigravity_executor_credits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ import (
)

func resetAntigravityCreditsRetryState() {
antigravityCreditsFailureByAuth = sync.Map{}
antigravityShortCooldownByAuth = sync.Map{}
antigravityCreditsBalanceByAuth = sync.Map{}
antigravityCreditsHintRefreshByID = sync.Map{}
clearAntigravityTestSyncMap(&antigravityCreditsFailureByAuth)
clearAntigravityTestSyncMap(&antigravityShortCooldownByAuth)
clearAntigravityTestSyncMap(&antigravityCreditsBalanceByAuth)
clearAntigravityTestSyncMap(&antigravityCreditsHintRefreshByID)
}

func clearAntigravityTestSyncMap(values *sync.Map) {
values.Range(func(key, _ any) bool {
values.Delete(key)
return true
})
}

type fakeAntigravityKVClient struct {
Expand Down
18 changes: 11 additions & 7 deletions internal/runtime/executor/claude_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
ctx,
to,
responseFormat,
req.Model,
requestedModel,
opts.OriginalRequest,
bodyForTranslation,
data,
Expand Down Expand Up @@ -552,18 +552,22 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
scanner := bufio.NewScanner(decodedBody)
scanner.Buffer(nil, 52_428_800) // 50MB
var event bytes.Buffer
var param any
flushEvent := func() bool {
if event.Len() == 0 {
return true
}
cloned := bytes.Clone(event.Bytes())
event.Reset()
select {
case out <- cliproxyexecutor.StreamChunk{Payload: cloned}:
return true
case <-ctx.Done():
return false
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, requestedModel, opts.OriginalRequest, bodyForTranslation, cloned, &param)
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()
Expand Down Expand Up @@ -607,7 +611,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
ctx,
to,
responseFormat,
req.Model,
requestedModel,
opts.OriginalRequest,
bodyForTranslation,
bytes.Clone(line),
Expand Down
12 changes: 10 additions & 2 deletions internal/runtime/executor/claude_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1237,9 +1237,13 @@ func TestClaudeExecutor_ExecuteStreamStripsOpenAIEncryptedThinkingBeforeUpstream
}

func TestClaudeExecutor_ExecuteStreamDirectPassthroughEmitsCompleteSSEEvents(t *testing.T) {
startData := `{"type":"message_start","message":{"id":"msg_1","model":"physical-model"}}`
firstData := `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hi"}}`
secondData := `{"type":"message_stop"}`
upstreamStream := "event: content_block_delta\n" +
upstreamStream := "event: message_start\n" +
"data: " + startData + "\n" +
"\n" +
"event: content_block_delta\n" +
"data: " + firstData + "\n" +
"\n" +
"event: message_stop\n" +
Expand All @@ -1262,7 +1266,10 @@ func TestClaudeExecutor_ExecuteStreamDirectPassthroughEmitsCompleteSSEEvents(t *
result, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{
Model: "claude-3-5-sonnet-20241022",
Payload: payload,
}, cliproxyexecutor.Options{SourceFormat: sdktranslator.FromString("claude")})
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("claude"),
Metadata: map[string]any{cliproxyexecutor.RequestedModelMetadataKey: "canonical-model"},
})
if err != nil {
t.Fatalf("ExecuteStream() error = %v", err)
}
Expand All @@ -1276,6 +1283,7 @@ func TestClaudeExecutor_ExecuteStreamDirectPassthroughEmitsCompleteSSEEvents(t *
}

want := []string{
"event: message_start\n" + "data: " + strings.Replace(startData, "physical-model", "canonical-model", 1) + "\n\n",
"event: content_block_delta\n" + "data: " + firstData + "\n\n",
"event: message_stop\n" + "data: " + secondData + "\n\n",
}
Expand Down
6 changes: 3 additions & 3 deletions internal/runtime/executor/codex_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re

var param any
clientCompletedData := applyCodexIdentityExposeResponsePayload(completedData, identityState)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, clientCompletedData, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, requestedModel, originalPayload, body, clientCompletedData, &param)
resp = cliproxyexecutor.Response{Payload: out, Headers: httpResp.Header.Clone()}
return resp, nil
}
Expand Down Expand Up @@ -1096,7 +1096,7 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A
reporter.EnsurePublished(ctx)
var param any
clientData := applyCodexIdentityExposeResponsePayload(upstreamData, identityState)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, originalPayload, body, clientData, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, requestedModel, originalPayload, body, clientData, &param)
resp = cliproxyexecutor.Response{Payload: out, Headers: httpResp.Header.Clone()}
return resp, nil
}
Expand Down Expand Up @@ -1264,7 +1264,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
}

translatedLine = applyCodexIdentityExposeResponsePayload(translatedLine, identityState)
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, req.Model, originalPayload, body, translatedLine, &param)
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, requestedModel, originalPayload, body, translatedLine, &param)
for i := range chunks {
select {
case out <- cliproxyexecutor.StreamChunk{Payload: chunks[i]}:
Expand Down
6 changes: 3 additions & 3 deletions internal/runtime/executor/openai_compat_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
reporter.EnsurePublished(ctx)
// Translate response back to source format when needed
var param any
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, req.Model, opts.OriginalRequest, translated, body, &param)
out := sdktranslator.TranslateNonStream(ctx, to, responseFormat, requestedModel, opts.OriginalRequest, translated, body, &param)
resp = cliproxyexecutor.Response{Payload: out, Headers: httpResp.Header.Clone()}
return resp, nil
}
Expand Down Expand Up @@ -423,7 +423,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
}

// OpenAI-compatible streams must use SSE data lines.
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, req.Model, opts.OriginalRequest, translated, bytes.Clone(trimmedLine), &param)
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, requestedModel, opts.OriginalRequest, translated, bytes.Clone(trimmedLine), &param)
for i := range chunks {
select {
case out <- cliproxyexecutor.StreamChunk{Payload: chunks[i]}:
Expand All @@ -443,7 +443,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
// In case the upstream close the stream without a terminal [DONE] marker.
// Feed a synthetic done marker through the translator so pending
// response.completed events are still emitted exactly once.
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, req.Model, opts.OriginalRequest, translated, []byte("data: [DONE]"), &param)
chunks := sdktranslator.TranslateStream(ctx, to, responseFormat, requestedModel, opts.OriginalRequest, translated, []byte("data: [DONE]"), &param)
for i := range chunks {
select {
case out <- cliproxyexecutor.StreamChunk{Payload: chunks[i]}:
Expand Down
25 changes: 25 additions & 0 deletions internal/runtime/executor/openai_compat_executor_compact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ func TestOpenAICompatExecutorPayloadOverrideWinsOverThinkingSuffix(t *testing.T)
}
}

func TestOpenAICompatExecutorNormalizesRequestedModelInResponse(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"chatcmpl_1","object":"chat.completion","model":"physical-model","choices":[]}`))
}))
defer server.Close()

executor := NewOpenAICompatExecutor("deepseek", &config.Config{})
auth := &cliproxyauth.Auth{Attributes: map[string]string{"base_url": server.URL, "api_key": "test"}}
resp, err := executor.Execute(t.Context(), auth, cliproxyexecutor.Request{
Model: "physical-model",
Payload: []byte(`{"model":"canonical-model","messages":[]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FormatOpenAI,
ResponseFormat: sdktranslator.FormatOpenAI,
Metadata: map[string]any{cliproxyexecutor.RequestedModelMetadataKey: "canonical-model"},
})
if err != nil {
t.Fatalf("Execute error: %v", err)
}
if model := gjson.GetBytes(resp.Payload, "model").String(); model != "canonical-model" {
t.Fatalf("response model=%q payload=%s", model, resp.Payload)
}
}

func TestOpenAICompatExecutorImagesGenerationsPassthrough(t *testing.T) {
var gotPath string
var gotBody []byte
Expand Down
Loading