From 30b2ac8996ea920285ef983dfaaa8e17624c8d21 Mon Sep 17 00:00:00 2001 From: sususu Date: Sun, 13 Sep 2026 20:21:04 +0800 Subject: [PATCH] refactor(devin): deduplicate auth credentials extraction, filter sparse tool calls, and optimize model lookup --- internal/registry/devin_models.go | 10 ++++- internal/runtime/executor/devin_executor.go | 44 +++++++------------ internal/runtime/executor/helps/devin_wire.go | 3 ++ 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/internal/registry/devin_models.go b/internal/registry/devin_models.go index e78444de2..f28435432 100644 --- a/internal/registry/devin_models.go +++ b/internal/registry/devin_models.go @@ -62,7 +62,15 @@ func LookupDevinModel(modelID string) *ModelInfo { return nil } - for _, m := range GetDevinModels() { + devinCatalogStore.mu.RLock() + models := devinCatalogStore.models + devinCatalogStore.mu.RUnlock() + + if len(models) == 0 { + models = GetDevinModels() + } + + for _, m := range models { mClean := strings.ToLower(strings.TrimPrefix(m.ID, "devin/")) if mClean == clean { return cloneModelInfo(m) diff --git a/internal/runtime/executor/devin_executor.go b/internal/runtime/executor/devin_executor.go index 1caf8f01a..bc11b858c 100644 --- a/internal/runtime/executor/devin_executor.go +++ b/internal/runtime/executor/devin_executor.go @@ -144,38 +144,15 @@ func (e *DevinExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (* return nil, errors.New("devin executor: auth is nil") } - sessionToken := strings.TrimSpace(auth.Attributes["api_key"]) - if sessionToken == "" { - sessionToken = strings.TrimSpace(auth.Attributes["session_token"]) - } - if sessionToken == "" && auth.Metadata != nil { - if v, ok := auth.Metadata["api_key"].(string); ok { - sessionToken = strings.TrimSpace(v) - } - if sessionToken == "" { - if v, ok := auth.Metadata["session_token"].(string); ok { - sessionToken = strings.TrimSpace(v) - } - } - } + sessionToken, baseURL, deviceSeed := devinAuthCredentials(auth) if sessionToken == "" { return auth, nil } httpClient := helps.NewDevinHTTPClient(ctx, e.cfg, auth, 30*time.Second) authService := devinauth.NewDevinAuthService(httpClient) - if baseURL := strings.TrimSpace(auth.Attributes["base_url"]); baseURL != "" { + if baseURL != "" { authService.SetServerBaseURL(baseURL) - } else if auth.Metadata != nil { - if v, ok := auth.Metadata["base_url"].(string); ok && strings.TrimSpace(v) != "" { - authService.SetServerBaseURL(v) - } - } - deviceSeed := strings.TrimSpace(auth.Attributes["device_seed"]) - if deviceSeed == "" && auth.Metadata != nil { - if v, ok := auth.Metadata["device_seed"].(string); ok { - deviceSeed = strings.TrimSpace(v) - } } status, err := authService.FetchUserStatus(ctx, sessionToken, deviceSeed) @@ -784,7 +761,9 @@ func (e *DevinExecutor) streamDevinFrames( completedEvent, _ = sjson.SetBytes(completedEvent, "interaction.usage.total_cached_tokens", finalUsage.CachedTokens) completedEvent, _ = sjson.SetBytes(completedEvent, "interaction.usage.total_tokens", totalTokens) if detail, ok := helps.ParseInteractionsStreamUsage(completedEvent); ok { - reporter.Publish(ctx, detail) + if reporter != nil { + reporter.Publish(ctx, detail) + } } } _ = emitInteractionsEvent(completedEvent) @@ -846,13 +825,20 @@ func consumeDevinFramesToInteractions(body io.Reader, model, chatModelUID string if len(toolBuilders) == 0 { return nil } - res := make([]helps.DevinToolCall, len(toolBuilders)) + res := make([]helps.DevinToolCall, 0, len(toolBuilders)) for i := range toolBuilders { - res[i] = helps.DevinToolCall{ + if toolBuilders[i] == nil { + continue + } + // Skip unpopulated sparse placeholders + if toolBuilders[i].id == "" && toolBuilders[i].name == "" && toolBuilders[i].args.Len() == 0 { + continue + } + res = append(res, helps.DevinToolCall{ ID: toolBuilders[i].id, Name: toolBuilders[i].name, Arguments: toolBuilders[i].args.String(), - } + }) } return res } diff --git a/internal/runtime/executor/helps/devin_wire.go b/internal/runtime/executor/helps/devin_wire.go index d0de1c94c..1f214e05b 100644 --- a/internal/runtime/executor/helps/devin_wire.go +++ b/internal/runtime/executor/helps/devin_wire.go @@ -447,6 +447,9 @@ func BuildDevinGetChatMessageRequest( 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") }