package cli import ( "encoding/json" "fmt" "ragflow/internal/common" "regexp" "strings" ) func tokenTypeDescription(t int, tok Token) string { if tok.Type == t && tok.Value != "" { return fmt.Sprintf("%s %q", tokenTypeToString(t), tok.Value) } return tokenTypeToString(t) } // Command parsers func (p *Parser) parseAPILoginUser() (*Command, error) { cmd := NewCommand("api_login_user") p.nextToken() // consume LOGIN if p.curToken.Type != TokenUser { return nil, fmt.Errorf("expected USER after LOGIN") } p.nextToken() email, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["email"] = email p.nextToken() // Optional: PASSWORD 'password' if p.curToken.Type == TokenPassword { p.nextToken() var password string password, err = p.parseQuotedString() if err != nil { return nil, err } cmd.Params["password"] = password p.nextToken() } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPILogout() (*Command, error) { cmd := NewCommand("api_logout") p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIPingServer() (*Command, error) { cmd := NewCommand("api_ping_server") p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIRegister() (*Command, error) { cmd := NewCommand("api_register_user") if err := p.expectPeek(TokenUser); err != nil { return nil, err } p.nextToken() // consume USER userName, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["user_name"] = userName p.nextToken() // consume Email if p.curToken.Type != TokenAs { return nil, fmt.Errorf("expected AS") } p.nextToken() // consume AS nickname, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["nickname"] = nickname p.nextToken() // consume nickname if p.curToken.Type != TokenPassword { return nil, fmt.Errorf("expected PASSWORD") } p.nextToken() // consume PASSWORD password, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["password"] = password p.nextToken() // consume 'password' // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // region LIST commands // LIST CONFIGS; // LIST PROVIDER 'provider_name' MODELS; // LIST PROVIDER 'provider_name' INSTANCE 'instance_name' MODELS // LIST MODELS; func (p *Parser) parseAPIListCommands() (*Command, error) { p.nextToken() // consume LIST switch p.curToken.Type { case TokenDatasets: return p.parseAPIListDatasets() case TokenDataset: return p.parseAPIListDatasetCommands() case TokenAgents: return p.parseAPIListAgents() case TokenChats: return p.parseAPIListChats() case TokenSearches: return p.parseAPIListSearches() case TokenMemories: return p.parseAPIListMemories() case TokenKeys: return p.parseAPIListAPIKeys() case TokenProviders: return p.parseAPIListProviders() case TokenProvider: return p.parseAPIListProviderCommands() case TokenModels: return p.parseAPIListAllModels() case TokenIngestion: return p.parseAPIListIngestionTasks() case TokenSyncLogs: return p.parseAPIListSyncLogs() case TokenDefault: return p.parseAPIListDefaultModels() case TokenAvailable: return p.parseAPIListAvailableProviders() case TokenAPI: return p.parseAPIListAPIServers() case TokenEnvs: return p.parseAPIListEnvironments() case TokenGroups: return p.parseAPIListModelGroups() default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } } // LIST GROUPS; func (p *Parser) parseAPIListModelGroups() (*Command, error) { cmd := NewCommand("api_list_model_groups") p.nextToken() // consume GROUPS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIListDatasets() (*Command, error) { cmd := NewCommand("api_list_datasets") p.nextToken() // consume DATASETS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // LIST DATASET 'dataset_name' DOCUMENTS; func (p *Parser) parseAPIListDatasetCommands() (*Command, error) { p.nextToken() // consume DATASET datasetID, err := p.parseQuotedString() if err != nil { return nil, err } p.nextToken() switch p.curToken.Type { case TokenDocuments: return p.parseAPIListDatasetDocuments(datasetID) case TokenFiles: return p.parseAPIListDatasetFiles(datasetID) case TokenIngestion: return p.parseAPIListDatasetIngestionTasks(datasetID) case TokenSyncLogs: return p.parseAPIListDatasetSyncLogs(datasetID) default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } } func (p *Parser) parseAPIListDatasetDocuments(datasetID string) (*Command, error) { p.nextToken() // consume DOCUMENTS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd := NewCommand("api_list_dataset_documents") cmd.Params["dataset_id"] = datasetID return cmd, nil } func (p *Parser) parseAPIListDatasetFiles(datasetName string) (*Command, error) { p.nextToken() // consume FILES // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd := NewCommand("api_list_dataset_files") cmd.Params["dataset_name"] = datasetName return cmd, nil } func (p *Parser) parseAPIListDatasetIngestionTasks(datasetName string) (*Command, error) { p.nextToken() // consume INGESTION if p.curToken.Type != TokenTasks { return nil, fmt.Errorf("expected TASKS") } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd := NewCommand("api_list_ingestion_tasks") cmd.Params["dataset_name"] = datasetName return cmd, nil } // LIST SYNC_LOGS; // LIST SYNC_LOGS FROM 'dataset_id' // LIST SYNC_LOGS [FROM 'dataset_id'] WITH PAGE 2 PAGE_SIZE 50 func (p *Parser) parseAPIListSyncLogs() (*Command, error) { p.nextToken() // consume SYNC_LOGS cmd := NewCommand("api_list_sync_logs") if p.curToken.Type == TokenFrom { p.nextToken() datasetID, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["dataset_id"] = datasetID p.nextToken() // move past the dataset id } if err := p.parseSyncLogsWithOptions(1, cmd); err != nil { return nil, err } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // LIST DATASET 'dataset_name' SYNC_LOGS [WITH PAGE 2 PAGE_SIZE 50]; func (p *Parser) parseAPIListDatasetSyncLogs(datasetName string) (*Command, error) { p.nextToken() // consume SYNC_LOGS cmd := NewCommand("api_list_sync_logs") cmd.Params["dataset_name"] = datasetName if err := p.parseSyncLogsWithOptions(1, cmd); err != nil { return nil, err } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // parseSyncLogsWithOptions parses the optional WITH clause of the sync logs // listing commands. Only PAGE and PAGE_SIZE are accepted, both as integers, // mirroring the search command's space-separated WITH syntax. func (p *Parser) parseSyncLogsWithOptions(commandCount int, cmd *Command) error { if p.curToken.Type != TokenWith && !(p.curToken.Type == TokenIdentifier && strings.EqualFold(p.curToken.Value, "with")) { return nil } p.nextToken() // consume WITH for p.curToken.Type != TokenEOF && p.curToken.Type != TokenSemicolon { if p.curToken.Type == TokenComma { return fmt.Errorf("syntax error: WITH options must be space-separated, not comma-separated") } if p.curToken.Type != TokenIdentifier { break } paramName := strings.ToLower(p.curToken.Value) p.nextToken() if p.curToken.Type != TokenInteger { if p.curToken.Type == TokenEOF || p.curToken.Type == TokenSemicolon { return fmt.Errorf("WITH option %q is missing a value", paramName) } return fmt.Errorf("WITH option %q must be an integer, got %s", paramName, tokenTypeDescription(p.curToken.Type, p.curToken)) } value, err := p.parseNumber() if err != nil { return err } p.nextToken() switch paramName { case "page", "page_size": cmd.Params[paramName] = value default: return fmt.Errorf("unknown WITH option %q", paramName) } } return nil } func (p *Parser) parseAPIListAgents() (*Command, error) { p.nextToken() // consume AGENTS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_agents"), nil } func (p *Parser) parseAPIListChats() (*Command, error) { p.nextToken() // consume CHATS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_chats"), nil } func (p *Parser) parseAPIListSearches() (*Command, error) { p.nextToken() // consume SEARCHES // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_searches"), nil } func (p *Parser) parseAPIListMemories() (*Command, error) { p.nextToken() // consume MEMORIES // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_memories"), nil } func (p *Parser) parseAPIListAPIKeys() (*Command, error) { p.nextToken() // consume KEYS cmd := NewCommand("api_list_api_keys") // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // LIST PROVIDERS func (p *Parser) parseAPIListProviders() (*Command, error) { p.nextToken() // consume PROVIDERS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_providers"), nil } // LIST PROVIDER 'provider_name' INSTANCES func (p *Parser) parseAPIListProviderCommands() (*Command, error) { p.nextToken() // consume PROVIDER providerName, err := p.parseQuotedString() if err != nil { return nil, err } p.nextToken() switch p.curToken.Type { case TokenInstances: return p.parseListProviderInstances(providerName) case TokenInstance: return p.parseListProviderInstanceCommands(providerName) case TokenModels: return p.parseListProviderModels(providerName) default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } } // LIST PROVIDER 'provider_name' INSTANCES func (p *Parser) parseListProviderInstances(providerName string) (*Command, error) { p.nextToken() // consume INSTANCES // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd := NewCommand("api_list_provider_instances") cmd.Params["provider_name"] = providerName return cmd, nil } // LIST PROVIDER 'provider_name' INSTANCE 'instance_name' MODELS // LIST PROVIDER 'provider_name' INSTANCE 'instance_name' MODELS SYNC, get model list by API from remote server func (p *Parser) parseListProviderInstanceCommands(providerName string) (*Command, error) { p.nextToken() // consume INSTANCE instanceName, err := p.parseQuotedString() if err != nil { return nil, err } p.nextToken() switch p.curToken.Type { case TokenModels: return p.parseListProviderInstanceModels(providerName, instanceName) case TokenTasks: return p.parseListProviderInstanceTasks(providerName, instanceName) default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } } func (p *Parser) parseListProviderInstanceModels(providerName, instanceName string) (*Command, error) { p.nextToken() // consume MODELS cmd := NewCommand("api_list_provider_instance_models") if p.curToken.Type == TokenSync { cmd = NewCommand("api_list_provider_instance_models_sync") } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd.Params["provider_name"] = providerName cmd.Params["instance_name"] = instanceName return cmd, nil } func (p *Parser) parseListProviderInstanceTasks(providerName, instanceName string) (*Command, error) { p.nextToken() // consume TASKS // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd := NewCommand("api_list_provider_instance_tasks") cmd.Params["provider_name"] = providerName cmd.Params["instance_name"] = instanceName return cmd, nil } func (p *Parser) parseListProviderModels(providerName string) (*Command, error) { p.nextToken() // consume MODELS cmd := NewCommand("api_list_provider_models") // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } cmd.Params["provider_name"] = providerName return cmd, nil } // LIST MODELS func (p *Parser) parseAPIListAllModels() (*Command, error) { p.nextToken() // consume models cmd := NewCommand("api_list_all_models") if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIListIngestionTasks() (*Command, error) { p.nextToken() // consume Ingestion if p.curToken.Type != TokenTasks { return nil, fmt.Errorf("expected TASKS") } p.nextToken() // consume TASKS cmd := NewCommand("api_list_ingestion_tasks") if p.curToken.Type == TokenFrom { p.nextToken() datasetID, err := p.parseQuotedString() if err != nil { return nil, err } cmd.Params["dataset_id"] = datasetID } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIListDefaultModels() (*Command, error) { p.nextToken() // consume DEFAULT if p.curToken.Type != TokenModels { return nil, fmt.Errorf("expected MODELS") } p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_list_default_models"), nil } func (p *Parser) parseAPIListAvailableProviders() (*Command, error) { p.nextToken() // consume AVAILABLE if p.curToken.Type != TokenProviders { return nil, fmt.Errorf("expected PROVIDERS") } return NewCommand("api_list_available_providers"), nil } func (p *Parser) parseAPIListAPIServers() (*Command, error) { p.nextToken() // consume API var cmd *Command switch p.curToken.Type { case TokenServer: p.nextToken() cmd = NewCommand("api_list_api_servers") default: return nil, fmt.Errorf("expected SERVER after API") } // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } func (p *Parser) parseAPIListEnvironments() (*Command, error) { p.nextToken() // consume Envs cmd := NewCommand("api_list_environments") // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // endregion LIST commands // region SHOW commands func (p *Parser) parseAPIShowCommands() (*Command, error) { p.nextToken() // consume SHOW switch p.curToken.Type { case TokenVersion: return p.parseAPIShowVersion() case TokenKey: return p.parseAPIShowKey() case TokenCurrent: return p.parseAPIShowCurrent() case TokenProvider: return p.parseAPIShowProviderCommands() case TokenModel: return p.parseAPIShowModel() case TokenAdmin: return p.parseAPIShowAdmin() case TokenAPI: return p.parseAPIShowAPI() case TokenLog: return p.parseAPIShowLogCommands() case TokenHardware: return p.parseAPIShowHardware() case TokenCores: return p.parseAPIShowCores() case TokenMemory: return p.parseAPIShowMemory() case TokenConcurrency: return p.parseAPIShowConcurrency() case TokenQuota: return p.parseAPIShowQuota() case TokenGroup: return p.parseAPIShowGroup() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } } func (p *Parser) parseAPIShowVersion() (*Command, error) { p.nextToken() // consume VERSION // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_show_version"), nil } func (p *Parser) parseAPIShowKey() (*Command, error) { p.nextToken() // consume KEY // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_show_api_key"), nil } func (p *Parser) parseAPIShowCurrent() (*Command, error) { p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return NewCommand("api_show_current"), nil } // SHOW PROVIDER ; // SHOW PROVIDER INSTANCE ; // SHOW PROVIDER INSTANCE BALANCE; // SHOW PROVIDER INSTANCE TASK ; // SHOW PROVIDER 'provider_name' MODEL 'model_name'; func (p *Parser) parseAPIShowProviderCommands() (*Command, error) { p.nextToken() // consume PROVIDER providerName, err := p.parseQuotedString() if err != nil { return nil, fmt.Errorf("expected provider name: %w", err) } p.nextToken() // consume provider_name switch p.curToken.Type { case TokenInstance: return p.parseAPIShowProviderInstance(providerName) case TokenModel: return p.parseAPIShowProviderModel(providerName) case TokenSemicolon, TokenEOF: p.nextToken() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } cmd := NewCommand("api_show_provider") cmd.Params["provider_name"] = providerName return cmd, nil } // SHOW PROVIDER INSTANCE ; func (p *Parser) parseAPIShowProviderInstance(providerName string) (*Command, error) { p.nextToken() // consume INSTANCE instanceName, err := p.parseQuotedString() if err != nil { return nil, fmt.Errorf("expected instance name: %w", err) } p.nextToken() // consume instance_name switch p.curToken.Type { case TokenBalance: return p.parseAPIShowProviderInstanceBalance(providerName, instanceName) case TokenTask: return p.parseAPIShowProviderInstanceTask(providerName, instanceName) case TokenSemicolon, TokenEOF: p.nextToken() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } cmd := NewCommand("api_show_provider_instance") cmd.Params["instance_name"] = instanceName cmd.Params["provider_name"] = providerName return cmd, nil } // SHOW PROVIDER INSTANCE BALANCE func (p *Parser) parseAPIShowProviderInstanceBalance(providerName, instanceName string) (*Command, error) { p.nextToken() // consume BALANCE cmd := NewCommand("api_show_provider_instance_balance") cmd.Params["instance_name"] = instanceName cmd.Params["provider_name"] = providerName // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // SHOW PROVIDER INSTANCE TASK func (p *Parser) parseAPIShowProviderInstanceTask(providerName, instanceName string) (*Command, error) { p.nextToken() // consume TASK taskID, err := p.parseQuotedString() if err != nil { return nil, fmt.Errorf("expected task id: %w", err) } p.nextToken() // consume task_id cmd := NewCommand("api_show_provider_instance_task") cmd.Params["instance_name"] = instanceName cmd.Params["provider_name"] = providerName cmd.Params["task_id"] = taskID // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // SHOW PROVIDER MODEL func (p *Parser) parseAPIShowProviderModel(providerName string) (*Command, error) { p.nextToken() // consume MODEL modelName, err := p.parseQuotedString() if err != nil { return nil, fmt.Errorf("expected model name: %w", err) } p.nextToken() // consume model_name cmd := NewCommand("api_show_provider_model") cmd.Params["model_name"] = modelName cmd.Params["provider_name"] = providerName // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } return cmd, nil } // SHOW MODEL 'model_name'; // SHOW MODEL STATS [