From c1da753dd75aa3dc9c487e650c8fc78a3ab5b51c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:00:08 +0000 Subject: [PATCH 1/5] Initial plan From 7d2619759466ba1f935d5b198a06b53fbdc1d436 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:05:12 +0000 Subject: [PATCH 2/5] feat: add multi-argument rpc metadata and codegen support Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/gogen/gogen.go | 41 +++++++++++++++------- pkg/gogen/gogen_test.go | 46 +++++++++++++++++++++++++ pkg/tsgen/tsgen.go | 51 +++++++++++++++++++--------- pkg/tsgen/tsgen_wshclientapi_test.go | 28 +++++++++++++++ pkg/wshrpc/wshrpcmeta.go | 6 ++++ pkg/wshrpc/wshrpcmeta_test.go | 37 ++++++++++++++++++++ pkg/wshrpc/wshrpctypes.go | 6 +++- pkg/wshutil/wshadapter.go | 38 ++++++++++++++++----- 8 files changed, 215 insertions(+), 38 deletions(-) create mode 100644 pkg/gogen/gogen_test.go create mode 100644 pkg/tsgen/tsgen_wshclientapi_test.go create mode 100644 pkg/wshrpc/wshrpcmeta_test.go diff --git a/pkg/gogen/gogen.go b/pkg/gogen/gogen.go index 25b511de3b..e2299879f2 100644 --- a/pkg/gogen/gogen.go +++ b/pkg/gogen/gogen.go @@ -75,12 +75,7 @@ func GenerateMetaMapConsts(buf *strings.Builder, constPrefix string, rtype refle func GenMethod_Call(buf *strings.Builder, methodDecl *wshrpc.WshRpcMethodDecl) { fmt.Fprintf(buf, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName) - var dataType string - dataVarName := "nil" - if methodDecl.CommandDataType != nil { - dataType = ", data " + methodDecl.CommandDataType.String() - dataVarName = "data" - } + dataType, dataVarName := getWshMethodDataParamsAndExpr(methodDecl) returnType := "error" respName := "_" tParamVal := "any" @@ -101,12 +96,7 @@ func GenMethod_Call(buf *strings.Builder, methodDecl *wshrpc.WshRpcMethodDecl) { func GenMethod_ResponseStream(buf *strings.Builder, methodDecl *wshrpc.WshRpcMethodDecl) { fmt.Fprintf(buf, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName) - var dataType string - dataVarName := "nil" - if methodDecl.CommandDataType != nil { - dataType = ", data " + methodDecl.CommandDataType.String() - dataVarName = "data" - } + dataType, dataVarName := getWshMethodDataParamsAndExpr(methodDecl) respType := "any" if methodDecl.DefaultResponseDataType != nil { respType = methodDecl.DefaultResponseDataType.String() @@ -115,3 +105,30 @@ func GenMethod_ResponseStream(buf *strings.Builder, methodDecl *wshrpc.WshRpcMet fmt.Fprintf(buf, "\treturn sendRpcRequestResponseStreamHelper[%s](w, %q, %s, opts)\n", respType, methodDecl.Command, dataVarName) fmt.Fprintf(buf, "}\n\n") } + +func getWshMethodDataParamsAndExpr(methodDecl *wshrpc.WshRpcMethodDecl) (string, string) { + dataTypes := methodDecl.CommandDataTypes + if len(dataTypes) == 0 && methodDecl.CommandDataType != nil { + dataTypes = []reflect.Type{methodDecl.CommandDataType} + } + if len(dataTypes) == 0 { + return "", "nil" + } + if len(dataTypes) == 1 { + return ", data " + dataTypes[0].String(), "data" + } + var paramBuilder strings.Builder + var argBuilder strings.Builder + for idx, dataType := range dataTypes { + argName := fmt.Sprintf("arg%d", idx+1) + paramBuilder.WriteString(", ") + paramBuilder.WriteString(argName) + paramBuilder.WriteString(" ") + paramBuilder.WriteString(dataType.String()) + if idx > 0 { + argBuilder.WriteString(", ") + } + argBuilder.WriteString(argName) + } + return paramBuilder.String(), fmt.Sprintf("wshrpc.MultiArg{Args: []any{%s}}", argBuilder.String()) +} diff --git a/pkg/gogen/gogen_test.go b/pkg/gogen/gogen_test.go new file mode 100644 index 0000000000..2965d26f1f --- /dev/null +++ b/pkg/gogen/gogen_test.go @@ -0,0 +1,46 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gogen + +import ( + "reflect" + "strings" + "testing" + + "github.com/wavetermdev/waveterm/pkg/wshrpc" +) + +func TestGetWshMethodDataParamsAndExpr_MultiArg(t *testing.T) { + methodDecl := &wshrpc.WshRpcMethodDecl{ + CommandDataTypes: []reflect.Type{ + reflect.TypeOf(""), + reflect.TypeOf(0), + }, + } + params, expr := getWshMethodDataParamsAndExpr(methodDecl) + if params != ", arg1 string, arg2 int" { + t.Fatalf("unexpected params: %q", params) + } + if expr != "wshrpc.MultiArg{Args: []any{arg1, arg2}}" { + t.Fatalf("unexpected expr: %q", expr) + } +} + +func TestGenMethodCall_MultiArg(t *testing.T) { + methodDecl := &wshrpc.WshRpcMethodDecl{ + Command: "test", + CommandType: wshrpc.RpcType_Call, + MethodName: "TestCommand", + CommandDataTypes: []reflect.Type{reflect.TypeOf(""), reflect.TypeOf(0)}, + } + var sb strings.Builder + GenMethod_Call(&sb, methodDecl) + out := sb.String() + if !strings.Contains(out, "func TestCommand(w *wshutil.WshRpc, arg1 string, arg2 int, opts *wshrpc.RpcOpts) error {") { + t.Fatalf("generated method missing multi-arg signature:\n%s", out) + } + if !strings.Contains(out, "sendRpcRequestCallHelper[any](w, \"test\", wshrpc.MultiArg{Args: []any{arg1, arg2}}, opts)") { + t.Fatalf("generated method missing MultiArg payload:\n%s", out) + } +} diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index 23bc2f7b8d..d0d24de5e6 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -464,16 +464,12 @@ func generateWshClientApiMethod_ResponseStream(methodDecl *wshrpc.WshRpcMethodDe if methodDecl.DefaultResponseDataType != nil { respType, _ = TypeToTSType(methodDecl.DefaultResponseDataType, tsTypesMap) } - dataName := "null" - if methodDecl.CommandDataType != nil { - dataName = "data" - } + methodSigDataParams, dataName := getTsWshMethodDataParamsAndExpr(methodDecl, tsTypesMap) genRespType := fmt.Sprintf("AsyncGenerator<%s, void, boolean>", respType) - if methodDecl.CommandDataType != nil { - cmdDataTsName, _ := TypeToTSType(methodDecl.CommandDataType, tsTypesMap) - sb.WriteString(fmt.Sprintf(" %s(client: WshClient, data: %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, cmdDataTsName, genRespType)) - } else { + if methodSigDataParams == "" { sb.WriteString(fmt.Sprintf(" %s(client: WshClient, opts?: RpcOpts): %s {\n", methodDecl.MethodName, genRespType)) + } else { + sb.WriteString(fmt.Sprintf(" %s(client: WshClient, %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, methodSigDataParams, genRespType)) } sb.WriteString(fmt.Sprintf(" return client.wshRpcStream(%q, %s, opts);\n", methodDecl.Command, dataName)) sb.WriteString(" }\n") @@ -488,15 +484,11 @@ func generateWshClientApiMethod_Call(methodDecl *wshrpc.WshRpcMethodDecl, tsType rtnTypeName, _ := TypeToTSType(methodDecl.DefaultResponseDataType, tsTypesMap) rtnType = fmt.Sprintf("Promise<%s>", rtnTypeName) } - dataName := "null" - if methodDecl.CommandDataType != nil { - dataName = "data" - } - if methodDecl.CommandDataType != nil { - cmdDataTsName, _ := TypeToTSType(methodDecl.CommandDataType, tsTypesMap) - sb.WriteString(fmt.Sprintf(" %s(client: WshClient, data: %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, cmdDataTsName, rtnType)) - } else { + methodSigDataParams, dataName := getTsWshMethodDataParamsAndExpr(methodDecl, tsTypesMap) + if methodSigDataParams == "" { sb.WriteString(fmt.Sprintf(" %s(client: WshClient, opts?: RpcOpts): %s {\n", methodDecl.MethodName, rtnType)) + } else { + sb.WriteString(fmt.Sprintf(" %s(client: WshClient, %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, methodSigDataParams, rtnType)) } methodBody := fmt.Sprintf(" return client.wshRpcCall(%q, %s, opts);\n", methodDecl.Command, dataName) sb.WriteString(methodBody) @@ -504,6 +496,33 @@ func generateWshClientApiMethod_Call(methodDecl *wshrpc.WshRpcMethodDecl, tsType return sb.String() } +func getTsWshMethodDataParamsAndExpr(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) (string, string) { + dataTypes := methodDecl.CommandDataTypes + if len(dataTypes) == 0 && methodDecl.CommandDataType != nil { + dataTypes = []reflect.Type{methodDecl.CommandDataType} + } + if len(dataTypes) == 0 { + return "", "null" + } + if len(dataTypes) == 1 { + cmdDataTsName, _ := TypeToTSType(dataTypes[0], tsTypesMap) + return fmt.Sprintf("data: %s", cmdDataTsName), "data" + } + var methodParamBuilder strings.Builder + var argBuilder strings.Builder + for idx, dataType := range dataTypes { + if idx > 0 { + methodParamBuilder.WriteString(", ") + argBuilder.WriteString(", ") + } + argName := fmt.Sprintf("arg%d", idx+1) + cmdDataTsName, _ := TypeToTSType(dataType, tsTypesMap) + methodParamBuilder.WriteString(fmt.Sprintf("%s: %s", argName, cmdDataTsName)) + argBuilder.WriteString(argName) + } + return methodParamBuilder.String(), fmt.Sprintf("{ args: [%s] }", argBuilder.String()) +} + func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) { for _, typeUnion := range TypeUnions { GenerateTSTypeUnion(typeUnion, tsTypesMap) diff --git a/pkg/tsgen/tsgen_wshclientapi_test.go b/pkg/tsgen/tsgen_wshclientapi_test.go new file mode 100644 index 0000000000..2839a8be40 --- /dev/null +++ b/pkg/tsgen/tsgen_wshclientapi_test.go @@ -0,0 +1,28 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package tsgen + +import ( + "reflect" + "strings" + "testing" + + "github.com/wavetermdev/waveterm/pkg/wshrpc" +) + +func TestGenerateWshClientApiMethodCall_MultiArg(t *testing.T) { + methodDecl := &wshrpc.WshRpcMethodDecl{ + Command: "test", + CommandType: wshrpc.RpcType_Call, + MethodName: "TestCommand", + CommandDataTypes: []reflect.Type{reflect.TypeOf(""), reflect.TypeOf(0)}, + } + out := GenerateWshClientApiMethod(methodDecl, map[reflect.Type]string{}) + if !strings.Contains(out, "TestCommand(client: WshClient, arg1: string, arg2: number, opts?: RpcOpts): Promise {") { + t.Fatalf("generated method missing multi-arg signature:\n%s", out) + } + if !strings.Contains(out, "return client.wshRpcCall(\"test\", { args: [arg1, arg2] }, opts);") { + t.Fatalf("generated method missing MultiArg payload:\n%s", out) + } +} diff --git a/pkg/wshrpc/wshrpcmeta.go b/pkg/wshrpc/wshrpcmeta.go index 848de3453f..5c556c6de3 100644 --- a/pkg/wshrpc/wshrpcmeta.go +++ b/pkg/wshrpc/wshrpcmeta.go @@ -16,6 +16,7 @@ type WshRpcMethodDecl struct { CommandType string MethodName string CommandDataType reflect.Type + CommandDataTypes []reflect.Type DefaultResponseDataType reflect.Type } @@ -76,10 +77,15 @@ func generateWshCommandDecl(method reflect.Method) *WshRpcMethodDecl { decl.CommandType = getWshCommandType(method) decl.MethodName = method.Name var cdataType reflect.Type + var cdataTypes []reflect.Type if method.Type.NumIn() > 1 { cdataType = method.Type.In(1) } + for idx := 1; idx < method.Type.NumIn(); idx++ { + cdataTypes = append(cdataTypes, method.Type.In(idx)) + } decl.CommandDataType = cdataType + decl.CommandDataTypes = cdataTypes decl.DefaultResponseDataType = getWshMethodResponseType(decl.CommandType, method) return decl } diff --git a/pkg/wshrpc/wshrpcmeta_test.go b/pkg/wshrpc/wshrpcmeta_test.go new file mode 100644 index 0000000000..508a0f1b9e --- /dev/null +++ b/pkg/wshrpc/wshrpcmeta_test.go @@ -0,0 +1,37 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package wshrpc + +import ( + "context" + "reflect" + "testing" +) + +type testRpcInterfaceForDecls interface { + NoArgCommand(ctx context.Context) error + OneArgCommand(ctx context.Context, data string) error + TwoArgCommand(ctx context.Context, arg1 string, arg2 int) error +} + +func TestGenerateWshCommandDecl_MultiArgs(t *testing.T) { + rtype := reflect.TypeOf((*testRpcInterfaceForDecls)(nil)).Elem() + method, ok := rtype.MethodByName("TwoArgCommand") + if !ok { + t.Fatalf("TwoArgCommand method not found") + } + decl := generateWshCommandDecl(method) + if decl.Command != "twoarg" { + t.Fatalf("expected command twoarg, got %q", decl.Command) + } + if len(decl.CommandDataTypes) != 2 { + t.Fatalf("expected 2 command data types, got %d", len(decl.CommandDataTypes)) + } + if decl.CommandDataTypes[0].Kind() != reflect.String || decl.CommandDataTypes[1].Kind() != reflect.Int { + t.Fatalf("unexpected command data types: %#v", decl.CommandDataTypes) + } + if decl.CommandDataType == nil || decl.CommandDataType.Kind() != reflect.String { + t.Fatalf("expected legacy single data type to remain first arg type, got %v", decl.CommandDataType) + } +} diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index c7fa0cb428..dc4a4eaccb 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -23,10 +23,14 @@ type RespOrErrorUnion[T any] struct { Error error } +type MultiArg struct { + Args []any `json:"args"` +} + // Instructions for adding a new RPC call // * methods must end with Command // * methods must take context as their first parameter -// * methods may take up to one parameter, and may return either just an error, or one return value plus an error +// * methods may take additional typed parameters, and may return either just an error, or one return value plus an error // * after modifying WshRpcInterface, run `task generate` to regnerate bindings type WshRpcInterface interface { diff --git a/pkg/wshutil/wshadapter.go b/pkg/wshutil/wshadapter.go index a91bb4568f..acadab1c32 100644 --- a/pkg/wshutil/wshadapter.go +++ b/pkg/wshutil/wshadapter.go @@ -14,6 +14,7 @@ import ( ) var WshCommandDeclMap = wshrpc.GenerateWshCommandDeclMap() +var multiArgRType = reflect.TypeOf(wshrpc.MultiArg{}) func findCmdMethod(impl any, cmd string) *reflect.Method { rtype := reflect.TypeOf(impl) @@ -53,19 +54,15 @@ func noImplHandler(handler *RpcResponseHandler) bool { return true } -func recodeCommandData(command string, data any) (any, error) { - // only applies to initial command packet - if command == "" { +func recodeCommandData(command string, data any, commandDataType reflect.Type) (any, error) { + if command == "" || commandDataType == nil { return data, nil } methodDecl := WshCommandDeclMap[command] if methodDecl == nil { return data, fmt.Errorf("command %q not found", command) } - if methodDecl.CommandDataType == nil { - return data, nil - } - commandDataPtr := reflect.New(methodDecl.CommandDataType).Interface() + commandDataPtr := reflect.New(commandDataType).Interface() if data != nil { err := utilfn.ReUnmarshal(commandDataPtr, data) if err != nil { @@ -103,13 +100,36 @@ func serverImplAdapter(impl any) func(*RpcResponseHandler) bool { implMethod := reflect.ValueOf(impl).MethodByName(rmethod.Name) var callParams []reflect.Value callParams = append(callParams, reflect.ValueOf(handler.Context())) - if methodDecl.CommandDataType != nil { - cmdData, err := recodeCommandData(cmd, handler.GetCommandRawData()) + commandDataTypes := methodDecl.CommandDataTypes + if len(commandDataTypes) == 0 && methodDecl.CommandDataType != nil { + commandDataTypes = []reflect.Type{methodDecl.CommandDataType} + } + if len(commandDataTypes) == 1 { + cmdData, err := recodeCommandData(cmd, handler.GetCommandRawData(), commandDataTypes[0]) if err != nil { handler.SendResponseError(err) return true } callParams = append(callParams, reflect.ValueOf(cmdData)) + } else if len(commandDataTypes) > 1 { + multiArgAny, err := recodeCommandData(cmd, handler.GetCommandRawData(), multiArgRType) + if err != nil { + handler.SendResponseError(err) + return true + } + multiArg := multiArgAny.(wshrpc.MultiArg) + if len(multiArg.Args) != len(commandDataTypes) { + handler.SendResponseError(fmt.Errorf("command %q expected %d args, got %d", cmd, len(commandDataTypes), len(multiArg.Args))) + return true + } + for idx, commandDataType := range commandDataTypes { + cmdData, err := recodeCommandData(cmd, multiArg.Args[idx], commandDataType) + if err != nil { + handler.SendResponseError(err) + return true + } + callParams = append(callParams, reflect.ValueOf(cmdData)) + } } if methodDecl.CommandType == wshrpc.RpcType_Call { rtnVals := implMethod.Call(callParams) From e2b989d61a640aed9b402e9968f39ce7e2df35cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:08:06 +0000 Subject: [PATCH 3/5] fix: harden multi-arg adapter handling and centralize data type fallback Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/gogen/gogen.go | 5 +---- pkg/tsgen/tsgen.go | 5 +---- pkg/wshrpc/wshrpcmeta.go | 10 ++++++++++ pkg/wshrpc/wshrpcmeta_test.go | 3 +++ pkg/wshutil/wshadapter.go | 11 ++++++----- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/pkg/gogen/gogen.go b/pkg/gogen/gogen.go index e2299879f2..bc3662a678 100644 --- a/pkg/gogen/gogen.go +++ b/pkg/gogen/gogen.go @@ -107,10 +107,7 @@ func GenMethod_ResponseStream(buf *strings.Builder, methodDecl *wshrpc.WshRpcMet } func getWshMethodDataParamsAndExpr(methodDecl *wshrpc.WshRpcMethodDecl) (string, string) { - dataTypes := methodDecl.CommandDataTypes - if len(dataTypes) == 0 && methodDecl.CommandDataType != nil { - dataTypes = []reflect.Type{methodDecl.CommandDataType} - } + dataTypes := methodDecl.GetCommandDataTypes() if len(dataTypes) == 0 { return "", "nil" } diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index d0d24de5e6..062056e290 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -497,10 +497,7 @@ func generateWshClientApiMethod_Call(methodDecl *wshrpc.WshRpcMethodDecl, tsType } func getTsWshMethodDataParamsAndExpr(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) (string, string) { - dataTypes := methodDecl.CommandDataTypes - if len(dataTypes) == 0 && methodDecl.CommandDataType != nil { - dataTypes = []reflect.Type{methodDecl.CommandDataType} - } + dataTypes := methodDecl.GetCommandDataTypes() if len(dataTypes) == 0 { return "", "null" } diff --git a/pkg/wshrpc/wshrpcmeta.go b/pkg/wshrpc/wshrpcmeta.go index 5c556c6de3..fcbe571462 100644 --- a/pkg/wshrpc/wshrpcmeta.go +++ b/pkg/wshrpc/wshrpcmeta.go @@ -20,6 +20,16 @@ type WshRpcMethodDecl struct { DefaultResponseDataType reflect.Type } +func (decl *WshRpcMethodDecl) GetCommandDataTypes() []reflect.Type { + if len(decl.CommandDataTypes) > 0 { + return decl.CommandDataTypes + } + if decl.CommandDataType != nil { + return []reflect.Type{decl.CommandDataType} + } + return nil +} + var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem() var wshRpcInterfaceRType = reflect.TypeOf((*WshRpcInterface)(nil)).Elem() diff --git a/pkg/wshrpc/wshrpcmeta_test.go b/pkg/wshrpc/wshrpcmeta_test.go index 508a0f1b9e..5ac3366654 100644 --- a/pkg/wshrpc/wshrpcmeta_test.go +++ b/pkg/wshrpc/wshrpcmeta_test.go @@ -34,4 +34,7 @@ func TestGenerateWshCommandDecl_MultiArgs(t *testing.T) { if decl.CommandDataType == nil || decl.CommandDataType.Kind() != reflect.String { t.Fatalf("expected legacy single data type to remain first arg type, got %v", decl.CommandDataType) } + if len(decl.GetCommandDataTypes()) != 2 { + t.Fatalf("expected helper to return two command data types") + } } diff --git a/pkg/wshutil/wshadapter.go b/pkg/wshutil/wshadapter.go index acadab1c32..db7f7c20b6 100644 --- a/pkg/wshutil/wshadapter.go +++ b/pkg/wshutil/wshadapter.go @@ -100,10 +100,7 @@ func serverImplAdapter(impl any) func(*RpcResponseHandler) bool { implMethod := reflect.ValueOf(impl).MethodByName(rmethod.Name) var callParams []reflect.Value callParams = append(callParams, reflect.ValueOf(handler.Context())) - commandDataTypes := methodDecl.CommandDataTypes - if len(commandDataTypes) == 0 && methodDecl.CommandDataType != nil { - commandDataTypes = []reflect.Type{methodDecl.CommandDataType} - } + commandDataTypes := methodDecl.GetCommandDataTypes() if len(commandDataTypes) == 1 { cmdData, err := recodeCommandData(cmd, handler.GetCommandRawData(), commandDataTypes[0]) if err != nil { @@ -117,7 +114,11 @@ func serverImplAdapter(impl any) func(*RpcResponseHandler) bool { handler.SendResponseError(err) return true } - multiArg := multiArgAny.(wshrpc.MultiArg) + multiArg, ok := multiArgAny.(wshrpc.MultiArg) + if !ok { + handler.SendResponseError(fmt.Errorf("command %q invalid multi arg payload", cmd)) + return true + } if len(multiArg.Args) != len(commandDataTypes) { handler.SendResponseError(fmt.Errorf("command %q expected %d args, got %d", cmd, len(commandDataTypes), len(multiArg.Args))) return true From bd973b69243554bf98c5d44e6251a850f74a0ab3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:28:12 +0000 Subject: [PATCH 4/5] feat: add testmultiarg rpc command and cli invocation Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- cmd/wsh/cmd/wshcmd-test.go | 6 ++++++ frontend/app/store/wshclientapi.ts | 5 +++++ pkg/wshrpc/wshclient/wshclient.go | 6 ++++++ pkg/wshrpc/wshrpcmeta_test.go | 13 +++++++++++++ pkg/wshrpc/wshrpctypes.go | 19 ++++++++++--------- pkg/wshrpc/wshserver/wshserver.go | 10 ++++++++++ 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-test.go b/cmd/wsh/cmd/wshcmd-test.go index 20ec59e868..24706a1fe2 100644 --- a/cmd/wsh/cmd/wshcmd-test.go +++ b/cmd/wsh/cmd/wshcmd-test.go @@ -5,6 +5,7 @@ package cmd import ( "github.com/spf13/cobra" + "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) var testCmd = &cobra.Command{ @@ -20,5 +21,10 @@ func init() { } func runTestCmd(cmd *cobra.Command, args []string) error { + rtn, err := wshclient.TestMultiArgCommand(RpcClient, "testarg", 42, true, nil) + if err != nil { + return err + } + WriteStdout("%s\n", rtn) return nil } diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts index e246d761ef..33f85126d9 100644 --- a/frontend/app/store/wshclientapi.ts +++ b/frontend/app/store/wshclientapi.ts @@ -757,6 +757,11 @@ class RpcApiType { return client.wshRpcCall("test", data, opts); } + // command "testmultiarg" [call] + TestMultiArgCommand(client: WshClient, arg1: string, arg2: number, arg3: boolean, opts?: RpcOpts): Promise { + return client.wshRpcCall("testmultiarg", { args: [arg1, arg2, arg3] }, opts); + } + // command "vdomasyncinitiation" [call] VDomAsyncInitiationCommand(client: WshClient, data: VDomAsyncInitiationRequest, opts?: RpcOpts): Promise { return client.wshRpcCall("vdomasyncinitiation", data, opts); diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index 6ac4746d16..56c738dcaf 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -908,6 +908,12 @@ func TestCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error { return err } +// command "testmultiarg", wshserver.TestMultiArgCommand +func TestMultiArgCommand(w *wshutil.WshRpc, arg1 string, arg2 int, arg3 bool, opts *wshrpc.RpcOpts) (string, error) { + resp, err := sendRpcRequestCallHelper[string](w, "testmultiarg", wshrpc.MultiArg{Args: []any{arg1, arg2, arg3}}, opts) + return resp, err +} + // command "vdomasyncinitiation", wshserver.VDomAsyncInitiationCommand func VDomAsyncInitiationCommand(w *wshutil.WshRpc, data vdom.VDomAsyncInitiationRequest, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "vdomasyncinitiation", data, opts) diff --git a/pkg/wshrpc/wshrpcmeta_test.go b/pkg/wshrpc/wshrpcmeta_test.go index 5ac3366654..e4df9103e6 100644 --- a/pkg/wshrpc/wshrpcmeta_test.go +++ b/pkg/wshrpc/wshrpcmeta_test.go @@ -38,3 +38,16 @@ func TestGenerateWshCommandDecl_MultiArgs(t *testing.T) { t.Fatalf("expected helper to return two command data types") } } + +func TestGenerateWshCommandDeclMap_TestMultiArgCommand(t *testing.T) { + decl := GenerateWshCommandDeclMap()["testmultiarg"] + if decl == nil { + t.Fatalf("expected testmultiarg command declaration") + } + if decl.MethodName != "TestMultiArgCommand" { + t.Fatalf("expected TestMultiArgCommand method name, got %q", decl.MethodName) + } + if len(decl.GetCommandDataTypes()) != 3 { + t.Fatalf("expected 3 command args, got %d", len(decl.GetCommandDataTypes())) + } +} diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index dc4a4eaccb..c734b076eb 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -73,6 +73,7 @@ type WshRpcInterface interface { StreamWaveAiCommand(ctx context.Context, request WaveAIStreamRequest) chan RespOrErrorUnion[WaveAIPacketType] StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData] TestCommand(ctx context.Context, data string) error + TestMultiArgCommand(ctx context.Context, arg1 string, arg2 int, arg3 bool) (string, error) SetConfigCommand(ctx context.Context, data MetaSettingsType) error SetConnectionsConfigCommand(ctx context.Context, data ConnConfigRequest) error GetFullConfigCommand(ctx context.Context) (wconfig.FullConfigType, error) @@ -899,13 +900,13 @@ type BlockJobStatusData struct { } type FocusedBlockData struct { - BlockId string `json:"blockid"` - ViewType string `json:"viewtype"` - Controller string `json:"controller"` - ConnName string `json:"connname"` - BlockMeta waveobj.MetaMapType `json:"blockmeta"` - TermJobStatus *BlockJobStatusData `json:"termjobstatus,omitempty"` - ConnStatus *ConnStatus `json:"connstatus,omitempty"` - TermShellIntegrationStatus string `json:"termshellintegrationstatus,omitempty"` - TermLastCommand string `json:"termlastcommand,omitempty"` + BlockId string `json:"blockid"` + ViewType string `json:"viewtype"` + Controller string `json:"controller"` + ConnName string `json:"connname"` + BlockMeta waveobj.MetaMapType `json:"blockmeta"` + TermJobStatus *BlockJobStatusData `json:"termjobstatus,omitempty"` + ConnStatus *ConnStatus `json:"connstatus,omitempty"` + TermShellIntegrationStatus string `json:"termshellintegrationstatus,omitempty"` + TermLastCommand string `json:"termlastcommand,omitempty"` } diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index cdb7abe022..bd49c091d0 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -81,6 +81,16 @@ func (ws *WshServer) TestCommand(ctx context.Context, data string) error { return nil } +func (ws *WshServer) TestMultiArgCommand(ctx context.Context, arg1 string, arg2 int, arg3 bool) (string, error) { + defer func() { + panichandler.PanicHandler("TestMultiArgCommand", recover()) + }() + rpcSource := wshutil.GetRpcSourceFromContext(ctx) + rtn := fmt.Sprintf("src:%s arg1:%q arg2:%d arg3:%t", rpcSource, arg1, arg2, arg3) + log.Printf("TESTMULTI %s\n", rtn) + return rtn, nil +} + // for testing func (ws *WshServer) MessageCommand(ctx context.Context, data wshrpc.CommandMessageData) error { log.Printf("MESSAGE: %s\n", data.Message) From ae5f11c629b1a6bd476af057122f78e7cf23e580 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 2 Mar 2026 11:49:09 -0800 Subject: [PATCH 5/5] remove unused single data type --- pkg/wshrpc/wshrpcmeta.go | 14 +------------- pkg/wshrpc/wshrpcmeta_test.go | 3 --- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pkg/wshrpc/wshrpcmeta.go b/pkg/wshrpc/wshrpcmeta.go index fcbe571462..f3d6e3f4e7 100644 --- a/pkg/wshrpc/wshrpcmeta.go +++ b/pkg/wshrpc/wshrpcmeta.go @@ -15,19 +15,12 @@ type WshRpcMethodDecl struct { Command string CommandType string MethodName string - CommandDataType reflect.Type CommandDataTypes []reflect.Type DefaultResponseDataType reflect.Type } func (decl *WshRpcMethodDecl) GetCommandDataTypes() []reflect.Type { - if len(decl.CommandDataTypes) > 0 { - return decl.CommandDataTypes - } - if decl.CommandDataType != nil { - return []reflect.Type{decl.CommandDataType} - } - return nil + return decl.CommandDataTypes } var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem() @@ -86,15 +79,10 @@ func generateWshCommandDecl(method reflect.Method) *WshRpcMethodDecl { decl.Command = strings.ToLower(cmdStr) decl.CommandType = getWshCommandType(method) decl.MethodName = method.Name - var cdataType reflect.Type var cdataTypes []reflect.Type - if method.Type.NumIn() > 1 { - cdataType = method.Type.In(1) - } for idx := 1; idx < method.Type.NumIn(); idx++ { cdataTypes = append(cdataTypes, method.Type.In(idx)) } - decl.CommandDataType = cdataType decl.CommandDataTypes = cdataTypes decl.DefaultResponseDataType = getWshMethodResponseType(decl.CommandType, method) return decl diff --git a/pkg/wshrpc/wshrpcmeta_test.go b/pkg/wshrpc/wshrpcmeta_test.go index e4df9103e6..4479c52b14 100644 --- a/pkg/wshrpc/wshrpcmeta_test.go +++ b/pkg/wshrpc/wshrpcmeta_test.go @@ -31,9 +31,6 @@ func TestGenerateWshCommandDecl_MultiArgs(t *testing.T) { if decl.CommandDataTypes[0].Kind() != reflect.String || decl.CommandDataTypes[1].Kind() != reflect.Int { t.Fatalf("unexpected command data types: %#v", decl.CommandDataTypes) } - if decl.CommandDataType == nil || decl.CommandDataType.Kind() != reflect.String { - t.Fatalf("expected legacy single data type to remain first arg type, got %v", decl.CommandDataType) - } if len(decl.GetCommandDataTypes()) != 2 { t.Fatalf("expected helper to return two command data types") }