Proxy translating OpenAI/Anthropic Messages API to OpenCode Zen's free models. Pure stdlib Go 1.22, no external dependencies. - OpenAI Chat Completions (/v1/chat/completions) with streaming - Anthropic Messages API (/v1/messages) with streaming - Model aliases for 5 free models - Auth middleware support - Session rotation (30min per user key)
144 lines
3.5 KiB
Go
144 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const defaultAPIKey = "opx-57ffcb013651f7acae558cd9ab094dbd719fdec980a6ad53"
|
|
|
|
type visionRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []visionMessage `json:"messages"`
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type visionMessage struct {
|
|
Role string `json:"role"`
|
|
Content interface{} `json:"content"`
|
|
}
|
|
|
|
type visionResponse struct {
|
|
ID string `json:"id"`
|
|
Object string `json:"object"`
|
|
Created int64 `json:"created"`
|
|
Model string `json:"model"`
|
|
Choices []visionChoice `json:"choices"`
|
|
}
|
|
|
|
type visionChoice struct {
|
|
Index int `json:"index"`
|
|
Message visionContent `json:"message"`
|
|
FinishReason string `json:"finish_reason"`
|
|
}
|
|
|
|
type visionContent struct {
|
|
Role string `json:"role"`
|
|
Content interface{} `json:"content"`
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Fprintf(os.Stderr, "Usage: vision <image-path> [prompt]\n")
|
|
fmt.Fprintf(os.Stderr, "Example: vision C:\\path\\to\\photo.jpg \"Describe this image\"\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
imagePath := os.Args[1]
|
|
prompt := "Describe esta imagen en detalle."
|
|
if len(os.Args) > 2 {
|
|
prompt = strings.Join(os.Args[2:], " ")
|
|
}
|
|
|
|
ext := strings.ToLower(filepath.Ext(imagePath))
|
|
var mediaType string
|
|
switch ext {
|
|
case ".jpg", ".jpeg":
|
|
mediaType = "image/jpeg"
|
|
case ".png":
|
|
mediaType = "image/png"
|
|
case ".webp":
|
|
mediaType = "image/webp"
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Unsupported image format: %s (supported: jpg, png, webp)\n", ext)
|
|
os.Exit(1)
|
|
}
|
|
|
|
data, err := os.ReadFile(imagePath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
b64 := base64.StdEncoding.EncodeToString(data)
|
|
dataURL := fmt.Sprintf("data:%s;base64,%s", mediaType, b64)
|
|
|
|
reqBody := visionRequest{
|
|
Model: "mimo-v2.5-free",
|
|
Messages: []visionMessage{
|
|
{
|
|
Role: "user",
|
|
Content: []map[string]interface{}{
|
|
{"type": "text", "text": prompt},
|
|
{"type": "image_url", "image_url": map[string]string{"url": dataURL}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
|
|
httpReq, err := http.NewRequest("POST", "https://opencode.ai/zen/v1/chat/completions", bytes.NewReader(body))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
httpReq.Header.Set("Authorization", "Bearer "+defaultAPIKey)
|
|
httpReq.Header.Set("User-Agent", "vision-tool/1.0")
|
|
httpReq.Header.Set("x-opencode-client", "cli")
|
|
httpReq.Header.Set("x-opencode-project", "vision")
|
|
httpReq.Header.Set("x-opencode-request", fmt.Sprintf("req_%d", time.Now().UnixNano()))
|
|
httpReq.Header.Set("x-opencode-session", fmt.Sprintf("ses_%d", time.Now().UnixNano()))
|
|
|
|
client := &http.Client{Timeout: 2 * time.Minute}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respData, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode != 200 {
|
|
fmt.Fprintf(os.Stderr, "API error %d: %s\n", resp.StatusCode, string(respData))
|
|
os.Exit(1)
|
|
}
|
|
|
|
var result visionResponse
|
|
if err := json.Unmarshal(respData, &result); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n%s\n", err, string(respData))
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(result.Choices) > 0 {
|
|
switch c := result.Choices[0].Message.Content.(type) {
|
|
case string:
|
|
fmt.Println(c)
|
|
default:
|
|
b, _ := json.MarshalIndent(c, "", " ")
|
|
fmt.Println(string(b))
|
|
}
|
|
}
|
|
}
|