From d97ba477fc43a62872999ae0ae2259dc6b370b9e Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Tue, 25 Nov 2025 09:11:54 -0800 Subject: [PATCH 01/35] add an "add" button when there are no secrets, also add `wsh secret ui` cli command (#2598) --- cmd/wsh/cmd/wshcmd-secret.go | 35 +++++++++++++ docs/docs/wsh-reference.mdx | 51 +++++++++++++++++-- frontend/app/view/secretstore/secretstore.tsx | 13 +++-- package-lock.json | 4 +- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-secret.go b/cmd/wsh/cmd/wshcmd-secret.go index bce1d0beb9..cfd8daf407 100644 --- a/cmd/wsh/cmd/wshcmd-secret.go +++ b/cmd/wsh/cmd/wshcmd-secret.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) @@ -16,6 +17,8 @@ import ( // secretNameRegex must match the validation in pkg/wconfig/secretstore.go var secretNameRegex = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`) +var secretUiMagnified bool + var secretCmd = &cobra.Command{ Use: "secret", Short: "manage secrets", @@ -54,12 +57,22 @@ var secretDeleteCmd = &cobra.Command{ PreRunE: preRunSetupRpcClient, } +var secretUiCmd = &cobra.Command{ + Use: "ui", + Short: "open secrets UI", + Args: cobra.NoArgs, + RunE: secretUiRun, + PreRunE: preRunSetupRpcClient, +} + func init() { + secretUiCmd.Flags().BoolVarP(&secretUiMagnified, "magnified", "m", false, "open secrets UI in magnified mode") rootCmd.AddCommand(secretCmd) secretCmd.AddCommand(secretGetCmd) secretCmd.AddCommand(secretSetCmd) secretCmd.AddCommand(secretListCmd) secretCmd.AddCommand(secretDeleteCmd) + secretCmd.AddCommand(secretUiCmd) } func secretGetRun(cmd *cobra.Command, args []string) (rtnErr error) { @@ -156,4 +169,26 @@ func secretDeleteRun(cmd *cobra.Command, args []string) (rtnErr error) { WriteStdout("secret deleted: %s\n", name) return nil +} + +func secretUiRun(cmd *cobra.Command, args []string) (rtnErr error) { + defer func() { + sendActivity("secret", rtnErr == nil) + }() + + wshCmd := &wshrpc.CommandCreateBlockData{ + BlockDef: &waveobj.BlockDef{ + Meta: map[string]interface{}{ + waveobj.MetaKey_View: "secretstore", + }, + }, + Magnified: secretUiMagnified, + Focused: true, + } + + _, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) + if err != nil { + return fmt.Errorf("opening secrets UI: %w", err) + } + return nil } \ No newline at end of file diff --git a/docs/docs/wsh-reference.mdx b/docs/docs/wsh-reference.mdx index a6006010d0..b9df30f31d 100644 --- a/docs/docs/wsh-reference.mdx +++ b/docs/docs/wsh-reference.mdx @@ -342,19 +342,36 @@ This will connect to a WSL distribution on the local machine. It will use the de ## web -You can search for a given url using: +The `web` command opens URLs in a web block within Wave Terminal. ```sh -wsh web open [url] +wsh web open [url] [-m] [-r blockid] ``` -Alternatively, you can search with the configured search engine using: +You can open a specific URL or perform a search using the configured search engine. + +Flags: + +- `-m, --magnified` - open the web block in magnified mode +- `-r, --replace ` - replace an existing block instead of creating a new one + +Examples: ```sh -wsh web open [search-query] +# Open a URL +wsh web open https://waveterm.dev + +# Search with the configured search engine +wsh web open "wave terminal documentation" + +# Open in magnified mode +wsh web open -m https://github.com + +# Replace an existing block +wsh web open -r 2 https://example.com ``` -Both of these commands will open a new web block with the desired page. +The command will open a new web block with the desired page, or replace an existing block if the `-r` flag is used. Note that `--replace` and `--magnified` cannot be used together. --- @@ -978,6 +995,30 @@ wsh secret delete old_api_key wsh secret delete temp_token ``` +### ui + +```sh +wsh secret ui [-m] +``` + +Open the secrets management interface in a new block. This provides a graphical interface for viewing and managing all your secrets. + +Flags: + +- `-m, --magnified` - open the secrets UI in magnified mode + +Examples: + +```sh +# Open the secrets UI +wsh secret ui + +# Open the secrets UI in magnified mode +wsh secret ui -m +``` + +The secrets UI provides a convenient visual way to browse, add, edit, and delete secrets without needing to use the command-line interface. + :::tip Use secrets in your scripts to avoid hardcoding sensitive values. Secrets work across remote machines - store an API key locally with `wsh secret set`, then access it from any SSH or WSL connection with `wsh secret get`. The secret is securely retrieved from your local machine without needing to duplicate it on remote systems. ::: diff --git a/frontend/app/view/secretstore/secretstore.tsx b/frontend/app/view/secretstore/secretstore.tsx index d695522ea3..83e32a14fe 100644 --- a/frontend/app/view/secretstore/secretstore.tsx +++ b/frontend/app/view/secretstore/secretstore.tsx @@ -38,12 +38,19 @@ const LoadingSpinner = memo(({ message }: { message: string }) => { }); LoadingSpinner.displayName = "LoadingSpinner"; -const EmptyState = memo(() => { +const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { return ( -
+

No Secrets

Add a secret to get started

+
); }); @@ -352,7 +359,7 @@ export const SecretStoreView = memo(({ model }: { blockId: string; model: Secret } if (secretNames.length === 0) { - return ; + return model.startAddingSecret()} />; } return ( diff --git a/package-lock.json b/package-lock.json index b770d276d0..30e4dcf00a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.12.4", + "version": "0.12.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.12.4", + "version": "0.12.5", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ From 0be5537cf7febbceb088a6d56fd8f140c222839c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:32:01 -0800 Subject: [PATCH 02/35] Bump github.com/launchdarkly/eventsource from 1.10.0 to 1.11.0 (#2586) Bumps [github.com/launchdarkly/eventsource](https://github.com/launchdarkly/eventsource) from 1.10.0 to 1.11.0.
Release notes

Sourced from github.com/launchdarkly/eventsource's releases.

v1.11.0

1.11.0 (2025-11-20)

Features

  • Support SSE server with event limited jitter support (#58) (7b0f448)

Bug Fixes

  • Bump gopkg.in/yaml.v3 from 3.0.0 to 3.0.1 (#55) (7c0d519)
Changelog

Sourced from github.com/launchdarkly/eventsource's changelog.

1.11.0 (2025-11-20)

Features

  • Support SSE server with event limited jitter support (#58) (7b0f448)

Bug Fixes

  • Bump gopkg.in/yaml.v3 from 3.0.0 to 3.0.1 (#55) (7c0d519)
Commits
  • d27e310 chore(main): release 1.11.0 (#56)
  • 7b0f448 feat: Support SSE server with event limited jitter support (#58)
  • 71784de chore: Update go-unit-report from tag to SHA (#57)
  • 7c0d519 fix: Bump gopkg.in/yaml.v3 from 3.0.0 to 3.0.1 (#55)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/launchdarkly/eventsource&package-manager=go_modules&previous-version=1.10.0&new-version=1.11.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 03b75b5ce5..a4e1eaa96f 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/junegunn/fzf v0.65.2 github.com/kevinburke/ssh_config v1.2.0 - github.com/launchdarkly/eventsource v1.10.0 + github.com/launchdarkly/eventsource v1.11.0 github.com/mattn/go-sqlite3 v1.14.32 github.com/mitchellh/mapstructure v1.5.0 github.com/sashabaranov/go-openai v1.41.2 @@ -80,7 +80,6 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index fe81351f68..d8ec4db031 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/launchdarkly/eventsource v1.10.0 h1:H9Tp6AfGu/G2qzBJC26iperrvwhzdbiA/gx7qE2nDFI= -github.com/launchdarkly/eventsource v1.10.0/go.mod h1:J3oa50bPvJesZqNAJtb5btSIo5N6roDWhiAS3IpsKck= +github.com/launchdarkly/eventsource v1.11.0 h1:aAdvh2XmtXA17QsRFL0XKHURMqhxg7J+CceQmhSzBas= +github.com/launchdarkly/eventsource v1.11.0/go.mod h1:dU+rZxkPOlGPsyJPpiDqiepAcFwIITDUClY9+A6RrMw= github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg= github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -144,8 +144,6 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= From ff8fd7ba63342be31a58c73826ca8671d17fa108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:32:36 -0800 Subject: [PATCH 03/35] Bump github.com/aws/aws-sdk-go-v2/config from 1.31.17 to 1.32.0 (#2585) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.31.17 to 1.32.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/aws/aws-sdk-go-v2/config&package-manager=go_modules&previous-version=1.31.17&new-version=1.32.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 21 +++++++++++---------- go.sum | 42 ++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index a4e1eaa96f..2c54fb7767 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.24.6 require ( github.com/alexflint/go-filemutex v1.3.0 - github.com/aws/aws-sdk-go-v2 v1.39.6 - github.com/aws/aws-sdk-go-v2/config v1.31.17 + github.com/aws/aws-sdk-go-v2 v1.40.0 + github.com/aws/aws-sdk-go-v2/config v1.32.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 github.com/aws/smithy-go v1.23.2 github.com/creack/pty v1.1.24 @@ -51,19 +51,20 @@ require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/longrunning v0.5.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/ebitengine/purego v0.9.0 // indirect diff --git a/go.sum b/go.sum index d8ec4db031..3fd0787047 100644 --- a/go.sum +++ b/go.sum @@ -16,20 +16,20 @@ github.com/0xrawsec/golang-utils v1.3.2 h1:ww4jrtHRSnX9xrGzJYbalx5nXoZewy4zPxiY+ github.com/0xrawsec/golang-utils v1.3.2/go.mod h1:m7AzHXgdSAkFCD9tWWsApxNVxMlyy7anpPVOyT/yM7E= github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= -github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= -github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= +github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= +github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M= +github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA= +github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw= +github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M= +github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA= @@ -38,18 +38,20 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/A github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8= github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 h1:ef6gIJR+xv/JQWwpa5FYirzoQctfSJm7tuDe3SZsUf8= github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= From 17682e8872ee9c65b4a055b19b3b04dbd9e6cf74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:50:43 -0800 Subject: [PATCH 04/35] Bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.90.0 to 1.92.0 (#2583) Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.90.0 to 1.92.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/aws/aws-sdk-go-v2/service/s3&package-manager=go_modules&previous-version=1.90.0&new-version=1.92.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2c54fb7767..d6339d50a1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexflint/go-filemutex v1.3.0 github.com/aws/aws-sdk-go-v2 v1.40.0 github.com/aws/aws-sdk-go-v2/config v1.32.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 github.com/aws/smithy-go v1.23.2 github.com/creack/pty v1.1.24 github.com/emirpasic/gods v1.18.1 @@ -56,11 +56,11 @@ require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect diff --git a/go.sum b/go.sum index 3fd0787047..fbc5bc2d2f 100644 --- a/go.sum +++ b/go.sum @@ -32,18 +32,18 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 h1:ITi7qiDSv/mSGDSWNpZ4k4Ve0DQR6Ug2SJQ8zEHoDXg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14/go.mod h1:k1xtME53H1b6YpZt74YmwlONMWf4ecM+lut1WQLAF/U= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 h1:Hjkh7kE6D81PgrHlE/m9gx+4TyyeLHuY8xJs7yXN5C4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5/go.mod h1:nPRXgyCfAurhyaTMoBMwRBYBhaHI4lNPAnJmjM0Tslc= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 h1:ef6gIJR+xv/JQWwpa5FYirzoQctfSJm7tuDe3SZsUf8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= From 9f4914e9089adaff134659a69f7966fb788439d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:52:03 -0800 Subject: [PATCH 05/35] Bump actions/checkout from 5 to 6 in /.github/workflows (#2582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
Release notes

Sourced from actions/checkout's releases.

v6.0.0

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v5.0.0...v6.0.0

v6-beta

What's Changed

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

v5.0.1

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v5...v5.0.1

Changelog

Sourced from actions/checkout's changelog.

Changelog

V6.0.0

V5.0.1

V5.0.0

V4.3.1

V4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

v4.1.5

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-helper.yml | 2 +- .github/workflows/bump-version.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/deploy-docsite.yml | 2 +- .github/workflows/publish-release.yml | 8 ++++---- .github/workflows/testdriver-build.yml | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml index 2f5aad3568..9e29d9d7f3 100644 --- a/.github/workflows/build-helper.yml +++ b/.github/workflows/build-helper.yml @@ -31,7 +31,7 @@ jobs: # runner: "windows-11-arm64-16core" runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Linux Build Dependencies (Linux only) if: matrix.platform == 'linux' run: | diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 25d9b7149d..1fb15b9bf5 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -34,7 +34,7 @@ jobs: with: app-id: ${{ vars.WAVE_BUILDER_APPID }} private-key: ${{ secrets.WAVE_BUILDER_KEY }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 60930e9c0f..2f159bbc2f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Task uses: arduino/setup-task@v2 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 2a093ff357..14a102c81e 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -18,7 +18,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Go + Node versions match your helper - uses: actions/setup-go@v6 diff --git a/.github/workflows/deploy-docsite.yml b/.github/workflows/deploy-docsite.yml index 68857f62f1..377a8b6c14 100644 --- a/.github/workflows/deploy-docsite.yml +++ b/.github/workflows/deploy-docsite.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-node@v6 diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 784a2fbec4..821102d5c1 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -11,7 +11,7 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/') }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Task uses: arduino/setup-task@v2 with: @@ -30,7 +30,7 @@ jobs: needs: [publish-s3] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Task uses: arduino/setup-task@v2 with: @@ -55,7 +55,7 @@ jobs: needs: [publish-s3] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Task uses: arduino/setup-task@v2 with: @@ -80,7 +80,7 @@ jobs: needs: [publish-s3] runs-on: windows-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Task uses: arduino/setup-task@v2 with: diff --git a/.github/workflows/testdriver-build.yml b/.github/workflows/testdriver-build.yml index 46cb473bf3..89b68a365b 100644 --- a/.github/workflows/testdriver-build.yml +++ b/.github/workflows/testdriver-build.yml @@ -41,7 +41,7 @@ jobs: runs-on: windows-latest if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # General build dependencies - uses: actions/setup-go@v6 From 6211b214e33464b21a2bdfb18fde7998a5eeb97f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:54:23 -0800 Subject: [PATCH 06/35] Bump the prod-dependencies-patch group across 1 directory with 2 updates (#2548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the prod-dependencies-patch group with 1 update in the / directory: [@ai-sdk/react](https://github.com/vercel/ai). Updates `@ai-sdk/react` from 2.0.76 to 2.0.92
Release notes

Sourced from @​ai-sdk/react's releases.

@​ai-sdk/react@​2.0.92

Patch Changes

  • 250edbd: Added finishReason on useChat onFinish callbck
  • Updated dependencies [250edbd]
    • ai@5.0.92

@​ai-sdk/react@​2.0.91

Patch Changes

  • Updated dependencies [056c471]
    • @​ai-sdk/provider-utils@​3.0.17
    • ai@5.0.91

@​ai-sdk/react@​2.0.90

Patch Changes

  • Updated dependencies [818b144]
    • ai@5.0.90
Commits

Updates `ai` from 5.0.76 to 5.0.92
Release notes

Sourced from ai's releases.

ai@5.0.92

Patch Changes

  • 250edbd: Added finishReason on useChat onFinish callbck

ai@5.0.91

Patch Changes

  • Updated dependencies [056c471]
    • @​ai-sdk/provider-utils@​3.0.17
    • @​ai-sdk/gateway@​2.0.8

ai@5.0.90

Patch Changes

  • 818b144: fix not catching of empty arrays in validateUIMessage
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30e4dcf00a..da74067ccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "tsunami/frontend" ], "dependencies": { - "@ai-sdk/react": "^2.0.92", + "@ai-sdk/react": "^2.0.101", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", @@ -253,14 +253,14 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.11.tgz", - "integrity": "sha512-B0Vt2Xv88Lo9rg861Oyzq/SdTmT4LiqdjkpOxpSPpNk8Ih5TXTgyDAsV/qW14N6asPdK1YI5PosFLnVzfK5LrA==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.15.tgz", + "integrity": "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", - "@vercel/oidc": "3.0.3" + "@vercel/oidc": "3.0.5" }, "engines": { "node": ">=18" @@ -299,13 +299,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "2.0.96", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.96.tgz", - "integrity": "sha512-9Jbch0zzbOMIInfsc58DpffE16b3nxDqBvW3ZA5xPpMAAt1sKSyIBngb3tvl8YOz009y8xztCcRE3/qwDytkRw==", + "version": "2.0.101", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.101.tgz", + "integrity": "sha512-Cq9InVVGBs2Dw3FiqImsuGZK86HZnNp562+jygJfUtPpp5JUOwWBblCQcli7X8aDg9QsitdM0ZJpbVZQ+fwH6w==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider-utils": "3.0.17", - "ai": "5.0.96", + "ai": "5.0.101", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -10692,9 +10692,9 @@ "license": "ISC" }, "node_modules/@vercel/oidc": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz", - "integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz", + "integrity": "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==", "license": "Apache-2.0", "engines": { "node": ">= 20" @@ -11276,12 +11276,12 @@ } }, "node_modules/ai": { - "version": "5.0.96", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.96.tgz", - "integrity": "sha512-eSf9V0fKpPglCGb7gib7HoWmgpUbdlm6FAgfvY/lMNfIxKx8WWAfX0RenVqTODVq3OISKcxSs2Gsh+XzgYNm5g==", + "version": "5.0.101", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.101.tgz", + "integrity": "sha512-/P4fgs2PGYTBaZi192YkPikOudsl9vccA65F7J7LvoNTOoP5kh1yAsJPsKAy6FXU32bAngai7ft1UDyC3u7z5g==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "2.0.11", + "@ai-sdk/gateway": "2.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" diff --git a/package.json b/package.json index 2e13649700..81fbeebf6b 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "vitest": "^3.0.9" }, "dependencies": { - "@ai-sdk/react": "^2.0.92", + "@ai-sdk/react": "^2.0.101", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", From 73e56193e70588a6c887d8a2a342930f30b108f5 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 26 Nov 2025 11:43:19 -0800 Subject: [PATCH 07/35] implement openai chat completions api -- enables local model support (#2600) --- cmd/testai/main-testai.go | 140 ++++++- frontend/app/aipanel/aimode.tsx | 116 ++++++ frontend/app/aipanel/aipanel-contextmenu.ts | 20 +- frontend/app/aipanel/aipanel.tsx | 6 +- frontend/app/aipanel/aipanelmessages.tsx | 8 +- frontend/app/aipanel/aitypes.ts | 4 +- frontend/app/aipanel/thinkingmode.tsx | 131 ------- frontend/app/aipanel/waveai-model.tsx | 18 +- frontend/types/gotypes.d.ts | 24 +- go.mod | 1 + go.sum | 2 + pkg/aiusechat/aiutil/aiutil.go | 91 +++++ pkg/aiusechat/anthropic/anthropic-backend.go | 2 +- .../anthropic/anthropic-convertmessage.go | 6 +- pkg/aiusechat/openai/openai-backend.go | 115 +----- pkg/aiusechat/openai/openai-convertmessage.go | 6 +- .../openaichat/openaichat-backend.go | 257 +++++++++++++ .../openaichat/openaichat-convertmessage.go | 346 ++++++++++++++++++ pkg/aiusechat/openaichat/openaichat-types.go | 171 +++++++++ pkg/aiusechat/tools_readdir.go | 16 +- pkg/aiusechat/tools_readfile.go | 10 +- pkg/aiusechat/tools_screenshot.go | 1 + pkg/aiusechat/tools_writefile.go | 34 +- .../uctypes/{usechat-types.go => uctypes.go} | 99 +++-- pkg/aiusechat/usechat-backend.go | 51 ++- pkg/aiusechat/usechat-mode.go | 43 +++ pkg/aiusechat/usechat-prompts.go | 61 +++ pkg/aiusechat/usechat.go | 232 +++++------- pkg/telemetry/telemetrydata/telemetrydata.go | 2 +- pkg/waveobj/objrtinfo.go | 2 +- pkg/wconfig/defaultconfig/waveai.json | 41 +++ pkg/wconfig/settingsconfig.go | 20 + pkg/wshrpc/wshserver/wshserver.go | 2 - 33 files changed, 1632 insertions(+), 446 deletions(-) create mode 100644 frontend/app/aipanel/aimode.tsx delete mode 100644 frontend/app/aipanel/thinkingmode.tsx create mode 100644 pkg/aiusechat/openaichat/openaichat-backend.go create mode 100644 pkg/aiusechat/openaichat/openaichat-convertmessage.go create mode 100644 pkg/aiusechat/openaichat/openaichat-types.go rename pkg/aiusechat/uctypes/{usechat-types.go => uctypes.go} (84%) create mode 100644 pkg/aiusechat/usechat-mode.go create mode 100644 pkg/aiusechat/usechat-prompts.go create mode 100644 pkg/wconfig/defaultconfig/waveai.json diff --git a/cmd/testai/main-testai.go b/cmd/testai/main-testai.go index eace7ca61a..8e8fcdb3eb 100644 --- a/cmd/testai/main-testai.go +++ b/cmd/testai/main-testai.go @@ -24,8 +24,9 @@ import ( var testSchemaJSON string const ( - DefaultAnthropicModel = "claude-sonnet-4-5" - DefaultOpenAIModel = "gpt-5.1" + DefaultAnthropicModel = "claude-sonnet-4-5" + DefaultOpenAIModel = "gpt-5.1" + DefaultOpenRouterModel = "mistralai/mistral-small-3.2-24b-instruct" ) // TestResponseWriter implements http.ResponseWriter and additional interfaces for testing @@ -113,7 +114,7 @@ func testOpenAI(ctx context.Context, model, message string, tools []uctypes.Tool } opts := &uctypes.AIOptsType{ - APIType: aiusechat.APIType_OpenAI, + APIType: uctypes.APIType_OpenAIResponses, APIToken: apiKey, Model: model, MaxTokens: 4096, @@ -155,6 +156,106 @@ func testOpenAI(ctx context.Context, model, message string, tools []uctypes.Tool } } +func testOpenAIComp(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) { + apiKey := os.Getenv("OPENAI_APIKEY") + if apiKey == "" { + fmt.Println("Error: OPENAI_APIKEY environment variable not set") + os.Exit(1) + } + + opts := &uctypes.AIOptsType{ + APIType: uctypes.APIType_OpenAIChat, + APIToken: apiKey, + BaseURL: "https://api.openai.com/v1/chat/completions", + Model: model, + MaxTokens: 4096, + ThinkingLevel: uctypes.ThinkingLevelMedium, + } + + chatID := uuid.New().String() + + aiMessage := &uctypes.AIMessage{ + MessageId: uuid.New().String(), + Parts: []uctypes.AIMessagePart{ + { + Type: uctypes.AIMessagePartTypeText, + Text: message, + }, + }, + } + + fmt.Printf("Testing OpenAI Completions API with WaveAIPostMessageWrap, model: %s\n", model) + fmt.Printf("Message: %s\n", message) + fmt.Printf("Chat ID: %s\n", chatID) + fmt.Println("---") + + testWriter := &TestResponseWriter{} + sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx) + defer sseHandler.Close() + + chatOpts := uctypes.WaveChatOpts{ + ChatId: chatID, + ClientId: uuid.New().String(), + Config: *opts, + Tools: tools, + SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."}, + } + err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts) + if err != nil { + fmt.Printf("OpenAI Completions API streaming error: %v\n", err) + } +} + +func testOpenRouter(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) { + apiKey := os.Getenv("OPENROUTER_APIKEY") + if apiKey == "" { + fmt.Println("Error: OPENROUTER_APIKEY environment variable not set") + os.Exit(1) + } + + opts := &uctypes.AIOptsType{ + APIType: uctypes.APIType_OpenAIChat, + APIToken: apiKey, + BaseURL: "https://openrouter.ai/api/v1/chat/completions", + Model: model, + MaxTokens: 4096, + ThinkingLevel: uctypes.ThinkingLevelMedium, + } + + chatID := uuid.New().String() + + aiMessage := &uctypes.AIMessage{ + MessageId: uuid.New().String(), + Parts: []uctypes.AIMessagePart{ + { + Type: uctypes.AIMessagePartTypeText, + Text: message, + }, + }, + } + + fmt.Printf("Testing OpenRouter with WaveAIPostMessageWrap, model: %s\n", model) + fmt.Printf("Message: %s\n", message) + fmt.Printf("Chat ID: %s\n", chatID) + fmt.Println("---") + + testWriter := &TestResponseWriter{} + sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx) + defer sseHandler.Close() + + chatOpts := uctypes.WaveChatOpts{ + ChatId: chatID, + ClientId: uuid.New().String(), + Config: *opts, + Tools: tools, + SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."}, + } + err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts) + if err != nil { + fmt.Printf("OpenRouter streaming error: %v\n", err) + } +} + func testAnthropic(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) { apiKey := os.Getenv("ANTHROPIC_APIKEY") if apiKey == "" { @@ -163,7 +264,7 @@ func testAnthropic(ctx context.Context, model, message string, tools []uctypes.T } opts := &uctypes.AIOptsType{ - APIType: aiusechat.APIType_Anthropic, + APIType: uctypes.APIType_AnthropicMessages, APIToken: apiKey, Model: model, MaxTokens: 4096, @@ -217,33 +318,46 @@ func testT2(ctx context.Context) { testOpenAI(ctx, DefaultOpenAIModel, "what is 2+2+8, use the provider adder tool", tools) } +func testT3(ctx context.Context) { + testOpenAIComp(ctx, "gpt-4o", "what is 2+2? please be brief", nil) +} + func printUsage() { - fmt.Println("Usage: go run main-testai.go [--anthropic] [--tools] [--model ] [message]") + fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter] [--tools] [--model ] [message]") fmt.Println("Examples:") fmt.Println(" go run main-testai.go 'What is 2+2?'") fmt.Println(" go run main-testai.go --model o4-mini 'What is 2+2?'") fmt.Println(" go run main-testai.go --anthropic 'What is 2+2?'") fmt.Println(" go run main-testai.go --anthropic --model claude-3-5-sonnet-20241022 'What is 2+2?'") + fmt.Println(" go run main-testai.go --openaicomp --model gpt-4o 'What is 2+2?'") + fmt.Println(" go run main-testai.go --openrouter 'What is 2+2?'") + fmt.Println(" go run main-testai.go --openrouter --model anthropic/claude-3.5-sonnet 'What is 2+2?'") fmt.Println(" go run main-testai.go --tools 'Help me configure GitHub Actions monitoring'") fmt.Println("") fmt.Println("Default models:") fmt.Printf(" OpenAI: %s\n", DefaultOpenAIModel) fmt.Printf(" Anthropic: %s\n", DefaultAnthropicModel) + fmt.Printf(" OpenAI Completions: gpt-4o\n") + fmt.Printf(" OpenRouter: %s\n", DefaultOpenRouterModel) fmt.Println("") fmt.Println("Environment variables:") fmt.Println(" OPENAI_APIKEY (for OpenAI models)") fmt.Println(" ANTHROPIC_APIKEY (for Anthropic models)") + fmt.Println(" OPENROUTER_APIKEY (for OpenRouter models)") } func main() { - var anthropic, tools, help, t1, t2 bool + var anthropic, openaicomp, openrouter, tools, help, t1, t2, t3 bool var model string flag.BoolVar(&anthropic, "anthropic", false, "Use Anthropic API instead of OpenAI") + flag.BoolVar(&openaicomp, "openaicomp", false, "Use OpenAI Completions API") + flag.BoolVar(&openrouter, "openrouter", false, "Use OpenRouter API") flag.BoolVar(&tools, "tools", false, "Enable GitHub Actions Monitor tools for testing") - flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic)", DefaultOpenAIModel, DefaultAnthropicModel)) + flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic, %s for OpenRouter)", DefaultOpenAIModel, DefaultAnthropicModel, DefaultOpenRouterModel)) flag.BoolVar(&help, "help", false, "Show usage information") flag.BoolVar(&t1, "t1", false, fmt.Sprintf("Run preset T1 test (%s with 'what is 2+2')", DefaultAnthropicModel)) flag.BoolVar(&t2, "t2", false, fmt.Sprintf("Run preset T2 test (%s with 'what is 2+2')", DefaultOpenAIModel)) + flag.BoolVar(&t3, "t3", false, "Run preset T3 test (OpenAI Completions API with gpt-4o)") flag.Parse() if help { @@ -262,11 +376,19 @@ func main() { testT2(ctx) return } + if t3 { + testT3(ctx) + return + } // Set default model based on API type if not provided if model == "" { if anthropic { model = DefaultAnthropicModel + } else if openaicomp { + model = "gpt-4o" + } else if openrouter { + model = DefaultOpenRouterModel } else { model = DefaultOpenAIModel } @@ -285,6 +407,10 @@ func main() { if anthropic { testAnthropic(ctx, model, message, toolDefs) + } else if openaicomp { + testOpenAIComp(ctx, model, message, toolDefs) + } else if openrouter { + testOpenRouter(ctx, model, message, toolDefs) } else { testOpenAI(ctx, model, message, toolDefs) } diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx new file mode 100644 index 0000000000..d5ec9d3063 --- /dev/null +++ b/frontend/app/aipanel/aimode.tsx @@ -0,0 +1,116 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { atoms } from "@/app/store/global"; +import { cn, makeIconClass } from "@/util/util"; +import { useAtomValue } from "jotai"; +import { memo, useRef, useState } from "react"; +import { WaveAIModel } from "./waveai-model"; + +export const AIModeDropdown = memo(() => { + const model = WaveAIModel.getInstance(); + const aiMode = useAtomValue(model.currentAIMode); + const aiModeConfigs = useAtomValue(model.aiModeConfigs); + const rateLimitInfo = useAtomValue(atoms.waveAIRateLimitInfoAtom); + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; + const hideQuick = model.inBuilder && hasPremium; + + const sortedConfigs = Object.entries(aiModeConfigs) + .map(([mode, config]) => ({ mode, ...config })) + .sort((a, b) => { + const orderDiff = (a["display:order"] || 0) - (b["display:order"] || 0); + if (orderDiff !== 0) return orderDiff; + return (a["display:name"] || "").localeCompare(b["display:name"] || ""); + }) + .filter((config) => !(hideQuick && config.mode === "waveai@quick")); + + const handleSelect = (mode: string) => { + const config = aiModeConfigs[mode]; + if (!config) return; + if (!hasPremium && config["waveai:premium"]) { + return; + } + model.setAIMode(mode); + setIsOpen(false); + }; + + let currentMode = aiMode || "waveai@balanced"; + const currentConfig = aiModeConfigs[currentMode]; + if (currentConfig) { + if (!hasPremium && currentConfig["waveai:premium"]) { + currentMode = "waveai@quick"; + } + if (hideQuick && currentMode === "waveai@quick") { + currentMode = "waveai@balanced"; + } + } + + const displayConfig = aiModeConfigs[currentMode] || { + "display:name": "? Unknown", + "display:icon": "question", + }; + + return ( +
+ + + {isOpen && ( + <> +
setIsOpen(false)} /> +
+ {sortedConfigs.map((config, index) => { + const isFirst = index === 0; + const isLast = index === sortedConfigs.length - 1; + const isDisabled = !hasPremium && config["waveai:premium"]; + const isSelected = currentMode === config.mode; + return ( + + ); + })} +
+ + )} +
+ ); +}); + +AIModeDropdown.displayName = "AIModeDropdown"; diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index b7a7f718d4..05060b5e64 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -41,45 +41,45 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo const rateLimitInfo = globalStore.get(atoms.waveAIRateLimitInfoAtom); const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; - const currentThinkingMode = rtInfo?.["waveai:thinkingmode"] ?? (hasPremium ? "balanced" : "quick"); + const currentAIMode = rtInfo?.["waveai:mode"] ?? (hasPremium ? "waveai@balanced" : "waveai@quick"); const defaultTokens = model.inBuilder ? 24576 : 4096; const currentMaxTokens = rtInfo?.["waveai:maxoutputtokens"] ?? defaultTokens; - const thinkingModeSubmenu: ContextMenuItem[] = [ + const aiModeSubmenu: ContextMenuItem[] = [ { label: "Quick (gpt-5-mini)", type: "checkbox", - checked: currentThinkingMode === "quick", + checked: currentAIMode === "waveai@quick", click: () => { RpcApi.SetRTInfoCommand(TabRpcClient, { oref: model.orefContext, - data: { "waveai:thinkingmode": "quick" }, + data: { "waveai:mode": "waveai@quick" }, }); }, }, { label: hasPremium ? "Balanced (gpt-5.1, low thinking)" : "Balanced (premium)", type: "checkbox", - checked: currentThinkingMode === "balanced", + checked: currentAIMode === "waveai@balanced", enabled: hasPremium, click: () => { if (!hasPremium) return; RpcApi.SetRTInfoCommand(TabRpcClient, { oref: model.orefContext, - data: { "waveai:thinkingmode": "balanced" }, + data: { "waveai:mode": "waveai@balanced" }, }); }, }, { label: hasPremium ? "Deep (gpt-5.1, full thinking)" : "Deep (premium)", type: "checkbox", - checked: currentThinkingMode === "deep", + checked: currentAIMode === "waveai@deep", enabled: hasPremium, click: () => { if (!hasPremium) return; RpcApi.SetRTInfoCommand(TabRpcClient, { oref: model.orefContext, - data: { "waveai:thinkingmode": "deep" }, + data: { "waveai:mode": "waveai@deep" }, }); }, }, @@ -164,8 +164,8 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo } menu.push({ - label: "Thinking Mode", - submenu: thinkingModeSubmenu, + label: "AI Mode", + submenu: aiModeSubmenu, }); menu.push({ diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 79ae04fcc1..062fc2f559 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -16,12 +16,12 @@ import { memo, useCallback, useEffect, useRef, useState } from "react"; import { useDrop } from "react-dnd"; import { formatFileSizeError, isAcceptableFile, validateFileSize } from "./ai-utils"; import { AIDroppedFiles } from "./aidroppedfiles"; +import { AIModeDropdown } from "./aimode"; import { AIPanelHeader } from "./aipanelheader"; import { AIPanelInput } from "./aipanelinput"; import { AIPanelMessages } from "./aipanelmessages"; import { AIRateLimitStrip } from "./airatelimitstrip"; import { TelemetryRequiredMessage } from "./telemetryrequired"; -import { ThinkingLevelDropdown } from "./thinkingmode"; import { WaveAIModel } from "./waveai-model"; const AIBlockMask = memo(() => { @@ -246,6 +246,8 @@ const AIPanelComponentInner = memo(() => { model.registerUseChatData(sendMessage, setMessages, status, stop); // console.log("AICHAT messages", messages); + (window as any).aichatmessages = messages; + (window as any).aichatstatus = status; const handleKeyDown = (waveEvent: WaveKeyboardEvent): boolean => { if (checkKeyPressed(waveEvent, "Cmd:k")) { @@ -498,7 +500,7 @@ const AIPanelComponentInner = memo(() => { onContextMenu={(e) => handleWaveAIContextMenu(e, true)} >
- +
{model.inBuilder ? : }
diff --git a/frontend/app/aipanel/aipanelmessages.tsx b/frontend/app/aipanel/aipanelmessages.tsx index a32e3936b4..3d3ae0d912 100644 --- a/frontend/app/aipanel/aipanelmessages.tsx +++ b/frontend/app/aipanel/aipanelmessages.tsx @@ -4,7 +4,7 @@ import { useAtomValue } from "jotai"; import { memo, useEffect, useRef } from "react"; import { AIMessage } from "./aimessage"; -import { ThinkingLevelDropdown } from "./thinkingmode"; +import { AIModeDropdown } from "./aimode"; import { WaveAIModel } from "./waveai-model"; interface AIPanelMessagesProps { @@ -45,13 +45,13 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane useEffect(() => { const wasStreaming = prevStatusRef.current === "streaming"; const isNowNotStreaming = status !== "streaming"; - + if (wasStreaming && isNowNotStreaming) { requestAnimationFrame(() => { scrollToBottom(); }); } - + prevStatusRef.current = status; }, [status]); @@ -62,7 +62,7 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane onContextMenu={onContextMenu} >
- +
{messages.map((message, index) => { const isLastMessage = index === messages.length - 1; diff --git a/frontend/app/aipanel/aitypes.ts b/frontend/app/aipanel/aitypes.ts index a1192ec7ed..cc3c73d224 100644 --- a/frontend/app/aipanel/aitypes.ts +++ b/frontend/app/aipanel/aitypes.ts @@ -4,14 +4,14 @@ import { ChatRequestOptions, FileUIPart, UIMessage, UIMessagePart } from "ai"; type WaveUIDataTypes = { - // pkg/aiusechat/uctypes/usechat-types.go UIMessageDataUserFile + // pkg/aiusechat/uctypes/uctypes.go UIMessageDataUserFile userfile: { filename: string; size: number; mimetype: string; previewurl?: string; }; - // pkg/aiusechat/uctypes/usechat-types.go UIMessageDataToolUse + // pkg/aiusechat/uctypes/uctypes.go UIMessageDataToolUse tooluse: { toolcallid: string; toolname: string; diff --git a/frontend/app/aipanel/thinkingmode.tsx b/frontend/app/aipanel/thinkingmode.tsx deleted file mode 100644 index 1e0fb76be7..0000000000 --- a/frontend/app/aipanel/thinkingmode.tsx +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2025, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { atoms } from "@/app/store/global"; -import { cn } from "@/util/util"; -import { useAtomValue } from "jotai"; -import { memo, useRef, useState } from "react"; -import { WaveAIModel } from "./waveai-model"; - -type ThinkingMode = "quick" | "balanced" | "deep"; - -interface ThinkingModeMetadata { - icon: string; - name: string; - desc: string; - premium: boolean; -} - -const ThinkingModeData: Record = { - quick: { - icon: "fa-bolt", - name: "Quick", - desc: "Fastest responses (gpt-5-mini)", - premium: false, - }, - balanced: { - icon: "fa-sparkles", - name: "Balanced", - desc: "Good mix of speed and accuracy\n(gpt-5.1 with minimal thinking)", - premium: true, - }, - deep: { - icon: "fa-lightbulb", - name: "Deep", - desc: "Slower but most capable\n(gpt-5.1 with full reasoning)", - premium: true, - }, -}; - -export const ThinkingLevelDropdown = memo(() => { - const model = WaveAIModel.getInstance(); - const thinkingMode = useAtomValue(model.thinkingMode); - const rateLimitInfo = useAtomValue(atoms.waveAIRateLimitInfoAtom); - const [isOpen, setIsOpen] = useState(false); - const dropdownRef = useRef(null); - - const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; - const hideQuick = model.inBuilder && hasPremium; - - const handleSelect = (mode: ThinkingMode) => { - const metadata = ThinkingModeData[mode]; - if (!hasPremium && metadata.premium) { - return; - } - model.setThinkingMode(mode); - setIsOpen(false); - }; - - let currentMode = (thinkingMode as ThinkingMode) || "balanced"; - const currentMetadata = ThinkingModeData[currentMode]; - if (!hasPremium && currentMetadata.premium) { - currentMode = "quick"; - } - if (hideQuick && currentMode === "quick") { - currentMode = "balanced"; - } - - return ( -
- - - {isOpen && ( - <> -
setIsOpen(false)} /> -
- {(Object.keys(ThinkingModeData) as ThinkingMode[]) - .filter((mode) => !(hideQuick && mode === "quick")) - .map((mode, index, filteredModes) => { - const metadata = ThinkingModeData[mode]; - const isFirst = index === 0; - const isLast = index === filteredModes.length - 1; - const isDisabled = !hasPremium && metadata.premium; - const isSelected = currentMode === mode; - return ( - - ); - })} -
- - )} -
- ); -}); - -ThinkingLevelDropdown.displayName = "ThinkingLevelDropdown"; diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index 7af0914e88..34e11ec5ce 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -57,7 +57,8 @@ export class WaveAIModel { widgetAccessAtom!: jotai.Atom; droppedFiles: jotai.PrimitiveAtom = jotai.atom([]); chatId!: jotai.PrimitiveAtom; - thinkingMode: jotai.PrimitiveAtom = jotai.atom("balanced"); + currentAIMode: jotai.PrimitiveAtom = jotai.atom("waveai@balanced"); + aiModeConfigs!: jotai.Atom>; errorMessage: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom; modelAtom!: jotai.Atom; containerWidth: jotai.PrimitiveAtom = jotai.atom(0); @@ -82,6 +83,11 @@ export class WaveAIModel { const modelMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:model"); return get(modelMetaAtom) ?? "gpt-5.1"; }); + this.aiModeConfigs = jotai.atom((get) => { + const fullConfig = get(atoms.fullConfigAtom); + return fullConfig?.waveai ?? {}; + }); + this.widgetAccessAtom = jotai.atom((get) => { if (this.inBuilder) { @@ -337,11 +343,11 @@ export class WaveAIModel { }); } - setThinkingMode(mode: string) { - globalStore.set(this.thinkingMode, mode); + setAIMode(mode: string) { + globalStore.set(this.currentAIMode, mode); RpcApi.SetRTInfoCommand(TabRpcClient, { oref: this.orefContext, - data: { "waveai:thinkingmode": mode }, + data: { "waveai:mode": mode }, }); } @@ -359,8 +365,8 @@ export class WaveAIModel { } globalStore.set(this.chatId, chatIdValue); - const thinkingModeValue = rtInfo?.["waveai:thinkingmode"] ?? "balanced"; - globalStore.set(this.thinkingMode, thinkingModeValue); + const aiModeValue = rtInfo?.["waveai:mode"] ?? "waveai@balanced"; + globalStore.set(this.currentAIMode, aiModeValue); try { const chatData = await RpcApi.GetWaveAIChatCommand(TabRpcClient, { chatid: chatIdValue }); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 8b80fe62af..d00e629b4a 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -13,6 +13,25 @@ declare global { data64: string; }; + // wconfig.AIModeConfigType + type AIModeConfigType = { + "display:name": string; + "display:order"?: number; + "display:icon": string; + "display:shortdesc"?: string; + "display:description": string; + "ai:apitype": string; + "ai:model": string; + "ai:thinkinglevel": string; + "ai:baseurl"?: string; + "ai:apiversion"?: string; + "ai:apitoken"?: string; + "ai:apitokensecretname"?: string; + "ai:capabilities"?: string[]; + "waveai:cloud"?: boolean; + "waveai:premium": boolean; + }; + // wshrpc.ActivityDisplayType type ActivityDisplayType = { width: number; @@ -750,6 +769,7 @@ declare global { termthemes: {[key: string]: TermThemeType}; connections: {[key: string]: ConnKeywords}; bookmarks: {[key: string]: WebBookmark}; + waveai: {[key: string]: AIModeConfigType}; configerrors: ConfigError[]; }; @@ -930,7 +950,7 @@ declare global { "builder:appid"?: string; "builder:env"?: {[key: string]: string}; "waveai:chatid"?: string; - "waveai:thinkingmode"?: string; + "waveai:mode"?: string; "waveai:maxoutputtokens"?: number; }; @@ -1240,7 +1260,7 @@ declare global { "waveai:requestdurms"?: number; "waveai:widgetaccess"?: boolean; "waveai:thinkinglevel"?: string; - "waveai:thinkingmode"?: string; + "waveai:mode"?: string; "waveai:feedback"?: "good" | "bad"; "waveai:action"?: string; $set?: TEventUserProps; diff --git a/go.mod b/go.mod index d6339d50a1..b165226881 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index fbc5bc2d2f..e44a38bfdd 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/pkg/aiusechat/aiutil/aiutil.go b/pkg/aiusechat/aiutil/aiutil.go index fb9f8bb517..0fd4854469 100644 --- a/pkg/aiusechat/aiutil/aiutil.go +++ b/pkg/aiusechat/aiutil/aiutil.go @@ -5,6 +5,7 @@ package aiutil import ( "bytes" + "context" "crypto/sha256" "encoding/base64" "encoding/hex" @@ -12,9 +13,12 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/util/utilfn" + "github.com/wavetermdev/waveterm/pkg/wcore" + "github.com/wavetermdev/waveterm/pkg/web/sse" ) // ExtractXmlAttribute extracts an attribute value from an XML-like tag. @@ -180,3 +184,90 @@ func JsonEncodeRequestBody(reqBody any) (bytes.Buffer, error) { } return buf, nil } + +func IsOpenAIReasoningModel(model string) bool { + m := strings.ToLower(model) + return strings.HasPrefix(m, "o1") || + strings.HasPrefix(m, "o3") || + strings.HasPrefix(m, "o4") || + strings.HasPrefix(m, "gpt-5") || + strings.HasPrefix(m, "gpt-5.1") +} + +// CreateToolUseData creates a UIMessageDataToolUse from tool call information +func CreateToolUseData(toolCallID, toolName string, arguments string, chatOpts uctypes.WaveChatOpts) uctypes.UIMessageDataToolUse { + toolUseData := uctypes.UIMessageDataToolUse{ + ToolCallId: toolCallID, + ToolName: toolName, + Status: uctypes.ToolUseStatusPending, + } + + toolDef := chatOpts.GetToolDefinition(toolName) + if toolDef == nil { + toolUseData.Status = uctypes.ToolUseStatusError + toolUseData.ErrorMessage = "tool not found" + return toolUseData + } + + var parsedArgs any + if err := json.Unmarshal([]byte(arguments), &parsedArgs); err != nil { + toolUseData.Status = uctypes.ToolUseStatusError + toolUseData.ErrorMessage = fmt.Sprintf("failed to parse tool arguments: %v", err) + return toolUseData + } + + if toolDef.ToolCallDesc != nil { + toolUseData.ToolDesc = toolDef.ToolCallDesc(parsedArgs, nil, nil) + } + + if toolDef.ToolApproval != nil { + toolUseData.Approval = toolDef.ToolApproval(parsedArgs) + } + + if chatOpts.TabId != "" { + if argsMap, ok := parsedArgs.(map[string]any); ok { + if widgetId, ok := argsMap["widget_id"].(string); ok && widgetId != "" { + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, chatOpts.TabId, widgetId) + if err == nil { + toolUseData.BlockId = fullBlockId + } + } + } + } + + return toolUseData +} + + +// SendToolProgress sends tool progress updates via SSE if the tool has a progress descriptor +func SendToolProgress(toolCallID, toolName string, jsonData []byte, chatOpts uctypes.WaveChatOpts, sseHandler *sse.SSEHandlerCh, usePartialParse bool) { + toolDef := chatOpts.GetToolDefinition(toolName) + if toolDef == nil || toolDef.ToolProgressDesc == nil { + return + } + + var parsedJSON any + var err error + if usePartialParse { + parsedJSON, err = utilfn.ParsePartialJson(jsonData) + } else { + err = json.Unmarshal(jsonData, &parsedJSON) + } + if err != nil { + return + } + + statusLines, err := toolDef.ToolProgressDesc(parsedJSON) + if err != nil { + return + } + + progressData := &uctypes.UIMessageDataToolProgress{ + ToolCallId: toolCallID, + ToolName: toolName, + StatusLines: statusLines, + } + _ = sseHandler.AiMsgData("data-toolprogress", "progress-"+toolCallID, progressData) +} diff --git a/pkg/aiusechat/anthropic/anthropic-backend.go b/pkg/aiusechat/anthropic/anthropic-backend.go index 345d30bcdd..987b8c117e 100644 --- a/pkg/aiusechat/anthropic/anthropic-backend.go +++ b/pkg/aiusechat/anthropic/anthropic-backend.go @@ -56,7 +56,7 @@ func (m *anthropicChatMessage) GetUsage() *uctypes.AIUsage { } return &uctypes.AIUsage{ - APIType: "anthropic", + APIType: uctypes.APIType_AnthropicMessages, Model: m.Usage.Model, InputTokens: m.Usage.InputTokens, OutputTokens: m.Usage.OutputTokens, diff --git a/pkg/aiusechat/anthropic/anthropic-convertmessage.go b/pkg/aiusechat/anthropic/anthropic-convertmessage.go index 0daf9f99b9..e8a64f3246 100644 --- a/pkg/aiusechat/anthropic/anthropic-convertmessage.go +++ b/pkg/aiusechat/anthropic/anthropic-convertmessage.go @@ -171,7 +171,7 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage req.Header.Set("anthropic-version", apiVersion) req.Header.Set("accept", "text/event-stream") req.Header.Set("X-Wave-ClientId", chatOpts.ClientId) - req.Header.Set("X-Wave-APIType", "anthropic") + req.Header.Set("X-Wave-APIType", uctypes.APIType_AnthropicMessages) return req, nil } @@ -795,8 +795,8 @@ func ConvertToolResultsToAnthropicChatMessage(toolResults []uctypes.AIToolResult // ConvertAIChatToUIChat converts an AIChat to a UIChat for Anthropic func ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { - if aiChat.APIType != "anthropic" { - return nil, fmt.Errorf("APIType must be 'anthropic', got '%s'", aiChat.APIType) + if aiChat.APIType != uctypes.APIType_AnthropicMessages { + return nil, fmt.Errorf("APIType must be '%s', got '%s'", uctypes.APIType_AnthropicMessages, aiChat.APIType) } uiMessages := make([]uctypes.UIMessage, 0, len(aiChat.NativeMessages)) diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go index cced0dd06d..eb3ac08ee2 100644 --- a/pkg/aiusechat/openai/openai-backend.go +++ b/pkg/aiusechat/openai/openai-backend.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "net/url" "strings" @@ -17,11 +16,11 @@ import ( "github.com/google/uuid" "github.com/launchdarkly/eventsource" + "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil" "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/util/logutil" "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/wcore" "github.com/wavetermdev/waveterm/pkg/web/sse" ) @@ -150,7 +149,7 @@ func (m *OpenAIChatMessage) GetUsage() *uctypes.AIUsage { return nil } return &uctypes.AIUsage{ - APIType: "openai", + APIType: uctypes.APIType_OpenAIResponses, Model: m.Usage.Model, InputTokens: m.Usage.InputTokens, OutputTokens: m.Usage.OutputTokens, @@ -396,8 +395,7 @@ type openaiBlockState struct { } type openaiStreamingState struct { - blockMap map[string]*openaiBlockState // Use item_id as key for UI streaming - toolUseData map[string]*uctypes.UIMessageDataToolUse // Use toolCallId as key + blockMap map[string]*openaiBlockState // Use item_id as key for UI streaming msgID string model string stepStarted bool @@ -407,7 +405,7 @@ type openaiStreamingState struct { // ---------- Public entrypoint ---------- -func UpdateToolUseData(chatId string, callId string, newToolUseData *uctypes.UIMessageDataToolUse) error { +func UpdateToolUseData(chatId string, callId string, newToolUseData uctypes.UIMessageDataToolUse) error { chat := chatstore.DefaultChatStore.Get(chatId) if chat == nil { return fmt.Errorf("chat not found: %s", chatId) @@ -422,7 +420,7 @@ func UpdateToolUseData(chatId string, callId string, newToolUseData *uctypes.UIM if chatMsg.FunctionCall != nil && chatMsg.FunctionCall.CallId == callId { updatedMsg := *chatMsg updatedFunctionCall := *chatMsg.FunctionCall - updatedFunctionCall.ToolUseData = newToolUseData + updatedFunctionCall.ToolUseData = &newToolUseData updatedMsg.FunctionCall = &updatedFunctionCall aiOpts := &uctypes.AIOptsType{ @@ -592,9 +590,8 @@ func parseOpenAIHTTPError(resp *http.Response) error { func handleOpenAIStreamingResp(ctx context.Context, sse *sse.SSEHandlerCh, decoder *eventsource.Decoder, cont *uctypes.WaveContinueResponse, chatOpts uctypes.WaveChatOpts) (*uctypes.WaveStopReason, []*OpenAIChatMessage) { // Per-response state state := &openaiStreamingState{ - blockMap: map[string]*openaiBlockState{}, - toolUseData: map[string]*uctypes.UIMessageDataToolUse{}, - chatOpts: chatOpts, + blockMap: map[string]*openaiBlockState{}, + chatOpts: chatOpts, } var rtnStopReason *uctypes.WaveStopReason @@ -862,8 +859,7 @@ func handleOpenAIEvent( } if st := state.blockMap[ev.ItemId]; st != nil && st.kind == openaiBlockToolUse { st.partialJSON = append(st.partialJSON, []byte(ev.Delta)...) - toolDef := state.chatOpts.GetToolDefinition(st.toolName) - sendToolProgress(st, toolDef, sse, st.partialJSON, true) + aiutil.SendToolProgress(st.toolCallID, st.toolName, st.partialJSON, state.chatOpts, sse, true) } return nil, nil @@ -876,10 +872,7 @@ func handleOpenAIEvent( // Get the function call info from the block state if st := state.blockMap[ev.ItemId]; st != nil && st.kind == openaiBlockToolUse { - toolDef := state.chatOpts.GetToolDefinition(st.toolName) - toolUseData := createToolUseData(st.toolCallID, st.toolName, toolDef, ev.Arguments, state.chatOpts) - state.toolUseData[st.toolCallID] = toolUseData - sendToolProgress(st, toolDef, sse, []byte(ev.Arguments), false) + aiutil.SendToolProgress(st.toolCallID, st.toolName, []byte(ev.Arguments), state.chatOpts, sse, false) } return nil, nil @@ -936,76 +929,6 @@ func handleOpenAIEvent( } } -func sendToolProgress(st *openaiBlockState, toolDef *uctypes.ToolDefinition, sse *sse.SSEHandlerCh, jsonData []byte, usePartialParse bool) { - if toolDef == nil || toolDef.ToolProgressDesc == nil { - return - } - var parsedJSON any - var err error - if usePartialParse { - parsedJSON, err = utilfn.ParsePartialJson(jsonData) - } else { - err = json.Unmarshal(jsonData, &parsedJSON) - } - if err != nil { - return - } - statusLines, err := toolDef.ToolProgressDesc(parsedJSON) - if err != nil { - return - } - progressData := &uctypes.UIMessageDataToolProgress{ - ToolCallId: st.toolCallID, - ToolName: st.toolName, - StatusLines: statusLines, - } - _ = sse.AiMsgData("data-toolprogress", "progress-"+st.toolCallID, progressData) -} - -func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinition, arguments string, chatOpts uctypes.WaveChatOpts) *uctypes.UIMessageDataToolUse { - toolUseData := &uctypes.UIMessageDataToolUse{ - ToolCallId: toolCallID, - ToolName: toolName, - Status: uctypes.ToolUseStatusPending, - } - - if toolDef == nil { - toolUseData.Status = uctypes.ToolUseStatusError - toolUseData.ErrorMessage = "tool not found" - return toolUseData - } - - var parsedArgs any - if err := json.Unmarshal([]byte(arguments), &parsedArgs); err != nil { - toolUseData.Status = uctypes.ToolUseStatusError - toolUseData.ErrorMessage = fmt.Sprintf("failed to parse tool arguments: %v", err) - return toolUseData - } - - if toolDef.ToolCallDesc != nil { - toolUseData.ToolDesc = toolDef.ToolCallDesc(parsedArgs, nil, nil) - } - - if toolDef.ToolApproval != nil { - toolUseData.Approval = toolDef.ToolApproval(parsedArgs) - } - - if chatOpts.TabId != "" { - if argsMap, ok := parsedArgs.(map[string]any); ok { - if widgetId, ok := argsMap["widget_id"].(string); ok && widgetId != "" { - ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) - defer cancelFn() - fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, chatOpts.TabId, widgetId) - if err == nil { - toolUseData.BlockId = fullBlockId - } - } - } - } - - return toolUseData -} - // extractMessageAndToolsFromResponse extracts the final OpenAI message and tool calls from the completed response func extractMessageAndToolsFromResponse(resp openaiResponse, state *openaiStreamingState) ([]*OpenAIChatMessage, []uctypes.WaveToolCall) { var messageContent []OpenAIMessageContent @@ -1040,13 +963,6 @@ func extractMessageAndToolsFromResponse(resp openaiResponse, state *openaiStream } } - // Attach UIToolUseData if available - if data, ok := state.toolUseData[outputItem.CallId]; ok { - toolCall.ToolUseData = data - } else { - log.Printf("AI no data-tooluse for %s (callid: %s)\n", outputItem.Id, outputItem.CallId) - } - toolCalls = append(toolCalls, toolCall) // Create separate FunctionCall message @@ -1054,18 +970,13 @@ func extractMessageAndToolsFromResponse(resp openaiResponse, state *openaiStream if outputItem.Arguments != "" { argsStr = outputItem.Arguments } - var toolUseDataPtr *uctypes.UIMessageDataToolUse - if data, ok := state.toolUseData[outputItem.CallId]; ok { - toolUseDataPtr = data - } functionCallMsg := &OpenAIChatMessage{ MessageId: uuid.New().String(), FunctionCall: &OpenAIFunctionCallInput{ - Type: "function_call", - CallId: outputItem.CallId, - Name: outputItem.Name, - Arguments: argsStr, - ToolUseData: toolUseDataPtr, + Type: "function_call", + CallId: outputItem.CallId, + Name: outputItem.Name, + Arguments: argsStr, }, } messages = append(messages, functionCallMsg) diff --git a/pkg/aiusechat/openai/openai-convertmessage.go b/pkg/aiusechat/openai/openai-convertmessage.go index 156c635887..70b6f31aa6 100644 --- a/pkg/aiusechat/openai/openai-convertmessage.go +++ b/pkg/aiusechat/openai/openai-convertmessage.go @@ -299,7 +299,7 @@ func buildOpenAIHTTPRequest(ctx context.Context, inputs []any, chatOpts uctypes. req.Header.Set("X-Wave-ChatId", chatOpts.ChatId) } req.Header.Set("X-Wave-Version", wavebase.WaveVersion) - req.Header.Set("X-Wave-APIType", "openai") + req.Header.Set("X-Wave-APIType", uctypes.APIType_OpenAIResponses) req.Header.Set("X-Wave-RequestType", chatOpts.GetWaveRequestType()) return req, nil @@ -519,8 +519,8 @@ func (m *OpenAIChatMessage) convertToUIMessage() *uctypes.UIMessage { // ConvertAIChatToUIChat converts an AIChat to a UIChat for OpenAI func ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { - if aiChat.APIType != "openai" { - return nil, fmt.Errorf("APIType must be 'openai', got '%s'", aiChat.APIType) + if aiChat.APIType != uctypes.APIType_OpenAIResponses { + return nil, fmt.Errorf("APIType must be '%s', got '%s'", uctypes.APIType_OpenAIResponses, aiChat.APIType) } uiMessages := make([]uctypes.UIMessage, 0, len(aiChat.NativeMessages)) for i, nativeMsg := range aiChat.NativeMessages { diff --git a/pkg/aiusechat/openaichat/openaichat-backend.go b/pkg/aiusechat/openaichat/openaichat-backend.go new file mode 100644 index 0000000000..4eb6217421 --- /dev/null +++ b/pkg/aiusechat/openaichat/openaichat-backend.go @@ -0,0 +1,257 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package openaichat + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "github.com/google/uuid" + "github.com/launchdarkly/eventsource" + "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" + "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/web/sse" +) + +// RunChatStep executes a chat step using the chat completions API +func RunChatStep( + ctx context.Context, + sseHandler *sse.SSEHandlerCh, + chatOpts uctypes.WaveChatOpts, + cont *uctypes.WaveContinueResponse, +) (*uctypes.WaveStopReason, []*StoredChatMessage, *uctypes.RateLimitInfo, error) { + if sseHandler == nil { + return nil, nil, nil, errors.New("sse handler is nil") + } + + chat := chatstore.DefaultChatStore.Get(chatOpts.ChatId) + if chat == nil { + return nil, nil, nil, fmt.Errorf("chat not found: %s", chatOpts.ChatId) + } + + if chatOpts.Config.TimeoutMs > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, time.Duration(chatOpts.Config.TimeoutMs)*time.Millisecond) + defer cancel() + } + + // Convert stored messages to chat completions format + var messages []ChatRequestMessage + + // Add system prompt if provided + if len(chatOpts.SystemPrompt) > 0 { + messages = append(messages, ChatRequestMessage{ + Role: "system", + Content: strings.Join(chatOpts.SystemPrompt, "\n"), + }) + } + + // Convert native messages + for _, genMsg := range chat.NativeMessages { + chatMsg, ok := genMsg.(*StoredChatMessage) + if !ok { + return nil, nil, nil, fmt.Errorf("expected StoredChatMessage, got %T", genMsg) + } + messages = append(messages, *chatMsg.Message.clean()) + } + + req, err := buildChatHTTPRequest(ctx, messages, chatOpts) + if err != nil { + return nil, nil, nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, nil, nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, nil, nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes)) + } + + // Setup SSE if this is a new request (not a continuation) + if cont == nil { + if err := sseHandler.SetupSSE(); err != nil { + return nil, nil, nil, fmt.Errorf("failed to setup SSE: %w", err) + } + } + + // Stream processing + stopReason, assistantMsg, err := processChatStream(ctx, resp.Body, sseHandler, chatOpts, cont) + if err != nil { + return nil, nil, nil, err + } + + return stopReason, []*StoredChatMessage{assistantMsg}, nil, nil +} + +func processChatStream( + ctx context.Context, + body io.Reader, + sseHandler *sse.SSEHandlerCh, + chatOpts uctypes.WaveChatOpts, + cont *uctypes.WaveContinueResponse, +) (*uctypes.WaveStopReason, *StoredChatMessage, error) { + decoder := eventsource.NewDecoder(body) + var textBuilder strings.Builder + msgID := uuid.New().String() + textID := uuid.New().String() + var finishReason string + textStarted := false + var toolCallsInProgress []ToolCall + + if cont == nil { + _ = sseHandler.AiMsgStart(msgID) + } + _ = sseHandler.AiMsgStartStep() + + for { + if err := ctx.Err(); err != nil { + _ = sseHandler.AiMsgError("request cancelled") + return &uctypes.WaveStopReason{ + Kind: uctypes.StopKindCanceled, + ErrorType: "cancelled", + ErrorText: "request cancelled", + }, nil, err + } + + event, err := decoder.Decode() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + _ = sseHandler.AiMsgError(err.Error()) + return &uctypes.WaveStopReason{ + Kind: uctypes.StopKindError, + ErrorType: "stream", + ErrorText: err.Error(), + }, nil, fmt.Errorf("stream decode error: %w", err) + } + + data := event.Data() + if data == "[DONE]" { + break + } + + var chunk StreamChunk + if err := json.Unmarshal([]byte(data), &chunk); err != nil { + log.Printf("openaichat: failed to parse chunk: %v\n", err) + continue + } + + if len(chunk.Choices) == 0 { + continue + } + + choice := chunk.Choices[0] + if choice.Delta.Content != "" { + if !textStarted { + _ = sseHandler.AiMsgTextStart(textID) + textStarted = true + } + textBuilder.WriteString(choice.Delta.Content) + _ = sseHandler.AiMsgTextDelta(textID, choice.Delta.Content) + } + + if len(choice.Delta.ToolCalls) > 0 { + for _, tcDelta := range choice.Delta.ToolCalls { + idx := tcDelta.Index + for len(toolCallsInProgress) <= idx { + toolCallsInProgress = append(toolCallsInProgress, ToolCall{}) + } + + tc := &toolCallsInProgress[idx] + if tcDelta.ID != "" { + tc.ID = tcDelta.ID + } + if tcDelta.Type != "" { + tc.Type = tcDelta.Type + } + if tcDelta.Function != nil { + if tcDelta.Function.Name != "" { + tc.Function.Name = tcDelta.Function.Name + } + if tcDelta.Function.Arguments != "" { + tc.Function.Arguments += tcDelta.Function.Arguments + } + } + } + } + + if choice.FinishReason != nil && *choice.FinishReason != "" { + finishReason = *choice.FinishReason + } + } + + stopKind := uctypes.StopKindDone + if finishReason == "length" { + stopKind = uctypes.StopKindMaxTokens + } else if finishReason == "tool_calls" { + stopKind = uctypes.StopKindToolUse + } + + var validToolCalls []ToolCall + for _, tc := range toolCallsInProgress { + if tc.ID != "" && tc.Function.Name != "" { + validToolCalls = append(validToolCalls, tc) + } + } + + var waveToolCalls []uctypes.WaveToolCall + if len(validToolCalls) > 0 { + for _, tc := range validToolCalls { + var inputJSON any + if tc.Function.Arguments != "" { + if err := json.Unmarshal([]byte(tc.Function.Arguments), &inputJSON); err != nil { + log.Printf("openaichat: failed to parse tool call arguments: %v\n", err) + continue + } + } + waveToolCalls = append(waveToolCalls, uctypes.WaveToolCall{ + ID: tc.ID, + Name: tc.Function.Name, + Input: inputJSON, + }) + } + } + + stopReason := &uctypes.WaveStopReason{ + Kind: stopKind, + RawReason: finishReason, + ToolCalls: waveToolCalls, + } + + assistantMsg := &StoredChatMessage{ + MessageId: msgID, + Message: ChatRequestMessage{ + Role: "assistant", + }, + } + + if len(validToolCalls) > 0 { + assistantMsg.Message.ToolCalls = validToolCalls + } else { + assistantMsg.Message.Content = textBuilder.String() + } + + if textStarted { + _ = sseHandler.AiMsgTextEnd(textID) + } + _ = sseHandler.AiMsgFinishStep() + if stopKind != uctypes.StopKindToolUse { + _ = sseHandler.AiMsgFinish(finishReason, nil) + } + + return stopReason, assistantMsg, nil +} diff --git a/pkg/aiusechat/openaichat/openaichat-convertmessage.go b/pkg/aiusechat/openaichat/openaichat-convertmessage.go new file mode 100644 index 0000000000..d26c57884f --- /dev/null +++ b/pkg/aiusechat/openaichat/openaichat-convertmessage.go @@ -0,0 +1,346 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package openaichat + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "strings" + + "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil" + "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" + "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/wavebase" +) + +const ( + OpenAIChatDefaultMaxTokens = 4096 +) + +// appendToLastUserMessage appends text to the last user message in the messages slice +func appendToLastUserMessage(messages []ChatRequestMessage, text string) { + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Role == "user" { + messages[i].Content += "\n\n" + text + break + } + } +} + +// convertToolDefinitions converts Wave ToolDefinitions to OpenAI format +// Only includes tools whose required capabilities are met +func convertToolDefinitions(waveTools []uctypes.ToolDefinition, capabilities []string) []ToolDefinition { + if len(waveTools) == 0 { + return nil + } + + openaiTools := make([]ToolDefinition, 0, len(waveTools)) + for _, waveTool := range waveTools { + if !waveTool.HasRequiredCapabilities(capabilities) { + continue + } + openaiTool := ToolDefinition{ + Type: "function", + Function: ToolFunctionDef{ + Name: waveTool.Name, + Description: waveTool.Description, + Parameters: waveTool.InputSchema, + }, + } + openaiTools = append(openaiTools, openaiTool) + } + return openaiTools +} + +// buildChatHTTPRequest creates an HTTP request for the OpenAI chat completions API +func buildChatHTTPRequest(ctx context.Context, messages []ChatRequestMessage, chatOpts uctypes.WaveChatOpts) (*http.Request, error) { + opts := chatOpts.Config + + if opts.Model == "" { + return nil, errors.New("opts.model is required") + } + if opts.BaseURL == "" { + return nil, errors.New("BaseURL is required") + } + + maxTokens := opts.MaxTokens + if maxTokens <= 0 { + maxTokens = OpenAIChatDefaultMaxTokens + } + + finalMessages := messages + if len(chatOpts.SystemPrompt) > 0 { + systemMessage := ChatRequestMessage{ + Role: "system", + Content: strings.Join(chatOpts.SystemPrompt, "\n\n"), + } + finalMessages = append([]ChatRequestMessage{systemMessage}, messages...) + } + + // injected data + if chatOpts.TabState != "" { + appendToLastUserMessage(finalMessages, chatOpts.TabState) + } + if chatOpts.PlatformInfo != "" { + appendToLastUserMessage(finalMessages, "\n"+chatOpts.PlatformInfo+"\n") + } + + reqBody := &ChatRequest{ + Model: opts.Model, + Messages: finalMessages, + Stream: true, + } + + if aiutil.IsOpenAIReasoningModel(opts.Model) { + reqBody.MaxCompletionTokens = maxTokens + } else { + reqBody.MaxTokens = maxTokens + } + + // Add tool definitions if tools capability is available and tools exist + var allTools []uctypes.ToolDefinition + if opts.HasCapability(uctypes.AICapabilityTools) { + allTools = append(allTools, chatOpts.Tools...) + allTools = append(allTools, chatOpts.TabTools...) + if len(allTools) > 0 { + reqBody.Tools = convertToolDefinitions(allTools, opts.Capabilities) + } + } + + if wavebase.IsDevMode() { + log.Printf("openaichat: model %s, messages: %d, tools: %d\n", opts.Model, len(messages), len(allTools)) + } + + buf, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, opts.BaseURL, bytes.NewReader(buf)) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + if opts.APIToken != "" { + req.Header.Set("Authorization", "Bearer "+opts.APIToken) + } + req.Header.Set("Accept", "text/event-stream") + if chatOpts.ClientId != "" { + req.Header.Set("X-Wave-ClientId", chatOpts.ClientId) + } + if chatOpts.ChatId != "" { + req.Header.Set("X-Wave-ChatId", chatOpts.ChatId) + } + req.Header.Set("X-Wave-Version", wavebase.WaveVersion) + req.Header.Set("X-Wave-APIType", uctypes.APIType_OpenAIChat) + req.Header.Set("X-Wave-RequestType", chatOpts.GetWaveRequestType()) + + return req, nil +} + +// ConvertAIMessageToStoredChatMessage converts an AIMessage to StoredChatMessage +// These messages are ALWAYS role "user" +func ConvertAIMessageToStoredChatMessage(aiMsg uctypes.AIMessage) (*StoredChatMessage, error) { + if err := aiMsg.Validate(); err != nil { + return nil, fmt.Errorf("invalid AIMessage: %w", err) + } + + var textBuilder strings.Builder + firstText := true + for _, part := range aiMsg.Parts { + var partText string + + switch { + case part.Type == uctypes.AIMessagePartTypeText: + partText = part.Text + + case part.MimeType == "text/plain": + textData, err := aiutil.ExtractTextData(part.Data, part.URL) + if err != nil { + log.Printf("openaichat: error extracting text data for %s: %v\n", part.FileName, err) + continue + } + partText = aiutil.FormatAttachedTextFile(part.FileName, textData) + + case part.MimeType == "directory": + if len(part.Data) == 0 { + log.Printf("openaichat: directory listing part missing data for %s\n", part.FileName) + continue + } + partText = aiutil.FormatAttachedDirectoryListing(part.FileName, string(part.Data)) + + default: + continue + } + + if partText != "" { + if !firstText { + textBuilder.WriteString("\n\n") + } + textBuilder.WriteString(partText) + firstText = false + } + } + + return &StoredChatMessage{ + MessageId: aiMsg.MessageId, + Message: ChatRequestMessage{ + Role: "user", + Content: textBuilder.String(), + }, + }, nil +} + +// ConvertToolResultsToNativeChatMessage converts tool results to OpenAI tool messages +func ConvertToolResultsToNativeChatMessage(toolResults []uctypes.AIToolResult) ([]uctypes.GenAIMessage, error) { + if len(toolResults) == 0 { + return nil, nil + } + + messages := make([]uctypes.GenAIMessage, 0, len(toolResults)) + for _, toolResult := range toolResults { + var content string + if toolResult.ErrorText != "" { + content = fmt.Sprintf("Error: %s", toolResult.ErrorText) + } else { + content = toolResult.Text + } + + msg := &StoredChatMessage{ + MessageId: toolResult.ToolUseID, + Message: ChatRequestMessage{ + Role: "tool", + ToolCallID: toolResult.ToolUseID, + Name: toolResult.ToolName, + Content: content, + }, + } + messages = append(messages, msg) + } + + return messages, nil +} + +// ConvertAIChatToUIChat converts stored chat to UI format +func ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { + uiChat := &uctypes.UIChat{ + ChatId: aiChat.ChatId, + APIType: aiChat.APIType, + Model: aiChat.Model, + APIVersion: aiChat.APIVersion, + Messages: make([]uctypes.UIMessage, 0, len(aiChat.NativeMessages)), + } + + for _, genMsg := range aiChat.NativeMessages { + chatMsg, ok := genMsg.(*StoredChatMessage) + if !ok { + continue + } + + var parts []uctypes.UIMessagePart + + // Add text content if present + if chatMsg.Message.Content != "" { + parts = append(parts, uctypes.UIMessagePart{ + Type: "text", + Text: chatMsg.Message.Content, + }) + } + + // Add tool calls if present (assistant requesting tool use) + if len(chatMsg.Message.ToolCalls) > 0 { + for _, toolCall := range chatMsg.Message.ToolCalls { + if toolCall.Type != "function" { + continue + } + + // Only add if ToolUseData is available + if toolCall.ToolUseData != nil { + parts = append(parts, uctypes.UIMessagePart{ + Type: "data-tooluse", + ID: toolCall.ID, + Data: *toolCall.ToolUseData, + }) + } + } + } + + // Tool result messages (role "tool") are not converted to UIMessage + if chatMsg.Message.Role == "tool" && chatMsg.Message.ToolCallID != "" { + continue + } + + // Skip messages with no parts + if len(parts) == 0 { + continue + } + + uiMsg := uctypes.UIMessage{ + ID: chatMsg.MessageId, + Role: chatMsg.Message.Role, + Parts: parts, + } + + uiChat.Messages = append(uiChat.Messages, uiMsg) + } + + return uiChat, nil +} + +// GetFunctionCallInputByToolCallId searches for a tool call by ID in the chat history +func GetFunctionCallInputByToolCallId(aiChat uctypes.AIChat, toolCallId string) *uctypes.AIFunctionCallInput { + for _, genMsg := range aiChat.NativeMessages { + chatMsg, ok := genMsg.(*StoredChatMessage) + if !ok { + continue + } + idx := chatMsg.Message.FindToolCallIndex(toolCallId) + if idx == -1 { + continue + } + toolCall := chatMsg.Message.ToolCalls[idx] + return &uctypes.AIFunctionCallInput{ + CallId: toolCall.ID, + Name: toolCall.Function.Name, + Arguments: toolCall.Function.Arguments, + ToolUseData: toolCall.ToolUseData, + } + } + return nil +} + +// UpdateToolUseData updates the ToolUseData for a specific tool call in the chat history +func UpdateToolUseData(chatId string, callId string, newToolUseData uctypes.UIMessageDataToolUse) error { + chat := chatstore.DefaultChatStore.Get(chatId) + if chat == nil { + return fmt.Errorf("chat not found: %s", chatId) + } + + for _, genMsg := range chat.NativeMessages { + chatMsg, ok := genMsg.(*StoredChatMessage) + if !ok { + continue + } + idx := chatMsg.Message.FindToolCallIndex(callId) + if idx == -1 { + continue + } + updatedMsg := chatMsg.Copy() + updatedMsg.Message.ToolCalls[idx].ToolUseData = &newToolUseData + aiOpts := &uctypes.AIOptsType{ + APIType: chat.APIType, + Model: chat.Model, + APIVersion: chat.APIVersion, + } + return chatstore.DefaultChatStore.PostMessage(chatId, aiOpts, updatedMsg) + } + + return fmt.Errorf("tool call with callId %s not found in chat %s", callId, chatId) +} diff --git a/pkg/aiusechat/openaichat/openaichat-types.go b/pkg/aiusechat/openaichat/openaichat-types.go new file mode 100644 index 0000000000..f0bcc41614 --- /dev/null +++ b/pkg/aiusechat/openaichat/openaichat-types.go @@ -0,0 +1,171 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package openaichat + +import ( + "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" +) + +// OpenAI Chat Completions API types (simplified) + +type ChatRequest struct { + Model string `json:"model"` + Messages []ChatRequestMessage `json:"messages"` + Stream bool `json:"stream"` + MaxTokens int `json:"max_tokens,omitempty"` // legacy + MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` // newer + Temperature float64 `json:"temperature,omitempty"` + Tools []ToolDefinition `json:"tools,omitempty"` // if you use tools + ToolChoice any `json:"tool_choice,omitempty"` // "auto", "none", or struct +} + +type ChatRequestMessage struct { + Role string `json:"role"` // "system","user","assistant","tool" + Content string `json:"content,omitempty"` // normal text messages + ToolCalls []ToolCall `json:"tool_calls,omitempty"` // assistant tool-call message + ToolCallID string `json:"tool_call_id,omitempty"` // for role:"tool" + Name string `json:"name,omitempty"` // tool name on role:"tool" +} + +func (cm *ChatRequestMessage) clean() *ChatRequestMessage { + if len(cm.ToolCalls) == 0 { + return cm + } + rtn := *cm + rtn.ToolCalls = make([]ToolCall, len(cm.ToolCalls)) + for i, tc := range cm.ToolCalls { + rtn.ToolCalls[i] = *tc.clean() + } + return &rtn +} + +func (cm *ChatRequestMessage) FindToolCallIndex(toolCallId string) int { + for i, tc := range cm.ToolCalls { + if tc.ID == toolCallId { + return i + } + } + return -1 +} + +type ToolDefinition struct { + Type string `json:"type"` // "function" + Function ToolFunctionDef `json:"function"` +} + +type ToolFunctionDef struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parameters map[string]any `json:"parameters,omitempty"` // or jsonschema struct +} + +type ToolCall struct { + ID string `json:"id"` + Type string `json:"type"` // "function" + Function ToolFunctionCall `json:"function"` + ToolUseData *uctypes.UIMessageDataToolUse `json:"toolusedata,omitempty"` // Internal field (must be cleaned before sending to API) +} + +func (tc *ToolCall) clean() *ToolCall { + if tc.ToolUseData == nil { + return tc + } + rtn := *tc + rtn.ToolUseData = nil + return &rtn +} + +type ToolFunctionCall struct { + Name string `json:"name"` + Arguments string `json:"arguments"` // raw JSON string +} + +type StreamChunk struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []StreamChoice `json:"choices"` +} + +type StreamChoice struct { + Index int `json:"index"` + Delta ContentDelta `json:"delta"` + FinishReason *string `json:"finish_reason"` // "stop", "length" | "tool_calls" | "content_filter" +} + +// This is the important part: +type ContentDelta struct { + Role string `json:"role,omitempty"` + Content string `json:"content,omitempty"` + ToolCalls []ToolCallDelta `json:"tool_calls,omitempty"` +} + +type ToolCallDelta struct { + Index int `json:"index"` + ID string `json:"id,omitempty"` // only on first chunk + Type string `json:"type,omitempty"` // "function" + Function *ToolFunctionDelta `json:"function,omitempty"` +} + +type ToolFunctionDelta struct { + Name string `json:"name,omitempty"` // only on first chunk + Arguments string `json:"arguments,omitempty"` // streamed, append across chunks +} + +// StoredChatMessage is the stored message type +type StoredChatMessage struct { + MessageId string `json:"messageid"` + Message ChatRequestMessage `json:"message"` + Usage *ChatUsage `json:"usage,omitempty"` +} + +type ChatUsage struct { + Model string `json:"model,omitempty"` + InputTokens int `json:"prompt_tokens,omitempty"` + OutputTokens int `json:"completion_tokens,omitempty"` + TotalTokens int `json:"total_tokens,omitempty"` +} + +func (m *StoredChatMessage) GetMessageId() string { + return m.MessageId +} + +func (m *StoredChatMessage) GetRole() string { + return m.Message.Role +} + +func (m *StoredChatMessage) GetUsage() *uctypes.AIUsage { + if m.Usage == nil { + return nil + } + return &uctypes.AIUsage{ + APIType: uctypes.APIType_OpenAIChat, + Model: m.Usage.Model, + InputTokens: m.Usage.InputTokens, + OutputTokens: m.Usage.OutputTokens, + } +} + +func (m *StoredChatMessage) Copy() *StoredChatMessage { + if m == nil { + return nil + } + copied := *m + if len(m.Message.ToolCalls) > 0 { + copied.Message.ToolCalls = make([]ToolCall, len(m.Message.ToolCalls)) + for i, tc := range m.Message.ToolCalls { + copied.Message.ToolCalls[i] = tc + if tc.ToolUseData != nil { + toolUseDataCopy := *tc.ToolUseData + copied.Message.ToolCalls[i].ToolUseData = &toolUseDataCopy + } + } + } + if m.Usage != nil { + usageCopy := *m.Usage + copied.Usage = &usageCopy + } + return &copied +} diff --git a/pkg/aiusechat/tools_readdir.go b/pkg/aiusechat/tools_readdir.go index 4b90d664c0..da7d568f84 100644 --- a/pkg/aiusechat/tools_readdir.go +++ b/pkg/aiusechat/tools_readdir.go @@ -6,6 +6,7 @@ package aiusechat import ( "fmt" "os" + "path/filepath" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/util/fileutil" @@ -63,6 +64,10 @@ func verifyReadDirInput(input any, toolUseData *uctypes.UIMessageDataToolUse) er return fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return fmt.Errorf("path must be absolute, got relative path: %s", params.Path) + } + fileInfo, err := os.Stat(expandedPath) if err != nil { return fmt.Errorf("failed to stat path: %w", err) @@ -81,6 +86,15 @@ func readDirCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, return nil, err } + expandedPath, err := wavebase.ExpandHomeDir(params.Path) + if err != nil { + return nil, fmt.Errorf("failed to expand path: %w", err) + } + + if !filepath.IsAbs(expandedPath) { + return nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Path) + } + result, err := fileutil.ReadDir(params.Path, *params.MaxEntries) if err != nil { return nil, err @@ -118,7 +132,7 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition { "properties": map[string]any{ "path": map[string]any{ "type": "string", - "description": "Path to the directory to read. Supports '~' for the user's home directory.", + "description": "Absolute path to the directory to read. Supports '~' for the user's home directory. Relative paths are not supported.", }, "max_entries": map[string]any{ "type": "integer", diff --git a/pkg/aiusechat/tools_readfile.go b/pkg/aiusechat/tools_readfile.go index 423333c831..eecc2385b6 100644 --- a/pkg/aiusechat/tools_readfile.go +++ b/pkg/aiusechat/tools_readfile.go @@ -208,6 +208,10 @@ func verifyReadTextFileInput(input any, toolUseData *uctypes.UIMessageDataToolUs return fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + if blocked, reason := isBlockedFile(expandedPath); blocked { return fmt.Errorf("access denied: potentially sensitive file: %s", reason) } @@ -237,6 +241,10 @@ func readTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) return nil, fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + if blocked, reason := isBlockedFile(expandedPath); blocked { return nil, fmt.Errorf("access denied: potentially sensitive file: %s", reason) } @@ -328,7 +336,7 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition { "properties": map[string]any{ "filename": map[string]any{ "type": "string", - "description": "Path to the file to read. Supports '~' for the user's home directory.", + "description": "Absolute path to the file to read. Supports '~' for the user's home directory. Relative paths are not supported.", }, "origin": map[string]any{ "type": "string", diff --git a/pkg/aiusechat/tools_screenshot.go b/pkg/aiusechat/tools_screenshot.go index 4c924db292..9df5a18f0e 100644 --- a/pkg/aiusechat/tools_screenshot.go +++ b/pkg/aiusechat/tools_screenshot.go @@ -67,6 +67,7 @@ func GetCaptureScreenshotToolDefinition(tabId string) uctypes.ToolDefinition { "required": []string{"widget_id"}, "additionalProperties": false, }, + RequiredCapabilities: []string{uctypes.AICapabilityImages}, ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string { inputMap, ok := input.(map[string]any) if !ok { diff --git a/pkg/aiusechat/tools_writefile.go b/pkg/aiusechat/tools_writefile.go index 2c830fd64c..d554cfab09 100644 --- a/pkg/aiusechat/tools_writefile.go +++ b/pkg/aiusechat/tools_writefile.go @@ -112,6 +112,10 @@ func verifyWriteTextFileInput(input any, toolUseData *uctypes.UIMessageDataToolU return fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + contentsBytes := []byte(params.Contents) if utilfn.HasBinaryData(contentsBytes) { return fmt.Errorf("contents appear to contain binary data") @@ -137,6 +141,10 @@ func writeTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) return nil, fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + contentsBytes := []byte(params.Contents) if utilfn.HasBinaryData(contentsBytes) { return nil, fmt.Errorf("contents appear to contain binary data") @@ -184,7 +192,7 @@ func GetWriteTextFileToolDefinition() uctypes.ToolDefinition { "properties": map[string]any{ "filename": map[string]any{ "type": "string", - "description": "Path to the file to write. Supports '~' for the user's home directory.", + "description": "Absolute path to the file to write. Supports '~' for the user's home directory. Relative paths are not supported.", }, "contents": map[string]any{ "type": "string", @@ -247,6 +255,10 @@ func verifyEditTextFileInput(input any, toolUseData *uctypes.UIMessageDataToolUs return fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + _, err = validateTextFile(expandedPath, "edit", true) if err != nil { return err @@ -269,6 +281,10 @@ func EditTextFileDryRun(input any, fileOverride string) ([]byte, []byte, error) return nil, nil, fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return nil, nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + _, err = validateTextFile(expandedPath, "edit", true) if err != nil { return nil, nil, err @@ -303,6 +319,10 @@ func editTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) return nil, fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + _, err = validateTextFile(expandedPath, "edit", true) if err != nil { return nil, err @@ -340,7 +360,7 @@ func GetEditTextFileToolDefinition() uctypes.ToolDefinition { "properties": map[string]any{ "filename": map[string]any{ "type": "string", - "description": "Path to the file to edit. Supports '~' for the user's home directory.", + "description": "Absolute path to the file to edit. Supports '~' for the user's home directory. Relative paths are not supported.", }, "edits": map[string]any{ "type": "array", @@ -422,6 +442,10 @@ func verifyDeleteTextFileInput(input any, toolUseData *uctypes.UIMessageDataTool return fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + _, err = validateTextFile(expandedPath, "delete", true) if err != nil { return err @@ -442,6 +466,10 @@ func deleteTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse return nil, fmt.Errorf("failed to expand path: %w", err) } + if !filepath.IsAbs(expandedPath) { + return nil, fmt.Errorf("path must be absolute, got relative path: %s", params.Filename) + } + _, err = validateTextFile(expandedPath, "delete", true) if err != nil { return nil, err @@ -476,7 +504,7 @@ func GetDeleteTextFileToolDefinition() uctypes.ToolDefinition { "properties": map[string]any{ "filename": map[string]any{ "type": "string", - "description": "Path to the file to delete. Supports '~' for the user's home directory.", + "description": "Absolute path to the file to delete. Supports '~' for the user's home directory. Relative paths are not supported.", }, }, "required": []string{"filename"}, diff --git a/pkg/aiusechat/uctypes/usechat-types.go b/pkg/aiusechat/uctypes/uctypes.go similarity index 84% rename from pkg/aiusechat/uctypes/usechat-types.go rename to pkg/aiusechat/uctypes/uctypes.go index 4154ceacf0..9fc6a73f53 100644 --- a/pkg/aiusechat/uctypes/usechat-types.go +++ b/pkg/aiusechat/uctypes/uctypes.go @@ -6,6 +6,7 @@ package uctypes import ( "fmt" "net/url" + "slices" "strings" ) @@ -14,6 +15,12 @@ const DefaultAnthropicModel = "claude-sonnet-4-5" const DefaultOpenAIModel = "gpt-5-mini" const PremiumOpenAIModel = "gpt-5.1" +const ( + APIType_AnthropicMessages = "anthropic-messages" + APIType_OpenAIResponses = "openai-responses" + APIType_OpenAIChat = "openai-chat" +) + type UseChatRequest struct { Messages []UIMessage `json:"messages"` } @@ -78,13 +85,14 @@ type UIMessageDataUserFile struct { // ToolDefinition represents a tool that can be used by the AI model type ToolDefinition struct { - Name string `json:"name"` - DisplayName string `json:"displayname,omitempty"` // internal field (cannot marshal to API, must be stripped) - Description string `json:"description"` - ShortDescription string `json:"shortdescription,omitempty"` // internal field (cannot marshal to API, must be stripped) - ToolLogName string `json:"-"` // short name for telemetry (e.g., "term:getscrollback") - InputSchema map[string]any `json:"input_schema"` - Strict bool `json:"strict,omitempty"` + Name string `json:"name"` + DisplayName string `json:"displayname,omitempty"` // internal field (cannot marshal to API, must be stripped) + Description string `json:"description"` + ShortDescription string `json:"shortdescription,omitempty"` // internal field (cannot marshal to API, must be stripped) + ToolLogName string `json:"-"` // short name for telemetry (e.g., "term:getscrollback") + InputSchema map[string]any `json:"input_schema"` + Strict bool `json:"strict,omitempty"` + RequiredCapabilities []string `json:"requiredcapabilities,omitempty"` ToolTextCallback func(any) (string, error) `json:"-"` ToolAnyCallback func(any, *UIMessageDataToolUse) (any, error) `json:"-"` // *UIMessageDataToolUse will NOT be nil @@ -114,6 +122,18 @@ func (td *ToolDefinition) Desc() string { return td.Description } +func (td *ToolDefinition) HasRequiredCapabilities(capabilities []string) bool { + if td == nil || len(td.RequiredCapabilities) == 0 { + return true + } + for _, reqCap := range td.RequiredCapabilities { + if !slices.Contains(capabilities, reqCap) { + return false + } + } + return true +} + //------------------ // Wave specific types, stop reasons, tool calls, config // these are used internally to coordinate the calls/steps @@ -125,9 +145,9 @@ const ( ) const ( - ThinkingModeQuick = "quick" - ThinkingModeBalanced = "balanced" - ThinkingModeDeep = "deep" + AIModeQuick = "waveai@quick" + AIModeBalanced = "waveai@balanced" + AIModeDeep = "waveai@deep" ) const ( @@ -136,6 +156,12 @@ const ( ToolUseStatusCompleted = "completed" ) +const ( + AICapabilityTools = "tools" + AICapabilityImages = "images" + AICapabilityPdfs = "pdfs" +) + const ( ApprovalNeedsApproval = "needs-approval" ApprovalUserApproved = "user-approved" @@ -144,6 +170,28 @@ const ( ApprovalAutoApproved = "auto-approved" ) +type AIModeConfig struct { + Mode string `json:"mode"` + DisplayName string `json:"display:name"` + DisplayOrder float64 `json:"display:order,omitempty"` + DisplayIcon string `json:"display:icon"` + APIType string `json:"apitype"` + Model string `json:"model"` + ThinkingLevel string `json:"thinkinglevel"` + BaseURL string `json:"baseurl,omitempty"` + WaveAICloud bool `json:"waveaicloud,omitempty"` + APIVersion string `json:"apiversion,omitempty"` + APIToken string `json:"apitoken,omitempty"` + APITokenSecretName string `json:"apitokensecretname,omitempty"` + Premium bool `json:"premium"` + Description string `json:"description"` + Capabilities []string `json:"capabilities,omitempty"` +} + +func (c *AIModeConfig) HasCapability(cap string) bool { + return slices.Contains(c.Capabilities, cap) +} + // when updating this struct, also modify frontend/app/aipanel/aitypes.ts WaveUIDataTypes.tooluse type UIMessageDataToolUse struct { ToolCallId string `json:"toolcallid"` @@ -206,17 +254,18 @@ type WaveContinueResponse struct { // Wave Specific AI opts for configuration type AIOptsType struct { - APIType string `json:"apitype,omitempty"` - Model string `json:"model"` - APIToken string `json:"apitoken"` - OrgID string `json:"orgid,omitempty"` - APIVersion string `json:"apiversion,omitempty"` - BaseURL string `json:"baseurl,omitempty"` - ProxyURL string `json:"proxyurl,omitempty"` - MaxTokens int `json:"maxtokens,omitempty"` - TimeoutMs int `json:"timeoutms,omitempty"` - ThinkingLevel string `json:"thinkinglevel,omitempty"` // ThinkingLevelLow, ThinkingLevelMedium, or ThinkingLevelHigh - ThinkingMode string `json:"thinkingmode,omitempty"` // quick, balanced, or deep + APIType string `json:"apitype,omitempty"` + Model string `json:"model"` + APIToken string `json:"apitoken"` + OrgID string `json:"orgid,omitempty"` + APIVersion string `json:"apiversion,omitempty"` + BaseURL string `json:"baseurl,omitempty"` + ProxyURL string `json:"proxyurl,omitempty"` + MaxTokens int `json:"maxtokens,omitempty"` + TimeoutMs int `json:"timeoutms,omitempty"` + ThinkingLevel string `json:"thinkinglevel,omitempty"` // ThinkingLevelLow, ThinkingLevelMedium, or ThinkingLevelHigh + AIMode string `json:"aimode,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` } func (opts AIOptsType) IsWaveProxy() bool { @@ -227,6 +276,10 @@ func (opts AIOptsType) IsPremiumModel() bool { return opts.Model == "gpt-5" || opts.Model == "gpt-5.1" || strings.Contains(opts.Model, "claude-sonnet") } +func (opts AIOptsType) HasCapability(cap string) bool { + return slices.Contains(opts.Capabilities, cap) +} + type AIChat struct { ChatId string `json:"chatid"` APIType string `json:"apitype"` @@ -262,7 +315,7 @@ type AIMetrics struct { RequestDuration int `json:"requestduration"` // ms WidgetAccess bool `json:"widgetaccess"` ThinkingLevel string `json:"thinkinglevel,omitempty"` - ThinkingMode string `json:"thinkingmode,omitempty"` + AIMode string `json:"aimode,omitempty"` } type AIFunctionCallInput struct { @@ -559,7 +612,7 @@ func AreModelsCompatible(apiType, model1, model2 string) bool { return true } - if apiType == "openai" { + if apiType == APIType_OpenAIResponses { gpt5Models := map[string]bool{ "gpt-5.1": true, "gpt-5": true, diff --git a/pkg/aiusechat/usechat-backend.go b/pkg/aiusechat/usechat-backend.go index adebb11282..528cd3af5c 100644 --- a/pkg/aiusechat/usechat-backend.go +++ b/pkg/aiusechat/usechat-backend.go @@ -9,6 +9,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/aiusechat/anthropic" "github.com/wavetermdev/waveterm/pkg/aiusechat/openai" + "github.com/wavetermdev/waveterm/pkg/aiusechat/openaichat" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/web/sse" ) @@ -28,7 +29,7 @@ type UseChatBackend interface { // UpdateToolUseData updates the tool use data for a specific tool call in the chat. // This is used to update the UI state for tool execution (approval status, results, etc.) - UpdateToolUseData(chatId string, toolCallId string, toolUseData *uctypes.UIMessageDataToolUse) error + UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error // ConvertToolResultsToNativeChatMessage converts tool execution results into native chat messages // that can be sent back to the AI backend. Returns a slice of messages (some backends may @@ -51,14 +52,17 @@ type UseChatBackend interface { // Compile-time interface checks var _ UseChatBackend = (*openaiResponsesBackend)(nil) +var _ UseChatBackend = (*openaiCompletionsBackend)(nil) var _ UseChatBackend = (*anthropicBackend)(nil) // GetBackendByAPIType returns the appropriate UseChatBackend implementation for the given API type func GetBackendByAPIType(apiType string) (UseChatBackend, error) { switch apiType { - case APIType_OpenAI: + case uctypes.APIType_OpenAIResponses: return &openaiResponsesBackend{}, nil - case APIType_Anthropic: + case uctypes.APIType_OpenAIChat: + return &openaiCompletionsBackend{}, nil + case uctypes.APIType_AnthropicMessages: return &anthropicBackend{}, nil default: return nil, fmt.Errorf("unsupported API type: %s", apiType) @@ -82,7 +86,7 @@ func (b *openaiResponsesBackend) RunChatStep( return stopReason, genMsgs, rateLimitInfo, err } -func (b *openaiResponsesBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData *uctypes.UIMessageDataToolUse) error { +func (b *openaiResponsesBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error { return openai.UpdateToolUseData(chatId, toolCallId, toolUseData) } @@ -119,6 +123,43 @@ func (b *openaiResponsesBackend) ConvertAIChatToUIChat(aiChat uctypes.AIChat) (* return openai.ConvertAIChatToUIChat(aiChat) } +// openaiCompletionsBackend implements UseChatBackend for OpenAI Completions API +type openaiCompletionsBackend struct{} + +func (b *openaiCompletionsBackend) RunChatStep( + ctx context.Context, + sseHandler *sse.SSEHandlerCh, + chatOpts uctypes.WaveChatOpts, + cont *uctypes.WaveContinueResponse, +) (*uctypes.WaveStopReason, []uctypes.GenAIMessage, *uctypes.RateLimitInfo, error) { + stopReason, msgs, rateLimitInfo, err := openaichat.RunChatStep(ctx, sseHandler, chatOpts, cont) + var genMsgs []uctypes.GenAIMessage + for _, msg := range msgs { + genMsgs = append(genMsgs, msg) + } + return stopReason, genMsgs, rateLimitInfo, err +} + +func (b *openaiCompletionsBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error { + return openaichat.UpdateToolUseData(chatId, toolCallId, toolUseData) +} + +func (b *openaiCompletionsBackend) ConvertToolResultsToNativeChatMessage(toolResults []uctypes.AIToolResult) ([]uctypes.GenAIMessage, error) { + return openaichat.ConvertToolResultsToNativeChatMessage(toolResults) +} + +func (b *openaiCompletionsBackend) ConvertAIMessageToNativeChatMessage(message uctypes.AIMessage) (uctypes.GenAIMessage, error) { + return openaichat.ConvertAIMessageToStoredChatMessage(message) +} + +func (b *openaiCompletionsBackend) GetFunctionCallInputByToolCallId(aiChat uctypes.AIChat, toolCallId string) *uctypes.AIFunctionCallInput { + return openaichat.GetFunctionCallInputByToolCallId(aiChat, toolCallId) +} + +func (b *openaiCompletionsBackend) ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { + return openaichat.ConvertAIChatToUIChat(aiChat) +} + // anthropicBackend implements UseChatBackend for Anthropic API type anthropicBackend struct{} @@ -132,7 +173,7 @@ func (b *anthropicBackend) RunChatStep( return stopReason, []uctypes.GenAIMessage{msg}, rateLimitInfo, err } -func (b *anthropicBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData *uctypes.UIMessageDataToolUse) error { +func (b *anthropicBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error { return fmt.Errorf("UpdateToolUseData not implemented for anthropic backend") } diff --git a/pkg/aiusechat/usechat-mode.go b/pkg/aiusechat/usechat-mode.go new file mode 100644 index 0000000000..fe5bd2d786 --- /dev/null +++ b/pkg/aiusechat/usechat-mode.go @@ -0,0 +1,43 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package aiusechat + +import ( + "fmt" + + "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/wconfig" +) + +func resolveAIMode(requestedMode string, premium bool) (string, *wconfig.AIModeConfigType, error) { + mode := requestedMode + if mode == "" { + mode = uctypes.AIModeBalanced + } + + config, err := getAIModeConfig(mode) + if err != nil { + return "", nil, err + } + + if config.WaveAICloud && !premium { + mode = uctypes.AIModeQuick + config, err = getAIModeConfig(mode) + if err != nil { + return "", nil, err + } + } + + return mode, config, nil +} + +func getAIModeConfig(aiMode string) (*wconfig.AIModeConfigType, error) { + fullConfig := wconfig.GetWatcher().GetFullConfig() + config, ok := fullConfig.WaveAIModes[aiMode] + if !ok { + return nil, fmt.Errorf("invalid AI mode: %s", aiMode) + } + + return &config, nil +} diff --git a/pkg/aiusechat/usechat-prompts.go b/pkg/aiusechat/usechat-prompts.go new file mode 100644 index 0000000000..b8bcb7aa03 --- /dev/null +++ b/pkg/aiusechat/usechat-prompts.go @@ -0,0 +1,61 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package aiusechat + +import "strings" + +var SystemPromptText = strings.Join([]string{ + `You are Wave AI, an intelligent assistant embedded within Wave Terminal, a modern terminal application with graphical widgets.`, + `You appear as a pull-out panel on the left side of a tab, with the tab's widgets laid out on the right.`, + `Widget context is provided as informational only.`, + `Do NOT assume any API access or ability to interact with the widgets except via tools provided (note that some widgets may expose NO tools, so their context is informational only).`, +}, " ") + +var SystemPromptText_OpenAI = strings.Join([]string{ + `You are Wave AI, an assistant embedded in Wave Terminal (a terminal with graphical widgets).`, + `You appear as a pull-out panel on the left; widgets are on the right.`, + + // Capabilities & truthfulness + `Tools define your only capabilities. If a capability is not provided by a tool, you cannot do it. Never fabricate data or pretend to call tools. If you lack data or access, say so directly and suggest the next best step.`, + `Use read-only tools (capture_screenshot, read_text_file, read_dir, term_get_scrollback) automatically whenever they help answer the user's request. When a user clearly expresses intent to modify something (write/edit/delete files), call the corresponding tool directly.`, + + // Crisp behavior + `Be concise and direct. Prefer determinism over speculation. If a brief clarifying question eliminates guesswork, ask it.`, + + // Attached text files + `User-attached text files may appear inline as \ncontent\n.`, + `User-attached directories use the tag JSON DirInfo.`, + `If multiple attached files exist, treat each as a separate source file with its own file_name.`, + `When the user refers to these files, use their inline content directly; do NOT call any read_text_file or file-access tools to re-read them unless asked.`, + + // Output & formatting + `When presenting commands or any runnable multi-line code, always use fenced Markdown code blocks.`, + `Use an appropriate language hint after the opening fence (e.g., "bash" for shell commands, "go" for Go, "json" for JSON).`, + `For shell commands, do NOT prefix lines with "$" or shell prompts. Use placeholders in ALL_CAPS (e.g., PROJECT_ID) and explain them once after the block if needed.`, + "Reserve inline code (single backticks) for short references like command names (`grep`, `less`), flags, env vars, file paths, or tiny snippets not meant to be executed.", + `You may use Markdown (lists, tables, bold/italics) to improve readability.`, + `Never comment on or justify your formatting choices; just follow these rules.`, + `When generating code or command blocks, try to keep lines under ~100 characters wide where practical (soft wrap; do not break tokens mid-word). Favor indentation and short variable names to stay compact, but correctness always takes priority.`, + + // Safety & limits + `If a request would execute dangerous or destructive actions, warn briefly and provide a safer alternative.`, + `If output is very long, prefer a brief summary plus a copy-ready fenced block or offer a follow-up chunking strategy.`, + + `You can write and edit local files on disk using available tools, but you cannot execute shell commands, run code in the terminal, or read/write remote files.`, + `If the user asks you to execute commands or run code, or deal with remote files say that these features aren't available yet AND make sure to say that they are coming soon (stay tuned for updates).`, + `Instead, show them exactly what command or code they could copy-paste to run manually.`, + + // Final reminder + `You have NO API access to widgets or Wave unless provided via an explicit tool.`, +}, " ") + +var SystemPromptText_StrictToolAddOn = `## Tool Call Rules (STRICT) + +When you decide a file write/edit tool call is needed: + +- Output ONLY the tool call. +- Do NOT include any explanation, summary, or file content in the chat. +- Do NOT echo the file content before or after the tool call. +- After the tool call result is returned, respond ONLY with what the user directly asked for. If they did not ask to see the file content, do NOT show it. +` diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go index e5866bcaf4..477b0001c4 100644 --- a/pkg/aiusechat/usechat.go +++ b/pkg/aiusechat/usechat.go @@ -12,13 +12,16 @@ import ( "net/http" "os" "os/user" + "regexp" "strings" "sync" "time" "github.com/google/uuid" + "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil" "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/secretstore" "github.com/wavetermdev/waveterm/pkg/telemetry" "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata" "github.com/wavetermdev/waveterm/pkg/util/ds" @@ -32,14 +35,10 @@ import ( "github.com/wavetermdev/waveterm/pkg/wstore" ) -const ( - APIType_Anthropic = "anthropic" - APIType_OpenAI = "openai" -) - -const DefaultAPI = APIType_OpenAI +const DefaultAPI = uctypes.APIType_OpenAIResponses const DefaultMaxTokens = 4 * 1024 const BuilderMaxTokens = 24 * 1024 +const WaveAIEndpointEnvName = "WAVETERM_WAVEAI_ENDPOINT" var ( globalRateLimitInfo = &uctypes.RateLimitInfo{Unknown: true} @@ -49,112 +48,68 @@ var ( activeChats = ds.MakeSyncMap[bool]() // key is chatid ) -var SystemPromptText = strings.Join([]string{ - `You are Wave AI, an intelligent assistant embedded within Wave Terminal, a modern terminal application with graphical widgets.`, - `You appear as a pull-out panel on the left side of a tab, with the tab's widgets laid out on the right.`, - `Widget context is provided as informationa only.`, - `Do NOT assume any API access or ability to interact with the widgets except via tools provided (note that some widgets may expose NO tools, so their context is informational only).`, -}, " ") - -var SystemPromptText_OpenAI = strings.Join([]string{ - `You are Wave AI, an assistant embedded in Wave Terminal (a terminal with graphical widgets).`, - `You appear as a pull-out panel on the left; widgets are on the right.`, - - // Capabilities & truthfulness - `Tools define your only capabilities. If a capability is not provided by a tool, you cannot do it.`, - `Context from widgets is read-only unless a tool explicitly grants interaction.`, - `Never fabricate data. If you lack data or access, say so and offer the next best step (e.g., suggest enabling a tool).`, - - // Crisp behavior - `Be concise and direct. Prefer determinism over speculation. If a brief clarifying question eliminates guesswork, ask it.`, - - // Attached text files - `User-attached text files may appear inline as \ncontent\n.`, - `User-attached directories use the tag JSON DirInfo.`, - `If multiple attached files exist, treat each as a separate source file with its own file_name.`, - `When the user refers to these files, use their inline content directly; do NOT call any read_text_file or file-access tools to re-read them unless asked.`, - - // Output & formatting - `When presenting commands or any runnable multi-line code, always use fenced Markdown code blocks.`, - `Use an appropriate language hint after the opening fence (e.g., "bash" for shell commands, "go" for Go, "json" for JSON).`, - `For shell commands, do NOT prefix lines with "$" or shell prompts. Use placeholders in ALL_CAPS (e.g., PROJECT_ID) and explain them once after the block if needed.`, - "Reserve inline code (single backticks) for short references like command names (`grep`, `less`), flags, env vars, file paths, or tiny snippets not meant to be executed.", - `You may use Markdown (lists, tables, bold/italics) to improve readability.`, - `Never comment on or justify your formatting choices; just follow these rules.`, - `When generating code or command blocks, try to keep lines under ~100 characters wide where practical (soft wrap; do not break tokens mid-word). Favor indentation and short variable names to stay compact, but correctness always takes priority.`, - - // Safety & limits - `If a request would execute dangerous or destructive actions, warn briefly and provide a safer alternative.`, - `If output is very long, prefer a brief summary plus a copy-ready fenced block or offer a follow-up chunking strategy.`, - - `You can write and edit local files on disk using available tools, but you cannot execute shell commands, run code in the terminal, or read/write remote files.`, - `If the user asks you to execute commands or run code, or deal with remote files say that these features aren't available yet AND make sure to say that they are coming soon (stay tuned for updates).`, - `Instead, show them exactly what command or code they could copy-paste to run manually.`, - - // Final reminder - `You have NO API access to widgets or Wave unless provided via an explicit tool.`, -}, " ") - -func getWaveAISettings(premium bool, builderMode bool, rtInfo *waveobj.ObjRTInfo) (*uctypes.AIOptsType, error) { - baseUrl := uctypes.DefaultAIEndpoint - if os.Getenv("WAVETERM_WAVEAI_ENDPOINT") != "" { - baseUrl = os.Getenv("WAVETERM_WAVEAI_ENDPOINT") +func getSystemPrompt(apiType string, model string, isBuilder bool) []string { + if isBuilder { + return []string{} + } + basePrompt := SystemPromptText_OpenAI + modelLower := strings.ToLower(model) + needsStrictToolAddOn, _ := regexp.MatchString(`(?i)\b(mistral|o?llama|qwen|mixtral|yi|phi|deepseek)\b`, modelLower) + if needsStrictToolAddOn { + return []string{basePrompt, SystemPromptText_StrictToolAddOn} } + return []string{basePrompt} +} + +func getWaveAISettings(premium bool, builderMode bool, rtInfo waveobj.ObjRTInfo) (*uctypes.AIOptsType, error) { maxTokens := DefaultMaxTokens if builderMode { maxTokens = BuilderMaxTokens } - if rtInfo != nil && rtInfo.WaveAIMaxOutputTokens > 0 { + if rtInfo.WaveAIMaxOutputTokens > 0 { maxTokens = rtInfo.WaveAIMaxOutputTokens } - var thinkingMode string - if premium { - thinkingMode = uctypes.ThinkingModeBalanced - if rtInfo != nil && rtInfo.WaveAIThinkingMode != "" { - thinkingMode = rtInfo.WaveAIThinkingMode + aiMode, config, err := resolveAIMode(rtInfo.WaveAIMode, premium) + if err != nil { + return nil, err + } + apiToken := config.APIToken + if apiToken == "" && config.APITokenSecretName != "" { + secret, exists, err := secretstore.GetSecret(config.APITokenSecretName) + if err != nil { + return nil, fmt.Errorf("failed to retrieve secret %s: %w", config.APITokenSecretName, err) + } + if !exists || secret == "" { + return nil, fmt.Errorf("secret %s not found or empty", config.APITokenSecretName) + } + apiToken = secret + } + + var baseUrl string + if config.WaveAICloud { + baseUrl = uctypes.DefaultAIEndpoint + if os.Getenv(WaveAIEndpointEnvName) != "" { + baseUrl = os.Getenv(WaveAIEndpointEnvName) } + } else if config.BaseURL != "" { + baseUrl = config.BaseURL } else { - thinkingMode = uctypes.ThinkingModeQuick - } - if DefaultAPI == APIType_Anthropic { - thinkingLevel := uctypes.ThinkingLevelMedium - return &uctypes.AIOptsType{ - APIType: APIType_Anthropic, - Model: uctypes.DefaultAnthropicModel, - MaxTokens: maxTokens, - ThinkingLevel: thinkingLevel, - ThinkingMode: thinkingMode, - BaseURL: baseUrl, - }, nil - } else if DefaultAPI == APIType_OpenAI { - var model string - var thinkingLevel string - - switch thinkingMode { - case uctypes.ThinkingModeQuick: - model = uctypes.DefaultOpenAIModel - thinkingLevel = uctypes.ThinkingLevelLow - case uctypes.ThinkingModeBalanced: - model = uctypes.PremiumOpenAIModel - thinkingLevel = uctypes.ThinkingLevelLow - case uctypes.ThinkingModeDeep: - model = uctypes.PremiumOpenAIModel - thinkingLevel = uctypes.ThinkingLevelMedium - default: - model = uctypes.PremiumOpenAIModel - thinkingLevel = uctypes.ThinkingLevelLow - } - - return &uctypes.AIOptsType{ - APIType: APIType_OpenAI, - Model: model, - MaxTokens: maxTokens, - ThinkingLevel: thinkingLevel, - ThinkingMode: thinkingMode, - BaseURL: baseUrl, - }, nil - } - return nil, fmt.Errorf("invalid API type: %s", DefaultAPI) + return nil, fmt.Errorf("no BaseURL configured for AI mode %s", aiMode) + } + + opts := &uctypes.AIOptsType{ + APIType: config.APIType, + Model: config.Model, + MaxTokens: maxTokens, + ThinkingLevel: config.ThinkingLevel, + AIMode: aiMode, + BaseURL: baseUrl, + Capabilities: config.Capabilities, + } + if apiToken != "" { + opts.APIToken = apiToken + } + return opts, nil } func shouldUseChatCompletionsAPI(model string) bool { @@ -203,7 +158,7 @@ func GetGlobalRateLimit() *uctypes.RateLimitInfo { } func runAIChatStep(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseChatBackend, chatOpts uctypes.WaveChatOpts, cont *uctypes.WaveContinueResponse) (*uctypes.WaveStopReason, []uctypes.GenAIMessage, error) { - if chatOpts.Config.APIType == APIType_OpenAI && shouldUseChatCompletionsAPI(chatOpts.Config.Model) { + if chatOpts.Config.APIType == uctypes.APIType_OpenAIResponses && shouldUseChatCompletionsAPI(chatOpts.Config.Model) { return nil, nil, fmt.Errorf("Chat completions API not available (must use newer OpenAI models)") } stopReason, messages, rateLimitInfo, err := backend.RunChatStep(ctx, sseHandler, chatOpts, cont) @@ -236,7 +191,7 @@ func GetChatUsage(chat *uctypes.AIChat) uctypes.AIUsage { return usage } -func updateToolUseDataInChat(backend UseChatBackend, chatOpts uctypes.WaveChatOpts, toolCallID string, toolUseData *uctypes.UIMessageDataToolUse) { +func updateToolUseDataInChat(backend UseChatBackend, chatOpts uctypes.WaveChatOpts, toolCallID string, toolUseData uctypes.UIMessageDataToolUse) { if err := backend.UpdateToolUseData(chatOpts.ChatId, toolCallID, toolUseData); err != nil { log.Printf("failed to update tool use data in chat: %v\n", err) } @@ -276,7 +231,7 @@ func processToolCallInternal(backend UseChatBackend, toolCall uctypes.WaveToolCa } // ToolVerifyInput can modify the toolusedata. re-send it here. _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, *toolCall.ToolUseData) - updateToolUseDataInChat(backend, chatOpts, toolCall.ID, toolCall.ToolUseData) + updateToolUseDataInChat(backend, chatOpts, toolCall.ID, *toolCall.ToolUseData) } if toolCall.ToolUseData.Approval == uctypes.ApprovalNeedsApproval { @@ -305,7 +260,7 @@ func processToolCallInternal(backend UseChatBackend, toolCall uctypes.WaveToolCa // this still happens here because we need to update the FE to say the tool call was approved _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, *toolCall.ToolUseData) - updateToolUseDataInChat(backend, chatOpts, toolCall.ID, toolCall.ToolUseData) + updateToolUseDataInChat(backend, chatOpts, toolCall.ID, *toolCall.ToolUseData) } toolCall.ToolUseData.RunTs = time.Now().UnixMilli() @@ -341,7 +296,7 @@ func processToolCall(backend UseChatBackend, toolCall uctypes.WaveToolCall, chat if toolCall.ToolUseData != nil { _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, *toolCall.ToolUseData) - updateToolUseDataInChat(backend, chatOpts, toolCall.ID, toolCall.ToolUseData) + updateToolUseDataInChat(backend, chatOpts, toolCall.ID, *toolCall.ToolUseData) } return result @@ -353,17 +308,27 @@ func processToolCalls(backend UseChatBackend, stopReason *uctypes.WaveStopReason defer activeToolMap.Delete(toolCall.ID) } - // Send all data-tooluse packets at the beginning - for _, toolCall := range stopReason.ToolCalls { - if toolCall.ToolUseData != nil { - log.Printf("AI data-tooluse %s\n", toolCall.ID) - _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, *toolCall.ToolUseData) - updateToolUseDataInChat(backend, chatOpts, toolCall.ID, toolCall.ToolUseData) - if toolCall.ToolUseData.Approval == uctypes.ApprovalNeedsApproval && chatOpts.RegisterToolApproval != nil { - chatOpts.RegisterToolApproval(toolCall.ID) + // Create and send all data-tooluse packets at the beginning + for i := range stopReason.ToolCalls { + toolCall := &stopReason.ToolCalls[i] + // Create toolUseData from the tool call input + var argsJSON string + if toolCall.Input != nil { + argsBytes, err := json.Marshal(toolCall.Input) + if err == nil { + argsJSON = string(argsBytes) } } + toolUseData := aiutil.CreateToolUseData(toolCall.ID, toolCall.Name, argsJSON, chatOpts) + stopReason.ToolCalls[i].ToolUseData = &toolUseData + log.Printf("AI data-tooluse %s\n", toolCall.ID) + _ = sseHandler.AiMsgData("data-tooluse", toolCall.ID, toolUseData) + updateToolUseDataInChat(backend, chatOpts, toolCall.ID, toolUseData) + if toolUseData.Approval == uctypes.ApprovalNeedsApproval && chatOpts.RegisterToolApproval != nil { + chatOpts.RegisterToolApproval(toolCall.ID) + } } + // At this point, all ToolCalls are guaranteed to have non-nil ToolUseData var toolResults []uctypes.AIToolResult for _, toolCall := range stopReason.ToolCalls { @@ -389,8 +354,8 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha stepNum := chatstore.DefaultChatStore.CountUserMessages(chatOpts.ChatId) metrics := &uctypes.AIMetrics{ - ChatId: chatOpts.ChatId, - StepNum: stepNum, + ChatId: chatOpts.ChatId, + StepNum: stepNum, Usage: uctypes.AIUsage{ APIType: chatOpts.Config.APIType, Model: chatOpts.Config.Model, @@ -398,7 +363,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha WidgetAccess: chatOpts.WidgetAccess, ToolDetail: make(map[string]int), ThinkingLevel: chatOpts.Config.ThinkingLevel, - ThinkingMode: chatOpts.Config.ThinkingMode, + AIMode: chatOpts.Config.AIMode, } firstStep := true var cont *uctypes.WaveContinueResponse @@ -419,7 +384,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha chatOpts.PlatformInfo = platformInfo } } - stopReason, rtnMessage, err := runAIChatStep(ctx, sseHandler, backend, chatOpts, cont) + stopReason, rtnMessages, err := runAIChatStep(ctx, sseHandler, backend, chatOpts, cont) metrics.RequestCount++ if chatOpts.Config.IsPremiumModel() { metrics.PremiumReqCount++ @@ -427,8 +392,8 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha if chatOpts.Config.IsWaveProxy() { metrics.ProxyReqCount++ } - if len(rtnMessage) > 0 { - usage := getUsage(rtnMessage) + if len(rtnMessages) > 0 { + usage := getUsage(rtnMessages) log.Printf("usage: input=%d output=%d websearch=%d\n", usage.InputTokens, usage.OutputTokens, usage.NativeWebSearchCount) metrics.Usage.InputTokens += usage.InputTokens metrics.Usage.OutputTokens += usage.OutputTokens @@ -447,14 +412,14 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha _ = sseHandler.AiMsgFinish("", nil) break } - for _, msg := range rtnMessage { + for _, msg := range rtnMessages { if msg != nil { chatstore.DefaultChatStore.PostMessage(chatOpts.ChatId, &chatOpts.Config, msg) } } firstStep = false - if stopReason != nil && stopReason.Kind == uctypes.StopKindPremiumRateLimit && chatOpts.Config.APIType == APIType_OpenAI && chatOpts.Config.Model == uctypes.PremiumOpenAIModel { - log.Printf("Premium rate limit hit with gpt-5.1, switching to gpt-5-mini\n") + if stopReason != nil && stopReason.Kind == uctypes.StopKindPremiumRateLimit && chatOpts.Config.APIType == uctypes.APIType_OpenAIResponses && chatOpts.Config.Model == uctypes.PremiumOpenAIModel { + log.Printf("Premium rate limit hit with %s, switching to %s\n", uctypes.PremiumOpenAIModel, uctypes.DefaultOpenAIModel) cont = &uctypes.WaveContinueResponse{ Model: uctypes.DefaultOpenAIModel, ContinueFromKind: uctypes.StopKindPremiumRateLimit, @@ -597,7 +562,7 @@ func sendAIMetricsTelemetry(ctx context.Context, metrics *uctypes.AIMetrics) { WaveAIRequestDurMs: metrics.RequestDuration, WaveAIWidgetAccess: metrics.WidgetAccess, WaveAIThinkingLevel: metrics.ThinkingLevel, - WaveAIThinkingMode: metrics.ThinkingMode, + WaveAIMode: metrics.AIMode, }) _ = telemetry.RecordTEvent(ctx, event) } @@ -645,11 +610,14 @@ func WaveAIPostMessageHandler(w http.ResponseWriter, r *http.Request) { oref := waveobj.MakeORef(waveobj.OType_Builder, req.BuilderId) rtInfo = wstore.GetRTInfo(oref) } + if rtInfo == nil { + rtInfo = &waveobj.ObjRTInfo{} + } // Get WaveAI settings premium := shouldUsePremium() builderMode := req.BuilderId != "" - aiOpts, err := getWaveAISettings(premium, builderMode, rtInfo) + aiOpts, err := getWaveAISettings(premium, builderMode, *rtInfo) if err != nil { http.Error(w, fmt.Sprintf("WaveAI configuration error: %v", err), http.StatusInternalServerError) return @@ -673,15 +641,7 @@ func WaveAIPostMessageHandler(w http.ResponseWriter, r *http.Request) { BuilderId: req.BuilderId, BuilderAppId: req.BuilderAppId, } - if chatOpts.Config.APIType == APIType_OpenAI { - if chatOpts.BuilderId != "" { - chatOpts.SystemPrompt = []string{} - } else { - chatOpts.SystemPrompt = []string{SystemPromptText_OpenAI} - } - } else { - chatOpts.SystemPrompt = []string{SystemPromptText} - } + chatOpts.SystemPrompt = getSystemPrompt(chatOpts.Config.APIType, chatOpts.Config.Model, chatOpts.BuilderId != "") if req.TabId != "" { chatOpts.TabStateGenerator = func() (string, []uctypes.ToolDefinition, string, error) { diff --git a/pkg/telemetry/telemetrydata/telemetrydata.go b/pkg/telemetry/telemetrydata/telemetrydata.go index 79ec3d6941..7dd7bffdb9 100644 --- a/pkg/telemetry/telemetrydata/telemetrydata.go +++ b/pkg/telemetry/telemetrydata/telemetrydata.go @@ -147,7 +147,7 @@ type TEventProps struct { WaveAIRequestDurMs int `json:"waveai:requestdurms,omitempty"` // ms WaveAIWidgetAccess bool `json:"waveai:widgetaccess,omitempty"` WaveAIThinkingLevel string `json:"waveai:thinkinglevel,omitempty"` - WaveAIThinkingMode string `json:"waveai:thinkingmode,omitempty"` + WaveAIMode string `json:"waveai:mode,omitempty"` WaveAIFeedback string `json:"waveai:feedback,omitempty" tstype:"\"good\" | \"bad\""` WaveAIAction string `json:"waveai:action,omitempty"` diff --git a/pkg/waveobj/objrtinfo.go b/pkg/waveobj/objrtinfo.go index ff88f7090c..77dadf9985 100644 --- a/pkg/waveobj/objrtinfo.go +++ b/pkg/waveobj/objrtinfo.go @@ -22,6 +22,6 @@ type ObjRTInfo struct { BuilderEnv map[string]string `json:"builder:env,omitempty"` WaveAIChatId string `json:"waveai:chatid,omitempty"` - WaveAIThinkingMode string `json:"waveai:thinkingmode,omitempty"` + WaveAIMode string `json:"waveai:mode,omitempty"` WaveAIMaxOutputTokens int `json:"waveai:maxoutputtokens,omitempty"` } diff --git a/pkg/wconfig/defaultconfig/waveai.json b/pkg/wconfig/defaultconfig/waveai.json new file mode 100644 index 0000000000..03e51f3e64 --- /dev/null +++ b/pkg/wconfig/defaultconfig/waveai.json @@ -0,0 +1,41 @@ +{ + "waveai@quick": { + "display:name": "Quick", + "display:order": -3, + "display:icon": "bolt", + "display:shortdesc": "gpt-5-mini", + "display:description": "Fastest responses (gpt-5-mini)", + "ai:apitype": "openai-responses", + "ai:model": "gpt-5-mini", + "ai:thinkinglevel": "low", + "ai:capabilities": ["tools", "images", "pdfs"], + "waveai:cloud": true, + "waveai:premium": false + }, + "waveai@balanced": { + "display:name": "Balanced", + "display:order": -2, + "display:icon": "sparkles", + "display:shortdesc": "gpt-5.1, low thinking", + "display:description": "Good mix of speed and accuracy\n(gpt-5.1 with minimal thinking)", + "ai:apitype": "openai-responses", + "ai:model": "gpt-5.1", + "ai:thinkinglevel": "low", + "ai:capabilities": ["tools", "images", "pdfs"], + "waveai:cloud": true, + "waveai:premium": true + }, + "waveai@deep": { + "display:name": "Deep", + "display:order": -1, + "display:icon": "lightbulb", + "display:shortdesc": "gpt-5.1, full thinking", + "display:description": "Slower but most capable\n(gpt-5.1 with full reasoning)", + "ai:apitype": "openai-responses", + "ai:model": "gpt-5.1", + "ai:thinkinglevel": "medium", + "ai:capabilities": ["tools", "images", "pdfs"], + "waveai:cloud": true, + "waveai:premium": true + } +} diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 4de30cbb6d..c493cf49d5 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -257,6 +257,24 @@ type WebBookmark struct { DisplayOrder float64 `json:"display:order,omitempty"` } +type AIModeConfigType struct { + DisplayName string `json:"display:name"` + DisplayOrder float64 `json:"display:order,omitempty"` + DisplayIcon string `json:"display:icon"` + DisplayShortDesc string `json:"display:shortdesc,omitempty"` + DisplayDescription string `json:"display:description"` + APIType string `json:"ai:apitype"` + Model string `json:"ai:model"` + ThinkingLevel string `json:"ai:thinkinglevel"` + BaseURL string `json:"ai:baseurl,omitempty"` + APIVersion string `json:"ai:apiversion,omitempty"` + APIToken string `json:"ai:apitoken,omitempty"` + APITokenSecretName string `json:"ai:apitokensecretname,omitempty"` + Capabilities []string `json:"ai:capabilities,omitempty"` + WaveAICloud bool `json:"waveai:cloud,omitempty"` + WaveAIPremium bool `json:"waveai:premium,omitempty"` +} + type FullConfigType struct { Settings SettingsType `json:"settings" merge:"meta"` MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` @@ -266,8 +284,10 @@ type FullConfigType struct { TermThemes map[string]TermThemeType `json:"termthemes"` Connections map[string]ConnKeywords `json:"connections"` Bookmarks map[string]WebBookmark `json:"bookmarks"` + WaveAIModes map[string]AIModeConfigType `json:"waveai"` ConfigErrors []ConfigError `json:"configerrors" configfile:"-"` } + type ConnKeywords struct { ConnWshEnabled *bool `json:"conn:wshenabled,omitempty"` ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"` diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index 131ac51e60..6f6c2afc7a 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -580,12 +580,10 @@ func (ws *WshServer) EventReadHistoryCommand(ctx context.Context, data wshrpc.Co } func (ws *WshServer) SetConfigCommand(ctx context.Context, data wshrpc.MetaSettingsType) error { - log.Printf("SETCONFIG: %v\n", data) return wconfig.SetBaseConfigValue(data.MetaMapType) } func (ws *WshServer) SetConnectionsConfigCommand(ctx context.Context, data wshrpc.ConnConfigRequest) error { - log.Printf("SET CONNECTIONS CONFIG: %v\n", data) return wconfig.SetConnectionsConfigValue(data.Host, data.MetaMapType) } From 12d1222187904bf8424b99ee5a172b9c34a27e36 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Wed, 26 Nov 2025 22:29:28 -0800 Subject: [PATCH 08/35] update readme (#2605) --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad49c7c2b2..da8224ef5a 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,27 @@ Modern development involves constantly switching between terminals and browsers - Flexible drag & drop interface to organize terminal blocks, editors, web browsers, and AI assistants - Built-in editor for seamlessly editing remote files with syntax highlighting and modern editor features - Rich file preview system for remote files (markdown, images, video, PDFs, CSVs, directories) -- Integrated AI chat with support for multiple models (OpenAI, Claude, Azure, Perplexity, Ollama) +- Quick full-screen toggle for any block - expand terminals, editors, and previews for better visibility, then instantly return to multi-block view +- Wave AI - Context-aware terminal assistant that reads your terminal output, analyzes widgets, and performs file operations +- AI chat widget with support for multiple models (OpenAI, Claude, Azure, Perplexity, Ollama) - Command Blocks for isolating and monitoring individual commands with auto-close options - One-click remote connections with full terminal and file system access +- Secure secret storage using native system backends - store API keys and credentials locally, access them across SSH sessions - Rich customization including tab themes, terminal styles, and background images - Powerful `wsh` command system for managing your workspace from the CLI and sharing data between terminal sessions +- Connected file management with `wsh file` - seamlessly copy and sync files between local, remote SSH hosts, Wave filesystem, and S3 + +## Wave AI + +Wave AI is your context-aware terminal assistant with access to your workspace: + +- **Terminal Context**: Reads terminal output and scrollback for debugging and analysis +- **File Operations**: Read, write, and edit files with automatic backups and user approval +- **CLI Integration**: Use `wsh ai` to pipe output or attach files directly from the command line +- **Free Beta**: Included AI credits while we refine the experience +- **Coming Soon**: Command execution (with approval), local model support, and alternate AI providers (BYOK) + +Learn more in our [Wave AI documentation](https://docs.waveterm.dev/waveai). ## Installation From d1ebcc9d07fef631cdc660aafe980820ae2f9277 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Fri, 28 Nov 2025 11:56:59 -0800 Subject: [PATCH 09/35] new waveconfig widget, consolidate config/help in widget sidebar (#2604) --- aiprompts/tailwind-container-queries.md | 38 ++- cmd/generateschema/main-generateschema.go | 14 + cmd/wsh/cmd/wshcmd-editconfig.go | 17 +- frontend/app/block/block.tsx | 2 + .../app/view/codeeditor/schemaendpoints.ts | 2 + frontend/app/view/secretstore/secretstore.tsx | 22 +- .../app/view/waveconfig/waveconfig-model.ts | 312 ++++++++++++++++++ frontend/app/view/waveconfig/waveconfig.tsx | 252 ++++++++++++++ frontend/app/workspace/widgets.tsx | 259 ++++++++++----- frontend/tailwindsetup.css | 1 + frontend/types/gotypes.d.ts | 2 +- pkg/wconfig/settingsconfig.go | 11 + schema/bgpresets.json | 46 +++ schema/waveai.json | 71 ++++ 14 files changed, 934 insertions(+), 115 deletions(-) create mode 100644 frontend/app/view/waveconfig/waveconfig-model.ts create mode 100644 frontend/app/view/waveconfig/waveconfig.tsx create mode 100644 schema/bgpresets.json create mode 100644 schema/waveai.json diff --git a/aiprompts/tailwind-container-queries.md b/aiprompts/tailwind-container-queries.md index 007cc080cf..646bf970bb 100644 --- a/aiprompts/tailwind-container-queries.md +++ b/aiprompts/tailwind-container-queries.md @@ -19,20 +19,52 @@ In v3: install `@tailwindcss/container-queries`. - `@container` marks the parent. - `@sm:` / `@md:` refer to **container width**, not viewport. +#### Max-Width Container Queries + +For max-width queries, use `@max-` prefix: + +```html +
+ +
Only on containers < sm
+ + +
+ Fixed overlay on small, normal on large +
+
+``` + +- `@max-sm:` = max-width query (container **below** sm breakpoint) +- `@sm:` = min-width query (container **at or above** sm breakpoint) + +**IMPORTANT**: The syntax is `@max-w600:` NOT `max-@w600:` (prefix comes before the @) + #### Notes - Based on native CSS container queries (well supported in modern browsers). - Breakpoints for container queries reuse Tailwind’s `sm`, `md`, `lg`, etc. scales. - Safe for modern webapps; no IE/legacy support. -we have special breakpoints set up for panels: +We have special breakpoints set up for panels: + --container-w600: 600px; + --container-w450: 450px; --container-xs: 300px; --container-xxs: 200px; --container-tiny: 120px; since often sm, md, and lg are too big for panels. -so to use you'd do: +Usage examples: + +```html + +
-@xs:ml-4 + +
+ + +
+``` diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go index aa16eeb960..5480f3dd5d 100644 --- a/cmd/generateschema/main-generateschema.go +++ b/cmd/generateschema/main-generateschema.go @@ -18,6 +18,8 @@ const WaveSchemaSettingsFileName = "schema/settings.json" const WaveSchemaConnectionsFileName = "schema/connections.json" const WaveSchemaAiPresetsFileName = "schema/aipresets.json" const WaveSchemaWidgetsFileName = "schema/widgets.json" +const WaveSchemaBgPresetsFileName = "schema/bgpresets.json" +const WaveSchemaWaveAIFileName = "schema/waveai.json" func generateSchema(template any, dir string) error { settingsSchema := jsonschema.Reflect(template) @@ -59,4 +61,16 @@ func main() { if err != nil { log.Fatalf("widgets schema error: %v", err) } + + bgPresetsTemplate := make(map[string]wconfig.BgPresetsType) + err = generateSchema(&bgPresetsTemplate, WaveSchemaBgPresetsFileName) + if err != nil { + log.Fatalf("bg presets schema error: %v", err) + } + + waveAITemplate := make(map[string]wconfig.AIModeConfigType) + err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName) + if err != nil { + log.Fatalf("waveai schema error: %v", err) + } } diff --git a/cmd/wsh/cmd/wshcmd-editconfig.go b/cmd/wsh/cmd/wshcmd-editconfig.go index 2adf1b7647..5f2153dd77 100644 --- a/cmd/wsh/cmd/wshcmd-editconfig.go +++ b/cmd/wsh/cmd/wshcmd-editconfig.go @@ -5,12 +5,10 @@ package cmd import ( "fmt" - "path/filepath" "github.com/spf13/cobra" "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) var editConfigMagnified bool @@ -34,32 +32,23 @@ func editConfigRun(cmd *cobra.Command, args []string) (rtnErr error) { sendActivity("editconfig", rtnErr == nil) }() - // Get config directory from Wave info - resp, err := wshclient.WaveInfoCommand(RpcClient, &wshrpc.RpcOpts{Timeout: 2000}) - if err != nil { - return fmt.Errorf("getting Wave info: %w", err) - } - configFile := "settings.json" // default if len(args) > 0 { configFile = args[0] } - settingsFile := filepath.Join(resp.ConfigDir, configFile) - wshCmd := &wshrpc.CommandCreateBlockData{ BlockDef: &waveobj.BlockDef{ Meta: map[string]interface{}{ - waveobj.MetaKey_View: "preview", - waveobj.MetaKey_File: settingsFile, - waveobj.MetaKey_Edit: true, + waveobj.MetaKey_View: "waveconfig", + waveobj.MetaKey_File: configFile, }, }, Magnified: editConfigMagnified, Focused: true, } - _, err = RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) + _, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { return fmt.Errorf("opening config file: %w", err) } diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index d8260965d4..7b307b9f33 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -36,6 +36,7 @@ import clsx from "clsx"; import { atom, useAtomValue } from "jotai"; import { memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { QuickTipsViewModel } from "../view/quicktipsview/quicktipsview"; +import { WaveConfigViewModel } from "../view/waveconfig/waveconfig-model"; import "./block.scss"; import { BlockFrame } from "./blockframe"; import { blockViewToIcon, blockViewToName } from "./blockutil"; @@ -54,6 +55,7 @@ BlockRegistry.set("launcher", LauncherViewModel); BlockRegistry.set("tsunami", TsunamiViewModel); BlockRegistry.set("aifilediff", AiFileDiffViewModel); BlockRegistry.set("secretstore", SecretStoreViewModel); +BlockRegistry.set("waveconfig", WaveConfigViewModel); function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel { const ctor = BlockRegistry.get(blockView); diff --git a/frontend/app/view/codeeditor/schemaendpoints.ts b/frontend/app/view/codeeditor/schemaendpoints.ts index 5056f7121e..0f79837eb1 100644 --- a/frontend/app/view/codeeditor/schemaendpoints.ts +++ b/frontend/app/view/codeeditor/schemaendpoints.ts @@ -37,6 +37,8 @@ const allFilepaths: Map> = new Map(); allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, makeConfigPathMatches("/settings.json")); allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, makeConfigPathMatches("/connections.json")); allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, makeConfigPathMatches("/presets/ai.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/bgpresets.json`, makeConfigPathMatches("/presets/bg.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/waveai.json`, makeConfigPathMatches("/waveai.json")); allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, makeConfigPathMatches("/widgets.json")); async function getSchemaEndpointInfo(endpoint: string): Promise { diff --git a/frontend/app/view/secretstore/secretstore.tsx b/frontend/app/view/secretstore/secretstore.tsx index 83e32a14fe..4368f33d37 100644 --- a/frontend/app/view/secretstore/secretstore.tsx +++ b/frontend/app/view/secretstore/secretstore.tsx @@ -40,7 +40,7 @@ LoadingSpinner.displayName = "LoadingSpinner"; const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { return ( -
+

No Secrets

Add a secret to get started

@@ -83,7 +83,7 @@ interface SecretListViewProps { const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: SecretListViewProps) => { return ( -
+

Secrets

{secretNames.length} @@ -92,7 +92,7 @@ const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: Secre {secretNames.map((name) => (
onSelectSecret(name)} > @@ -102,7 +102,7 @@ const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: Secre ))}
@@ -140,7 +140,7 @@ const AddSecretForm = memo( const isNameInvalid = newSecretName !== "" && !secretNameRegex.test(newSecretName); return ( -
+

Add New Secret

@@ -217,7 +217,7 @@ const SecretDetailView = memo(({ model }: SecretDetailViewProps) => { } return ( -
+

{secretName}

@@ -326,7 +326,9 @@ export const SecretStoreView = memo(({ model }: { blockId: string; model: Secret if (storageBackendError) { return (
- +
+ +
); } @@ -334,7 +336,9 @@ export const SecretStoreView = memo(({ model }: { blockId: string; model: Secret if (isLoading && secretNames.length === 0 && !selectedSecret) { return (
- +
+ +
); } @@ -374,7 +378,7 @@ export const SecretStoreView = memo(({ model }: { blockId: string; model: Secret return (
{errorMessage && ( -
+
)} diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts new file mode 100644 index 0000000000..bd5536b299 --- /dev/null +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -0,0 +1,312 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { BlockNodeModel } from "@/app/block/blocktypes"; +import { getApi, getBlockMetaKeyAtom, WOS } from "@/app/store/global"; +import { globalStore } from "@/app/store/jotaiStore"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { WaveConfigView } from "@/app/view/waveconfig/waveconfig"; +import { base64ToString, stringToBase64 } from "@/util/util"; +import { atom, type PrimitiveAtom } from "jotai"; +import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; +import * as React from "react"; + +type ValidationResult = { success: true } | { error: string }; +type ConfigValidator = (parsed: any) => ValidationResult; + +export type ConfigFile = { + name: string; + path: string; + language: string; + deprecated?: boolean; + docsUrl?: string; + validator?: ConfigValidator; +}; + +function validateBgJson(parsed: any): ValidationResult { + const keys = Object.keys(parsed); + for (const key of keys) { + if (!key.startsWith("bg@")) { + return { error: `Invalid key "${key}": all top-level keys must start with "bg@"` }; + } + } + return { success: true }; +} + +function validateAiJson(parsed: any): ValidationResult { + const keys = Object.keys(parsed); + for (const key of keys) { + if (!key.startsWith("ai@")) { + return { error: `Invalid key "${key}": all top-level keys must start with "ai@"` }; + } + } + return { success: true }; +} + +function validateWaveAiJson(parsed: any): ValidationResult { + const keys = Object.keys(parsed); + const keyPattern = /^[a-zA-Z0-9_@-]+$/; + for (const key of keys) { + if (!keyPattern.test(key)) { + return { + error: `Invalid key "${key}": keys must only contain letters, numbers, underscores, @ and hyphens`, + }; + } + } + return { success: true }; +} + +const configFiles: ConfigFile[] = [ + { + name: "General", + path: "settings.json", + language: "json", + docsUrl: "https://docs.waveterm.dev/config", + }, + { + name: "Connections", + path: "connections.json", + language: "json", + docsUrl: "https://docs.waveterm.dev/connections", + }, + { + name: "Widgets", + path: "widgets.json", + language: "json", + docsUrl: "https://docs.waveterm.dev/customwidgets", + }, + { + name: "Wave AI", + path: "waveai.json", + language: "json", + validator: validateWaveAiJson, + }, + { + name: "Backgrounds", + path: "presets/bg.json", + language: "json", + docsUrl: "https://docs.waveterm.dev/presets#background-configurations", + validator: validateBgJson, + }, +]; + +const deprecatedConfigFiles: ConfigFile[] = [ + { + name: "Presets", + path: "presets.json", + language: "json", + deprecated: true, + }, + { + name: "AI Presets", + path: "presets/ai.json", + language: "json", + deprecated: true, + docsUrl: "https://docs.waveterm.dev/ai-presets", + validator: validateAiJson, + }, +]; + +export class WaveConfigViewModel implements ViewModel { + blockId: string; + viewType = "waveconfig"; + viewIcon = atom("gear"); + viewName = atom("Wave Config"); + viewComponent = WaveConfigView; + noPadding = atom(true); + nodeModel: BlockNodeModel; + + selectedFileAtom: PrimitiveAtom; + fileContentAtom: PrimitiveAtom; + originalContentAtom: PrimitiveAtom; + hasEditedAtom: PrimitiveAtom; + isLoadingAtom: PrimitiveAtom; + isSavingAtom: PrimitiveAtom; + errorMessageAtom: PrimitiveAtom; + validationErrorAtom: PrimitiveAtom; + isMenuOpenAtom: PrimitiveAtom; + presetsJsonExistsAtom: PrimitiveAtom; + configDir: string; + saveShortcut: string; + editorRef: React.RefObject; + + constructor(blockId: string, nodeModel: BlockNodeModel) { + this.blockId = blockId; + this.nodeModel = nodeModel; + this.configDir = getApi().getConfigDir(); + const platform = getApi().getPlatform(); + this.saveShortcut = platform === "darwin" ? "Cmd+S" : "Alt+S"; + + this.selectedFileAtom = atom(null) as PrimitiveAtom; + this.fileContentAtom = atom(""); + this.originalContentAtom = atom(""); + this.hasEditedAtom = atom(false); + this.isLoadingAtom = atom(false); + this.isSavingAtom = atom(false); + this.errorMessageAtom = atom(null) as PrimitiveAtom; + this.validationErrorAtom = atom(null) as PrimitiveAtom; + this.isMenuOpenAtom = atom(false); + this.presetsJsonExistsAtom = atom(false); + this.editorRef = React.createRef(); + + this.checkPresetsJsonExists(); + this.initialize(); + } + + async checkPresetsJsonExists() { + try { + const fullPath = `${this.configDir}/presets.json`; + const fileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { + info: { path: fullPath }, + }); + if (!fileInfo.notfound) { + globalStore.set(this.presetsJsonExistsAtom, true); + } + } catch { + // File doesn't exist + } + } + + initialize() { + const selectedFile = globalStore.get(this.selectedFileAtom); + if (!selectedFile) { + const metaFileAtom = getBlockMetaKeyAtom(this.blockId, "file"); + const savedFilePath = globalStore.get(metaFileAtom); + + let fileToLoad: ConfigFile | null = null; + if (savedFilePath) { + fileToLoad = + configFiles.find((f) => f.path === savedFilePath) || + deprecatedConfigFiles.find((f) => f.path === savedFilePath) || + null; + } + + if (!fileToLoad) { + fileToLoad = configFiles[0]; + } + + if (fileToLoad) { + this.loadFile(fileToLoad); + } + } + } + + getConfigFiles(): ConfigFile[] { + return configFiles; + } + + getDeprecatedConfigFiles(): ConfigFile[] { + const presetsJsonExists = globalStore.get(this.presetsJsonExistsAtom); + return deprecatedConfigFiles.filter((f) => { + if (f.path === "presets.json") { + return presetsJsonExists; + } + return true; + }); + } + + hasChanges(): boolean { + return globalStore.get(this.hasEditedAtom); + } + + markAsEdited() { + globalStore.set(this.hasEditedAtom, true); + } + + async loadFile(file: ConfigFile) { + globalStore.set(this.isLoadingAtom, true); + globalStore.set(this.errorMessageAtom, null); + globalStore.set(this.hasEditedAtom, false); + try { + const fullPath = `${this.configDir}/${file.path}`; + const fileData = await RpcApi.FileReadCommand(TabRpcClient, { + info: { path: fullPath }, + }); + const content = fileData?.data64 ? base64ToString(fileData.data64) : ""; + globalStore.set(this.originalContentAtom, content); + if (content.trim() === "") { + globalStore.set(this.fileContentAtom, "{\n\n}"); + } else { + globalStore.set(this.fileContentAtom, content); + } + globalStore.set(this.selectedFileAtom, file); + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { file: file.path }, + }); + } catch (err) { + globalStore.set(this.errorMessageAtom, `Failed to load ${file.name}: ${err.message || String(err)}`); + globalStore.set(this.fileContentAtom, ""); + globalStore.set(this.originalContentAtom, ""); + } finally { + globalStore.set(this.isLoadingAtom, false); + } + } + + async saveFile() { + const selectedFile = globalStore.get(this.selectedFileAtom); + if (!selectedFile) return; + + const fileContent = globalStore.get(this.fileContentAtom); + + try { + const parsed = JSON.parse(fileContent); + + if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) { + globalStore.set(this.validationErrorAtom, "JSON must be an object, not an array, primitive, or null"); + return; + } + + if (selectedFile.validator) { + const validationResult = selectedFile.validator(parsed); + if ("error" in validationResult) { + globalStore.set(this.validationErrorAtom, validationResult.error); + return; + } + } + + const formatted = JSON.stringify(parsed, null, 2); + + globalStore.set(this.isSavingAtom, true); + globalStore.set(this.errorMessageAtom, null); + globalStore.set(this.validationErrorAtom, null); + + try { + const fullPath = `${this.configDir}/${selectedFile.path}`; + await RpcApi.FileWriteCommand(TabRpcClient, { + info: { path: fullPath }, + data64: stringToBase64(formatted), + }); + globalStore.set(this.fileContentAtom, formatted); + globalStore.set(this.originalContentAtom, formatted); + globalStore.set(this.hasEditedAtom, false); + } catch (err) { + globalStore.set( + this.errorMessageAtom, + `Failed to save ${selectedFile.name}: ${err.message || String(err)}` + ); + } finally { + globalStore.set(this.isSavingAtom, false); + } + } catch (err) { + globalStore.set(this.validationErrorAtom, `Invalid JSON: ${err.message || String(err)}`); + } + } + + clearError() { + globalStore.set(this.errorMessageAtom, null); + } + + clearValidationError() { + globalStore.set(this.validationErrorAtom, null); + } + + giveFocus(): boolean { + if (this.editorRef?.current) { + this.editorRef.current.focus(); + return true; + } + return false; + } +} diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx new file mode 100644 index 0000000000..1465684c58 --- /dev/null +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -0,0 +1,252 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Tooltip } from "@/app/element/tooltip"; +import { globalStore } from "@/app/store/jotaiStore"; +import { CodeEditor } from "@/app/view/codeeditor/codeeditor"; +import type { ConfigFile, WaveConfigViewModel } from "@/app/view/waveconfig/waveconfig-model"; +import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; +import { useAtom, useAtomValue } from "jotai"; +import { memo, useCallback, useEffect, useRef } from "react"; +import { debounce } from "throttle-debounce"; + +interface ConfigSidebarProps { + model: WaveConfigViewModel; +} + +const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => { + const selectedFile = useAtomValue(model.selectedFileAtom); + const [isMenuOpen, setIsMenuOpen] = useAtom(model.isMenuOpenAtom); + const configFiles = model.getConfigFiles(); + const deprecatedConfigFiles = model.getDeprecatedConfigFiles(); + + const handleFileSelect = (file: ConfigFile) => { + model.loadFile(file); + setIsMenuOpen(false); + }; + + return ( +
+
+ Config Files + +
+ {configFiles.map((file) => ( +
handleFileSelect(file)} + className={`px-4 py-2 border-b border-border cursor-pointer transition-colors whitespace-nowrap overflow-hidden text-ellipsis ${ + selectedFile?.path === file.path ? "bg-accentbg text-primary" : "hover:bg-secondary/50" + }`} + > + {file.name} +
+ ))} + {deprecatedConfigFiles.length > 0 && ( + <> + {deprecatedConfigFiles.map((file) => ( +
handleFileSelect(file)} + className={`px-4 py-2 border-b border-border cursor-pointer transition-colors ${ + selectedFile?.path === file.path ? "bg-accentbg text-primary" : "hover:bg-secondary/50" + }`} + > +
+ {file.name} + + deprecated + +
+
+ ))} + + )} +
+ ); +}); + +ConfigSidebar.displayName = "ConfigSidebar"; + +const WaveConfigView = memo(({ blockId, model }: ViewComponentProps) => { + const selectedFile = useAtomValue(model.selectedFileAtom); + const [fileContent, setFileContent] = useAtom(model.fileContentAtom); + const isLoading = useAtomValue(model.isLoadingAtom); + const isSaving = useAtomValue(model.isSavingAtom); + const errorMessage = useAtomValue(model.errorMessageAtom); + const validationError = useAtomValue(model.validationErrorAtom); + const [isMenuOpen, setIsMenuOpen] = useAtom(model.isMenuOpenAtom); + const hasChanges = useAtomValue(model.hasEditedAtom); + const editorContainerRef = useRef(null); + + const handleContentChange = useCallback( + (newContent: string) => { + setFileContent(newContent); + model.markAsEdited(); + }, + [setFileContent, model] + ); + + const handleEditorMount = useCallback( + (editor) => { + model.editorRef.current = editor; + const isFocused = globalStore.get(model.nodeModel.isFocused); + if (isFocused) { + editor.focus(); + } + return () => { + model.editorRef.current = null; + }; + }, + [model] + ); + + useEffect(() => { + const handleKeyDown = keydownWrapper((e: WaveKeyboardEvent) => { + if (checkKeyPressed(e, "Cmd:s")) { + if (hasChanges && !isSaving) { + model.saveFile(); + } + return true; + } + return false; + }); + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [hasChanges, isSaving, model]); + + useEffect(() => { + if (!editorContainerRef.current) { + return; + } + const debouncedLayout = debounce(100, () => { + if (model.editorRef.current) { + model.editorRef.current.layout(); + } + }); + const resizeObserver = new ResizeObserver(debouncedLayout); + resizeObserver.observe(editorContainerRef.current); + return () => resizeObserver.disconnect(); + }, [model]); + + const saveTooltip = `Save (${model.saveShortcut})`; + + return ( +
+ {isMenuOpen && ( +
setIsMenuOpen(false)} /> + )} +
+ +
+
+ {selectedFile && ( + <> +
+
+ +
+ {selectedFile.name} +
+ {selectedFile.docsUrl && ( + + + + )} +
+ {selectedFile.path} +
+
+
+ {hasChanges && ( + + Unsaved changes + + )} + + + +
+
+ {errorMessage && ( +
+ {errorMessage} + +
+ )} + {validationError && ( +
+ {validationError} + +
+ )} +
+ {isLoading ? ( +
+ Loading... +
+ ) : ( + + )} +
+ + )} +
+
+ ); +}); + +WaveConfigView.displayName = "WaveConfigView"; + +export { WaveConfigView }; diff --git a/frontend/app/workspace/widgets.tsx b/frontend/app/workspace/widgets.tsx index 100c9f6869..5cf95abe4f 100644 --- a/frontend/app/workspace/widgets.tsx +++ b/frontend/app/workspace/widgets.tsx @@ -202,6 +202,115 @@ const AppsFloatingWindow = memo( } ); +const SettingsFloatingWindow = memo( + ({ + isOpen, + onClose, + referenceElement, + }: { + isOpen: boolean; + onClose: () => void; + referenceElement: HTMLElement; + }) => { + const { refs, floatingStyles, context } = useFloating({ + open: isOpen, + onOpenChange: onClose, + placement: "left-start", + middleware: [offset(-2), shift({ padding: 12 })], + whileElementsMounted: autoUpdate, + elements: { + reference: referenceElement, + }, + }); + + const dismiss = useDismiss(context); + const { getFloatingProps } = useInteractions([dismiss]); + + if (!isOpen) return null; + + const menuItems = [ + { + icon: "gear", + label: "Settings", + onClick: () => { + const blockDef: BlockDef = { + meta: { + view: "waveconfig", + }, + }; + createBlock(blockDef, false, true); + onClose(); + }, + }, + { + icon: "lightbulb", + label: "Tips", + onClick: () => { + const blockDef: BlockDef = { + meta: { + view: "tips", + }, + }; + createBlock(blockDef, true, true); + onClose(); + }, + }, + { + icon: "lock", + label: "Secrets", + onClick: () => { + const blockDef: BlockDef = { + meta: { + view: "secretstore", + }, + }; + createBlock(blockDef, false, true); + onClose(); + }, + }, + { + icon: "circle-question", + label: "Help", + onClick: () => { + const blockDef: BlockDef = { + meta: { + view: "help", + }, + }; + createBlock(blockDef); + onClose(); + }, + }, + ]; + + return ( + +
+ {menuItems.map((item, idx) => ( +
+
+ +
+
{item.label}
+
+ ))} +
+
+ ); + } +); + +SettingsFloatingWindow.displayName = "SettingsFloatingWindow"; + const Widgets = memo(() => { const fullConfig = useAtomValue(atoms.fullConfigAtom); const hasCustomAIPresets = useAtomValue(atoms.hasCustomAIPresetsAtom); @@ -209,26 +318,6 @@ const Widgets = memo(() => { const containerRef = useRef(null); const measurementRef = useRef(null); - const helpWidget: WidgetConfigType = { - icon: "circle-question", - label: "help", - blockdef: { - meta: { - view: "help", - }, - }, - }; - const tipsWidget: WidgetConfigType = { - icon: "lightbulb", - label: "tips", - blockdef: { - meta: { - view: "tips", - }, - }, - magnified: true, - }; - const showHelp = fullConfig?.settings?.["widget:showhelp"] ?? true; const featureWaveAppBuilder = fullConfig?.settings?.["feature:waveappbuilder"] ?? false; const widgetsMap = fullConfig?.widgets ?? {}; const filteredWidgets = hasCustomAIPresets @@ -238,6 +327,8 @@ const Widgets = memo(() => { const [isAppsOpen, setIsAppsOpen] = useState(false); const appsButtonRef = useRef(null); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const settingsButtonRef = useRef(null); const checkModeNeeded = useCallback(() => { if (!containerRef.current || !measurementRef.current) return; @@ -252,7 +343,7 @@ const Widgets = memo(() => { newMode = "compact"; // Calculate total widget count for supercompact check - const totalWidgets = (widgets?.length || 0) + (showHelp ? 2 : 0); + const totalWidgets = (widgets?.length || 0) + 1; const minHeightPerWidget = 32; const requiredHeight = totalWidgets * minHeightPerWidget; @@ -264,7 +355,7 @@ const Widgets = memo(() => { if (newMode !== mode) { setMode(newMode); } - }, [mode, widgets, showHelp]); + }, [mode, widgets]); useEffect(() => { const resizeObserver = new ResizeObserver(() => { @@ -282,7 +373,7 @@ const Widgets = memo(() => { useEffect(() => { checkModeNeeded(); - }, [widgets, showHelp, checkModeNeeded]); + }, [widgets, checkModeNeeded]); const handleWidgetsBarContextMenu = (e: React.MouseEvent) => { e.preventDefault(); @@ -299,31 +390,6 @@ const Widgets = memo(() => { }); }, }, - { - label: "Show Help Widgets", - submenu: [ - { - label: "On", - type: "checkbox", - checked: showHelp, - click: () => { - fireAndForget(async () => { - await RpcApi.SetConfigCommand(TabRpcClient, { "widget:showhelp": true }); - }); - }, - }, - { - label: "Off", - type: "checkbox", - checked: !showHelp, - click: () => { - fireAndForget(async () => { - await RpcApi.SetConfigCommand(TabRpcClient, { "widget:showhelp": false }); - }); - }, - }, - ], - }, ]; ContextMenuModel.showContextMenu(menu, e); }; @@ -343,29 +409,32 @@ const Widgets = memo(() => { ))}
- {isDev() || featureWaveAppBuilder || showHelp ? ( -
- {isDev() || featureWaveAppBuilder ? ( -
setIsAppsOpen(!isAppsOpen)} - > - -
- -
-
+
+ {isDev() || featureWaveAppBuilder ? ( +
setIsAppsOpen(!isAppsOpen)} + > + +
+ +
+
+
+ ) : null} +
setIsSettingsOpen(!isSettingsOpen)} + > + +
+
- ) : null} - {showHelp ? ( - <> - - - - ) : null} +
- ) : null} +
) : ( <> @@ -380,23 +449,30 @@ const Widgets = memo(() => { onClick={() => setIsAppsOpen(!isAppsOpen)} > -
- -
- {mode === "normal" && ( -
- apps +
+
+
- )} + {mode === "normal" && ( +
+ apps +
+ )} +
) : null} - {showHelp ? ( - <> - - - - ) : null} +
setIsSettingsOpen(!isSettingsOpen)} + > + +
+ +
+
+
)} {isDev() ? ( @@ -415,6 +491,13 @@ const Widgets = memo(() => { referenceElement={appsButtonRef.current} /> )} + {settingsButtonRef.current && ( + setIsSettingsOpen(false)} + referenceElement={settingsButtonRef.current} + /> + )}
{ ))}
- {showHelp ? ( - <> - - - - ) : null} +
+
+ +
+
settings
+
{isDev() ? (
diff --git a/frontend/tailwindsetup.css b/frontend/tailwindsetup.css index cf400b209c..7661b3f91a 100644 --- a/frontend/tailwindsetup.css +++ b/frontend/tailwindsetup.css @@ -65,6 +65,7 @@ --ansi-brightcyan: #b7b8cb; --ansi-brightwhite: #f0f0f0; + --container-w600: 600px; --container-w450: 450px; --container-xs: 300px; --container-xxs: 200px; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index d00e629b4a..4c30d15bba 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -29,7 +29,7 @@ declare global { "ai:apitokensecretname"?: string; "ai:capabilities"?: string[]; "waveai:cloud"?: boolean; - "waveai:premium": boolean; + "waveai:premium"?: boolean; }; // wshrpc.ActivityDisplayType diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index c493cf49d5..a034d42140 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -810,6 +810,17 @@ type WidgetConfigType struct { BlockDef waveobj.BlockDef `json:"blockdef"` } +type BgPresetsType struct { + BgClear bool `json:"bg:*,omitempty"` + Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"` + BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"` + BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"` + BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"` + BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"` + DisplayName string `json:"display:name,omitempty" jsonschema_description:"The name shown in the context menu"` + DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"` +} + type MimeTypeConfigType struct { Icon string `json:"icon"` Color string `json:"color"` diff --git a/schema/bgpresets.json b/schema/bgpresets.json new file mode 100644 index 0000000000..d9c9bf9e5c --- /dev/null +++ b/schema/bgpresets.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "BgPresetsType": { + "properties": { + "bg:*": { + "type": "boolean" + }, + "bg": { + "type": "string", + "description": "CSS background property value" + }, + "bg:opacity": { + "type": "number", + "description": "Background opacity (0.0-1.0)" + }, + "bg:blendmode": { + "type": "string", + "description": "CSS background-blend-mode property value" + }, + "bg:bordercolor": { + "type": "string", + "description": "Block frame border color" + }, + "bg:activebordercolor": { + "type": "string", + "description": "Block frame focused border color" + }, + "display:name": { + "type": "string", + "description": "The name shown in the context menu" + }, + "display:order": { + "type": "number", + "description": "Determines the order of the background in the context menu" + } + }, + "additionalProperties": false, + "type": "object" + } + }, + "additionalProperties": { + "$ref": "#/$defs/BgPresetsType" + }, + "type": "object" +} diff --git a/schema/waveai.json b/schema/waveai.json new file mode 100644 index 0000000000..1f598c6a4d --- /dev/null +++ b/schema/waveai.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "AIModeConfigType": { + "properties": { + "display:name": { + "type": "string" + }, + "display:order": { + "type": "number" + }, + "display:icon": { + "type": "string" + }, + "display:shortdesc": { + "type": "string" + }, + "display:description": { + "type": "string" + }, + "ai:apitype": { + "type": "string" + }, + "ai:model": { + "type": "string" + }, + "ai:thinkinglevel": { + "type": "string" + }, + "ai:baseurl": { + "type": "string" + }, + "ai:apiversion": { + "type": "string" + }, + "ai:apitoken": { + "type": "string" + }, + "ai:apitokensecretname": { + "type": "string" + }, + "ai:capabilities": { + "items": { + "type": "string" + }, + "type": "array" + }, + "waveai:cloud": { + "type": "boolean" + }, + "waveai:premium": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "display:name", + "display:icon", + "display:description", + "ai:apitype", + "ai:model", + "ai:thinkinglevel" + ] + } + }, + "additionalProperties": { + "$ref": "#/$defs/AIModeConfigType" + }, + "type": "object" +} \ No newline at end of file From 7be7a94ffae9a63e570607fc9444cee272eab4bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:59:58 -0800 Subject: [PATCH 10/35] Bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.92.0 to 1.92.1 (#2613) Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.92.0 to 1.92.1.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/aws/aws-sdk-go-v2/service/s3&package-manager=go_modules&previous-version=1.92.0&new-version=1.92.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b165226881..86867988e9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/alexflint/go-filemutex v1.3.0 github.com/aws/aws-sdk-go-v2 v1.40.0 github.com/aws/aws-sdk-go-v2/config v1.32.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 github.com/aws/smithy-go v1.23.2 github.com/creack/pty v1.1.24 github.com/emirpasic/gods v1.18.1 @@ -81,7 +81,6 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index e44a38bfdd..0ea79d59a6 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w= -github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 h1:OgQy/+0+Kc3khtqiEOk23xQAglXi3Tj0y5doOxbi5tg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= @@ -146,8 +146,6 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= From bafaf6f220678bc31e2f6defaf324d1fa9b6de75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:00:54 -0800 Subject: [PATCH 11/35] Bump github.com/shirou/gopsutil/v4 from 4.25.9 to 4.25.10 (#2611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.25.9 to 4.25.10.
Release notes

Sourced from github.com/shirou/gopsutil/v4's releases.

v4.25.10

What's Changed

cpu

disk

host

net

Other Changes

New Contributors

Full Changelog: https://github.com/shirou/gopsutil/compare/v4.25.9...v4.25.10

Commits
  • 1da1bb1 Merge pull request #1941 from StefanoBalzarottiNozomi/refactor-disk-windows
  • 4bea90c Merge pull request #1942 from shirou/fix/linter_error
  • 5683c90 [net][linux]: fix gosec linter issue
  • 3a9eeb4 fix: linter error about string concat and build tag
  • 94fc64e fix build
  • 1e8bd3e Update disk/disk_windows.go
  • cf1133d Update disk/disk_windows.go
  • 7ebc85a Update disk/disk_windows.go
  • 0a39842 linting
  • b65c122 these tests are only for windows
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/shirou/gopsutil/v4&package-manager=go_modules&previous-version=4.25.9&new-version=4.25.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 86867988e9..16bddf744f 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/sashabaranov/go-openai v1.41.2 github.com/sawka/txwrap v0.2.0 - github.com/shirou/gopsutil/v4 v4.25.9 + github.com/shirou/gopsutil/v4 v4.25.10 github.com/skeema/knownhosts v1.3.1 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/spf13/cobra v1.10.1 diff --git a/go.sum b/go.sum index 0ea79d59a6..cb7a02a9fb 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,8 @@ github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TV github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sawka/txwrap v0.2.0 h1:V3LfvKVLULxcYSxdMguLwFyQFMEU9nFDJopg0ZkL+94= github.com/sawka/txwrap v0.2.0/go.mod h1:wwQ2SQiN4U+6DU/iVPhbvr7OzXAtgZlQCIGuvOswEfA= -github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= -github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= +github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= +github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= From a04e4e2e58288bd625c3cced2fc383c9ed8802af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:01:15 -0800 Subject: [PATCH 12/35] Bump google.golang.org/api from 0.255.0 to 0.256.0 (#2612) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.255.0 to 0.256.0.
Release notes

Sourced from google.golang.org/api's releases.

v0.256.0

0.256.0 (2025-11-10)

Features

Changelog

Sourced from google.golang.org/api's changelog.

0.256.0 (2025-11-10)

Features

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/api&package-manager=go_modules&previous-version=0.255.0&new-version=0.256.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 16bddf744f..ef1927874f 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( golang.org/x/sync v0.18.0 golang.org/x/sys v0.38.0 golang.org/x/term v0.37.0 - google.golang.org/api v0.255.0 + google.golang.org/api v0.256.0 gopkg.in/ini.v1 v1.67.0 ) @@ -73,7 +73,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -97,11 +97,11 @@ require ( go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index cb7a02a9fb..ee73e96e87 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -218,8 +218,8 @@ golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -239,14 +239,14 @@ golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.255.0 h1:OaF+IbRwOottVCYV2wZan7KUq7UeNUQn1BcPc4K7lE4= -google.golang.org/api v0.255.0/go.mod h1:d1/EtvCLdtiWEV4rAEHDHGh2bCnqsWhw+M8y2ECN4a8= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From 0bcfe3bc7ba0351c9baddaf85d6fb4d13fa7a61f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:03:31 -0800 Subject: [PATCH 13/35] Bump actions/upload-artifact from 4 to 5 in /.github/workflows (#2495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
Release notes

Sourced from actions/upload-artifact's releases.

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

New Contributors

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v5.0.0

v4.6.2

What's Changed

New Contributors

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.2

v4.6.1

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.1

v4.6.0

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.0

v4.5.0

What's Changed

New Contributors

... (truncated)

Commits
  • 330a01c Merge pull request #734 from actions/danwkennedy/prepare-5.0.0
  • 03f2824 Update github.dep.yml
  • 905a1ec Prepare v5.0.0
  • 2d9f9cd Merge pull request #725 from patrikpolyak/patch-1
  • 9687587 Merge branch 'main' into patch-1
  • 2848b2c Merge pull request #727 from danwkennedy/patch-1
  • 9b51177 Spell out the first use of GHES
  • cd231ca Update GHES guidance to include reference to Node 20 version
  • de65e23 Merge pull request #712 from actions/nebuk89-patch-1
  • 8747d8c Update README.md
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-helper.yml | 4 ++-- .github/workflows/testdriver-build.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml index 9e29d9d7f3..8111271eee 100644 --- a/.github/workflows/build-helper.yml +++ b/.github/workflows/build-helper.yml @@ -166,13 +166,13 @@ jobs: AWS_SECRET_ACCESS_KEY: "${{ secrets.ARTIFACTS_KEY_SECRET }}" AWS_DEFAULT_REGION: us-west-2 - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.runner }} path: make - name: Upload Snapcraft logs on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.runner }}-log path: /home/runner/.local/state/snapcraft/log diff --git a/.github/workflows/testdriver-build.yml b/.github/workflows/testdriver-build.yml index 89b68a365b..934eb2c756 100644 --- a/.github/workflows/testdriver-build.yml +++ b/.github/workflows/testdriver-build.yml @@ -77,7 +77,7 @@ jobs: # Upload .exe as an artifact - name: Upload .exe artifact id: upload - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: windows-exe path: make/*.exe From 7543e40c85ece6b08b60e3da24d53afb65afb9a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:05:41 -0800 Subject: [PATCH 14/35] Bump the dev-dependencies-minor group across 1 directory with 5 updates (#2614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the dev-dependencies-minor group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@types/papaparse](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/papaparse) | `5.3.16` | `5.5.0` | | [node-abi](https://github.com/electron/node-abi) | `4.17.0` | `4.24.0` | | [prettier](https://github.com/prettier/prettier) | `3.6.2` | `3.7.1` | | [prettier-plugin-jsdoc](https://github.com/hosseinmd/prettier-plugin-jsdoc) | `1.5.0` | `1.7.0` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.46.4` | `8.48.0` | Updates `@types/papaparse` from 5.3.16 to 5.5.0
Commits

Updates `node-abi` from 4.17.0 to 4.24.0
Release notes

Sourced from node-abi's releases.

v4.24.0

4.24.0 (2025-11-14)

Features

v4.23.0

4.23.0 (2025-11-14)

Features

v4.22.0

4.22.0 (2025-11-13)

Features

v4.21.0

4.21.0 (2025-11-13)

Features

v4.20.0

4.20.0 (2025-11-13)

Features

v4.19.0

4.19.0 (2025-11-13)

Features

v4.18.0

4.18.0 (2025-11-13)

... (truncated)

Commits

Updates `prettier` from 3.6.2 to 3.7.1
Release notes

Sourced from prettier's releases.

3.7.1

🔗 Changelog

3.7.0

diff

🔗 Release note

Changelog

Sourced from prettier's changelog.

3.7.1

diff

API: Fix performance regression in doc printer (#18342 by @​fisker)

Prettier 3.7.0 can be very slow when formatting big files, the regression has been fixed.

3.7.0

diff

🔗 Release Notes

Commits
  • 47c40b3 Release 3.7.1
  • 99df071 Release @prettier/plugin-hermes & @prettier/plugin-oxc v0.1.1
  • d147f67 Fix performance regression in doc printer (#18342)
  • 1fe6a12 Git blame ignore 3.7.0
  • 3a098e3 Bump Prettier dependency to 3.7.0
  • c4905e5 Clean changelog_unreleased
  • 43236e2 Add blog post for v3.7 (#18323)
  • 8147ddd Release 3.7.0
  • 8a59916 Release @​prettier/plugin-hermes & @​prettier/plugin-oxc v0.1.0
  • b77751e chore(deps): update dependency @​angular/compiler to v21.0.1 (#18334)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for prettier since your current version.


Updates `prettier-plugin-jsdoc` from 1.5.0 to 1.7.0
Commits

Updates `typescript-eslint` from 8.46.4 to 8.48.0
Release notes

Sourced from typescript-eslint's releases.

v8.48.0

8.48.0 (2025-11-24)

🚀 Features

  • eslint-plugin: [no-redundant-type-constituents] use assignability checking for redundancy checks (#10744)
  • rule-tester: remove workaround for jest circular structure error (#11772)
  • typescript-estree: gate all errors behind allowInvalidAST (#11693)
  • typescript-estree: replace fast-glob with tinyglobby (#11740)

🩹 Fixes

  • eslint-plugin: [consistent-generic-constructors] ignore when constructor is typed array (#10477)
  • scope-manager: change unhelpful aaa error message and change analyze to expects Program (#11747)
  • typescript-estree: infers singleRun as true for project service (#11327)
  • typescript-estree: disallow binding patterns in parameter properties (#11760)

❤️ Thank You

You can read about our versioning strategy and releases on our website.

v8.47.0

8.47.0 (2025-11-17)

🚀 Features

  • eslint-plugin: [no-unused-private-class-members] new extension rule (#10913)

❤️ Thank You

You can read about our versioning strategy and releases on our website.

Changelog

Sourced from typescript-eslint's changelog.

8.48.0 (2025-11-24)

This was a version bump only for typescript-eslint to align it with other projects, there were no code changes.

You can read about our versioning strategy and releases on our website.

8.47.0 (2025-11-17)

This was a version bump only for typescript-eslint to align it with other projects, there were no code changes.

You can read about our versioning strategy and releases on our website.

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package.json | 6 +- package-lock.json | 245 ++++++++++++++++++++++++++++++---------------- package.json | 8 +- 3 files changed, 169 insertions(+), 90 deletions(-) diff --git a/docs/package.json b/docs/package.json index cf09870d02..de819a7afb 100644 --- a/docs/package.json +++ b/docs/package.json @@ -47,8 +47,8 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mdx": "^3.6.2", - "prettier": "^3.6.2", - "prettier-plugin-jsdoc": "^1.5.0", + "prettier": "^3.7.1", + "prettier-plugin-jsdoc": "^1.7.0", "prettier-plugin-organize-imports": "^4.3.0", "remark-cli": "^12.0.1", "remark-frontmatter": "^5.0.0", @@ -56,7 +56,7 @@ "remark-preset-lint-consistent": "^6.0.1", "remark-preset-lint-recommended": "^7.0.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.4" + "typescript-eslint": "^8.48.0" }, "resolutions": { "path-to-regexp@npm:2.2.1": "^3", diff --git a/package-lock.json b/package-lock.json index da74067ccb..e130d72620 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,10 +113,10 @@ "electron-vite": "^4.0.1", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", - "node-abi": "^4.17.0", + "node-abi": "^4.24.0", "postcss": "^8.5.6", - "prettier": "^3.6.2", - "prettier-plugin-jsdoc": "^1.5.0", + "prettier": "^3.7.1", + "prettier-plugin-jsdoc": "^1.7.0", "prettier-plugin-organize-imports": "^4.3.0", "sass": "1.91.0", "tailwindcss": "^4.1.17", @@ -125,7 +125,7 @@ "tslib": "^2.8.1", "tsx": "^4.20.6", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.4", + "typescript-eslint": "^8.48.0", "vite": "^6.4.1", "vite-plugin-image-optimizer": "^2.0.3", "vite-plugin-static-copy": "^3.1.4", @@ -171,8 +171,8 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-mdx": "^3.6.2", - "prettier": "^3.6.2", - "prettier-plugin-jsdoc": "^1.5.0", + "prettier": "^3.7.1", + "prettier-plugin-jsdoc": "^1.7.0", "prettier-plugin-organize-imports": "^4.3.0", "remark-cli": "^12.0.1", "remark-frontmatter": "^5.0.0", @@ -180,7 +180,7 @@ "remark-preset-lint-consistent": "^6.0.1", "remark-preset-lint-recommended": "^7.0.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.4" + "typescript-eslint": "^8.48.0" }, "engines": { "node": ">=18.0" @@ -10112,9 +10112,9 @@ } }, "node_modules/@types/papaparse": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.16.tgz", - "integrity": "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.0.tgz", + "integrity": "sha512-GVs5iMQmUr54BAZYYkByv8zPofFxmyxUpISPb2oh8sayR3+1zbxasrOvoKiHJ/nnoq/uULuPsu1Lze1EkagVFg==", "dev": true, "license": "MIT", "dependencies": { @@ -10415,17 +10415,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", - "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", + "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/type-utils": "8.46.4", - "@typescript-eslint/utils": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/type-utils": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -10439,7 +10439,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.4", + "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -10455,16 +10455,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz", - "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", + "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "engines": { @@ -10480,14 +10480,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz", - "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "engines": { @@ -10502,14 +10502,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", - "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4" + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10520,9 +10520,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz", - "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", "dev": true, "license": "MIT", "engines": { @@ -10537,15 +10537,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz", - "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", + "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -10562,9 +10562,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", - "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", "dev": true, "license": "MIT", "engines": { @@ -10576,21 +10576,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", - "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.4", - "@typescript-eslint/tsconfig-utils": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -10631,16 +10630,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz", - "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4" + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10655,13 +10654,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", - "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -23092,9 +23091,9 @@ } }, "node_modules/node-abi": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.17.0.tgz", - "integrity": "sha512-ljZ7PiChMA2O3sGPX5/bpBhW0O9rXn+orb2xo3Z0vleSlil7G65WZjSFjmIeAtHZHa2GXiTOMdFCsiyImMEIMg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.24.0.tgz", + "integrity": "sha512-u2EC1CeNe25uVtX3EZbdQ275c74zdZmmpzrHEQh2aIYqoVjlglfUpOX9YY85x1nlBydEKDVaSmMNhR7N82Qj8A==", "dev": true, "license": "MIT", "dependencies": { @@ -26006,9 +26005,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.1.tgz", + "integrity": "sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -26021,15 +26020,16 @@ } }, "node_modules/prettier-plugin-jsdoc": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-1.5.0.tgz", - "integrity": "sha512-Fehp5qkFQhNFcxUilDPEcqHX8AdP6oGyCRLatqRc0gLXv3qOtndTnnUxfHCYc26I4Lc1A4lVozAtWEE8o7ubUA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-1.7.0.tgz", + "integrity": "sha512-tvmMg1y9G7Hy5N2SnLsWsgJWtoSSpfphq+a7dAEoED+siiaBHDowI6N9HzhLA4/SRJhlRdHkDXwCPrXgzbRhng==", "dev": true, "license": "MIT", "dependencies": { "binary-searching": "^2.0.5", "comment-parser": "^1.4.0", - "mdast-util-from-markdown": "^2.0.0" + "mdast-util-from-markdown": "^2.0.0", + "prettier-plugin-tailwindcss": "^0.7.1" }, "engines": { "node": ">=14.13.1 || >=16.0.0" @@ -26055,6 +26055,85 @@ } } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz", + "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -30840,16 +30919,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz", - "integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", + "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.4", - "@typescript-eslint/parser": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4" + "@typescript-eslint/eslint-plugin": "8.48.0", + "@typescript-eslint/parser": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 81fbeebf6b..00fac917ac 100644 --- a/package.json +++ b/package.json @@ -54,10 +54,10 @@ "electron-vite": "^4.0.1", "eslint": "^8.57.0", "eslint-config-prettier": "^10.1.8", - "node-abi": "^4.17.0", + "node-abi": "^4.24.0", "postcss": "^8.5.6", - "prettier": "^3.6.2", - "prettier-plugin-jsdoc": "^1.5.0", + "prettier": "^3.7.1", + "prettier-plugin-jsdoc": "^1.7.0", "prettier-plugin-organize-imports": "^4.3.0", "sass": "1.91.0", "tailwindcss": "^4.1.17", @@ -66,7 +66,7 @@ "tslib": "^2.8.1", "tsx": "^4.20.6", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.4", + "typescript-eslint": "^8.48.0", "vite": "^6.4.1", "vite-plugin-image-optimizer": "^2.0.3", "vite-plugin-static-copy": "^3.1.4", From 5e78147e037dab88bb3ae02efcb3404d4a7b623b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:20:14 -0800 Subject: [PATCH 15/35] Bump the prod-dependencies-patch group with 2 updates (#2615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the prod-dependencies-patch group with 2 updates: [@ai-sdk/react](https://github.com/vercel/ai) and [ai](https://github.com/vercel/ai). Updates `@ai-sdk/react` from 2.0.101 to 2.0.104
Commits

Updates `ai` from 5.0.101 to 5.0.104
Release notes

Sourced from ai's releases.

ai@5.0.104

Patch Changes

  • d1dbe5d: move DelayedPromise into provider utils
  • Updated dependencies [d1dbe5d]
    • @​ai-sdk/provider-utils@​3.0.18
    • @​ai-sdk/gateway@​2.0.17

ai@5.0.103

Patch Changes

  • Updated dependencies [e609736]
    • @​ai-sdk/gateway@​2.0.16
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index e130d72620..fc0b29779c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "tsunami/frontend" ], "dependencies": { - "@ai-sdk/react": "^2.0.101", + "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", @@ -253,13 +253,13 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.15.tgz", - "integrity": "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.17.tgz", + "integrity": "sha512-oVAG6q72KsjKlrYdLhWjRO7rcqAR8CjokAbYuyVZoCO4Uh2PH/VzZoxZav71w2ipwlXhHCNaInGYWNs889MMDA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", + "@ai-sdk/provider-utils": "3.0.18", "@vercel/oidc": "3.0.5" }, "engines": { @@ -282,9 +282,9 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz", + "integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", @@ -299,13 +299,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "2.0.101", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.101.tgz", - "integrity": "sha512-Cq9InVVGBs2Dw3FiqImsuGZK86HZnNp562+jygJfUtPpp5JUOwWBblCQcli7X8aDg9QsitdM0ZJpbVZQ+fwH6w==", + "version": "2.0.104", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.104.tgz", + "integrity": "sha512-vpRNUwOrHXSsywZuEge78/LPbYMR/3tkBnwijGpIGnORMa/SzYhuVsE+sZBFVo/v0m5K/tg+CXNNvuJrVZ/MBQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider-utils": "3.0.17", - "ai": "5.0.101", + "@ai-sdk/provider-utils": "3.0.18", + "ai": "5.0.104", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -11275,14 +11275,14 @@ } }, "node_modules/ai": { - "version": "5.0.101", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.101.tgz", - "integrity": "sha512-/P4fgs2PGYTBaZi192YkPikOudsl9vccA65F7J7LvoNTOoP5kh1yAsJPsKAy6FXU32bAngai7ft1UDyC3u7z5g==", + "version": "5.0.104", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.104.tgz", + "integrity": "sha512-MZOkL9++nY5PfkpWKBR3Rv+Oygxpb9S16ctv8h91GvrSif7UnNEdPMVZe3bUyMd2djxf0AtBk/csBixP0WwWZQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "2.0.15", + "@ai-sdk/gateway": "2.0.17", "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", + "@ai-sdk/provider-utils": "3.0.18", "@opentelemetry/api": "1.9.0" }, "engines": { diff --git a/package.json b/package.json index 00fac917ac..93df435e1a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "vitest": "^3.0.9" }, "dependencies": { - "@ai-sdk/react": "^2.0.101", + "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", From c32eb6690b0c66701ae9db482743471efc7dfa91 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Tue, 2 Dec 2025 15:50:23 -0800 Subject: [PATCH 16/35] fixup macos zsh_history (merge wave history back to => ~/.zsh_history) (#2625) --- cmd/server/main-server.go | 4 + go.mod | 1 + go.sum | 2 + pkg/telemetry/telemetry.go | 7 +- pkg/util/envutil/envutil.go | 24 ++++ .../shellutil/shellintegration/zsh_zshrc.sh | 5 + pkg/util/shellutil/shellutil.go | 118 ++++++++++++++++++ pkg/wcloud/wcloud.go | 10 +- 8 files changed, 164 insertions(+), 7 deletions(-) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index b52d97491c..30540e351f 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -465,6 +465,10 @@ func main() { return } + err = shellutil.FixupWaveZshHistory() + if err != nil { + log.Printf("error fixing up wave zsh history: %v\n", err) + } createMainWshClient() sigutil.InstallShutdownSignalHandlers(doShutdown) sigutil.InstallSIGUSR1Handler() diff --git a/go.mod b/go.mod index ef1927874f..6711ded66a 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index ee73e96e87..ea416cd09a 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 3a208055b9..24c7541b54 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -254,10 +254,13 @@ func RecordTEvent(ctx context.Context, tevent *telemetrydata.TEvent) error { } func CleanOldTEvents(ctx context.Context) error { + daysToKeep := 7 + if !IsTelemetryEnabled() { + daysToKeep = 1 + } + olderThan := time.Now().AddDate(0, 0, -daysToKeep).UnixMilli() return wstore.WithTx(ctx, func(tx *wstore.TxWrap) error { - // delete events older than 28 days query := `DELETE FROM db_tevent WHERE ts < ?` - olderThan := time.Now().AddDate(0, 0, -28).UnixMilli() tx.Exec(query, olderThan) return nil }) diff --git a/pkg/util/envutil/envutil.go b/pkg/util/envutil/envutil.go index c2505e7c47..dff40c1842 100644 --- a/pkg/util/envutil/envutil.go +++ b/pkg/util/envutil/envutil.go @@ -67,3 +67,27 @@ func RmEnv(envStr string, key string) string { delete(envMap, key) return MapToEnv(envMap) } + +func SliceToEnv(env []string) string { + var sb strings.Builder + for _, envVar := range env { + if len(envVar) == 0 { + continue + } + sb.WriteString(envVar) + sb.WriteByte('\x00') + } + return sb.String() +} + +func EnvToSlice(envStr string) []string { + envLines := strings.Split(envStr, "\x00") + result := make([]string, 0, len(envLines)) + for _, line := range envLines { + if len(line) == 0 { + continue + } + result = append(result, line) + } + return result +} diff --git a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh index f24abee181..0c0271a29d 100644 --- a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh +++ b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh @@ -18,6 +18,11 @@ if [[ -n ${_comps+x} ]]; then source <(wsh completion zsh) fi +# fix history (macos) +if [[ "$HISTFILE" == "$WAVETERM_ZDOTDIR/.zsh_history" ]]; then + HISTFILE="$HOME/.zsh_history" +fi + typeset -g _WAVETERM_SI_FIRSTPRECMD=1 # shell integration diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index dd9fe697a1..a6268bf5dd 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -18,6 +18,7 @@ import ( "sync" "time" + "github.com/wavetermdev/waveterm/pkg/util/envutil" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" @@ -47,6 +48,8 @@ var ( //go:embed shellintegration/pwsh_wavepwsh.sh PwshStartup_wavepwsh string + + ZshExtendedHistoryPattern = regexp.MustCompile(`^: [0-9]+:`) ) const DefaultTermType = "xterm-256color" @@ -74,6 +77,7 @@ const ( PwshIntegrationDir = "shell/pwsh" FishIntegrationDir = "shell/fish" WaveHomeBinDir = "bin" + ZshHistoryFileName = ".zsh_history" ) func DetectLocalShellPath() string { @@ -208,6 +212,46 @@ func GetLocalZshZDotDir() string { return filepath.Join(wavebase.GetWaveDataDir(), ZshIntegrationDir) } +func HasWaveZshHistory() (bool, int64) { + zshDir := GetLocalZshZDotDir() + historyFile := filepath.Join(zshDir, ZshHistoryFileName) + fileInfo, err := os.Stat(historyFile) + if err != nil { + return false, 0 + } + return true, fileInfo.Size() +} + +func IsExtendedZshHistoryFile(fileName string) (bool, error) { + file, err := os.Open(fileName) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + defer file.Close() + + buf := make([]byte, 1024) + n, err := file.Read(buf) + if err != nil { + return false, err + } + + content := string(buf[:n]) + lines := strings.Split(content, "\n") + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + return ZshExtendedHistoryPattern.MatchString(line), nil + } + + return false, nil +} + func GetLocalWshBinaryPath(version string, goos string, goarch string) (string, error) { ext := "" if goarch == "amd64" { @@ -422,6 +466,80 @@ func getShellVersion(shellPath string, shellType string) (string, error) { return matches[1], nil } +func FixupWaveZshHistory() error { + if runtime.GOOS != "darwin" { + return nil + } + + hasHistory, size := HasWaveZshHistory() + if !hasHistory { + return nil + } + + zshDir := GetLocalZshZDotDir() + waveHistFile := filepath.Join(zshDir, ZshHistoryFileName) + + if size == 0 { + err := os.Remove(waveHistFile) + if err != nil { + log.Printf("error removing wave zsh history file %s: %v\n", waveHistFile, err) + } + return nil + } + + log.Printf("merging wave zsh history %s into ~/.zsh_history\n", waveHistFile) + + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error getting home directory: %w", err) + } + realHistFile := filepath.Join(homeDir, ".zsh_history") + + isExtended, err := IsExtendedZshHistoryFile(realHistFile) + if err != nil { + return fmt.Errorf("error checking if history is extended: %w", err) + } + + hasExtendedStr := "false" + if isExtended { + hasExtendedStr = "true" + } + + quotedWaveHistFile := utilfn.ShellQuote(waveHistFile, true, -1) + + script := fmt.Sprintf(` + HISTFILE=~/.zsh_history + HISTSIZE=999999 + SAVEHIST=999999 + has_extended_history=%s + [[ $has_extended_history == true ]] && setopt EXTENDED_HISTORY + fc -RI + fc -RI %s + fc -W + `, hasExtendedStr, quotedWaveHistFile) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + cmd := exec.CommandContext(ctx, "zsh", "-f", "-i", "-c", script) + cmd.Stdin = nil + envStr := envutil.SliceToEnv(os.Environ()) + envStr = envutil.RmEnv(envStr, "ZDOTDIR") + cmd.Env = envutil.EnvToSlice(envStr) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error executing zsh history fixup script: %w, output: %s", err, string(output)) + } + + err = os.Remove(waveHistFile) + if err != nil { + log.Printf("error removing wave zsh history file %s: %v\n", waveHistFile, err) + } + log.Printf("successfully merged wave zsh history %s into ~/.zsh_history\n", waveHistFile) + + return nil +} + func FormatOSC(oscNum int, parts ...string) string { if len(parts) == 0 { return fmt.Sprintf("\x1b]%d\x07", oscNum) diff --git a/pkg/wcloud/wcloud.go b/pkg/wcloud/wcloud.go index 9101b34469..455acd8944 100644 --- a/pkg/wcloud/wcloud.go +++ b/pkg/wcloud/wcloud.go @@ -214,11 +214,11 @@ func sendTEvents(clientId string) (int, error) { } func SendAllTelemetry(clientId string) error { - defer func() { - ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) - defer cancelFn() - telemetry.CleanOldTEvents(ctx) - }() + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + if err := telemetry.CleanOldTEvents(ctx); err != nil { + log.Printf("error cleaning old telemetry events: %v\n", err) + } if !telemetry.IsTelemetryEnabled() { log.Printf("telemetry disabled, not sending\n") return nil From f91531a2c9e8ef31548d23926480548c11072e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 15:50:59 -0800 Subject: [PATCH 17/35] Bump mdast-util-to-hast from 13.2.0 to 13.2.1 (#2624) Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
Release notes

Sourced from mdast-util-to-hast's releases.

13.2.1

Fix

  • ab3a795 Fix support for spaces in class names

Types

  • efb5312 Refactor to use @imports
  • a5bc210 Add declaration maps

Full Changelog: https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mdast-util-to-hast&package-manager=npm_and_yarn&previous-version=13.2.0&new-version=13.2.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/wavetermdev/waveterm/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc0b29779c..5caf0c340c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20513,9 +20513,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", From 8441981930f0c10c28245f9b9f0f5f3ddf37a4be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:43:05 -0800 Subject: [PATCH 18/35] Bump tsx from 4.20.6 to 4.21.0 in the dev-dependencies-minor group (#2638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the dev-dependencies-minor group with 1 update: [tsx](https://github.com/privatenumber/tsx). Updates `tsx` from 4.20.6 to 4.21.0
Release notes

Sourced from tsx's releases.

v4.21.0

4.21.0 (2025-11-30)

Features


This release is also available on:

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tsx&package-manager=npm_and_yarn&previous-version=4.20.6&new-version=4.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 494 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 490 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5caf0c340c..0e94709684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -123,7 +123,7 @@ "tailwindcss-animate": "^1.0.7", "ts-node": "^10.9.2", "tslib": "^2.8.1", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.48.0", "vite": "^6.4.1", @@ -30820,13 +30820,13 @@ "link": true }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -30839,6 +30839,490 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 93df435e1a..26d0b3fdc0 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "tailwindcss-animate": "^1.0.7", "ts-node": "^10.9.2", "tslib": "^2.8.1", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.48.0", "vite": "^6.4.1", From 34931b105ebf17c7de7b2ef9075736ecbbafa7ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:43:19 -0800 Subject: [PATCH 19/35] Bump github.com/aws/aws-sdk-go-v2 from 1.40.0 to 1.40.1 (#2636) Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.40.0 to 1.40.1.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/aws/aws-sdk-go-v2&package-manager=go_modules&previous-version=1.40.0&new-version=1.40.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 ++--- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 6711ded66a..e218cb5918 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.24.6 require ( github.com/alexflint/go-filemutex v1.3.0 - github.com/aws/aws-sdk-go-v2 v1.40.0 + github.com/aws/aws-sdk-go-v2 v1.40.1 github.com/aws/aws-sdk-go-v2/config v1.32.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 - github.com/aws/smithy-go v1.23.2 + github.com/aws/smithy-go v1.24.0 github.com/creack/pty v1.1.24 github.com/emirpasic/gods v1.18.1 github.com/fsnotify/fsnotify v1.9.0 @@ -81,7 +81,6 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index ea416cd09a..6e9a114918 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/0xrawsec/golang-utils v1.3.2 h1:ww4jrtHRSnX9xrGzJYbalx5nXoZewy4zPxiY+ github.com/0xrawsec/golang-utils v1.3.2/go.mod h1:m7AzHXgdSAkFCD9tWWsApxNVxMlyy7anpPVOyT/yM7E= github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= -github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= +github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= +github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA= @@ -52,8 +52,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -146,8 +146,6 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= From 5e7dafe01bc5033aa2241d964bd5cc3746052b04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:43:54 -0800 Subject: [PATCH 20/35] Bump github.com/spf13/cobra from 1.10.1 to 1.10.2 (#2635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
Release notes

Sourced from github.com/spf13/cobra's releases.

v1.10.2

🔧 Dependencies

  • chore: Migrate from gopkg.in/yaml.v3 to go.yaml.in/yaml/v3 by @​dims in spf13/cobra#2336 - the gopkg.in/yaml.v3 package has been deprecated for some time: this should significantly cleanup dependency/supply-chains for consumers of spf13/cobra

📈 CI/CD

🔥✍🏼 Docs

🍂 Refactors

🤗 New Contributors

Full Changelog: https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2

Thank you to our amazing contributors!!!!! 🐍 🚀

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/spf13/cobra&package-manager=go_modules&previous-version=1.10.1&new-version=1.10.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e218cb5918..e89564f03f 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/shirou/gopsutil/v4 v4.25.10 github.com/skeema/knownhosts v1.3.1 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b github.com/wavetermdev/htmltoken v0.2.0 github.com/wavetermdev/waveterm/tsunami v0.12.3 diff --git a/go.sum b/go.sum index 6e9a114918..082e6b9430 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -212,6 +212,7 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= From f57873e5cff6a86e829491d9d7611419060780f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:44:23 -0800 Subject: [PATCH 21/35] Bump github.com/golang-migrate/migrate/v4 from 4.19.0 to 4.19.1 (#2634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/golang-migrate/migrate/v4](https://github.com/golang-migrate/migrate) from 4.19.0 to 4.19.1.
Release notes

Sourced from github.com/golang-migrate/migrate/v4's releases.

v4.19.1

What's Changed

New Contributors

Full Changelog: https://github.com/golang-migrate/migrate/compare/v4.19.0...v4.19.1

Commits
  • 89e308c chore: remove dependency on "hashicorp/go-multierror" (#1322)
  • 472ef2e Merge pull request #1336 from golang-migrate/dependabot/go_modules/golang.org...
  • 8d76259 Bump golang.org/x/crypto from 0.43.0 to 0.45.0
  • 9f9df7c chore: Update cloud.google.com/go/spanner version (#1330)
  • a371c8e Merge pull request #1304 from iamonah/fix/clarify-databaseName-meaning
  • 43cc3b3 Merge pull request #1325 from HaraldNordgren/linter_issues
  • f939a89 Merge pull request #1335 from golang-migrate/dependabot/go_modules/golang.org...
  • 6dd86e0 Bump golang.org/x/crypto from 0.36.0 to 0.45.0
  • 70e6d6d Merge pull request #1333 from matsoob/updateGoInBaseImage
  • a51d0da Merge pull request #1334 from golang-migrate/dependabot/go_modules/github.com...
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/golang-migrate/migrate/v4&package-manager=go_modules&previous-version=4.19.0&new-version=4.19.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 ++++------ go.sum | 27 ++++++++++++--------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index e89564f03f..9bb5b64197 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/emirpasic/gods v1.18.1 github.com/fsnotify/fsnotify v1.9.0 github.com/golang-jwt/jwt/v5 v5.3.0 - github.com/golang-migrate/migrate/v4 v4.19.0 + github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/generative-ai-go v0.20.1 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 @@ -44,12 +44,12 @@ require ( ) require ( - cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go v0.121.6 // indirect cloud.google.com/go/ai v0.8.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/longrunning v0.5.7 // indirect + cloud.google.com/go/longrunning v0.6.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect @@ -75,8 +75,6 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -100,7 +98,7 @@ require ( golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect diff --git a/go.sum b/go.sum index 082e6b9430..e18f442fee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= -cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= @@ -8,8 +8,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/0xrawsec/golang-utils v1.3.2 h1:ww4jrtHRSnX9xrGzJYbalx5nXoZewy4zPxiY+ubJgtg= @@ -62,8 +62,9 @@ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -88,8 +89,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= -github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= +github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ= @@ -109,11 +110,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= @@ -150,8 +146,9 @@ github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hx github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -244,8 +241,8 @@ google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= From 2f92e23ece653f7f25fa420f47c58ea5ea6d74af Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Fri, 5 Dec 2025 10:10:43 -0800 Subject: [PATCH 22/35] more config updates (secrets, waveai, ai:provider) (#2631) --- aiprompts/aimodesconfig.md | 709 ++++++++++++++++++ cmd/server/main-server.go | 53 +- cmd/testai/main-testai.go | 4 +- docs/docs/ai-presets.mdx | 2 +- docs/docs/connections.mdx | 3 + docs/docs/faq.mdx | 100 --- docs/docs/secrets.mdx | 147 ++++ docs/docs/waveai-modes.mdx | 359 +++++++++ docs/src/components/versionbadge.css | 18 + docs/src/components/versionbadge.tsx | 9 + emain/emain-menu.ts | 1 + frontend/app/aipanel/ai-utils.ts | 43 ++ frontend/app/aipanel/aimessage.tsx | 2 +- frontend/app/aipanel/aimode.tsx | 115 ++- frontend/app/aipanel/aipanel-contextmenu.ts | 108 ++- frontend/app/aipanel/aipanelmessages.tsx | 4 +- frontend/app/aipanel/waveai-model.tsx | 7 +- frontend/app/modals/conntypeahead.tsx | 5 +- frontend/app/modals/userinputmodal.scss | 62 -- frontend/app/modals/userinputmodal.tsx | 23 +- .../app/view/waveconfig/secretscontent.tsx | 389 ++++++++++ frontend/app/view/waveconfig/waveaivisual.tsx | 20 + .../app/view/waveconfig/waveconfig-model.ts | 257 ++++++- frontend/app/view/waveconfig/waveconfig.tsx | 71 +- frontend/app/workspace/widgets.tsx | 9 +- frontend/types/custom.d.ts | 4 +- frontend/types/gotypes.d.ts | 19 +- frontend/util/util.ts | 13 + pkg/aiusechat/aiutil/aiutil.go | 32 +- .../anthropic/anthropic-convertmessage.go | 28 +- pkg/aiusechat/openai/openai-backend.go | 2 +- pkg/aiusechat/openai/openai-convertmessage.go | 31 +- .../openaichat/openaichat-convertmessage.go | 44 +- pkg/aiusechat/tools.go | 5 + pkg/aiusechat/uctypes/uctypes.go | 23 +- pkg/aiusechat/usechat-mode.go | 140 +++- pkg/aiusechat/usechat.go | 28 +- pkg/remote/sshclient.go | 28 +- pkg/wconfig/defaultconfig/settings.json | 4 +- pkg/wconfig/defaultconfig/waveai.json | 12 +- pkg/wconfig/filewatcher.go | 23 +- pkg/wconfig/metaconsts.go | 3 + pkg/wconfig/settingsconfig.go | 26 +- schema/bgpresets.json | 88 +-- schema/connections.json | 3 + schema/settings.json | 6 + schema/waveai.json | 51 +- 47 files changed, 2707 insertions(+), 426 deletions(-) create mode 100644 aiprompts/aimodesconfig.md create mode 100644 docs/docs/secrets.mdx create mode 100644 docs/docs/waveai-modes.mdx create mode 100644 docs/src/components/versionbadge.css create mode 100644 docs/src/components/versionbadge.tsx delete mode 100644 frontend/app/modals/userinputmodal.scss create mode 100644 frontend/app/view/waveconfig/secretscontent.tsx create mode 100644 frontend/app/view/waveconfig/waveaivisual.tsx diff --git a/aiprompts/aimodesconfig.md b/aiprompts/aimodesconfig.md new file mode 100644 index 0000000000..207b6fad88 --- /dev/null +++ b/aiprompts/aimodesconfig.md @@ -0,0 +1,709 @@ +# Wave AI Modes Configuration - Visual Editor Architecture + +## Overview + +Wave Terminal's AI modes configuration system allows users to define custom AI assistants with different providers, models, and capabilities. The configuration is stored in `~/.waveterm/config/waveai.json` and provides a flexible way to configure multiple AI modes that appear in the Wave AI panel. + +**Key Design Decisions:** +- Visual editor works on **valid JSON only** - if JSON is invalid, fall back to JSON editor +- Default modes (`waveai@quick`, `waveai@balanced`, `waveai@deep`) are **read-only** in visual editor +- Edits modify the **in-memory JSON directly** - changes saved via existing save button +- Mode keys are **auto-generated** from provider + model or random ID (last 4-6 chars) +- Secrets use **fixed naming convention** per provider (e.g., `OPENAI_KEY`, `OPENROUTER_KEY`) +- Quick **inline secret editor** instead of complex secret management + +## Current System Architecture + +### Data Structure + +**Location:** `pkg/wconfig/settingsconfig.go:264-284` + +```go +type AIModeConfigType struct { + // Display Configuration + DisplayName string `json:"display:name"` // Required + DisplayOrder float64 `json:"display:order,omitempty"` + DisplayIcon string `json:"display:icon,omitempty"` + DisplayShortDesc string `json:"display:shortdesc,omitempty"` + DisplayDescription string `json:"display:description,omitempty"` + + // Provider & Model + Provider string `json:"ai:provider,omitempty"` // wave, google, openrouter, openai, azure, azure-legacy, custom + APIType string `json:"ai:apitype"` // Required: anthropic-messages, openai-responses, openai-chat + Model string `json:"ai:model"` // Required + + // AI Behavior + ThinkingLevel string `json:"ai:thinkinglevel,omitempty"` // low, medium, high + Capabilities []string `json:"ai:capabilities,omitempty"` // pdfs, images, tools + + // Connection Details + Endpoint string `json:"ai:endpoint,omitempty"` + APIVersion string `json:"ai:apiversion,omitempty"` + APIToken string `json:"ai:apitoken,omitempty"` + APITokenSecretName string `json:"ai:apitokensecretname,omitempty"` + + // Azure-Specific + AzureResourceName string `json:"ai:azureresourcename,omitempty"` + AzureDeployment string `json:"ai:azuredeployment,omitempty"` + + // Wave AI Specific + WaveAICloud bool `json:"waveai:cloud,omitempty"` + WaveAIPremium bool `json:"waveai:premium,omitempty"` +} +``` + +**Storage:** `FullConfigType.WaveAIModes` - `map[string]AIModeConfigType` + +Keys follow pattern: `provider@modename` (e.g., `waveai@quick`, `openai@gpt4`) + +### Provider Types & Defaults + +**Defined in:** `pkg/aiusechat/uctypes/uctypes.go:27-35` + +1. **wave** - Wave AI Cloud service + - Auto-sets: `waveai:cloud = true`, endpoint from env or default + - Default endpoint: `https://cfapi.waveterm.dev/api/waveai` + - Used for Wave's hosted AI modes + +2. **openai** - OpenAI API + - Auto-sets: endpoint `https://api.openai.com/v1` + - Auto-detects API type based on model: + - Legacy models (gpt-4o, gpt-3.5): `openai-chat` + - New models (gpt-5*, gpt-4.1*, o1*, o3*): `openai-responses` + +3. **openrouter** - OpenRouter service + - Auto-sets: endpoint `https://openrouter.ai/api/v1`, API type `openai-chat` + +4. **google** - Google AI (Gemini, etc.) + - No auto-defaults currently + +5. **azure** - Azure OpenAI (new unified API) + - Auto-sets: API version `v1`, endpoint from resource name + - Endpoint pattern: `https://{resource}.openai.azure.com/openai/v1/{responses|chat/completions}` + - Auto-detects API type based on model + +6. **azure-legacy** - Azure OpenAI (legacy chat completions) + - Auto-sets: API version `2025-04-01-preview`, API type `openai-chat` + - Endpoint pattern: `https://{resource}.openai.azure.com/openai/deployments/{deployment}/chat/completions?api-version={version}` + - Requires `AzureResourceName` and `AzureDeployment` + +7. **custom** - Custom provider + - No auto-defaults + - User must specify all fields manually + +### Default Configuration + +**Location:** `pkg/wconfig/defaultconfig/waveai.json` + +Ships with three Wave AI modes: +- `waveai@quick` - Fast responses (gpt-5-mini, low thinking) +- `waveai@balanced` - Balanced (gpt-5.1, low thinking) [premium] +- `waveai@deep` - Maximum capability (gpt-5.1, medium thinking) [premium] + +### Current UI State + +**Location:** `frontend/app/view/waveconfig/waveaivisual.tsx` + +Currently shows placeholder: "Visual editor coming soon..." + +The component receives: +- `model: WaveConfigViewModel` - Access to config file operations +- Existing patterns from `SecretsContent` for list/detail views + +## Visual Editor Design Plan + +### High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Wave AI Modes Configuration │ +│ ┌───────────────┐ ┌──────────────────────────────┐ │ +│ │ │ │ │ │ +│ │ Mode List │ │ Mode Editor/Viewer │ │ +│ │ │ │ │ │ +│ │ [Quick] │ │ Provider: [wave ▼] │ │ +│ │ [Balanced] │ │ │ │ +│ │ [Deep] │ │ Display Configuration │ │ +│ │ [Custom] │ │ ├─ Name: ... │ │ +│ │ │ │ ├─ Icon: ... │ │ +│ │ [+ Add New] │ │ └─ Description: ... │ │ +│ │ │ │ │ │ +│ │ │ │ Provider Configuration │ │ +│ │ │ │ (Provider-specific fields) │ │ +│ │ │ │ │ │ +│ │ │ │ [Save] [Delete] [Cancel] │ │ +│ └───────────────┘ └──────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### Component Structure + +```typescript +WaveAIVisualContent +├─ ModeList (left panel) +│ ├─ Header with "Add New Mode" button +│ ├─ List of existing modes (sorted by display:order) +│ │ └─ ModeListItem (icon, name, short desc, provider badge) +│ └─ Empty state if no modes +│ +└─ ModeEditor (right panel) + ├─ Provider selector dropdown (when creating/editing) + ├─ Display section (common to all providers) + │ ├─ Name input (required) + │ ├─ Icon picker (optional) + │ ├─ Display order (optional, number) + │ ├─ Short description (optional) + │ └─ Description textarea (optional) + │ + ├─ Provider Configuration section (dynamic based on provider) + │ └─ [Provider-specific form fields] + │ + └─ Action buttons (Save, Delete, Cancel) +``` + +### Provider-Specific Form Fields + +#### 1. Wave Provider (`wave`) +**Read-only/Auto-managed:** +- Endpoint (shows default or env override) +- Cloud flag (always true) +- Secret: Not applicable (managed by Wave) + +**User-configurable:** +- Model (required, text input with suggestions: gpt-5-mini, gpt-5.1) +- API Type (required, dropdown: openai-responses, openai-chat) +- Thinking Level (optional, dropdown: low, medium, high) +- Capabilities (optional, checkboxes: tools, images, pdfs) +- Premium flag (checkbox) + +#### 2. OpenAI Provider (`openai`) +**Auto-managed:** +- Endpoint (shows: api.openai.com/v1) +- API Type (auto-detected from model, editable) +- Secret Name: Fixed as `OPENAI_KEY` + +**User-configurable:** +- Model (required, text input with suggestions: gpt-4o, gpt-5-mini, gpt-5.1, o1-preview) +- API Key (via secret modal - see Secret Management below) +- Thinking Level (optional) +- Capabilities (optional) + +#### 3. OpenRouter Provider (`openrouter`) +**Auto-managed:** +- Endpoint (shows: openrouter.ai/api/v1) +- API Type (always openai-chat) +- Secret Name: Fixed as `OPENROUTER_KEY` + +**User-configurable:** +- Model (required, text input - OpenRouter model format) +- API Key (via secret modal) +- Thinking Level (optional) +- Capabilities (optional) + +#### 4. Azure Provider (`azure`) +**Auto-managed:** +- API Version (always v1) +- Endpoint (computed from resource name) +- API Type (auto-detected from model) +- Secret Name: Fixed as `AZURE_KEY` + +**User-configurable:** +- Azure Resource Name (required, validated format) +- Model (required) +- API Key (via secret modal) +- Thinking Level (optional) +- Capabilities (optional) + +#### 5. Azure Legacy Provider (`azure-legacy`) +**Auto-managed:** +- API Version (default: 2025-04-01-preview, editable) +- API Type (always openai-chat) +- Endpoint (computed from resource + deployment + version) +- Secret Name: Fixed as `AZURE_KEY` + +**User-configurable:** +- Azure Resource Name (required, validated) +- Azure Deployment (required) +- Model (required) +- API Key (via secret modal) +- Thinking Level (optional) +- Capabilities (optional) + +#### 6. Google Provider (`google`) +**Auto-managed:** +- Secret Name: Fixed as `GOOGLE_KEY` + +**User-configurable:** +- Model (required) +- API Type (required dropdown) +- Endpoint (required) +- API Key (via secret modal) +- API Version (optional) +- Thinking Level (optional) +- Capabilities (optional) + +#### 7. Custom Provider (`custom`) +**User must specify everything:** +- Model (required) +- API Type (required dropdown) +- Endpoint (required) +- Secret Name (required text input - user defines their own secret name) +- API Key (via secret modal using custom secret name) +- API Version (optional) +- Thinking Level (optional) +- Capabilities (optional) +- Azure Resource Name (optional) +- Azure Deployment (optional) + +### Data Flow + +``` +Load JSON → Parse → Render Visual Editor + ↓ + User Edits Mode → Update fileContentAtom (JSON string) + ↓ + Click Save → Existing save logic validates & writes +``` + +**Simplified Operations:** +1. **Load:** Parse `fileContentAtom` JSON string into mode objects for display +2. **Edit Mode:** Update parsed object → stringify → set `fileContentAtom` → marks as edited +3. **Add Mode:** + - Generate unique key from provider/model or random ID + - Add new mode to parsed object → stringify → set `fileContentAtom` +4. **Delete Mode:** Remove key from parsed object → stringify → set `fileContentAtom` +5. **Save:** Existing `model.saveFile()` handles validation and write + +**Mode Key Generation:** +```typescript +function generateModeKey(provider: string, model: string): string { + // Try semantic key first: provider@model-sanitized + const sanitized = model.toLowerCase() + .replace(/[^a-z0-9]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); + const semanticKey = `${provider}@${sanitized}`; + + // Check for collision, if exists append random suffix + if (existingModes[semanticKey]) { + const randomId = crypto.randomUUID().slice(-6); + return `${provider}@${sanitized}-${randomId}`; + } + return semanticKey; +} +// Examples: openai@gpt-4o, openrouter@claude-3-5-sonnet, azure@custom-fb4a2c +``` + +**Secret Naming Convention:** +```typescript +// Fixed secret names per provider (except custom) +const SECRET_NAMES = { + openai: "OPENAI_KEY", + openrouter: "OPENROUTER_KEY", + azure: "AZURE_KEY", + "azure-legacy": "AZURE_KEY", + google: "GOOGLE_KEY", + // custom provider: user specifies their own secret name +} as const; + +function getSecretName(provider: string, customSecretName?: string): string { + if (provider === "custom") { + return customSecretName || "CUSTOM_API_KEY"; + } + return SECRET_NAMES[provider]; +} +``` + +### Secret Management UI + +**Secret Status Indicator:** +Display next to API Key field for providers that need one: +- ✅ Green check icon: Secret exists and is set +- ⚠️ Warning icon (yellow/orange): Secret not set or empty +- Click icon to open secret modal + +**Secret Modal:** +``` +┌─────────────────────────────────────┐ +│ Set API Key for OpenAI │ +│ │ +│ Secret Name: OPENAI_KEY │ +│ [read-only for non-custom] │ +│ │ +│ API Key: │ +│ [********************] [Show/Hide]│ +│ │ +│ [Cancel] [Save] │ +└─────────────────────────────────────┘ +``` + +**Modal Behavior:** +1. **Open Modal:** Click status icon or "Set API Key" button +2. **Show Secret Name:** + - Non-custom providers: Read-only, shows fixed name + - Custom provider: Editable text input (user specifies) +3. **API Key Input:** + - Masked password field + - Show/Hide toggle button + - Load existing value if secret already exists +4. **Save:** + - Validates not empty + - Calls RPC to set secret + - Updates status icon +5. **Cancel:** Close without changes + +**Integration with Mode Editor:** +- Check secret existence on mode load/select +- Update icon based on RPC `GetSecretsCommand` result +- "Save" button for mode only saves JSON config +- Secret is set immediately via modal (separate from JSON save) + +### Key Features + +#### 1. Mode List +- Display modes sorted by `display:order` (ascending) +- Show icon, name, short description +- Badge showing provider type +- Highlight Wave AI premium modes +- Click to edit + +#### 2. Add New Mode Flow +1. Click "Add New Mode" +2. Enter mode key (validated: alphanumeric, @, -, ., _) +3. Select provider from dropdown +4. Form dynamically updates to show provider-specific fields +5. Fill required fields (marked with *) +6. Save → validates → adds to config → refreshes list + +#### 3. Edit Mode Flow +1. Click mode from list +2. Load mode data into form +3. Provider is fixed (show read-only or with warning about changing) +4. Edit fields +5. Save → validates → updates config → refreshes list + +**Raw JSON Editor Option:** +- "Edit Raw JSON" button in mode editor (available for all modes) +- Opens modal with Monaco editor showing just this mode's JSON +- Validates JSON structure before allowing save +- Useful for: + - Modes without a provider field (edge cases) + - Advanced users who want precise control + - Copying/modifying complex configurations +- Validation checks: + - Valid JSON syntax + - Required fields present (`display:name`, `ai:apitype`, `ai:model`) + - Enum values valid + - Custom error messages for each validation failure + +#### 4. Delete Mode Flow +1. Click mode from list +2. Delete button in editor +3. Confirm dialog +4. Remove from config → save → refresh list + +#### 5. Secret Integration +- For API Token fields, provide two options: + - Direct input (text field, masked) + - Secret reference (dropdown of existing secrets + link to secrets page) +- When secret is selected, store name in `ai:apitokensecretname` +- When direct token, store in `ai:apitoken` + +#### 6. Validation +- **Mode Key:** Must match pattern `^[a-zA-Z0-9_@.-]+$` +- **Required Fields:** `display:name`, `ai:apitype`, `ai:model` +- **Azure Resource Name:** Must match `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$` (1-63 chars) +- **Provider:** Must be one of the valid enum values +- **API Type:** Must be valid enum value +- **Thinking Level:** Must be low/medium/high if present +- **Capabilities:** Must be from valid enum (pdfs, images, tools) + +#### 7. Smart Defaults +When provider changes or model changes: +- Show info about what will be auto-configured +- Display computed endpoint (read-only with info icon) +- Display auto-detected API type (editable with warning) +- Pre-fill common values based on provider + +### UI Components Needed + +#### New Components +```typescript +// Main container +WaveAIVisualContent + +// Left panel +ModeList +├─ ModeListItem (icon, name, provider badge, premium badge, drag handle) +└─ AddModeButton + +// Right panel - viewer +ModeViewer +├─ ModeHeader (name, icon, actions) +├─ DisplaySection (read-only view of display fields) +├─ ProviderSection (read-only view of provider config) +└─ EditButton + +// Right panel - editor +ModeEditor +├─ ProviderSelector (dropdown, only for new modes) +├─ DisplayFieldsForm +├─ ProviderFieldsForm (dynamic based on provider) +│ ├─ WaveProviderForm +│ ├─ OpenAIProviderForm +│ ├─ OpenRouterProviderForm +│ ├─ AzureProviderForm +│ ├─ AzureLegacyProviderForm +│ ├─ GoogleProviderForm +│ └─ CustomProviderForm +└─ ActionButtons (Edit Raw JSON, Delete, Cancel) + +// Modals +RawJSONModal +├─ Title ("Edit Raw JSON: {mode name}") +├─ MonacoEditor (JSON, single mode object) +├─ ValidationErrors (inline display) +└─ Actions (Cancel, Save) + +// Shared components +SecretSelector (dropdown + link to secrets) +InfoTooltip (explains auto-configured fields) +ProviderBadge (visual indicator) +IconPicker (select from available icons) +DragHandle (for reordering modes in list) +``` + +**Drag & Drop for Reordering:** +```typescript +// Reordering updates display:order automatically +function handleModeReorder(draggedKey: string, targetKey: string) { + const modes = parseAIModes(fileContent); + const modesList = Object.entries(modes) + .sort((a, b) => (a[1]["display:order"] || 0) - (b[1]["display:order"] || 0)); + + // Find indices + const draggedIndex = modesList.findIndex(([k]) => k === draggedKey); + const targetIndex = modesList.findIndex(([k]) => k === targetKey); + + // Recalculate display:order for all modes + const newOrder = [...modesList]; + newOrder.splice(draggedIndex, 1); + newOrder.splice(targetIndex, 0, modesList[draggedIndex]); + + // Assign new order values (0, 10, 20, 30...) + newOrder.forEach(([key, mode], index) => { + modes[key] = { ...mode, "display:order": index * 10 }; + }); + + updateFileContent(JSON.stringify(modes, null, 2)); +} +``` + +### Model Extensions (Minimal) + +**No new atoms needed!** Visual editor uses existing `fileContentAtom`: + +```typescript +// Use existing atoms from WaveConfigViewModel: +// - fileContentAtom (contains JSON string) +// - hasEditedAtom (tracks if modified) +// - errorMessageAtom (for errors) + +// Visual editor parses fileContentAtom on render: +function parseAIModes(jsonString: string): Record | null { + try { + return JSON.parse(jsonString); + } catch { + return null; // Show "invalid JSON" error + } +} + +// Updates modify fileContentAtom: +function updateMode(key: string, mode: AIModeConfigType) { + const modes = parseAIModes(globalStore.get(model.fileContentAtom)); + if (!modes) return; + + modes[key] = mode; + const newJson = JSON.stringify(modes, null, 2); + globalStore.set(model.fileContentAtom, newJson); + globalStore.set(model.hasEditedAtom, true); +} + +// Secrets use existing model methods: +// - model.refreshSecrets() - already exists +// - RpcApi.GetSecretsCommand() - check if secret exists +// - RpcApi.SetSecretsCommand() - set secret value +``` + +**Component State (useState):** +```typescript +// In WaveAIVisualContent component: +const [selectedModeKey, setSelectedModeKey] = useState(null); +const [isAddingMode, setIsAddingMode] = useState(false); +const [showSecretModal, setShowSecretModal] = useState(false); +const [secretModalProvider, setSecretModalProvider] = useState(""); +``` + +### Implementation Phases + +#### Phase 1: Foundation & List View +- Parse `fileContentAtom` JSON into modes on render +- Display mode list (left panel, ~300px) + - Built-in modes with 🔒 icon at top + - Custom modes below + - Sort by `display:order` +- Select mode → show in right panel (empty state initially) +- Handle invalid JSON → show error, switch to JSON tab + +#### Phase 2: Built-in Mode Viewer +- Click built-in mode → show read-only details +- Display all fields (display, provider, config) +- "Built-in Mode" badge/banner +- No edit/delete buttons + +#### Phase 3: Custom Mode Editor (Basic) +- Click custom mode → load into editor form +- Display fields (name, icon, order, description) +- Provider field (read-only, badge) +- Model field (text input) +- Save → update `fileContentAtom` JSON +- Cancel → revert to previous selection + +#### Phase 4: Provider-Specific Fields +- Dynamic form based on provider type +- OpenAI: model, thinking level, capabilities +- Azure: resource name, model, thinking, capabilities +- Azure Legacy: resource name, deployment, model +- OpenRouter: model +- Google: model, API type, endpoint +- Custom: everything manual +- Info tooltips for auto-configured fields + +#### Phase 5: Secret Integration +- Check secret existence on mode select +- Display status icon (✅ / ⚠️) +- Click icon → open secret modal +- Secret modal: fixed name (or custom input), password field +- Save secret → immediate RPC call +- Update status icon after save + +#### Phase 6: Add New Mode +- "Add New Mode" button +- Provider dropdown selector +- Auto-generate mode key from provider + model +- Form with provider-specific fields +- Add to modes → update JSON → mark edited +- Select newly created mode + +#### Phase 7: Delete Mode +- Delete button for custom modes only +- Simple confirmation dialog +- Remove from modes → update JSON → deselect + +#### Phase 8: Raw JSON Editor +- "Edit Raw JSON" button in mode editor (all modes) +- Modal with Monaco editor for single mode +- JSON validation before save: + - Syntax check with error highlighting + - Required fields check (`display:name`, `ai:apitype`, `ai:model`) + - Enum validation (provider, apitype, thinkinglevel, capabilities) + - Display specific error messages per validation failure +- Parse validated JSON and update mode in main JSON +- Useful for edge cases (modes without provider) and power users + +#### Phase 9: Drag & Drop Reordering +- Add drag handle icon to custom mode list items +- Implement drag & drop functionality: + - Visual feedback during drag (opacity, cursor) + - Drop target highlighting + - Smooth reordering animation +- On drop: + - Recalculate `display:order` for all affected modes + - Use spacing (0, 10, 20, 30...) for easy manual adjustment + - Update JSON with new order values + - Built-in modes always stay at top (negative order values) + +#### Phase 10: Polish & UX Refinements +- Field validation with inline error messages +- Empty state when no mode selected +- Icon picker dropdown (Font Awesome icons) +- Capabilities checkboxes with descriptions +- Thinking level dropdown with explanations +- Help tooltips throughout +- Keyboard shortcuts (e.g., Ctrl/Cmd+E for raw JSON) +- Loading states for secret checks +- Smooth transitions and animations + +#### Phase 8: Raw JSON Editor +- "Edit Raw JSON" button in mode editor +- Modal with Monaco editor for single mode +- JSON validation before save: + - Syntax check + - Required fields check + - Enum validation + - Display specific error messages +- Parse and update mode in main JSON + +#### Phase 9: Drag & Drop Reordering +- Make mode list items draggable (custom modes only) +- Visual feedback during drag (drag handle icon) +- Drop target highlighting +- On drop: + - Calculate new `display:order` values + - Maintain spacing between modes + - Update all affected modes in JSON + - Preserve built-in modes at top + +#### Phase 10: Polish & UX Refinements +- Field validation (required, format) +- Error messages inline +- Empty state when no mode selected +- Icon picker dropdown +- Capabilities checkboxes +- Thinking level dropdown +- Help tooltips throughout +- Keyboard shortcuts (e.g., Cmd+E for raw JSON) + +### Technical Considerations + +1. **JSON Sync:** Parse/stringify from `fileContentAtom` on every read/write +2. **Validation:** Validate on blur or before updating JSON +3. **Built-in Detection:** Check if key starts with `waveai@` → read-only +4. **Type Safety:** Use `AIModeConfigType` from gotypes.d.ts +5. **State Management:** + - Model atoms for shared state (`fileContentAtom`, `hasEditedAtom`) + - Component useState for UI state (selected mode, modals) +6. **Error Handling:** + - Invalid JSON → show message, disable visual editor + - Parse errors → gracefully handle, don't crash +7. **Performance:** + - Parse JSON on mount and when `fileContentAtom` changes externally + - Debounce frequent updates if needed +8. **Secret Checks:** + - Load secret existence on mode select + - Cache results to avoid repeated RPC calls + +### Testing Strategy + +1. **Unit Tests:** Validation functions, key generation +2. **Integration Tests:** Form submission, backend sync +3. **E2E Tests:** Full add/edit/delete flows +4. **Provider Tests:** Each provider form with various inputs +5. **Edge Cases:** Empty config, invalid JSON, malformed data + +### Documentation Needs + +1. **In-app help:** Tooltips and info bubbles explaining fields +2. **Provider guides:** What each provider needs, where to get API keys +3. **Examples:** Show example configurations for common setups +4. **Troubleshooting:** Common errors and solutions + +## Next Steps + +1. Create detailed mockups/wireframes +2. Implement Phase 1 (basic list view) +3. Add RPC methods if needed for secrets integration +4. Iterate on provider forms +5. Polish and ship + +This design provides a user-friendly way to configure AI modes without directly editing JSON, while still maintaining the power and flexibility of the underlying system. \ No newline at end of file diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 30540e351f..7971511159 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -91,6 +91,9 @@ func doShutdown(reason string) { // watch stdin, kill server if stdin is closed func stdinReadWatch() { + defer func() { + panichandler.PanicHandler("stdinReadWatch", recover()) + }() buf := make([]byte, 1024) for { _, err := os.Stdin.Read(buf) @@ -109,6 +112,9 @@ func startConfigWatcher() { } func telemetryLoop() { + defer func() { + panichandler.PanicHandler("telemetryLoop", recover()) + }() var nextSend int64 time.Sleep(InitialTelemetryWait) for { @@ -120,6 +126,42 @@ func telemetryLoop() { } } +func sendNoTelemetryUpdate(telemetryEnabled bool) { + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + clientData, err := wstore.DBGetSingleton[*waveobj.Client](ctx) + if err != nil { + log.Printf("telemetry update: error getting client data: %v\n", err) + return + } + if clientData == nil { + log.Printf("telemetry update: client data is nil\n") + return + } + err = wcloud.SendNoTelemetryUpdate(ctx, clientData.OID, !telemetryEnabled) + if err != nil { + log.Printf("[error] sending no-telemetry update: %v\n", err) + return + } +} + +func setupTelemetryConfigHandler() { + watcher := wconfig.GetWatcher() + if watcher == nil { + return + } + currentConfig := watcher.GetFullConfig() + currentTelemetryEnabled := currentConfig.Settings.TelemetryEnabled + + watcher.RegisterUpdateHandler(func(newConfig wconfig.FullConfigType) { + newTelemetryEnabled := newConfig.Settings.TelemetryEnabled + if newTelemetryEnabled != currentTelemetryEnabled { + currentTelemetryEnabled = newTelemetryEnabled + go sendNoTelemetryUpdate(newTelemetryEnabled) + } + }) +} + func backupCleanupLoop() { defer func() { panichandler.PanicHandler("backupCleanupLoop", recover()) @@ -232,6 +274,9 @@ func beforeSendActivityUpdate(ctx context.Context) { } func startupActivityUpdate(firstLaunch bool) { + defer func() { + panichandler.PanicHandler("startupActivityUpdate", recover()) + }() ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() activity := wshrpc.ActivityUpdate{Startup: 1} @@ -476,11 +521,17 @@ func main() { maybeStartPprofServer() go stdinReadWatch() go telemetryLoop() + setupTelemetryConfigHandler() go updateTelemetryCountsLoop() go backupCleanupLoop() go startupActivityUpdate(firstLaunch) // must be after startConfigWatcher() blocklogger.InitBlockLogger() - go wavebase.GetSystemSummary() // get this cached (used in AI) + go func() { + defer func() { + panichandler.PanicHandler("GetSystemSummary", recover()) + }() + wavebase.GetSystemSummary() + }() webListener, err := web.MakeTCPListener("web") if err != nil { diff --git a/cmd/testai/main-testai.go b/cmd/testai/main-testai.go index 8e8fcdb3eb..04c3ae91f9 100644 --- a/cmd/testai/main-testai.go +++ b/cmd/testai/main-testai.go @@ -166,7 +166,7 @@ func testOpenAIComp(ctx context.Context, model, message string, tools []uctypes. opts := &uctypes.AIOptsType{ APIType: uctypes.APIType_OpenAIChat, APIToken: apiKey, - BaseURL: "https://api.openai.com/v1/chat/completions", + Endpoint: "https://api.openai.com/v1/chat/completions", Model: model, MaxTokens: 4096, ThinkingLevel: uctypes.ThinkingLevelMedium, @@ -216,7 +216,7 @@ func testOpenRouter(ctx context.Context, model, message string, tools []uctypes. opts := &uctypes.AIOptsType{ APIType: uctypes.APIType_OpenAIChat, APIToken: apiKey, - BaseURL: "https://openrouter.ai/api/v1/chat/completions", + Endpoint: "https://openrouter.ai/api/v1/chat/completions", Model: model, MaxTokens: 4096, ThinkingLevel: uctypes.ThinkingLevelMedium, diff --git a/docs/docs/ai-presets.mdx b/docs/docs/ai-presets.mdx index b8c7b34546..6321dae3ad 100644 --- a/docs/docs/ai-presets.mdx +++ b/docs/docs/ai-presets.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 3.6 id: "ai-presets" -title: "AI Presets" +title: "AI Presets (Deprecated)" --- :::warning Deprecation Notice The AI Widget and its presets are being replaced by [Wave AI](./waveai.mdx). Please refer to the Wave AI documentation for the latest AI features and configuration options. diff --git a/docs/docs/connections.mdx b/docs/docs/connections.mdx index 77dc4aacd6..08a8ac2632 100644 --- a/docs/docs/connections.mdx +++ b/docs/docs/connections.mdx @@ -4,6 +4,8 @@ id: "connections" title: "Connections" --- +import { VersionBadge } from "@site/src/components/versionbadge"; + # Connections Wave allows users to connect to various machines and unify them together in a way that preserves the unique behavior of each. At the moment, this extends to SSH remote connections, local WSL connections, and AWS S3 buckets. @@ -156,6 +158,7 @@ In addition to the regular ssh config file, wave also has its own config file to | ssh:batchmode | A boolean indicating if password and passphrase prompts should be skipped. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored.| | ssh:pubkeyauthentication | A boolean indicating if public key authentication is enabled. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored.| | ssh:passwordauthentication | A boolean indicating if password authentication is enabled. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored. | +| ssh:passwordsecretname | A string specifying the name of a secret stored in the [secret store](/secrets) to use as the SSH password. When set, this password will be automatically used for password authentication instead of prompting the user. | | ssh:kbdinteractiveauthentication | A boolean indicating if keyboard interactive authentication is enabled. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored. | | ssh:preferredauthentications | A list of strings indicating an ordering of different types of authentications. Each authentication type will be tried in order. This supports `"publickey"`, `"keyboard-interactive"`, and `"password"` as valid types. Other types of authentication are not handled and will be skipped. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored.| | ssh:addkeystoagent | A boolean indicating if the keys used for a connection should be added to the ssh agent. Can be used to override the value in `~/.ssh/config` or to set it if the ssh config is being ignored.| diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx index 74967cbb91..37c714e610 100644 --- a/docs/docs/faq.mdx +++ b/docs/docs/faq.mdx @@ -6,25 +6,6 @@ title: "FAQ" # FAQ -### How do I enable Claude Code support with Shift+Enter? - -Wave supports Claude Code and similar AI coding tools that expect Shift+Enter to send an escape sequence + newline (`\u001b\n`) instead of a regular carriage return. This can be enabled using the `term:shiftenternewline` configuration setting. - -To enable this globally for all terminals: -```bash -wsh setconfig term:shiftenternewline=true -``` - -To enable this for just a specific terminal block: -```bash -wsh setmeta term:shiftenternewline=true -``` - -You can also set this in your [settings.json](./config) file: -```json -"term:shiftenternewline": true -``` - ### How can I see the block numbers? The block numbers will appear when you hold down Ctrl-Shift (and disappear once you release the key combo). @@ -48,87 +29,6 @@ Just remember in JSON, backslashes need to be escaped. So add this to your [sett `wsh` is an internal CLI for extending control over Wave to the command line, you can learn more about it [here](./wsh). To prevent misuse by other applications, `wsh` requires an access token provided by Wave to work and will not function outside of the app. -### How do I make new blocks or splits inherit my shell’s current directory? - -Wave uses a special escape sequence (OSC 7) to track the shell’s working directory and maintain the working directory of new terminal blocks and splits. Wave listens for these sequences to update its `cmd:cwd` metadata. That metadata is copied to new blocks when you: - -- Open a new terminal block (Alt N / Cmd N) -- Split a pane (Cmd D / Cmd Shift D) - -Not all shells emit this escape sequence, so new blocks or splits may start in your home directory instead. To ensure your shell emits the OSC 7 escape sequence, add the following to your shell startup/config file and restart Wave (or source your config). - -#### Bash - -Add to `~/.bashrc` or `~/.bash_profile`: - -```bash -# Emit OSC 7 on each prompt to tell terminal about new working directory -__update_cwd() { - # Only run in interactive shells - [[ $- == *i* ]] || return - # Only run if attached to a terminal - [ -t 1 ] || return - # Redirect to tty so output doesn't show in shell - printf "\033]7;file://%s%s\007" "$HOSTNAME" "${PWD// /%20}" > /dev/tty -} -if [[ -n "$PROMPT_COMMAND" ]]; then - export PROMPT_COMMAND="__update_cwd; $PROMPT_COMMAND" -else - export PROMPT_COMMAND="__update_cwd" -fi -``` - -#### Zsh - -Add to `~/.zshrc`: - -```zsh -# Emit OSC 7 escape on directory change and prompt -function _wave_emit_cwd() { - printf "\033]7;file://%s%s\007" "$HOSTNAME" "${PWD// /%20}" > /dev/tty -} -autoload -U add-zsh-hook -add-zsh-hook chpwd _wave_emit_cwd -add-zsh-hook precmd _wave_emit_cwd -``` - -#### Fish - -> Fish shell (v4.0.0 and later) emits OSC 7 by default—no config required. - -For older Fish versions, add to `~/.config/fish/config.fish`: - -```fish -# Emit OSC 7 on each PWD change -function _wave_emit_cwd --on-variable PWD - printf "\033]7;file://%s%s\007" (hostname) (string replace ' ' '%20' $PWD) > /dev/tty -end -``` - -After configuring, open a new block or split (Alt T / Cmd T, Alt N / Cmd N, Cmd D / Cmd Shift D) and verify blocks start in your current directory. - -#### Verifying Current Directory Preservation - -1. Open a Wave terminal block. -2. `cd` into a project folder, e.g. `cd ~/projects/foo`. -3. Right-click on the block's title bar and select "Copy BlockId" to retrieve the block’s ID. -4. Use the copied BlockId to retrieve the block’s metadata: - - ```bash - # Example: replace BLOCK_ID with your actual block reference - wsh getmeta --block BLOCK_ID - ``` - -5. Confirm the output JSON contains a `cmd:cwd` field, for example: - - ```json - { - "cmd:cwd": "/Users/you/projects/foo", - ... - } - ``` - -6. Open a new block or split the pane—both should start in `/Users/you/projects/foo`. ## Why does Wave warn me about ARM64 translation when it launches? diff --git a/docs/docs/secrets.mdx b/docs/docs/secrets.mdx new file mode 100644 index 0000000000..ab6f7902bc --- /dev/null +++ b/docs/docs/secrets.mdx @@ -0,0 +1,147 @@ +--- +sidebar_position: 3.2 +id: "secrets" +title: "Secrets" +--- + +import { VersionBadge } from "@site/src/components/versionbadge"; + +# Secrets + + + +Wave Terminal provides a secure way to store sensitive information like passwords, API keys, and tokens. Secrets are stored encrypted in your system's native keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service), ensuring your sensitive data remains protected. + +## Why Use Secrets? + +Secrets in Wave Terminal allow you to: + +- **Store SSH passwords** - Automatically authenticate to SSH connections without typing passwords +- **Manage API keys** - Keep API tokens, keys, and credentials secure +- **Share across sessions** - Access your secrets from any terminal block or remote connection +- **Avoid plaintext storage** - Never store sensitive data in configuration files or scripts + +## Opening the Secrets UI + +There are several ways to access the secrets management interface: + +1. **From the widgets bar** (recommended): + - Click the **** settings icon on the widgets bar + - Select **Secrets** from the menu + +2. **From the command line:** + ```bash + wsh secret ui + ``` + + +The secrets UI provides a visual interface to view, add, edit, and delete secrets. + +## Managing Secrets via CLI + +Wave Terminal provides a complete CLI for managing secrets from any terminal block: + +```bash +# List all secret names (not values) +wsh secret list + +# Get a specific secret value +wsh secret get MY_SECRET_NAME + +# Set a secret (format: name=value, no spaces around =) +wsh secret set GITHUB_TOKEN=ghp_xxxxxxxxxx +wsh secret set DB_PASSWORD=super_secure_password + +# Delete a secret +wsh secret delete MY_SECRET_NAME +``` + +## Secret Naming Rules + +Secret names must match the pattern: `^[A-Za-z][A-Za-z0-9_]*$` + +This means: +- Must start with a letter (A-Z or a-z) +- Can only contain letters, numbers, and underscores +- Cannot contain spaces or special characters + +**Valid names:** `MY_SECRET`, `ApiKey`, `ssh_password_1` +**Invalid names:** `123_SECRET`, `my-secret`, `secret name` + +## Using Secrets with SSH Connections + + + +Secrets can be used to automatically provide passwords for SSH connections, eliminating the need to type passwords repeatedly. + +### Configure in connections.json + +Add the `ssh:passwordsecretname` field to your connection configuration: + +```json +{ + "myserver": { + "ssh:hostname": "example.com", + "ssh:user": "myuser", + "ssh:passwordsecretname": "SERVER_PASSWORD" + } +} +``` + +Then store your password as a secret: + +```bash +wsh secret set SERVER_PASSWORD=my_actual_password +``` + +Now when Wave connects to `myserver`, it will automatically use the password from your secret store instead of prompting you. + +### Benefits + +- **Security**: Password stored encrypted in your system keychain +- **Convenience**: No need to type passwords for each connection +- **Flexibility**: Update passwords by changing the secret, not the configuration + +## Security Considerations + +- **Encrypted Storage**: Secrets are stored encrypted in your Wave configuration directory. The encryption key itself is protected by your operating system's secure credential storage (macOS Keychain, Windows Credential Manager, or Linux Secret Service). + +- **No Plaintext**: Secrets are never stored unencrypted in logs or accessible files. + +- **Access Control**: Secrets are only accessible to Wave Terminal. + + +## Storage Backend + +Wave Terminal automatically detects and uses the appropriate secret storage backend for your operating system: + +- **macOS**: Uses the macOS Keychain +- **Windows**: Uses Windows Credential Manager +- **Linux**: Uses the Secret Service API (freedesktop.org specification) + +:::warning Linux Secret Storage +On Linux systems, Wave requires a compatible secret service backend (typically GNOME Keyring or KWallet). These are usually pre-installed with your desktop environment. If no compatible backend is detected, you won't be able to set secrets, and the UI will display a warning. +::: + +## Troubleshooting + +### "No appropriate secret manager found" + +This error occurs on Linux when no compatible secret service backend is available. Install GNOME Keyring or KWallet and ensure the secret service is running. + +### Secret not found + +Ensure the secret name is spelled correctly (names are case-sensitive) and that the secret exists: + +```bash +wsh secret list +``` + +### Permission denied on Linux + +The secret service may require you to unlock your keyring. This typically happens after login. Consult your desktop environment's documentation for keyring management. + +## Related Documentation + +- [Connections](/connections) - Learn about SSH connections and configuration +- [wsh Command Reference](/wsh-reference#secret) - Complete CLI command documentation for secrets \ No newline at end of file diff --git a/docs/docs/waveai-modes.mdx b/docs/docs/waveai-modes.mdx new file mode 100644 index 0000000000..0794a61a3a --- /dev/null +++ b/docs/docs/waveai-modes.mdx @@ -0,0 +1,359 @@ +--- +sidebar_position: 1.6 +id: "waveai-modes" +title: "Wave AI (Local Models)" +--- + +Wave AI supports custom AI modes that allow you to use local models, custom API endpoints, and alternative AI providers. This gives you complete control over which models and providers you use with Wave's AI features. + +## Configuration Overview + +AI modes are configured in `~/.config/waveterm/waveai.json`. + +**To edit using the UI:** +1. Click the settings (gear) icon in the widget bar +2. Select "Settings" from the menu +3. Choose "Wave AI Modes" from the settings sidebar + +**Or edit from the command line:** +```bash +wsh editconfig waveai.json +``` + +Each mode defines a complete AI configuration including the model, API endpoint, authentication, and display properties. + +## Provider-Based Configuration + +Wave AI now supports provider-based configuration which automatically applies sensible defaults for common providers. By specifying the `ai:provider` field, you can significantly simplify your configuration as the system will automatically set up endpoints, API types, and secret names. + +### Supported Providers + +- **`openai`** - OpenAI API (automatically configures endpoint and secret name) +- **`openrouter`** - OpenRouter API (automatically configures endpoint and secret name) +- **`google`** - Google AI (Gemini) +- **`azure`** - Azure OpenAI Service (modern API) +- **`azure-legacy`** - Azure OpenAI Service (legacy deployment API) +- **`custom`** - Custom API endpoint (fully manual configuration) + +### Supported API Types + +Wave AI supports two OpenAI-compatible API types: + +- **`openai-chat`**: Uses the `/v1/chat/completions` endpoint (most common) +- **`openai-responses`**: Uses the `/v1/responses` endpoint (modern API for GPT-5+ models) + +## Configuration Structure + +### Minimal Configuration (with Provider) + +```json +{ + "mode-key": { + "display:name": "Display Name", + "ai:provider": "openrouter", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct" + } +} +``` + +### Full Configuration (all fields) + +```json +{ + "mode-key": { + "display:name": "Display Name", + "display:order": 1, + "display:icon": "icon-name", + "display:description": "Full description", + "ai:provider": "custom", + "ai:apitype": "openai-chat", + "ai:model": "model-name", + "ai:thinkinglevel": "medium", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:azureapiversion": "v1", + "ai:apitoken": "your-token", + "ai:apitokensecretname": "PROVIDER_KEY", + "ai:azureresourcename": "your-resource", + "ai:azuredeployment": "your-deployment", + "ai:capabilities": ["tools", "images", "pdfs"] + } +} +``` + +### Field Reference + +| Field | Required | Description | +|-------|----------|-------------| +| `display:name` | Yes | Name shown in the AI mode selector | +| `display:order` | No | Sort order in the selector (lower numbers first) | +| `display:icon` | No | Icon identifier for the mode | +| `display:description` | No | Full description of the mode | +| `ai:provider` | No | Provider preset: `openai`, `openrouter`, `google`, `azure`, `azure-legacy`, `custom` | +| `ai:apitype` | No | API type: `openai-chat` or `openai-responses` (defaults to `openai-chat` if not specified) | +| `ai:model` | No | Model identifier (required for most providers) | +| `ai:thinkinglevel` | No | Thinking level: `low`, `medium`, or `high` | +| `ai:endpoint` | No | Full API endpoint URL (auto-set by provider when available) | +| `ai:azureapiversion` | No | Azure API version (for `azure-legacy` provider, defaults to `2025-04-01-preview`) | +| `ai:apitoken` | No | API key/token (not recommended - use secrets instead) | +| `ai:apitokensecretname` | No | Name of secret containing API token (auto-set by provider) | +| `ai:azureresourcename` | No | Azure resource name (for Azure providers) | +| `ai:azuredeployment` | No | Azure deployment name (for `azure-legacy` provider) | +| `ai:capabilities` | No | Array of supported capabilities: `"tools"`, `"images"`, `"pdfs"` | +| `waveai:cloud` | No | Internal - for Wave Cloud AI configuration only | +| `waveai:premium` | No | Internal - for Wave Cloud AI configuration only | + +### AI Capabilities + +The `ai:capabilities` field specifies what features the AI mode supports: + +- **`tools`** - Enables AI tool usage for file reading/writing, shell integration, and widget interaction +- **`images`** - Allows image attachments in chat (model can view uploaded images) +- **`pdfs`** - Allows PDF file attachments in chat (model can read PDF content) + +Most models support `tools` and can benefit from it. Vision-capable models should include `images`. Not all models support PDFs, so only include `pdfs` if your model can process them. + +## Local Model Examples + +### Ollama + +[Ollama](https://ollama.ai) provides an OpenAI-compatible API for running models locally: + +```json +{ + "ollama-llama": { + "display:name": "Ollama - Llama 3.3", + "display:order": 1, + "display:icon": "llama", + "display:description": "Local Llama 3.3 70B model via Ollama", + "ai:apitype": "openai-chat", + "ai:model": "llama3.3:70b", + "ai:thinkinglevel": "normal", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + } +} +``` + +:::tip +The `ai:apitoken` field is required but Ollama ignores it - you can set it to any value like `"ollama"`. +::: + +### LM Studio + +[LM Studio](https://lmstudio.ai) provides a local server that can run various models: + +```json +{ + "lmstudio-qwen": { + "display:name": "LM Studio - Qwen", + "display:order": 2, + "display:icon": "server", + "display:description": "Local Qwen model via LM Studio", + "ai:apitype": "openai-chat", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct", + "ai:thinkinglevel": "normal", + "ai:endpoint": "http://localhost:1234/v1/chat/completions", + "ai:apitoken": "not-needed" + } +} +``` + +### Jan + +[Jan](https://jan.ai) is another local AI runtime with OpenAI API compatibility: + +```json +{ + "jan-local": { + "display:name": "Jan", + "display:order": 3, + "display:icon": "server", + "display:description": "Local model via Jan", + "ai:apitype": "openai-chat", + "ai:model": "your-model-name", + "ai:thinkinglevel": "normal", + "ai:endpoint": "http://localhost:1337/v1/chat/completions", + "ai:apitoken": "not-needed" + } +} +``` + +## Cloud Provider Examples + +### OpenAI + +Using the `openai` provider automatically configures the endpoint and secret name: + +```json +{ + "openai-gpt4o": { + "display:name": "GPT-4o", + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +The provider automatically sets: +- `ai:endpoint` to `https://api.openai.com/v1/chat/completions` +- `ai:apitype` to `openai-chat` (or `openai-responses` for GPT-5+ models) +- `ai:apitokensecretname` to `OPENAI_KEY` (store your OpenAI API key with this name) + +For newer models like GPT-4.1 or GPT-5, the API type is automatically determined: + +```json +{ + "openai-gpt41": { + "display:name": "GPT-4.1", + "ai:provider": "openai", + "ai:model": "gpt-4.1" + } +} +``` + +### OpenRouter + +[OpenRouter](https://openrouter.ai) provides access to multiple AI models. Using the `openrouter` provider simplifies configuration: + +```json +{ + "openrouter-qwen": { + "display:name": "OpenRouter - Qwen", + "ai:provider": "openrouter", + "ai:model": "qwen/qwen-2.5-coder-32b-instruct" + } +} +``` + +The provider automatically sets: +- `ai:endpoint` to `https://openrouter.ai/api/v1/chat/completions` +- `ai:apitype` to `openai-chat` +- `ai:apitokensecretname` to `OPENROUTER_KEY` (store your OpenRouter API key with this name) + +### Azure OpenAI (Modern API) + +For the modern Azure OpenAI API, use the `azure` provider: + +```json +{ + "azure-gpt4": { + "display:name": "Azure GPT-4", + "ai:provider": "azure", + "ai:model": "gpt-4", + "ai:azureresourcename": "your-resource-name" + } +} +``` + +The provider automatically sets: +- `ai:endpoint` to `https://your-resource-name.openai.azure.com/openai/v1/chat/completions` (or `/responses` for newer models) +- `ai:apitype` based on the model +- `ai:apitokensecretname` to `AZURE_OPENAI_KEY` (store your Azure OpenAI key with this name) + +### Azure OpenAI (Legacy Deployment API) + +For legacy Azure deployments, use the `azure-legacy` provider: + +```json +{ + "azure-legacy-gpt4": { + "display:name": "Azure GPT-4 (Legacy)", + "ai:provider": "azure-legacy", + "ai:azureresourcename": "your-resource-name", + "ai:azuredeployment": "your-deployment-name" + } +} +``` + +The provider automatically constructs the full endpoint URL and sets the API version (defaults to `2025-04-01-preview`). You can override the API version with `ai:azureapiversion` if needed. + +## Using Secrets for API Keys + +Instead of storing API keys directly in the configuration, you should use Wave's secret store to keep your credentials secure. Secrets are stored encrypted using your system's native keychain. + +### Storing an API Key + +**Using the Secrets UI (recommended):** +1. Click the settings (gear) icon in the widget bar +2. Select "Secrets" from the menu +3. Click "Add New Secret" +4. Enter the secret name (e.g., `OPENAI_API_KEY`) and your API key +5. Click "Save" + +**Or from the command line:** +```bash +wsh secret set OPENAI_KEY=sk-xxxxxxxxxxxxxxxx +wsh secret set OPENROUTER_KEY=sk-xxxxxxxxxxxxxxxx +``` + +### Referencing the Secret + +When using providers like `openai` or `openrouter`, the secret name is automatically set. Just ensure the secret exists with the correct name: + +```json +{ + "my-openai-mode": { + "display:name": "OpenAI GPT-4o", + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +The `openai` provider automatically looks for the `OPENAI_KEY` secret. See the [Secrets documentation](./secrets.mdx) for more information on managing secrets securely in Wave. + +## Multiple Modes Example + +You can define multiple AI modes and switch between them easily: + +```json +{ + "ollama-llama": { + "display:name": "Ollama - Llama 3.3", + "display:order": 1, + "ai:model": "llama3.3:70b", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + }, + "ollama-codellama": { + "display:name": "Ollama - CodeLlama", + "display:order": 2, + "ai:model": "codellama:34b", + "ai:endpoint": "http://localhost:11434/v1/chat/completions", + "ai:apitoken": "ollama" + }, + "openai-gpt4o": { + "display:name": "GPT-4o", + "display:order": 10, + "ai:provider": "openai", + "ai:model": "gpt-4o" + } +} +``` + +## Troubleshooting + +### Connection Issues + +If Wave can't connect to your model server: + +1. **For cloud providers with `ai:provider` set**: Ensure you have the correct secret stored (e.g., `OPENAI_KEY`, `OPENROUTER_KEY`) +2. **For local/custom endpoints**: Verify the server is running (`curl http://localhost:11434/v1/models` for Ollama) +3. Check the `ai:endpoint` is the complete endpoint URL including the path (e.g., `http://localhost:11434/v1/chat/completions`) +4. Verify the `ai:apitype` matches your server's API (defaults are usually correct when using providers) +5. Check firewall settings if using a non-localhost address + +### Model Not Found + +If you get "model not found" errors: + +1. Verify the model name matches exactly what your server expects +2. For Ollama, use `ollama list` to see available models +3. Some servers require prefixes or specific naming formats + +### API Type Selection + +- The API type defaults to `openai-chat` if not specified, which works for most providers +- Use `openai-chat` for Ollama, LM Studio, custom endpoints, and most cloud providers +- Use `openai-responses` for newer OpenAI models (GPT-5+) or when your provider specifically requires it +- Provider presets automatically set the correct API type when needed diff --git a/docs/src/components/versionbadge.css b/docs/src/components/versionbadge.css new file mode 100644 index 0000000000..4883d04aa0 --- /dev/null +++ b/docs/src/components/versionbadge.css @@ -0,0 +1,18 @@ +.version-badge { + display: inline-block; + padding: 0.125rem 0.5rem; + margin-left: 0.25rem; + font-size: 0.75rem; + font-weight: 600; + line-height: 1.5; + border-radius: 0.25rem; + background-color: var(--ifm-color-primary-lightest); + color: var(--ifm-background-color); + vertical-align: middle; + white-space: nowrap; +} + +[data-theme="dark"] .version-badge { + background-color: var(--ifm-color-primary-dark); + color: var(--ifm-background-color); +} \ No newline at end of file diff --git a/docs/src/components/versionbadge.tsx b/docs/src/components/versionbadge.tsx new file mode 100644 index 0000000000..36903ce8bd --- /dev/null +++ b/docs/src/components/versionbadge.tsx @@ -0,0 +1,9 @@ +import "./versionbadge.css"; + +interface VersionBadgeProps { + version: string; +} + +export function VersionBadge({ version }: VersionBadgeProps) { + return {version}; +} \ No newline at end of file diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts index 36efa8ec65..84e9303220 100644 --- a/emain/emain-menu.ts +++ b/emain/emain-menu.ts @@ -412,6 +412,7 @@ function convertMenuDefArrToMenu( wc.send("contextmenu-click", menuDef.id); }, checked: menuDef.checked, + enabled: menuDef.enabled, }; if (menuDef.submenu != null) { menuItemTemplate.submenu = convertMenuDefArrToMenu(webContents, menuDef.submenu); diff --git a/frontend/app/aipanel/ai-utils.ts b/frontend/app/aipanel/ai-utils.ts index 1477db6af5..fce9a7194d 100644 --- a/frontend/app/aipanel/ai-utils.ts +++ b/frontend/app/aipanel/ai-utils.ts @@ -1,6 +1,8 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { sortByDisplayOrder } from "@/util/util"; + const TextFileLimit = 200 * 1024; // 200KB const PdfLimit = 5 * 1024 * 1024; // 5MB const ImageLimit = 10 * 1024 * 1024; // 10MB @@ -529,3 +531,44 @@ export const createImagePreview = async (file: File): Promise => img.src = url; }); }; + + +/** + * Filter and organize AI mode configs into Wave and custom provider groups + * Returns organized configs that should be displayed based on settings and premium status + */ +export interface FilteredAIModeConfigs { + waveProviderConfigs: Array<{ mode: string } & AIModeConfigType>; + otherProviderConfigs: Array<{ mode: string } & AIModeConfigType>; + shouldShowCloudModes: boolean; +} + +export const getFilteredAIModeConfigs = ( + aiModeConfigs: Record, + showCloudModes: boolean, + inBuilder: boolean, + hasPremium: boolean +): FilteredAIModeConfigs => { + const hideQuick = inBuilder && hasPremium; + + const allConfigs = Object.entries(aiModeConfigs) + .map(([mode, config]) => ({ mode, ...config })) + .filter((config) => !(hideQuick && config.mode === "waveai@quick")); + + const otherProviderConfigs = allConfigs + .filter((config) => config["ai:provider"] !== "wave") + .sort(sortByDisplayOrder); + + const hasCustomModels = otherProviderConfigs.length > 0; + const shouldShowCloudModes = showCloudModes || !hasCustomModels; + + const waveProviderConfigs = shouldShowCloudModes + ? allConfigs.filter((config) => config["ai:provider"] === "wave").sort(sortByDisplayOrder) + : []; + + return { + waveProviderConfigs, + otherProviderConfigs, + shouldShowCloudModes, + }; +}; diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index e6fb70ce11..1c9dea2b66 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -223,7 +223,7 @@ export const AIMessage = memo(({ message, isStreaming }: AIMessageProps) => { className={cn( "px-2 rounded-lg [&>*:first-child]:!mt-0", message.role === "user" - ? "py-2 bg-accent-800 text-white max-w-[calc(100%-50px)] @w450:max-w-[calc(100%-105px)]" + ? "py-2 bg-accent-800 text-white max-w-[calc(100%-50px)]" : "min-w-[min(100%,500px)]" )} > diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index d5ec9d3063..a30bc0136e 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -1,10 +1,11 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { atoms } from "@/app/store/global"; -import { cn, makeIconClass } from "@/util/util"; +import { atoms, createBlock, getSettingsKeyAtom } from "@/app/store/global"; +import { cn, fireAndForget, makeIconClass } from "@/util/util"; import { useAtomValue } from "jotai"; import { memo, useRef, useState } from "react"; +import { getFilteredAIModeConfigs } from "./ai-utils"; import { WaveAIModel } from "./waveai-model"; export const AIModeDropdown = memo(() => { @@ -12,20 +13,21 @@ export const AIModeDropdown = memo(() => { const aiMode = useAtomValue(model.currentAIMode); const aiModeConfigs = useAtomValue(model.aiModeConfigs); const rateLimitInfo = useAtomValue(atoms.waveAIRateLimitInfoAtom); + const showCloudModes = useAtomValue(getSettingsKeyAtom("waveai:showcloudmodes")); + const defaultMode = useAtomValue(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced"; const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; - const hideQuick = model.inBuilder && hasPremium; - const sortedConfigs = Object.entries(aiModeConfigs) - .map(([mode, config]) => ({ mode, ...config })) - .sort((a, b) => { - const orderDiff = (a["display:order"] || 0) - (b["display:order"] || 0); - if (orderDiff !== 0) return orderDiff; - return (a["display:name"] || "").localeCompare(b["display:name"] || ""); - }) - .filter((config) => !(hideQuick && config.mode === "waveai@quick")); + const { waveProviderConfigs, otherProviderConfigs } = getFilteredAIModeConfigs( + aiModeConfigs, + showCloudModes, + model.inBuilder, + hasPremium + ); + + const hasBothModeTypes = waveProviderConfigs.length > 0 && otherProviderConfigs.length > 0; const handleSelect = (mode: string) => { const config = aiModeConfigs[mode]; @@ -37,13 +39,13 @@ export const AIModeDropdown = memo(() => { setIsOpen(false); }; - let currentMode = aiMode || "waveai@balanced"; + let currentMode = aiMode || defaultMode; const currentConfig = aiModeConfigs[currentMode]; if (currentConfig) { if (!hasPremium && currentConfig["waveai:premium"]) { currentMode = "waveai@quick"; } - if (hideQuick && currentMode === "waveai@quick") { + if (model.inBuilder && hasPremium && currentMode === "waveai@quick") { currentMode = "waveai@balanced"; } } @@ -53,7 +55,7 @@ export const AIModeDropdown = memo(() => { "display:icon": "question", }; - return ( + return (
+ ); + })} + {hasBothModeTypes && ( +
+ )} + {hasBothModeTypes && ( +
+ Custom +
+ )} + {otherProviderConfigs.map((config, index) => { + const isFirst = index === 0 && !hasBothModeTypes; + const isLast = index === otherProviderConfigs.length - 1; + const isDisabled = !hasPremium && config["waveai:premium"]; + const isSelected = currentMode === config.mode; + return ( + ); })} +
+
)} diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index 05060b5e64..2c4766f90e 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -1,9 +1,10 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { getFilteredAIModeConfigs } from "@/app/aipanel/ai-utils"; import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils"; import { ContextMenuModel } from "@/app/store/contextmenu"; -import { atoms, isDev } from "@/app/store/global"; +import { atoms, getSettingsKeyAtom, isDev } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; @@ -41,49 +42,76 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo const rateLimitInfo = globalStore.get(atoms.waveAIRateLimitInfoAtom); const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; + const aiModeConfigs = globalStore.get(model.aiModeConfigs); + const showCloudModes = globalStore.get(getSettingsKeyAtom("waveai:showcloudmodes")); const currentAIMode = rtInfo?.["waveai:mode"] ?? (hasPremium ? "waveai@balanced" : "waveai@quick"); const defaultTokens = model.inBuilder ? 24576 : 4096; const currentMaxTokens = rtInfo?.["waveai:maxoutputtokens"] ?? defaultTokens; - const aiModeSubmenu: ContextMenuItem[] = [ - { - label: "Quick (gpt-5-mini)", - type: "checkbox", - checked: currentAIMode === "waveai@quick", - click: () => { - RpcApi.SetRTInfoCommand(TabRpcClient, { - oref: model.orefContext, - data: { "waveai:mode": "waveai@quick" }, - }); - }, - }, - { - label: hasPremium ? "Balanced (gpt-5.1, low thinking)" : "Balanced (premium)", - type: "checkbox", - checked: currentAIMode === "waveai@balanced", - enabled: hasPremium, - click: () => { - if (!hasPremium) return; - RpcApi.SetRTInfoCommand(TabRpcClient, { - oref: model.orefContext, - data: { "waveai:mode": "waveai@balanced" }, - }); - }, - }, - { - label: hasPremium ? "Deep (gpt-5.1, full thinking)" : "Deep (premium)", - type: "checkbox", - checked: currentAIMode === "waveai@deep", - enabled: hasPremium, - click: () => { - if (!hasPremium) return; - RpcApi.SetRTInfoCommand(TabRpcClient, { - oref: model.orefContext, - data: { "waveai:mode": "waveai@deep" }, - }); - }, - }, - ]; + const { waveProviderConfigs, otherProviderConfigs } = getFilteredAIModeConfigs( + aiModeConfigs, + showCloudModes, + model.inBuilder, + hasPremium + ); + + const aiModeSubmenu: ContextMenuItem[] = []; + + if (waveProviderConfigs.length > 0) { + aiModeSubmenu.push({ + label: "Wave AI Modes", + type: "header", + enabled: false, + }); + + waveProviderConfigs.forEach(({ mode, ...config }) => { + const isPremium = config["waveai:premium"] === true; + const isEnabled = !isPremium || hasPremium; + aiModeSubmenu.push({ + label: config["display:name"] || mode, + type: "checkbox", + checked: currentAIMode === mode, + enabled: isEnabled, + click: () => { + if (!isEnabled) return; + RpcApi.SetRTInfoCommand(TabRpcClient, { + oref: model.orefContext, + data: { "waveai:mode": mode }, + }); + }, + }); + }); + } + + if (otherProviderConfigs.length > 0) { + if (waveProviderConfigs.length > 0) { + aiModeSubmenu.push({ type: "separator" }); + } + + aiModeSubmenu.push({ + label: "Custom Modes", + type: "header", + enabled: false, + }); + + otherProviderConfigs.forEach(({ mode, ...config }) => { + const isPremium = config["waveai:premium"] === true; + const isEnabled = !isPremium || hasPremium; + aiModeSubmenu.push({ + label: config["display:name"] || mode, + type: "checkbox", + checked: currentAIMode === mode, + enabled: isEnabled, + click: () => { + if (!isEnabled) return; + RpcApi.SetRTInfoCommand(TabRpcClient, { + oref: model.orefContext, + data: { "waveai:mode": mode }, + }); + }, + }); + }); + } const maxTokensSubmenu: ContextMenuItem[] = []; diff --git a/frontend/app/aipanel/aipanelmessages.tsx b/frontend/app/aipanel/aipanelmessages.tsx index 3d3ae0d912..1c55f1f071 100644 --- a/frontend/app/aipanel/aipanelmessages.tsx +++ b/frontend/app/aipanel/aipanelmessages.tsx @@ -58,10 +58,10 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane return (
-
+
{messages.map((message, index) => { diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index 34e11ec5ce..270796c4e4 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -8,7 +8,7 @@ import { WaveUIMessagePart, } from "@/app/aipanel/aitypes"; import { FocusManager } from "@/app/store/focusManager"; -import { atoms, createBlock, getOrefMetaKeyAtom } from "@/app/store/global"; +import { atoms, createBlock, getOrefMetaKeyAtom, getSettingsKeyAtom } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import * as WOS from "@/app/store/wos"; import { RpcApi } from "@/app/store/wshclientapi"; @@ -77,6 +77,8 @@ export class WaveAIModel { private constructor(orefContext: ORef, inBuilder: boolean) { this.orefContext = orefContext; this.inBuilder = inBuilder; + const defaultMode = globalStore.get(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced"; + this.currentAIMode = jotai.atom(defaultMode); this.chatId = jotai.atom(null) as jotai.PrimitiveAtom; this.modelAtom = jotai.atom((get) => { @@ -365,7 +367,8 @@ export class WaveAIModel { } globalStore.set(this.chatId, chatIdValue); - const aiModeValue = rtInfo?.["waveai:mode"] ?? "waveai@balanced"; + const defaultMode = globalStore.get(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced"; + const aiModeValue = rtInfo?.["waveai:mode"] ?? defaultMode; globalStore.set(this.currentAIMode, aiModeValue); try { diff --git a/frontend/app/modals/conntypeahead.tsx b/frontend/app/modals/conntypeahead.tsx index bee43cb03d..b5e21c2257 100644 --- a/frontend/app/modals/conntypeahead.tsx +++ b/frontend/app/modals/conntypeahead.tsx @@ -272,11 +272,10 @@ function getConnectionsEditItem( onSelect: () => { util.fireAndForget(async () => { globalStore.set(changeConnModalAtom, false); - const path = `${getApi().getConfigDir()}/connections.json`; const blockDef: BlockDef = { meta: { - view: "preview", - file: path, + view: "waveconfig", + file: "connections.json", }, }; await createBlock(blockDef, false, true); diff --git a/frontend/app/modals/userinputmodal.scss b/frontend/app/modals/userinputmodal.scss deleted file mode 100644 index c630422cbc..0000000000 --- a/frontend/app/modals/userinputmodal.scss +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -.userinput-header { - font-weight: bold; - color: var(--main-text-color); - padding-bottom: 10px; -} - -.userinput-body { - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 1rem; - margin: 0 1rem 1rem 1rem; - max-width: 500px; - - font: var(--fixed-font); - color: var(--main-text-color); - - .userinput-markdown { - color: inherit; - } - - .userinput-text { - } - - .userinput-inputbox { - resize: none; - background-color: var(--panel-bg-color); - border-radius: 6px; - margin: 0; - border: var(--border-color); - padding: 5px 0 5px 16px; - min-height: 30px; - color: inherit; - - &:hover { - cursor: text; - } - - &:focus { - outline-color: var(--accent-color); - } - } - - .userinput-checkbox-container { - display: flex; - flex-direction: column; - gap: 6px; - - .userinput-checkbox-row { - display: flex; - align-items: center; - gap: 6px; - - .userinput-checkbox { - accent-color: var(--accent-color); - } - } - } -} diff --git a/frontend/app/modals/userinputmodal.tsx b/frontend/app/modals/userinputmodal.tsx index d277a73236..fc97a185ee 100644 --- a/frontend/app/modals/userinputmodal.tsx +++ b/frontend/app/modals/userinputmodal.tsx @@ -8,7 +8,6 @@ import * as keyutil from "@/util/keyutil"; import { fireAndForget } from "@/util/util"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { UserInputService } from "../store/services"; -import "./userinputmodal.scss"; const UserInputModal = (userInputRequest: UserInputRequest) => { const [responseText, setResponseText] = useState(""); @@ -68,21 +67,22 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { (waveEvent: WaveKeyboardEvent): boolean => { if (keyutil.checkKeyPressed(waveEvent, "Escape")) { handleSendErrResponse(); - return; + return true; } if (keyutil.checkKeyPressed(waveEvent, "Enter")) { handleSubmit(); return true; } + return false; }, [handleSendErrResponse, handleSubmit] ); const queryText = useMemo(() => { if (userInputRequest.markdown) { - return ; + return ; } - return {userInputRequest.querytext}; + return {userInputRequest.querytext}; }, [userInputRequest.markdown, userInputRequest.querytext]); const inputBox = useMemo(() => { @@ -95,7 +95,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { onChange={(e) => setResponseText(e.target.value)} value={responseText} maxLength={400} - className="userinput-inputbox" + className="resize-none bg-panel rounded-md border border-border py-1.5 pl-4 min-h-[30px] text-inherit cursor-text focus:ring-2 focus:ring-accent focus:outline-none" autoFocus={true} onKeyDown={(e) => keyutil.keydownWrapper(handleKeyDown)(e)} /> @@ -107,15 +107,15 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { return <>; } return ( -
-
+
+
- +
); @@ -148,14 +148,15 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { return ( handleSubmit()} onCancel={() => handleNegativeResponse()} onClose={() => handleSendErrResponse()} okLabel={userInputRequest.oklabel} cancelLabel={userInputRequest.cancellabel} > -
{userInputRequest.title + ` (${countdown}s)`}
-
+
{userInputRequest.title + ` (${countdown}s)`}
+
{queryText} {inputBox} {optionalCheckbox} diff --git a/frontend/app/view/waveconfig/secretscontent.tsx b/frontend/app/view/waveconfig/secretscontent.tsx new file mode 100644 index 0000000000..8be44f7756 --- /dev/null +++ b/frontend/app/view/waveconfig/secretscontent.tsx @@ -0,0 +1,389 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { SecretNameRegex, type WaveConfigViewModel } from "@/app/view/waveconfig/waveconfig-model"; +import { cn } from "@/util/util"; +import { useAtomValue, useSetAtom } from "jotai"; +import { memo } from "react"; + +interface ErrorDisplayProps { + message: string; + variant?: "error" | "warning"; +} + +const ErrorDisplay = memo(({ message, variant = "error" }: ErrorDisplayProps) => { + const icon = variant === "error" ? "fa-circle-exclamation" : "fa-triangle-exclamation"; + const baseClasses = "flex items-center gap-2 p-4 border rounded-lg"; + const variantClasses = + variant === "error" + ? "bg-red-500/10 border-red-500/20 text-red-400" + : "bg-yellow-500/10 border-yellow-500/20 text-yellow-400"; + + return ( +
+ + {message} +
+ ); +}); +ErrorDisplay.displayName = "ErrorDisplay"; + +const LoadingSpinner = memo(({ message }: { message: string }) => { + return ( +
+ + {message} +
+ ); +}); +LoadingSpinner.displayName = "LoadingSpinner"; + +const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { + return ( +
+ +

No Secrets

+

Add a secret to get started

+ +
+ ); +}); +EmptyState.displayName = "EmptyState"; + +const CLIInfoBubble = memo(() => { + return ( +
+
+ +
CLI Access
+
+
+ wsh secret list +
+ wsh secret get [name] +
+ wsh secret set [name]=[value] +
+
+ ); +}); +CLIInfoBubble.displayName = "CLIInfoBubble"; + +interface SecretListViewProps { + secretNames: string[]; + onSelectSecret: (name: string) => void; + onAddSecret: () => void; +} + +const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: SecretListViewProps) => { + return ( +
+
+ {secretNames.map((name) => ( +
onSelectSecret(name)} + > + + {name} + +
+ ))} +
+ + Add New Secret +
+
+ +
+ ); +}); +SecretListView.displayName = "SecretListView"; + +interface AddSecretFormProps { + newSecretName: string; + newSecretValue: string; + isLoading: boolean; + onNameChange: (name: string) => void; + onValueChange: (value: string) => void; + onCancel: () => void; + onSubmit: () => void; +} + +const AddSecretForm = memo( + ({ + newSecretName, + newSecretValue, + isLoading, + onNameChange, + onValueChange, + onCancel, + onSubmit, + }: AddSecretFormProps) => { + const isNameInvalid = newSecretName !== "" && !SecretNameRegex.test(newSecretName); + + return ( +
+

Add New Secret

+
+ + onNameChange(e.target.value)} + placeholder="MY_SECRET_NAME" + disabled={isLoading} + /> +
+ Must start with a letter and contain only letters, numbers, and underscores +
+
+
+ +