yao/sandbox/proxy/types.go
Max ea8be6c287 Add secure proxy configuration with options and secrets support
- Implement secure config file location (/tmp/.yao/proxy.json) instead of user-visible /workspace/
- Add generic options map support for backend-specific parameters (e.g., thinking for Volcengine GLM-4.7)
- Add secrets support for passing sensitive env vars (e.g., GITHUB_TOKEN) to sandbox container
- Remove excessive debug logs, keep critical ones with log.Printf("[Sandbox]...")
- Fix test assertions for system prompt passing via CLI args instead of env var
- Update i18n messages for sandbox loading states

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-31 22:46:48 +08:00

293 lines
9.6 KiB
Go

package proxy
import "encoding/json"
// ============================================
// Anthropic API Types
// ============================================
// AnthropicRequest represents a request to the Anthropic Messages API
type AnthropicRequest struct {
Model string `json:"model"`
Messages []AnthropicMsg `json:"messages"`
System interface{} `json:"system,omitempty"` // string or []SystemBlock
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
TopK *int `json:"top_k,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
Tools []AnthropicTool `json:"tools,omitempty"`
ToolChoice *AnthropicToolChoice `json:"tool_choice,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// AnthropicMsg represents a message in Anthropic format
type AnthropicMsg struct {
Role string `json:"role"`
Content interface{} `json:"content"` // string or []ContentBlock
}
// ContentBlock represents a content block in Anthropic messages
type ContentBlock struct {
Type string `json:"type"`
// For text blocks
Text string `json:"text,omitempty"`
// For image blocks
Source *ImageSource `json:"source,omitempty"`
// For tool_use blocks
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input interface{} `json:"input,omitempty"`
// For tool_result blocks
ToolUseID string `json:"tool_use_id,omitempty"`
Content interface{} `json:"content,omitempty"` // string or []ContentBlock
IsError bool `json:"is_error,omitempty"`
}
// ImageSource represents an image source in Anthropic format
type ImageSource struct {
Type string `json:"type"` // "base64" or "url"
MediaType string `json:"media_type,omitempty"` // e.g., "image/jpeg"
Data string `json:"data,omitempty"` // base64 encoded data
URL string `json:"url,omitempty"` // URL for url type
}
// SystemBlock represents a system message block
type SystemBlock struct {
Type string `json:"type"`
Text string `json:"text"`
}
// AnthropicTool represents a tool definition in Anthropic format
type AnthropicTool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema interface{} `json:"input_schema"`
}
// AnthropicToolChoice represents tool choice in Anthropic format
type AnthropicToolChoice struct {
Type string `json:"type"` // "auto", "any", "tool"
Name string `json:"name,omitempty"`
}
// AnthropicResponse represents a response from the Anthropic Messages API
type AnthropicResponse struct {
ID string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content []ContentBlock `json:"content"`
Model string `json:"model"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence,omitempty"`
Usage *Usage `json:"usage"`
}
// Usage represents token usage statistics
type Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
}
// AnthropicStreamEvent represents an SSE event in Anthropic format
type AnthropicStreamEvent struct {
Type string `json:"type"`
Index int `json:"index,omitempty"`
Message *AnthropicResponse `json:"message,omitempty"`
ContentBlock *ContentBlock `json:"content_block,omitempty"`
Delta *DeltaContent `json:"delta,omitempty"`
Usage *Usage `json:"usage,omitempty"`
}
// DeltaContent represents delta content in streaming
type DeltaContent struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
PartialJSON string `json:"partial_json,omitempty"`
StopReason *string `json:"stop_reason,omitempty"`
}
// ============================================
// OpenAI API Types
// ============================================
// OpenAIRequest represents a request to OpenAI Chat Completions API
type OpenAIRequest struct {
Model string `json:"model"`
Messages []OpenAIMsg `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Stream bool `json:"stream,omitempty"`
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
Stop []string `json:"stop,omitempty"`
Tools []OpenAITool `json:"tools,omitempty"`
ToolChoice interface{} `json:"tool_choice,omitempty"` // "auto", "none", "required", or object
// Extra options for backend-specific parameters (e.g., thinking for GLM-4)
// These are merged into the final JSON request
ExtraOptions map[string]interface{} `json:"-"`
}
// MarshalJSON custom marshaler to merge ExtraOptions into the request
func (r OpenAIRequest) MarshalJSON() ([]byte, error) {
// Create a map with standard fields
m := map[string]interface{}{
"model": r.Model,
"messages": r.Messages,
}
if r.MaxTokens > 0 {
m["max_tokens"] = r.MaxTokens
}
if r.Stream {
m["stream"] = r.Stream
}
if r.StreamOptions != nil {
m["stream_options"] = r.StreamOptions
}
if r.Temperature != nil {
m["temperature"] = *r.Temperature
}
if r.TopP != nil {
m["top_p"] = *r.TopP
}
if len(r.Stop) > 0 {
m["stop"] = r.Stop
}
if len(r.Tools) > 0 {
m["tools"] = r.Tools
}
if r.ToolChoice != nil {
m["tool_choice"] = r.ToolChoice
}
// Merge extra options (backend-specific parameters like thinking, etc.)
for k, v := range r.ExtraOptions {
// Don't override standard fields
if _, exists := m[k]; !exists {
m[k] = v
}
}
return json.Marshal(m)
}
// StreamOptions represents stream options in OpenAI format
type StreamOptions struct {
IncludeUsage bool `json:"include_usage"`
}
// OpenAIMsg represents a message in OpenAI format
type OpenAIMsg struct {
Role string `json:"role"`
Content interface{} `json:"content,omitempty"` // string or []OpenAIContent
ToolCalls []OpenAIToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
Name string `json:"name,omitempty"`
}
// OpenAIContent represents content in OpenAI messages (for multimodal)
type OpenAIContent struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
ImageURL *OpenAIImageURL `json:"image_url,omitempty"`
}
// OpenAIImageURL represents an image URL in OpenAI format
type OpenAIImageURL struct {
URL string `json:"url"`
Detail string `json:"detail,omitempty"` // "auto", "low", "high"
}
// OpenAITool represents a tool definition in OpenAI format
type OpenAITool struct {
Type string `json:"type"`
Function OpenAIFunction `json:"function"`
}
// OpenAIFunction represents a function in OpenAI tool
type OpenAIFunction struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters interface{} `json:"parameters"`
}
// OpenAIToolCall represents a tool call in OpenAI format
type OpenAIToolCall struct {
ID string `json:"id"`
Type string `json:"type"`
Function OpenAIFunctionCall `json:"function"`
Index *int `json:"index,omitempty"` // For streaming
}
// OpenAIFunctionCall represents a function call in OpenAI format
type OpenAIFunctionCall struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
// OpenAIResponse represents a response from OpenAI Chat Completions API
type OpenAIResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []OpenAIChoice `json:"choices"`
Usage *OpenAIUsage `json:"usage,omitempty"`
}
// OpenAIChoice represents a choice in OpenAI response
type OpenAIChoice struct {
Index int `json:"index"`
Message OpenAIMsg `json:"message"`
FinishReason string `json:"finish_reason"`
}
// OpenAIUsage represents usage statistics in OpenAI format
type OpenAIUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
// OpenAIStreamChunk represents a streaming chunk from OpenAI
type OpenAIStreamChunk struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []OpenAIStreamChoice `json:"choices"`
Usage *OpenAIUsage `json:"usage,omitempty"`
}
// OpenAIStreamChoice represents a choice in OpenAI streaming response
type OpenAIStreamChoice struct {
Index int `json:"index"`
Delta OpenAIStreamDelta `json:"delta"`
FinishReason string `json:"finish_reason,omitempty"`
}
// OpenAIStreamDelta represents delta content in OpenAI streaming
type OpenAIStreamDelta struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
ToolCalls []OpenAIToolCall `json:"tool_calls,omitempty"`
}
// ============================================
// Internal Types
// ============================================
// ToolCallAccumulator accumulates tool call data during streaming
type ToolCallAccumulator struct {
Index int
ID string
Name string
Args string
}