diff --git a/pkg/backends/backend.go b/pkg/backends/backend.go index ae22b1c..db18920 100644 --- a/pkg/backends/backend.go +++ b/pkg/backends/backend.go @@ -23,6 +23,7 @@ type backend interface { SetPort(int) GetHost() string Validate() error + ParseCommand(string) (any, error) } var backendConstructors = map[BackendType]func() backend{ diff --git a/pkg/backends/llama.go b/pkg/backends/llama.go index dc07457..bb0f205 100644 --- a/pkg/backends/llama.go +++ b/pkg/backends/llama.go @@ -378,19 +378,19 @@ func (o *LlamaServerOptions) BuildDockerArgs() []string { return o.BuildCommandArgs() } -// ParseLlamaCommand parses a llama-server command string into LlamaServerOptions +// ParseCommand parses a llama-server command string into LlamaServerOptions // Supports multiple formats: // 1. Full command: "llama-server --model file.gguf" // 2. Full path: "/usr/local/bin/llama-server --model file.gguf" // 3. Args only: "--model file.gguf --gpu-layers 32" // 4. Multiline commands with backslashes -func ParseLlamaCommand(command string) (*LlamaServerOptions, error) { +func (o *LlamaServerOptions) ParseCommand(command string) (any, error) { executableNames := []string{"llama-server"} var subcommandNames []string // Llama has no subcommands // Use package-level llamaMultiValuedFlags variable var llamaOptions LlamaServerOptions - if err := ParseCommand(command, executableNames, subcommandNames, llamaMultiValuedFlags, &llamaOptions); err != nil { + if err := parseCommand(command, executableNames, subcommandNames, llamaMultiValuedFlags, &llamaOptions); err != nil { return nil, err } diff --git a/pkg/backends/llama_test.go b/pkg/backends/llama_test.go index c05a3a5..1698d37 100644 --- a/pkg/backends/llama_test.go +++ b/pkg/backends/llama_test.go @@ -385,7 +385,9 @@ func TestParseLlamaCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := backends.ParseLlamaCommand(tt.command) + var opts backends.LlamaServerOptions + resultAny, err := opts.ParseCommand(tt.command) + result, _ := resultAny.(*backends.LlamaServerOptions) if tt.expectErr { if err == nil { @@ -413,7 +415,9 @@ func TestParseLlamaCommand(t *testing.T) { func TestParseLlamaCommandArrays(t *testing.T) { command := "llama-server --model test.gguf --lora adapter1.bin --lora=adapter2.bin" - result, err := backends.ParseLlamaCommand(command) + var opts backends.LlamaServerOptions + resultAny, err := opts.ParseCommand(command) + result, _ := resultAny.(*backends.LlamaServerOptions) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -429,4 +433,4 @@ func TestParseLlamaCommandArrays(t *testing.T) { t.Errorf("expected lora[%d]=%s got %s", i, v, result.Lora[i]) } } -} \ No newline at end of file +} diff --git a/pkg/backends/mlx.go b/pkg/backends/mlx.go index d0ec602..4b70e3c 100644 --- a/pkg/backends/mlx.go +++ b/pkg/backends/mlx.go @@ -70,19 +70,19 @@ func (o *MlxServerOptions) BuildDockerArgs() []string { return []string{} } -// ParseMlxCommand parses a mlx_lm.server command string into MlxServerOptions +// ParseCommand parses a mlx_lm.server command string into MlxServerOptions // Supports multiple formats: // 1. Full command: "mlx_lm.server --model model/path" // 2. Full path: "/usr/local/bin/mlx_lm.server --model model/path" // 3. Args only: "--model model/path --host 0.0.0.0" // 4. Multiline commands with backslashes -func ParseMlxCommand(command string) (*MlxServerOptions, error) { +func (o *MlxServerOptions) ParseCommand(command string) (any, error) { executableNames := []string{"mlx_lm.server"} var subcommandNames []string // MLX has no subcommands multiValuedFlags := map[string]bool{} // MLX has no multi-valued flags var mlxOptions MlxServerOptions - if err := ParseCommand(command, executableNames, subcommandNames, multiValuedFlags, &mlxOptions); err != nil { + if err := parseCommand(command, executableNames, subcommandNames, multiValuedFlags, &mlxOptions); err != nil { return nil, err } diff --git a/pkg/backends/mlx_test.go b/pkg/backends/mlx_test.go index 0194551..d15be3d 100644 --- a/pkg/backends/mlx_test.go +++ b/pkg/backends/mlx_test.go @@ -96,7 +96,9 @@ func TestParseMlxCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := backends.ParseMlxCommand(tt.command) + var opts backends.MlxServerOptions + resultAny, err := opts.ParseCommand(tt.command) + result, _ := resultAny.(*backends.MlxServerOptions) if tt.expectErr { if err == nil { @@ -174,11 +176,11 @@ func TestMlxBuildCommandArgs_BooleanFields(t *testing.T) { func TestMlxBuildCommandArgs_ZeroValues(t *testing.T) { options := backends.MlxServerOptions{ - Port: 0, // Should be excluded - TopK: 0, // Should be excluded - Temp: 0, // Should be excluded - Model: "", // Should be excluded - LogLevel: "", // Should be excluded + Port: 0, // Should be excluded + TopK: 0, // Should be excluded + Temp: 0, // Should be excluded + Model: "", // Should be excluded + LogLevel: "", // Should be excluded TrustRemoteCode: false, // Should be excluded } @@ -199,4 +201,4 @@ func TestMlxBuildCommandArgs_ZeroValues(t *testing.T) { t.Errorf("Zero value argument %q should not be present in %v", excludedArg, args) } } -} \ No newline at end of file +} diff --git a/pkg/backends/parser.go b/pkg/backends/parser.go index df585c9..7a73249 100644 --- a/pkg/backends/parser.go +++ b/pkg/backends/parser.go @@ -9,8 +9,8 @@ import ( "strings" ) -// ParseCommand parses a command string into a target struct -func ParseCommand(command string, executableNames []string, subcommandNames []string, multiValuedFlags map[string]bool, target any) error { +// parseCommand parses a command string into a target struct +func parseCommand(command string, executableNames []string, subcommandNames []string, multiValuedFlags map[string]bool, target any) error { // Normalize multiline commands command = normalizeCommand(command) if command == "" { diff --git a/pkg/backends/vllm.go b/pkg/backends/vllm.go index 857eab3..a7f669d 100644 --- a/pkg/backends/vllm.go +++ b/pkg/backends/vllm.go @@ -202,14 +202,14 @@ func (o *VllmServerOptions) BuildDockerArgs() []string { return args } -// ParseVllmCommand parses a vLLM serve command string into VllmServerOptions +// ParseCommand parses a vLLM serve command string into VllmServerOptions // Supports multiple formats: // 1. Full command: "vllm serve --model MODEL_NAME --other-args" // 2. Full path: "/usr/local/bin/vllm serve --model MODEL_NAME" // 3. Serve only: "serve --model MODEL_NAME --other-args" // 4. Args only: "--model MODEL_NAME --other-args" // 5. Multiline commands with backslashes -func ParseVllmCommand(command string) (*VllmServerOptions, error) { +func (o *VllmServerOptions) ParseCommand(command string) (any, error) { executableNames := []string{"vllm"} subcommandNames := []string{"serve"} multiValuedFlags := map[string]bool{ @@ -223,7 +223,7 @@ func ParseVllmCommand(command string) (*VllmServerOptions, error) { } var vllmOptions VllmServerOptions - if err := ParseCommand(command, executableNames, subcommandNames, multiValuedFlags, &vllmOptions); err != nil { + if err := parseCommand(command, executableNames, subcommandNames, multiValuedFlags, &vllmOptions); err != nil { return nil, err } diff --git a/pkg/backends/vllm_test.go b/pkg/backends/vllm_test.go index b9e6a13..9f93fd1 100644 --- a/pkg/backends/vllm_test.go +++ b/pkg/backends/vllm_test.go @@ -92,7 +92,9 @@ func TestParseVllmCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := backends.ParseVllmCommand(tt.command) + var opts backends.VllmServerOptions + resultAny, err := opts.ParseCommand(tt.command) + result, _ := resultAny.(*backends.VllmServerOptions) if tt.expectErr { if err == nil { @@ -173,11 +175,11 @@ func TestVllmBuildCommandArgs_BooleanFields(t *testing.T) { func TestVllmBuildCommandArgs_ZeroValues(t *testing.T) { options := backends.VllmServerOptions{ - Port: 0, // Should be excluded - TensorParallelSize: 0, // Should be excluded - GPUMemoryUtilization: 0, // Should be excluded - Model: "", // Should be excluded (positional arg) - Host: "", // Should be excluded + Port: 0, // Should be excluded + TensorParallelSize: 0, // Should be excluded + GPUMemoryUtilization: 0, // Should be excluded + Model: "", // Should be excluded (positional arg) + Host: "", // Should be excluded EnableLogOutputs: false, // Should be excluded }