1228 lines · 40.6 KB · Go
RawDownload
package executor import (	"bytes"	"context"	"fmt"	"io"	"net/http"	"net/http/httptest"	"strings"	"testing"	"time" 	"github.com/google/uuid"	devinauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/devin"	"github.com/router-for-me/CLIProxyAPI/v7/internal/config"	"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"	"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"	"github.com/router-for-me/CLIProxyAPI/v7/internal/util"	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"	"google.golang.org/protobuf/encoding/protowire") func TestDevinExecutorIdentifierAndFormat(t *testing.T) {	exec := NewDevinExecutor(&config.Config{})	if exec.Identifier() != "devin" {		t.Fatalf("Identifier() = %q, want %q", exec.Identifier(), "devin")	} 	format := exec.RequestToFormat(cliproxyexecutor.Request{}, cliproxyexecutor.Options{})	if format != sdktranslator.FormatInteractions {		t.Fatalf("RequestToFormat() = %q, want %q", format, sdktranslator.FormatInteractions)	}} func TestDevinExecutorPrepareRequest(t *testing.T) {	exec := NewDevinExecutor(&config.Config{})	auth := &cliproxyauth.Auth{		Attributes: map[string]string{			"api_key": "my-secret-key",		},	}	req, err := http.NewRequest(http.MethodPost, "https://server.codeium.com/test", nil)	if err != nil {		t.Fatalf("NewRequest failed: %v", err)	} 	if err := exec.PrepareRequest(req, auth); err != nil {		t.Fatalf("PrepareRequest failed: %v", err)	} 	authHeader := req.Header.Get("Authorization")	if authHeader != "Basic my-secret-key-my-secret-key" {		t.Fatalf("Authorization = %q, want %q", authHeader, "Basic my-secret-key-my-secret-key")	}	if req.Header.Get("Content-Type") != "application/connect+proto" {		t.Fatalf("Content-Type = %q, want application/connect+proto", req.Header.Get("Content-Type"))	}	if req.Header.Get("Connect-Protocol-Version") != "1" {		t.Fatalf("Connect-Protocol-Version = %q, want 1", req.Header.Get("Connect-Protocol-Version"))	}	if req.Header.Get("Accept") != "*/*" {		t.Fatalf("Accept = %q, want */*", req.Header.Get("Accept"))	}	sentryTrace := req.Header.Get("Sentry-Trace")	if sentryTrace == "" {		t.Fatalf("Sentry-Trace header missing")	}	parts := strings.Split(sentryTrace, "-")	if len(parts) != 3 || len(parts[0]) != 32 || len(parts[1]) != 16 || parts[2] != "1" {		t.Fatalf("invalid Sentry-Trace format: %q", sentryTrace)	} 	// Verify User-Agent suppression on the wire	var receivedUA []string	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {		receivedUA = r.Header["User-Agent"]	}))	defer ts.Close() 	wireReq, err := http.NewRequest(http.MethodPost, ts.URL, nil)	if err != nil {		t.Fatalf("NewRequest failed: %v", err)	}	if err := exec.PrepareRequest(wireReq, auth); err != nil {		t.Fatalf("PrepareRequest failed: %v", err)	}	resp, err := ts.Client().Do(wireReq)	if err != nil {		t.Fatalf("Do request failed: %v", err)	}	_ = resp.Body.Close() 	if len(receivedUA) != 0 {		t.Errorf("expected User-Agent to be completely omitted on wire, got: %v", receivedUA)	}} func TestDevinAuthCredentials(t *testing.T) {	auth := &cliproxyauth.Auth{		Attributes: map[string]string{			"session_token": "token-xyz",			"base_url":      "https://custom.endpoint.com",			"device_seed":   "seed-456",		},	}	apiKey, baseURL, seed := devinAuthCredentials(auth)	if apiKey != "token-xyz" {		t.Errorf("apiKey = %q, want token-xyz", apiKey)	}	if baseURL != "https://custom.endpoint.com" {		t.Errorf("baseURL = %q, want https://custom.endpoint.com", baseURL)	}	if seed != "seed-456" {		t.Errorf("seed = %q, want seed-456", seed)	}} func TestDevinExecutor_GetSensitiveWords(t *testing.T) {	eEmpty := &DevinExecutor{}	if words := eEmpty.getSensitiveWords(); len(words) != 0 {		t.Errorf("words = %v, want empty", words)	} 	eWithWords := &DevinExecutor{		cfg: &config.Config{			Devin: config.DevinConfig{				SensitiveWords: []string{"sample-word-1", "sample-word-2"},			},		},	}	words := eWithWords.getSensitiveWords()	if len(words) != 2 || words[0] != "sample-word-1" || words[1] != "sample-word-2" {		t.Errorf("words = %v, want [sample-word-1 sample-word-2]", words)	}} func TestParseInteractionsPayload(t *testing.T) {	interactionsPayload := []byte(`{		"system_instruction": "You are a helpful coding assistant.",		"generation_config": {			"temperature": 0.8,			"max_output_tokens": 16000,			"thinking_level": "high"		},		"previous_interaction_id": "session-uuid-1",		"input": [			{"type":"user_input","content":[{"type":"text","text":"hello"}]},			{"type":"thought","content":[{"type":"text","text":"planning..."}],"signature":"c2VhbGVkLnYxLnRlc3Q="},			{"type":"model_output","content":[{"type":"text","text":"I can help with that."}]},			{"type":"function_call","name":"read_file","id":"call_1","arguments":{"path":"main.go"}},			{"type":"function_result","id":"call_1","result":"package main\n"}		],		"tools": [			{"name":"read_file","description":"Read file content","parameters":{"type":"object"}}		]	}`) 	sys, prompts, tools, temp, maxTokens, sessID, cascadeID, level, _ := parseInteractionsPayload(interactionsPayload, nil) 	if sys != "You are a helpful coding assistant." {		t.Errorf("systemPrompt = %q, want expected", sys)	}	if temp == nil || *temp != 0.8 {		t.Errorf("temperature = %v, want 0.8", temp)	}	if maxTokens != 16000 {		t.Errorf("maxTokens = %d, want 16000", maxTokens)	}	if level != "high" {		t.Errorf("thinkingLevel = %q, want high", level)	}	if sessID != "session-uuid-1" || cascadeID != "session-uuid-1" {		t.Errorf("session/cascade ID = %q / %q, want session-uuid-1", sessID, cascadeID)	} 	if len(tools) != 1 || tools[0].Name != "read_file" {		t.Fatalf("tools count/name mismatch: %+v", tools)	} 	if len(prompts) != 3 {		t.Fatalf("expected 3 prompt items (user, assistant-with-thought-and-call, tool-result), got %d: %+v", len(prompts), prompts)	} 	// 1. User turn	if prompts[0].Source != 1 || prompts[0].Content != "hello" {		t.Errorf("prompt[0] user turn mismatch: %+v", prompts[0])	} 	// 2. Assistant turn (attached thought + content + function call)	if prompts[1].Source != 2 {		t.Errorf("prompt[1] source = %d, want 2", prompts[1].Source)	}	if prompts[1].Thinking != "planning..." {		t.Errorf("prompt[1] thinking = %q, want planning...", prompts[1].Thinking)	}	if string(prompts[1].Signature) != "sealed.v1.test" {		t.Errorf("prompt[1] signature = %q, want sealed.v1.test", string(prompts[1].Signature))	}	if len(prompts[1].ToolCalls) != 1 || prompts[1].ToolCalls[0].Name != "read_file" {		t.Errorf("prompt[1] tool calls mismatch: %+v", prompts[1].ToolCalls)	} 	// 3. Tool result turn	if prompts[2].Source != 4 || prompts[2].ToolCallID != "call_1" || prompts[2].Content != "package main\n" {		t.Errorf("prompt[2] tool result mismatch: %+v", prompts[2])	}} func TestParseInteractionsPayload_MultipleThoughtsAndZeroTemperature(t *testing.T) {	interactionsPayload := []byte(`{		"generation_config": {			"temperature": 0.0		},		"input": [			{"type": "user_input", "content": [{"type": "text", "text": "hello"}]},			{"type": "thought", "text": "Thought part 1"},			{"type": "thought", "text": "Thought part 2"},			{"type": "model_output", "text": "Hello there!"}		]	}`) 	_, prompts, _, temp, _, _, _, _, _ := parseInteractionsPayload(interactionsPayload, nil) 	if temp == nil || *temp != 0.0 {		t.Fatalf("temperature = %v, want 0.0", temp)	} 	if len(prompts) != 2 {		t.Fatalf("prompts len = %d, want 2", len(prompts))	} 	asst := prompts[1]	if asst.Source != 2 {		t.Fatalf("assistant source = %d, want 2", asst.Source)	}	wantThinking := "Thought part 1\n\nThought part 2"	if asst.Thinking != wantThinking {		t.Fatalf("assistant thinking = %q, want %q", asst.Thinking, wantThinking)	}	if asst.Content != "Hello there!" {		t.Fatalf("assistant content = %q, want Hello there!", asst.Content)	}} func TestSupplementSignaturesFromOriginal(t *testing.T) {	originalRequest := []byte(`{		"messages": [			{"role":"user","content":"hello"},			{"role":"assistant","content":[				{"type":"thinking","thinking":"let me think","signature":"Q0FRU3Rlc3Q="},				{"type":"text","text":"here is the answer"}			]}		]	}`) 	prompts := []helps.DevinPrompt{		{Source: 1, Content: "hello"},		{Source: 2, Content: "here is the answer"}, // signature missing in interactions	} 	supplementSignaturesFromOriginal(originalRequest, prompts) 	if len(prompts[1].Signature) == 0 {		t.Fatal("expected signature to be supplemented from original request")	}	if string(prompts[1].Signature) != "CAQStest" {		t.Errorf("signature = %q, want CAQStest", string(prompts[1].Signature))	}	if prompts[1].SignatureType != "anthropic" {		t.Errorf("signatureType = %q, want anthropic", prompts[1].SignatureType)	}} func TestDetectSignatureType_GlobalDetectorIntegration(t *testing.T) {	tests := []struct {		name     string		sig      string		wantType string	}{		{			name:     "Devin native sealed signature",			sig:      "sealed.v1.abcde12345",			wantType: "sealed",		},		{			name:     "Anthropic CAQS signature",			sig:      "CAQStest12345",			wantType: "anthropic",		},		{			name:     "Anthropic with claude# prefix",			sig:      "claude#CAQStest12345",			wantType: "anthropic",		},		{			name:     "OpenAI gAAAA Fernet signature",			sig:      "gAAAAABk1234567890",			wantType: "openai",		},		{			name:     "Gemini AY signature",			sig:      "AY12345",			wantType: "gemini",		},	} 	for _, tt := range tests {		t.Run(tt.name, func(t *testing.T) {			got := detectSignatureType(tt.sig)			if got != tt.wantType {				t.Errorf("detectSignatureType(%q) = %q, want %q", tt.sig, got, tt.wantType)			}			_, pType := parseSignatureBytes(tt.sig)			if pType != tt.wantType {				t.Errorf("parseSignatureBytes(%q) type = %q, want %q", tt.sig, pType, tt.wantType)			}		})	}} func TestDevinStatusError_RetryAfter(t *testing.T) {	// 1. HTTP 429 with integer Retry-After	hdr429 := http.Header{}	hdr429.Set("Retry-After", "30")	err1 := newDevinStatusError(http.StatusTooManyRequests, hdr429, []byte("rate limited"))	if err1.code != 429 {		t.Fatalf("expected code 429, got %d", err1.code)	}	if err1.retryAfter == nil || *err1.retryAfter != 30*time.Second {		t.Fatalf("expected retryAfter 30s, got %v", err1.retryAfter)	} 	// 2. HTTP 429 with HTTP Date	hdrDate := http.Header{}	futureTime := time.Now().Add(60 * time.Second).UTC().Format(http.TimeFormat)	hdrDate.Set("Retry-After", futureTime)	err2 := newDevinStatusError(http.StatusTooManyRequests, hdrDate, []byte("rate limited"))	if err2.retryAfter == nil || *err2.retryAfter <= 0 || *err2.retryAfter > 65*time.Second {		t.Fatalf("expected retryAfter ~60s, got %v", err2.retryAfter)	} 	// 3. HTTP 500 with Retry-After (should not set retryAfter)	err3 := newDevinStatusError(http.StatusInternalServerError, hdr429, []byte("server error"))	if err3.retryAfter != nil {		t.Fatalf("expected nil retryAfter for 500, got %v", err3.retryAfter)	}} func TestResolveDevinSessionAndCascadeIDs(t *testing.T) {	// 1. Direct UUID preservation	rawUUID := "8176cf8a-feff-44c1-8e3e-b10f6d737ae1"	sid, cid := resolveDevinSessionAndCascadeIDs(context.Background(), rawUUID, rawUUID, cliproxyexecutor.Options{})	if sid != rawUUID || cid != rawUUID {		t.Fatalf("sid/cid = %q/%q, want %q", sid, cid, rawUUID)	} 	// 2. Non-UUID mapping to deterministic UUID	sid1, cid1 := resolveDevinSessionAndCascadeIDs(context.Background(), "lcp:12345678", "", cliproxyexecutor.Options{})	sid2, cid2 := resolveDevinSessionAndCascadeIDs(context.Background(), "lcp:12345678", "", cliproxyexecutor.Options{})	if sid1 != sid2 || cid1 != cid2 {		t.Fatalf("deterministic mapping failed: %q != %q", sid1, sid2)	}	if _, err := uuid.Parse(sid1); err != nil {		t.Fatalf("mapped sid is not a valid UUID: %q", sid1)	} 	// 3. Fallback to ctx session	ctx := util.WithSessionID(context.Background(), "ctx-session-abc")	sidCtx, cidCtx := resolveDevinSessionAndCascadeIDs(ctx, "", "", cliproxyexecutor.Options{})	if _, err := uuid.Parse(sidCtx); err != nil {		t.Fatalf("sidCtx is not a valid UUID: %q", sidCtx)	}	if sidCtx != cidCtx {		t.Fatalf("sidCtx %q != cidCtx %q", sidCtx, cidCtx)	} 	// 4. Fallback to fresh UUID when nothing supplied	sidEmpty, cidEmpty := resolveDevinSessionAndCascadeIDs(context.Background(), "", "", cliproxyexecutor.Options{})	if _, err := uuid.Parse(sidEmpty); err != nil {		t.Fatalf("sidEmpty is not a valid UUID: %q", sidEmpty)	}	if sidEmpty != cidEmpty {		t.Fatalf("sidEmpty %q != cidEmpty %q", sidEmpty, cidEmpty)	}} func TestConsumeDevinFramesToInteractions(t *testing.T) {	// Synthesize a Connect stream with 2 data frames and 1 EOS trailer	var streamBuf bytes.Buffer 	// Frame 1: thinking + content	var f1 []byte	f1 = appendDevinFieldBytes(f1, 1, []byte("bot-uuid-1"))	f1 = appendDevinFieldBytes(f1, 9, []byte("reasoning step"))	f1 = appendDevinFieldBytes(f1, 3, []byte("hello response"))	f1 = appendDevinFieldBytes(f1, 10, []byte("sealed.v1.sig"))	streamBuf.Write(helps.WrapConnectEnvelope(f1)) 	// Frame 2: tool call + usage	var f2 []byte	var tcBytes []byte	tcBytes = appendDevinFieldBytes(tcBytes, 1, []byte("toolu_1"))	tcBytes = appendDevinFieldBytes(tcBytes, 2, []byte("bash"))	tcBytes = appendDevinFieldBytes(tcBytes, 3, []byte(`{"command":"ls"}`))	f2 = appendDevinFieldBytes(f2, 6, tcBytes) 	var usageBytes []byte	usageBytes = appendVarintField(usageBytes, 2, 100) // prompt	usageBytes = appendVarintField(usageBytes, 3, 50)  // completion	usageBytes = appendVarintField(usageBytes, 5, 20)  // cached	f2 = appendDevinFieldBytes(f2, 7, usageBytes)	streamBuf.Write(helps.WrapConnectEnvelope(f2)) 	// Frame 3: EOS Trailer flag 0x02	trailerJSON := []byte(`{}`)	streamBuf.Write(helps.WrapConnectEnvelopeWithFlag(helps.ConnectFlagEndStream, trailerJSON)) 	interactionsJSON, respLog, err := consumeDevinFramesToInteractions(&streamBuf, "swe-2", "swe-2-high")	if err != nil {		t.Fatalf("consumeDevinFramesToInteractions failed: %v", err)	}	if respLog == nil {		t.Fatal("expected non-nil respLog")	}	if respLog.FramesCount != 3 {		t.Errorf("FramesCount = %d, want 3", respLog.FramesCount)	} 	root := gjson.ParseBytes(interactionsJSON)	if root.Get("status").String() != "completed" {		t.Errorf("status = %q, want completed", root.Get("status").String())	}	if root.Get("usage.total_input_tokens").Int() != 120 {		t.Errorf("input tokens = %d, want 120", root.Get("usage.total_input_tokens").Int())	}	if root.Get("usage.total_output_tokens").Int() != 50 {		t.Errorf("output tokens = %d, want 50", root.Get("usage.total_output_tokens").Int())	}	if root.Get("usage.total_cached_tokens").Int() != 20 {		t.Errorf("cached tokens = %d, want 20", root.Get("usage.total_cached_tokens").Int())	}	if root.Get("usage.total_tokens").Int() != 170 {		t.Errorf("total tokens = %d, want 170", root.Get("usage.total_tokens").Int())	} 	steps := root.Get("steps").Array()	if len(steps) != 3 {		t.Fatalf("steps count = %d, want 3 (thought, model_output, function_call). Payload: %s", len(steps), string(interactionsJSON))	} 	// Thought step has signature	if steps[0].Get("type").String() != "thought" {		t.Errorf("step[0] type = %q, want thought", steps[0].Get("type").String())	}	expectedSig := "sealed.v1.sig"	if steps[0].Get("signature").String() != expectedSig {		t.Errorf("step[0] signature = %q, want %q", steps[0].Get("signature").String(), expectedSig)	} 	// Model output step	if steps[1].Get("type").String() != "model_output" {		t.Errorf("step[1] type = %q, want model_output", steps[1].Get("type").String())	}	if steps[1].Get("content.0.text").String() != "hello response" {		t.Errorf("step[1] text = %q, want 'hello response'", steps[1].Get("content.0.text").String())	} 	// Function call step	if steps[2].Get("type").String() != "function_call" {		t.Errorf("step[2] type = %q, want function_call", steps[2].Get("type").String())	}	if steps[2].Get("name").String() != "bash" {		t.Errorf("step[2] tool name = %q, want bash", steps[2].Get("name").String())	}} func appendDevinFieldBytes(dst []byte, fieldNum int, val []byte) []byte {	tag := uint64(fieldNum<<3 | 2)	dst = appendVarintRaw(dst, tag)	dst = appendVarintRaw(dst, uint64(len(val)))	dst = append(dst, val...)	return dst} func appendVarintField(dst []byte, fieldNum int, v uint64) []byte {	tag := uint64(fieldNum<<3 | 0)	dst = appendVarintRaw(dst, tag)	dst = appendVarintRaw(dst, v)	return dst} func appendVarintRaw(dst []byte, v uint64) []byte {	for v >= 0x80 {		dst = append(dst, byte(v)|0x80)		v >>= 7	}

Lines 1–500 of 1228.