mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
659 lines
20 KiB
Go
659 lines
20 KiB
Go
package claude
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestConvertClaudeRequestToAntigravity_BasicStructure(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{"type": "text", "text": "Hello"}
|
|
]
|
|
}
|
|
],
|
|
"system": [
|
|
{"type": "text", "text": "You are helpful"}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check model
|
|
if gjson.Get(outputStr, "model").String() != "claude-sonnet-4-5" {
|
|
t.Errorf("Expected model 'claude-sonnet-4-5', got '%s'", gjson.Get(outputStr, "model").String())
|
|
}
|
|
|
|
// Check contents exist
|
|
contents := gjson.Get(outputStr, "request.contents")
|
|
if !contents.Exists() || !contents.IsArray() {
|
|
t.Error("request.contents should exist and be an array")
|
|
}
|
|
|
|
// Check role mapping (assistant -> model)
|
|
firstContent := gjson.Get(outputStr, "request.contents.0")
|
|
if firstContent.Get("role").String() != "user" {
|
|
t.Errorf("Expected role 'user', got '%s'", firstContent.Get("role").String())
|
|
}
|
|
|
|
// Check systemInstruction
|
|
sysInstruction := gjson.Get(outputStr, "request.systemInstruction")
|
|
if !sysInstruction.Exists() {
|
|
t.Error("systemInstruction should exist")
|
|
}
|
|
if sysInstruction.Get("parts.0.text").String() != "You are helpful" {
|
|
t.Error("systemInstruction text mismatch")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_RoleMapping(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [
|
|
{"role": "user", "content": [{"type": "text", "text": "Hi"}]},
|
|
{"role": "assistant", "content": [{"type": "text", "text": "Hello"}]}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// assistant should be mapped to model
|
|
secondContent := gjson.Get(outputStr, "request.contents.1")
|
|
if secondContent.Get("role").String() != "model" {
|
|
t.Errorf("Expected role 'model' (mapped from 'assistant'), got '%s'", secondContent.Get("role").String())
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ThinkingBlocks(t *testing.T) {
|
|
// Valid signature must be at least 50 characters
|
|
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "thinking", "thinking": "Let me think...", "signature": "` + validSignature + `"},
|
|
{"type": "text", "text": "Answer"}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check thinking block conversion
|
|
firstPart := gjson.Get(outputStr, "request.contents.0.parts.0")
|
|
if !firstPart.Get("thought").Bool() {
|
|
t.Error("thinking block should have thought: true")
|
|
}
|
|
if firstPart.Get("text").String() != "Let me think..." {
|
|
t.Error("thinking text mismatch")
|
|
}
|
|
if firstPart.Get("thoughtSignature").String() != validSignature {
|
|
t.Errorf("Expected thoughtSignature '%s', got '%s'", validSignature, firstPart.Get("thoughtSignature").String())
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ThinkingBlockWithoutSignature(t *testing.T) {
|
|
// Unsigned thinking blocks should be removed entirely (not converted to text)
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "thinking", "thinking": "Let me think..."},
|
|
{"type": "text", "text": "Answer"}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Without signature, thinking block should be removed (not converted to text)
|
|
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
|
if len(parts) != 1 {
|
|
t.Fatalf("Expected 1 part (thinking removed), got %d", len(parts))
|
|
}
|
|
|
|
// Only text part should remain
|
|
if parts[0].Get("thought").Bool() {
|
|
t.Error("Thinking block should be removed, not preserved")
|
|
}
|
|
if parts[0].Get("text").String() != "Answer" {
|
|
t.Errorf("Expected text 'Answer', got '%s'", parts[0].Get("text").String())
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolDeclarations(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [],
|
|
"tools": [
|
|
{
|
|
"name": "test_tool",
|
|
"description": "A test tool",
|
|
"input_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string"}
|
|
},
|
|
"required": ["name"]
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("gemini-1.5-pro", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check tools structure
|
|
tools := gjson.Get(outputStr, "request.tools")
|
|
if !tools.Exists() {
|
|
t.Error("Tools should exist in output")
|
|
}
|
|
|
|
funcDecl := gjson.Get(outputStr, "request.tools.0.functionDeclarations.0")
|
|
if funcDecl.Get("name").String() != "test_tool" {
|
|
t.Errorf("Expected tool name 'test_tool', got '%s'", funcDecl.Get("name").String())
|
|
}
|
|
|
|
// Check input_schema renamed to parametersJsonSchema
|
|
if funcDecl.Get("parametersJsonSchema").Exists() {
|
|
t.Log("parametersJsonSchema exists (expected)")
|
|
}
|
|
if funcDecl.Get("input_schema").Exists() {
|
|
t.Error("input_schema should be removed")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolUse(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"type": "tool_use",
|
|
"id": "call_123",
|
|
"name": "get_weather",
|
|
"input": "{\"location\": \"Paris\"}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Now we expect only 1 part (tool_use), no dummy thinking block injected
|
|
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
|
if len(parts) != 1 {
|
|
t.Fatalf("Expected 1 part (tool only, no dummy injection), got %d", len(parts))
|
|
}
|
|
|
|
// Check function call conversion at parts[0]
|
|
funcCall := parts[0].Get("functionCall")
|
|
if !funcCall.Exists() {
|
|
t.Error("functionCall should exist at parts[0]")
|
|
}
|
|
if funcCall.Get("name").String() != "get_weather" {
|
|
t.Errorf("Expected function name 'get_weather', got '%s'", funcCall.Get("name").String())
|
|
}
|
|
if funcCall.Get("id").String() != "call_123" {
|
|
t.Errorf("Expected function id 'call_123', got '%s'", funcCall.Get("id").String())
|
|
}
|
|
// Verify skip_thought_signature_validator is added (bypass for tools without valid thinking)
|
|
expectedSig := "skip_thought_signature_validator"
|
|
actualSig := parts[0].Get("thoughtSignature").String()
|
|
if actualSig != expectedSig {
|
|
t.Errorf("Expected thoughtSignature '%s', got '%s'", expectedSig, actualSig)
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolUse_WithSignature(t *testing.T) {
|
|
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "thinking", "thinking": "Let me think...", "signature": "` + validSignature + `"},
|
|
{
|
|
"type": "tool_use",
|
|
"id": "call_123",
|
|
"name": "get_weather",
|
|
"input": "{\"location\": \"Paris\"}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check function call has the signature from the preceding thinking block
|
|
part := gjson.Get(outputStr, "request.contents.0.parts.1")
|
|
if part.Get("functionCall.name").String() != "get_weather" {
|
|
t.Errorf("Expected functionCall, got %s", part.Raw)
|
|
}
|
|
if part.Get("thoughtSignature").String() != validSignature {
|
|
t.Errorf("Expected thoughtSignature '%s' on tool_use, got '%s'", validSignature, part.Get("thoughtSignature").String())
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ReorderThinking(t *testing.T) {
|
|
// Case: text block followed by thinking block -> should be reordered to thinking first
|
|
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "text", "text": "Here is the plan."},
|
|
{"type": "thinking", "thinking": "Planning...", "signature": "` + validSignature + `"}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Verify order: Thinking block MUST be first
|
|
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
|
if len(parts) != 2 {
|
|
t.Fatalf("Expected 2 parts, got %d", len(parts))
|
|
}
|
|
|
|
if !parts[0].Get("thought").Bool() {
|
|
t.Error("First part should be thinking block after reordering")
|
|
}
|
|
if parts[1].Get("text").String() != "Here is the plan." {
|
|
t.Error("Second part should be text block")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolResult(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "tool_result",
|
|
"tool_use_id": "get_weather-call-123",
|
|
"content": "22C sunny"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check function response conversion
|
|
funcResp := gjson.Get(outputStr, "request.contents.0.parts.0.functionResponse")
|
|
if !funcResp.Exists() {
|
|
t.Error("functionResponse should exist")
|
|
}
|
|
if funcResp.Get("id").String() != "get_weather-call-123" {
|
|
t.Errorf("Expected function id, got '%s'", funcResp.Get("id").String())
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ThinkingConfig(t *testing.T) {
|
|
// Note: This test requires the model to be registered in the registry
|
|
// with Thinking metadata. If the registry is not populated in test environment,
|
|
// thinkingConfig won't be added. We'll test the basic structure only.
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [],
|
|
"thinking": {
|
|
"type": "enabled",
|
|
"budget_tokens": 8000
|
|
}
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check thinking config conversion (only if model supports thinking in registry)
|
|
thinkingConfig := gjson.Get(outputStr, "request.generationConfig.thinkingConfig")
|
|
if thinkingConfig.Exists() {
|
|
if thinkingConfig.Get("thinkingBudget").Int() != 8000 {
|
|
t.Errorf("Expected thinkingBudget 8000, got %d", thinkingConfig.Get("thinkingBudget").Int())
|
|
}
|
|
if !thinkingConfig.Get("includeThoughts").Bool() {
|
|
t.Error("includeThoughts should be true")
|
|
}
|
|
} else {
|
|
t.Log("thinkingConfig not present - model may not be registered in test registry")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ImageContent(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "image",
|
|
"source": {
|
|
"type": "base64",
|
|
"media_type": "image/png",
|
|
"data": "iVBORw0KGgoAAAANSUhEUg=="
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Check inline data conversion
|
|
inlineData := gjson.Get(outputStr, "request.contents.0.parts.0.inlineData")
|
|
if !inlineData.Exists() {
|
|
t.Error("inlineData should exist")
|
|
}
|
|
if inlineData.Get("mime_type").String() != "image/png" {
|
|
t.Error("mime_type mismatch")
|
|
}
|
|
if !strings.Contains(inlineData.Get("data").String(), "iVBORw0KGgo") {
|
|
t.Error("data mismatch")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_GenerationConfig(t *testing.T) {
|
|
inputJSON := []byte(`{
|
|
"model": "claude-3-5-sonnet-20240620",
|
|
"messages": [],
|
|
"temperature": 0.7,
|
|
"top_p": 0.9,
|
|
"top_k": 40,
|
|
"max_tokens": 2000
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
genConfig := gjson.Get(outputStr, "request.generationConfig")
|
|
if genConfig.Get("temperature").Float() != 0.7 {
|
|
t.Errorf("Expected temperature 0.7, got %f", genConfig.Get("temperature").Float())
|
|
}
|
|
if genConfig.Get("topP").Float() != 0.9 {
|
|
t.Errorf("Expected topP 0.9, got %f", genConfig.Get("topP").Float())
|
|
}
|
|
if genConfig.Get("topK").Float() != 40 {
|
|
t.Errorf("Expected topK 40, got %f", genConfig.Get("topK").Float())
|
|
}
|
|
if genConfig.Get("maxOutputTokens").Float() != 2000 {
|
|
t.Errorf("Expected maxOutputTokens 2000, got %f", genConfig.Get("maxOutputTokens").Float())
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Trailing Unsigned Thinking Block Removal
|
|
// ============================================================================
|
|
|
|
func TestConvertClaudeRequestToAntigravity_TrailingUnsignedThinking_Removed(t *testing.T) {
|
|
// Last assistant message ends with unsigned thinking block - should be removed
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [{"type": "text", "text": "Hello"}]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "text", "text": "Here is my answer"},
|
|
{"type": "thinking", "thinking": "I should think more..."}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// The last part of the last assistant message should NOT be a thinking block
|
|
lastMessageParts := gjson.Get(outputStr, "request.contents.1.parts")
|
|
if !lastMessageParts.IsArray() {
|
|
t.Fatal("Last message should have parts array")
|
|
}
|
|
parts := lastMessageParts.Array()
|
|
if len(parts) == 0 {
|
|
t.Fatal("Last message should have at least one part")
|
|
}
|
|
|
|
// The unsigned thinking should be removed, leaving only the text
|
|
lastPart := parts[len(parts)-1]
|
|
if lastPart.Get("thought").Bool() {
|
|
t.Error("Trailing unsigned thinking block should be removed")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_TrailingSignedThinking_Kept(t *testing.T) {
|
|
// Last assistant message ends with signed thinking block - should be kept
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [{"type": "text", "text": "Hello"}]
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "text", "text": "Here is my answer"},
|
|
{"type": "thinking", "thinking": "Valid thinking...", "signature": "abc123validSignature1234567890123456789012345678901234567890"}
|
|
]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// The signed thinking block should be preserved
|
|
lastMessageParts := gjson.Get(outputStr, "request.contents.1.parts")
|
|
parts := lastMessageParts.Array()
|
|
if len(parts) < 2 {
|
|
t.Error("Signed thinking block should be preserved")
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_MiddleUnsignedThinking_Removed(t *testing.T) {
|
|
// Middle message has unsigned thinking - should be removed entirely
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [
|
|
{
|
|
"role": "assistant",
|
|
"content": [
|
|
{"type": "thinking", "thinking": "Middle thinking..."},
|
|
{"type": "text", "text": "Answer"}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": [{"type": "text", "text": "Follow up"}]
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// Unsigned thinking should be removed entirely
|
|
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
|
if len(parts) != 1 {
|
|
t.Fatalf("Expected 1 part (thinking removed), got %d", len(parts))
|
|
}
|
|
|
|
// Only text part should remain
|
|
if parts[0].Get("thought").Bool() {
|
|
t.Error("Thinking block should be removed, not preserved")
|
|
}
|
|
if parts[0].Get("text").String() != "Answer" {
|
|
t.Errorf("Expected text 'Answer', got '%s'", parts[0].Get("text").String())
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tool + Thinking System Hint Injection
|
|
// ============================================================================
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolAndThinking_HintInjected(t *testing.T) {
|
|
// When both tools and thinking are enabled, hint should be injected into system instruction
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}],
|
|
"system": [{"type": "text", "text": "You are helpful."}],
|
|
"tools": [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get weather",
|
|
"input_schema": {"type": "object", "properties": {"location": {"type": "string"}}}
|
|
}
|
|
],
|
|
"thinking": {"type": "enabled", "budget_tokens": 8000}
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// System instruction should contain the interleaved thinking hint
|
|
sysInstruction := gjson.Get(outputStr, "request.systemInstruction")
|
|
if !sysInstruction.Exists() {
|
|
t.Fatal("systemInstruction should exist")
|
|
}
|
|
|
|
// Check if hint is appended
|
|
sysText := sysInstruction.Get("parts").Array()
|
|
found := false
|
|
for _, part := range sysText {
|
|
if strings.Contains(part.Get("text").String(), "Interleaved thinking is enabled") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Interleaved thinking hint should be injected when tools and thinking are both active, got: %v", sysInstruction.Raw)
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolsOnly_NoHint(t *testing.T) {
|
|
// When only tools are present (no thinking), hint should NOT be injected
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5",
|
|
"messages": [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}],
|
|
"system": [{"type": "text", "text": "You are helpful."}],
|
|
"tools": [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get weather",
|
|
"input_schema": {"type": "object", "properties": {"location": {"type": "string"}}}
|
|
}
|
|
]
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// System instruction should NOT contain the hint
|
|
sysInstruction := gjson.Get(outputStr, "request.systemInstruction")
|
|
if sysInstruction.Exists() {
|
|
for _, part := range sysInstruction.Get("parts").Array() {
|
|
if strings.Contains(part.Get("text").String(), "Interleaved thinking is enabled") {
|
|
t.Error("Hint should NOT be injected when only tools are present (no thinking)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ThinkingOnly_NoHint(t *testing.T) {
|
|
// When only thinking is enabled (no tools), hint should NOT be injected
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}],
|
|
"system": [{"type": "text", "text": "You are helpful."}],
|
|
"thinking": {"type": "enabled", "budget_tokens": 8000}
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// System instruction should NOT contain the hint (no tools)
|
|
sysInstruction := gjson.Get(outputStr, "request.systemInstruction")
|
|
if sysInstruction.Exists() {
|
|
for _, part := range sysInstruction.Get("parts").Array() {
|
|
if strings.Contains(part.Get("text").String(), "Interleaved thinking is enabled") {
|
|
t.Error("Hint should NOT be injected when only thinking is present (no tools)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConvertClaudeRequestToAntigravity_ToolAndThinking_NoExistingSystem(t *testing.T) {
|
|
// When tools + thinking but no system instruction, should create one with hint
|
|
inputJSON := []byte(`{
|
|
"model": "claude-sonnet-4-5-thinking",
|
|
"messages": [{"role": "user", "content": [{"type": "text", "text": "Hello"}]}],
|
|
"tools": [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get weather",
|
|
"input_schema": {"type": "object", "properties": {"location": {"type": "string"}}}
|
|
}
|
|
],
|
|
"thinking": {"type": "enabled", "budget_tokens": 8000}
|
|
}`)
|
|
|
|
output := ConvertClaudeRequestToAntigravity("claude-sonnet-4-5-thinking", inputJSON, false)
|
|
outputStr := string(output)
|
|
|
|
// System instruction should be created with hint
|
|
sysInstruction := gjson.Get(outputStr, "request.systemInstruction")
|
|
if !sysInstruction.Exists() {
|
|
t.Fatal("systemInstruction should be created when tools + thinking are active")
|
|
}
|
|
|
|
sysText := sysInstruction.Get("parts").Array()
|
|
found := false
|
|
for _, part := range sysText {
|
|
if strings.Contains(part.Get("text").String(), "Interleaved thinking is enabled") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Interleaved thinking hint should be in created systemInstruction, got: %v", sysInstruction.Raw)
|
|
}
|
|
}
|