forked from templates/template-go-orm
Go-сервис-прокси между Codex CLI и Ollama. Добавляет Bearer-авторизацию, LLM-маршрутизатор (deepseek классифицирует запросы: code/doc/general), поддержку OpenAI Responses API для Codex CLI, стриминг SSE, кеш модели. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
2.9 KiB
Go
108 lines
2.9 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
)
|
|
|
|
// ResponsesRequest — запрос к OpenAI Responses API (/responses или /v1/responses)
|
|
type ResponsesRequest struct {
|
|
Model string `json:"model"`
|
|
Input json.RawMessage `json:"input"` // string или []ResponsesInputMessage
|
|
Instructions string `json:"instructions,omitempty"`
|
|
Stream *bool `json:"stream,omitempty"`
|
|
}
|
|
|
|
// ResponsesInputMessage — сообщение в формате Responses API
|
|
// content может быть строкой или массивом [{type: "input_text", text: "..."}]
|
|
type ResponsesInputMessage struct {
|
|
Type string `json:"type,omitempty"` // "message"
|
|
Role string `json:"role"`
|
|
Content json.RawMessage `json:"content"`
|
|
}
|
|
|
|
// ContentPart — элемент массива content в Responses API
|
|
type ContentPart struct {
|
|
Type string `json:"type"` // "input_text", "output_text", etc.
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
// ToMessages конвертирует Input + Instructions в []Message для Ollama
|
|
func (r *ResponsesRequest) ToMessages() []Message {
|
|
var messages []Message
|
|
|
|
// Instructions → system message
|
|
if r.Instructions != "" {
|
|
messages = append(messages, Message{Role: "system", Content: r.Instructions})
|
|
}
|
|
|
|
if len(r.Input) == 0 {
|
|
return messages
|
|
}
|
|
|
|
log.Printf("responses: raw input: %s", string(r.Input))
|
|
|
|
// Попробовать как строку
|
|
var inputStr string
|
|
if err := json.Unmarshal(r.Input, &inputStr); err == nil {
|
|
messages = append(messages, Message{Role: "user", Content: inputStr})
|
|
return messages
|
|
}
|
|
|
|
// Попробовать как массив ResponsesInputMessage (формат Codex CLI)
|
|
var inputMsgs []ResponsesInputMessage
|
|
if err := json.Unmarshal(r.Input, &inputMsgs); err == nil {
|
|
for _, msg := range inputMsgs {
|
|
content := extractContent(msg.Content)
|
|
role := msg.Role
|
|
if role == "" {
|
|
role = "user"
|
|
}
|
|
if content != "" {
|
|
messages = append(messages, Message{Role: role, Content: content})
|
|
}
|
|
}
|
|
if len(messages) > 0 {
|
|
return messages
|
|
}
|
|
}
|
|
|
|
// Попробовать как простой массив Message (совместимость)
|
|
var simpleMsgs []Message
|
|
if err := json.Unmarshal(r.Input, &simpleMsgs); err == nil {
|
|
messages = append(messages, simpleMsgs...)
|
|
}
|
|
|
|
return messages
|
|
}
|
|
|
|
// extractContent извлекает текст из content, которое может быть строкой или массивом ContentPart
|
|
func extractContent(raw json.RawMessage) string {
|
|
if len(raw) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// Как строку
|
|
var s string
|
|
if err := json.Unmarshal(raw, &s); err == nil {
|
|
return s
|
|
}
|
|
|
|
// Как массив ContentPart
|
|
var parts []ContentPart
|
|
if err := json.Unmarshal(raw, &parts); err == nil {
|
|
var text string
|
|
for _, p := range parts {
|
|
if p.Text != "" {
|
|
if text != "" {
|
|
text += "\n"
|
|
}
|
|
text += p.Text
|
|
}
|
|
}
|
|
return text
|
|
}
|
|
|
|
return ""
|
|
}
|