Merge pull request #5806 from router-for-me/feat/devin-followups

fix(devin): wire protocol submessage decoding, usage fallbacks, model uniqueness, and APICall body token replacement
This commit is contained in:
Luis Pater
2026-09-14 09:39:49 +08:00
committed by GitHub
11 changed files with 667 additions and 64 deletions

View File

@@ -3,6 +3,7 @@ package management
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -140,26 +141,48 @@ func (h *Handler) APICall(c *gin.Context) {
var token string
var tokenResolved bool
var tokenErr error
for key, value := range reqHeaders {
if !strings.Contains(value, "$TOKEN$") {
continue
}
resolveToken := func() error {
if !tokenResolved {
token, tokenErr = h.resolveTokenForAuth(c.Request.Context(), auth, requestProxyURL)
tokenResolved = true
}
if auth != nil && token == "" {
if tokenErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "auth token refresh failed"})
return
return errors.New("auth token refresh failed")
}
c.JSON(http.StatusBadRequest, gin.H{"error": "auth token not found"})
return
return errors.New("auth token not found")
}
if token == "" {
return nil
}
for key, value := range reqHeaders {
if !strings.Contains(value, "$TOKEN$") {
continue
}
reqHeaders[key] = strings.ReplaceAll(value, "$TOKEN$", token)
if errToken := resolveToken(); errToken != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": errToken.Error()})
return
}
if token != "" {
reqHeaders[key] = strings.ReplaceAll(value, "$TOKEN$", token)
}
}
if strings.Contains(body.Data, "$TOKEN$") {
if errToken := resolveToken(); errToken != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": errToken.Error()})
return
}
if token != "" {
replacement := token
if json.Valid([]byte(body.Data)) && strings.ContainsAny(token, "\"\\\r\n\t") {
if b, errMarshal := json.Marshal(token); errMarshal == nil && len(b) >= 2 {
replacement = string(b[1 : len(b)-1])
}
}
body.Data = strings.ReplaceAll(body.Data, "$TOKEN$", replacement)
}
}
var requestBody io.Reader
@@ -237,6 +260,9 @@ func tokenValueForAuth(auth *coreauth.Auth) string {
if v := strings.TrimSpace(auth.Attributes["api_key"]); v != "" {
return v
}
if v := strings.TrimSpace(auth.Attributes["session_token"]); v != "" {
return v
}
}
return ""
}
@@ -457,6 +483,12 @@ func tokenValueFromMetadata(metadata map[string]any) string {
if v, ok := metadata["id_token"].(string); ok && strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
if v, ok := metadata["api_key"].(string); ok && strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
if v, ok := metadata["session_token"].(string); ok && strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
if v, ok := metadata["cookie"].(string); ok && strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}

View File

@@ -3,6 +3,7 @@ package management
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
@@ -315,3 +316,64 @@ func TestAuthByIndexDistinguishesSharedAPIKeysAcrossProviders(t *testing.T) {
t.Fatalf("authByIndex(compat) returned %q, want %q", gotCompat.ID, compatAuth.ID)
}
}
func TestAPICallReplacesTokenInBodyData(t *testing.T) {
t.Parallel()
var receivedBody string
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
receivedBody = string(b)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ok":true}`))
}))
defer upstreamServer.Close()
manager := coreauth.NewManager(nil, &coreauth.RoundRobinSelector{}, nil)
devinAuth := &coreauth.Auth{
ID: "devin-test.json",
Provider: "devin",
Attributes: map[string]string{
"api_key": "secret-session-token-xyz",
},
Metadata: map[string]any{
"type": "devin",
"api_key": "secret-session-token-xyz",
},
}
if _, errRegister := manager.Register(context.Background(), devinAuth); errRegister != nil {
t.Fatalf("register devin auth: %v", errRegister)
}
authIndex := devinAuth.EnsureIndex()
h := &Handler{
cfg: &config.Config{},
authManager: manager,
}
router := gin.New()
router.POST("/", h.APICall)
reqPayload := map[string]any{
"method": "POST",
"url": upstreamServer.URL,
"auth_index": authIndex,
"header": map[string]string{
"Content-Type": "application/json",
},
"data": `{"metadata":{"apiKey":"$TOKEN$","ideName":"chisel"}}`,
}
reqBytes, _ := json.Marshal(reqPayload)
recorder := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(reqBytes)))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, req)
if recorder.Code != http.StatusOK {
t.Fatalf("status code = %d, want %d; body = %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
expectedBody := `{"metadata":{"apiKey":"secret-session-token-xyz","ideName":"chisel"}}`
if receivedBody != expectedBody {
t.Fatalf("received body = %q, want %q", receivedBody, expectedBody)
}
}

View File

@@ -928,17 +928,17 @@ func (h *Host) rollbackReplacement(lp *loadedPlugin, item runtimeItemConfig) (ca
return capabilityRecord{}, pluginFile{}, false
}
return capabilityRecord{
id: lp.id,
path: lp.path,
version: lp.version,
priority: item.Priority,
meta: plugin.Metadata,
plugin: plugin,
}, pluginFile{
ID: lp.id,
Path: lp.path,
Version: lp.version,
}, true
id: lp.id,
path: lp.path,
version: lp.version,
priority: item.Priority,
meta: plugin.Metadata,
plugin: plugin,
}, pluginFile{
ID: lp.id,
Path: lp.path,
Version: lp.version,
}, true
}
func (h *Host) callRegister(ctx context.Context, lp *loadedPlugin, item runtimeItemConfig) (pluginapi.Plugin, bool) {

View File

@@ -163,6 +163,7 @@ func sanitizeAndValidateDevinModels(models []*ModelInfo) ([]*ModelInfo, error) {
if !strings.HasPrefix(strings.ToLower(id), "devin/") {
id = "devin/" + id
}
id = strings.ToLower(id)
m.ID = id
if _, exists := seen[id]; exists {
return nil, fmt.Errorf("duplicate model id: %q", id)

View File

@@ -98,13 +98,17 @@ func TestValidateDevinModelsJSON(t *testing.T) {
}
})
t.Run("duplicate model id", func(t *testing.T) {
data := []byte(`{"devin": [{"id": "devin/swe-2"}, {"id": "devin/swe-2"}]}`)
_, err := ValidateDevinModelsJSON(data)
if err == nil {
t.Fatal("expected error on duplicate model id, got nil")
}
})
for name, data := range map[string][]byte{
"exact duplicate": []byte(`{"devin": [{"id": "devin/swe-2"}, {"id": "devin/swe-2"}]}`),
"case duplicate": []byte(`{"devin": [{"id": "devin/SWE-2"}, {"id": "devin/swe-2"}]}`),
} {
t.Run("duplicate model id/"+name, func(t *testing.T) {
_, err := ValidateDevinModelsJSON(data)
if err == nil {
t.Fatal("expected error on duplicate model id, got nil")
}
})
}
}
func TestEmbeddedDevinModelsLoadedOnStartup(t *testing.T) {

View File

@@ -33,17 +33,17 @@ func claudeReplayTestAuth(baseURL string) *cliproxyauth.Auth {
func claudeReplayTestRequest(payload []byte, sessionID string, isCompat bool, source sdktranslator.Format) (cliproxyexecutor.Request, cliproxyexecutor.Options) {
return cliproxyexecutor.Request{
Model: "claude-synthetic-4772",
Payload: payload,
Metadata: map[string]any{
claudeReplayResolvedModelInfoKey: &registry.ModelInfo{IsCompat: isCompat},
},
}, cliproxyexecutor.Options{
SourceFormat: source,
Metadata: map[string]any{
cliproxyexecutor.ExecutionSessionMetadataKey: sessionID,
},
}
Model: "claude-synthetic-4772",
Payload: payload,
Metadata: map[string]any{
claudeReplayResolvedModelInfoKey: &registry.ModelInfo{IsCompat: isCompat},
},
}, cliproxyexecutor.Options{
SourceFormat: source,
Metadata: map[string]any{
cliproxyexecutor.ExecutionSessionMetadataKey: sessionID,
},
}
}
func TestClaudeThinkingReplayEnabledRequiresCompatClaudeAPIKey(t *testing.T) {

View File

@@ -37,12 +37,12 @@ func codexTestAuth(baseURL string) *cliproxyauth.Auth {
func codexTestRequest() (cliproxyexecutor.Request, cliproxyexecutor.Options) {
return cliproxyexecutor.Request{
Model: "gpt-5.6-terra",
Payload: []byte(`{"model":"gpt-5.6-terra","input":"hello"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
}
Model: "gpt-5.6-terra",
Payload: []byte(`{"model":"gpt-5.6-terra","input":"hello"}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
Stream: true,
}
}
// codexSSEServer streams the supplied event payloads as an HTTP 200 SSE response.
@@ -83,11 +83,11 @@ func codexWebsocketServer(t *testing.T, frames ...string) *httptest.Server {
func codexWebsocketRequest() (cliproxyexecutor.Request, cliproxyexecutor.Options) {
return cliproxyexecutor.Request{
Model: "gpt-5.6-terra",
Payload: []byte(`{"model":"gpt-5.6-terra","input":[{"type":"message","role":"user","content":"hello"}]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
}
Model: "gpt-5.6-terra",
Payload: []byte(`{"model":"gpt-5.6-terra","input":[{"type":"message","role":"user","content":"hello"}]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FromString("openai-response"),
}
}
// drainChunks collects every payload and the first error from a stream result.

View File

@@ -568,7 +568,49 @@ func (e *DevinExecutor) streamDevinFrames(
}
if frameRes.Usage != nil {
finalUsage = frameRes.Usage
if finalUsage == nil {
finalUsage = frameRes.Usage
} else {
if frameRes.Usage.PromptTokens > 0 {
finalUsage.PromptTokens = frameRes.Usage.PromptTokens
}
if frameRes.Usage.CompletionTokens > 0 {
finalUsage.CompletionTokens = frameRes.Usage.CompletionTokens
}
if frameRes.Usage.CachedTokens > 0 {
finalUsage.CachedTokens = frameRes.Usage.CachedTokens
}
if frameRes.Usage.RequestID != "" {
finalUsage.RequestID = frameRes.Usage.RequestID
}
if frameRes.Usage.ModelName != "" {
finalUsage.ModelName = frameRes.Usage.ModelName
}
if len(frameRes.Usage.Headers) > 0 {
if finalUsage.Headers == nil {
finalUsage.Headers = make(map[string]string, len(frameRes.Usage.Headers))
}
for hk, hv := range frameRes.Usage.Headers {
finalUsage.Headers[hk] = hv
}
}
}
}
if len(frameRes.ResponseDimensionGroups) > 0 && (finalUsage == nil || finalUsage.PromptTokens == 0 || finalUsage.CompletionTokens == 0 || finalUsage.CachedTokens == 0) {
if inTok, outTok, cachedTok, ok := helps.ParseDevinResponseDimensionGroups(frameRes.ResponseDimensionGroups...); ok {
if finalUsage == nil {
finalUsage = &helps.DevinUsage{}
}
if finalUsage.PromptTokens == 0 {
finalUsage.PromptTokens = inTok
}
if finalUsage.CompletionTokens == 0 {
finalUsage.CompletionTokens = outTok
}
if finalUsage.CachedTokens == 0 {
finalUsage.CachedTokens = cachedTok
}
}
}
if len(frameRes.DeltaSignature) > 0 {
accumulatedSignature = append(accumulatedSignature, frameRes.DeltaSignature...)
@@ -905,7 +947,49 @@ func consumeDevinFramesToInteractions(body io.Reader, model, chatModelUID string
}
if frameRes.Usage != nil {
finalUsage = frameRes.Usage
if finalUsage == nil {
finalUsage = frameRes.Usage
} else {
if frameRes.Usage.PromptTokens > 0 {
finalUsage.PromptTokens = frameRes.Usage.PromptTokens
}
if frameRes.Usage.CompletionTokens > 0 {
finalUsage.CompletionTokens = frameRes.Usage.CompletionTokens
}
if frameRes.Usage.CachedTokens > 0 {
finalUsage.CachedTokens = frameRes.Usage.CachedTokens
}
if frameRes.Usage.RequestID != "" {
finalUsage.RequestID = frameRes.Usage.RequestID
}
if frameRes.Usage.ModelName != "" {
finalUsage.ModelName = frameRes.Usage.ModelName
}
if len(frameRes.Usage.Headers) > 0 {
if finalUsage.Headers == nil {
finalUsage.Headers = make(map[string]string, len(frameRes.Usage.Headers))
}
for hk, hv := range frameRes.Usage.Headers {
finalUsage.Headers[hk] = hv
}
}
}
}
if len(frameRes.ResponseDimensionGroups) > 0 && (finalUsage == nil || finalUsage.PromptTokens == 0 || finalUsage.CompletionTokens == 0 || finalUsage.CachedTokens == 0) {
if inTok, outTok, cachedTok, ok := helps.ParseDevinResponseDimensionGroups(frameRes.ResponseDimensionGroups...); ok {
if finalUsage == nil {
finalUsage = &helps.DevinUsage{}
}
if finalUsage.PromptTokens == 0 {
finalUsage.PromptTokens = inTok
}
if finalUsage.CompletionTokens == 0 {
finalUsage.CompletionTokens = outTok
}
if finalUsage.CachedTokens == 0 {
finalUsage.CachedTokens = cachedTok
}
}
}
if len(frameRes.DeltaSignature) > 0 {
accumulatedSignature = append(accumulatedSignature, frameRes.DeltaSignature...)

View File

@@ -94,12 +94,13 @@ type DevinPrompt struct {
// DevinUsage captures token accounting from response Field 7.
type DevinUsage struct {
PromptTokens int64
CompletionTokens int64
CachedTokens int64
StatusCode uint64
RequestID string
ModelName string
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.
@@ -116,7 +117,7 @@ type DevinFrameResult struct {
Latency float64
MessageID string
Usage *DevinUsage
ResponseDimensionGroups []byte
ResponseDimensionGroups [][]byte
UnknownFieldNumbers []int
}
@@ -587,7 +588,7 @@ func ParseDevinFrame(payload []byte) (DevinFrameResult, error) {
case 21:
res.DeltaSignatureType = string(val)
case 28:
res.ResponseDimensionGroups = val
res.ResponseDimensionGroups = append(res.ResponseDimensionGroups, val)
default:
res.UnknownFieldNumbers = append(res.UnknownFieldNumbers, int(num))
}
@@ -715,6 +716,42 @@ func parseDevinTimestamp(data []byte) uint64 {
return secs
}
// parseDevinHeaderField parses a repeated submessage in Field 7 (subfield 8) representing upstream response headers:
// Tag 1 (string): Header name (e.g. "x-request-id", "Request-Id", "openai-processing-ms")
// Tag 2 (string): Header value (e.g. "req_011Cf1JivhJrXDq9ycq7cEtH", "chatcmpl-...")
func parseDevinHeaderField(data []byte) (string, string) {
var key, val string
pos := 0
for pos < len(data) {
num, typ, n := protowire.ConsumeTag(data[pos:])
if n <= 0 {
break
}
pos += n
switch typ {
case protowire.BytesType:
b, bn := protowire.ConsumeBytes(data[pos:])
if bn <= 0 {
return key, val
}
pos += bn
switch num {
case 1:
key = string(b)
case 2:
val = string(b)
}
default:
nSkip := protowire.ConsumeFieldValue(num, typ, data[pos:])
if nSkip <= 0 {
return key, val
}
pos += nSkip
}
}
return key, val
}
func parseDevinUsageField(data []byte) *DevinUsage {
u := &DevinUsage{}
pos := 0
@@ -733,10 +770,12 @@ func parseDevinUsageField(data []byte) *DevinUsage {
}
pos += vn
switch num {
case 2: // Prompt tokens (uncached input)
u.PromptTokens = int64(v)
case 2: // Prompt tokens (uncached input from turn message)
u.PromptTokens += int64(v)
case 3: // Output tokens
u.CompletionTokens = int64(v)
case 4: // Additional context/system prompt tokens in OpenAI-family models (total prompt = 2 + 4)
u.PromptTokens += int64(v)
case 5: // Cache read tokens
u.CachedTokens = int64(v)
case 6: // Status code
@@ -750,7 +789,18 @@ func parseDevinUsageField(data []byte) *DevinUsage {
pos += bn
switch num {
case 8:
u.RequestID = string(val)
k, v := parseDevinHeaderField(val)
if k != "" {
if u.Headers == nil {
u.Headers = make(map[string]string)
}
u.Headers[k] = v
if (strings.EqualFold(k, "x-request-id") || strings.EqualFold(k, "request-id")) && v != "" {
u.RequestID = v
}
} else if len(val) > 0 && isPrintableASCII(val) && u.RequestID == "" {
u.RequestID = string(val)
}
case 9:
u.ModelName = string(val)
}
@@ -767,12 +817,139 @@ func parseDevinUsageField(data []byte) *DevinUsage {
}
pos += fn
default:
return u
nSkip := protowire.ConsumeFieldValue(num, typ, data[pos:])
if nSkip <= 0 {
return u
}
pos += nSkip
}
}
return u
}
// ParseDevinResponseDimensionGroups parses Field 28 (ResponseDimensionGroups) entries to extract Token Usage metrics:
// input_tokens, output_tokens, cached_input_tokens.
// Accepts one or more group payloads (each corresponding to a Field 28 value), or an outer envelope containing Tag 28.
func ParseDevinResponseDimensionGroups(groups ...[]byte) (promptTokens, completionTokens, cachedTokens int64, found bool) {
for _, gBytes := range groups {
if len(gBytes) == 0 {
continue
}
// If outer envelope carries Tag 28, unwrap it to get inner group bytes.
if num, typ, n := protowire.ConsumeTag(gBytes); n > 0 && num == 28 && typ == protowire.BytesType {
if inner, bn := protowire.ConsumeBytes(gBytes[n:]); bn > 0 {
gBytes = inner
}
}
gPos := 0
var title string
type metricItem struct {
key string
val float32
}
var metrics []metricItem
for gPos < len(gBytes) {
gNum, gTyp, gn := protowire.ConsumeTag(gBytes[gPos:])
if gn <= 0 {
break
}
gPos += gn
if gTyp != protowire.BytesType {
gSkip := protowire.ConsumeFieldValue(gNum, gTyp, gBytes[gPos:])
if gSkip <= 0 {
break
}
gPos += gSkip
continue
}
gb, gbn := protowire.ConsumeBytes(gBytes[gPos:])
if gbn <= 0 {
break
}
gPos += gbn
if gNum == 1 {
title = string(gb)
} else if gNum == 2 {
mPos := 0
var mKey string
var mVal float32
for mPos < len(gb) {
mNum, mTyp, mn := protowire.ConsumeTag(gb[mPos:])
if mn <= 0 {
break
}
mPos += mn
if mTyp != protowire.BytesType {
mSkip := protowire.ConsumeFieldValue(mNum, mTyp, gb[mPos:])
if mSkip <= 0 {
break
}
mPos += mSkip
continue
}
mb, mbn := protowire.ConsumeBytes(gb[mPos:])
if mbn <= 0 {
break
}
mPos += mbn
if mNum == 5 {
mKey = string(mb)
} else if mNum == 4 {
dPos := 0
for dPos < len(mb) {
dNum, dTyp, dn := protowire.ConsumeTag(mb[dPos:])
if dn <= 0 {
break
}
dPos += dn
if dTyp == protowire.Fixed32Type {
dv, dfn := protowire.ConsumeFixed32(mb[dPos:])
if dfn <= 0 {
break
}
dPos += dfn
if dNum == 2 {
mVal = math.Float32frombits(dv)
}
} else {
dSkip := protowire.ConsumeFieldValue(dNum, dTyp, mb[dPos:])
if dSkip <= 0 {
break
}
dPos += dSkip
}
}
}
}
if mKey != "" {
metrics = append(metrics, metricItem{key: mKey, val: mVal})
}
}
}
if strings.EqualFold(title, "Token Usage") {
for _, m := range metrics {
switch m.key {
case "input_tokens":
promptTokens = int64(m.val)
found = true
case "output_tokens":
completionTokens = int64(m.val)
found = true
case "cached_input_tokens":
cachedTokens = int64(m.val)
found = true
}
}
if found {
return promptTokens, completionTokens, cachedTokens, true
}
}
}
return promptTokens, completionTokens, cachedTokens, found
}
// ParseDevinTrailerError inspects Connect-RPC EOS trailer frames and maps error status codes.
func ParseDevinTrailerError(payload []byte) (statusCode int, err error) {
trimmed := bytes.TrimSpace(payload)

View File

@@ -2,6 +2,7 @@ package helps
import (
"bytes"
"math"
"strings"
"testing"
@@ -588,3 +589,201 @@ func extractField15Subfields(t *testing.T, reqBytes []byte) (string, map[int]uin
}
return sessionID, subfields
}
func TestParseDevinUsageField_HeadersAndField4(t *testing.T) {
var f7Bytes []byte
f7Bytes = protowire.AppendTag(f7Bytes, 2, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 3)
f7Bytes = protowire.AppendTag(f7Bytes, 4, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 58)
f7Bytes = protowire.AppendTag(f7Bytes, 3, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 39)
f7Bytes = protowire.AppendTag(f7Bytes, 5, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 19179)
f7Bytes = protowire.AppendTag(f7Bytes, 6, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 66)
// Submessage 1: openai-version
var h1 []byte
h1 = protowire.AppendTag(h1, 1, protowire.BytesType)
h1 = protowire.AppendString(h1, "openai-version")
h1 = protowire.AppendTag(h1, 2, protowire.BytesType)
h1 = protowire.AppendString(h1, "2020-10-01")
f7Bytes = protowire.AppendTag(f7Bytes, 8, protowire.BytesType)
f7Bytes = protowire.AppendBytes(f7Bytes, h1)
// Submessage 2: x-request-id
var h2 []byte
h2 = protowire.AppendTag(h2, 1, protowire.BytesType)
h2 = protowire.AppendString(h2, "x-request-id")
h2 = protowire.AppendTag(h2, 2, protowire.BytesType)
h2 = protowire.AppendString(h2, "req_5bb00ad48ae048119e3420bddf36257f")
f7Bytes = protowire.AppendTag(f7Bytes, 8, protowire.BytesType)
f7Bytes = protowire.AppendBytes(f7Bytes, h2)
// Submessage 3: openai-processing-ms
var h3 []byte
h3 = protowire.AppendTag(h3, 1, protowire.BytesType)
h3 = protowire.AppendString(h3, "openai-processing-ms")
h3 = protowire.AppendTag(h3, 2, protowire.BytesType)
h3 = protowire.AppendString(h3, "419")
f7Bytes = protowire.AppendTag(f7Bytes, 8, protowire.BytesType)
f7Bytes = protowire.AppendBytes(f7Bytes, h3)
f7Bytes = protowire.AppendTag(f7Bytes, 9, protowire.BytesType)
f7Bytes = protowire.AppendString(f7Bytes, "gpt-5-6-luna-low")
usage := parseDevinUsageField(f7Bytes)
if usage == nil {
t.Fatal("expected non-nil usage")
}
// 3 + 58 = 61
if usage.PromptTokens != 61 {
t.Errorf("PromptTokens = %d, want 61 (3 turn + 58 context)", usage.PromptTokens)
}
if usage.CompletionTokens != 39 {
t.Errorf("CompletionTokens = %d, want 39", usage.CompletionTokens)
}
if usage.CachedTokens != 19179 {
t.Errorf("CachedTokens = %d, want 19179", usage.CachedTokens)
}
if usage.StatusCode != 66 {
t.Errorf("StatusCode = %d, want 66", usage.StatusCode)
}
if usage.RequestID != "req_5bb00ad48ae048119e3420bddf36257f" {
t.Errorf("RequestID = %q, want clean request-id", usage.RequestID)
}
if usage.ModelName != "gpt-5-6-luna-low" {
t.Errorf("ModelName = %q, want gpt-5-6-luna-low", usage.ModelName)
}
if usage.Headers["openai-processing-ms"] != "419" {
t.Errorf("header processing-ms = %q, want 419", usage.Headers["openai-processing-ms"])
}
if usage.Headers["openai-version"] != "2020-10-01" {
t.Errorf("header openai-version = %q, want 2020-10-01", usage.Headers["openai-version"])
}
}
func TestParseDevinUsageField_AnthropicRequestId(t *testing.T) {
var f7Bytes []byte
f7Bytes = protowire.AppendTag(f7Bytes, 2, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 4)
f7Bytes = protowire.AppendTag(f7Bytes, 3, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 109)
f7Bytes = protowire.AppendTag(f7Bytes, 5, protowire.VarintType)
f7Bytes = protowire.AppendVarint(f7Bytes, 577)
// Anthropic uses capitalized "Request-Id"
var h []byte
h = protowire.AppendTag(h, 1, protowire.BytesType)
h = protowire.AppendString(h, "Request-Id")
h = protowire.AppendTag(h, 2, protowire.BytesType)
h = protowire.AppendString(h, "req_011Cf1JivhJrXDq9ycq7cEtH")
f7Bytes = protowire.AppendTag(f7Bytes, 8, protowire.BytesType)
f7Bytes = protowire.AppendBytes(f7Bytes, h)
usage := parseDevinUsageField(f7Bytes)
if usage == nil {
t.Fatal("expected non-nil usage")
}
if usage.RequestID != "req_011Cf1JivhJrXDq9ycq7cEtH" {
t.Errorf("RequestID = %q, want req_011Cf1JivhJrXDq9ycq7cEtH", usage.RequestID)
}
if usage.PromptTokens != 4 {
t.Errorf("PromptTokens = %d, want 4", usage.PromptTokens)
}
if usage.CompletionTokens != 109 {
t.Errorf("CompletionTokens = %d, want 109", usage.CompletionTokens)
}
if usage.CachedTokens != 577 {
t.Errorf("CachedTokens = %d, want 577", usage.CachedTokens)
}
}
func TestParseDevinResponseDimensionGroups(t *testing.T) {
buildMetric := func(key string, val float32) []byte {
// Dimension submessage (Tag 4 of Metric)
var dim []byte
dim = protowire.AppendTag(dim, 2, protowire.Fixed32Type)
dim = protowire.AppendFixed32(dim, math.Float32bits(val))
// Metric submessage (Tag 2 of Group)
var metric []byte
metric = protowire.AppendTag(metric, 4, protowire.BytesType)
metric = protowire.AppendBytes(metric, dim)
metric = protowire.AppendTag(metric, 5, protowire.BytesType)
metric = protowire.AppendString(metric, key)
return metric
}
// Build Group (Tag 28)
var group []byte
group = protowire.AppendTag(group, 1, protowire.BytesType)
group = protowire.AppendString(group, "Token Usage")
group = protowire.AppendTag(group, 2, protowire.BytesType)
group = protowire.AppendBytes(group, buildMetric("input_tokens", 575.0))
group = protowire.AppendTag(group, 2, protowire.BytesType)
group = protowire.AppendBytes(group, buildMetric("output_tokens", 5.0))
group = protowire.AppendTag(group, 2, protowire.BytesType)
group = protowire.AppendBytes(group, buildMetric("cached_input_tokens", 128.0))
// Envelope Tag 28
var root []byte
root = protowire.AppendTag(root, 28, protowire.BytesType)
root = protowire.AppendBytes(root, group)
promptTokens, completionTokens, cachedTokens, found := ParseDevinResponseDimensionGroups(root)
if !found {
t.Fatal("expected found = true")
}
if promptTokens != 575 {
t.Errorf("promptTokens = %d, want 575", promptTokens)
}
if completionTokens != 5 {
t.Errorf("completionTokens = %d, want 5", completionTokens)
}
if cachedTokens != 128 {
t.Errorf("cachedTokens = %d, want 128", cachedTokens)
}
// Verify inner group directly (as extracted by ParseDevinFrame case 28)
p2, c2, ca2, found2 := ParseDevinResponseDimensionGroups(group)
if !found2 || p2 != 575 || c2 != 5 || ca2 != 128 {
t.Errorf("inner group ParseDevinResponseDimensionGroups = (%d,%d,%d,%t), want (575,5,128,true)", p2, c2, ca2, found2)
}
// Verify multi-group where unrelated group precedes Token Usage
var latencyGroup []byte
latencyGroup = protowire.AppendTag(latencyGroup, 1, protowire.BytesType)
latencyGroup = protowire.AppendString(latencyGroup, "Latency Metrics")
p3, c3, ca3, found3 := ParseDevinResponseDimensionGroups(latencyGroup, group)
if !found3 || p3 != 575 || c3 != 5 || ca3 != 128 {
t.Errorf("multi-group ParseDevinResponseDimensionGroups = (%d,%d,%d,%t), want (575,5,128,true)", p3, c3, ca3, found3)
}
}
func TestParseDevinResponseDimensionGroups_UnrelatedGroup(t *testing.T) {
var group []byte
group = protowire.AppendTag(group, 1, protowire.BytesType)
group = protowire.AppendString(group, "Latency Metrics")
var root []byte
root = protowire.AppendTag(root, 28, protowire.BytesType)
root = protowire.AppendBytes(root, group)
promptTokens, completionTokens, cachedTokens, found := ParseDevinResponseDimensionGroups(root)
if found {
t.Errorf("expected found = false for unrelated group, got true with prompt=%d, comp=%d, cached=%d", promptTokens, completionTokens, cachedTokens)
}
}

View File

@@ -298,6 +298,50 @@ func TestApplyOAuthModelAlias_PerAuthAliasSkipsAPIKey(t *testing.T) {
}
}
func TestApplyOAuthModelAlias_Devin(t *testing.T) {
t.Parallel()
aliases := map[string][]internalconfig.OAuthModelAlias{
"devin": {
{
Name: "devin/claude-fable-5-1",
Alias: "fable-5-1",
Fork: true,
ForceMapping: true,
},
},
}
mgr := NewManager(nil, nil, nil)
mgr.SetConfig(&internalconfig.Config{})
mgr.SetOAuthModelAlias(aliases)
auth := &Auth{ID: "devin-auth", Provider: "devin", Attributes: map[string]string{"auth_kind": "oauth"}}
resolvedModel := mgr.applyOAuthModelAlias(auth, "fable-5-1")
if resolvedModel != "devin/claude-fable-5-1" {
t.Errorf("applyOAuthModelAlias() model = %q, want %q", resolvedModel, "devin/claude-fable-5-1")
}
// Suffix preservation with Devin thinking effort
suffixed := mgr.applyOAuthModelAlias(auth, "fable-5-1(max)")
if suffixed != "devin/claude-fable-5-1(max)" {
t.Errorf("applyOAuthModelAlias() suffixed model = %q, want %q", suffixed, "devin/claude-fable-5-1(max)")
}
// Force mapping result
result := mgr.applyOAuthModelAliasWithResult(auth, "fable-5-1(max)")
if result.UpstreamModel != "devin/claude-fable-5-1(max)" {
t.Errorf("UpstreamModel = %q, want %q", result.UpstreamModel, "devin/claude-fable-5-1(max)")
}
if !result.ForceMapping {
t.Errorf("ForceMapping = false, want true")
}
if result.OriginalAlias != "fable-5-1" {
t.Errorf("OriginalAlias = %q, want %q", result.OriginalAlias, "fable-5-1")
}
}
func TestApplyOAuthModelAlias_PluginProvider(t *testing.T) {
t.Parallel()