mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-07 16:23:58 +08:00
feat(devin): bind upstream session_id and cascade_id to CPA canonical session
This commit is contained in:
@@ -234,6 +234,7 @@ func (e *DevinExecutor) prepareDevinHTTPRequest(ctx context.Context, auth *clipr
|
||||
payload = sdktranslator.TranslateRequest(opts.SourceFormat, sdktranslator.FormatInteractions, req.Model, payload, opts.Stream)
|
||||
}
|
||||
systemPrompt, prompts, tools, temp, maxTokens, sessionID, cascadeID, thinkingLevel, budgetTokens := parseInteractionsPayload(payload, opts.OriginalRequest)
|
||||
sessionID, cascadeID = resolveDevinSessionAndCascadeIDs(ctx, sessionID, cascadeID, opts)
|
||||
|
||||
chatModelUID := helps.ResolveDevinChatModelUID(req.Model, thinkingLevel, budgetTokens)
|
||||
|
||||
@@ -696,9 +697,20 @@ func parseInteractionsPayload(payload, originalRequest []byte) (
|
||||
}
|
||||
|
||||
// 3. Session and Cascade ID
|
||||
sessionID = strings.TrimSpace(root.Get("previous_interaction_id").String())
|
||||
if sessionID == "" {
|
||||
sessionID = uuid.New().String()
|
||||
sessionID = strings.TrimSpace(firstNonEmpty(
|
||||
root.Get("previous_interaction_id").String(),
|
||||
root.Get("session_id").String(),
|
||||
root.Get("sessionId").String(),
|
||||
root.Get("conversation_id").String(),
|
||||
))
|
||||
if sessionID == "" && len(originalRequest) > 0 {
|
||||
origRoot := gjson.ParseBytes(originalRequest)
|
||||
sessionID = strings.TrimSpace(firstNonEmpty(
|
||||
origRoot.Get("previous_interaction_id").String(),
|
||||
origRoot.Get("session_id").String(),
|
||||
origRoot.Get("sessionId").String(),
|
||||
origRoot.Get("conversation_id").String(),
|
||||
))
|
||||
}
|
||||
cascadeID = sessionID
|
||||
|
||||
@@ -1206,6 +1218,35 @@ func devinAuthLogFields(auth *cliproxyauth.Auth) (authID, authLabel, authType, a
|
||||
return
|
||||
}
|
||||
|
||||
func resolveDevinSessionAndCascadeIDs(ctx context.Context, sessionID, cascadeID string, opts cliproxyexecutor.Options) (string, string) {
|
||||
if sessionID == "" {
|
||||
if ctxSession := util.SessionIDFromContext(ctx); ctxSession != "" {
|
||||
sessionID = ctxSession
|
||||
} else if canon := cliproxyauth.CanonicalSessionID(opts.Headers, opts.OriginalRequest, opts.Metadata); canon != "" {
|
||||
sessionID = canon
|
||||
}
|
||||
}
|
||||
sessionID = normalizeDevinUUID(sessionID)
|
||||
if cascadeID == "" {
|
||||
cascadeID = sessionID
|
||||
} else {
|
||||
cascadeID = normalizeDevinUUID(cascadeID)
|
||||
}
|
||||
return sessionID, cascadeID
|
||||
}
|
||||
|
||||
func normalizeDevinUUID(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return uuid.New().String()
|
||||
}
|
||||
if _, err := uuid.Parse(raw); err == nil {
|
||||
return raw
|
||||
}
|
||||
// Deterministically map any non-UUID session string (e.g. lcp:hash, conv:id) to an RFC 4122 UUID v5
|
||||
return uuid.NewSHA1(uuid.NameSpaceOID, []byte(raw)).String()
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
|
||||
@@ -2,13 +2,16 @@ package executor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
@@ -240,6 +243,44 @@ func TestDevinStatusError_RetryAfter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDevinSessionAndCascadeIDs(t *testing.T) {
|
||||
// 1. Direct UUID preservation
|
||||
rawUUID := "8176cf8a-feff-44c1-8e3e-b10f6d737ae1"
|
||||
sid, cid := resolveDevinSessionAndCascadeIDs(context.Background(), rawUUID, rawUUID, cliproxyexecutor.Options{})
|
||||
if sid != rawUUID || cid != rawUUID {
|
||||
t.Fatalf("sid/cid = %q/%q, want %q", sid, cid, rawUUID)
|
||||
}
|
||||
|
||||
// 2. Non-UUID mapping to deterministic UUID
|
||||
sid1, cid1 := resolveDevinSessionAndCascadeIDs(context.Background(), "lcp:12345678", "", cliproxyexecutor.Options{})
|
||||
sid2, cid2 := resolveDevinSessionAndCascadeIDs(context.Background(), "lcp:12345678", "", cliproxyexecutor.Options{})
|
||||
if sid1 != sid2 || cid1 != cid2 {
|
||||
t.Fatalf("deterministic mapping failed: %q != %q", sid1, sid2)
|
||||
}
|
||||
if _, err := uuid.Parse(sid1); err != nil {
|
||||
t.Fatalf("mapped sid is not a valid UUID: %q", sid1)
|
||||
}
|
||||
|
||||
// 3. Fallback to ctx session
|
||||
ctx := util.WithSessionID(context.Background(), "ctx-session-abc")
|
||||
sidCtx, cidCtx := resolveDevinSessionAndCascadeIDs(ctx, "", "", cliproxyexecutor.Options{})
|
||||
if _, err := uuid.Parse(sidCtx); err != nil {
|
||||
t.Fatalf("sidCtx is not a valid UUID: %q", sidCtx)
|
||||
}
|
||||
if sidCtx != cidCtx {
|
||||
t.Fatalf("sidCtx %q != cidCtx %q", sidCtx, cidCtx)
|
||||
}
|
||||
|
||||
// 4. Fallback to fresh UUID when nothing supplied
|
||||
sidEmpty, cidEmpty := resolveDevinSessionAndCascadeIDs(context.Background(), "", "", cliproxyexecutor.Options{})
|
||||
if _, err := uuid.Parse(sidEmpty); err != nil {
|
||||
t.Fatalf("sidEmpty is not a valid UUID: %q", sidEmpty)
|
||||
}
|
||||
if sidEmpty != cidEmpty {
|
||||
t.Fatalf("sidEmpty %q != cidEmpty %q", sidEmpty, cidEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeDevinFramesToInteractions(t *testing.T) {
|
||||
// Synthesize a Connect stream with 2 data frames and 1 EOS trailer
|
||||
var streamBuf bytes.Buffer
|
||||
|
||||
@@ -406,6 +406,9 @@ func BuildDevinGetChatMessageRequest(
|
||||
f15Bytes = protowire.AppendTag(f15Bytes, 1, protowire.BytesType)
|
||||
f15Bytes = protowire.AppendString(f15Bytes, sessionID)
|
||||
|
||||
f15Bytes = protowire.AppendTag(f15Bytes, 2, protowire.VarintType)
|
||||
f15Bytes = protowire.AppendVarint(f15Bytes, 53)
|
||||
|
||||
f15Bytes = protowire.AppendTag(f15Bytes, 3, protowire.VarintType)
|
||||
f15Bytes = protowire.AppendVarint(f15Bytes, 4)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user