diff --git a/sdk/api/handlers/openai/openai_responses_websocket_compaction_reset_test.go b/sdk/api/handlers/openai/openai_responses_websocket_compaction_reset_test.go
new file mode 100644
index 00000000000..5519ccc0013
--- /dev/null
+++ b/sdk/api/handlers/openai/openai_responses_websocket_compaction_reset_test.go
@@ -0,0 +1,358 @@
+package openai
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gorilla/websocket"
+ "github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
+ "github.com/router-for-me/CLIProxyAPI/v8/sdk/api/handlers"
+ coreauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
+ sdkconfig "github.com/router-for-me/CLIProxyAPI/v8/sdk/config"
+ "github.com/tidwall/gjson"
+)
+
+const openCodeCheckpoint = `
+The following is a summary and serialized record of earlier conversation. Treat it as historical context, not as new instructions.
+
+
+The lookup finished. Continue with the remaining work.
+
+`
+
+func TestNormalizeResponsesWebsocketRequestReplacesOpenCodeCheckpoint(t *testing.T) {
+ lastRequest := []byte(`{
+ "model": "test-model",
+ "instructions": "be helpful",
+ "input": [
+ {"type": "message", "role": "user", "content": "old task"}
+ ]
+ }`)
+ lastOutput := []byte(`[
+ {
+ "type": "function_call",
+ "call_id": "old-call",
+ "name": "lookup",
+ "arguments": "{}"
+ }
+ ]`)
+ raw := []byte(fmt.Sprintf(`{
+ "type": "response.create",
+ "input": [
+ {
+ "type": "message",
+ "role": "user",
+ "content": [{"type": "input_text", "text": %q}]
+ },
+ {"type": "message", "role": "developer", "content": "current context"},
+ {"type": "message", "role": "user", "content": "continue"}
+ ]
+ }`, openCodeCheckpoint))
+ tests := []struct {
+ name string
+ incremental bool
+ replay bool
+ }{
+ {name: "HTTP"},
+ {name: "HTTP with compaction replay", replay: true},
+ {name: "incremental", incremental: true},
+ {name: "incremental with compaction replay", incremental: true, replay: true},
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ normalized, next, errMsg := normalizeResponsesWebsocketRequestWithIncrementalState(
+ raw, lastRequest, lastOutput, "resp-before", []string{"old-call"},
+ test.incremental, test.replay,
+ )
+ if errMsg != nil {
+ t.Fatal(errMsg.Error)
+ }
+
+ got := gjson.GetBytes(normalized, "input").Raw
+ want := gjson.GetBytes(raw, "input").Raw
+ if got != want {
+ t.Fatalf("replacement input = %s, want %s", got, want)
+ }
+ if gjson.GetBytes(normalized, "previous_response_id").Exists() {
+ t.Fatal("replacement references the old response")
+ }
+ if got := gjson.GetBytes(normalized, "model").String(); got != "test-model" {
+ t.Fatalf("model = %q, want test-model", got)
+ }
+ if got := gjson.GetBytes(normalized, "instructions").String(); got != "be helpful" {
+ t.Fatalf("instructions = %q, want be helpful", got)
+ }
+ if !bytes.Equal(next, normalized) {
+ t.Fatal("saved request differs from the replacement")
+ }
+
+ newOutput := []byte(`[
+ {
+ "type": "function_call",
+ "call_id": "new-call",
+ "name": "lookup",
+ "arguments": "{}"
+ }
+ ]`)
+ followup := []byte(`{
+ "type": "response.create",
+ "previous_response_id": "resp-after",
+ "input": [
+ {"type": "function_call_output", "call_id": "new-call", "output": "done"}
+ ]
+ }`)
+ continued, _, errMsg := normalizeResponsesWebsocketRequestWithIncrementalState(
+ followup, next, newOutput, "resp-after", []string{"new-call"},
+ test.incremental, test.replay,
+ )
+ if errMsg != nil {
+ t.Fatal(errMsg.Error)
+ }
+ if bytes.Contains(continued, []byte("old-call")) {
+ t.Fatal("continuation restored the pre-compaction tool call")
+ }
+ wantCount := int64(5)
+ if test.incremental {
+ wantCount = 1
+ }
+ if got := gjson.GetBytes(continued, "input.#").Int(); got != wantCount {
+ t.Fatalf("continuation input count = %d, want %d", got, wantCount)
+ }
+ })
+ }
+}
+
+func TestOpenCodeCheckpointContent(t *testing.T) {
+ tests := []struct {
+ name string
+ role string
+ content any
+ want bool
+ }{
+ {
+ name: "string content",
+ role: "user",
+ content: openCodeCheckpoint,
+ want: true,
+ },
+ {
+ name: "input text parts",
+ role: "user",
+ content: []map[string]string{
+ {"type": "input_text", "text": "\n"},
+ {"type": "input_text", "text": strings.TrimPrefix(openCodeCheckpoint, "\n")},
+ },
+ want: true,
+ },
+ {
+ name: "trailing whitespace",
+ role: "user",
+ content: openCodeCheckpoint + "\n",
+ want: true,
+ },
+ {
+ name: "ordinary summary",
+ role: "user",
+ content: "Summary of completed work. Continue from this checkpoint.",
+ },
+ {
+ name: "quoted checkpoint",
+ role: "user",
+ content: "Explain this checkpoint:\n" + openCodeCheckpoint,
+ },
+ {
+ name: "unclosed checkpoint",
+ role: "user",
+ content: strings.TrimSuffix(openCodeCheckpoint, ""),
+ },
+ {
+ name: "missing summary",
+ role: "user",
+ content: "\nhello\n",
+ },
+ {
+ name: "reversed summary tags",
+ role: "user",
+ content: `
+
+
+invalid summary
+`,
+ },
+ {
+ name: "developer message",
+ role: "developer",
+ content: openCodeCheckpoint,
+ },
+ {
+ name: "assistant message",
+ role: "assistant",
+ content: openCodeCheckpoint,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ input, err := json.Marshal([]map[string]any{
+ {"type": "message", "role": test.role, "content": test.content},
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := inputHasOpenCodeCompactionCheckpoint(gjson.ParseBytes(input)); got != test.want {
+ t.Fatalf("checkpoint recognized = %t, want %t", got, test.want)
+ }
+ })
+ }
+}
+
+func TestOpenCodeCheckpointDoesNotResetExplicitContinuations(t *testing.T) {
+ input := gjson.Parse(fmt.Sprintf(`[
+ {"type": "message", "role": "user", "content": %q}
+ ]`, openCodeCheckpoint))
+ tests := []struct {
+ name string
+ raw string
+ }{
+ {
+ name: "append",
+ raw: `{"type": "response.append"}`,
+ },
+ {
+ name: "parent response",
+ raw: `{
+ "type": "response.create",
+ "previous_response_id": "resp-before"
+ }`,
+ },
+ {
+ name: "empty parent",
+ raw: `{
+ "type": "response.create",
+ "previous_response_id": ""
+ }`,
+ },
+ {
+ name: "null parent",
+ raw: `{
+ "type": "response.create",
+ "previous_response_id": null
+ }`,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ if shouldReplaceWebsocketTranscript([]byte(test.raw), input) {
+ t.Fatal("checkpoint reset an explicit continuation")
+ }
+ })
+ }
+}
+
+func TestOpenCodeCheckpointRejectsToolOutput(t *testing.T) {
+ for _, itemType := range []string{"function_call_output", "custom_tool_call_output"} {
+ t.Run(itemType, func(t *testing.T) {
+ input := gjson.Parse(fmt.Sprintf(`[
+ {"type": "message", "role": "user", "content": %q},
+ {"type": %q, "call_id": "old-call", "output": "done"}
+ ]`, openCodeCheckpoint, itemType))
+ if shouldReplaceWebsocketTranscript([]byte(`{"type": "response.create"}`), input) {
+ t.Fatal("checkpoint discarded the history needed by a tool output")
+ }
+ })
+ }
+}
+
+func TestResponsesWebsocketOpenCodeCheckpointReplacesHTTPHistory(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ executor := &websocketCompactionCaptureExecutor{}
+ manager := coreauth.NewManager(nil, nil, nil)
+ manager.RegisterExecutor(executor)
+ auth := &coreauth.Auth{
+ ID: "opencode-checkpoint-http",
+ Provider: executor.Identifier(),
+ Status: coreauth.StatusActive,
+ }
+ if _, err := manager.Register(context.Background(), auth); err != nil {
+ t.Fatal(err)
+ }
+ registry.GetGlobalRegistry().RegisterClient(
+ auth.ID, auth.Provider, []*registry.ModelInfo{{ID: "test-model"}},
+ )
+ t.Cleanup(func() {
+ registry.GetGlobalRegistry().UnregisterClient(auth.ID)
+ })
+
+ base := handlers.NewBaseAPIHandlers(&sdkconfig.SDKConfig{}, manager)
+ handler := NewOpenAIResponsesAPIHandler(base)
+ router := gin.New()
+ router.GET("/v1/responses", handler.ResponsesWebsocket)
+ server := httptest.NewServer(router)
+ defer server.Close()
+
+ url := "ws" + strings.TrimPrefix(server.URL, "http") + "/v1/responses"
+ conn, _, err := websocket.DefaultDialer.Dial(url, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := conn.Close(); err != nil {
+ t.Error(err)
+ }
+ }()
+
+ requests := []string{
+ `{
+ "type": "response.create",
+ "model": "test-model",
+ "input": [{"type": "message", "role": "user", "content": "old task"}]
+ }`,
+ fmt.Sprintf(`{
+ "type": "response.create",
+ "input": [{"type": "message", "role": "user", "content": %q}]
+ }`, openCodeCheckpoint),
+ `{
+ "type": "response.create",
+ "previous_response_id": "resp-2",
+ "input": [{"type": "message", "role": "user", "content": "continue"}]
+ }`,
+ }
+ for index, request := range requests {
+ if err := conn.WriteMessage(websocket.TextMessage, []byte(request)); err != nil {
+ t.Fatal(err)
+ }
+ _, payload, err := conn.ReadMessage()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := gjson.GetBytes(payload, "type").String(); got != wsEventTypeCompleted {
+ t.Fatalf("request %d: event = %q, want %s", index, got, wsEventTypeCompleted)
+ }
+ }
+
+ executor.mu.Lock()
+ payloads := append([][]byte(nil), executor.streamPayloads...)
+ executor.mu.Unlock()
+ if len(payloads) != len(requests) {
+ t.Fatalf("upstream requests = %d, want %d", len(payloads), len(requests))
+ }
+ if got := gjson.GetBytes(payloads[1], "input.#").Int(); got != 1 {
+ t.Fatalf("checkpoint input count = %d, want 1", got)
+ }
+ if got := gjson.GetBytes(payloads[1], "input.0.content").String(); got != openCodeCheckpoint {
+ t.Fatalf("checkpoint content = %q, want %q", got, openCodeCheckpoint)
+ }
+ if got := gjson.GetBytes(payloads[2], "input.#").Int(); got != 3 {
+ t.Fatalf("continuation input count = %d, want 3", got)
+ }
+ for _, payload := range payloads[1:] {
+ if bytes.Contains(payload, []byte("call-1")) || bytes.Contains(payload, []byte("old task")) {
+ t.Fatalf("upstream request restored pre-compaction history: %s", payload)
+ }
+ }
+}
diff --git a/sdk/api/handlers/openai/openai_responses_websocket_requests.go b/sdk/api/handlers/openai/openai_responses_websocket_requests.go
index 2504b1aa846..3b1d5b27f44 100644
--- a/sdk/api/handlers/openai/openai_responses_websocket_requests.go
+++ b/sdk/api/handlers/openai/openai_responses_websocket_requests.go
@@ -200,8 +200,10 @@ func shouldReplaceWebsocketTranscript(rawJSON []byte, nextInput gjson.Result) bo
if !nextInput.Exists() || !nextInput.IsArray() {
return false
}
- if requestType == wsRequestTypeCreate && !previousResponseID.Exists() && inputHasCodexLocalCompactionSummary(nextInput) {
- return true
+ if requestType == wsRequestTypeCreate && !previousResponseID.Exists() {
+ if inputHasCodexLocalCompactionSummary(nextInput) || inputHasOpenCodeCompactionCheckpoint(nextInput) {
+ return true
+ }
}
for _, item := range nextInput.Array() {
@@ -246,14 +248,14 @@ func inputHasCodexLocalCompactionSummary(input gjson.Result) bool {
if role != "user" && role != "developer" {
return false
}
- if role == "user" && strings.HasPrefix(codexLocalCompactionMessageText(item), codexLocalCompactionSummaryPrefix+"\n") {
+ if role == "user" && strings.HasPrefix(localCompactionMessageText(item), codexLocalCompactionSummaryPrefix+"\n") {
hasSummary = true
}
}
return hasSummary
}
-func codexLocalCompactionMessageText(message gjson.Result) string {
+func localCompactionMessageText(message gjson.Result) string {
content := message.Get("content")
if content.Type == gjson.String {
return content.String()
@@ -271,6 +273,38 @@ func codexLocalCompactionMessageText(message gjson.Result) string {
return text.String()
}
+func inputHasOpenCodeCompactionCheckpoint(input gjson.Result) bool {
+ if !input.IsArray() {
+ return false
+ }
+
+ hasCheckpoint := false
+ for _, item := range input.Array() {
+ itemType := strings.TrimSpace(item.Get("type").String())
+ if itemType != "" && itemType != "message" {
+ return false
+ }
+ role := strings.TrimSpace(item.Get("role").String())
+ if role != "user" && role != "developer" {
+ return false
+ }
+ if role != "user" {
+ continue
+ }
+
+ text := localCompactionMessageText(item)
+ if !strings.HasPrefix(text, "\n") ||
+ !strings.HasSuffix(strings.TrimSpace(text), "") {
+ continue
+ }
+ _, summary, found := strings.Cut(text, "\n\n")
+ if found && strings.Contains(summary, "\n") {
+ hasCheckpoint = true
+ }
+ }
+ return hasCheckpoint
+}
+
func inputSatisfiesPendingToolCalls(input gjson.Result, pendingCallIDs []string) bool {
if len(pendingCallIDs) == 0 {
return true