mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-03 06:35:00 +08:00
Align the remaining measured OAuth wire profiles, including the ordered connection writer in internal/httpwire that reproduces the observed header sequence, and the refresh/profile response shapes in internal/auth/claude. Replay the measured Fast path and keep diagnostic continuity across cloaked and native requests. Preserve the native direct token-counting shape so a caller that reaches count_tokens itself is not reshaped into the cloaked form. Scope cloak dates to the credential's timezone rather than the host's, so currentDate matches what the real client would have sent for that account.
93 lines
4.4 KiB
Go
93 lines
4.4 KiB
Go
package executor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
claudeauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestInjectClaudeDiagnosticsMatchesNativeFieldOrderAndContinuity(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
body := []byte(`{"context_management":{"edits":[{"type":"clear_thinking_20251015","keep":"all"}]},"max_tokens":1,"messages":[]}`)
|
|
first, state := injectClaudeDiagnostics(body, "credential-diagnostics-order", "session-diagnostics-order")
|
|
wantOrder := `"context_management":{"edits":[{"type":"clear_thinking_20251015","keep":"all"}]},"diagnostics":{"previous_message_id":null},"max_tokens"`
|
|
if !bytes.Contains(first, []byte(wantOrder)) {
|
|
t.Fatalf("diagnostics field order differs from native: %s", first)
|
|
}
|
|
if got := gjson.GetBytes(first, "diagnostics.previous_message_id"); got.Type != gjson.Null {
|
|
t.Fatalf("first previous_message_id = %s, want null", got.Raw)
|
|
}
|
|
|
|
commitClaudeDiagnostics(state, "msg_01ABCDEF0123456789ABCDEFG")
|
|
second, _ := injectClaudeDiagnostics(body, "credential-diagnostics-order", "session-diagnostics-order")
|
|
if got := gjson.GetBytes(second, "diagnostics.previous_message_id").String(); got != "msg_01ABCDEF0123456789ABCDEFG" {
|
|
t.Fatalf("second previous_message_id = %q, want committed upstream ID", got)
|
|
}
|
|
}
|
|
|
|
func TestClaudeExecutorDiagnosticsAdvancesAfterSuccessfulResponse(t *testing.T) {
|
|
var previousValues []gjson.Result
|
|
call := 0
|
|
transport := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
|
body, errRead := io.ReadAll(req.Body)
|
|
if errRead != nil {
|
|
t.Fatal(errRead)
|
|
}
|
|
previousValues = append(previousValues, gjson.GetBytes(body, "diagnostics.previous_message_id"))
|
|
call++
|
|
response := `{"id":"msg_diagnostics_` + string(rune('0'+call)) + `","type":"message","model":"claude-opus-5","role":"assistant","content":[{"type":"text","text":"ok"}],"usage":{"input_tokens":1,"output_tokens":1}}`
|
|
return &http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(strings.NewReader(response)), Request: req}, nil
|
|
})
|
|
ctx := context.WithValue(context.Background(), "cliproxy.roundtripper", http.RoundTripper(transport))
|
|
deviceIDs := []string{"0000000000000000000000000000000000000000000000000000000000000000"}
|
|
auth := &cliproxyauth.Auth{
|
|
ID: "diagnostics-live-path",
|
|
Attributes: map[string]string{"api_key": "sk-ant-oat-diagnostics-live-path"},
|
|
Metadata: map[string]any{
|
|
"account_uuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
claudeauth.ClaudeDeviceIDsMetadataKey: deviceIDs,
|
|
},
|
|
}
|
|
executor := NewClaudeExecutor(&config.Config{})
|
|
request := cliproxyexecutor.Request{Model: "claude-opus-5", Payload: []byte(`{"model":"claude-opus-5","messages":[{"role":"user","content":"x"}],"max_tokens":16}`)}
|
|
options := cliproxyexecutor.Options{
|
|
SourceFormat: sdktranslator.FormatClaude,
|
|
Metadata: map[string]any{cliproxyexecutor.ExecutionSessionMetadataKey: "diagnostics-conversation"},
|
|
}
|
|
for range 2 {
|
|
if _, errExecute := executor.Execute(ctx, auth, request, options); errExecute != nil {
|
|
t.Fatalf("Execute() error = %v", errExecute)
|
|
}
|
|
}
|
|
if len(previousValues) != 2 || previousValues[0].Type != gjson.Null || previousValues[0].Raw != "null" {
|
|
t.Fatalf("first diagnostics value = %#v, want explicit null", previousValues)
|
|
}
|
|
if got := previousValues[1].String(); got != "msg_diagnostics_1" {
|
|
t.Fatalf("second diagnostics previous_message_id = %q, want first upstream response ID", got)
|
|
}
|
|
}
|
|
|
|
func TestClaudeMessageIDFromSSECommitsOnlyCompletedMessage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
complete := []byte("event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_complete\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n")
|
|
if got := claudeMessageIDFromSSE(complete); got != "msg_complete" {
|
|
t.Fatalf("completed SSE message ID = %q, want msg_complete", got)
|
|
}
|
|
incomplete := []byte(strings.Replace(string(complete), "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n", "", 1))
|
|
if got := claudeMessageIDFromSSE(incomplete); got != "" {
|
|
t.Fatalf("incomplete SSE message ID = %q, want empty", got)
|
|
}
|
|
}
|