| package main |
|
|
| import ( |
| "bufio" |
| "bytes" |
| "encoding/json" |
| "io" |
| "log" |
| "net/http" |
| "os" |
| "strings" |
|
|
| "github.com/google/uuid" |
| ) |
|
|
| const ( |
| waveAPIURL = "https://cfapi.waveterm.dev/api/waveai" |
| ) |
|
|
| type ResponsesRequest struct { |
| Model string `json:"model"` |
| Input json.RawMessage `json:"input"` |
| Instructions string `json:"instructions,omitempty"` |
| MaxOutputTokens int `json:"max_output_tokens,omitempty"` |
| Temperature float64 `json:"temperature,omitempty"` |
| TopP float64 `json:"top_p,omitempty"` |
| Stream *bool `json:"stream,omitempty"` |
| Tools json.RawMessage `json:"tools,omitempty"` |
| ToolChoice interface{} `json:"tool_choice,omitempty"` |
| Reasoning *ReasoningConfig `json:"reasoning,omitempty"` |
| Metadata map[string]interface{} `json:"metadata,omitempty"` |
| } |
|
|
| type ReasoningConfig struct { |
| Effort string `json:"effort,omitempty"` |
| Summary string `json:"summary,omitempty"` |
| } |
|
|
| type WaveRequest struct { |
| Input json.RawMessage `json:"input"` |
| Instructions string `json:"instructions,omitempty"` |
| MaxOutputTokens int `json:"max_output_tokens,omitempty"` |
| Model string `json:"model"` |
| Reasoning *ReasoningConfig `json:"reasoning,omitempty"` |
| Stream bool `json:"stream"` |
| StreamOptions *StreamOptions `json:"stream_options,omitempty"` |
| Text *TextConfig `json:"text,omitempty"` |
| Tools json.RawMessage `json:"tools,omitempty"` |
| ToolChoice interface{} `json:"tool_choice,omitempty"` |
| Temperature float64 `json:"temperature,omitempty"` |
| TopP float64 `json:"top_p,omitempty"` |
| } |
|
|
| type StreamOptions struct { |
| IncludeObfuscation bool `json:"include_obfuscation"` |
| } |
|
|
| type TextConfig struct { |
| Verbosity string `json:"verbosity,omitempty"` |
| } |
|
|
| func main() { |
| port := os.Getenv("PORT") |
| if port == "" { |
| port = "7860" |
| } |
|
|
| http.HandleFunc("/v1/responses", handleResponses) |
| http.HandleFunc("/responses", handleResponses) |
| http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { |
| w.Write([]byte(`{"status":"ok"}`)) |
| }) |
|
|
| log.Printf("Server starting on :%s", port) |
| if err := http.ListenAndServe(":"+port, nil); err != nil { |
| log.Fatalf("Server failed: %v", err) |
| } |
| } |
|
|
| func handleResponses(w http.ResponseWriter, r *http.Request) { |
| if r.Method != http.MethodPost { |
| http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
| return |
| } |
|
|
| body, _ := io.ReadAll(r.Body) |
| var req ResponsesRequest |
| json.Unmarshal(body, &req) |
|
|
| waveReq := convertToWaveRequest(&req) |
| stream := true |
| if req.Stream != nil { |
| stream = *req.Stream |
| } |
|
|
| if stream { |
| handleStreamingResponse(w, waveReq) |
| } else { |
| handleNonStreamingResponse(w, waveReq) |
| } |
| } |
|
|
| func convertToWaveRequest(req *ResponsesRequest) *WaveRequest { |
| maxTokens := req.MaxOutputTokens |
| if maxTokens == 0 { |
| maxTokens = 4096 |
| } |
|
|
| return &WaveRequest{ |
| Input: req.Input, |
| Instructions: req.Instructions, |
| MaxOutputTokens: maxTokens, |
| Model: "gpt-5.1", |
| Stream: true, |
| StreamOptions: &StreamOptions{IncludeObfuscation: false}, |
| Text: &TextConfig{Verbosity: "low"}, |
| Reasoning: &ReasoningConfig{Effort: "medium", Summary: "auto"}, |
| } |
| } |
|
|
| func makeWaveRequest(waveReq *WaveRequest) (*http.Response, error) { |
| body, _ := json.Marshal(waveReq) |
| httpReq, _ := http.NewRequest(http.MethodPost, waveAPIURL, bytes.NewReader(body)) |
|
|
| httpReq.Header.Set("Content-Type", "application/json") |
| httpReq.Header.Set("Accept", "text/event-stream") |
| httpReq.Header.Set("X-Wave-Apitype", "openai-responses") |
| httpReq.Header.Set("X-Wave-Clientid", uuid.New().String()) |
|
|
| client := &http.Client{} |
| return client.Do(httpReq) |
| } |
|
|
| func handleStreamingResponse(w http.ResponseWriter, waveReq *WaveRequest) { |
| resp, err := makeWaveRequest(waveReq) |
| if err != nil { |
| http.Error(w, err.Error(), http.StatusBadGateway) |
| return |
| } |
| defer resp.Body.Close() |
|
|
| w.Header().Set("Content-Type", "text/event-stream") |
| flusher, _ := w.(http.Flusher) |
| scanner := bufio.NewScanner(resp.Body) |
| for scanner.Scan() { |
| w.Write([]byte(scanner.Text() + "\n")) |
| flusher.Flush() |
| } |
| } |
|
|
| func handleNonStreamingResponse(w http.ResponseWriter, waveReq *WaveRequest) { |
| resp, _ := makeWaveRequest(waveReq) |
| defer resp.Body.Close() |
|
|
| var outputText strings.Builder |
| scanner := bufio.NewScanner(resp.Body) |
| for scanner.Scan() { |
| line := scanner.Text() |
| if strings.HasPrefix(line, "data: ") { |
| var event map[string]interface{} |
| json.Unmarshal([]byte(strings.TrimPrefix(line, "data: ")), &event) |
| if delta, ok := event["delta"].(string); ok { |
| outputText.WriteString(delta) |
| } |
| } |
| } |
|
|
| w.Header().Set("Content-Type", "application/json") |
| json.NewEncoder(w).Encode(map[string]interface{}{ |
| "choices": []map[string]interface{}{ |
| {"message": map[string]string{"content": outputText.String()}}, |
| }, |
| }) |
| } |