From 59ff522cd1c0afd8d08923ea88ee62ba880e1677 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 8 May 2023 16:06:51 -0700 Subject: [PATCH] updates to read openai token from clientdata --- cmd/main-server.go | 4 + db/migrations/000019_clientopenai.down.sql | 1 + db/migrations/000019_clientopenai.up.sql | 2 + pkg/cmdrunner/cmdrunner.go | 212 ++++++++++++++++----- pkg/remote/openai/openai.go | 6 +- pkg/remote/remote.go | 5 +- pkg/sstore/dbops.go | 15 +- pkg/sstore/migrate.go | 2 +- pkg/sstore/sstore.go | 21 ++ pkg/sstore/updatebus.go | 28 ++- 10 files changed, 235 insertions(+), 61 deletions(-) create mode 100644 db/migrations/000019_clientopenai.down.sql create mode 100644 db/migrations/000019_clientopenai.up.sql diff --git a/cmd/main-server.go b/cmd/main-server.go index 0aeeb2e46..171bb5279 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -144,6 +144,7 @@ func HandleGetClientData(w http.ResponseWriter, r *http.Request) { WriteJsonError(w, err) return } + cdata = cdata.Clean() mdata, err := sstore.GetCmdMigrationInfo(r.Context()) if err != nil { WriteJsonError(w, err) @@ -368,6 +369,9 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) { WriteJsonError(w, err) return } + if update != nil { + update.Clean() + } WriteJsonSuccess(w, update) return } diff --git a/db/migrations/000019_clientopenai.down.sql b/db/migrations/000019_clientopenai.down.sql new file mode 100644 index 000000000..983f5d519 --- /dev/null +++ b/db/migrations/000019_clientopenai.down.sql @@ -0,0 +1 @@ +ALTER TABLE client DROP COLUMN openaiopts; diff --git a/db/migrations/000019_clientopenai.up.sql b/db/migrations/000019_clientopenai.up.sql new file mode 100644 index 000000000..a591cabc0 --- /dev/null +++ b/db/migrations/000019_clientopenai.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE client ADD COLUMN openaiopts json NOT NULL DEFAULT '{}'; + diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index debea3027..3de56de1b 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -53,6 +53,8 @@ const MaxCommandLen = 4096 const MaxSignalLen = 12 const MaxSignalNum = 64 const MaxEvalDepth = 5 +const MaxOpenAIAPITokenLen = 100 +const MaxOpenAIModelLen = 100 const ( KwArgRenderer = "renderer" @@ -200,7 +202,6 @@ func init() { registerCmdFn("bookmark:delete", BookmarkDeleteCommand) registerCmdFn("openai", OpenAICommand) - registerCmdFn("openai:stream", OpenAICommand) registerCmdFn("_killserver", KillServerCommand) @@ -613,7 +614,7 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Screens: []*sstore.ScreenType{screen}, } return update, nil @@ -759,7 +760,7 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if !setNonAnchor { return nil, nil } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Screens: []*sstore.ScreenType{screen}, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), @@ -798,7 +799,7 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } mshell := ids.Remote.MShell go mshell.RunInstall() - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: ids.Remote.RemotePtr.RemoteId, }, @@ -812,7 +813,7 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke } mshell := ids.Remote.MShell go mshell.CancelInstall() - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: ids.Remote.RemotePtr.RemoteId, }, @@ -825,7 +826,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, err } go ids.Remote.MShell.Launch(true) - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: ids.Remote.RemotePtr.RemoteId, }, @@ -839,7 +840,7 @@ func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy } force := resolveBool(pk.Kwargs["force"], false) go ids.Remote.MShell.Disconnect(force) - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: ids.Remote.RemotePtr.RemoteId, }, @@ -853,7 +854,7 @@ func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { if err != nil { redit.ErrorStr = err.Error() } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ RemoteEdit: redit, }, @@ -880,7 +881,7 @@ func makeRemoteEditUpdate_edit(ids resolvedIds, err error) sstore.UpdatePacket { if err != nil { redit.ErrorStr = err.Error() } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ RemoteEdit: redit, }, @@ -1081,7 +1082,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)) } // SUCCESS - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: r.RemoteId, }, @@ -1110,13 +1111,13 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new error updating remote: %v", err)) } if visualEdit { - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: ids.Remote.RemoteCopy.RemoteId, }, }, nil } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName), TimeoutMs: 2000, @@ -1131,7 +1132,7 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, err } state := ids.Remote.RState - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ PtyRemoteId: state.RemoteId, }, @@ -1150,7 +1151,7 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } buf.WriteString(fmt.Sprintf("%-12s %-5s %8s %s\n", rstate.Status, rstate.RemoteType, rstate.RemoteId[0:8], name)) } - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ RemoteView: &sstore.RemoteViewType{ RemoteShowAll: true, }, @@ -1176,7 +1177,7 @@ func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr) buf.WriteString(outStr) } - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("all screens for session"), InfoLines: splitLinesForInfo(buf.String()), @@ -1289,7 +1290,7 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re } buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr)) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoLines: splitLinesForInfo(buf.String()), }, @@ -1452,10 +1453,19 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, fmt.Errorf("/%s error: %w", GetCmdStr(pk), err) } - opts := &sstore.OpenAIOptsType{ - Model: "gpt-3.5-turbo", - APIToken: OpenAIKey, - MaxTokens: 1000, + clientData, err := sstore.EnsureClientData(ctx) + if err != nil { + return nil, fmt.Errorf("cannot retrieve client data: %v", err) + } + if clientData.OpenAIOpts == nil || clientData.OpenAIOpts.APIToken == "" { + return nil, fmt.Errorf("no openai API token found, configure in client settings") + } + opts := clientData.OpenAIOpts + if opts.Model == "" { + opts.Model = openai.DefaultModel + } + if opts.MaxTokens == 0 { + opts.MaxTokens = openai.DefaultMaxTokens } promptStr := firstArg(pk) if promptStr == "" { @@ -1476,7 +1486,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor return nil, fmt.Errorf("cannot add new line: %v", err) } prompt := []sstore.OpenAIPromptMessageType{{Role: sstore.OpenAIRoleUser, Content: promptStr}} - if pk.MetaSubCmd == "stream" { + if resolveBool(pk.Kwargs["stream"], true) { go doOpenAIStreamCompletion(cmd, opts, prompt) } else { go doOpenAICompletion(cmd, opts, prompt) @@ -1490,7 +1500,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor // ignore error again (nothing to do) log.Printf("/openai error updating screen selected line: %v\n", err) } - update := sstore.ModelUpdate{Line: line, Cmd: cmd, Screens: []*sstore.ScreenType{screen}} + update := &sstore.ModelUpdate{Line: line, Cmd: cmd, Screens: []*sstore.ScreenType{screen}} return update, nil } @@ -1656,7 +1666,7 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd if len(comps) == 0 { comps = []string{"(no completions)"} } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("%s completions", compType), InfoComps: comps, @@ -1786,7 +1796,7 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if newSP == nil || cmdSP == *newSP { return nil, nil } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ CmdLine: &sstore.CmdLineType{CmdLine: newSP.Str, CursorPos: newSP.Pos}, } return update, nil @@ -1814,7 +1824,7 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto // ignore error again (nothing to do) log.Printf("/comment error updating screen selected line: %v\n", err) } - update := sstore.ModelUpdate{Line: rtnLine, Screens: []*sstore.ScreenType{screen}} + update := &sstore.ModelUpdate{Line: rtnLine, Screens: []*sstore.ScreenType{screen}} return update, nil } @@ -2008,7 +2018,7 @@ func ScreenWebShareCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("cannot get updated screen: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Screens: []*sstore.ScreenType{screen}, Info: &sstore.InfoMsgType{ InfoMsg: infoMsg, @@ -2129,7 +2139,7 @@ func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( buf.WriteString(fmt.Sprintf(" %-15s %d\n", "cmds", stats.NumCmds)) buf.WriteString(fmt.Sprintf(" %-15s %0.2fM\n", "disksize", float64(stats.DiskStats.TotalSize)/1000000)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "disk-location", stats.DiskStats.Location)) - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: "session info", InfoLines: splitLinesForInfo(buf.String()), @@ -2155,7 +2165,7 @@ func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType outStr := fmt.Sprintf("%-30s %s %s\n", session.Name+archivedStr, session.SessionId, sessionIdxStr) buf.WriteString(outStr) } - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: "all sessions", InfoLines: splitLinesForInfo(buf.String()), @@ -2188,7 +2198,7 @@ func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) } bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId) - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Sessions: []*sstore.SessionType{bareSession}, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)), @@ -2215,7 +2225,7 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ ActiveSessionId: ritem.Id, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), @@ -2303,7 +2313,7 @@ func HistoryPurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/history:purge error purging items: %v", err) } - update := sstore.ModelUpdate{} + update := &sstore.ModelUpdate{} for _, historyItem := range historyItemsRemoved { if historyItem.LineId == "" { continue @@ -2405,7 +2415,7 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType } hvdata.Lines = lines hvdata.Cmds = cmds - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ HistoryViewData: hvdata, MainView: sstore.MainViewHistory, } @@ -2456,7 +2466,7 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto log.Printf("error updating current activity (history): %v\n", err) } } - update := sstore.ModelUpdate{} + update := &sstore.ModelUpdate{} update.History = &sstore.HistoryInfoType{ HistoryType: htype, SessionId: ids.SessionId, @@ -2601,7 +2611,7 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Line: updatedLine, Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)), @@ -2672,7 +2682,7 @@ func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { log.Printf("error updating current activity (bookmarks): %v\n", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ MainView: sstore.MainViewBookmarks, Bookmarks: bms, } @@ -2709,7 +2719,7 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, fmt.Errorf("error retrieving edited bookmark: %v", err) } - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: "bookmark edited", }, @@ -2734,7 +2744,7 @@ func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType return nil, fmt.Errorf("error deleting bookmark: %v", err) } bm := &sstore.BookmarkType{BookmarkId: bookmarkId, Remove: true} - return sstore.ModelUpdate{ + return &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: "bookmark deleted", }, @@ -2788,7 +2798,7 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) newBmId = newBm.BookmarkId } bms, err := sstore.GetBookmarks(ctx, "") - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ MainView: sstore.MainViewBookmarks, Bookmarks: bms, SelectedBookmark: newBmId, @@ -2838,7 +2848,7 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - return sstore.ModelUpdate{Line: lineObj}, nil + return &sstore.ModelUpdate{Line: lineObj}, nil } func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -2873,7 +2883,7 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - return sstore.ModelUpdate{Line: lineObj}, nil + return &sstore.ModelUpdate{Line: lineObj}, nil } func LinePurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -2899,7 +2909,7 @@ func LinePurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if err != nil { return nil, fmt.Errorf("/line:purge error purging lines: %v", err) } - update := sstore.ModelUpdate{} + update := &sstore.ModelUpdate{} for _, lineId := range lineIds { lineObj := &sstore.LineType{ ScreenId: ids.ScreenId, @@ -2975,7 +2985,7 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst buf.WriteString(fmt.Sprintf(" %-15s %s\n", "file-data", fileDataStr)) } } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("line %d info", line.LineNum), InfoLines: splitLinesForInfo(buf.String()), @@ -3069,7 +3079,7 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, fmt.Errorf("cannot send signal: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("sent line %s signal %s", lineArg, sigArg), }, @@ -3093,7 +3103,7 @@ func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { pcloud.ResetUpdateWriterNumFailures() sstore.NotifyUpdateWriter() - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("notified update writer"), }, @@ -3123,12 +3133,42 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ ClientData: clientData, } return update, nil } +func validateOpenAIAPIToken(key string) error { + if len(key) == 0 { + return fmt.Errorf("invalid openai token, zero length") + } + if len(key) > MaxOpenAIAPITokenLen { + return fmt.Errorf("invalid openai token, too long") + } + for idx, ch := range key { + if !unicode.IsPrint(ch) { + return fmt.Errorf("invalid openai token, char at idx:%d is invalid %q", idx, string(ch)) + } + } + return nil +} + +func validateOpenAIModel(model string) error { + if len(model) == 0 { + return nil + } + if len(model) > MaxOpenAIModelLen { + return fmt.Errorf("invalid openai model, too long") + } + for idx, ch := range model { + if !unicode.IsPrint(ch) { + return fmt.Errorf("invalid openai model, char at idx:%d is invalid %q", idx, string(ch)) + } + } + return nil +} + func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { @@ -3151,14 +3191,88 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } varsUpdated = append(varsUpdated, "termfontsize") } + if apiToken, found := pk.Kwargs["openaiapitoken"]; found { + err = validateOpenAIAPIToken(apiToken) + if err != nil { + return nil, err + } + varsUpdated = append(varsUpdated, "openaiapitoken") + aiOpts := clientData.OpenAIOpts + if aiOpts == nil { + aiOpts = &sstore.OpenAIOptsType{} + clientData.OpenAIOpts = aiOpts + } + aiOpts.APIToken = apiToken + err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) + if err != nil { + return nil, fmt.Errorf("error updating client openai api token: %v", err) + } + } + if aiModel, found := pk.Kwargs["openaimodel"]; found { + err = validateOpenAIModel(aiModel) + if err != nil { + return nil, err + } + varsUpdated = append(varsUpdated, "openaimodel") + aiOpts := clientData.OpenAIOpts + if aiOpts == nil { + aiOpts = &sstore.OpenAIOptsType{} + clientData.OpenAIOpts = aiOpts + } + aiOpts.Model = aiModel + err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) + if err != nil { + return nil, fmt.Errorf("error updating client openai model: %v", err) + } + } + if maxTokensStr, found := pk.Kwargs["openaimaxtokens"]; found { + maxTokens, err := strconv.Atoi(maxTokensStr) + if err != nil { + return nil, fmt.Errorf("error updating client openai maxtokens, invalid number: %v", err) + } + if maxTokens < 0 || maxTokens > 1000000 { + return nil, fmt.Errorf("error updating client openai maxtokens, out of range: %d", maxTokens) + } + varsUpdated = append(varsUpdated, "openaimaxtokens") + aiOpts := clientData.OpenAIOpts + if aiOpts == nil { + aiOpts = &sstore.OpenAIOptsType{} + clientData.OpenAIOpts = aiOpts + } + aiOpts.MaxTokens = maxTokens + err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) + if err != nil { + return nil, fmt.Errorf("error updating client openai maxtokens: %v", err) + } + } + if maxChoicesStr, found := pk.Kwargs["openaimaxchoices"]; found { + maxChoices, err := strconv.Atoi(maxChoicesStr) + if err != nil { + return nil, fmt.Errorf("error updating client openai maxchoices, invalid number: %v", err) + } + if maxChoices < 0 || maxChoices > 10 { + return nil, fmt.Errorf("error updating client openai maxchoices, out of range: %d", maxChoices) + } + varsUpdated = append(varsUpdated, "openaimaxchoices") + aiOpts := clientData.OpenAIOpts + if aiOpts == nil { + aiOpts = &sstore.OpenAIOptsType{} + clientData.OpenAIOpts = aiOpts + } + aiOpts.MaxChoices = maxChoices + err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) + if err != nil { + return nil, fmt.Errorf("error updating client openai maxchoices: %v", err) + } + } if len(varsUpdated) == 0 { - return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize"}, "or", false)) + return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "openaiapitoken", "openaimodel", "openaimaxtokens", "openaimaxchoices"}, "or", false)) } clientData, err = sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)), TimeoutMs: 2000, @@ -3189,7 +3303,7 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s buf.WriteString(fmt.Sprintf(" %-15s %s\n", "client-version", clientVersion)) buf.WriteString(fmt.Sprintf(" %-15s %s %s\n", "server-version", scbase.PromptVersion, scbase.BuildTime)) buf.WriteString(fmt.Sprintf(" %-15s %s (%s)\n", "arch", scbase.ClientArch(), scbase.MacOSRelease())) - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("client info"), InfoLines: splitLinesForInfo(buf.String()), @@ -3273,7 +3387,7 @@ func TelemetryShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } var buf bytes.Buffer buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on"))) - update := sstore.ModelUpdate{ + update := &sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("telemetry info"), InfoLines: splitLinesForInfo(buf.String()), diff --git a/pkg/remote/openai/openai.go b/pkg/remote/openai/openai.go index 04eb05d79..ed04582fb 100644 --- a/pkg/remote/openai/openai.go +++ b/pkg/remote/openai/openai.go @@ -13,6 +13,8 @@ import ( // https://github.com/tiktoken-go/tokenizer +const DefaultMaxTokens = 1000 +const DefaultModel = "gpt-3.5-turbo" const DefaultStreamChanSize = 10 func convertUsage(resp openaiapi.ChatCompletionResponse) *packet.OpenAIUsageType { @@ -141,7 +143,7 @@ func marshalResponse(resp openaiapi.ChatCompletionResponse) []*packet.OpenAIPack func CreateErrorPacket(errStr string) *packet.OpenAIPacketType { errPk := packet.MakeOpenAIPacket() - errPk.Text = errStr - errPk.FinishReason = "stop" + errPk.FinishReason = "error" + errPk.Error = errStr return errPk } diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index b14465580..76060f334 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -622,6 +622,9 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { vars["bestname"] = vars["bestuser"] + "@" + vars["besthost"] vars["bestshortname"] = vars["bestuser"] + "@" + vars["bestshorthost"] } + if vars["remoteuser"] == "root" || vars["sudo"] == "1" { + vars["isroot"] = "1" + } state.RemoteVars = vars return state } @@ -1627,7 +1630,7 @@ func (msh *MShellProc) notifyHangups_nolock() { if err != nil { continue } - update := sstore.ModelUpdate{Cmd: cmd} + update := &sstore.ModelUpdate{Cmd: cmd} sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update) } msh.RunningCmds = make(map[base.CommandKey]RunCmdType) diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index a5eb11814..5e28d4376 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -624,6 +624,15 @@ func UpdateClientFeOpts(ctx context.Context, feOpts FeOptsType) error { return txErr } +func UpdateClientOpenAIOpts(ctx context.Context, aiOpts OpenAIOptsType) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `UPDATE client SET openaiopts = ?` + tx.Exec(query, quickJson(aiOpts)) + return nil + }) + return txErr +} + func containsStr(strs []string, testStr string) bool { for _, s := range strs { if s == testStr { @@ -1076,7 +1085,7 @@ func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (Upda if err != nil { return nil, fmt.Errorf("cannot retrive archived screen: %w", err) } - update := ModelUpdate{Screens: []*ScreenType{newScreen}} + update := &ModelUpdate{Screens: []*ScreenType{newScreen}} if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { @@ -1151,7 +1160,7 @@ func PurgeScreen(ctx context.Context, screenId string, sessionDel bool) (UpdateP if sessionDel { return nil, nil } - update := ModelUpdate{} + update := &ModelUpdate{} update.Screens = []*ScreenType{&ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}} if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) @@ -1521,7 +1530,7 @@ func PurgeSession(ctx context.Context, sessionId string) (UpdatePacket, error) { if txErr != nil { return nil, txErr } - update := ModelUpdate{} + update := &ModelUpdate{} if newActiveSessionId != "" { update.ActiveSessionId = newActiveSessionId } diff --git a/pkg/sstore/migrate.go b/pkg/sstore/migrate.go index f1a7b665c..3d9fc3cb3 100644 --- a/pkg/sstore/migrate.go +++ b/pkg/sstore/migrate.go @@ -17,7 +17,7 @@ import ( "github.com/golang-migrate/migrate/v4" ) -const MaxMigration = 18 +const MaxMigration = 19 const MigratePrimaryScreenVersion = 9 func MakeMigrate() (*migrate.Migrate, error) { diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 89a8bf6a6..cd07ddb3a 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -38,6 +38,7 @@ const DefaultSessionName = "default" const LocalRemoteAlias = "local" const DefaultCwd = "~" +const APITokenSentinel = "--apitoken--" const ( LineTypeCmd = "cmd" @@ -259,10 +260,30 @@ type ClientData struct { CmdStoreType string `json:"cmdstoretype"` Migration *ClientMigrationData `json:"migration,omitempty" dbmap:"-"` DBVersion int `json:"dbversion" dbmap:"-"` + OpenAIOpts *OpenAIOptsType `json:"openaiopts,omitempty" dbmap:"openaiopts"` } func (ClientData) UseDBMap() {} +func (cdata *ClientData) Clean() *ClientData { + if cdata == nil { + return nil + } + rtn := *cdata + if rtn.OpenAIOpts != nil { + rtn.OpenAIOpts = &OpenAIOptsType{ + Model: cdata.OpenAIOpts.Model, + MaxTokens: cdata.OpenAIOpts.MaxTokens, + MaxChoices: cdata.OpenAIOpts.MaxChoices, + // omit API Token + } + if cdata.OpenAIOpts.APIToken != "" { + rtn.OpenAIOpts.APIToken = APITokenSentinel + } + } + return &rtn +} + type SessionType struct { SessionId string `json:"sessionid"` Name string `json:"name"` diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index ae505aab6..e53822248 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -14,6 +14,7 @@ const UpdateChSize = 100 type UpdatePacket interface { UpdateType() string + Clean() } type PtyDataUpdate struct { @@ -25,10 +26,12 @@ type PtyDataUpdate struct { PtyDataLen int64 `json:"ptydatalen"` } -func (PtyDataUpdate) UpdateType() string { +func (*PtyDataUpdate) UpdateType() string { return PtyDataUpdateStr } +func (pdu *PtyDataUpdate) Clean() {} + type ModelUpdate struct { Sessions []*SessionType `json:"sessions,omitempty"` ActiveSessionId string `json:"activesessionid,omitempty"` @@ -52,10 +55,17 @@ type ModelUpdate struct { RemoteView *RemoteViewType `json:"remoteview,omitempty"` } -func (ModelUpdate) UpdateType() string { +func (*ModelUpdate) UpdateType() string { return ModelUpdateStr } +func (update *ModelUpdate) Clean() { + if update == nil { + return + } + update.ClientData = update.ClientData.Clean() +} + type RemoteViewType struct { RemoteShowAll bool `json:"remoteshowall,omitempty"` PtyRemoteId string `json:"ptyremoteid,omitempty"` @@ -63,7 +73,7 @@ type RemoteViewType struct { } func ReadHistoryDataFromUpdate(update UpdatePacket) (string, string, *RemotePtrType) { - modelUpdate, ok := update.(ModelUpdate) + modelUpdate, ok := update.(*ModelUpdate) if !ok { return "", "", nil } @@ -183,7 +193,11 @@ func (bus *UpdateBus) UnregisterChannel(clientId string) { } } -func (bus *UpdateBus) SendUpdate(update interface{}) { +func (bus *UpdateBus) SendUpdate(update UpdatePacket) { + if update == nil { + return + } + update.Clean() bus.Lock.Lock() defer bus.Lock.Unlock() for _, uch := range bus.Channels { @@ -196,7 +210,11 @@ func (bus *UpdateBus) SendUpdate(update interface{}) { } } -func (bus *UpdateBus) SendScreenUpdate(screenId string, update interface{}) { +func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) { + if update == nil { + return + } + update.Clean() bus.Lock.Lock() defer bus.Lock.Unlock() for _, uch := range bus.Channels {