From 42ff9655072aecbe2035f96de3ac9d0672d2b011 Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Fri, 26 Jun 2026 08:21:47 +0100 Subject: [PATCH] feat: add Requesty adapter (OpenAI-compatible) Signed-off-by: Thibault Jaigu --- README.md | 2 + cmd/model_validation.go | 4 + examples/requesty-conversation.yaml | 53 ++++ examples/requesty-solo.yaml | 36 +++ internal/providers/providers.json | 53 +++- pkg/adapters/requesty.go | 315 ++++++++++++++++++++++++ pkg/adapters/requesty_test.go | 363 ++++++++++++++++++++++++++++ 7 files changed, 825 insertions(+), 1 deletion(-) create mode 100644 examples/requesty-conversation.yaml create mode 100644 examples/requesty-solo.yaml create mode 100644 pkg/adapters/requesty.go create mode 100644 pkg/adapters/requesty_test.go diff --git a/README.md b/README.md index 815f998..fc2a431 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ All agents now use a **standardized interaction pattern** with structured three- - โœ… **Kimi** (Moonshot AI) - Interactive AI agent with advanced reasoning (interactive-first CLI) - โœ… **OpenCode** (SST) - AI coding agent built for the terminal (non-interactive run mode) - โœ… **OpenRouter** - Unified API access to 400+ models from multiple providers (API-based, no CLI required) ๐ŸŒ **API-based** +- โœ… **Requesty** - OpenAI-compatible gateway with access to 400+ models from multiple providers (API-based, no CLI required) ๐ŸŒ **API-based** - โœ… **Qoder** - Agentic coding platform with enhanced context engineering - โœ… **Qwen** (Alibaba) - Multilingual capabilities - โœ… **Ollama** - Local LLM support (planned) @@ -317,6 +318,7 @@ AgentPipe supports three formats for specifying agents via the `--agents` / `-a` | `groq` | โœ… Optional | No | `llama3-70b`, `mixtral-8x7b` | | `crush` | โœ… Optional | No | `deepseek-r1`, `qwen-2.5` | | `openrouter` | โœ… **Required** | Yes | `anthropic/claude-sonnet-4-5`, `google/gemini-2.5-pro` | +| `requesty` | โœ… **Required** | Yes | `openai/gpt-4o-mini`, `anthropic/claude-sonnet-4-5` | | `kimi` | โŒ Not supported | No | N/A | | `cursor` | โŒ Not supported | No | N/A | | `amp` | โŒ Not supported | No | N/A | diff --git a/cmd/model_validation.go b/cmd/model_validation.go index c1e54ef..eaaa24d 100644 --- a/cmd/model_validation.go +++ b/cmd/model_validation.go @@ -59,6 +59,10 @@ var agentModelSupport = map[string]ModelSupport{ Supported: true, Required: true, }, + "requesty": { + Supported: true, + Required: true, + }, // CLI agents without --model support "kimi": { diff --git a/examples/requesty-conversation.yaml b/examples/requesty-conversation.yaml new file mode 100644 index 0000000..fcdbdda --- /dev/null +++ b/examples/requesty-conversation.yaml @@ -0,0 +1,53 @@ +version: "1.0" + +# Requesty Multi-Agent Conversation Example +# +# This example demonstrates using Requesty's unified, OpenAI-compatible API to +# access multiple AI models from different providers in a single conversation. +# +# Requirements: +# - Set REQUESTY_API_KEY environment variable +# - Requesty account with credits (https://requesty.ai) +# +# Run with: +# export REQUESTY_API_KEY=your-key-here +# agentpipe run -c examples/requesty-conversation.yaml + +agents: + - id: claude-via-requesty + type: requesty + name: "Claude (Requesty)" + prompt: "You are Claude, an AI assistant created by Anthropic. You are thoughtful, analytical, and enjoy deep discussions." + announcement: "๐Ÿค– Claude has joined via Requesty API!" + model: anthropic/claude-sonnet-4-5 + temperature: 0.7 + max_tokens: 500 + + - id: gemini-via-requesty + type: requesty + name: "Gemini (Requesty)" + prompt: "You are Gemini, Google's AI assistant. You are curious, factual, and love to explore new ideas." + announcement: "โœจ Gemini has joined via Requesty API!" + model: google/gemini-2.5-flash + temperature: 0.6 + max_tokens: 500 + + - id: gpt-via-requesty + type: requesty + name: "GPT (Requesty)" + prompt: "You are GPT, OpenAI's language model. You are helpful, creative, and enjoy collaborative problem-solving." + announcement: "๐Ÿง  GPT has joined via Requesty API!" + model: openai/gpt-4o-mini + temperature: 0.8 + max_tokens: 500 + +orchestrator: + mode: round-robin + max_turns: 8 + turn_timeout: 30s + response_delay: 2s + initial_prompt: "Let's have a discussion about the future of AI and its impact on society. Each of you represents a different AI platform - what unique perspectives do you bring to this topic?" + +logging: + enabled: true + show_metrics: true diff --git a/examples/requesty-solo.yaml b/examples/requesty-solo.yaml new file mode 100644 index 0000000..897f609 --- /dev/null +++ b/examples/requesty-solo.yaml @@ -0,0 +1,36 @@ +version: "1.0" + +# Requesty Solo Agent Example +# +# This example shows how to use a single Requesty agent for a conversation. +# This is useful for testing or when you want to use a specific model via API +# without installing its CLI tool. +# +# Requirements: +# - Set REQUESTY_API_KEY environment variable +# - Requesty account with credits (https://requesty.ai) +# +# Run with: +# export REQUESTY_API_KEY=your-key-here +# agentpipe run -c examples/requesty-solo.yaml + +agents: + - id: gpt-via-requesty + type: requesty + name: "GPT (Requesty)" + prompt: "You are a powerful reasoning assistant. When solving problems, break down your thinking step by step and show your reasoning process clearly." + announcement: "๐Ÿงช GPT has joined via Requesty API!" + model: openai/gpt-4o-mini + temperature: 0.5 + max_tokens: 2000 + +orchestrator: + mode: round-robin + max_turns: 3 + turn_timeout: 60s + response_delay: 1s + initial_prompt: "Solve this logic puzzle: You have 12 balls that look identical. One ball is slightly heavier or lighter than the others. Using a balance scale only 3 times, how can you identify which ball is different and whether it's heavier or lighter?" + +logging: + enabled: true + show_metrics: true diff --git a/internal/providers/providers.json b/internal/providers/providers.json index 78399f0..f94237b 100644 --- a/internal/providers/providers.json +++ b/internal/providers/providers.json @@ -3458,6 +3458,57 @@ } ] }, + { + "name": "Requesty", + "id": "requesty", + "type": "openai", + "api_key": "$REQUESTY_API_KEY", + "api_endpoint": "https://router.requesty.ai/v1", + "default_large_model_id": "anthropic/claude-sonnet-4-5", + "default_small_model_id": "openai/gpt-4o-mini", + "models": [ + { + "id": "openai/gpt-4o-mini", + "name": "OpenAI: GPT-4o mini", + "cost_per_1m_in": 0.15, + "cost_per_1m_out": 0.6, + "context_window": 128000, + "default_max_tokens": 4096, + "can_reason": false, + "supports_attachments": true + }, + { + "id": "openai/gpt-4o", + "name": "OpenAI: GPT-4o", + "cost_per_1m_in": 2.5, + "cost_per_1m_out": 10, + "context_window": 128000, + "default_max_tokens": 4096, + "can_reason": false, + "supports_attachments": true + }, + { + "id": "anthropic/claude-sonnet-4-5", + "name": "Anthropic: Claude Sonnet 4.5", + "cost_per_1m_in": 3, + "cost_per_1m_out": 15, + "context_window": 200000, + "default_max_tokens": 8192, + "can_reason": true, + "supports_attachments": true + }, + { + "id": "google/gemini-2.5-flash", + "name": "Google: Gemini 2.5 Flash", + "cost_per_1m_in": 0.3, + "cost_per_1m_out": 2.5, + "context_window": 1048576, + "default_max_tokens": 8192, + "can_reason": true, + "supports_attachments": true + } + ] + }, { "name": "Venice AI", "id": "venice", @@ -3663,4 +3714,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/pkg/adapters/requesty.go b/pkg/adapters/requesty.go new file mode 100644 index 0000000..47dde13 --- /dev/null +++ b/pkg/adapters/requesty.go @@ -0,0 +1,315 @@ +package adapters + +import ( + "context" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/kevinelliott/agentpipe/internal/providers" + "github.com/kevinelliott/agentpipe/pkg/agent" + "github.com/kevinelliott/agentpipe/pkg/client" + "github.com/kevinelliott/agentpipe/pkg/log" + "github.com/kevinelliott/agentpipe/pkg/utils" +) + +// RequestyAgent is an API-based agent that uses Requesty's unified API. +type RequestyAgent struct { + agent.BaseAgent + client *client.OpenAICompatClient + apiKey string +} + +// NewRequestyAgent creates a new Requesty agent instance. +func NewRequestyAgent() agent.Agent { + return &RequestyAgent{} +} + +// Initialize configures the Requesty agent with the provided configuration. +func (r *RequestyAgent) Initialize(config agent.AgentConfig) error { + if err := r.BaseAgent.Initialize(config); err != nil { + log.WithFields(map[string]interface{}{ + "agent_id": config.ID, + "agent_name": config.Name, + }).WithError(err).Error("requesty agent base initialization failed") + return err + } + + // Get API key from environment + apiKey := os.Getenv("REQUESTY_API_KEY") + if apiKey == "" { + log.WithFields(map[string]interface{}{ + "agent_id": r.ID, + "agent_name": r.Name, + }).Error("REQUESTY_API_KEY environment variable not set") + return fmt.Errorf("REQUESTY_API_KEY environment variable is required") + } + r.apiKey = apiKey + + // Validate model is configured + if r.Config.Model == "" { + log.WithFields(map[string]interface{}{ + "agent_id": r.ID, + "agent_name": r.Name, + }).Error("model not specified in configuration") + return fmt.Errorf("model must be specified for Requesty agent") + } + + // Verify model exists in provider registry + registry := providers.GetRegistry() + modelInfo, provider, err := registry.GetModel(r.Config.Model) + if err != nil { + log.WithFields(map[string]interface{}{ + "agent_id": r.ID, + "agent_name": r.Name, + "model": r.Config.Model, + }).Warn("model not found in provider registry (cost estimates may be inaccurate)") + } else { + log.WithFields(map[string]interface{}{ + "agent_id": r.ID, + "agent_name": r.Name, + "model": modelInfo.ID, + "provider": provider.Name, + }).Debug("model found in provider registry") + } + + // Create HTTP client + r.client = client.NewOpenAICompatClient("https://router.requesty.ai/v1", apiKey) + + log.WithFields(map[string]interface{}{ + "agent_id": r.ID, + "agent_name": r.Name, + "model": r.Config.Model, + }).Info("requesty agent initialized successfully") + + return nil +} + +// IsAvailable checks if the Requesty API is available (API key is set). +func (r *RequestyAgent) IsAvailable() bool { + return os.Getenv("REQUESTY_API_KEY") != "" +} + +// GetCLIVersion returns a version string indicating this is an API-based agent. +func (r *RequestyAgent) GetCLIVersion() string { + return "N/A (API)" +} + +// HealthCheck performs a health check by making a test API request. +func (r *RequestyAgent) HealthCheck(ctx context.Context) error { + if r.client == nil { + log.WithField("agent_name", r.Name).Error("requesty health check failed: not initialized") + return fmt.Errorf("requesty agent not initialized") + } + + log.WithField("agent_name", r.Name).Debug("starting requesty health check") + + // Make a minimal test request + req := client.ChatCompletionRequest{ + Model: r.Config.Model, + Messages: []client.ChatCompletionMessage{ + {Role: "user", Content: "test"}, + }, + MaxTokens: intPtr(1), + } + + _, err := r.client.CreateChatCompletion(ctx, req) + if err != nil { + log.WithField("agent_name", r.Name).WithError(err).Error("requesty health check failed") + return fmt.Errorf("requesty API health check failed: %w", err) + } + + log.WithField("agent_name", r.Name).Info("requesty health check passed") + return nil +} + +// SendMessage sends a message to Requesty and returns the response. +func (r *RequestyAgent) SendMessage(ctx context.Context, messages []agent.Message) (string, error) { + if len(messages) == 0 { + return "", nil + } + + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "message_count": len(messages), + "model": r.Config.Model, + }).Debug("sending message to requesty") + + // Build conversation history + apiMessages := r.buildConversationHistory(messages) + + // Build request + req := client.ChatCompletionRequest{ + Model: r.Config.Model, + Messages: apiMessages, + } + + if r.Config.Temperature > 0 { + req.Temperature = &r.Config.Temperature + } + + if r.Config.MaxTokens > 0 { + req.MaxTokens = &r.Config.MaxTokens + } + + // Send request + startTime := time.Now() + resp, err := r.client.CreateChatCompletion(ctx, req) + duration := time.Since(startTime) + + if err != nil { + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "duration": duration.String(), + "model": r.Config.Model, + }).WithError(err).Error("requesty request failed") + return "", fmt.Errorf("requesty request failed: %w", err) + } + + if len(resp.Choices) == 0 { + log.WithField("agent_name", r.Name).Error("requesty returned no choices") + return "", fmt.Errorf("no response from requesty") + } + + content := resp.Choices[0].Message.Content + + // Log metrics + if resp.Usage != nil { + cost := utils.EstimateCost(r.Config.Model, resp.Usage.PromptTokens, resp.Usage.CompletionTokens) + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "duration": duration.String(), + "model": resp.Model, + "prompt_tokens": resp.Usage.PromptTokens, + "completion_tokens": resp.Usage.CompletionTokens, + "total_tokens": resp.Usage.TotalTokens, + "cost": fmt.Sprintf("$%.4f", cost), + }).Info("requesty message sent successfully") + } else { + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "duration": duration.String(), + "model": resp.Model, + }).Info("requesty message sent successfully") + } + + return strings.TrimSpace(content), nil +} + +// StreamMessage sends a message to Requesty and streams the response. +func (r *RequestyAgent) StreamMessage(ctx context.Context, messages []agent.Message, writer io.Writer) error { + if len(messages) == 0 { + return nil + } + + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "message_count": len(messages), + "model": r.Config.Model, + }).Debug("starting requesty streaming message") + + // Build conversation history + apiMessages := r.buildConversationHistory(messages) + + // Build request + req := client.ChatCompletionRequest{ + Model: r.Config.Model, + Messages: apiMessages, + } + + if r.Config.Temperature > 0 { + req.Temperature = &r.Config.Temperature + } + + if r.Config.MaxTokens > 0 { + req.MaxTokens = &r.Config.MaxTokens + } + + // Send streaming request + startTime := time.Now() + usage, err := r.client.CreateChatCompletionStream(ctx, req, writer) + duration := time.Since(startTime) + + if err != nil { + log.WithField("agent_name", r.Name).WithError(err).Error("requesty streaming failed") + return fmt.Errorf("requesty streaming failed: %w", err) + } + + // Log metrics + if usage != nil { + cost := utils.EstimateCost(r.Config.Model, usage.PromptTokens, usage.CompletionTokens) + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "duration": duration.String(), + "model": r.Config.Model, + "prompt_tokens": usage.PromptTokens, + "completion_tokens": usage.CompletionTokens, + "total_tokens": usage.TotalTokens, + "cost": fmt.Sprintf("$%.4f", cost), + }).Info("requesty streaming message completed") + } else { + log.WithFields(map[string]interface{}{ + "agent_name": r.Name, + "duration": duration.String(), + "model": r.Config.Model, + }).Info("requesty streaming message completed") + } + + return nil +} + +// buildConversationHistory converts AgentPipe messages to OpenAI API format. +func (r *RequestyAgent) buildConversationHistory(messages []agent.Message) []client.ChatCompletionMessage { + apiMessages := make([]client.ChatCompletionMessage, 0) + + // Add system prompt if configured + if r.Config.Prompt != "" { + apiMessages = append(apiMessages, client.ChatCompletionMessage{ + Role: "system", + Content: r.Config.Prompt, + }) + } + + // Convert conversation messages + for _, msg := range messages { + // Skip this agent's own messages to avoid confusion + if msg.AgentName == r.Name || msg.AgentID == r.ID { + continue + } + + var role string + var content string + + switch msg.Role { + case "system": + // System messages (orchestrator prompts, announcements) + role = "user" // Most APIs don't support multiple system messages, so use user role + content = fmt.Sprintf("[System] %s", msg.Content) + + case "user": + role = "user" + content = msg.Content + + case "agent": + role = "user" // Treat other agents' messages as user messages + content = fmt.Sprintf("%s: %s", msg.AgentName, msg.Content) + + default: + // Unknown role, skip + continue + } + + apiMessages = append(apiMessages, client.ChatCompletionMessage{ + Role: role, + Content: content, + }) + } + + return apiMessages +} + +func init() { + agent.RegisterFactory("requesty", NewRequestyAgent) +} diff --git a/pkg/adapters/requesty_test.go b/pkg/adapters/requesty_test.go new file mode 100644 index 0000000..89c9c63 --- /dev/null +++ b/pkg/adapters/requesty_test.go @@ -0,0 +1,363 @@ +package adapters + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/kevinelliott/agentpipe/pkg/agent" +) + +func TestNewRequestyAgent(t *testing.T) { + a := NewRequestyAgent() + if a == nil { + t.Fatal("NewRequestyAgent returned nil") + } + + _, ok := a.(*RequestyAgent) + if !ok { + t.Error("NewRequestyAgent did not return *RequestyAgent") + } +} + +func TestRequestyAgent_Initialize(t *testing.T) { + tests := []struct { + name string + config agent.AgentConfig + apiKey string + shouldError bool + errorMsg string + }{ + { + name: "successful initialization", + config: agent.AgentConfig{ + ID: "test-1", + Type: "requesty", + Name: "Test Requesty", + Model: "anthropic/claude-sonnet-4-5", + Prompt: "You are a helpful assistant", + }, + apiKey: "test-api-key", + shouldError: false, + }, + { + name: "missing api key", + config: agent.AgentConfig{ + ID: "test-2", + Type: "requesty", + Name: "Test Requesty", + Model: "openai/gpt-4o-mini", + Prompt: "You are a helpful assistant", + }, + apiKey: "", + shouldError: true, + errorMsg: "REQUESTY_API_KEY", + }, + { + name: "missing model", + config: agent.AgentConfig{ + ID: "test-3", + Type: "requesty", + Name: "Test Requesty", + Prompt: "You are a helpful assistant", + }, + apiKey: "test-api-key", + shouldError: true, + errorMsg: "model must be specified", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up environment + if tt.apiKey != "" { + os.Setenv("REQUESTY_API_KEY", tt.apiKey) + defer os.Unsetenv("REQUESTY_API_KEY") + } else { + os.Unsetenv("REQUESTY_API_KEY") + } + + a := NewRequestyAgent() + err := a.Initialize(tt.config) + + if tt.shouldError { + if err == nil { + t.Error("Expected error, got nil") + } else if !strings.Contains(err.Error(), tt.errorMsg) { + t.Errorf("Expected error containing '%s', got: %v", tt.errorMsg, err) + } + } else { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Verify initialization + requestyAgent, ok := a.(*RequestyAgent) + if !ok { + t.Fatal("Agent is not *RequestyAgent") + } + + if requestyAgent.GetID() != tt.config.ID { + t.Errorf("Expected ID %s, got %s", tt.config.ID, requestyAgent.GetID()) + } + + if requestyAgent.GetName() != tt.config.Name { + t.Errorf("Expected Name %s, got %s", tt.config.Name, requestyAgent.GetName()) + } + + if requestyAgent.GetModel() != tt.config.Model { + t.Errorf("Expected Model %s, got %s", tt.config.Model, requestyAgent.GetModel()) + } + + if requestyAgent.client == nil { + t.Error("Expected client to be initialized, got nil") + } + } + }) + } +} + +func TestRequestyAgent_IsAvailable(t *testing.T) { + tests := []struct { + name string + apiKey string + available bool + }{ + { + name: "api key set", + apiKey: "test-api-key", + available: true, + }, + { + name: "api key not set", + apiKey: "", + available: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.apiKey != "" { + os.Setenv("REQUESTY_API_KEY", tt.apiKey) + defer os.Unsetenv("REQUESTY_API_KEY") + } else { + os.Unsetenv("REQUESTY_API_KEY") + } + + a := NewRequestyAgent() + available := a.IsAvailable() + + if available != tt.available { + t.Errorf("Expected IsAvailable() to return %v, got %v", tt.available, available) + } + }) + } +} + +func TestRequestyAgent_GetCLIVersion(t *testing.T) { + a := NewRequestyAgent() + version := a.GetCLIVersion() + + expected := "N/A (API)" + if version != expected { + t.Errorf("Expected GetCLIVersion() to return '%s', got '%s'", expected, version) + } +} + +func TestRequestyAgent_BuildConversationHistory(t *testing.T) { + // Set up environment + os.Setenv("REQUESTY_API_KEY", "test-api-key") + defer os.Unsetenv("REQUESTY_API_KEY") + + a := NewRequestyAgent() + config := agent.AgentConfig{ + ID: "test-agent", + Type: "requesty", + Name: "Test Agent", + Model: "openai/gpt-4o-mini", + Prompt: "You are a helpful assistant", + } + + if err := a.Initialize(config); err != nil { + t.Fatalf("Failed to initialize agent: %v", err) + } + + requestyAgent, ok := a.(*RequestyAgent) + if !ok { + t.Fatal("Agent is not *RequestyAgent") + } + + messages := []agent.Message{ + { + AgentID: "system", + AgentName: "System", + Role: "system", + Content: "Initial prompt: Let's discuss AI", + Timestamp: 1000, + }, + { + AgentID: "other-agent", + AgentName: "Other Agent", + Role: "agent", + Content: "AI is fascinating!", + Timestamp: 2000, + }, + { + AgentID: "test-agent", + AgentName: "Test Agent", + Role: "agent", + Content: "I agree, let's explore it", + Timestamp: 3000, + }, + { + AgentID: "user-1", + AgentName: "User", + Role: "user", + Content: "What are your thoughts?", + Timestamp: 4000, + }, + } + + apiMessages := requestyAgent.buildConversationHistory(messages) + + // Should have: + // 1. System prompt from config + // 2. System message (converted to user role) + // 3. Other agent's message (converted to user role) + // 4. Test agent's own message (skipped) + // 5. User message + // Total: 4 messages + + if len(apiMessages) != 4 { + t.Fatalf("Expected 4 API messages, got %d", len(apiMessages)) + } + + // Check first message (system prompt from config) + if apiMessages[0].Role != "system" { + t.Errorf("Expected first message role to be 'system', got '%s'", apiMessages[0].Role) + } + if apiMessages[0].Content != "You are a helpful assistant" { + t.Errorf("Expected first message to be system prompt, got: %s", apiMessages[0].Content) + } + + // Check second message (system message converted to user) + if apiMessages[1].Role != "user" { + t.Errorf("Expected second message role to be 'user', got '%s'", apiMessages[1].Role) + } + if !strings.Contains(apiMessages[1].Content, "[System]") { + t.Errorf("Expected system message to be prefixed with [System], got: %s", apiMessages[1].Content) + } + + // Check third message (other agent's message) + if apiMessages[2].Role != "user" { + t.Errorf("Expected third message role to be 'user', got '%s'", apiMessages[2].Role) + } + if !strings.Contains(apiMessages[2].Content, "Other Agent:") { + t.Errorf("Expected agent message to include agent name, got: %s", apiMessages[2].Content) + } + + // Check fourth message (actual user message) + if apiMessages[3].Role != "user" { + t.Errorf("Expected fourth message role to be 'user', got '%s'", apiMessages[3].Role) + } + if apiMessages[3].Content != "What are your thoughts?" { + t.Errorf("Expected user message content, got: %s", apiMessages[3].Content) + } +} + +func TestRequestyAgent_HealthCheck_NotInitialized(t *testing.T) { + a := NewRequestyAgent() + ctx := context.Background() + + err := a.HealthCheck(ctx) + + if err == nil { + t.Error("Expected error for uninitialized agent, got nil") + } + + if !strings.Contains(err.Error(), "not initialized") { + t.Errorf("Expected 'not initialized' error, got: %v", err) + } +} + +// Integration tests (skipped if REQUESTY_API_KEY is not set) + +func TestRequestyAgent_HealthCheck_Integration(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + apiKey := os.Getenv("REQUESTY_API_KEY") + if apiKey == "" { + t.Skip("REQUESTY_API_KEY not set, skipping integration test") + } + + a := NewRequestyAgent() + config := agent.AgentConfig{ + ID: "test-health", + Type: "requesty", + Name: "Health Check Test", + Model: "openai/gpt-4o-mini", + Prompt: "You are a test assistant", + } + + if err := a.Initialize(config); err != nil { + t.Fatalf("Failed to initialize agent: %v", err) + } + + ctx := context.Background() + err := a.HealthCheck(ctx) + + if err != nil { + t.Errorf("Health check failed: %v", err) + } +} + +func TestRequestyAgent_SendMessage_Integration(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + apiKey := os.Getenv("REQUESTY_API_KEY") + if apiKey == "" { + t.Skip("REQUESTY_API_KEY not set, skipping integration test") + } + + a := NewRequestyAgent() + config := agent.AgentConfig{ + ID: "test-send", + Type: "requesty", + Name: "Send Message Test", + Model: "openai/gpt-4o-mini", + Prompt: "You are a test assistant. Keep responses very short.", + MaxTokens: 20, + } + + if err := a.Initialize(config); err != nil { + t.Fatalf("Failed to initialize agent: %v", err) + } + + messages := []agent.Message{ + { + AgentID: "user", + AgentName: "User", + Role: "user", + Content: "Say 'test successful' and nothing else", + Timestamp: 1000, + }, + } + + ctx := context.Background() + response, err := a.SendMessage(ctx, messages) + + if err != nil { + t.Fatalf("SendMessage failed: %v", err) + } + + if response == "" { + t.Error("Expected non-empty response") + } + + t.Logf("Received response: %s", response) +}