mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-23 10:57:44 +08:00
refactor: replace history_split with current_input_file configuration
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -272,14 +272,13 @@ func TestChatCompletionsSkipsHistoryWhenDisabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsHistorySplitPersistsHistoryText(t *testing.T) {
|
||||
func TestChatCompletionsCurrentInputFilePersistsNeutralPrompt(t *testing.T) {
|
||||
historyStore := newTestChatHistoryStore(t)
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &Handler{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -308,19 +307,19 @@ func TestChatCompletionsHistorySplitPersistsHistoryText(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected detail item, got %v", err)
|
||||
}
|
||||
if full.HistoryText == "" {
|
||||
t.Fatalf("expected history text to be persisted")
|
||||
}
|
||||
if !strings.Contains(full.HistoryText, "first user turn") || !strings.Contains(full.HistoryText, "tool result") {
|
||||
t.Fatalf("expected earlier turns in history text, got %q", full.HistoryText)
|
||||
}
|
||||
if strings.Contains(full.HistoryText, "latest user turn") {
|
||||
t.Fatalf("expected latest turn to stay out of persisted history text, got %q", full.HistoryText)
|
||||
if full.HistoryText != "" {
|
||||
t.Fatalf("expected current input file flow to leave history text empty, got %q", full.HistoryText)
|
||||
}
|
||||
if len(ds.uploadCalls) != 1 {
|
||||
t.Fatalf("expected history upload to happen, got %d", len(ds.uploadCalls))
|
||||
t.Fatalf("expected current input upload to happen, got %d", len(ds.uploadCalls))
|
||||
}
|
||||
if full.HistoryText != string(ds.uploadCalls[0].Data) {
|
||||
t.Fatalf("expected persisted history text to match uploaded HISTORY.txt contents")
|
||||
if ds.uploadCalls[0].Filename != "IGNORE.txt" {
|
||||
t.Fatalf("expected IGNORE.txt upload, got %q", ds.uploadCalls[0].Filename)
|
||||
}
|
||||
if len(full.Messages) != 1 {
|
||||
t.Fatalf("expected neutral prompt to be the only persisted message, got %#v", full.Messages)
|
||||
}
|
||||
if !strings.Contains(full.Messages[0].Content, "Answer the latest user request directly.") {
|
||||
t.Fatalf("expected neutral prompt to be persisted, got %#v", full.Messages[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +42,17 @@ func (h *Handler) compatStripReferenceMarkers() bool {
|
||||
return shared.CompatStripReferenceMarkers(h.Store)
|
||||
}
|
||||
|
||||
func (h *Handler) applyHistorySplit(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
func (h *Handler) applyCurrentInputFile(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
if h == nil {
|
||||
return stdReq, nil
|
||||
}
|
||||
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 {
|
||||
return stdReq, err
|
||||
if err != nil || out.CurrentInputFileApplied {
|
||||
return out, err
|
||||
}
|
||||
if out.CurrentInputFileApplied {
|
||||
return out, nil
|
||||
}
|
||||
return svc.Apply(ctx, a, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *Handler) preprocessInlineFileInputs(ctx context.Context, a *auth.RequestAuth, req map[string]any) error {
|
||||
@@ -91,7 +88,7 @@ func writeOpenAIInlineFileError(w http.ResponseWriter, err error) {
|
||||
files.WriteInlineFileError(w, err)
|
||||
}
|
||||
|
||||
func mapHistorySplitError(err error) (int, string) {
|
||||
func mapCurrentInputFileError(err error) (int, string) {
|
||||
return history.MapError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,9 +68,9 @@ func (h *Handler) ChatCompletions(w http.ResponseWriter, r *http.Request) {
|
||||
writeOpenAIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
stdReq, err = h.applyHistorySplit(r.Context(), a, stdReq)
|
||||
stdReq, err = h.applyCurrentInputFile(r.Context(), a, stdReq)
|
||||
if err != nil {
|
||||
status, message := mapHistorySplitError(err)
|
||||
status, message := mapCurrentInputFileError(err)
|
||||
writeOpenAIError(w, status, message)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestStreamLeaseTTL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVercelStreamPrepareAppliesHistorySplit(t *testing.T) {
|
||||
func TestHandleVercelStreamPrepareAppliesCurrentInputFile(t *testing.T) {
|
||||
t.Setenv("VERCEL", "1")
|
||||
t.Setenv("DS2API_VERCEL_INTERNAL_SECRET", "stream-secret")
|
||||
|
||||
@@ -95,8 +95,7 @@ func TestHandleVercelStreamPrepareAppliesHistorySplit(t *testing.T) {
|
||||
h := &Handler{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -119,7 +118,7 @@ func TestHandleVercelStreamPrepareAppliesHistorySplit(t *testing.T) {
|
||||
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if len(ds.uploadCalls) != 1 {
|
||||
t.Fatalf("expected 1 history upload, got %d", len(ds.uploadCalls))
|
||||
t.Fatalf("expected 1 current input upload, got %d", len(ds.uploadCalls))
|
||||
}
|
||||
|
||||
var body map[string]any
|
||||
@@ -131,11 +130,11 @@ func TestHandleVercelStreamPrepareAppliesHistorySplit(t *testing.T) {
|
||||
t.Fatalf("expected payload object, got %#v", body["payload"])
|
||||
}
|
||||
promptText, _ := payload["prompt"].(string)
|
||||
if !strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected latest user turn in prompt, got %s", promptText)
|
||||
if !strings.Contains(promptText, "Answer the latest user request directly.") {
|
||||
t.Fatalf("expected neutral prompt, got %s", promptText)
|
||||
}
|
||||
if strings.Contains(promptText, "first user turn") {
|
||||
t.Fatalf("expected historical turns removed from prompt, got %s", promptText)
|
||||
if strings.Contains(promptText, "first user turn") || strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected original turns hidden from prompt, got %s", promptText)
|
||||
}
|
||||
refIDs, _ := payload["ref_file_ids"].([]any)
|
||||
if len(refIDs) == 0 || refIDs[0] != "file-inline-1" {
|
||||
@@ -143,7 +142,7 @@ func TestHandleVercelStreamPrepareAppliesHistorySplit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVercelStreamPrepareMapsHistorySplitManagedAuthFailureTo401(t *testing.T) {
|
||||
func TestHandleVercelStreamPrepareMapsCurrentInputFileManagedAuthFailureTo401(t *testing.T) {
|
||||
t.Setenv("VERCEL", "1")
|
||||
t.Setenv("DS2API_VERCEL_INTERNAL_SECRET", "stream-secret")
|
||||
|
||||
@@ -153,8 +152,7 @@ func TestHandleVercelStreamPrepareMapsHistorySplitManagedAuthFailureTo401(t *tes
|
||||
h := &Handler{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusManagedAuthStub{},
|
||||
DS: ds,
|
||||
|
||||
@@ -69,9 +69,9 @@ func (h *Handler) handleVercelStreamPrepare(w http.ResponseWriter, r *http.Reque
|
||||
writeOpenAIError(w, http.StatusBadRequest, "stream must be true")
|
||||
return
|
||||
}
|
||||
stdReq, err = h.applyHistorySplit(r.Context(), a, stdReq)
|
||||
stdReq, err = h.applyCurrentInputFile(r.Context(), a, stdReq)
|
||||
if err != nil {
|
||||
status, message := mapHistorySplitError(err)
|
||||
status, message := mapCurrentInputFileError(err)
|
||||
writeOpenAIError(w, status, message)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ func (s Service) ApplyCurrentInputFile(ctx context.Context, a *auth.RequestAuth,
|
||||
if index < 0 {
|
||||
return stdReq, nil
|
||||
}
|
||||
historySplitReached := s.Store.HistorySplitEnabled() && wouldSplitHistory(stdReq.Messages, s.Store.HistorySplitTriggerAfterTurns())
|
||||
if len([]rune(text)) < threshold && !historySplitReached {
|
||||
if len([]rune(text)) < threshold {
|
||||
return stdReq, nil
|
||||
}
|
||||
fileText := promptcompat.BuildOpenAICurrentInputContextTranscript(stdReq.Messages)
|
||||
@@ -84,11 +83,6 @@ func latestUserInputForFile(messages []any) (int, string) {
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
func wouldSplitHistory(messages []any, triggerAfterTurns int) bool {
|
||||
_, historyMessages := SplitOpenAIHistoryMessages(messages, triggerAfterTurns)
|
||||
return len(historyMessages) > 0
|
||||
}
|
||||
|
||||
func currentInputFilePrompt() string {
|
||||
return "The current request and prior conversation context have already been provided. Answer the latest user request directly."
|
||||
}
|
||||
|
||||
@@ -2,60 +2,21 @@ package history
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"ds2api/internal/auth"
|
||||
dsclient "ds2api/internal/deepseek/client"
|
||||
"ds2api/internal/httpapi/openai/shared"
|
||||
"ds2api/internal/promptcompat"
|
||||
)
|
||||
|
||||
const (
|
||||
historySplitFilename = "HISTORY.txt"
|
||||
historySplitContentType = "text/plain; charset=utf-8"
|
||||
historySplitPurpose = "assistants"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Store shared.ConfigReader
|
||||
DS shared.DeepSeekCaller
|
||||
}
|
||||
|
||||
// Apply is retained for legacy compatibility only. The active split path is
|
||||
// current input file handling in ApplyCurrentInputFile.
|
||||
func (s Service) Apply(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
if s.DS == nil || s.Store == nil || a == nil || !s.Store.HistorySplitEnabled() {
|
||||
return stdReq, nil
|
||||
}
|
||||
|
||||
promptMessages, historyMessages := SplitOpenAIHistoryMessages(stdReq.Messages, s.Store.HistorySplitTriggerAfterTurns())
|
||||
if len(historyMessages) == 0 {
|
||||
return stdReq, nil
|
||||
}
|
||||
|
||||
historyText := promptcompat.BuildOpenAIHistoryTranscript(historyMessages)
|
||||
if strings.TrimSpace(historyText) == "" {
|
||||
return stdReq, errors.New("history split produced empty transcript")
|
||||
}
|
||||
|
||||
result, err := s.DS.UploadFile(ctx, a, dsclient.UploadFileRequest{
|
||||
Filename: historySplitFilename,
|
||||
ContentType: historySplitContentType,
|
||||
Purpose: historySplitPurpose,
|
||||
Data: []byte(historyText),
|
||||
}, 3)
|
||||
if err != nil {
|
||||
return stdReq, fmt.Errorf("upload history file: %w", err)
|
||||
}
|
||||
fileID := strings.TrimSpace(result.ID)
|
||||
if fileID == "" {
|
||||
return stdReq, errors.New("upload history file returned empty file id")
|
||||
}
|
||||
|
||||
stdReq.Messages = promptMessages
|
||||
stdReq.HistoryText = historyText
|
||||
stdReq.RefFileIDs = prependUniqueRefFileID(stdReq.RefFileIDs, fileID)
|
||||
stdReq.FinalPrompt, stdReq.ToolNames = promptcompat.BuildOpenAIPrompt(promptMessages, stdReq.ToolsRaw, "", stdReq.ToolChoice, stdReq.Thinking)
|
||||
return stdReq, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ func (streamStatusManagedAuthStub) DetermineCaller(_ *http.Request) (*auth.Reque
|
||||
|
||||
func (streamStatusManagedAuthStub) Release(_ *auth.RequestAuth) {}
|
||||
|
||||
func TestBuildOpenAIHistoryTranscriptUsesInjectedFileWrapper(t *testing.T) {
|
||||
func TestBuildOpenAICurrentInputContextTranscriptUsesInjectedFileWrapper(t *testing.T) {
|
||||
_, historyMessages := splitOpenAIHistoryMessages(historySplitTestMessages(), 1)
|
||||
transcript := buildOpenAIHistoryTranscript(historyMessages)
|
||||
transcript := buildOpenAICurrentInputContextTranscript(historyMessages)
|
||||
|
||||
if !strings.HasPrefix(transcript, "[file content end]\n\n") {
|
||||
t.Fatalf("expected injected file wrapper prefix, got %q", transcript)
|
||||
@@ -107,7 +107,7 @@ func TestSplitOpenAIHistoryMessagesUsesLatestUserTurn(t *testing.T) {
|
||||
t.Fatalf("expected middle user turn to be moved into history, got %s", promptText)
|
||||
}
|
||||
|
||||
historyText := buildOpenAIHistoryTranscript(historyMessages)
|
||||
historyText := buildOpenAICurrentInputContextTranscript(historyMessages)
|
||||
if !strings.Contains(historyText, "middle user turn") {
|
||||
t.Fatalf("expected middle user turn in split history, got %s", historyText)
|
||||
}
|
||||
@@ -116,13 +116,13 @@ func TestSplitOpenAIHistoryMessagesUsesLatestUserTurn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHistorySplitSkipsFirstTurn(t *testing.T) {
|
||||
func TestApplyCurrentInputFileSkipsShortInputWhenThresholdNotReached(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
currentInputMin: 10,
|
||||
},
|
||||
DS: ds,
|
||||
}
|
||||
@@ -137,9 +137,9 @@ func TestApplyHistorySplitSkipsFirstTurn(t *testing.T) {
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply history split failed: %v", err)
|
||||
t.Fatalf("apply current input file failed: %v", err)
|
||||
}
|
||||
if len(ds.uploadCalls) != 0 {
|
||||
t.Fatalf("expected no upload on first turn, got %d", len(ds.uploadCalls))
|
||||
@@ -153,10 +153,8 @@ func TestApplyThinkingInjectionAppendsLatestUserPrompt(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
thinkingInjection: boolPtr(true),
|
||||
wideInput: true,
|
||||
thinkingInjection: boolPtr(true),
|
||||
},
|
||||
DS: ds,
|
||||
}
|
||||
@@ -171,7 +169,7 @@ func TestApplyThinkingInjectionAppendsLatestUserPrompt(t *testing.T) {
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply thinking injection failed: %v", err)
|
||||
}
|
||||
@@ -204,7 +202,7 @@ func TestApplyThinkingInjectionUsesCustomPrompt(t *testing.T) {
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply thinking injection failed: %v", err)
|
||||
}
|
||||
@@ -213,12 +211,11 @@ func TestApplyThinkingInjectionUsesCustomPrompt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHistorySplitDirectPassThroughWhenBothSplitsDisabled(t *testing.T) {
|
||||
func TestApplyCurrentInputFileDisabledPassThrough(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: false,
|
||||
currentInputEnabled: false,
|
||||
},
|
||||
DS: ds,
|
||||
@@ -232,9 +229,9 @@ func TestApplyHistorySplitDirectPassThroughWhenBothSplitsDisabled(t *testing.T)
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply history split failed: %v", err)
|
||||
t.Fatalf("apply current input file failed: %v", err)
|
||||
}
|
||||
if len(ds.uploadCalls) != 0 {
|
||||
t.Fatalf("expected no uploads when both split modes are disabled, got %d", len(ds.uploadCalls))
|
||||
@@ -252,8 +249,6 @@ func TestApplyCurrentInputFileUploadsFirstTurnWithInjectedWrapper(t *testing.T)
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
currentInputMin: 10,
|
||||
thinkingInjection: boolPtr(true),
|
||||
@@ -271,7 +266,7 @@ func TestApplyCurrentInputFileUploadsFirstTurnWithInjectedWrapper(t *testing.T)
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply current input file failed: %v", err)
|
||||
}
|
||||
@@ -309,15 +304,13 @@ func TestApplyCurrentInputFileUploadsFirstTurnWithInjectedWrapper(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCurrentInputFileReplacesHistorySplitWithFullContextFile(t *testing.T) {
|
||||
func TestApplyCurrentInputFileUploadsFullContextFile(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
currentInputMin: 1000,
|
||||
currentInputMin: 0,
|
||||
thinkingInjection: boolPtr(true),
|
||||
},
|
||||
DS: ds,
|
||||
@@ -331,12 +324,12 @@ func TestApplyCurrentInputFileReplacesHistorySplitWithFullContextFile(t *testing
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply current input file failed: %v", err)
|
||||
}
|
||||
if !out.CurrentInputFileApplied {
|
||||
t.Fatalf("expected current input file to replace history split")
|
||||
t.Fatalf("expected current input file to apply")
|
||||
}
|
||||
if len(ds.uploadCalls) != 1 {
|
||||
t.Fatalf("expected one current input upload, got %d", len(ds.uploadCalls))
|
||||
@@ -351,9 +344,6 @@ func TestApplyCurrentInputFileReplacesHistorySplitWithFullContextFile(t *testing
|
||||
t.Fatalf("expected full context file to contain %q, got %q", want, uploadedText)
|
||||
}
|
||||
}
|
||||
if out.HistoryText != "" {
|
||||
t.Fatalf("expected no HISTORY transcript when current input file replaces split, got %q", out.HistoryText)
|
||||
}
|
||||
if strings.Contains(out.FinalPrompt, "first user turn") || strings.Contains(out.FinalPrompt, "latest user turn") || strings.Contains(out.FinalPrompt, "CURRENT_USER_INPUT.txt") || strings.Contains(out.FinalPrompt, "IGNORE.txt") || strings.Contains(out.FinalPrompt, "Read that file") {
|
||||
t.Fatalf("expected live prompt to use only a neutral continuation instruction, got %s", out.FinalPrompt)
|
||||
}
|
||||
@@ -362,13 +352,12 @@ func TestApplyCurrentInputFileReplacesHistorySplitWithFullContextFile(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHistorySplitCarriesHistoryText(t *testing.T) {
|
||||
func TestApplyCurrentInputFileLeavesHistoryTextEmpty(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
DS: ds,
|
||||
}
|
||||
@@ -381,25 +370,24 @@ func TestApplyHistorySplitCarriesHistoryText(t *testing.T) {
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
out, err := h.applyCurrentInputFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
|
||||
if err != nil {
|
||||
t.Fatalf("apply history split failed: %v", err)
|
||||
t.Fatalf("apply current input file failed: %v", err)
|
||||
}
|
||||
if len(ds.uploadCalls) != 1 {
|
||||
t.Fatalf("expected 1 upload call, got %d", len(ds.uploadCalls))
|
||||
}
|
||||
if out.HistoryText != string(ds.uploadCalls[0].Data) {
|
||||
t.Fatalf("expected history text to be preserved on normalized request")
|
||||
if out.HistoryText != "" {
|
||||
t.Fatalf("expected current input file flow to leave history text empty, got %q", out.HistoryText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsHistorySplitUploadsHistoryFileAndKeepsLatestPrompt(t *testing.T) {
|
||||
func TestChatCompletionsCurrentInputFileUploadsContextAndKeepsNeutralPrompt(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -423,7 +411,7 @@ func TestChatCompletionsHistorySplitUploadsHistoryFileAndKeepsLatestPrompt(t *te
|
||||
t.Fatalf("expected 1 upload call, got %d", len(ds.uploadCalls))
|
||||
}
|
||||
upload := ds.uploadCalls[0]
|
||||
if upload.Filename != "HISTORY.txt" {
|
||||
if upload.Filename != "IGNORE.txt" {
|
||||
t.Fatalf("unexpected upload filename: %q", upload.Filename)
|
||||
}
|
||||
if upload.Purpose != "assistants" {
|
||||
@@ -433,32 +421,31 @@ func TestChatCompletionsHistorySplitUploadsHistoryFileAndKeepsLatestPrompt(t *te
|
||||
if !strings.Contains(historyText, "[file content end]") || !strings.Contains(historyText, "[file name]: IGNORE") {
|
||||
t.Fatalf("expected injected IGNORE wrapper, got %s", historyText)
|
||||
}
|
||||
if strings.Contains(historyText, "latest user turn") {
|
||||
t.Fatalf("expected latest turn to remain live, got %s", historyText)
|
||||
if !strings.Contains(historyText, "latest user turn") {
|
||||
t.Fatalf("expected full context to include latest turn, got %s", historyText)
|
||||
}
|
||||
if ds.completionReq == nil {
|
||||
t.Fatal("expected completion payload to be captured")
|
||||
}
|
||||
promptText, _ := ds.completionReq["prompt"].(string)
|
||||
if !strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected latest turn in completion prompt, got %s", promptText)
|
||||
if !strings.Contains(promptText, "Answer the latest user request directly.") {
|
||||
t.Fatalf("expected neutral completion prompt, got %s", promptText)
|
||||
}
|
||||
if strings.Contains(promptText, "first user turn") {
|
||||
t.Fatalf("expected historical turns removed from completion prompt, got %s", promptText)
|
||||
if strings.Contains(promptText, "first user turn") || strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected prompt to hide original turns, got %s", promptText)
|
||||
}
|
||||
refIDs, _ := ds.completionReq["ref_file_ids"].([]any)
|
||||
if len(refIDs) == 0 || refIDs[0] != "file-inline-1" {
|
||||
t.Fatalf("expected uploaded history file to be first ref_file_id, got %#v", ds.completionReq["ref_file_ids"])
|
||||
t.Fatalf("expected uploaded current input file to be first ref_file_id, got %#v", ds.completionReq["ref_file_ids"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsesHistorySplitUploadsHistoryAndKeepsLatestPrompt(t *testing.T) {
|
||||
func TestResponsesCurrentInputFileUploadsContextAndKeepsNeutralPrompt(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -487,23 +474,22 @@ func TestResponsesHistorySplitUploadsHistoryAndKeepsLatestPrompt(t *testing.T) {
|
||||
t.Fatal("expected completion payload to be captured")
|
||||
}
|
||||
promptText, _ := ds.completionReq["prompt"].(string)
|
||||
if !strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected latest turn in completion prompt, got %s", promptText)
|
||||
if !strings.Contains(promptText, "Answer the latest user request directly.") {
|
||||
t.Fatalf("expected neutral completion prompt, got %s", promptText)
|
||||
}
|
||||
if strings.Contains(promptText, "first user turn") {
|
||||
t.Fatalf("expected historical turns removed from completion prompt, got %s", promptText)
|
||||
if strings.Contains(promptText, "first user turn") || strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("expected prompt to hide original turns, got %s", promptText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsHistorySplitMapsManagedAuthFailureTo401(t *testing.T) {
|
||||
func TestChatCompletionsCurrentInputFileMapsManagedAuthFailureTo401(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{
|
||||
uploadErr: &dsclient.RequestFailure{Op: "upload file", Kind: dsclient.FailureManagedUnauthorized, Message: "expired token"},
|
||||
}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusManagedAuthStub{},
|
||||
DS: ds,
|
||||
@@ -528,15 +514,14 @@ func TestChatCompletionsHistorySplitMapsManagedAuthFailureTo401(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsesHistorySplitMapsDirectAuthFailureTo401(t *testing.T) {
|
||||
func TestResponsesCurrentInputFileMapsDirectAuthFailureTo401(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{
|
||||
uploadErr: &dsclient.RequestFailure{Op: "upload file", Kind: dsclient.FailureDirectUnauthorized, Message: "invalid token"},
|
||||
}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -563,13 +548,12 @@ func TestResponsesHistorySplitMapsDirectAuthFailureTo401(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsHistorySplitUploadFailureReturnsInternalServerError(t *testing.T) {
|
||||
func TestChatCompletionsCurrentInputFileUploadFailureReturnsInternalServerError(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{uploadErr: errors.New("boom")}
|
||||
h := &openAITestSurface{
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -591,7 +575,7 @@ func TestChatCompletionsHistorySplitUploadFailureReturnsInternalServerError(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistorySplitWorksAcrossAutoDeleteModes(t *testing.T) {
|
||||
func TestCurrentInputFileWorksAcrossAutoDeleteModes(t *testing.T) {
|
||||
for _, mode := range []string{"none", "single", "all"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
ds := &inlineUploadDSStub{}
|
||||
@@ -599,8 +583,7 @@ func TestHistorySplitWorksAcrossAutoDeleteModes(t *testing.T) {
|
||||
Store: mockOpenAIConfig{
|
||||
wideInput: true,
|
||||
autoDeleteMode: mode,
|
||||
historySplitEnabled: true,
|
||||
historySplitTurns: 1,
|
||||
currentInputEnabled: true,
|
||||
},
|
||||
Auth: streamStatusAuthStub{},
|
||||
DS: ds,
|
||||
@@ -621,13 +604,13 @@ func TestHistorySplitWorksAcrossAutoDeleteModes(t *testing.T) {
|
||||
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if len(ds.uploadCalls) != 1 {
|
||||
t.Fatalf("expected history split upload for mode=%s, got %d", mode, len(ds.uploadCalls))
|
||||
t.Fatalf("expected current input upload for mode=%s, got %d", mode, len(ds.uploadCalls))
|
||||
}
|
||||
if ds.completionReq == nil {
|
||||
t.Fatalf("expected completion payload for mode=%s", mode)
|
||||
}
|
||||
promptText, _ := ds.completionReq["prompt"].(string)
|
||||
if !strings.Contains(promptText, "latest user turn") || strings.Contains(promptText, "first user turn") {
|
||||
if !strings.Contains(promptText, "Answer the latest user request directly.") || strings.Contains(promptText, "first user turn") || strings.Contains(promptText, "latest user turn") {
|
||||
t.Fatalf("unexpected prompt for mode=%s: %s", mode, promptText)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -36,20 +36,17 @@ func (h *Handler) compatStripReferenceMarkers() bool {
|
||||
return shared.CompatStripReferenceMarkers(h.Store)
|
||||
}
|
||||
|
||||
func (h *Handler) applyHistorySplit(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
func (h *Handler) applyCurrentInputFile(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
if h == nil {
|
||||
return stdReq, nil
|
||||
}
|
||||
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 {
|
||||
return stdReq, err
|
||||
if err != nil || out.CurrentInputFileApplied {
|
||||
return out, err
|
||||
}
|
||||
if out.CurrentInputFileApplied {
|
||||
return out, nil
|
||||
}
|
||||
return svc.Apply(ctx, a, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *Handler) preprocessInlineFileInputs(ctx context.Context, a *auth.RequestAuth, req map[string]any) error {
|
||||
@@ -89,7 +86,7 @@ func writeOpenAIInlineFileError(w http.ResponseWriter, err error) {
|
||||
files.WriteInlineFileError(w, err)
|
||||
}
|
||||
|
||||
func mapHistorySplitError(err error) (int, string) {
|
||||
func mapCurrentInputFileError(err error) (int, string) {
|
||||
return history.MapError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -85,9 +85,9 @@ func (h *Handler) Responses(w http.ResponseWriter, r *http.Request) {
|
||||
writeOpenAIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
stdReq, err = h.applyHistorySplit(r.Context(), a, stdReq)
|
||||
stdReq, err = h.applyCurrentInputFile(r.Context(), a, stdReq)
|
||||
if err != nil {
|
||||
status, message := mapHistorySplitError(err)
|
||||
status, message := mapCurrentInputFileError(err)
|
||||
writeOpenAIError(w, status, message)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,17 +83,14 @@ func (h *openAITestSurface) ChatCompletions(w http.ResponseWriter, r *http.Reque
|
||||
h.chatHandler().ChatCompletions(w, r)
|
||||
}
|
||||
|
||||
func (h *openAITestSurface) applyHistorySplit(ctx context.Context, a *auth.RequestAuth, stdReq promptcompat.StandardRequest) (promptcompat.StandardRequest, error) {
|
||||
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 {
|
||||
return stdReq, err
|
||||
if err != nil || out.CurrentInputFileApplied {
|
||||
return out, err
|
||||
}
|
||||
if out.CurrentInputFileApplied {
|
||||
return out, nil
|
||||
}
|
||||
return svc.Apply(ctx, a, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *openAITestSurface) preprocessInlineFileInputs(ctx context.Context, a *auth.RequestAuth, req map[string]any) error {
|
||||
@@ -114,8 +111,8 @@ func splitOpenAIHistoryMessages(messages []any, triggerAfterTurns int) ([]any, [
|
||||
return history.SplitOpenAIHistoryMessages(messages, triggerAfterTurns)
|
||||
}
|
||||
|
||||
func buildOpenAIHistoryTranscript(messages []any) string {
|
||||
return promptcompat.BuildOpenAIHistoryTranscript(messages)
|
||||
func buildOpenAICurrentInputContextTranscript(messages []any) string {
|
||||
return promptcompat.BuildOpenAICurrentInputContextTranscript(messages)
|
||||
}
|
||||
|
||||
func writeOpenAIError(w http.ResponseWriter, status int, message string) {
|
||||
|
||||
Reference in New Issue
Block a user