diff --git a/pkg/api.go b/pkg/api.go index b5a047c..63f02f2 100644 --- a/pkg/api.go +++ b/pkg/api.go @@ -671,7 +671,17 @@ func RunActionWrapper(ctx context.Context, user shuffle.User, value shuffle.Cate value.AppName = "" } - if strings.ToLower(value.AppName) == "http" { + // Smart app correction: Only for generic API calls, not custom_action + // custom_action means user already chose an app - don't override + if strings.ToLower(value.AppName) == "http" && value.Label == "api" && len(value.Query) > 0 { + correctedAppName := AnalyzeIntentAndCorrectApp(ctx, value.Query, value.Fields) + if len(correctedAppName) > 0 && strings.ToLower(correctedAppName) != "http" { + value.AppName = correctedAppName + log.Printf("[INFO] Corrected app selection from 'http' to '%s' based on query intent", correctedAppName) + } + } + + if strings.ToLower(value.AppName) == "http" { value.AppName = "" } @@ -703,7 +713,7 @@ func RunActionWrapper(ctx context.Context, user shuffle.User, value shuffle.Cate // WITHOUT finding the app first - if /*len(value.AppName) == 0 &&*/ len(value.Category) == 0 && len(value.AppId) == 0 && (value.Label == "api" || value.Label == "custom_action" || value.Action == "custom_action") { + if len(value.AppName) == 0 && len(value.Category) == 0 && len(value.AppId) == 0 && (value.Label == "api" || value.Label == "custom_action" || value.Action == "custom_action") { log.Printf("[INFO] Got Singul 'custom action' request WITHOUT app. Mapping to HTTP 1.4.0.") value = GetUpdatedHttpValue(value) @@ -4464,6 +4474,87 @@ func AuthenticateAppCli(appname string) error { return nil } +// AnalyzeIntentAndCorrectApp uses LLM to identify the app from URL/query +// LLM identifies app freely, then we search for it (user's apps or Algolia) +// Returns app name or empty string if HTTP is correct +func AnalyzeIntentAndCorrectApp(ctx context.Context, query string, fields []shuffle.Valuereplace) string { + + urlValue := "" + fieldsSummary := "" + + for _, field := range fields { + if field.Key == "url" { + urlValue = strings.TrimSpace(field.Value) + } + + fieldLen := len(field.Value) + if fieldLen > 20 { + fieldLen = 20 + } + fieldsSummary += field.Key + ":" + field.Value[:fieldLen] + "|" + } + + systemMessage := `You are an API identification assistant. +Your job is to determine the most likely intended service or app that an API request belongs to. +The provided intent or query field may describe the reason or goal of the request and may be incorrect, vague, or misleading. Do not blindly trust it +Use all available signals together, including the URL domain, path, request fields, payload structure, headers, and the described intent. + +Your goal is to identify the service the request should belong to, not necessarily what it is currently labeled as. +If the URL and fields clearly match a known service, return that service even if the intent text disagrees. +If the request does not strongly match any known service and appears to be a generic or custom HTTP call, return "http" + +Examples: +- gmail.googleapis.com → Gmail +- slack.com/api → Slack +- api.github.com → GitHub +- random-api.com → http + +Output rules: +Return only the service or app name. +Do not include explanations, reasoning, or extra text. +` + + userMessage := fmt.Sprintf(`Analyze this API request: +- Intent: "%s" +- URL: "%s" +- Fields: %s + +What service/app does this API belong to? +Return ONLY the app/service name (e.g., Gmail, Slack, GitHub). +If this is a generic HTTP call with no specific service, return "http".`, + query, urlValue, fieldsSummary) + + responseBody, err := shuffle.RunAiQuery(systemMessage, userMessage) + if err != nil { + log.Printf("[WARNING] Failed calling LLM for app intent correction: %s", err) + return "" + } + + // Parse LLM response + contentOutput := strings.TrimSpace(responseBody) + if after, ok := strings.CutPrefix(contentOutput, "```"); ok { + contentOutput = after + } + if after, ok := strings.CutSuffix(contentOutput, "```"); ok { + contentOutput = after + } + contentOutput = strings.TrimSpace(contentOutput) + + identifiedApp := strings.TrimSpace(contentOutput) + if strings.ToLower(identifiedApp) == "http" { + return "" + } + + if len(identifiedApp) > 0 { + if debug { + log.Printf("[DEBUG] LLM identified app '%s' for URL: %s, Intent: %s", identifiedApp, urlValue, query) + } + return identifiedApp + } + + return "" +} + // For handling the function without changing ALL the resp.X functions type FakeResponseWriter struct { HeaderMap http.Header