package helps import ( "bytes" "compress/gzip" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "fmt" "io" "math" "net/http" "runtime" "strings" "sync/atomic" "unicode/utf8" "github.com/google/uuid" "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "google.golang.org/protobuf/encoding/protowire") const ( // ConnectFlagData marks an uncompressed Connect-proto data frame. ConnectFlagData byte = 0x00 // ConnectFlagCompressed marks a gzipped Connect-proto frame. ConnectFlagCompressed byte = 0x01 // ConnectFlagEndStream marks the terminal Connect-proto trailer frame. ConnectFlagEndStream byte = 0x02 // DevinDefaultBaseURL is the default upstream Codeium/Devin endpoint. DevinDefaultBaseURL = "https://server.codeium.com" // DevinChatPath is the Connect-RPC endpoint for chat completions. DevinChatPath = "/exa.api_server_pb.ApiServerService/GetChatMessage" // DevinDefaultClientName is the client identifier declared in metadata. DevinDefaultClientName = "chisel" // DevinDefaultClientVersion is the client version declared in metadata. DevinDefaultClientVersion = "3000.10.21" // DevinFingerprintHexLen is the mandatory 732-hex-character length for metadata #31. DevinFingerprintHexLen = 732 // DevinDefaultMaxTokens is the fallback max completion tokens. DevinDefaultMaxTokens = 128000 maxConnectFrameSize = 16 * 1024 * 1024 maxDecompressedFrameSize = 64 * 1024 * 1024) // DevinTool represents a tool definition in GetChatMessageRequest.type DevinTool struct { Name string Description string Parameters []byte} // DevinToolCall represents a tool call in a ChatMessagePrompt.type DevinToolCall struct { ID string Name string Arguments string} // DevinToolCallDelta represents a streaming tool call chunk from response Field 6.type DevinToolCallDelta struct { ID string Name string Arguments string Index int} // DevinImage represents an image attachment in a DevinPrompt (Prompt #10).type DevinImage struct { Base64Data string MimeType string} // DevinPrompt represents a single turn in the request history (repeated Field 3).type DevinPrompt struct { MessageID string Source int // 1=user, 2=assistant, 4=tool Content string Images []DevinImage ToolCalls []DevinToolCall ToolCallID string // For source=4 (tool result) Thinking string Signature []byte SignatureType string} // DevinUsage captures token accounting from response Field 7.type DevinUsage struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` CachedTokens int64 `json:"cached_tokens"` StatusCode uint64 `json:"status_code,omitempty"` RequestID string `json:"request_id,omitempty"` ModelName string `json:"model_name,omitempty"` Headers map[string]string `json:"headers,omitempty"`} // DevinFrameResult represents decoded content from a single Connect-proto frame.type DevinFrameResult struct { OutputID string Timestamp uint64 ContentText string DeltaTokens uint64 StopReason uint64 // 2/4=stop, 10=tool_calls ToolCallDeltas []DevinToolCallDelta ThinkingText string DeltaSignature []byte DeltaSignatureType string Latency float64 MessageID string Usage *DevinUsage ResponseDimensionGroups [][]byte UnknownFieldNumbers []int} // GenerateDevinDeviceFingerprint generates a 732-character hex device fingerprint.// When seed is empty, it generates a cryptographically random 732-character hex string per request (matching native devin-cli).// When seed is provided, it derives a deterministic 732-character hex fingerprint.func GenerateDevinDeviceFingerprint(seed string) string { if seed == "" { var b [DevinFingerprintHexLen / 2]byte if _, err := rand.Read(b[:]); err == nil { return hex.EncodeToString(b[:]) } seed = uuid.New().String() } var sb strings.Builder counter := 0 for sb.Len() < DevinFingerprintHexLen { h := sha256.Sum256([]byte(fmt.Sprintf("%s-%d", seed, counter))) sb.WriteString(hex.EncodeToString(h[:])) counter++ } // Truncate to exact 732 chars (11 full 64-char sha256 hex blocks + 28 chars of the 12th block) to match Devin CLI. return sb.String()[:DevinFingerprintHexLen]} // GenerateDevinSentryTrace generates a Sentry distributed tracing header in the format:// "<32-hex-trace-id>-<16-hex-span-id>-1"func GenerateDevinSentryTrace() string { var b [24]byte if _, err := rand.Read(b[:]); err != nil { u1 := strings.ReplaceAll(uuid.New().String(), "-", "") u2 := strings.ReplaceAll(uuid.New().String(), "-", "")[:16] return u1 + "-" + u2 + "-1" } traceID := hex.EncodeToString(b[:16]) spanID := hex.EncodeToString(b[16:24]) return traceID + "-" + spanID + "-1"} const defaultMaxSessionTurnCounters = 5000 var sessionTurnLRU = cache.NewBoundedLRU[string, *atomic.Uint64](defaultMaxSessionTurnCounters, nil) // NextDevinSessionTurnIndex returns the next 0-based request ordinal for a session (Field 15.2).// In native devin-cli, the counter is process-scoped per session:// First request in a session returns 0 (which is omitted on the wire).// Subsequent requests return 1, 2, 3... monotonically.func NextDevinSessionTurnIndex(sessionID string) int { cleanID := strings.TrimSpace(sessionID) if cleanID == "" { return 0 } counter := sessionTurnLRU.GetOrAdd(cleanID, func() *atomic.Uint64 { return &atomic.Uint64{} }) return int(counter.Add(1) - 1)} // ResetDevinSessionTurnIndex clears the session counter (used for testing or explicit session reset).func ResetDevinSessionTurnIndex(sessionID string) { sessionTurnLRU.Delete(strings.TrimSpace(sessionID))} // WrapConnectEnvelope wraps raw payload bytes into a standard 5-byte Connect envelope:// [1 byte flag: 0x00] + [4 byte big-endian length] + [payload].func WrapConnectEnvelope(protoBytes []byte) []byte { return WrapConnectEnvelopeWithFlag(ConnectFlagData, protoBytes)} // WrapConnectEnvelopeWithFlag wraps payload bytes with an explicit Connect flag.func WrapConnectEnvelopeWithFlag(flag byte, protoBytes []byte) []byte { header := make([]byte, 5, 5+len(protoBytes)) header[0] = flag binary.BigEndian.PutUint32(header[1:5], uint32(len(protoBytes))) return append(header, protoBytes...)} // ReadConnectFrame reads a single framed message from a Connect-proto stream.func ReadConnectFrame(r io.Reader) (flag byte, payload []byte, err error) { header := make([]byte, 5) if _, err := io.ReadFull(r, header); err != nil { return 0, nil, err } flag = header[0] if flag != ConnectFlagData && flag != ConnectFlagCompressed && flag != ConnectFlagEndStream && flag != (ConnectFlagCompressed|ConnectFlagEndStream) { return flag, nil, fmt.Errorf("invalid connect frame flag: 0x%02x", flag) } length := binary.BigEndian.Uint32(header[1:5]) if length > maxConnectFrameSize { return flag, nil, fmt.Errorf("connect frame length %d exceeds maximum limit (%d)", length, maxConnectFrameSize) } payload = make([]byte, length) if _, err := io.ReadFull(r, payload); err != nil { return 0, nil, err } if flag&ConnectFlagCompressed != 0 { gz, errGz := gzip.NewReader(bytes.NewReader(payload)) if errGz != nil { return flag, nil, fmt.Errorf("decompress gzip connect frame: %w", errGz) } defer func() { _ = gz.Close() }() initCap := int(length) * 4 if initCap > 4*1024*1024 { initCap = 4 * 1024 * 1024 } else if initCap < 4096 { initCap = 4096 } decompBuf := bytes.NewBuffer(make([]byte, 0, initCap)) limitedReader := io.LimitReader(gz, maxDecompressedFrameSize+1) if _, errRead := decompBuf.ReadFrom(limitedReader); errRead != nil { return flag, nil, fmt.Errorf("read decompressed connect frame: %w", errRead) } if decompBuf.Len() > maxDecompressedFrameSize { return flag, nil, fmt.Errorf("decompressed frame size exceeds maximum limit (%d)", maxDecompressedFrameSize) } payload = decompBuf.Bytes() } return flag, payload, nil} // BuildDevinClientMetadataBytes constructs the serialized bytes for Field 1 (ClientMetadata).func BuildDevinClientMetadataBytes(sessionToken, deviceSeed, osName string) []byte { if osName == "" { osName = runtime.GOOS } deviceFingerprint := GenerateDevinDeviceFingerprint(deviceSeed) var f1Bytes []byte f1Bytes = protowire.AppendTag(f1Bytes, 1, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, "devin-cli") f1Bytes = protowire.AppendTag(f1Bytes, 2, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientVersion) f1Bytes = protowire.AppendTag(f1Bytes, 3, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, sessionToken) f1Bytes = protowire.AppendTag(f1Bytes, 4, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, "en") f1Bytes = protowire.AppendTag(f1Bytes, 5, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, osName) f1Bytes = protowire.AppendTag(f1Bytes, 7, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientVersion) f1Bytes = protowire.AppendTag(f1Bytes, 12, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientName) f1Bytes = protowire.AppendTag(f1Bytes, 28, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientName) f1Bytes = protowire.AppendTag(f1Bytes, 31, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, deviceFingerprint) return f1Bytes} // BuildDevinGetChatMessageRequest encodes an entire GetChatMessageRequest protobuf payload.func BuildDevinGetChatMessageRequest( sessionToken string, deviceSeed string, chatModelUID string, systemPrompt string, prompts []DevinPrompt, tools []DevinTool, temperature *float64, maxTokens int, sessionID string, cascadeID string, matcher *SensitiveWordMatcher,) []byte { if maxTokens <= 0 { maxTokens = DevinDefaultMaxTokens } if sessionID == "" { sessionID = uuid.New().String() } if cascadeID == "" { cascadeID = sessionID } osName := runtime.GOOS estimatedSize := 4096 + len(systemPrompt) for _, p := range prompts { estimatedSize += 256 + len(p.Content) } reqBytes := make([]byte, 0, estimatedSize) // 1. ClientMetadata (Field 1) f1Bytes := BuildDevinClientMetadataBytes(sessionToken, deviceSeed, osName) reqBytes = protowire.AppendTag(reqBytes, 1, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f1Bytes) // 2. System prompt (Field 2) if systemPrompt != "" { sanitized := SanitizeDevinSystemPrompt(systemPrompt, matcher) if sanitized != "" { reqBytes = protowire.AppendTag(reqBytes, 2, protowire.BytesType) reqBytes = protowire.AppendString(reqBytes, sanitized) } } // 3. Repeated History Prompts (Field 3) for _, p := range prompts { var pBytes []byte msgID := p.MessageID if msgID == "" { msgID = uuid.New().String() } pBytes = protowire.AppendTag(pBytes, 1, protowire.BytesType) pBytes = protowire.AppendString(pBytes, msgID) source := p.Source if source <= 0 { source = 1 // default to user } pBytes = protowire.AppendTag(pBytes, 2, protowire.VarintType) pBytes = protowire.AppendVarint(pBytes, uint64(source)) content := p.Content pBytes = protowire.AppendTag(pBytes, 3, protowire.BytesType) pBytes = protowire.AppendString(pBytes, content) for _, tc := range p.ToolCalls { var tcBytes []byte if tc.ID != "" { tcBytes = protowire.AppendTag(tcBytes, 1, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.ID) } if tc.Name != "" { tcBytes = protowire.AppendTag(tcBytes, 2, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.Name) } if tc.Arguments != "" { tcBytes = protowire.AppendTag(tcBytes, 3, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.Arguments) } pBytes = protowire.AppendTag(pBytes, 6, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, tcBytes) } if p.ToolCallID != "" { pBytes = protowire.AppendTag(pBytes, 7, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.ToolCallID) } for _, img := range p.Images { data := strings.TrimSpace(img.Base64Data) if data == "" { continue } var imgBytes []byte imgBytes = protowire.AppendTag(imgBytes, 1, protowire.BytesType) imgBytes = protowire.AppendString(imgBytes, data) mime := strings.TrimSpace(img.MimeType) if mime == "" { mime = "image/png" } imgBytes = protowire.AppendTag(imgBytes, 2, protowire.BytesType) imgBytes = protowire.AppendString(imgBytes, mime) pBytes = protowire.AppendTag(pBytes, 10, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, imgBytes) } if p.Thinking != "" { pBytes = protowire.AppendTag(pBytes, 11, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.Thinking) } if len(p.Signature) > 0 { pBytes = protowire.AppendTag(pBytes, 12, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, p.Signature) } if p.SignatureType != "" { pBytes = protowire.AppendTag(pBytes, 18, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.SignatureType) } reqBytes = protowire.AppendTag(reqBytes, 3, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, pBytes) } // 4. Fixed flags (Field 7: Varint 5) reqBytes = protowire.AppendTag(reqBytes, 7, protowire.VarintType) reqBytes = protowire.AppendVarint(reqBytes, 5) // 5. Completion config (Field 8) var f8Bytes []byte f8Bytes = protowire.AppendTag(f8Bytes, 1, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 1) f8Bytes = protowire.AppendTag(f8Bytes, 2, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, uint64(maxTokens)) f8Bytes = protowire.AppendTag(f8Bytes, 3, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 400) tempVal := 1.0 if temperature != nil { tempVal = *temperature } f8Bytes = protowire.AppendTag(f8Bytes, 5, protowire.Fixed64Type) f8Bytes = protowire.AppendFixed64(f8Bytes, math.Float64bits(tempVal)) f8Bytes = protowire.AppendTag(f8Bytes, 7, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 40) f8Bytes = protowire.AppendTag(f8Bytes, 8, protowire.Fixed64Type) f8Bytes = protowire.AppendFixed64(f8Bytes, math.Float64bits(float64(float32(0.95)))) reqBytes = protowire.AppendTag(reqBytes, 8, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f8Bytes) // 6. Repeated Tools (Field 10) for _, tool := range tools { var tBytes []byte if tool.Name != "" { tBytes = protowire.AppendTag(tBytes, 1, protowire.BytesType) tBytes = protowire.AppendString(tBytes, tool.Name) } desc := tool.Description // Claude Code subagent tool descriptions hardcode snake_case "task_id", but Devin's // upstream tool execution environment strictly expects camelCase "taskId". Normalizing // the prompt description prevents the model from generating incompatible parameter names. if strings.Contains(desc, "Takes a task_id parameter identifying the task") { desc = strings.ReplaceAll(desc, "Takes a task_id parameter identifying the task", "Takes a taskId parameter identifying the task") } if desc != "" { tBytes = protowire.AppendTag(tBytes, 2, protowire.BytesType) tBytes = protowire.AppendString(tBytes, desc) } if len(tool.Parameters) > 0 { tBytes = protowire.AppendTag(tBytes, 3, protowire.BytesType) tBytes = protowire.AppendBytes(tBytes, tool.Parameters) } reqBytes = protowire.AppendTag(reqBytes, 10, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, tBytes) } // 7. Thread session metadata (Field 15) // In native devin-cli: // Field 1: sessionID (UUID string) // Field 2: turnIndex (per-session request ordinal, omitted when 0) // Field 3: 4 (varint) // Field 4: 14 (emitted conditionally on user-turn boundaries) turnIndex := NextDevinSessionTurnIndex(sessionID) var f15Bytes []byte f15Bytes = protowire.AppendTag(f15Bytes, 1, protowire.BytesType) f15Bytes = protowire.AppendString(f15Bytes, sessionID) if turnIndex > 0 { f15Bytes = protowire.AppendTag(f15Bytes, 2, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, uint64(turnIndex)) } f15Bytes = protowire.AppendTag(f15Bytes, 3, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, 4) // In native devin-cli, Field 15.4=14 is emitted on user-turn boundaries if len(prompts) > 0 && prompts[len(prompts)-1].Source == 1 { if turnIndex == 0 || len(prompts) < 2 || prompts[len(prompts)-2].Source != 1 { f15Bytes = protowire.AppendTag(f15Bytes, 4, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, 14) } } reqBytes = protowire.AppendTag(reqBytes, 15, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f15Bytes) // 8. Cascade ID (Field 16: session-stable prompt cache key)Lines 1–500 of 1233. Load more
package helps import ( "bytes" "compress/gzip" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "fmt" "io" "math" "net/http" "runtime" "strings" "sync/atomic" "unicode/utf8" "github.com/google/uuid" "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "google.golang.org/protobuf/encoding/protowire" ) const ( // ConnectFlagData marks an uncompressed Connect-proto data frame. ConnectFlagData byte = 0x00 // ConnectFlagCompressed marks a gzipped Connect-proto frame. ConnectFlagCompressed byte = 0x01 // ConnectFlagEndStream marks the terminal Connect-proto trailer frame. ConnectFlagEndStream byte = 0x02 // DevinDefaultBaseURL is the default upstream Codeium/Devin endpoint. DevinDefaultBaseURL = "https://server.codeium.com" // DevinChatPath is the Connect-RPC endpoint for chat completions. DevinChatPath = "/exa.api_server_pb.ApiServerService/GetChatMessage" // DevinDefaultClientName is the client identifier declared in metadata. DevinDefaultClientName = "chisel" // DevinDefaultClientVersion is the client version declared in metadata. DevinDefaultClientVersion = "3000.10.21" // DevinFingerprintHexLen is the mandatory 732-hex-character length for metadata #31. DevinFingerprintHexLen = 732 // DevinDefaultMaxTokens is the fallback max completion tokens. DevinDefaultMaxTokens = 128000 maxConnectFrameSize = 16 * 1024 * 1024 maxDecompressedFrameSize = 64 * 1024 * 1024 ) // DevinTool represents a tool definition in GetChatMessageRequest. type DevinTool struct { Name string Description string Parameters []byte } // DevinToolCall represents a tool call in a ChatMessagePrompt. type DevinToolCall struct { ID string Name string Arguments string } // DevinToolCallDelta represents a streaming tool call chunk from response Field 6. type DevinToolCallDelta struct { ID string Name string Arguments string Index int } // DevinImage represents an image attachment in a DevinPrompt (Prompt #10). type DevinImage struct { Base64Data string MimeType string } // DevinPrompt represents a single turn in the request history (repeated Field 3). type DevinPrompt struct { MessageID string Source int // 1=user, 2=assistant, 4=tool Content string Images []DevinImage ToolCalls []DevinToolCall ToolCallID string // For source=4 (tool result) Thinking string Signature []byte SignatureType string } // DevinUsage captures token accounting from response Field 7. type DevinUsage struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` CachedTokens int64 `json:"cached_tokens"` StatusCode uint64 `json:"status_code,omitempty"` RequestID string `json:"request_id,omitempty"` ModelName string `json:"model_name,omitempty"` Headers map[string]string `json:"headers,omitempty"` } // DevinFrameResult represents decoded content from a single Connect-proto frame. type DevinFrameResult struct { OutputID string Timestamp uint64 ContentText string DeltaTokens uint64 StopReason uint64 // 2/4=stop, 10=tool_calls ToolCallDeltas []DevinToolCallDelta ThinkingText string DeltaSignature []byte DeltaSignatureType string Latency float64 MessageID string Usage *DevinUsage ResponseDimensionGroups [][]byte UnknownFieldNumbers []int } // GenerateDevinDeviceFingerprint generates a 732-character hex device fingerprint. // When seed is empty, it generates a cryptographically random 732-character hex string per request (matching native devin-cli). // When seed is provided, it derives a deterministic 732-character hex fingerprint. func GenerateDevinDeviceFingerprint(seed string) string { if seed == "" { var b [DevinFingerprintHexLen / 2]byte if _, err := rand.Read(b[:]); err == nil { return hex.EncodeToString(b[:]) } seed = uuid.New().String() } var sb strings.Builder counter := 0 for sb.Len() < DevinFingerprintHexLen { h := sha256.Sum256([]byte(fmt.Sprintf("%s-%d", seed, counter))) sb.WriteString(hex.EncodeToString(h[:])) counter++ } // Truncate to exact 732 chars (11 full 64-char sha256 hex blocks + 28 chars of the 12th block) to match Devin CLI. return sb.String()[:DevinFingerprintHexLen] } // GenerateDevinSentryTrace generates a Sentry distributed tracing header in the format: // "<32-hex-trace-id>-<16-hex-span-id>-1" func GenerateDevinSentryTrace() string { var b [24]byte if _, err := rand.Read(b[:]); err != nil { u1 := strings.ReplaceAll(uuid.New().String(), "-", "") u2 := strings.ReplaceAll(uuid.New().String(), "-", "")[:16] return u1 + "-" + u2 + "-1" } traceID := hex.EncodeToString(b[:16]) spanID := hex.EncodeToString(b[16:24]) return traceID + "-" + spanID + "-1" } const defaultMaxSessionTurnCounters = 5000 var sessionTurnLRU = cache.NewBoundedLRU[string, *atomic.Uint64](defaultMaxSessionTurnCounters, nil) // NextDevinSessionTurnIndex returns the next 0-based request ordinal for a session (Field 15.2). // In native devin-cli, the counter is process-scoped per session: // First request in a session returns 0 (which is omitted on the wire). // Subsequent requests return 1, 2, 3... monotonically. func NextDevinSessionTurnIndex(sessionID string) int { cleanID := strings.TrimSpace(sessionID) if cleanID == "" { return 0 } counter := sessionTurnLRU.GetOrAdd(cleanID, func() *atomic.Uint64 { return &atomic.Uint64{} }) return int(counter.Add(1) - 1) } // ResetDevinSessionTurnIndex clears the session counter (used for testing or explicit session reset). func ResetDevinSessionTurnIndex(sessionID string) { sessionTurnLRU.Delete(strings.TrimSpace(sessionID)) } // WrapConnectEnvelope wraps raw payload bytes into a standard 5-byte Connect envelope: // [1 byte flag: 0x00] + [4 byte big-endian length] + [payload]. func WrapConnectEnvelope(protoBytes []byte) []byte { return WrapConnectEnvelopeWithFlag(ConnectFlagData, protoBytes) } // WrapConnectEnvelopeWithFlag wraps payload bytes with an explicit Connect flag. func WrapConnectEnvelopeWithFlag(flag byte, protoBytes []byte) []byte { header := make([]byte, 5, 5+len(protoBytes)) header[0] = flag binary.BigEndian.PutUint32(header[1:5], uint32(len(protoBytes))) return append(header, protoBytes...) } // ReadConnectFrame reads a single framed message from a Connect-proto stream. func ReadConnectFrame(r io.Reader) (flag byte, payload []byte, err error) { header := make([]byte, 5) if _, err := io.ReadFull(r, header); err != nil { return 0, nil, err } flag = header[0] if flag != ConnectFlagData && flag != ConnectFlagCompressed && flag != ConnectFlagEndStream && flag != (ConnectFlagCompressed|ConnectFlagEndStream) { return flag, nil, fmt.Errorf("invalid connect frame flag: 0x%02x", flag) } length := binary.BigEndian.Uint32(header[1:5]) if length > maxConnectFrameSize { return flag, nil, fmt.Errorf("connect frame length %d exceeds maximum limit (%d)", length, maxConnectFrameSize) } payload = make([]byte, length) if _, err := io.ReadFull(r, payload); err != nil { return 0, nil, err } if flag&ConnectFlagCompressed != 0 { gz, errGz := gzip.NewReader(bytes.NewReader(payload)) if errGz != nil { return flag, nil, fmt.Errorf("decompress gzip connect frame: %w", errGz) } defer func() { _ = gz.Close() }() initCap := int(length) * 4 if initCap > 4*1024*1024 { initCap = 4 * 1024 * 1024 } else if initCap < 4096 { initCap = 4096 } decompBuf := bytes.NewBuffer(make([]byte, 0, initCap)) limitedReader := io.LimitReader(gz, maxDecompressedFrameSize+1) if _, errRead := decompBuf.ReadFrom(limitedReader); errRead != nil { return flag, nil, fmt.Errorf("read decompressed connect frame: %w", errRead) } if decompBuf.Len() > maxDecompressedFrameSize { return flag, nil, fmt.Errorf("decompressed frame size exceeds maximum limit (%d)", maxDecompressedFrameSize) } payload = decompBuf.Bytes() } return flag, payload, nil } // BuildDevinClientMetadataBytes constructs the serialized bytes for Field 1 (ClientMetadata). func BuildDevinClientMetadataBytes(sessionToken, deviceSeed, osName string) []byte { if osName == "" { osName = runtime.GOOS } deviceFingerprint := GenerateDevinDeviceFingerprint(deviceSeed) var f1Bytes []byte f1Bytes = protowire.AppendTag(f1Bytes, 1, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, "devin-cli") f1Bytes = protowire.AppendTag(f1Bytes, 2, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientVersion) f1Bytes = protowire.AppendTag(f1Bytes, 3, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, sessionToken) f1Bytes = protowire.AppendTag(f1Bytes, 4, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, "en") f1Bytes = protowire.AppendTag(f1Bytes, 5, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, osName) f1Bytes = protowire.AppendTag(f1Bytes, 7, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientVersion) f1Bytes = protowire.AppendTag(f1Bytes, 12, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientName) f1Bytes = protowire.AppendTag(f1Bytes, 28, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, DevinDefaultClientName) f1Bytes = protowire.AppendTag(f1Bytes, 31, protowire.BytesType) f1Bytes = protowire.AppendString(f1Bytes, deviceFingerprint) return f1Bytes } // BuildDevinGetChatMessageRequest encodes an entire GetChatMessageRequest protobuf payload. func BuildDevinGetChatMessageRequest( sessionToken string, deviceSeed string, chatModelUID string, systemPrompt string, prompts []DevinPrompt, tools []DevinTool, temperature *float64, maxTokens int, sessionID string, cascadeID string, matcher *SensitiveWordMatcher, ) []byte { if maxTokens <= 0 { maxTokens = DevinDefaultMaxTokens } if sessionID == "" { sessionID = uuid.New().String() } if cascadeID == "" { cascadeID = sessionID } osName := runtime.GOOS estimatedSize := 4096 + len(systemPrompt) for _, p := range prompts { estimatedSize += 256 + len(p.Content) } reqBytes := make([]byte, 0, estimatedSize) // 1. ClientMetadata (Field 1) f1Bytes := BuildDevinClientMetadataBytes(sessionToken, deviceSeed, osName) reqBytes = protowire.AppendTag(reqBytes, 1, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f1Bytes) // 2. System prompt (Field 2) if systemPrompt != "" { sanitized := SanitizeDevinSystemPrompt(systemPrompt, matcher) if sanitized != "" { reqBytes = protowire.AppendTag(reqBytes, 2, protowire.BytesType) reqBytes = protowire.AppendString(reqBytes, sanitized) } } // 3. Repeated History Prompts (Field 3) for _, p := range prompts { var pBytes []byte msgID := p.MessageID if msgID == "" { msgID = uuid.New().String() } pBytes = protowire.AppendTag(pBytes, 1, protowire.BytesType) pBytes = protowire.AppendString(pBytes, msgID) source := p.Source if source <= 0 { source = 1 // default to user } pBytes = protowire.AppendTag(pBytes, 2, protowire.VarintType) pBytes = protowire.AppendVarint(pBytes, uint64(source)) content := p.Content pBytes = protowire.AppendTag(pBytes, 3, protowire.BytesType) pBytes = protowire.AppendString(pBytes, content) for _, tc := range p.ToolCalls { var tcBytes []byte if tc.ID != "" { tcBytes = protowire.AppendTag(tcBytes, 1, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.ID) } if tc.Name != "" { tcBytes = protowire.AppendTag(tcBytes, 2, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.Name) } if tc.Arguments != "" { tcBytes = protowire.AppendTag(tcBytes, 3, protowire.BytesType) tcBytes = protowire.AppendString(tcBytes, tc.Arguments) } pBytes = protowire.AppendTag(pBytes, 6, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, tcBytes) } if p.ToolCallID != "" { pBytes = protowire.AppendTag(pBytes, 7, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.ToolCallID) } for _, img := range p.Images { data := strings.TrimSpace(img.Base64Data) if data == "" { continue } var imgBytes []byte imgBytes = protowire.AppendTag(imgBytes, 1, protowire.BytesType) imgBytes = protowire.AppendString(imgBytes, data) mime := strings.TrimSpace(img.MimeType) if mime == "" { mime = "image/png" } imgBytes = protowire.AppendTag(imgBytes, 2, protowire.BytesType) imgBytes = protowire.AppendString(imgBytes, mime) pBytes = protowire.AppendTag(pBytes, 10, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, imgBytes) } if p.Thinking != "" { pBytes = protowire.AppendTag(pBytes, 11, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.Thinking) } if len(p.Signature) > 0 { pBytes = protowire.AppendTag(pBytes, 12, protowire.BytesType) pBytes = protowire.AppendBytes(pBytes, p.Signature) } if p.SignatureType != "" { pBytes = protowire.AppendTag(pBytes, 18, protowire.BytesType) pBytes = protowire.AppendString(pBytes, p.SignatureType) } reqBytes = protowire.AppendTag(reqBytes, 3, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, pBytes) } // 4. Fixed flags (Field 7: Varint 5) reqBytes = protowire.AppendTag(reqBytes, 7, protowire.VarintType) reqBytes = protowire.AppendVarint(reqBytes, 5) // 5. Completion config (Field 8) var f8Bytes []byte f8Bytes = protowire.AppendTag(f8Bytes, 1, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 1) f8Bytes = protowire.AppendTag(f8Bytes, 2, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, uint64(maxTokens)) f8Bytes = protowire.AppendTag(f8Bytes, 3, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 400) tempVal := 1.0 if temperature != nil { tempVal = *temperature } f8Bytes = protowire.AppendTag(f8Bytes, 5, protowire.Fixed64Type) f8Bytes = protowire.AppendFixed64(f8Bytes, math.Float64bits(tempVal)) f8Bytes = protowire.AppendTag(f8Bytes, 7, protowire.VarintType) f8Bytes = protowire.AppendVarint(f8Bytes, 40) f8Bytes = protowire.AppendTag(f8Bytes, 8, protowire.Fixed64Type) f8Bytes = protowire.AppendFixed64(f8Bytes, math.Float64bits(float64(float32(0.95)))) reqBytes = protowire.AppendTag(reqBytes, 8, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f8Bytes) // 6. Repeated Tools (Field 10) for _, tool := range tools { var tBytes []byte if tool.Name != "" { tBytes = protowire.AppendTag(tBytes, 1, protowire.BytesType) tBytes = protowire.AppendString(tBytes, tool.Name) } desc := tool.Description // Claude Code subagent tool descriptions hardcode snake_case "task_id", but Devin's // upstream tool execution environment strictly expects camelCase "taskId". Normalizing // the prompt description prevents the model from generating incompatible parameter names. if strings.Contains(desc, "Takes a task_id parameter identifying the task") { desc = strings.ReplaceAll(desc, "Takes a task_id parameter identifying the task", "Takes a taskId parameter identifying the task") } if desc != "" { tBytes = protowire.AppendTag(tBytes, 2, protowire.BytesType) tBytes = protowire.AppendString(tBytes, desc) } if len(tool.Parameters) > 0 { tBytes = protowire.AppendTag(tBytes, 3, protowire.BytesType) tBytes = protowire.AppendBytes(tBytes, tool.Parameters) } reqBytes = protowire.AppendTag(reqBytes, 10, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, tBytes) } // 7. Thread session metadata (Field 15) // In native devin-cli: // Field 1: sessionID (UUID string) // Field 2: turnIndex (per-session request ordinal, omitted when 0) // Field 3: 4 (varint) // Field 4: 14 (emitted conditionally on user-turn boundaries) turnIndex := NextDevinSessionTurnIndex(sessionID) var f15Bytes []byte f15Bytes = protowire.AppendTag(f15Bytes, 1, protowire.BytesType) f15Bytes = protowire.AppendString(f15Bytes, sessionID) if turnIndex > 0 { f15Bytes = protowire.AppendTag(f15Bytes, 2, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, uint64(turnIndex)) } f15Bytes = protowire.AppendTag(f15Bytes, 3, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, 4) // In native devin-cli, Field 15.4=14 is emitted on user-turn boundaries if len(prompts) > 0 && prompts[len(prompts)-1].Source == 1 { if turnIndex == 0 || len(prompts) < 2 || prompts[len(prompts)-2].Source != 1 { f15Bytes = protowire.AppendTag(f15Bytes, 4, protowire.VarintType) f15Bytes = protowire.AppendVarint(f15Bytes, 14) } } reqBytes = protowire.AppendTag(reqBytes, 15, protowire.BytesType) reqBytes = protowire.AppendBytes(reqBytes, f15Bytes) // 8. Cascade ID (Field 16: session-stable prompt cache key)