| 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 |
| } |
|
|