mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
package openai
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"ds2api/internal/auth"
|
|
"ds2api/internal/chathistory"
|
|
"ds2api/internal/httpapi/openai/chat"
|
|
"ds2api/internal/httpapi/openai/embeddings"
|
|
"ds2api/internal/httpapi/openai/files"
|
|
"ds2api/internal/httpapi/openai/history"
|
|
"ds2api/internal/httpapi/openai/responses"
|
|
"ds2api/internal/httpapi/openai/shared"
|
|
"ds2api/internal/promptcompat"
|
|
)
|
|
|
|
type openAITestSurface struct {
|
|
Store shared.ConfigReader
|
|
Auth shared.AuthResolver
|
|
DS shared.DeepSeekCaller
|
|
ChatHistory *chathistory.Store
|
|
|
|
chat *chat.Handler
|
|
responses *responses.Handler
|
|
files *files.Handler
|
|
embeddings *embeddings.Handler
|
|
models *shared.ModelsHandler
|
|
}
|
|
|
|
func (h *openAITestSurface) deps() shared.Deps {
|
|
if h == nil {
|
|
return shared.Deps{}
|
|
}
|
|
return shared.Deps{Store: h.Store, Auth: h.Auth, DS: h.DS, ChatHistory: h.ChatHistory}
|
|
}
|
|
|
|
func (h *openAITestSurface) chatHandler() *chat.Handler {
|
|
if h.chat == nil {
|
|
deps := h.deps()
|
|
h.chat = &chat.Handler{Store: deps.Store, Auth: deps.Auth, DS: deps.DS, ChatHistory: deps.ChatHistory}
|
|
}
|
|
return h.chat
|
|
}
|
|
|
|
func (h *openAITestSurface) responsesHandler() *responses.Handler {
|
|
if h.responses == nil {
|
|
deps := h.deps()
|
|
h.responses = &responses.Handler{Store: deps.Store, Auth: deps.Auth, DS: deps.DS, ChatHistory: deps.ChatHistory}
|
|
}
|
|
return h.responses
|
|
}
|
|
|
|
func (h *openAITestSurface) filesHandler() *files.Handler {
|
|
if h.files == nil {
|
|
deps := h.deps()
|
|
h.files = &files.Handler{Store: deps.Store, Auth: deps.Auth, DS: deps.DS, ChatHistory: deps.ChatHistory}
|
|
}
|
|
return h.files
|
|
}
|
|
|
|
func (h *openAITestSurface) embeddingsHandler() *embeddings.Handler {
|
|
if h.embeddings == nil {
|
|
deps := h.deps()
|
|
h.embeddings = &embeddings.Handler{Store: deps.Store, Auth: deps.Auth, DS: deps.DS, ChatHistory: deps.ChatHistory}
|
|
}
|
|
return h.embeddings
|
|
}
|
|
|
|
func (h *openAITestSurface) modelsHandler() *shared.ModelsHandler {
|
|
if h.models == nil {
|
|
h.models = &shared.ModelsHandler{Store: h.Store}
|
|
}
|
|
return h.models
|
|
}
|
|
|
|
func (h *openAITestSurface) ChatCompletions(w http.ResponseWriter, r *http.Request) {
|
|
h.chatHandler().ChatCompletions(w, r)
|
|
}
|
|
|
|
func (h *openAITestSurface) applyCurrentInputFile(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
|
stdReq = shared.ApplyThinkingInjection(h.Store, stdReq)
|
|
svc := history.Service{Store: h.Store, DS: h.DS}
|
|
out, err := svc.ApplyCurrentInputFile(ctx, a, stdReq)
|
|
if err != nil || out.CurrentInputFileApplied {
|
|
return out, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (h *openAITestSurface) preprocessInlineFileInputs(ctx context.Context, a *auth.RequestAuth, req map[string]any) error {
|
|
return h.filesHandler().PreprocessInlineFileInputs(ctx, a, req)
|
|
}
|
|
|
|
func registerOpenAITestRoutes(r chi.Router, h *openAITestSurface) {
|
|
r.Get("/v1/models", h.modelsHandler().ListModels)
|
|
r.Get("/v1/models/{model_id}", h.modelsHandler().GetModel)
|
|
r.Post("/v1/chat/completions", h.chatHandler().ChatCompletions)
|
|
r.Post("/v1/responses", h.responsesHandler().Responses)
|
|
r.Get("/v1/responses/{response_id}", h.responsesHandler().GetResponseByID)
|
|
r.Post("/v1/files", h.filesHandler().UploadFile)
|
|
r.Get("/v1/files/{file_id}", h.filesHandler().RetrieveFile)
|
|
r.Post("/v1/embeddings", h.embeddingsHandler().Embeddings)
|
|
}
|
|
|
|
func buildOpenAICurrentInputContextTranscript(messages []any) string {
|
|
return promptcompat.BuildOpenAICurrentInputContextTranscript(messages)
|
|
}
|
|
|
|
func writeOpenAIError(w http.ResponseWriter, status int, message string) {
|
|
shared.WriteOpenAIError(w, status, message)
|
|
}
|
|
|
|
func replaceCitationMarkersWithLinks(text string, links map[int]string) string {
|
|
return shared.ReplaceCitationMarkersWithLinks(text, links)
|
|
}
|
|
|
|
func sanitizeLeakedOutput(text string) string {
|
|
return shared.CleanVisibleOutput(text, false)
|
|
}
|
|
|
|
func requestTraceID(r *http.Request) string {
|
|
return shared.RequestTraceID(r)
|
|
}
|
|
|
|
func asString(v any) string {
|
|
return shared.AsString(v)
|
|
}
|
|
|
|
func parseSSEDataFrames(t *testing.T, body string) ([]map[string]any, bool) {
|
|
t.Helper()
|
|
lines := strings.Split(body, "\n")
|
|
frames := make([]map[string]any, 0, len(lines))
|
|
done := false
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "data:") {
|
|
continue
|
|
}
|
|
payload := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
|
|
if payload == "" {
|
|
continue
|
|
}
|
|
if payload == "[DONE]" {
|
|
done = true
|
|
continue
|
|
}
|
|
var frame map[string]any
|
|
if err := json.Unmarshal([]byte(payload), &frame); err != nil {
|
|
t.Fatalf("decode sse frame failed: %v, payload=%s", err, payload)
|
|
}
|
|
frames = append(frames, frame)
|
|
}
|
|
return frames, done
|
|
}
|