Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 93 additions & 2 deletions pkg/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down