@@ -671,7 +671,17 @@ func RunActionWrapper(ctx context.Context, user shuffle.User, value shuffle.Cate
671671 value .AppName = ""
672672 }
673673
674- if strings .ToLower (value .AppName ) == "http" {
674+ // Smart app correction: Only for generic API calls, not custom_action
675+ // custom_action means user already chose an app - don't override
676+ if strings .ToLower (value .AppName ) == "http" && value .Label == "api" && len (value .Query ) > 0 {
677+ correctedAppName := AnalyzeIntentAndCorrectApp (ctx , value .Query , value .Fields )
678+ if len (correctedAppName ) > 0 && strings .ToLower (correctedAppName ) != "http" {
679+ value .AppName = correctedAppName
680+ log .Printf ("[INFO] Corrected app selection from 'http' to '%s' based on query intent" , correctedAppName )
681+ }
682+ }
683+
684+ if strings .ToLower (value .AppName ) == "http" {
675685 value .AppName = ""
676686 }
677687
@@ -703,7 +713,7 @@ func RunActionWrapper(ctx context.Context, user shuffle.User, value shuffle.Cate
703713
704714
705715 // WITHOUT finding the app first
706- if /* len(value.AppName) == 0 &&*/ len (value .Category ) == 0 && len (value .AppId ) == 0 && (value .Label == "api" || value .Label == "custom_action" || value .Action == "custom_action" ) {
716+ if len (value .AppName ) == 0 && len (value .Category ) == 0 && len (value .AppId ) == 0 && (value .Label == "api" || value .Label == "custom_action" || value .Action == "custom_action" ) {
707717 log .Printf ("[INFO] Got Singul 'custom action' request WITHOUT app. Mapping to HTTP 1.4.0." )
708718
709719 value = GetUpdatedHttpValue (value )
@@ -4468,6 +4478,87 @@ func AuthenticateAppCli(appname string) error {
44684478 return nil
44694479}
44704480
4481+ // AnalyzeIntentAndCorrectApp uses LLM to identify the app from URL/query
4482+ // LLM identifies app freely, then we search for it (user's apps or Algolia)
4483+ // Returns app name or empty string if HTTP is correct
4484+ func AnalyzeIntentAndCorrectApp (ctx context.Context , query string , fields []shuffle.Valuereplace ) string {
4485+
4486+ urlValue := ""
4487+ fieldsSummary := ""
4488+
4489+ for _ , field := range fields {
4490+ if field .Key == "url" {
4491+ urlValue = strings .TrimSpace (field .Value )
4492+ }
4493+
4494+ fieldLen := len (field .Value )
4495+ if fieldLen > 20 {
4496+ fieldLen = 20
4497+ }
4498+ fieldsSummary += field .Key + ":" + field .Value [:fieldLen ] + "|"
4499+ }
4500+
4501+ systemMessage := `You are an API identification assistant.
4502+ Your job is to determine the most likely intended service or app that an API request belongs to.
4503+ 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
4504+ Use all available signals together, including the URL domain, path, request fields, payload structure, headers, and the described intent.
4505+
4506+ Your goal is to identify the service the request should belong to, not necessarily what it is currently labeled as.
4507+ If the URL and fields clearly match a known service, return that service even if the intent text disagrees.
4508+ If the request does not strongly match any known service and appears to be a generic or custom HTTP call, return "http"
4509+
4510+ Examples:
4511+ - gmail.googleapis.com → Gmail
4512+ - slack.com/api → Slack
4513+ - api.github.com → GitHub
4514+ - random-api.com → http
4515+
4516+ Output rules:
4517+ Return only the service or app name.
4518+ Do not include explanations, reasoning, or extra text.
4519+ `
4520+
4521+ userMessage := fmt .Sprintf (`Analyze this API request:
4522+ - Intent: "%s"
4523+ - URL: "%s"
4524+ - Fields: %s
4525+
4526+ What service/app does this API belong to?
4527+ Return ONLY the app/service name (e.g., Gmail, Slack, GitHub).
4528+ If this is a generic HTTP call with no specific service, return "http".` ,
4529+ query , urlValue , fieldsSummary )
4530+
4531+ responseBody , err := shuffle .RunAiQuery (systemMessage , userMessage )
4532+ if err != nil {
4533+ log .Printf ("[WARNING] Failed calling LLM for app intent correction: %s" , err )
4534+ return ""
4535+ }
4536+
4537+ // Parse LLM response
4538+ contentOutput := strings .TrimSpace (responseBody )
4539+ if after , ok := strings .CutPrefix (contentOutput , "```" ); ok {
4540+ contentOutput = after
4541+ }
4542+ if after , ok := strings .CutSuffix (contentOutput , "```" ); ok {
4543+ contentOutput = after
4544+ }
4545+ contentOutput = strings .TrimSpace (contentOutput )
4546+
4547+ identifiedApp := strings .TrimSpace (contentOutput )
4548+ if strings .ToLower (identifiedApp ) == "http" {
4549+ return ""
4550+ }
4551+
4552+ if len (identifiedApp ) > 0 {
4553+ if debug {
4554+ log .Printf ("[DEBUG] LLM identified app '%s' for URL: %s, Intent: %s" , identifiedApp , urlValue , query )
4555+ }
4556+ return identifiedApp
4557+ }
4558+
4559+ return ""
4560+ }
4561+
44714562// For handling the function without changing ALL the resp.X functions
44724563type FakeResponseWriter struct {
44734564 HeaderMap http.Header
0 commit comments