Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/wsh/cmd/wshcmd-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"github.com/spf13/cobra"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
)

var testCmd = &cobra.Command{
Expand All @@ -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
}
5 changes: 5 additions & 0 deletions frontend/app/store/wshclientapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
return client.wshRpcCall("testmultiarg", { args: [arg1, arg2, arg3] }, opts);
}

// command "vdomasyncinitiation" [call]
VDomAsyncInitiationCommand(client: WshClient, data: VDomAsyncInitiationRequest, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("vdomasyncinitiation", data, opts);
Expand Down
38 changes: 26 additions & 12 deletions pkg/gogen/gogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()
Expand All @@ -115,3 +105,27 @@ 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.GetCommandDataTypes()
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())
}
46 changes: 46 additions & 0 deletions pkg/gogen/gogen_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
48 changes: 32 additions & 16 deletions pkg/tsgen/tsgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -488,22 +484,42 @@ 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)
sb.WriteString(" }\n")
return sb.String()
}

func getTsWshMethodDataParamsAndExpr(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMap map[reflect.Type]string) (string, string) {
dataTypes := methodDecl.GetCommandDataTypes()
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)
Expand Down
28 changes: 28 additions & 0 deletions pkg/tsgen/tsgen_wshclientapi_test.go
Original file line number Diff line number Diff line change
@@ -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<void> {") {
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)
}
}
6 changes: 6 additions & 0 deletions pkg/wshrpc/wshclient/wshclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 9 additions & 5 deletions pkg/wshrpc/wshrpcmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ type WshRpcMethodDecl struct {
Command string
CommandType string
MethodName string
CommandDataType reflect.Type
CommandDataTypes []reflect.Type
DefaultResponseDataType reflect.Type
}

func (decl *WshRpcMethodDecl) GetCommandDataTypes() []reflect.Type {
return decl.CommandDataTypes
}

var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
var wshRpcInterfaceRType = reflect.TypeOf((*WshRpcInterface)(nil)).Elem()

Expand Down Expand Up @@ -75,11 +79,11 @@ func generateWshCommandDecl(method reflect.Method) *WshRpcMethodDecl {
decl.Command = strings.ToLower(cmdStr)
decl.CommandType = getWshCommandType(method)
decl.MethodName = method.Name
var cdataType reflect.Type
if method.Type.NumIn() > 1 {
cdataType = method.Type.In(1)
var cdataTypes []reflect.Type
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
}
Expand Down
50 changes: 50 additions & 0 deletions pkg/wshrpc/wshrpcmeta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 len(decl.GetCommandDataTypes()) != 2 {
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()))
}
}
25 changes: 15 additions & 10 deletions pkg/wshrpc/wshrpctypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -69,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)
Expand Down Expand Up @@ -895,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"`
}
10 changes: 10 additions & 0 deletions pkg/wshrpc/wshserver/wshserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading