2026-03-25 21:39:14 +08:00
|
|
|
package cli
|
|
|
|
|
|
2026-03-26 21:07:06 +08:00
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
2026-04-02 20:20:35 +08:00
|
|
|
"strings"
|
2026-03-26 21:07:06 +08:00
|
|
|
)
|
2026-03-25 21:39:14 +08:00
|
|
|
|
|
|
|
|
// Command parsers
|
2026-03-26 11:54:23 +08:00
|
|
|
func (p *Parser) parseLogout() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("logout")
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseLoginUser() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("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()
|
2026-04-07 09:44:51 +08:00
|
|
|
// Optional: PASSWORD 'password'
|
|
|
|
|
if p.curToken.Type == TokenPassword {
|
2026-04-01 16:16:25 +08:00
|
|
|
p.nextToken()
|
|
|
|
|
password, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["password"] = password
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parsePingServer() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("ping")
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseRegisterCommand() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("register_user")
|
|
|
|
|
|
|
|
|
|
if err := p.expectPeek(TokenUser); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenAs {
|
|
|
|
|
return nil, fmt.Errorf("expected AS")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
nickname, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["nickname"] = nickname
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenPassword {
|
|
|
|
|
return nil, fmt.Errorf("expected PASSWORD")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
password, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["password"] = password
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume LIST
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenServices:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_services"), nil
|
|
|
|
|
case TokenUsers:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_users"), nil
|
|
|
|
|
case TokenRoles:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_roles"), nil
|
|
|
|
|
case TokenVars:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_variables"), nil
|
|
|
|
|
case TokenConfigs:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_configs"), nil
|
|
|
|
|
case TokenEnvs:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_environments"), nil
|
|
|
|
|
case TokenDatasets:
|
|
|
|
|
return p.parseListDatasets()
|
|
|
|
|
case TokenAgents:
|
|
|
|
|
return p.parseListAgents()
|
|
|
|
|
case TokenTokens:
|
|
|
|
|
return p.parseListTokens()
|
|
|
|
|
case TokenModel:
|
|
|
|
|
return p.parseListModelProviders()
|
2026-04-21 16:52:32 +08:00
|
|
|
case TokenSupported:
|
|
|
|
|
return p.parseListModelsOfProvider()
|
2026-03-31 18:42:12 +08:00
|
|
|
case TokenModels:
|
|
|
|
|
return p.parseListModelsOfProvider()
|
|
|
|
|
case TokenProviders:
|
|
|
|
|
return p.parseListProviders()
|
2026-04-02 20:20:35 +08:00
|
|
|
case TokenInstances:
|
|
|
|
|
return p.parseListInstances()
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenDefault:
|
|
|
|
|
return p.parseListDefaultModels()
|
2026-03-31 18:42:12 +08:00
|
|
|
case TokenAvailable:
|
|
|
|
|
return p.parseCommonListProviders()
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenChats:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_user_chats"), nil
|
|
|
|
|
case TokenFiles:
|
|
|
|
|
return p.parseListFiles()
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListDatasets() (*Command, error) {
|
2026-04-08 19:32:53 +08:00
|
|
|
cmd := NewCommand("list_datasets")
|
2026-03-25 21:39:14 +08:00
|
|
|
p.nextToken() // consume DATASETS
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListAgents() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume AGENTS
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
return NewCommand("list_user_agents"), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("list_agents")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListTokens() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume TOKENS
|
|
|
|
|
cmd := NewCommand("list_tokens")
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListModelProviders() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume MODEL
|
|
|
|
|
if p.curToken.Type != TokenProviders {
|
|
|
|
|
return nil, fmt.Errorf("expected PROVIDERS")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_user_model_providers"), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 18:42:12 +08:00
|
|
|
// parseListProviders parses LIST PROVIDERS command
|
|
|
|
|
func (p *Parser) parseListProviders() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume PROVIDERS
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_providers"), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseListDefaultModels() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DEFAULT
|
|
|
|
|
if p.curToken.Type != TokenModels {
|
|
|
|
|
return nil, fmt.Errorf("expected MODELS")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("list_user_default_models"), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseListFiles() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume FILES
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenDataset {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("list_user_dataset_files")
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShowCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume SHOW
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenVersion:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("show_version"), nil
|
|
|
|
|
case TokenToken:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("show_token"), nil
|
|
|
|
|
case TokenCurrent:
|
|
|
|
|
p.nextToken()
|
2026-04-02 20:20:35 +08:00
|
|
|
if p.curToken.Type == TokenUser {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW CURRENT USER
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("show_current_user"), nil
|
|
|
|
|
} else if p.curToken.Type == TokenModel {
|
2026-03-25 21:39:14 +08:00
|
|
|
p.nextToken()
|
2026-04-02 20:20:35 +08:00
|
|
|
// Semicolon is optional for SHOW CURRENT MODEL
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("show_current_model"), nil
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("expected USER or MODEL after CURRENT")
|
2026-03-25 21:39:14 +08:00
|
|
|
}
|
|
|
|
|
case TokenUser:
|
|
|
|
|
return p.parseShowUser()
|
|
|
|
|
case TokenRole:
|
|
|
|
|
return p.parseShowRole()
|
|
|
|
|
case TokenVar:
|
|
|
|
|
return p.parseShowVariable()
|
|
|
|
|
case TokenService:
|
|
|
|
|
return p.parseShowService()
|
2026-03-31 18:42:12 +08:00
|
|
|
case TokenProvider:
|
|
|
|
|
return p.parseShowProvider()
|
|
|
|
|
case TokenModel:
|
|
|
|
|
return p.parseShowModel()
|
2026-04-02 20:20:35 +08:00
|
|
|
case TokenInstance:
|
|
|
|
|
return p.parseShowInstance()
|
2026-04-21 21:31:50 +08:00
|
|
|
case TokenBalance:
|
|
|
|
|
return p.parseShowBalance()
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShowUser() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume USER
|
|
|
|
|
|
|
|
|
|
// Check for PERMISSION
|
|
|
|
|
if p.curToken.Type == TokenPermission {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd := NewCommand("show_user_permission")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_user")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShowRole() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ROLE
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_role")
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShowVariable() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume VAR
|
|
|
|
|
varName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_variable")
|
|
|
|
|
cmd.Params["var_name"] = varName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShowService() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume SERVICE
|
|
|
|
|
serviceNum, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_service")
|
|
|
|
|
cmd.Params["number"] = serviceNum
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 18:42:12 +08:00
|
|
|
func (p *Parser) parseShowModel() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume model
|
|
|
|
|
|
|
|
|
|
modelName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected model name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_model")
|
|
|
|
|
cmd.Params["model_name"] = modelName
|
|
|
|
|
|
|
|
|
|
p.nextToken() // consume model_name
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // consume from
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
p.nextToken() // consume provider name
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseShowProvider parses SHOW PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseShowProvider() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume PROVIDER
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_provider")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseCreateCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CREATE
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenUser:
|
|
|
|
|
return p.parseCreateUser()
|
|
|
|
|
case TokenRole:
|
|
|
|
|
return p.parseCreateRole()
|
|
|
|
|
case TokenModel:
|
|
|
|
|
return p.parseCreateModelProvider()
|
|
|
|
|
case TokenDataset:
|
|
|
|
|
return p.parseCreateDataset()
|
|
|
|
|
case TokenChat:
|
|
|
|
|
return p.parseCreateChat()
|
|
|
|
|
case TokenToken:
|
|
|
|
|
return p.parseCreateToken()
|
2026-04-09 09:52:31 +08:00
|
|
|
case TokenDatasetTable:
|
|
|
|
|
return p.parseCreateDatasetTable()
|
|
|
|
|
case TokenMetadata:
|
|
|
|
|
return p.parseCreateMetadataTable()
|
2026-04-02 20:20:35 +08:00
|
|
|
case TokenProvider:
|
|
|
|
|
return p.parseCreateProviderInstance()
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown CREATE target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
func (p *Parser) parseAddCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ADD
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenProvider:
|
|
|
|
|
return p.parseAddProvider()
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenModel:
|
|
|
|
|
return p.parseAddModel()
|
2026-04-02 20:20:35 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseCreateToken() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume TOKEN
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NewCommand("create_token"), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
// Internal CLI for GO
|
|
|
|
|
// parseCreateDatasetTable parses: CREATE DATASET TABLE 'name' VECTOR SIZE N
|
|
|
|
|
func (p *Parser) parseCreateDatasetTable() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET TABLE compound token
|
2026-03-26 11:54:10 +08:00
|
|
|
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected dataset name, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
2026-04-09 09:52:31 +08:00
|
|
|
if p.curToken.Type != TokenVector {
|
|
|
|
|
return nil, fmt.Errorf("expected VECTOR after dataset name, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenSize {
|
|
|
|
|
return nil, fmt.Errorf("expected SIZE after VECTOR, got %s", p.curToken.Value)
|
2026-03-26 11:54:10 +08:00
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
2026-04-07 15:09:45 +08:00
|
|
|
if p.curToken.Type != TokenInteger {
|
2026-03-26 11:54:10 +08:00
|
|
|
return nil, fmt.Errorf("expected vector size number, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
vectorSize, err := strconv.Atoi(p.curToken.Value)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("invalid vector size: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
cmd := NewCommand("create_dataset_table")
|
2026-03-26 11:54:10 +08:00
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
cmd.Params["vector_size"] = vectorSize
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
// Internal CLI for GO
|
|
|
|
|
// parseCreateMetadataTable parses: CREATE METADATA TABLE
|
|
|
|
|
func (p *Parser) parseCreateMetadataTable() (*Command, error) {
|
|
|
|
|
// CREATE METADATA TABLE
|
|
|
|
|
p.nextToken() // consume METADATA
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenTable {
|
|
|
|
|
return nil, fmt.Errorf("expected TABLE after METADATA, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NewCommand("create_metadata_table"), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseCreateUser() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume USER
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
password, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("create_user")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
cmd.Params["password"] = password
|
|
|
|
|
cmd.Params["role"] = "user"
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseCreateRole() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ROLE
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("create_role")
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenDescription {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
description, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["description"] = description
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseCreateModelProvider() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume MODEL
|
|
|
|
|
if p.curToken.Type != TokenProvider {
|
|
|
|
|
return nil, fmt.Errorf("expected PROVIDER")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
providerKey, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("create_model_provider")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["provider_key"] = providerKey
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
// parseAddProvider parses ADD PROVIDER commands
|
|
|
|
|
// ADD PROVIDER <name>
|
|
|
|
|
// ADD PROVIDER <name> <api_key>
|
|
|
|
|
func (p *Parser) parseAddProvider() (*Command, error) {
|
2026-03-31 18:42:12 +08:00
|
|
|
p.nextToken() // consume PROVIDER
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
cmd := NewCommand("add_provider")
|
2026-03-31 18:42:12 +08:00
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Check if api_key is provided (optional)
|
|
|
|
|
if p.curToken.Type == TokenQuotedString {
|
|
|
|
|
apiKey, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected api key: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["api_key"] = apiKey
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 17:05:08 +08:00
|
|
|
// syntax: add model 'xxx' to provider 'vllm' instance 'test' with tokens 1024 chat think vision;
|
|
|
|
|
func (p *Parser) parseAddModel() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume MODEL
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected model name")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modelName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // consume model name
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenTo {
|
|
|
|
|
return nil, fmt.Errorf("expected TO")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenProvider {
|
|
|
|
|
return nil, fmt.Errorf("expected PROVIDER")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// provider name
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name")
|
|
|
|
|
}
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenInstance {
|
|
|
|
|
return nil, fmt.Errorf("expected INSTANCE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// instance name
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name")
|
|
|
|
|
}
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
2026-04-29 19:18:49 +08:00
|
|
|
var modelTypes []string
|
2026-04-29 17:05:08 +08:00
|
|
|
var supportThink *bool = nil
|
|
|
|
|
maxTokens := 0
|
|
|
|
|
if p.curToken.Type == TokenWith {
|
|
|
|
|
p.nextToken() // pass WITH
|
|
|
|
|
optionsLoop:
|
|
|
|
|
for {
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenThink:
|
|
|
|
|
if supportThink != nil {
|
|
|
|
|
return nil, fmt.Errorf("think model is already set")
|
|
|
|
|
}
|
|
|
|
|
supportThink = new(bool)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
*supportThink = true
|
|
|
|
|
case TokenVision:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "vision")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenChat:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "chat")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenEmbedding:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "embedding")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenRerank:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "rerank")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenOCR:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "ocr")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenTTS:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "tts")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenASR:
|
|
|
|
|
p.nextToken()
|
2026-04-29 19:18:49 +08:00
|
|
|
modelTypes = append(modelTypes, "asr")
|
2026-04-29 17:05:08 +08:00
|
|
|
case TokenTokens:
|
|
|
|
|
p.nextToken() // pass TOKENS
|
|
|
|
|
if maxTokens != 0 {
|
|
|
|
|
return nil, fmt.Errorf("max tokens is already given %d", maxTokens)
|
|
|
|
|
}
|
|
|
|
|
if p.curToken.Type != TokenInteger {
|
|
|
|
|
return nil, fmt.Errorf("expected integer")
|
|
|
|
|
}
|
|
|
|
|
maxTokens, err = p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // consume
|
|
|
|
|
case TokenSemicolon:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
break optionsLoop // done
|
|
|
|
|
default:
|
|
|
|
|
// No more options to process
|
|
|
|
|
break optionsLoop
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("add_custom_model")
|
|
|
|
|
cmd.Params["model_name"] = modelName
|
2026-04-29 19:18:49 +08:00
|
|
|
cmd.Params["model_types"] = modelTypes
|
2026-04-29 17:05:08 +08:00
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
if supportThink != nil {
|
|
|
|
|
cmd.Params["support_think"] = *supportThink
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["max_tokens"] = maxTokens
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseCreateDataset() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenWith {
|
|
|
|
|
return nil, fmt.Errorf("expected WITH")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenEmbedding {
|
|
|
|
|
return nil, fmt.Errorf("expected EMBEDDING")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
embedding, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
cmd := NewCommand("create_user_dataset")
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
cmd.Params["embedding"] = embedding
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenParser {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
parserType, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["parser_type"] = parserType
|
|
|
|
|
p.nextToken()
|
|
|
|
|
} else if p.curToken.Type == TokenPipeline {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
pipeline, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["pipeline"] = pipeline
|
|
|
|
|
p.nextToken()
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("expected PARSER or PIPELINE")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseCreateChat() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CHAT
|
|
|
|
|
chatName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("create_user_chat")
|
|
|
|
|
cmd.Params["chat_name"] = chatName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseDropCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DROP
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenUser:
|
|
|
|
|
return p.parseDropUser()
|
|
|
|
|
case TokenRole:
|
|
|
|
|
return p.parseDropRole()
|
|
|
|
|
case TokenDataset:
|
|
|
|
|
return p.parseDropDataset()
|
|
|
|
|
case TokenChat:
|
|
|
|
|
return p.parseDropChat()
|
|
|
|
|
case TokenToken:
|
|
|
|
|
return p.parseDropToken()
|
2026-04-09 09:52:31 +08:00
|
|
|
case TokenDatasetTable:
|
|
|
|
|
return p.parseDropDatasetTable()
|
|
|
|
|
case TokenMetadata:
|
|
|
|
|
return p.parseDropMetadataTable()
|
2026-04-02 20:20:35 +08:00
|
|
|
case TokenInstance:
|
|
|
|
|
return p.parseDropInstance()
|
2026-04-29 19:18:49 +08:00
|
|
|
case TokenModel:
|
|
|
|
|
return p.parseDropInstanceModel()
|
2026-04-02 20:20:35 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown DROP target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseDeleteCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DELETE
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenProvider:
|
|
|
|
|
return p.parseDeleteProvider()
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown DROP target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 09:44:51 +08:00
|
|
|
func (p *Parser) parseRemoveCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume RM
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenTag:
|
|
|
|
|
return p.parseRemoveTags()
|
2026-04-09 09:52:31 +08:00
|
|
|
case TokenChunks, TokenAll:
|
|
|
|
|
return p.parseRemoveChunk()
|
2026-04-07 09:44:51 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown REMOVE target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseDropToken() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume TOKEN
|
|
|
|
|
|
|
|
|
|
tokenValue, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_token")
|
|
|
|
|
cmd.Params["token"] = tokenValue
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
// Internal CLI for GO
|
|
|
|
|
// parseDropDatasetTable parses: DROP DATASET TABLE 'name'
|
|
|
|
|
func (p *Parser) parseDropDatasetTable() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET TABLE
|
2026-03-26 11:54:10 +08:00
|
|
|
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected dataset name, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
cmd := NewCommand("drop_dataset_table")
|
2026-03-26 11:54:10 +08:00
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
// Internal CLI for GO
|
|
|
|
|
// parseDropMetadataTable parses: DROP METADATA TABLE
|
|
|
|
|
func (p *Parser) parseDropMetadataTable() (*Command, error) {
|
|
|
|
|
// DROP METADATA TABLE
|
|
|
|
|
p.nextToken() // consume METADATA
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenTable {
|
|
|
|
|
return nil, fmt.Errorf("expected TABLE after METADATA, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_metadata_table")
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseDropUser() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume USER
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_user")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseDropRole() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ROLE
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_role")
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
// parseDeleteProvider parses DELETE PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseDeleteProvider() (*Command, error) {
|
2026-03-31 18:42:12 +08:00
|
|
|
p.nextToken() // consume PROVIDER
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
cmd := NewCommand("delete_provider")
|
2026-03-31 18:42:12 +08:00
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseDropDataset() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_user_dataset")
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseDropChat() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CHAT
|
|
|
|
|
chatName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_user_chat")
|
|
|
|
|
cmd.Params["chat_name"] = chatName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseAlterCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ALTER
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenUser:
|
|
|
|
|
return p.parseAlterUser()
|
|
|
|
|
case TokenRole:
|
|
|
|
|
return p.parseAlterRole()
|
2026-03-31 18:42:12 +08:00
|
|
|
case TokenProvider:
|
|
|
|
|
return p.parseAlterProvider()
|
2026-04-02 20:20:35 +08:00
|
|
|
case TokenInstance:
|
|
|
|
|
return p.parseAlterInstance()
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown ALTER target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseAlterUser() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume USER
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenActive {
|
|
|
|
|
return p.parseActivateUser()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenPassword {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
password, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("alter_user")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
cmd.Params["password"] = password
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for SHOW TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenSet {
|
|
|
|
|
return nil, fmt.Errorf("expected SET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenRole {
|
|
|
|
|
return nil, fmt.Errorf("expected ROLE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("alter_user_role")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseActivateUser() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ACTIVE
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Accept 'on' or 'off' as identifier
|
|
|
|
|
status := p.curToken.Value
|
|
|
|
|
if status != "on" && status != "off" {
|
|
|
|
|
return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("activate_user")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
cmd.Params["activate_status"] = status
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseAlterRole() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ROLE
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenSet {
|
|
|
|
|
return nil, fmt.Errorf("expected SET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenDescription {
|
|
|
|
|
return nil, fmt.Errorf("expected DESCRIPTION")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
description, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("alter_role")
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
cmd.Params["description"] = description
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 18:42:12 +08:00
|
|
|
// parseAlterProvider parses ALTER PROVIDER <name> NAME <new_name> command
|
|
|
|
|
func (p *Parser) parseAlterProvider() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume PROVIDER
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenName {
|
|
|
|
|
return nil, fmt.Errorf("expected NAME")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
newName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected new provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("alter_provider")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["new_name"] = newName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 17:05:08 +08:00
|
|
|
// parseCreateProviderInstance parses CREATE PROVIDER <name> INSTANCE <instance_name> KEY <api_key> URL <base_url> command
|
2026-04-02 20:20:35 +08:00
|
|
|
// instance_name cannot be "default"
|
|
|
|
|
func (p *Parser) parseCreateProviderInstance() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume PROVIDER
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenInstance {
|
|
|
|
|
return nil, fmt.Errorf("expected INSTANCE after provider name")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 17:05:08 +08:00
|
|
|
p.nextToken()
|
2026-04-02 20:20:35 +08:00
|
|
|
|
2026-04-29 17:05:08 +08:00
|
|
|
if p.curToken.Type != TokenKey {
|
|
|
|
|
return nil, fmt.Errorf("expected KEY after instance name")
|
|
|
|
|
}
|
2026-04-02 20:20:35 +08:00
|
|
|
p.nextToken()
|
2026-04-29 17:05:08 +08:00
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
apiKey, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected API key: %w", err)
|
|
|
|
|
}
|
2026-04-29 17:05:08 +08:00
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
baseURL := ""
|
|
|
|
|
if p.curToken.Type == TokenURL {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
baseURL, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected base URL: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
region := ""
|
|
|
|
|
if p.curToken.Type == TokenRegion {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
region, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected base URL: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
2026-04-02 20:20:35 +08:00
|
|
|
|
|
|
|
|
cmd := NewCommand("create_provider_instance")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["api_key"] = apiKey
|
2026-04-29 17:05:08 +08:00
|
|
|
if baseURL != "" {
|
|
|
|
|
// Only local model provider need to set URL
|
|
|
|
|
cmd.Params["base_url"] = baseURL
|
|
|
|
|
if region == "" {
|
|
|
|
|
region = instanceName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if region != "" {
|
|
|
|
|
cmd.Params["region"] = region
|
|
|
|
|
}
|
2026-04-02 20:20:35 +08:00
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseListInstances parses LIST INSTANCES FROM PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseListInstances() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSTANCES
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("list_provider_instances")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseShowInstance parses SHOW INSTANCE <name> FROM PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseShowInstance() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSTANCE
|
|
|
|
|
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_provider_instance")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:31:50 +08:00
|
|
|
// parseShowInstance parses SHOW BALANCE FROM <provider_name> <instance_name>
|
|
|
|
|
func (p *Parser) parseShowBalance() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSTANCE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER")
|
|
|
|
|
}
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name")
|
|
|
|
|
}
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("show_instance_balance")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
// parseAlterInstance parses ALTER INSTANCE <name> NAME <new_name> FROM PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseAlterInstance() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSTANCE
|
|
|
|
|
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenName {
|
|
|
|
|
return nil, fmt.Errorf("expected NAME")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
newName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected new instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenProvider {
|
|
|
|
|
return nil, fmt.Errorf("expected PROVIDER after FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("alter_provider_instance")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["new_name"] = newName
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseDropInstance parses DROP INSTANCE <name> FROM PROVIDER <name> command
|
|
|
|
|
func (p *Parser) parseDropInstance() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSTANCE
|
|
|
|
|
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_provider_instance")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 19:18:49 +08:00
|
|
|
// parseDropInstanceModel parses DROP MODEL <name> FROM <provider_name> <instance_name> command
|
|
|
|
|
// Only works for local deployed model
|
|
|
|
|
func (p *Parser) parseDropInstanceModel() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume MODEL
|
|
|
|
|
|
|
|
|
|
modelName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
providerName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM PROVIDER: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name after provider name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("drop_instance_model")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["model_name"] = modelName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseGrantCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume GRANT
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenAdmin {
|
|
|
|
|
return p.parseGrantAdmin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return p.parseGrantPermission()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseGrantAdmin() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ADMIN
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("grant_admin")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseGrantPermission() (*Command, error) {
|
|
|
|
|
actions, err := p.parseIdentifierList()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenOn {
|
|
|
|
|
return nil, fmt.Errorf("expected ON")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
resource, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenTo {
|
|
|
|
|
return nil, fmt.Errorf("expected TO")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenRole {
|
|
|
|
|
return nil, fmt.Errorf("expected ROLE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("grant_permission")
|
|
|
|
|
cmd.Params["actions"] = actions
|
|
|
|
|
cmd.Params["resource"] = resource
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseRevokeCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume REVOKE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenAdmin {
|
|
|
|
|
return p.parseRevokeAdmin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return p.parseRevokePermission()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseRevokeAdmin() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ADMIN
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("revoke_admin")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseRevokePermission() (*Command, error) {
|
|
|
|
|
actions, err := p.parseIdentifierList()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenOn {
|
|
|
|
|
return nil, fmt.Errorf("expected ON")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
resource, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenRole {
|
|
|
|
|
return nil, fmt.Errorf("expected ROLE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
roleName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("revoke_permission")
|
|
|
|
|
cmd.Params["actions"] = actions
|
|
|
|
|
cmd.Params["resource"] = resource
|
|
|
|
|
cmd.Params["role_name"] = roleName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseIdentifierList() ([]string, error) {
|
|
|
|
|
var list []string
|
|
|
|
|
|
|
|
|
|
ident, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
list = append(list, ident)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
for p.curToken.Type == TokenComma {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
ident, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
list = append(list, ident)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseSetCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume SET
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenVar {
|
|
|
|
|
return p.parseSetVariable()
|
|
|
|
|
}
|
|
|
|
|
if p.curToken.Type == TokenDefault {
|
|
|
|
|
return p.parseSetDefault()
|
|
|
|
|
}
|
|
|
|
|
if p.curToken.Type == TokenToken {
|
|
|
|
|
return p.parseSetToken()
|
|
|
|
|
}
|
2026-04-07 09:44:51 +08:00
|
|
|
if p.curToken.Type == TokenMetadata {
|
|
|
|
|
return p.parseSetMeta()
|
|
|
|
|
}
|
2026-04-08 19:32:53 +08:00
|
|
|
if p.curToken.Type == TokenLog {
|
|
|
|
|
return p.parseSetLog()
|
|
|
|
|
}
|
2026-03-25 21:39:14 +08:00
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseSetVariable() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume VAR
|
|
|
|
|
varName, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
varValue, err := p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("set_variable")
|
|
|
|
|
cmd.Params["var_name"] = varName
|
|
|
|
|
cmd.Params["var_value"] = varValue
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseSetDefault() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DEFAULT
|
|
|
|
|
|
2026-04-20 15:31:12 +08:00
|
|
|
var modelType, compositeModelName string
|
2026-04-17 18:05:33 +08:00
|
|
|
var err error
|
2026-03-25 21:39:14 +08:00
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
2026-04-20 15:31:12 +08:00
|
|
|
case TokenChat:
|
2026-04-17 18:05:33 +08:00
|
|
|
modelType = "chat"
|
2026-04-20 15:31:12 +08:00
|
|
|
case TokenVision:
|
|
|
|
|
modelType = "vision"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenEmbedding:
|
2026-04-17 18:05:33 +08:00
|
|
|
modelType = "embedding"
|
2026-04-20 15:31:12 +08:00
|
|
|
case TokenRerank:
|
2026-04-17 18:05:33 +08:00
|
|
|
modelType = "rerank"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenASR:
|
2026-04-17 18:05:33 +08:00
|
|
|
modelType = "asr"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenTTS:
|
2026-04-17 18:05:33 +08:00
|
|
|
modelType = "tts"
|
2026-04-20 15:31:12 +08:00
|
|
|
case TokenOCR:
|
|
|
|
|
modelType = "ocr"
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value)
|
|
|
|
|
}
|
2026-04-20 15:31:12 +08:00
|
|
|
p.nextToken() // pass model type
|
2026-03-25 21:39:14 +08:00
|
|
|
|
2026-04-20 15:31:12 +08:00
|
|
|
if p.curToken.Type != TokenModel {
|
|
|
|
|
return nil, fmt.Errorf("expected MODEL")
|
2026-04-17 18:05:33 +08:00
|
|
|
}
|
2026-04-20 15:31:12 +08:00
|
|
|
p.nextToken() // pass MODEL
|
2026-04-17 18:05:33 +08:00
|
|
|
|
2026-04-20 15:31:12 +08:00
|
|
|
// Format: 'provider/instance/model' or just 'message'
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected quoted string with format provider/instance/model")
|
2026-04-17 18:05:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:31:12 +08:00
|
|
|
compositeModelName, err = p.parseQuotedString()
|
2026-03-25 21:39:14 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-20 15:31:12 +08:00
|
|
|
p.nextToken()
|
2026-03-25 21:39:14 +08:00
|
|
|
|
|
|
|
|
cmd := NewCommand("set_default_model")
|
|
|
|
|
cmd.Params["model_type"] = modelType
|
2026-04-20 15:31:12 +08:00
|
|
|
cmd.Params["composite_model_name"] = compositeModelName
|
2026-03-25 21:39:14 +08:00
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseSetToken() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume TOKEN
|
|
|
|
|
|
|
|
|
|
tokenValue, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("set_token")
|
|
|
|
|
cmd.Params["token"] = tokenValue
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 19:32:53 +08:00
|
|
|
func (p *Parser) parseSetLog() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume LOG
|
|
|
|
|
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenLevel:
|
|
|
|
|
return p.parseSetLogLevel()
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseSetLogLevel() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume LEVEL
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("set_log_level")
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenDebug:
|
|
|
|
|
cmd.Params["level"] = "debug"
|
|
|
|
|
case TokenInfo:
|
|
|
|
|
cmd.Params["level"] = "info"
|
|
|
|
|
case TokenWarn:
|
|
|
|
|
cmd.Params["level"] = "warn"
|
|
|
|
|
case TokenError:
|
|
|
|
|
cmd.Params["level"] = "error"
|
|
|
|
|
case TokenFatal:
|
|
|
|
|
cmd.Params["level"] = "fatal"
|
|
|
|
|
case TokenPanic:
|
|
|
|
|
cmd.Params["level"] = "panic"
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown log target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseResetCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume RESET
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenDefault {
|
|
|
|
|
return nil, fmt.Errorf("expected DEFAULT")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
var modelType string
|
|
|
|
|
switch p.curToken.Type {
|
2026-04-20 15:31:12 +08:00
|
|
|
case TokenChat:
|
|
|
|
|
modelType = "chat"
|
|
|
|
|
case TokenVision:
|
|
|
|
|
modelType = "vision"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenEmbedding:
|
2026-04-20 15:31:12 +08:00
|
|
|
modelType = "embedding"
|
|
|
|
|
case TokenRerank:
|
|
|
|
|
modelType = "rerank"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenASR:
|
2026-04-20 15:31:12 +08:00
|
|
|
modelType = "asr"
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenTTS:
|
2026-04-20 15:31:12 +08:00
|
|
|
modelType = "tts"
|
|
|
|
|
case TokenOCR:
|
|
|
|
|
modelType = "ocr"
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("reset_default_model")
|
|
|
|
|
cmd.Params["model_type"] = modelType
|
|
|
|
|
p.nextToken()
|
2026-04-20 15:31:12 +08:00
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenModel {
|
|
|
|
|
return nil, fmt.Errorf("expected MODEL")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // pass MODEL
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseGenerateCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume GENERATE
|
|
|
|
|
if p.curToken.Type != TokenToken {
|
|
|
|
|
return nil, fmt.Errorf("expected TOKEN")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenFor {
|
|
|
|
|
return nil, fmt.Errorf("expected FOR")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenUser {
|
|
|
|
|
return nil, fmt.Errorf("expected USER")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
userName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("generate_token")
|
|
|
|
|
cmd.Params["user_name"] = userName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseImportCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume IMPORT
|
|
|
|
|
documentPaths, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenInto {
|
|
|
|
|
return nil, fmt.Errorf("expected INTO")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenDataset {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("import_docs_into_dataset")
|
|
|
|
|
cmd.Params["document_paths"] = documentPaths
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 16:16:25 +08:00
|
|
|
// parseInsertCommand parses INSERT command and dispatches to specific handler
|
|
|
|
|
func (p *Parser) parseInsertCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume INSERT
|
|
|
|
|
|
|
|
|
|
// Expect DATASET or METADATA
|
|
|
|
|
if p.curToken.Type == TokenDataset {
|
|
|
|
|
return p.parseInsertDatasetFromFile()
|
|
|
|
|
}
|
|
|
|
|
if p.curToken.Type == TokenMetadata {
|
|
|
|
|
return p.parseInsertMetadataFromFile()
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET or METADATA after INSERT, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Internal CLI for GO
|
|
|
|
|
// parseInsertDatasetFromFile parses: INSERT DATASET FROM FILE "file_path"
|
|
|
|
|
func (p *Parser) parseInsertDatasetFromFile() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET
|
|
|
|
|
|
|
|
|
|
// Expect FROM
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Expect FILE
|
|
|
|
|
if p.curToken.Type != TokenFile {
|
|
|
|
|
return nil, fmt.Errorf("expected FILE, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Get file path (quoted string)
|
|
|
|
|
filePath, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("insert_dataset_from_file")
|
|
|
|
|
cmd.Params["file_path"] = filePath
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Internal CLI for GO
|
2026-04-24 15:30:14 +08:00
|
|
|
// parseInsertMetadataFromFile parses: INSERT METADATA FROM FILE "file_path"
|
2026-04-01 16:16:25 +08:00
|
|
|
func (p *Parser) parseInsertMetadataFromFile() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume METADATA
|
|
|
|
|
|
|
|
|
|
// Expect FROM
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Expect FILE
|
|
|
|
|
if p.curToken.Type != TokenFile {
|
|
|
|
|
return nil, fmt.Errorf("expected FILE, got %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Get file path (quoted string)
|
|
|
|
|
filePath, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("insert_metadata_from_file")
|
|
|
|
|
cmd.Params["file_path"] = filePath
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseSearchCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume SEARCH
|
2026-04-07 13:59:27 +08:00
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
var question string
|
|
|
|
|
if p.curToken.Type == TokenQuotedString {
|
|
|
|
|
question, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
} else if p.curToken.Type == TokenIdentifier {
|
|
|
|
|
question, err = p.parseIdentifier()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("expected quoted string or identifier")
|
2026-03-25 21:39:14 +08:00
|
|
|
}
|
2026-04-07 13:59:27 +08:00
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
p.nextToken()
|
2026-04-07 11:30:09 +08:00
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenOn {
|
|
|
|
|
p.nextToken() // skip on
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenDatasets {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASETS")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
datasets, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("search_on_datasets")
|
|
|
|
|
cmd.Params["question"] = question
|
|
|
|
|
cmd.Params["datasets"] = datasets
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
2026-03-25 21:39:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-30 12:36:03 +08:00
|
|
|
cmd := NewCommand("ce_search")
|
2026-04-07 11:30:09 +08:00
|
|
|
|
|
|
|
|
cmd.Params["query"] = question
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenEOF {
|
|
|
|
|
cmd.Params["path"] = "."
|
|
|
|
|
return cmd, nil
|
2026-03-25 21:39:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-07 11:30:09 +08:00
|
|
|
for p.curToken.Type != TokenEOF {
|
|
|
|
|
if p.curToken.Type == TokenDash {
|
|
|
|
|
p.nextToken() // skip dash
|
|
|
|
|
if p.curToken.Type != TokenIdentifier {
|
|
|
|
|
return nil, fmt.Errorf("expect identifier")
|
|
|
|
|
}
|
2026-03-25 21:39:14 +08:00
|
|
|
|
2026-04-07 11:30:09 +08:00
|
|
|
if strings.ToLower(p.curToken.Value) == "n" {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
var err error
|
2026-04-07 15:09:45 +08:00
|
|
|
if p.curToken.Type != TokenInteger {
|
2026-04-07 11:30:09 +08:00
|
|
|
return nil, fmt.Errorf("expect number")
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["number"], err = p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 13:59:27 +08:00
|
|
|
//if strings.ToLower(p.curToken.Value) == "t" {
|
|
|
|
|
// p.nextToken()
|
|
|
|
|
// var err error
|
2026-04-07 15:09:45 +08:00
|
|
|
// if p.curToken.Type != TokenInteger {
|
2026-04-07 13:59:27 +08:00
|
|
|
// return nil, fmt.Errorf("expect number")
|
|
|
|
|
// }
|
|
|
|
|
// cmd.Params["threshold"], err = p.parseFloat()
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return nil, err
|
|
|
|
|
// }
|
|
|
|
|
// p.nextToken()
|
|
|
|
|
// continue
|
|
|
|
|
//}
|
2026-04-07 11:30:09 +08:00
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("unknow parameter: %s", p.curToken.Value)
|
|
|
|
|
} else if p.curToken.Type == TokenIdentifier {
|
|
|
|
|
if cmd.Params["path"] == nil {
|
|
|
|
|
cmd.Params["path"] = p.curToken.Value
|
|
|
|
|
} else {
|
2026-04-07 13:59:27 +08:00
|
|
|
cmd.Params["path"] = fmt.Sprintf("%s%s", cmd.Params["path"], p.curToken.Value)
|
2026-04-07 11:30:09 +08:00
|
|
|
}
|
|
|
|
|
p.nextToken() // skip path
|
|
|
|
|
continue
|
2026-04-07 13:59:27 +08:00
|
|
|
} else if p.curToken.Type == TokenSlash {
|
|
|
|
|
if cmd.Params["path"] == nil {
|
|
|
|
|
cmd.Params["path"] = "/"
|
|
|
|
|
} else {
|
|
|
|
|
cmd.Params["path"] = fmt.Sprintf("%s/", cmd.Params["path"])
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // skip slash
|
|
|
|
|
if p.curToken.Type == TokenIdentifier {
|
|
|
|
|
cmd.Params["path"] = fmt.Sprintf("%s%s", cmd.Params["path"], p.curToken.Value)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
continue
|
2026-04-07 11:30:09 +08:00
|
|
|
}
|
2026-03-25 21:39:14 +08:00
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
func (p *Parser) parseListModelsOfProvider() (*Command, error) {
|
2026-04-21 16:52:32 +08:00
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenSupported {
|
|
|
|
|
// List supported models
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("list_supported_models")
|
|
|
|
|
if p.curToken.Type != TokenModels {
|
|
|
|
|
return nil, fmt.Errorf("expected MODELS")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected quoted string for provider name")
|
|
|
|
|
}
|
|
|
|
|
firstName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected quoted string for instance name")
|
|
|
|
|
}
|
|
|
|
|
secondName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
cmd.Params["provider_name"] = firstName
|
|
|
|
|
cmd.Params["instance_name"] = secondName
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
if p.curToken.Type != TokenModels {
|
|
|
|
|
return nil, fmt.Errorf("expected MODELS")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
2026-04-21 16:52:32 +08:00
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse first quoted string (could be instance_name or provider_name)
|
|
|
|
|
firstName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Check if there's a second quoted string (provider_name)
|
|
|
|
|
// If so, format is: LIST MODELS FROM <instance_name> <provider_name>
|
|
|
|
|
// If not, format is: LIST MODELS FROM <provider_name>
|
|
|
|
|
if p.curToken.Type == TokenQuotedString {
|
|
|
|
|
// Two arguments: instance_name and provider_name
|
|
|
|
|
instanceName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd := NewCommand("list_instance_models")
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
cmd.Params["provider_name"] = firstName
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only one argument: provider_name
|
|
|
|
|
cmd := NewCommand("list_provider_models")
|
|
|
|
|
cmd.Params["provider_name"] = firstName
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseEnableCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume ENABLE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenModel {
|
|
|
|
|
return nil, fmt.Errorf("expected MODEL")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelProvider, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelInstance, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("enable_model")
|
|
|
|
|
cmd.Params["model_name"] = modelName
|
|
|
|
|
cmd.Params["instance_name"] = modelInstance
|
|
|
|
|
cmd.Params["provider_name"] = modelProvider
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseDisableCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DISABLE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenModel {
|
|
|
|
|
return nil, fmt.Errorf("expected MODEL")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelProvider, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
modelInstance, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("disable_model")
|
|
|
|
|
cmd.Params["model_name"] = modelName
|
|
|
|
|
cmd.Params["instance_name"] = modelInstance
|
|
|
|
|
cmd.Params["provider_name"] = modelProvider
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
// CHAT 'model@instance@provider' 'hello world'
|
|
|
|
|
// CHAT WITH 'model@instance@provider' MESSAGE 'hello world' 'who are you' IMAGE 'url1' 'file0' VIDEO "url2.mov" "file1" FILE "url" "path file2" AUDIO "file.wav"
|
2026-04-02 20:20:35 +08:00
|
|
|
func (p *Parser) parseChatCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CHAT
|
|
|
|
|
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
var err error
|
|
|
|
|
var compositeModelName string = ""
|
|
|
|
|
var messages []string
|
|
|
|
|
var images []string
|
|
|
|
|
var videos []string
|
|
|
|
|
var audios []string
|
|
|
|
|
var files []string
|
|
|
|
|
effort := "default"
|
|
|
|
|
verbosity := "low"
|
2026-04-03 18:11:23 +08:00
|
|
|
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
optionsLoop:
|
|
|
|
|
for {
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenWith:
|
2026-04-02 20:20:35 +08:00
|
|
|
p.nextToken()
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
// 'model@instance@provider'
|
|
|
|
|
if compositeModelName != "" {
|
|
|
|
|
return nil, fmt.Errorf("model name is already set")
|
|
|
|
|
}
|
|
|
|
|
compositeModelName, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2026-04-02 20:20:35 +08:00
|
|
|
}
|
|
|
|
|
p.nextToken()
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
case TokenMessage:
|
2026-04-02 20:20:35 +08:00
|
|
|
p.nextToken()
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
if len(messages) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("message is already set")
|
|
|
|
|
}
|
|
|
|
|
messageLoop:
|
|
|
|
|
for {
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
break messageLoop
|
2026-04-24 20:59:30 +08:00
|
|
|
}
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
var message string
|
|
|
|
|
message, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
message = strings.TrimSpace(message)
|
|
|
|
|
messages = append(messages, message)
|
2026-04-24 20:59:30 +08:00
|
|
|
p.nextToken()
|
|
|
|
|
}
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
case TokenImage:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if len(images) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("image is already set")
|
|
|
|
|
}
|
|
|
|
|
imageLoop:
|
|
|
|
|
for {
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
break imageLoop
|
|
|
|
|
}
|
|
|
|
|
var image string
|
|
|
|
|
image, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
images = append(images, image)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
case TokenVideo:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if len(videos) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("video is already set")
|
|
|
|
|
}
|
|
|
|
|
videoLoop:
|
|
|
|
|
for {
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
break videoLoop
|
2026-04-24 20:59:30 +08:00
|
|
|
}
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
var video string
|
|
|
|
|
video, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
videos = append(videos, video)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
case TokenAudio:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if len(audios) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("video is already set")
|
|
|
|
|
}
|
|
|
|
|
audioLoop:
|
|
|
|
|
for {
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
break audioLoop
|
|
|
|
|
}
|
|
|
|
|
var audio string
|
|
|
|
|
audio, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
audios = append(audios, audio)
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
case TokenFile:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if len(files) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("video is already set")
|
|
|
|
|
}
|
|
|
|
|
fileLoop:
|
|
|
|
|
for {
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
break fileLoop
|
|
|
|
|
}
|
|
|
|
|
var file string
|
|
|
|
|
file, err = p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
files = append(files, file)
|
2026-04-24 20:59:30 +08:00
|
|
|
p.nextToken()
|
|
|
|
|
}
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
case TokenEffort:
|
|
|
|
|
p.nextToken() // pass Effort
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenNone:
|
|
|
|
|
effort = "none"
|
|
|
|
|
case TokenMinimal:
|
|
|
|
|
effort = "minimal"
|
|
|
|
|
case TokenLow:
|
|
|
|
|
effort = "low"
|
|
|
|
|
case TokenMedium:
|
|
|
|
|
effort = "medium"
|
|
|
|
|
case TokenHigh:
|
|
|
|
|
effort = "high"
|
|
|
|
|
case TokenMax:
|
|
|
|
|
effort = "max"
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("invalid effort level")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
break optionsLoop
|
|
|
|
|
case TokenVerbosity:
|
|
|
|
|
p.nextToken() // pass VERBOSITY
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenLow:
|
|
|
|
|
verbosity = "low"
|
|
|
|
|
case TokenMedium:
|
|
|
|
|
verbosity = "median"
|
|
|
|
|
case TokenHigh:
|
|
|
|
|
verbosity = "high"
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("invalid verbosity level")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
break optionsLoop
|
|
|
|
|
case TokenSemicolon:
|
|
|
|
|
p.nextToken()
|
|
|
|
|
break optionsLoop // done
|
2026-04-24 20:59:30 +08:00
|
|
|
default:
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
// No more options to process
|
|
|
|
|
break optionsLoop
|
2026-04-24 20:59:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
cmd := NewCommand("chat_to_model")
|
2026-04-24 20:59:30 +08:00
|
|
|
|
Go: CLI chat with text, image, video (#14573)
### What problem does this PR solve?
```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.
1. The video starts with a blank Google search page.
2. The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3. The search is initiated and the page shows "About 0 results".
4. The search results load, showing information about Zhipu AI, including its website.
5. The user clicks on the main website link (www.zhipuai.cn).
6. The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".
In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-05-05 18:14:39 +08:00
|
|
|
cmd.Params["composite_model_name"] = compositeModelName
|
|
|
|
|
cmd.Params["messages"] = messages
|
|
|
|
|
cmd.Params["images"] = images
|
|
|
|
|
cmd.Params["videos"] = videos
|
|
|
|
|
cmd.Params["audios"] = audios
|
|
|
|
|
cmd.Params["files"] = files
|
2026-04-21 16:52:32 +08:00
|
|
|
cmd.Params["thinking"] = false
|
|
|
|
|
cmd.Params["stream"] = false
|
2026-04-24 20:59:30 +08:00
|
|
|
cmd.Params["effort"] = effort
|
|
|
|
|
cmd.Params["verbosity"] = verbosity
|
2026-04-02 20:20:35 +08:00
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 18:11:23 +08:00
|
|
|
func (p *Parser) parseThinkCommand() (*Command, error) {
|
|
|
|
|
|
|
|
|
|
p.nextToken() // consume THINK
|
2026-04-21 16:52:32 +08:00
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenChat {
|
|
|
|
|
return nil, fmt.Errorf("expected CHAT after THINK")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 18:11:23 +08:00
|
|
|
command, err := p.parseChatCommand()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-21 16:52:32 +08:00
|
|
|
command.Params["thinking"] = true
|
|
|
|
|
return command, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseStreamCommand() (*Command, error) {
|
|
|
|
|
|
|
|
|
|
p.nextToken() // consume STREAM
|
|
|
|
|
|
|
|
|
|
var command *Command
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenChat {
|
|
|
|
|
command, err = p.parseChatCommand()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
} else if p.curToken.Type == TokenThink {
|
|
|
|
|
command, err = p.parseThinkCommand()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.Params["stream"] = true
|
2026-04-03 18:11:23 +08:00
|
|
|
return command, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 10:16:20 +08:00
|
|
|
func (p *Parser) parseCheckCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CHECK
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenInstance {
|
|
|
|
|
return nil, fmt.Errorf("expected INSTANCE after CHECK")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected instance name after INSTANCE")
|
|
|
|
|
}
|
|
|
|
|
instanceName := p.curToken.Value
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM after instance name")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenQuotedString {
|
|
|
|
|
return nil, fmt.Errorf("expected provider name after FROM")
|
|
|
|
|
}
|
|
|
|
|
providerName := p.curToken.Value
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("check_provider_connection")
|
|
|
|
|
cmd.Params["provider_name"] = providerName
|
|
|
|
|
cmd.Params["instance_name"] = instanceName
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 20:20:35 +08:00
|
|
|
func (p *Parser) parseUseCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume USE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenModel {
|
|
|
|
|
return nil, fmt.Errorf("expected MODEL after USE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // consume MODEL
|
|
|
|
|
|
2026-04-24 20:59:30 +08:00
|
|
|
// Parse model identifier in format 'model@instance@provider'
|
2026-04-20 15:31:12 +08:00
|
|
|
compositeModelName, err := p.parseQuotedString()
|
2026-04-02 20:20:35 +08:00
|
|
|
if err != nil {
|
2026-04-24 20:59:30 +08:00
|
|
|
return nil, fmt.Errorf("expected model identifier in format 'model@instance@provider': %w", err)
|
2026-04-02 20:20:35 +08:00
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("use_model")
|
2026-04-20 15:31:12 +08:00
|
|
|
cmd.Params["composite_model_name"] = compositeModelName
|
2026-04-02 20:20:35 +08:00
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 21:39:14 +08:00
|
|
|
func (p *Parser) parseParseCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume PARSE
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type == TokenDataset {
|
|
|
|
|
return p.parseParseDataset()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return p.parseParseDocs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseParseDataset() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume DATASET
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
var method string
|
|
|
|
|
if p.curToken.Type == TokenSync {
|
|
|
|
|
method = "sync"
|
|
|
|
|
} else if p.curToken.Type == TokenAsync {
|
|
|
|
|
method = "async"
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("expected SYNC or ASYNC")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("parse_dataset")
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
cmd.Params["method"] = method
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseParseDocs() (*Command, error) {
|
|
|
|
|
documentNames, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenDataset {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("parse_dataset_docs")
|
|
|
|
|
cmd.Params["document_names"] = documentNames
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseBenchmarkCommand() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("benchmark")
|
|
|
|
|
|
|
|
|
|
p.nextToken() // consume BENCHMARK
|
|
|
|
|
concurrency, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["concurrency"] = concurrency
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
iterations, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["iterations"] = iterations
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Parse user_statement
|
|
|
|
|
nestedCmd, err := p.parseUserStatement() // Not only user statement
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["command"] = nestedCmd
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseUserStatement() (*Command, error) {
|
|
|
|
|
switch p.curToken.Type {
|
|
|
|
|
case TokenPing:
|
|
|
|
|
return p.parsePingServer()
|
|
|
|
|
case TokenShow:
|
|
|
|
|
return p.parseShowCommand()
|
|
|
|
|
case TokenCreate:
|
|
|
|
|
return p.parseCreateCommand()
|
|
|
|
|
case TokenDrop:
|
|
|
|
|
return p.parseDropCommand()
|
|
|
|
|
case TokenSet:
|
|
|
|
|
return p.parseSetCommand()
|
|
|
|
|
case TokenUnset:
|
|
|
|
|
return p.parseUnsetCommand()
|
|
|
|
|
case TokenReset:
|
|
|
|
|
return p.parseResetCommand()
|
|
|
|
|
case TokenList:
|
|
|
|
|
return p.parseListCommand()
|
|
|
|
|
case TokenParse:
|
|
|
|
|
return p.parseParseCommand()
|
|
|
|
|
case TokenImport:
|
|
|
|
|
return p.parseImportCommand()
|
2026-04-01 16:16:25 +08:00
|
|
|
case TokenInsert:
|
|
|
|
|
return p.parseInsertCommand()
|
2026-03-25 21:39:14 +08:00
|
|
|
case TokenSearch:
|
|
|
|
|
return p.parseSearchCommand()
|
2026-04-07 09:44:51 +08:00
|
|
|
case TokenUpdate:
|
|
|
|
|
return p.parseUpdateCommand()
|
|
|
|
|
case TokenRemove:
|
|
|
|
|
return p.parseRemoveCommand()
|
2026-03-25 21:39:14 +08:00
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseStartupCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume STARTUP
|
|
|
|
|
if p.curToken.Type != TokenService {
|
|
|
|
|
return nil, fmt.Errorf("expected SERVICE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
serviceNum, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("startup_service")
|
|
|
|
|
cmd.Params["number"] = serviceNum
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseShutdownCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume SHUTDOWN
|
|
|
|
|
if p.curToken.Type != TokenService {
|
|
|
|
|
return nil, fmt.Errorf("expected SERVICE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
serviceNum, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("shutdown_service")
|
|
|
|
|
cmd.Params["number"] = serviceNum
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseRestartCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume RESTART
|
|
|
|
|
if p.curToken.Type != TokenService {
|
|
|
|
|
return nil, fmt.Errorf("expected SERVICE")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
serviceNum, err := p.parseNumber()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("restart_service")
|
|
|
|
|
cmd.Params["number"] = serviceNum
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Parser) parseUnsetCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume UNSET
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenToken {
|
|
|
|
|
return nil, fmt.Errorf("expected TOKEN after UNSET")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional for UNSET TOKEN
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
return NewCommand("unset_token"), nil
|
|
|
|
|
}
|
2026-04-07 09:44:51 +08:00
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
// Internal
|
|
|
|
|
// parseUpdateCommand parses: UPDATE CHUNK 'chunk_id' OF DATASET 'dataset_name' SET '{"content": "..."}'
|
2026-04-07 09:44:51 +08:00
|
|
|
func (p *Parser) parseUpdateCommand() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume UPDATE
|
|
|
|
|
|
2026-04-09 09:52:31 +08:00
|
|
|
if p.curToken.Type == TokenChunk {
|
|
|
|
|
return p.parseUpdateChunk()
|
2026-04-07 09:44:51 +08:00
|
|
|
}
|
2026-04-09 09:52:31 +08:00
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("unknown UPDATE target: %s", p.curToken.Value)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 15:30:14 +08:00
|
|
|
// Internal CLI for GO
|
2026-04-09 09:52:31 +08:00
|
|
|
// parseUpdateChunk parses: UPDATE CHUNK 'chunk_id' OF DATASET 'dataset_name' SET '{"content": "..."}'
|
|
|
|
|
func (p *Parser) parseUpdateChunk() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume CHUNK
|
2026-04-07 09:44:51 +08:00
|
|
|
|
|
|
|
|
// Parse chunk_id
|
|
|
|
|
chunkID, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected chunk_id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := NewCommand("update_chunk")
|
|
|
|
|
cmd.Params["chunk_id"] = chunkID
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF after chunk_id")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
if p.curToken.Type != TokenDataset {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET after OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse dataset_name
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected dataset_name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type != TokenSet {
|
|
|
|
|
return nil, fmt.Errorf("expected SET after dataset_name")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse JSON body
|
|
|
|
|
jsonBody, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected JSON body: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["json_body"] = jsonBody
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseSetMeta parses: SET METADATA OF DOCUMENT 'doc_id' TO '{"key": "value"}'
|
|
|
|
|
func (p *Parser) parseSetMeta() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume METADATA
|
|
|
|
|
|
|
|
|
|
// Expect OF
|
|
|
|
|
if p.curToken.Type != TokenOf {
|
|
|
|
|
return nil, fmt.Errorf("expected OF after SET METADATA")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Expect DOCUMENT
|
|
|
|
|
if p.curToken.Type != TokenDocument {
|
|
|
|
|
return nil, fmt.Errorf("expected DOCUMENT after SET METADATA OF")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse doc_id
|
|
|
|
|
docID, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected doc_id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd := NewCommand("set_meta")
|
|
|
|
|
cmd.Params["doc_id"] = docID
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Expect TO
|
|
|
|
|
if p.curToken.Type != TokenTo {
|
|
|
|
|
return nil, fmt.Errorf("expected TO after doc_id")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse meta JSON
|
|
|
|
|
meta, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected meta JSON: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["meta"] = meta
|
|
|
|
|
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseRemoveTags parses: REMOVE TAGS 'tag1', 'tag2' from DATASET 'dataset_name';
|
|
|
|
|
func (p *Parser) parseRemoveTags() (*Command, error) {
|
|
|
|
|
p.nextToken() // consume TAGS
|
|
|
|
|
|
|
|
|
|
// Parse first tag
|
|
|
|
|
tag, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected tag: %w", err)
|
|
|
|
|
}
|
|
|
|
|
tags := []string{tag}
|
|
|
|
|
|
|
|
|
|
// Parse additional tags separated by commas
|
|
|
|
|
for {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenComma {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
tag, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected tag after comma: %w", err)
|
|
|
|
|
}
|
|
|
|
|
tags = append(tags, tag)
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cmd := NewCommand("rm_tags")
|
|
|
|
|
cmd.Params["tags"] = tags
|
|
|
|
|
|
|
|
|
|
// Expect from
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM after tags")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Expect DATASET
|
|
|
|
|
if p.curToken.Type != TokenDataset {
|
|
|
|
|
return nil, fmt.Errorf("expected DATASET after FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse dataset_name
|
|
|
|
|
datasetName, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected dataset_name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["dataset_name"] = datasetName
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|
2026-04-09 09:52:31 +08:00
|
|
|
|
|
|
|
|
// parseRemoveChunk parses:
|
|
|
|
|
// - REMOVE CHUNKS 'chunk_id1', 'chunk_id2' FROM DOCUMENT 'doc_id';
|
|
|
|
|
// - REMOVE ALL CHUNKS FROM DOCUMENT 'doc_id';
|
|
|
|
|
func (p *Parser) parseRemoveChunk() (*Command, error) {
|
|
|
|
|
cmd := NewCommand("remove_chunks")
|
|
|
|
|
|
|
|
|
|
// Check if ALL CHUNKS - if we came here from TokenAll case, curToken is already ALL
|
|
|
|
|
if p.curToken.Type == TokenAll {
|
|
|
|
|
p.nextToken() // consume ALL
|
|
|
|
|
if p.curToken.Type != TokenChunks {
|
|
|
|
|
return nil, fmt.Errorf("expected CHUNKS after ALL")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken() // consume CHUNKS
|
|
|
|
|
cmd.Params["delete_all"] = true
|
|
|
|
|
} else {
|
|
|
|
|
// curToken is TokenChunks, consume it first
|
|
|
|
|
p.nextToken()
|
|
|
|
|
// Multiple chunks: REMOVE CHUNKS 'id1', 'id2' FROM DOCUMENT 'doc_id'
|
|
|
|
|
// Parse first chunk ID
|
|
|
|
|
chunkID, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected chunk_id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
chunkIDs := []string{chunkID}
|
|
|
|
|
|
|
|
|
|
// Parse additional chunk IDs separated by commas
|
|
|
|
|
for {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
if p.curToken.Type == TokenComma {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
chunkID, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected chunk_id after comma: %w", err)
|
|
|
|
|
}
|
|
|
|
|
chunkIDs = append(chunkIDs, chunkID)
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["chunk_ids"] = chunkIDs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expect FROM
|
|
|
|
|
if p.curToken.Type != TokenFrom {
|
|
|
|
|
return nil, fmt.Errorf("expected FROM after chunk(s)")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Expect DOCUMENT
|
|
|
|
|
if p.curToken.Type != TokenDocument {
|
|
|
|
|
return nil, fmt.Errorf("expected DOCUMENT after FROM")
|
|
|
|
|
}
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Parse doc_id
|
|
|
|
|
docID, err := p.parseQuotedString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("expected doc_id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cmd.Params["doc_id"] = docID
|
|
|
|
|
p.nextToken()
|
|
|
|
|
|
|
|
|
|
// Semicolon is optional
|
|
|
|
|
if p.curToken.Type == TokenSemicolon {
|
|
|
|
|
p.nextToken()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|