mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-30 09:48:27 -05:00
Compare commits
5 Commits
kvg-exampl
...
skillgen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd60b849b1 | ||
|
|
6e49ba436e | ||
|
|
4cff979491 | ||
|
|
e995349ea0 | ||
|
|
4c9765f1fb |
@@ -171,6 +171,23 @@ steps:
|
|||||||
alloydbainl \
|
alloydbainl \
|
||||||
alloydbainl
|
alloydbainl
|
||||||
|
|
||||||
|
- id: "alloydb-omni"
|
||||||
|
name: golang:1
|
||||||
|
waitFor: ["compile-test-binary"]
|
||||||
|
entrypoint: /bin/bash
|
||||||
|
env:
|
||||||
|
- "GOPATH=/gopath"
|
||||||
|
volumes:
|
||||||
|
- name: "go"
|
||||||
|
path: "/gopath"
|
||||||
|
args:
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
.ci/test_with_coverage.sh \
|
||||||
|
"AlloyDB Omni" \
|
||||||
|
alloydbomni \
|
||||||
|
postgres
|
||||||
|
|
||||||
- id: "bigtable"
|
- id: "bigtable"
|
||||||
name: golang:1
|
name: golang:1
|
||||||
waitFor: ["compile-test-binary"]
|
waitFor: ["compile-test-binary"]
|
||||||
@@ -906,7 +923,7 @@ steps:
|
|||||||
tar -C /usr/local -xzf go.tar.gz
|
tar -C /usr/local -xzf go.tar.gz
|
||||||
export PATH="/usr/local/go/bin:$$PATH"
|
export PATH="/usr/local/go/bin:$$PATH"
|
||||||
|
|
||||||
go test -v ./internal/sources/oracle/... \
|
go test -v ./tests/oracle/... \
|
||||||
-coverprofile=oracle_coverage.out \
|
-coverprofile=oracle_coverage.out \
|
||||||
-coverpkg=./internal/sources/oracle/...,./internal/tools/oracle/...
|
-coverpkg=./internal/sources/oracle/...,./internal/tools/oracle/...
|
||||||
|
|
||||||
@@ -914,8 +931,8 @@ steps:
|
|||||||
total_coverage=$(go tool cover -func=oracle_coverage.out | grep "total:" | awk '{print $3}')
|
total_coverage=$(go tool cover -func=oracle_coverage.out | grep "total:" | awk '{print $3}')
|
||||||
echo "Oracle total coverage: $total_coverage"
|
echo "Oracle total coverage: $total_coverage"
|
||||||
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
|
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
|
||||||
if awk -v cov="$coverage_numeric" 'BEGIN {exit !(cov < 20)}'; then
|
if awk -v cov="$coverage_numeric" 'BEGIN {exit !(cov < 60)}'; then
|
||||||
echo "Coverage failure: $total_coverage is below 20%."
|
echo "Coverage failure: $total_coverage is below 60%."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
// Copyright 2026 Google LLC
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newInvokeCmd(root *Command) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "invoke",
|
|
||||||
Short: "Invoke a tool",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
invoke(root)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func invoke(cmd *Command) {
|
|
||||||
fmt.Println("Hello, World! Here is one of my flags" + cmd.cfg.Address)
|
|
||||||
}
|
|
||||||
131
cmd/invoke_tool_test.go
Normal file
131
cmd/invoke_tool_test.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvokeTool(t *testing.T) {
|
||||||
|
// Create a temporary tools file
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
toolsFileContent := `
|
||||||
|
sources:
|
||||||
|
my-sqlite:
|
||||||
|
kind: sqlite
|
||||||
|
database: test.db
|
||||||
|
tools:
|
||||||
|
hello-sqlite:
|
||||||
|
kind: sqlite-sql
|
||||||
|
source: my-sqlite
|
||||||
|
description: "hello tool"
|
||||||
|
statement: "SELECT 'hello' as greeting"
|
||||||
|
echo-tool:
|
||||||
|
kind: sqlite-sql
|
||||||
|
source: my-sqlite
|
||||||
|
description: "echo tool"
|
||||||
|
statement: "SELECT ? as msg"
|
||||||
|
parameters:
|
||||||
|
- name: message
|
||||||
|
type: string
|
||||||
|
description: message to echo
|
||||||
|
`
|
||||||
|
|
||||||
|
toolsFilePath := filepath.Join(tmpDir, "tools.yaml")
|
||||||
|
if err := os.WriteFile(toolsFilePath, []byte(toolsFileContent), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write tools file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
errStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "success - basic tool call",
|
||||||
|
args: []string{"invoke", "hello-sqlite", "--tools-file", toolsFilePath},
|
||||||
|
want: `"greeting": "hello"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "success - tool call with parameters",
|
||||||
|
args: []string{"invoke", "echo-tool", `{"message": "world"}`, "--tools-file", toolsFilePath},
|
||||||
|
want: `"msg": "world"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "error - tool not found",
|
||||||
|
args: []string{"invoke", "non-existent", "--tools-file", toolsFilePath},
|
||||||
|
wantErr: true,
|
||||||
|
errStr: `tool "non-existent" not found`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "error - invalid JSON params",
|
||||||
|
args: []string{"invoke", "echo-tool", `invalid-json`, "--tools-file", toolsFilePath},
|
||||||
|
wantErr: true,
|
||||||
|
errStr: `params must be a valid JSON string`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
_, got, err := invokeCommandWithContext(context.Background(), tc.args)
|
||||||
|
if (err != nil) != tc.wantErr {
|
||||||
|
t.Fatalf("got error %v, wantErr %v", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
if tc.wantErr && !strings.Contains(err.Error(), tc.errStr) {
|
||||||
|
t.Fatalf("got error %v, want error containing %q", err, tc.errStr)
|
||||||
|
}
|
||||||
|
if !tc.wantErr && !strings.Contains(got, tc.want) {
|
||||||
|
t.Fatalf("got %q, want it to contain %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvokeTool_AuthUnsupported(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
toolsFileContent := `
|
||||||
|
sources:
|
||||||
|
my-bq:
|
||||||
|
kind: bigquery
|
||||||
|
project: my-project
|
||||||
|
useClientOAuth: true
|
||||||
|
tools:
|
||||||
|
bq-tool:
|
||||||
|
kind: bigquery-sql
|
||||||
|
source: my-bq
|
||||||
|
description: "bq tool"
|
||||||
|
statement: "SELECT 1"
|
||||||
|
`
|
||||||
|
toolsFilePath := filepath.Join(tmpDir, "auth_tools.yaml")
|
||||||
|
if err := os.WriteFile(toolsFilePath, []byte(toolsFileContent), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write tools file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"invoke", "bq-tool", "--tools-file", toolsFilePath}
|
||||||
|
_, _, err := invokeCommandWithContext(context.Background(), args)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for tool requiring client auth, but got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "client authorization is not supported") {
|
||||||
|
t.Fatalf("unexpected error message: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
201
cmd/root.go
201
cmd/root.go
@@ -34,6 +34,8 @@ import (
|
|||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
yaml "github.com/goccy/go-yaml"
|
yaml "github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/auth"
|
"github.com/googleapis/genai-toolbox/internal/auth"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/cli/invoke"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/cli/skills"
|
||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/log"
|
"github.com/googleapis/genai-toolbox/internal/log"
|
||||||
"github.com/googleapis/genai-toolbox/internal/prebuiltconfigs"
|
"github.com/googleapis/genai-toolbox/internal/prebuiltconfigs"
|
||||||
@@ -365,38 +367,43 @@ func NewCommand(opts ...Option) *Command {
|
|||||||
baseCmd.SetErr(cmd.errStream)
|
baseCmd.SetErr(cmd.errStream)
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
persistentFlags := cmd.PersistentFlags()
|
||||||
|
|
||||||
flags.StringVarP(&cmd.cfg.Address, "address", "a", "127.0.0.1", "Address of the interface the server will listen on.")
|
flags.StringVarP(&cmd.cfg.Address, "address", "a", "127.0.0.1", "Address of the interface the server will listen on.")
|
||||||
flags.IntVarP(&cmd.cfg.Port, "port", "p", 5000, "Port the server will listen on.")
|
flags.IntVarP(&cmd.cfg.Port, "port", "p", 5000, "Port the server will listen on.")
|
||||||
|
|
||||||
flags.StringVar(&cmd.tools_file, "tools_file", "", "File path specifying the tool configuration. Cannot be used with --tools-files, or --tools-folder.")
|
flags.StringVar(&cmd.tools_file, "tools_file", "", "File path specifying the tool configuration. Cannot be used with --tools-files, or --tools-folder.")
|
||||||
// deprecate tools_file
|
// deprecate tools_file
|
||||||
_ = flags.MarkDeprecated("tools_file", "please use --tools-file instead")
|
_ = flags.MarkDeprecated("tools_file", "please use --tools-file instead")
|
||||||
flags.StringVar(&cmd.tools_file, "tools-file", "", "File path specifying the tool configuration. Cannot be used with --tools-files, or --tools-folder.")
|
persistentFlags.StringVar(&cmd.tools_file, "tools-file", "", "File path specifying the tool configuration. Cannot be used with --tools-files, or --tools-folder.")
|
||||||
flags.StringSliceVar(&cmd.tools_files, "tools-files", []string{}, "Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --tools-file, or --tools-folder.")
|
persistentFlags.StringSliceVar(&cmd.tools_files, "tools-files", []string{}, "Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --tools-file, or --tools-folder.")
|
||||||
flags.StringVar(&cmd.tools_folder, "tools-folder", "", "Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --tools-file, or --tools-files.")
|
persistentFlags.StringVar(&cmd.tools_folder, "tools-folder", "", "Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --tools-file, or --tools-files.")
|
||||||
flags.Var(&cmd.cfg.LogLevel, "log-level", "Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'.")
|
persistentFlags.Var(&cmd.cfg.LogLevel, "log-level", "Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'.")
|
||||||
flags.Var(&cmd.cfg.LoggingFormat, "logging-format", "Specify logging format to use. Allowed: 'standard' or 'JSON'.")
|
persistentFlags.Var(&cmd.cfg.LoggingFormat, "logging-format", "Specify logging format to use. Allowed: 'standard' or 'JSON'.")
|
||||||
flags.BoolVar(&cmd.cfg.TelemetryGCP, "telemetry-gcp", false, "Enable exporting directly to Google Cloud Monitoring.")
|
persistentFlags.BoolVar(&cmd.cfg.TelemetryGCP, "telemetry-gcp", false, "Enable exporting directly to Google Cloud Monitoring.")
|
||||||
flags.StringVar(&cmd.cfg.TelemetryOTLP, "telemetry-otlp", "", "Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318')")
|
persistentFlags.StringVar(&cmd.cfg.TelemetryOTLP, "telemetry-otlp", "", "Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318')")
|
||||||
flags.StringVar(&cmd.cfg.TelemetryServiceName, "telemetry-service-name", "toolbox", "Sets the value of the service.name resource attribute for telemetry data.")
|
persistentFlags.StringVar(&cmd.cfg.TelemetryServiceName, "telemetry-service-name", "toolbox", "Sets the value of the service.name resource attribute for telemetry data.")
|
||||||
// Fetch prebuilt tools sources to customize the help description
|
// Fetch prebuilt tools sources to customize the help description
|
||||||
prebuiltHelp := fmt.Sprintf(
|
prebuiltHelp := fmt.Sprintf(
|
||||||
"Use a prebuilt tool configuration by source type. Allowed: '%s'. Can be specified multiple times.",
|
"Use a prebuilt tool configuration by source type. Allowed: '%s'. Can be specified multiple times.",
|
||||||
strings.Join(prebuiltconfigs.GetPrebuiltSources(), "', '"),
|
strings.Join(prebuiltconfigs.GetPrebuiltSources(), "', '"),
|
||||||
)
|
)
|
||||||
flags.StringSliceVar(&cmd.prebuiltConfigs, "prebuilt", []string{}, prebuiltHelp)
|
persistentFlags.StringSliceVar(&cmd.prebuiltConfigs, "prebuilt", []string{}, prebuiltHelp)
|
||||||
flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.")
|
flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.")
|
||||||
flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
|
flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
|
||||||
flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.")
|
flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.")
|
||||||
// TODO: Insecure by default. Might consider updating this for v1.0.0
|
// TODO: Insecure by default. Might consider updating this for v1.0.0
|
||||||
flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Defaults to '*'.")
|
flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Defaults to '*'.")
|
||||||
flags.StringSliceVar(&cmd.cfg.AllowedHosts, "allowed-hosts", []string{"*"}, "Specifies a list of hosts permitted to access this server. Defaults to '*'.")
|
flags.StringSliceVar(&cmd.cfg.AllowedHosts, "allowed-hosts", []string{"*"}, "Specifies a list of hosts permitted to access this server. Defaults to '*'.")
|
||||||
flags.StringSliceVar(&cmd.cfg.UserAgentMetadata, "user-agent-metadata", []string{}, "Appends additional metadata to the User-Agent.")
|
persistentFlags.StringSliceVar(&cmd.cfg.UserAgentMetadata, "user-agent-metadata", []string{}, "Appends additional metadata to the User-Agent.")
|
||||||
|
|
||||||
// wrap RunE command so that we have access to original Command object
|
// wrap RunE command so that we have access to original Command object
|
||||||
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) }
|
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) }
|
||||||
|
|
||||||
baseCmd.AddCommand(newInvokeCmd(cmd))
|
// Register subcommands for tool invocation
|
||||||
|
baseCmd.AddCommand(invoke.NewCommand(cmd))
|
||||||
|
// Register subcommands for skill generation
|
||||||
|
baseCmd.AddCommand(skills.NewCommand(cmd))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -921,70 +928,23 @@ func resolveWatcherInputs(toolsFile string, toolsFiles []string, toolsFolder str
|
|||||||
return watchDirs, watchedFiles
|
return watchDirs, watchedFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(cmd *Command) error {
|
func (cmd *Command) Config() server.ServerConfig {
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
return cmd.cfg
|
||||||
defer cancel()
|
}
|
||||||
|
|
||||||
// watch for sigterm / sigint signals
|
func (cmd *Command) Out() io.Writer {
|
||||||
signals := make(chan os.Signal, 1)
|
return cmd.outStream
|
||||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
}
|
||||||
go func(sCtx context.Context) {
|
|
||||||
var s os.Signal
|
|
||||||
select {
|
|
||||||
case <-sCtx.Done():
|
|
||||||
// this should only happen when the context supplied when testing is canceled
|
|
||||||
return
|
|
||||||
case s = <-signals:
|
|
||||||
}
|
|
||||||
switch s {
|
|
||||||
case syscall.SIGINT:
|
|
||||||
cmd.logger.DebugContext(sCtx, "Received SIGINT signal to shutdown.")
|
|
||||||
case syscall.SIGTERM:
|
|
||||||
cmd.logger.DebugContext(sCtx, "Sending SIGTERM signal to shutdown.")
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}(ctx)
|
|
||||||
|
|
||||||
// If stdio, set logger's out stream (usually DEBUG and INFO logs) to errStream
|
func (cmd *Command) Logger() log.Logger {
|
||||||
loggerOut := cmd.outStream
|
return cmd.logger
|
||||||
if cmd.cfg.Stdio {
|
}
|
||||||
loggerOut = cmd.errStream
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle logger separately from config
|
func (cmd *Command) LoadConfig(ctx context.Context) error {
|
||||||
switch strings.ToLower(cmd.cfg.LoggingFormat.String()) {
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
case "json":
|
|
||||||
logger, err := log.NewStructuredLogger(loggerOut, cmd.errStream, cmd.cfg.LogLevel.String())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to initialize logger: %w", err)
|
|
||||||
}
|
|
||||||
cmd.logger = logger
|
|
||||||
case "standard":
|
|
||||||
logger, err := log.NewStdLogger(loggerOut, cmd.errStream, cmd.cfg.LogLevel.String())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to initialize logger: %w", err)
|
|
||||||
}
|
|
||||||
cmd.logger = logger
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("logging format invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = util.WithLogger(ctx, cmd.logger)
|
|
||||||
|
|
||||||
// Set up OpenTelemetry
|
|
||||||
otelShutdown, err := telemetry.SetupOTel(ctx, cmd.cfg.Version, cmd.cfg.TelemetryOTLP, cmd.cfg.TelemetryGCP, cmd.cfg.TelemetryServiceName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Errorf("error setting up OpenTelemetry: %w", err)
|
return err
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
|
||||||
return errMsg
|
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
err := otelShutdown(ctx)
|
|
||||||
if err != nil {
|
|
||||||
errMsg := fmt.Errorf("error shutting down OpenTelemetry: %w", err)
|
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var allToolsFiles []ToolsFile
|
var allToolsFiles []ToolsFile
|
||||||
|
|
||||||
@@ -994,12 +954,12 @@ func run(cmd *Command) error {
|
|||||||
slices.Sort(cmd.prebuiltConfigs)
|
slices.Sort(cmd.prebuiltConfigs)
|
||||||
sourcesList := strings.Join(cmd.prebuiltConfigs, ", ")
|
sourcesList := strings.Join(cmd.prebuiltConfigs, ", ")
|
||||||
logMsg := fmt.Sprintf("Using prebuilt tool configurations for: %s", sourcesList)
|
logMsg := fmt.Sprintf("Using prebuilt tool configurations for: %s", sourcesList)
|
||||||
cmd.logger.InfoContext(ctx, logMsg)
|
logger.InfoContext(ctx, logMsg)
|
||||||
|
|
||||||
for _, configName := range cmd.prebuiltConfigs {
|
for _, configName := range cmd.prebuiltConfigs {
|
||||||
buf, err := prebuiltconfigs.Get(configName)
|
buf, err := prebuiltconfigs.Get(configName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.logger.ErrorContext(ctx, err.Error())
|
logger.ErrorContext(ctx, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,7 +967,7 @@ func run(cmd *Command) error {
|
|||||||
parsed, err := parseToolsFile(ctx, buf)
|
parsed, err := parseToolsFile(ctx, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Errorf("unable to parse prebuilt tool configuration for '%s': %w", configName, err)
|
errMsg := fmt.Errorf("unable to parse prebuilt tool configuration for '%s': %w", configName, err)
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
return errMsg
|
return errMsg
|
||||||
}
|
}
|
||||||
allToolsFiles = append(allToolsFiles, parsed)
|
allToolsFiles = append(allToolsFiles, parsed)
|
||||||
@@ -1033,7 +993,7 @@ func run(cmd *Command) error {
|
|||||||
(cmd.tools_file != "" && cmd.tools_folder != "") ||
|
(cmd.tools_file != "" && cmd.tools_folder != "") ||
|
||||||
(len(cmd.tools_files) > 0 && cmd.tools_folder != "") {
|
(len(cmd.tools_files) > 0 && cmd.tools_folder != "") {
|
||||||
errMsg := fmt.Errorf("--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously")
|
errMsg := fmt.Errorf("--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously")
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
return errMsg
|
return errMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,18 +1002,18 @@ func run(cmd *Command) error {
|
|||||||
|
|
||||||
if len(cmd.tools_files) > 0 {
|
if len(cmd.tools_files) > 0 {
|
||||||
// Use tools-files
|
// Use tools-files
|
||||||
cmd.logger.InfoContext(ctx, fmt.Sprintf("Loading and merging %d tool configuration files", len(cmd.tools_files)))
|
logger.InfoContext(ctx, fmt.Sprintf("Loading and merging %d tool configuration files", len(cmd.tools_files)))
|
||||||
customTools, err = loadAndMergeToolsFiles(ctx, cmd.tools_files)
|
customTools, err = loadAndMergeToolsFiles(ctx, cmd.tools_files)
|
||||||
} else if cmd.tools_folder != "" {
|
} else if cmd.tools_folder != "" {
|
||||||
// Use tools-folder
|
// Use tools-folder
|
||||||
cmd.logger.InfoContext(ctx, fmt.Sprintf("Loading and merging all YAML files from directory: %s", cmd.tools_folder))
|
logger.InfoContext(ctx, fmt.Sprintf("Loading and merging all YAML files from directory: %s", cmd.tools_folder))
|
||||||
customTools, err = loadAndMergeToolsFolder(ctx, cmd.tools_folder)
|
customTools, err = loadAndMergeToolsFolder(ctx, cmd.tools_folder)
|
||||||
} else {
|
} else {
|
||||||
// Use single file (tools-file or default `tools.yaml`)
|
// Use single file (tools-file or default `tools.yaml`)
|
||||||
buf, readFileErr := os.ReadFile(cmd.tools_file)
|
buf, readFileErr := os.ReadFile(cmd.tools_file)
|
||||||
if readFileErr != nil {
|
if readFileErr != nil {
|
||||||
errMsg := fmt.Errorf("unable to read tool file at %q: %w", cmd.tools_file, readFileErr)
|
errMsg := fmt.Errorf("unable to read tool file at %q: %w", cmd.tools_file, readFileErr)
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
return errMsg
|
return errMsg
|
||||||
}
|
}
|
||||||
customTools, err = parseToolsFile(ctx, buf)
|
customTools, err = parseToolsFile(ctx, buf)
|
||||||
@@ -1063,7 +1023,7 @@ func run(cmd *Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.logger.ErrorContext(ctx, err.Error())
|
logger.ErrorContext(ctx, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allToolsFiles = append(allToolsFiles, customTools)
|
allToolsFiles = append(allToolsFiles, customTools)
|
||||||
@@ -1085,7 +1045,7 @@ func run(cmd *Command) error {
|
|||||||
// This will error if custom tools collide with prebuilt tools
|
// This will error if custom tools collide with prebuilt tools
|
||||||
finalToolsFile, err := mergeToolsFiles(allToolsFiles...)
|
finalToolsFile, err := mergeToolsFiles(allToolsFiles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.logger.ErrorContext(ctx, err.Error())
|
logger.ErrorContext(ctx, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,15 +1056,91 @@ func run(cmd *Command) error {
|
|||||||
cmd.cfg.ToolsetConfigs = finalToolsFile.Toolsets
|
cmd.cfg.ToolsetConfigs = finalToolsFile.Toolsets
|
||||||
cmd.cfg.PromptConfigs = finalToolsFile.Prompts
|
cmd.cfg.PromptConfigs = finalToolsFile.Prompts
|
||||||
|
|
||||||
instrumentation, err := telemetry.CreateTelemetryInstrumentation(versionString)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Command) Setup(ctx context.Context) (context.Context, func(context.Context) error, error) {
|
||||||
|
// If stdio, set logger's out stream (usually DEBUG and INFO logs) to errStream
|
||||||
|
loggerOut := cmd.outStream
|
||||||
|
if cmd.cfg.Stdio {
|
||||||
|
loggerOut = cmd.errStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle logger separately from config
|
||||||
|
logger, err := log.NewLogger(cmd.cfg.LoggingFormat.String(), cmd.cfg.LogLevel.String(), loggerOut, cmd.errStream)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, nil, fmt.Errorf("unable to initialize logger: %w", err)
|
||||||
|
}
|
||||||
|
cmd.logger = logger
|
||||||
|
|
||||||
|
ctx = util.WithLogger(ctx, cmd.logger)
|
||||||
|
|
||||||
|
// Set up OpenTelemetry
|
||||||
|
otelShutdown, err := telemetry.SetupOTel(ctx, cmd.cfg.Version, cmd.cfg.TelemetryOTLP, cmd.cfg.TelemetryGCP, cmd.cfg.TelemetryServiceName)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error setting up OpenTelemetry: %w", err)
|
||||||
|
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return ctx, nil, errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdownFunc := func(ctx context.Context) error {
|
||||||
|
err := otelShutdown(ctx)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error shutting down OpenTelemetry: %w", err)
|
||||||
|
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
instrumentation, err := telemetry.CreateTelemetryInstrumentation(cmd.cfg.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Errorf("unable to create telemetry instrumentation: %w", err)
|
errMsg := fmt.Errorf("unable to create telemetry instrumentation: %w", err)
|
||||||
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
cmd.logger.ErrorContext(ctx, errMsg.Error())
|
||||||
return errMsg
|
return ctx, shutdownFunc, errMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = util.WithInstrumentation(ctx, instrumentation)
|
ctx = util.WithInstrumentation(ctx, instrumentation)
|
||||||
|
|
||||||
|
return ctx, shutdownFunc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(cmd *Command) error {
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// watch for sigterm / sigint signals
|
||||||
|
signals := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
go func(sCtx context.Context) {
|
||||||
|
var s os.Signal
|
||||||
|
select {
|
||||||
|
case <-sCtx.Done():
|
||||||
|
// this should only happen when the context supplied when testing is canceled
|
||||||
|
return
|
||||||
|
case s = <-signals:
|
||||||
|
}
|
||||||
|
switch s {
|
||||||
|
case syscall.SIGINT:
|
||||||
|
cmd.logger.DebugContext(sCtx, "Received SIGINT signal to shutdown.")
|
||||||
|
case syscall.SIGTERM:
|
||||||
|
cmd.logger.DebugContext(sCtx, "Sending SIGTERM signal to shutdown.")
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
ctx, shutdown, err := cmd.Setup(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.LoadConfig(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// start server
|
// start server
|
||||||
s, err := server.NewServer(ctx, cmd.cfg)
|
s, err := server.NewServer(ctx, cmd.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1144,6 +1180,9 @@ func run(cmd *Command) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if Custom Files are configured (re-check as loadAndMergeConfig might have updated defaults)
|
||||||
|
isCustomConfigured := cmd.tools_file != "" || len(cmd.tools_files) > 0 || cmd.tools_folder != ""
|
||||||
|
|
||||||
if isCustomConfigured && !cmd.cfg.DisableReload {
|
if isCustomConfigured && !cmd.cfg.DisableReload {
|
||||||
watchDirs, watchedFiles := resolveWatcherInputs(cmd.tools_file, cmd.tools_files, cmd.tools_folder)
|
watchDirs, watchedFiles := resolveWatcherInputs(cmd.tools_file, cmd.tools_files, cmd.tools_folder)
|
||||||
// start watching the file(s) or folder for changes to trigger dynamic reloading
|
// start watching the file(s) or folder for changes to trigger dynamic reloading
|
||||||
|
|||||||
@@ -2054,6 +2054,7 @@ func TestSingleEdit(t *testing.T) {
|
|||||||
|
|
||||||
func TestPrebuiltTools(t *testing.T) {
|
func TestPrebuiltTools(t *testing.T) {
|
||||||
// Get prebuilt configs
|
// Get prebuilt configs
|
||||||
|
alloydb_omni_config, _ := prebuiltconfigs.Get("alloydb-omni")
|
||||||
alloydb_admin_config, _ := prebuiltconfigs.Get("alloydb-postgres-admin")
|
alloydb_admin_config, _ := prebuiltconfigs.Get("alloydb-postgres-admin")
|
||||||
alloydb_config, _ := prebuiltconfigs.Get("alloydb-postgres")
|
alloydb_config, _ := prebuiltconfigs.Get("alloydb-postgres")
|
||||||
bigquery_config, _ := prebuiltconfigs.Get("bigquery")
|
bigquery_config, _ := prebuiltconfigs.Get("bigquery")
|
||||||
@@ -2104,6 +2105,12 @@ func TestPrebuiltTools(t *testing.T) {
|
|||||||
t.Setenv("ALLOYDB_POSTGRES_USER", "your_alloydb_user")
|
t.Setenv("ALLOYDB_POSTGRES_USER", "your_alloydb_user")
|
||||||
t.Setenv("ALLOYDB_POSTGRES_PASSWORD", "your_alloydb_password")
|
t.Setenv("ALLOYDB_POSTGRES_PASSWORD", "your_alloydb_password")
|
||||||
|
|
||||||
|
t.Setenv("ALLOYDB_OMNI_HOST", "localhost")
|
||||||
|
t.Setenv("ALLOYDB_OMNI_PORT", "5432")
|
||||||
|
t.Setenv("ALLOYDB_OMNI_DATABASE", "your_alloydb_db")
|
||||||
|
t.Setenv("ALLOYDB_OMNI_USER", "your_alloydb_user")
|
||||||
|
t.Setenv("ALLOYDB_OMNI_PASSWORD", "your_alloydb_password")
|
||||||
|
|
||||||
t.Setenv("CLICKHOUSE_PROTOCOL", "your_clickhouse_protocol")
|
t.Setenv("CLICKHOUSE_PROTOCOL", "your_clickhouse_protocol")
|
||||||
t.Setenv("CLICKHOUSE_DATABASE", "your_clickhouse_database")
|
t.Setenv("CLICKHOUSE_DATABASE", "your_clickhouse_database")
|
||||||
t.Setenv("CLICKHOUSE_PASSWORD", "your_clickhouse_password")
|
t.Setenv("CLICKHOUSE_PASSWORD", "your_clickhouse_password")
|
||||||
@@ -2197,6 +2204,16 @@ func TestPrebuiltTools(t *testing.T) {
|
|||||||
in []byte
|
in []byte
|
||||||
wantToolset server.ToolsetConfigs
|
wantToolset server.ToolsetConfigs
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "alloydb omni prebuilt tools",
|
||||||
|
in: alloydb_omni_config,
|
||||||
|
wantToolset: server.ToolsetConfigs{
|
||||||
|
"alloydb_omni_database_tools": tools.ToolsetConfig{
|
||||||
|
Name: "alloydb_omni_database_tools",
|
||||||
|
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_columnar_configurations", "list_columnar_recommended_columns", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences", "long_running_transactions", "list_locks", "replication_stats", "list_query_stats", "get_column_cardinality", "list_publication_tables", "list_tablespaces", "list_pg_settings", "list_database_stats", "list_roles", "list_table_stats", "list_stored_procedure"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "alloydb postgres admin prebuilt tools",
|
name: "alloydb postgres admin prebuilt tools",
|
||||||
in: alloydb_admin_config,
|
in: alloydb_admin_config,
|
||||||
|
|||||||
166
cmd/skill_generate_test.go
Normal file
166
cmd/skill_generate_test.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateSkill(t *testing.T) {
|
||||||
|
// Create a temporary directory for tests
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
outputDir := filepath.Join(tmpDir, "skills")
|
||||||
|
|
||||||
|
// Create a tools.yaml file with a sqlite tool
|
||||||
|
toolsFileContent := `
|
||||||
|
sources:
|
||||||
|
my-sqlite:
|
||||||
|
kind: sqlite
|
||||||
|
database: test.db
|
||||||
|
tools:
|
||||||
|
hello-sqlite:
|
||||||
|
kind: sqlite-sql
|
||||||
|
source: my-sqlite
|
||||||
|
description: "hello tool"
|
||||||
|
statement: "SELECT 'hello' as greeting"
|
||||||
|
`
|
||||||
|
|
||||||
|
toolsFilePath := filepath.Join(tmpDir, "tools.yaml")
|
||||||
|
if err := os.WriteFile(toolsFilePath, []byte(toolsFileContent), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write tools file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"skills-generate",
|
||||||
|
"--tools-file", toolsFilePath,
|
||||||
|
"--output-dir", outputDir,
|
||||||
|
"--name", "hello-sqlite",
|
||||||
|
"--description", "hello tool",
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a longer timeout because generate-skill starts a server and polls it
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, got, err := invokeCommandWithContext(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("command failed: %v\nOutput: %s", err, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify generated directory structure
|
||||||
|
skillPath := filepath.Join(outputDir, "hello-sqlite")
|
||||||
|
if _, err := os.Stat(skillPath); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("skill directory not created: %s", skillPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check SKILL.md
|
||||||
|
skillMarkdown := filepath.Join(skillPath, "SKILL.md")
|
||||||
|
content, err := os.ReadFile(skillMarkdown)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read SKILL.md: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), "name: hello-sqlite") {
|
||||||
|
t.Errorf("SKILL.md does not contain expected name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), "description: hello tool") {
|
||||||
|
t.Errorf("SKILL.md does not contain expected description")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check script file
|
||||||
|
scriptFilename := "hello-sqlite.js"
|
||||||
|
scriptPath := filepath.Join(skillPath, "scripts", scriptFilename)
|
||||||
|
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("script file not created: %s", scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptContent, err := os.ReadFile(scriptPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read script file: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(scriptContent), "hello-sqlite") {
|
||||||
|
t.Errorf("script file does not contain expected tool name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check assets
|
||||||
|
assetPath := filepath.Join(skillPath, "assets", "hello-sqlite.yaml")
|
||||||
|
if _, err := os.Stat(assetPath); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("asset file not created: %s", assetPath)
|
||||||
|
}
|
||||||
|
assetContent, err := os.ReadFile(assetPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read asset file: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(assetContent), "hello-sqlite") {
|
||||||
|
t.Errorf("asset file does not contain expected tool name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSkill_NoConfig(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
outputDir := filepath.Join(tmpDir, "skills")
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"skills-generate",
|
||||||
|
"--output-dir", outputDir,
|
||||||
|
"--name", "test",
|
||||||
|
"--description", "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := invokeCommandWithContext(context.Background(), args)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected command to fail when no configuration is provided and tools.yaml is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not have created the directory if no config was processed
|
||||||
|
if _, err := os.Stat(outputDir); !os.IsNotExist(err) {
|
||||||
|
t.Errorf("output directory should not have been created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSkill_MissingArguments(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
toolsFilePath := filepath.Join(tmpDir, "tools.yaml")
|
||||||
|
if err := os.WriteFile(toolsFilePath, []byte("tools: {}"), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write tools file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing name",
|
||||||
|
args: []string{"skills-generate", "--tools-file", toolsFilePath, "--description", "test"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing description",
|
||||||
|
args: []string{"skills-generate", "--tools-file", toolsFilePath, "--name", "test"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, got, err := invokeCommandWithContext(context.Background(), tt.args)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected command to fail due to missing arguments, but it succeeded\nOutput: %s", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
107
docs/en/how-to/generate_skill.md
Normal file
107
docs/en/how-to/generate_skill.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: "Generate a Skill"
|
||||||
|
type: docs
|
||||||
|
weight: 10
|
||||||
|
description: >
|
||||||
|
How to generate a skill from a toolset configuration.
|
||||||
|
---
|
||||||
|
|
||||||
|
The `skills-generate` command allows you to convert a **toolset** into a **skill**. A toolset is a collection of tools, and the generated skill will contain metadata and execution scripts for all tools within that toolset.
|
||||||
|
|
||||||
|
## Before you begin
|
||||||
|
|
||||||
|
1. Make sure you have the `toolbox` executable in your PATH.
|
||||||
|
2. Make sure you have [Node.js](https://nodejs.org/) installed on your system.
|
||||||
|
|
||||||
|
## Generating a Skill from a Toolset
|
||||||
|
|
||||||
|
A skill package consists of a `SKILL.md` file and a set of Node.js scripts. Each tool defined in your toolset maps to a corresponding script in the generated skill.
|
||||||
|
|
||||||
|
### Command Signature
|
||||||
|
|
||||||
|
The `skills-generate` command follows this signature:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox [--tools-file <path> | --prebuilt <name>] skills-generate \
|
||||||
|
--name <skill-name> \
|
||||||
|
--toolset <toolset-name> \
|
||||||
|
--description <description> \
|
||||||
|
--output-dir <output-directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Custom Tools File
|
||||||
|
|
||||||
|
1. Create a `tools.yaml` file with a toolset and some tools:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tools:
|
||||||
|
tool_a:
|
||||||
|
description: "First tool"
|
||||||
|
run:
|
||||||
|
command: "echo 'Tool A'"
|
||||||
|
tool_b:
|
||||||
|
description: "Second tool"
|
||||||
|
run:
|
||||||
|
command: "echo 'Tool B'"
|
||||||
|
toolsets:
|
||||||
|
my_toolset:
|
||||||
|
tools:
|
||||||
|
- tool_a
|
||||||
|
- tool_b
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Generate the skill:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --tools-file tools.yaml skills-generate \
|
||||||
|
--name "my-multi-tool-skill" \
|
||||||
|
--toolset "my_toolset" \
|
||||||
|
--description "A skill containing multiple tools" \
|
||||||
|
--output-dir "generated-skills"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The generated skill directory structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
generated-skills/
|
||||||
|
└── my-multi-tool-skill/
|
||||||
|
├── SKILL.md
|
||||||
|
├── assets/
|
||||||
|
│ ├── tool_a.yaml
|
||||||
|
│ └── tool_b.yaml
|
||||||
|
└── scripts/
|
||||||
|
├── tool_a.js
|
||||||
|
└── tool_b.js
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the skill contains two Node.js scripts (`tool_a.js` and `tool_b.js`), each mapping to a tool in the original toolset.
|
||||||
|
|
||||||
|
### Example: Prebuilt Configuration
|
||||||
|
|
||||||
|
You can also generate skills from prebuilt toolsets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --prebuilt alloydb-postgres-admin skills-generate \
|
||||||
|
--name "alloydb-postgres-admin" \
|
||||||
|
--description "skill for performing administrative operations on alloydb"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Directory
|
||||||
|
|
||||||
|
By default, skills are generated in the `skills` directory. You can specify a different output directory using the `--output-dir` flag.
|
||||||
|
|
||||||
|
## Shared Node.js Scripts
|
||||||
|
|
||||||
|
The `skills-generate` command generates shared Node.js scripts (`.js`) that work across different platforms (Linux, macOS, Windows). This ensures that the generated skills are portable.
|
||||||
|
|
||||||
|
## Installing the Generated Skill in Gemini CLI
|
||||||
|
|
||||||
|
Once you have generated a skill, you can install it into the Gemini CLI using the `gemini skills install` command.
|
||||||
|
|
||||||
|
### Installation Command
|
||||||
|
|
||||||
|
Provide the path to the directory containing the generated skill:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gemini skills install /path/to/generated-skills/my-multi-tool-skill
|
||||||
|
```
|
||||||
74
docs/en/how-to/invoke_tool.md
Normal file
74
docs/en/how-to/invoke_tool.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: "Invoke Tools via CLI"
|
||||||
|
type: docs
|
||||||
|
weight: 10
|
||||||
|
description: >
|
||||||
|
Learn how to invoke your tools directly from the command line using the `invoke` command.
|
||||||
|
---
|
||||||
|
|
||||||
|
The `invoke` command allows you to invoke tools defined in your configuration directly from the CLI. This is useful for:
|
||||||
|
|
||||||
|
- **Ephemeral Invocation:** Executing a tool without spinning up a full MCP server/client.
|
||||||
|
- **Debugging:** Isolating tool execution logic and testing with various parameter combinations.
|
||||||
|
|
||||||
|
{{< notice tip >}}
|
||||||
|
**Keep configurations minimal:** The `invoke` command initializes *all* resources (sources, tools, etc.) defined in your configuration files during execution. To ensure fast response times, consider using a minimal configuration file containing only the tools you need for the specific invocation.
|
||||||
|
{{< notice tip >}}
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- You have the `toolbox` binary installed or built.
|
||||||
|
- You have a valid tool configuration file (e.g., `tools.yaml`).
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
The basic syntax for the command is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox [--tools-file <path> | --prebuilt <name>] invoke <tool-name> [params]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<tool-name>`: The name of the tool you want to call. This must match the name defined in your `tools.yaml`.
|
||||||
|
- `[params]`: (Optional) A JSON string representing the arguments for the tool.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### 1. Calling a Tool without Parameters
|
||||||
|
|
||||||
|
If your tool takes no parameters, simply provide the tool name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --tools-file tools.yaml invoke my-simple-tool
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Calling a Tool with Parameters
|
||||||
|
|
||||||
|
For tools that require arguments, pass them as a JSON string. Ensure you escape quotes correctly for your shell.
|
||||||
|
|
||||||
|
**Example: A tool that takes parameters**
|
||||||
|
|
||||||
|
Assuming a tool named `mytool` taking `a` and `b`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --tools-file tools.yaml invoke mytool '{"a": 10, "b": 20}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: A tool that queries a database**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --tools-file tools.yaml invoke db-query '{"sql": "SELECT * FROM users LIMIT 5"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Using Prebuilt Configurations
|
||||||
|
|
||||||
|
You can also use the `--prebuilt` flag to load prebuilt toolsets.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox --prebuilt cloudsql-postgres invoke cloudsql-postgres-list-instances
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Tool not found:** Ensure the `<tool-name>` matches exactly what is in your YAML file and that the file is correctly loaded via `--tools-file`.
|
||||||
|
- **Invalid parameters:** Double-check your JSON syntax. The error message will usually indicate if the JSON parsing failed or if the parameters didn't match the tool's schema.
|
||||||
|
- **Auth errors:** The `invoke` command currently does not support flows requiring client-side authorization (like OAuth flow initiation via the CLI). It works best for tools using service-side authentication (e.g., Application Default Credentials).
|
||||||
@@ -30,6 +30,46 @@ description: >
|
|||||||
| | `--user-agent-metadata` | Appends additional metadata to the User-Agent. | |
|
| | `--user-agent-metadata` | Appends additional metadata to the User-Agent. | |
|
||||||
| `-v` | `--version` | version for toolbox | |
|
| `-v` | `--version` | version for toolbox | |
|
||||||
|
|
||||||
|
## Sub Commands
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><code>invoke</code></summary>
|
||||||
|
|
||||||
|
Executes a tool directly with the provided parameters. This is useful for testing tool configurations and parameters without needing a full client setup.
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox invoke <tool-name> [params]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<tool-name>`: The name of the tool to execute (as defined in your configuration).
|
||||||
|
- `[params]`: (Optional) A JSON string containing the parameters for the tool.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><code>skills-generate</code></summary>
|
||||||
|
|
||||||
|
Generates a skill package from a specified toolset. Each tool in the toolset will have a corresponding Node.js execution script in the generated skill.
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
toolbox [--tools-file <path> | --prebuilt <name>] skills-generate --name <name> --toolset <toolset> --description <description> --output-dir <output>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flags:**
|
||||||
|
|
||||||
|
- `--name`: (Required) Name of the generated skill.
|
||||||
|
- `--toolset`: (Required) Name of the toolset to convert into a skill.
|
||||||
|
- `--description`: (Required) Description of the generated skill.
|
||||||
|
- `--output-dir`: Directory to output generated skills (default: "skills").
|
||||||
|
|
||||||
|
For more detailed instructions, see [Generate a Skill](../how-to/generate_skill.md).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Transport Configuration
|
### Transport Configuration
|
||||||
|
|||||||
@@ -100,6 +100,43 @@ See [Usage Examples](../reference/cli.md#examples).
|
|||||||
(timeseries metrics) for queries running in an AlloyDB instance using a
|
(timeseries metrics) for queries running in an AlloyDB instance using a
|
||||||
PromQL query.
|
PromQL query.
|
||||||
|
|
||||||
|
## AlloyDB Omni
|
||||||
|
|
||||||
|
* `--prebuilt` value: `alloydb-omni`
|
||||||
|
* **Environment Variables:**
|
||||||
|
* `ALLOYDB_OMNI_HOST`: (Optional) The hostname or IP address (Default: localhost).
|
||||||
|
* `ALLOYDB_OMNI_PORT`: (Optional) The port number (Default: 5432).
|
||||||
|
* `ALLOYDB_OMNI_DATABASE`: The name of the database to connect to.
|
||||||
|
* `ALLOYDB_OMNI_USER`: The database username.
|
||||||
|
* `ALLOYDB_OMNI_PASSWORD`: (Optional) The password for the database user.
|
||||||
|
* `ALLOYDB_OMNI_QUERY_PARAMS`: (Optional) Connection query parameters.
|
||||||
|
* **Tools:**
|
||||||
|
* `execute_sql`: Executes a SQL query.
|
||||||
|
* `list_tables`: Lists tables in the database.
|
||||||
|
* `list_autovacuum_configurations`: Lists autovacuum configurations in the
|
||||||
|
database.
|
||||||
|
* `list_columnar_configurations`: List AlloyDB Omni columnar-related configurations.
|
||||||
|
* `list_columnar_recommended_columns`: Lists columns that AlloyDB Omni recommends adding to the columnar engine.
|
||||||
|
* `list_memory_configurations`: Lists memory-related configurations in the
|
||||||
|
database.
|
||||||
|
* `list_top_bloated_tables`: List top bloated tables in the database.
|
||||||
|
* `list_replication_slots`: Lists replication slots in the database.
|
||||||
|
* `list_invalid_indexes`: Lists invalid indexes in the database.
|
||||||
|
* `get_query_plan`: Generate the execution plan of a statement.
|
||||||
|
* `list_views`: Lists views in the database from pg_views with a default
|
||||||
|
limit of 50 rows. Returns schemaname, viewname and the ownername.
|
||||||
|
* `list_schemas`: Lists schemas in the database.
|
||||||
|
* `database_overview`: Fetches the current state of the PostgreSQL server.
|
||||||
|
* `list_triggers`: Lists triggers in the database.
|
||||||
|
* `list_indexes`: List available user indexes in a PostgreSQL database.
|
||||||
|
* `list_sequences`: List sequences in a PostgreSQL database.
|
||||||
|
* `list_publication_tables`: List publication tables in a PostgreSQL database.
|
||||||
|
* `list_tablespaces`: Lists tablespaces in the database.
|
||||||
|
* `list_pg_settings`: List configuration parameters for the PostgreSQL server.
|
||||||
|
* `list_database_stats`: Lists the key performance and activity statistics for
|
||||||
|
each database in the AlloyDB instance.
|
||||||
|
* `list_roles`: Lists all the user-created roles in PostgreSQL database.
|
||||||
|
|
||||||
## BigQuery
|
## BigQuery
|
||||||
|
|
||||||
* `--prebuilt` value: `bigquery`
|
* `--prebuilt` value: `bigquery`
|
||||||
|
|||||||
41
go.mod
41
go.mod
@@ -47,11 +47,12 @@ require (
|
|||||||
github.com/sijms/go-ora/v2 v2.9.0
|
github.com/sijms/go-ora/v2 v2.9.0
|
||||||
github.com/snowflakedb/gosnowflake v1.18.1
|
github.com/snowflakedb/gosnowflake v1.18.1
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
|
github.com/testcontainers/testcontainers-go v0.40.0
|
||||||
github.com/thlib/go-timezone-local v0.0.7
|
github.com/thlib/go-timezone-local v0.0.7
|
||||||
github.com/trinodb/trino-go-client v0.330.0
|
github.com/trinodb/trino-go-client v0.330.0
|
||||||
github.com/valkey-io/valkey-go v1.0.68
|
github.com/valkey-io/valkey-go v1.0.68
|
||||||
github.com/yugabyte/pgx/v5 v5.5.3-yb-5
|
github.com/yugabyte/pgx/v5 v5.5.3-yb-5
|
||||||
go.mongodb.org/mongo-driver v1.17.4
|
go.mongodb.org/mongo-driver/v2 v2.4.2
|
||||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0
|
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0
|
||||||
go.opentelemetry.io/otel v1.38.0
|
go.opentelemetry.io/otel v1.38.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
|
||||||
@@ -91,16 +92,19 @@ require (
|
|||||||
cloud.google.com/go/iam v1.5.3 // indirect
|
cloud.google.com/go/iam v1.5.3 // indirect
|
||||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||||
cloud.google.com/go/trace v1.11.7 // indirect
|
cloud.google.com/go/trace v1.11.7 // indirect
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||||
github.com/99designs/keyring v1.2.2 // indirect
|
github.com/99designs/keyring v1.2.2 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
|
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
@@ -126,17 +130,29 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 // indirect
|
||||||
github.com/aws/smithy-go v1.23.0 // indirect
|
github.com/aws/smithy-go v1.23.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/couchbase/gocbcore/v10 v10.8.1 // indirect
|
github.com/couchbase/gocbcore/v10 v10.8.1 // indirect
|
||||||
github.com/couchbase/gocbcoreps v0.1.4 // indirect
|
github.com/couchbase/gocbcoreps v0.1.4 // indirect
|
||||||
github.com/couchbase/goprotostellar v1.0.2 // indirect
|
github.com/couchbase/goprotostellar v1.0.2 // indirect
|
||||||
github.com/couchbase/tools-common/errors v1.0.0 // indirect
|
github.com/couchbase/tools-common/errors v1.0.0 // indirect
|
||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 // indirect
|
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/docker v28.5.1+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
|
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
@@ -145,6 +161,7 @@ require (
|
|||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
@@ -180,27 +197,46 @@ require (
|
|||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
||||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/go-archive v0.1.0 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/mtibben/percent v0.2.1 // indirect
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
github.com/nakagami/chacha20 v0.1.0 // indirect
|
github.com/nakagami/chacha20 v0.1.0 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
github.com/zeebo/errs v1.4.0 // indirect
|
github.com/zeebo/errs v1.4.0 // indirect
|
||||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b // indirect
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b // indirect
|
||||||
@@ -233,6 +269,7 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.66.10 // indirect
|
modernc.org/libc v1.66.10 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
|||||||
63
go.sum
63
go.sum
@@ -647,6 +647,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb
|
|||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||||
@@ -800,6 +802,14 @@ 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/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||||
|
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||||
github.com/couchbase/gocb/v2 v2.11.1 h1:xWDco7Qk/XSvGUjbUWRaXi0V35nsMijJnm4vHXN/rqY=
|
github.com/couchbase/gocb/v2 v2.11.1 h1:xWDco7Qk/XSvGUjbUWRaXi0V35nsMijJnm4vHXN/rqY=
|
||||||
github.com/couchbase/gocb/v2 v2.11.1/go.mod h1:aSh1Cmd1sPRpYyiBD5iWPehPWaTVF/oYhrtOAITWb/4=
|
github.com/couchbase/gocb/v2 v2.11.1/go.mod h1:aSh1Cmd1sPRpYyiBD5iWPehPWaTVF/oYhrtOAITWb/4=
|
||||||
github.com/couchbase/gocbcore/v10 v10.8.1 h1:i4SnH0DH9APGC4GS2vS2m+3u08V7oJwviamOXdgAZOQ=
|
github.com/couchbase/gocbcore/v10 v10.8.1 h1:i4SnH0DH9APGC4GS2vS2m+3u08V7oJwviamOXdgAZOQ=
|
||||||
@@ -816,8 +826,12 @@ github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8 h1:MQ
|
|||||||
github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
|
github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
|
||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 h1:HU9DlAYYWR69jQnLN6cpg0fh0hxW/8d5hnglCXXjW78=
|
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 h1:HU9DlAYYWR69jQnLN6cpg0fh0hxW/8d5hnglCXXjW78=
|
||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
|
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||||
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -826,10 +840,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||||
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
@@ -840,6 +856,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo=
|
github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo=
|
||||||
github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||||
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
|
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
|
||||||
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
||||||
github.com/elastic/go-elasticsearch/v9 v9.2.0 h1:COeL/g20+ixnUbffe4Wfbu88emrHjAq/LhVfmrjqRQs=
|
github.com/elastic/go-elasticsearch/v9 v9.2.0 h1:COeL/g20+ixnUbffe4Wfbu88emrHjAq/LhVfmrjqRQs=
|
||||||
@@ -913,6 +931,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
@@ -1174,9 +1194,13 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/looker-open-source/sdk-codegen/go v0.25.22 h1:DGYt1v2R2uE/m71sWAvgxsJnDLM9B7C40N5/CTDlE2A=
|
github.com/looker-open-source/sdk-codegen/go v0.25.22 h1:DGYt1v2R2uE/m71sWAvgxsJnDLM9B7C40N5/CTDlE2A=
|
||||||
github.com/looker-open-source/sdk-codegen/go v0.25.22/go.mod h1:Br1ntSiruDJ/4nYNjpYyWyCbqJ7+GQceWbIgn0hYims=
|
github.com/looker-open-source/sdk-codegen/go v0.25.22/go.mod h1:Br1ntSiruDJ/4nYNjpYyWyCbqJ7+GQceWbIgn0hYims=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||||
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
@@ -1194,8 +1218,18 @@ github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8D
|
|||||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||||
|
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -1204,8 +1238,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
github.com/nakagami/chacha20 v0.1.0 h1:2fbf5KeVUw7oRpAe6/A7DqvBJLYYu0ka5WstFbnkEVo=
|
github.com/nakagami/chacha20 v0.1.0 h1:2fbf5KeVUw7oRpAe6/A7DqvBJLYYu0ka5WstFbnkEVo=
|
||||||
@@ -1254,6 +1288,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
@@ -1275,6 +1311,8 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF
|
|||||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
|
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
|
||||||
@@ -1312,9 +1350,15 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
|
||||||
github.com/thlib/go-timezone-local v0.0.7 h1:fX8zd3aJydqLlTs/TrROrIIdztzsdFV23OzOQx31jII=
|
github.com/thlib/go-timezone-local v0.0.7 h1:fX8zd3aJydqLlTs/TrROrIIdztzsdFV23OzOQx31jII=
|
||||||
github.com/thlib/go-timezone-local v0.0.7/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
|
github.com/thlib/go-timezone-local v0.0.7/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/trinodb/trino-go-client v0.330.0 h1:TBbHjFBuRjYbGtkNyRAJfzLOcwvz8ECihtMtxSzXqOc=
|
github.com/trinodb/trino-go-client v0.330.0 h1:TBbHjFBuRjYbGtkNyRAJfzLOcwvz8ECihtMtxSzXqOc=
|
||||||
github.com/trinodb/trino-go-client v0.330.0/go.mod h1:BXj9QNy6pA4Gn8eIu9dVdRhetABCjFAOZ6xxsVsOZJE=
|
github.com/trinodb/trino-go-client v0.330.0/go.mod h1:BXj9QNy6pA4Gn8eIu9dVdRhetABCjFAOZ6xxsVsOZJE=
|
||||||
github.com/valkey-io/valkey-go v1.0.68 h1:bTbfonp49b41DqrF30q+y2JL3gcbjd2IiacFAtO4JBA=
|
github.com/valkey-io/valkey-go v1.0.68 h1:bTbfonp49b41DqrF30q+y2JL3gcbjd2IiacFAtO4JBA=
|
||||||
@@ -1347,6 +1391,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||||
@@ -1356,8 +1402,8 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
|||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
go.mongodb.org/mongo-driver/v2 v2.4.2 h1:HrJ+Auygxceby9MLp3YITobef5a8Bv4HcPFIkml1U7U=
|
||||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
go.mongodb.org/mongo-driver/v2 v2.4.2/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -1627,6 +1673,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -1648,6 +1695,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -1700,6 +1748,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
@@ -2129,6 +2178,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
161
internal/cli/invoke/command.go
Normal file
161
internal/cli/invoke/command.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/log"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server/resources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RootCommand defines the interface for required by invoke subcommand.
|
||||||
|
// This allows subcommands to access shared resources and functionality without
|
||||||
|
// direct coupling to the root command's implementation.
|
||||||
|
type RootCommand interface {
|
||||||
|
// Config returns a copy of the current server configuration.
|
||||||
|
Config() server.ServerConfig
|
||||||
|
|
||||||
|
// Out returns the writer used for standard output.
|
||||||
|
Out() io.Writer
|
||||||
|
|
||||||
|
// LoadConfig loads and merges the configuration from files, folders, and prebuilts.
|
||||||
|
LoadConfig(ctx context.Context) error
|
||||||
|
|
||||||
|
// Setup initializes the runtime environment, including logging and telemetry.
|
||||||
|
// It returns the updated context and a shutdown function to be called when finished.
|
||||||
|
Setup(ctx context.Context) (context.Context, func(context.Context) error, error)
|
||||||
|
|
||||||
|
// Logger returns the logger instance.
|
||||||
|
Logger() log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommand(rootCmd RootCommand) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "invoke <tool-name> [params]",
|
||||||
|
Short: "Execute a tool directly",
|
||||||
|
Long: `Execute a tool directly with parameters.
|
||||||
|
Params must be a JSON string.
|
||||||
|
Example:
|
||||||
|
toolbox invoke my-tool '{"param1": "value1"}'`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(c *cobra.Command, args []string) error {
|
||||||
|
return runInvoke(c, args, rootCmd)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInvoke(cmd *cobra.Command, args []string, rootCmd RootCommand) error {
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx, shutdown, err := rootCmd.Setup(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load and merge tool configurations
|
||||||
|
if err := rootCmd.LoadConfig(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Resources
|
||||||
|
sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, err := server.InitializeConfigs(ctx, rootCmd.Config())
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("failed to initialize resources: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceMgr := resources.NewResourceManager(sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap)
|
||||||
|
|
||||||
|
// Execute Tool
|
||||||
|
toolName := args[0]
|
||||||
|
tool, ok := resourceMgr.GetTool(toolName)
|
||||||
|
if !ok {
|
||||||
|
errMsg := fmt.Errorf("tool %q not found", toolName)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramsInput string
|
||||||
|
if len(args) > 1 {
|
||||||
|
paramsInput = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make(map[string]any)
|
||||||
|
if paramsInput != "" {
|
||||||
|
if err := json.Unmarshal([]byte(paramsInput), ¶ms); err != nil {
|
||||||
|
errMsg := fmt.Errorf("params must be a valid JSON string: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedParams, err := parameters.ParseParams(tool.GetParameters(), params, nil)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("invalid parameters: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedParams, err = tool.EmbedParams(ctx, parsedParams, resourceMgr.GetEmbeddingModelMap())
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error embedding parameters: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client Auth not supported for ephemeral CLI call
|
||||||
|
requiresAuth, err := tool.RequiresClientAuthorization(resourceMgr)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("failed to check auth requirements: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
if requiresAuth {
|
||||||
|
errMsg := fmt.Errorf("client authorization is not supported")
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := tool.Invoke(ctx, resourceMgr, parsedParams, "")
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("tool execution failed: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print Result
|
||||||
|
output, err := json.MarshalIndent(result, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("failed to marshal result: %w", err)
|
||||||
|
rootCmd.Logger().ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
fmt.Fprintln(rootCmd.Out(), string(output))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
298
internal/cli/skills/command.go
Normal file
298
internal/cli/skills/command.go
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package skills
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/log"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server/resources"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RootCommand defines the interface for required by invoke subcommand.
|
||||||
|
// This allows subcommands to access shared resources and functionality without
|
||||||
|
// direct coupling to the root command's implementation.
|
||||||
|
type RootCommand interface {
|
||||||
|
// Config returns a copy of the current server configuration.
|
||||||
|
Config() server.ServerConfig
|
||||||
|
|
||||||
|
// LoadConfig loads and merges the configuration from files, folders, and prebuilts.
|
||||||
|
LoadConfig(ctx context.Context) error
|
||||||
|
|
||||||
|
// Setup initializes the runtime environment, including logging and telemetry.
|
||||||
|
// It returns the updated context and a shutdown function to be called when finished.
|
||||||
|
Setup(ctx context.Context) (context.Context, func(context.Context) error, error)
|
||||||
|
|
||||||
|
// Logger returns the logger instance.
|
||||||
|
Logger() log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command is the command for generating skills.
|
||||||
|
type Command struct {
|
||||||
|
*cobra.Command
|
||||||
|
rootCmd RootCommand
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
toolset string
|
||||||
|
outputDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter represents a parameter of a tool.
|
||||||
|
type Parameter struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Default interface{} `json:"default"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool represents a tool.
|
||||||
|
type Tool struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Parameters []Parameter `json:"parameters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config represents the structure of the tools.yaml file.
|
||||||
|
type Config struct {
|
||||||
|
Sources map[string]interface{} `yaml:"sources,omitempty"`
|
||||||
|
Tools map[string]map[string]interface{} `yaml:"tools"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverConfig holds the configuration used to start the toolbox server.
|
||||||
|
type serverConfig struct {
|
||||||
|
prebuiltConfigs []string
|
||||||
|
toolsFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommand creates a new Command.
|
||||||
|
func NewCommand(rootCmd RootCommand) *cobra.Command {
|
||||||
|
cmd := &Command{
|
||||||
|
rootCmd: rootCmd,
|
||||||
|
}
|
||||||
|
cmd.Command = &cobra.Command{
|
||||||
|
Use: "skills-generate",
|
||||||
|
Short: "Generate skills from tool configurations",
|
||||||
|
RunE: func(c *cobra.Command, args []string) error {
|
||||||
|
return cmd.run(c)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&cmd.outputDir, "output-dir", "skills", "Directory to output generated skills")
|
||||||
|
cmd.Flags().StringVar(&cmd.toolset, "toolset", "", "Name of the toolset (and generated skill folder). If provided, only tools in this toolset are generated.")
|
||||||
|
cmd.Flags().StringVar(&cmd.name, "name", "", "Name of the generated skill.")
|
||||||
|
cmd.Flags().StringVar(&cmd.description, "description", "", "Description of the generated skill")
|
||||||
|
cmd.MarkFlagRequired("name")
|
||||||
|
cmd.MarkFlagRequired("description")
|
||||||
|
return cmd.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) run(cmd *cobra.Command) error {
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx, shutdown, err := c.rootCmd.Setup(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger := c.rootCmd.Logger()
|
||||||
|
|
||||||
|
toolsFile, err := cmd.Flags().GetString("tools-file")
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error getting tools-file flag: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
prebuiltConfigs, err := cmd.Flags().GetStringSlice("prebuilt")
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error getting prebuilt flag: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and merge tool configurations
|
||||||
|
if err := c.rootCmd.LoadConfig(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prebuiltConfigs) == 0 && toolsFile == "" {
|
||||||
|
logger.InfoContext(ctx, "No configurations found to process. Use --tools-file or --prebuilt.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(c.outputDir, 0755); err != nil {
|
||||||
|
errMsg := fmt.Errorf("error creating output directory: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, fmt.Sprintf("Generating skill '%s'...", c.name))
|
||||||
|
|
||||||
|
config := serverConfig{
|
||||||
|
prebuiltConfigs: prebuiltConfigs,
|
||||||
|
toolsFile: toolsFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize toolbox and collect tools
|
||||||
|
allTools, err := c.collectTools(ctx)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error collecting tools: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allTools) == 0 {
|
||||||
|
logger.InfoContext(ctx, "No tools found to generate.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the combined skill
|
||||||
|
skillPath := filepath.Join(c.outputDir, c.name)
|
||||||
|
if err := os.MkdirAll(skillPath, 0755); err != nil {
|
||||||
|
errMsg := fmt.Errorf("error creating skill directory: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate assets directory if needed
|
||||||
|
assetsPath := filepath.Join(skillPath, "assets")
|
||||||
|
if toolsFile != "" {
|
||||||
|
if err := os.MkdirAll(assetsPath, 0755); err != nil {
|
||||||
|
errMsg := fmt.Errorf("error creating assets dir: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate scripts
|
||||||
|
scriptsPath := filepath.Join(skillPath, "scripts")
|
||||||
|
if err := os.MkdirAll(scriptsPath, 0755); err != nil {
|
||||||
|
errMsg := fmt.Errorf("error creating scripts dir: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range allTools {
|
||||||
|
specificToolsFileName := ""
|
||||||
|
if toolsFile != "" {
|
||||||
|
minimizedContent, err := generateFilteredConfig(toolsFile, tool.Name)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("Error generating filtered config for %s: %v", tool.Name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if minimizedContent != nil {
|
||||||
|
specificToolsFileName = fmt.Sprintf("%s.yaml", tool.Name)
|
||||||
|
destPath := filepath.Join(assetsPath, specificToolsFileName)
|
||||||
|
if err := os.WriteFile(destPath, minimizedContent, 0644); err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("Error writing filtered config for %s: %v", tool.Name, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptContent, err := generateShellScriptContent(tool.Name, config, specificToolsFileName)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("Error generating script content for %s: %v", tool.Name, err))
|
||||||
|
} else {
|
||||||
|
scriptFilename := filepath.Join(scriptsPath, fmt.Sprintf("%s.js", tool.Name))
|
||||||
|
if err := os.WriteFile(scriptFilename, []byte(scriptContent), 0755); err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("Error writing script %s: %v", scriptFilename, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate SKILL.md
|
||||||
|
skillContent, err := generateSkillMarkdown(c.name, c.description, allTools)
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Errorf("error generating SKILL.md content: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
skillMdPath := filepath.Join(skillPath, "SKILL.md")
|
||||||
|
if err := os.WriteFile(skillMdPath, []byte(skillContent), 0644); err != nil {
|
||||||
|
errMsg := fmt.Errorf("error writing SKILL.md: %w", err)
|
||||||
|
logger.ErrorContext(ctx, errMsg.Error())
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, fmt.Sprintf("Successfully generated skill '%s' with %d tools.", c.name, len(allTools)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) collectTools(ctx context.Context) (map[string]Tool, error) {
|
||||||
|
// Initialize Resources
|
||||||
|
sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, err := server.InitializeConfigs(ctx, c.rootCmd.Config())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize resources: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceMgr := resources.NewResourceManager(sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap)
|
||||||
|
|
||||||
|
result := make(map[string]Tool)
|
||||||
|
|
||||||
|
var toolsToProcess []string
|
||||||
|
|
||||||
|
if c.toolset != "" {
|
||||||
|
ts, ok := resourceMgr.GetToolset(c.toolset)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("toolset %q not found", c.toolset)
|
||||||
|
}
|
||||||
|
toolsToProcess = ts.ToolNames
|
||||||
|
} else {
|
||||||
|
// All tools
|
||||||
|
for name := range toolsMap {
|
||||||
|
toolsToProcess = append(toolsToProcess, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, toolName := range toolsToProcess {
|
||||||
|
t, ok := resourceMgr.GetTool(toolName)
|
||||||
|
if !ok {
|
||||||
|
// Should happen only if toolset refers to non-existent tool, but good to check
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
params := []Parameter{}
|
||||||
|
for _, p := range t.GetParameters() {
|
||||||
|
manifest := p.Manifest()
|
||||||
|
params = append(params, Parameter{
|
||||||
|
Name: p.GetName(),
|
||||||
|
Description: manifest.Description, // Use description from manifest
|
||||||
|
Type: p.GetType(),
|
||||||
|
Default: p.GetDefault(),
|
||||||
|
Required: p.GetRequired(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := t.Manifest()
|
||||||
|
result[toolName] = Tool{
|
||||||
|
Name: toolName,
|
||||||
|
Description: manifest.Description,
|
||||||
|
Parameters: params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
247
internal/cli/skills/generator.go
Normal file
247
internal/cli/skills/generator.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package skills
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const skillTemplate = `---
|
||||||
|
name: {{.SkillName}}
|
||||||
|
description: {{.SkillDescription}}
|
||||||
|
---
|
||||||
|
|
||||||
|
Here is a list of scripts which can be used.
|
||||||
|
|
||||||
|
{{range .Tools}}
|
||||||
|
# {{.Name}}
|
||||||
|
|
||||||
|
{{.Description}}
|
||||||
|
|
||||||
|
{{.ParametersSchema}}
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
{{.Usage}}
|
||||||
|
|
||||||
|
---
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
type toolTemplateData struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
ParametersSchema string
|
||||||
|
Usage string
|
||||||
|
}
|
||||||
|
|
||||||
|
type skillTemplateData struct {
|
||||||
|
SkillName string
|
||||||
|
SkillDescription string
|
||||||
|
Tools []toolTemplateData
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSkillMarkdown(skillName, skillDescription string, toolsMap map[string]Tool) (string, error) {
|
||||||
|
var toolsData []toolTemplateData
|
||||||
|
|
||||||
|
// Order tools based on name
|
||||||
|
var tools []Tool
|
||||||
|
for _, tool := range toolsMap {
|
||||||
|
tools = append(tools, tool)
|
||||||
|
}
|
||||||
|
sort.Slice(tools, func(i, j int) bool {
|
||||||
|
return tools[i].Name < tools[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tool := range tools {
|
||||||
|
parametersSchema, err := formatParameters(tool.Parameters)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
usage := fmt.Sprintf("`bash\nnode scripts/%s.js '{\"<param_name>\": \"<param_value>\"}'\n`", tool.Name)
|
||||||
|
|
||||||
|
toolsData = append(toolsData, toolTemplateData{
|
||||||
|
Name: tool.Name,
|
||||||
|
Description: tool.Description,
|
||||||
|
ParametersSchema: parametersSchema,
|
||||||
|
Usage: usage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
data := skillTemplateData{
|
||||||
|
SkillName: skillName,
|
||||||
|
SkillDescription: skillDescription,
|
||||||
|
Tools: toolsData,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("markdown").Parse(skillTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error parsing markdown template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
return "", fmt.Errorf("error executing markdown template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeScriptTemplate = `#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const toolName = "{{.Name}}";
|
||||||
|
const prebuiltNames = {{.PrebuiltNamesJSON}};
|
||||||
|
const toolsFileName = "{{.ToolsFileName}}";
|
||||||
|
|
||||||
|
let configArgs = [];
|
||||||
|
if (prebuiltNames.length > 0) {
|
||||||
|
prebuiltNames.forEach(name => {
|
||||||
|
configArgs.push("--prebuilt", name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolsFileName) {
|
||||||
|
configArgs.push("--tools-file", path.join(__dirname, "..", "assets", toolsFileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const toolboxArgs = [...configArgs, "invoke", toolName, ...args];
|
||||||
|
|
||||||
|
const command = process.platform === 'win32' ? 'toolbox.exe' : 'toolbox';
|
||||||
|
|
||||||
|
const child = spawn(command, toolboxArgs, { stdio: 'inherit' });
|
||||||
|
|
||||||
|
child.on('close', (code) => {
|
||||||
|
process.exit(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (err) => {
|
||||||
|
console.error("Error executing toolbox:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
||||||
|
type scriptData struct {
|
||||||
|
Name string
|
||||||
|
PrebuiltNamesJSON string
|
||||||
|
ToolsFileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateShellScriptContent(name string, config serverConfig, toolsFileName string) (string, error) {
|
||||||
|
prebuiltJSON, _ := json.Marshal(config.prebuiltConfigs)
|
||||||
|
|
||||||
|
data := scriptData{
|
||||||
|
Name: name,
|
||||||
|
PrebuiltNamesJSON: string(prebuiltJSON),
|
||||||
|
ToolsFileName: toolsFileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("script").Parse(nodeScriptTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error parsing script template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
return "", fmt.Errorf("error executing script template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatParameters(params []Parameter) (string, error) {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
properties := make(map[string]interface{})
|
||||||
|
var required []string
|
||||||
|
|
||||||
|
for _, p := range params {
|
||||||
|
paramMap := map[string]interface{}{
|
||||||
|
"type": p.Type,
|
||||||
|
"description": p.Description,
|
||||||
|
}
|
||||||
|
if p.Default != nil {
|
||||||
|
paramMap["default"] = p.Default
|
||||||
|
}
|
||||||
|
properties[p.Name] = paramMap
|
||||||
|
if p.Required {
|
||||||
|
required = append(required, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := map[string]interface{}{
|
||||||
|
"type": "object",
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
if len(required) > 0 {
|
||||||
|
schema["required"] = required
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaJSON, err := json.MarshalIndent(schema, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error generating parameters schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("## Parameters\n\n```json\n%s\n```", string(schemaJSON)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFilteredConfig(toolsFile, toolName string) ([]byte, error) {
|
||||||
|
data, err := os.ReadFile(toolsFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", toolsFile, err)
|
||||||
|
}
|
||||||
|
var cfg Config
|
||||||
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing YAML from %s: %w", toolsFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cfg.Tools[toolName]; !ok {
|
||||||
|
return nil, nil // Tool not found in this file
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredCfg := Config{
|
||||||
|
Tools: map[string]map[string]interface{}{
|
||||||
|
toolName: cfg.Tools[toolName],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add relevant source if exists
|
||||||
|
if src, ok := cfg.Tools[toolName]["source"].(string); ok && src != "" {
|
||||||
|
if sourceData, exists := cfg.Sources[src]; exists {
|
||||||
|
if filteredCfg.Sources == nil {
|
||||||
|
filteredCfg.Sources = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
filteredCfg.Sources[src] = sourceData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredData, err := yaml.Marshal(filteredCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling filtered tools for %s: %w", toolName, err)
|
||||||
|
}
|
||||||
|
return filteredData, nil
|
||||||
|
}
|
||||||
300
internal/cli/skills/generator_test.go
Normal file
300
internal/cli/skills/generator_test.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package skills
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatParameters(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
params []Parameter
|
||||||
|
wantContains []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty parameters",
|
||||||
|
params: []Parameter{},
|
||||||
|
wantContains: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single required string parameter",
|
||||||
|
params: []Parameter{
|
||||||
|
{
|
||||||
|
Name: "param1",
|
||||||
|
Description: "A test parameter",
|
||||||
|
Type: "string",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantContains: []string{
|
||||||
|
"## Parameters",
|
||||||
|
"```json",
|
||||||
|
`"type": "object"`,
|
||||||
|
`"properties": {`,
|
||||||
|
`"param1": {`,
|
||||||
|
`"type": "string"`,
|
||||||
|
`"description": "A test parameter"`,
|
||||||
|
`"required": [`,
|
||||||
|
`"param1"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed parameters with defaults",
|
||||||
|
params: []Parameter{
|
||||||
|
{
|
||||||
|
Name: "param1",
|
||||||
|
Description: "Param 1",
|
||||||
|
Type: "string",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "param2",
|
||||||
|
Description: "Param 2",
|
||||||
|
Type: "integer",
|
||||||
|
Default: 42,
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantContains: []string{
|
||||||
|
`"param1": {`,
|
||||||
|
`"param2": {`,
|
||||||
|
`"default": 42`,
|
||||||
|
`"required": [`,
|
||||||
|
`"param1"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := formatParameters(tt.params)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("formatParameters() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tt.params) == 0 {
|
||||||
|
if got != "" {
|
||||||
|
t.Errorf("formatParameters() = %v, want empty string", got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, want := range tt.wantContains {
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("formatParameters() result missing expected string: %s\nGot:\n%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSkillMarkdown(t *testing.T) {
|
||||||
|
tools := map[string]Tool{
|
||||||
|
"tool1": {
|
||||||
|
Name: "tool1",
|
||||||
|
Description: "First tool",
|
||||||
|
Parameters: []Parameter{
|
||||||
|
{Name: "p1", Type: "string", Description: "d1", Required: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := generateSkillMarkdown("MySkill", "My Description", tools)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generateSkillMarkdown() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSubstrings := []string{
|
||||||
|
"name: MySkill",
|
||||||
|
"description: My Description",
|
||||||
|
"# tool1",
|
||||||
|
"First tool",
|
||||||
|
"## Parameters",
|
||||||
|
"node scripts/tool1.js '{\"<param_name>\": \"<param_value>\"}'",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range expectedSubstrings {
|
||||||
|
if !strings.Contains(got, s) {
|
||||||
|
t.Errorf("generateSkillMarkdown() missing substring %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateShellScriptContent(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
toolName string
|
||||||
|
config serverConfig
|
||||||
|
toolsFileName string
|
||||||
|
wantContains []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic script",
|
||||||
|
toolName: "test-tool",
|
||||||
|
config: serverConfig{
|
||||||
|
prebuiltConfigs: []string{},
|
||||||
|
},
|
||||||
|
toolsFileName: "",
|
||||||
|
wantContains: []string{
|
||||||
|
`const toolName = "test-tool";`,
|
||||||
|
`const prebuiltNames = [];`,
|
||||||
|
`const toolsFileName = "";`,
|
||||||
|
`const toolboxArgs = [...configArgs, "invoke", toolName, ...args];`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "script with prebuilts and tools file",
|
||||||
|
toolName: "complex-tool",
|
||||||
|
config: serverConfig{
|
||||||
|
prebuiltConfigs: []string{"pre1", "pre2"},
|
||||||
|
},
|
||||||
|
toolsFileName: "tools.yaml",
|
||||||
|
wantContains: []string{
|
||||||
|
`const toolName = "complex-tool";`,
|
||||||
|
`const prebuiltNames = ["pre1","pre2"];`,
|
||||||
|
`const toolsFileName = "tools.yaml";`,
|
||||||
|
`configArgs.push("--prebuilt", name);`,
|
||||||
|
`configArgs.push("--tools-file", path.join(__dirname, "..", "assets", toolsFileName));`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := generateShellScriptContent(tt.toolName, tt.config, tt.toolsFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generateShellScriptContent() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range tt.wantContains {
|
||||||
|
if !strings.Contains(got, s) {
|
||||||
|
t.Errorf("generateShellScriptContent() missing substring %q\nGot:\n%s", s, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateFilteredConfig(t *testing.T) {
|
||||||
|
// Setup temporary directory and file
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
toolsFile := filepath.Join(tmpDir, "tools.yaml")
|
||||||
|
|
||||||
|
configContent := `
|
||||||
|
sources:
|
||||||
|
src1:
|
||||||
|
type: "postgres"
|
||||||
|
connection_string: "conn1"
|
||||||
|
src2:
|
||||||
|
type: "mysql"
|
||||||
|
connection_string: "conn2"
|
||||||
|
tools:
|
||||||
|
tool1:
|
||||||
|
source: "src1"
|
||||||
|
query: "SELECT 1"
|
||||||
|
tool2:
|
||||||
|
source: "src2"
|
||||||
|
query: "SELECT 2"
|
||||||
|
tool3:
|
||||||
|
type: "http" # No source
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(toolsFile, []byte(configContent), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create temp tools file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
toolName string
|
||||||
|
wantCfg Config
|
||||||
|
wantErr bool
|
||||||
|
wantNil bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "tool with source",
|
||||||
|
toolName: "tool1",
|
||||||
|
wantCfg: Config{
|
||||||
|
Sources: map[string]interface{}{
|
||||||
|
"src1": map[string]interface{}{
|
||||||
|
"type": "postgres",
|
||||||
|
"connection_string": "conn1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tools: map[string]map[string]interface{}{
|
||||||
|
"tool1": {
|
||||||
|
"source": "src1",
|
||||||
|
"query": "SELECT 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tool without source",
|
||||||
|
toolName: "tool3",
|
||||||
|
wantCfg: Config{
|
||||||
|
Tools: map[string]map[string]interface{}{
|
||||||
|
"tool3": {
|
||||||
|
"type": "http",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existent tool",
|
||||||
|
toolName: "missing-tool",
|
||||||
|
wantNil: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotBytes, err := generateFilteredConfig(toolsFile, tt.toolName)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("generateFilteredConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantNil {
|
||||||
|
if gotBytes != nil {
|
||||||
|
t.Errorf("generateFilteredConfig() expected nil, got %s", string(gotBytes))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotCfg Config
|
||||||
|
if err := yaml.Unmarshal(gotBytes, &gotCfg); err != nil {
|
||||||
|
t.Errorf("Failed to unmarshal result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.wantCfg, gotCfg); diff != "" {
|
||||||
|
t.Errorf("generateFilteredConfig() mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewLogger creates a new logger based on the provided format and level.
|
||||||
|
func NewLogger(format, level string, out, err io.Writer) (Logger, error) {
|
||||||
|
switch strings.ToLower(format) {
|
||||||
|
case "json":
|
||||||
|
return NewStructuredLogger(out, err, level)
|
||||||
|
case "standard":
|
||||||
|
return NewStdLogger(out, err, level)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("logging format invalid: %s", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StdLogger is the standard logger
|
// StdLogger is the standard logger
|
||||||
type StdLogger struct {
|
type StdLogger struct {
|
||||||
outLogger *slog.Logger
|
outLogger *slog.Logger
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var expectedToolSources = []string{
|
var expectedToolSources = []string{
|
||||||
|
"alloydb-omni",
|
||||||
"alloydb-postgres-admin",
|
"alloydb-postgres-admin",
|
||||||
"alloydb-postgres-observability",
|
"alloydb-postgres-observability",
|
||||||
"alloydb-postgres",
|
"alloydb-postgres",
|
||||||
@@ -99,36 +100,40 @@ func TestLoadPrebuiltToolYAMLs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPrebuiltTool(t *testing.T) {
|
func TestGetPrebuiltTool(t *testing.T) {
|
||||||
alloydb_admin_config, _ := Get("alloydb-postgres-admin")
|
alloydb_omni_config := getOrFatal(t, "alloydb-omni")
|
||||||
alloydb_observability_config, _ := Get("alloydb-postgres-observability")
|
alloydb_admin_config := getOrFatal(t, "alloydb-postgres-admin")
|
||||||
alloydb_config, _ := Get("alloydb-postgres")
|
alloydb_observability_config := getOrFatal(t, "alloydb-postgres-observability")
|
||||||
bigquery_config, _ := Get("bigquery")
|
alloydb_config := getOrFatal(t, "alloydb-postgres")
|
||||||
clickhouse_config, _ := Get("clickhouse")
|
bigquery_config := getOrFatal(t, "bigquery")
|
||||||
cloudsqlpg_observability_config, _ := Get("cloud-sql-postgres-observability")
|
clickhouse_config := getOrFatal(t, "clickhouse")
|
||||||
cloudsqlpg_config, _ := Get("cloud-sql-postgres")
|
cloudsqlpg_observability_config := getOrFatal(t, "cloud-sql-postgres-observability")
|
||||||
cloudsqlpg_admin_config, _ := Get("cloud-sql-postgres-admin")
|
cloudsqlpg_config := getOrFatal(t, "cloud-sql-postgres")
|
||||||
cloudsqlmysql_admin_config, _ := Get("cloud-sql-mysql-admin")
|
cloudsqlpg_admin_config := getOrFatal(t, "cloud-sql-postgres-admin")
|
||||||
cloudsqlmssql_admin_config, _ := Get("cloud-sql-mssql-admin")
|
cloudsqlmysql_admin_config := getOrFatal(t, "cloud-sql-mysql-admin")
|
||||||
cloudsqlmysql_observability_config, _ := Get("cloud-sql-mysql-observability")
|
cloudsqlmssql_admin_config := getOrFatal(t, "cloud-sql-mssql-admin")
|
||||||
cloudsqlmysql_config, _ := Get("cloud-sql-mysql")
|
cloudsqlmysql_observability_config := getOrFatal(t, "cloud-sql-mysql-observability")
|
||||||
cloudsqlmssql_observability_config, _ := Get("cloud-sql-mssql-observability")
|
cloudsqlmysql_config := getOrFatal(t, "cloud-sql-mysql")
|
||||||
cloudsqlmssql_config, _ := Get("cloud-sql-mssql")
|
cloudsqlmssql_observability_config := getOrFatal(t, "cloud-sql-mssql-observability")
|
||||||
dataplex_config, _ := Get("dataplex")
|
cloudsqlmssql_config := getOrFatal(t, "cloud-sql-mssql")
|
||||||
firestoreconfig, _ := Get("firestore")
|
dataplex_config := getOrFatal(t, "dataplex")
|
||||||
looker_config, _ := Get("looker")
|
firestoreconfig := getOrFatal(t, "firestore")
|
||||||
lookerca_config, _ := Get("looker-conversational-analytics")
|
looker_config := getOrFatal(t, "looker")
|
||||||
mysql_config, _ := Get("mysql")
|
lookerca_config := getOrFatal(t, "looker-conversational-analytics")
|
||||||
mssql_config, _ := Get("mssql")
|
mysql_config := getOrFatal(t, "mysql")
|
||||||
oceanbase_config, _ := Get("oceanbase")
|
mssql_config := getOrFatal(t, "mssql")
|
||||||
postgresconfig, _ := Get("postgres")
|
oceanbase_config := getOrFatal(t, "oceanbase")
|
||||||
singlestore_config, _ := Get("singlestore")
|
postgresconfig := getOrFatal(t, "postgres")
|
||||||
spanner_config, _ := Get("spanner")
|
singlestore_config := getOrFatal(t, "singlestore")
|
||||||
spannerpg_config, _ := Get("spanner-postgres")
|
spanner_config := getOrFatal(t, "spanner")
|
||||||
mindsdb_config, _ := Get("mindsdb")
|
spannerpg_config := getOrFatal(t, "spanner-postgres")
|
||||||
sqlite_config, _ := Get("sqlite")
|
mindsdb_config := getOrFatal(t, "mindsdb")
|
||||||
neo4jconfig, _ := Get("neo4j")
|
sqlite_config := getOrFatal(t, "sqlite")
|
||||||
healthcare_config, _ := Get("cloud-healthcare")
|
neo4jconfig := getOrFatal(t, "neo4j")
|
||||||
snowflake_config, _ := Get("snowflake")
|
healthcare_config := getOrFatal(t, "cloud-healthcare")
|
||||||
|
snowflake_config := getOrFatal(t, "snowflake")
|
||||||
|
if len(alloydb_omni_config) <= 0 {
|
||||||
|
t.Fatalf("unexpected error: could not fetch alloydb omni prebuilt tools yaml")
|
||||||
|
}
|
||||||
if len(alloydb_admin_config) <= 0 {
|
if len(alloydb_admin_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch alloydb admin prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch alloydb admin prebuilt tools yaml")
|
||||||
}
|
}
|
||||||
@@ -233,3 +238,11 @@ func TestFailGetPrebuiltTool(t *testing.T) {
|
|||||||
t.Fatalf("unexpected an error but got nil.")
|
t.Fatalf("unexpected an error but got nil.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrFatal(t *testing.T, prebuiltSourceConfig string) []byte {
|
||||||
|
bytes, err := Get(prebuiltSourceConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot get prebuilt config for %q, error %v", prebuiltSourceConfig, err)
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|||||||
277
internal/prebuiltconfigs/tools/alloydb-omni.yaml
Normal file
277
internal/prebuiltconfigs/tools/alloydb-omni.yaml
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Copyright 2026 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
sources:
|
||||||
|
alloydb-omni-source:
|
||||||
|
kind: postgres
|
||||||
|
host: ${ALLOYDB_OMNI_HOST:localhost}
|
||||||
|
port: ${ALLOYDB_OMNI_PORT:5432}
|
||||||
|
database: ${ALLOYDB_OMNI_DATABASE}
|
||||||
|
user: ${ALLOYDB_OMNI_USER}
|
||||||
|
password: ${ALLOYDB_OMNI_PASSWORD:}
|
||||||
|
queryParams: ${ALLOYDB_OMNI_QUERY_PARAMS:}
|
||||||
|
|
||||||
|
tools:
|
||||||
|
execute_sql:
|
||||||
|
kind: postgres-execute-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: Use this tool to execute sql.
|
||||||
|
|
||||||
|
list_tables:
|
||||||
|
kind: postgres-list-tables
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, owner, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas."
|
||||||
|
|
||||||
|
list_active_queries:
|
||||||
|
kind: postgres-list-active-queries
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List the top N (default 50) currently running queries (state='active') from pg_stat_activity, ordered by longest-running first. Returns pid, user, database, application_name, client_addr, state, wait_event_type/wait_event, backend/xact/query start times, computed query_duration, and the SQL text."
|
||||||
|
|
||||||
|
list_available_extensions:
|
||||||
|
kind: postgres-list-available-extensions
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "Discover all PostgreSQL extensions available for installation on this server, returning name, default_version, and description."
|
||||||
|
|
||||||
|
list_installed_extensions:
|
||||||
|
kind: postgres-list-installed-extensions
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."
|
||||||
|
|
||||||
|
long_running_transactions:
|
||||||
|
kind: postgres-long-running-transactions
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_locks:
|
||||||
|
kind: postgres-list-locks
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
replication_stats:
|
||||||
|
kind: postgres-replication-stats
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_autovacuum_configurations:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List PostgreSQL autovacuum-related configurations (name and current setting) from pg_settings."
|
||||||
|
statement: |
|
||||||
|
SELECT name,
|
||||||
|
setting
|
||||||
|
FROM pg_settings
|
||||||
|
WHERE category = 'Autovacuum';
|
||||||
|
|
||||||
|
list_columnar_configurations:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List AlloyDB Omni columnar-related configurations (name and current setting) from pg_settings."
|
||||||
|
statement: |
|
||||||
|
SELECT name,
|
||||||
|
setting
|
||||||
|
FROM pg_settings
|
||||||
|
WHERE name like 'google_columnar_engine.%';
|
||||||
|
|
||||||
|
list_columnar_recommended_columns:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "Lists columns that AlloyDB Omni recommends adding to the columnar engine to improve query performance."
|
||||||
|
statement: select * from g_columnar_recommended_columns;
|
||||||
|
|
||||||
|
list_memory_configurations:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List PostgreSQL memory-related configurations (name and current setting) from pg_settings."
|
||||||
|
statement: |
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
pg_size_pretty((setting::bigint * 1024)::bigint) setting
|
||||||
|
FROM pg_settings
|
||||||
|
WHERE name IN ('work_mem', 'maintenance_work_mem')
|
||||||
|
)
|
||||||
|
UNION ALL
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
pg_size_pretty((((setting::bigint) * 8) * 1024)::bigint)
|
||||||
|
FROM pg_settings
|
||||||
|
WHERE name IN ('shared_buffers', 'wal_buffers', 'effective_cache_size', 'temp_buffers')
|
||||||
|
)
|
||||||
|
ORDER BY 1 DESC;
|
||||||
|
|
||||||
|
list_top_bloated_tables:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: |
|
||||||
|
List the top tables by dead-tuple (approximate bloat signal), returning schema, table, live/dead tuples, percentage, and last vacuum/analyze times.
|
||||||
|
statement: |
|
||||||
|
SELECT
|
||||||
|
schemaname AS schema_name,
|
||||||
|
relname AS relation_name,
|
||||||
|
n_live_tup AS live_tuples,
|
||||||
|
n_dead_tup AS dead_tuples,
|
||||||
|
TRUNC((n_dead_tup::NUMERIC / NULLIF(n_live_tup + n_dead_tup, 0)) * 100, 2) AS dead_tuple_percentage,
|
||||||
|
last_vacuum,
|
||||||
|
last_autovacuum,
|
||||||
|
last_analyze,
|
||||||
|
last_autoanalyze
|
||||||
|
FROM pg_stat_user_tables
|
||||||
|
ORDER BY n_dead_tup DESC
|
||||||
|
LIMIT COALESCE($1::int, 50);
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
description: "The maximum number of results to return."
|
||||||
|
type: integer
|
||||||
|
default: 50
|
||||||
|
|
||||||
|
list_replication_slots:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "List key details for all PostgreSQL replication slots (e.g., type, database, active status) and calculates the size of the outstanding WAL that is being prevented from removal by the slot."
|
||||||
|
statement: |
|
||||||
|
SELECT
|
||||||
|
slot_name,
|
||||||
|
slot_type,
|
||||||
|
plugin,
|
||||||
|
database,
|
||||||
|
temporary,
|
||||||
|
active,
|
||||||
|
restart_lsn,
|
||||||
|
confirmed_flush_lsn,
|
||||||
|
xmin,
|
||||||
|
catalog_xmin,
|
||||||
|
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
|
||||||
|
FROM pg_replication_slots;
|
||||||
|
|
||||||
|
list_invalid_indexes:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "Lists all invalid PostgreSQL indexes which are taking up disk space but are unusable by the query planner. Typically created by failed CREATE INDEX CONCURRENTLY operations."
|
||||||
|
statement: |
|
||||||
|
SELECT
|
||||||
|
nspname AS schema_name,
|
||||||
|
indexrelid::regclass AS index_name,
|
||||||
|
indrelid::regclass AS table_name,
|
||||||
|
pg_size_pretty(pg_total_relation_size(indexrelid)) AS index_size,
|
||||||
|
indisready,
|
||||||
|
indisvalid,
|
||||||
|
pg_get_indexdef(pg_class.oid) AS index_def
|
||||||
|
FROM pg_index
|
||||||
|
JOIN pg_class ON pg_class.oid = pg_index.indexrelid
|
||||||
|
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
||||||
|
WHERE indisvalid = FALSE;
|
||||||
|
|
||||||
|
get_query_plan:
|
||||||
|
kind: postgres-sql
|
||||||
|
source: alloydb-omni-source
|
||||||
|
description: "Generate a PostgreSQL EXPLAIN plan in JSON format for a single SQL statement—without executing it. This returns the optimizer's estimated plan, costs, and rows (no ANALYZE, no extra options). Use in production safely for plan inspection, regression checks, and query tuning workflows."
|
||||||
|
statement: |
|
||||||
|
EXPLAIN (FORMAT JSON) {{.query}};
|
||||||
|
templateParameters:
|
||||||
|
- name: query
|
||||||
|
type: string
|
||||||
|
description: "The SQL statement for which you want to generate plan (omit the EXPLAIN keyword)."
|
||||||
|
required: true
|
||||||
|
|
||||||
|
list_views:
|
||||||
|
kind: postgres-list-views
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_schemas:
|
||||||
|
kind: postgres-list-schemas
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_indexes:
|
||||||
|
kind: postgres-list-indexes
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_sequences:
|
||||||
|
kind: postgres-list-sequences
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
database_overview:
|
||||||
|
kind: postgres-database-overview
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_triggers:
|
||||||
|
kind: postgres-list-triggers
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_query_stats:
|
||||||
|
kind: postgres-list-query-stats
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
get_column_cardinality:
|
||||||
|
kind: postgres-get-column-cardinality
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_table_stats:
|
||||||
|
kind: postgres-list-table-stats
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_publication_tables:
|
||||||
|
kind: postgres-list-publication-tables
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_tablespaces:
|
||||||
|
kind: postgres-list-tablespaces
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_pg_settings:
|
||||||
|
kind: postgres-list-pg-settings
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_database_stats:
|
||||||
|
kind: postgres-list-database-stats
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_roles:
|
||||||
|
kind: postgres-list-roles
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
list_stored_procedure:
|
||||||
|
kind: postgres-list-stored-procedure
|
||||||
|
source: alloydb-omni-source
|
||||||
|
|
||||||
|
toolsets:
|
||||||
|
alloydb_omni_database_tools:
|
||||||
|
- execute_sql
|
||||||
|
- list_tables
|
||||||
|
- list_active_queries
|
||||||
|
- list_available_extensions
|
||||||
|
- list_installed_extensions
|
||||||
|
- list_autovacuum_configurations
|
||||||
|
- list_columnar_configurations
|
||||||
|
- list_columnar_recommended_columns
|
||||||
|
- list_memory_configurations
|
||||||
|
- list_top_bloated_tables
|
||||||
|
- list_replication_slots
|
||||||
|
- list_invalid_indexes
|
||||||
|
- get_query_plan
|
||||||
|
- list_views
|
||||||
|
- list_schemas
|
||||||
|
- database_overview
|
||||||
|
- list_triggers
|
||||||
|
- list_indexes
|
||||||
|
- list_sequences
|
||||||
|
- long_running_transactions
|
||||||
|
- list_locks
|
||||||
|
- replication_stats
|
||||||
|
- list_query_stats
|
||||||
|
- get_column_cardinality
|
||||||
|
- list_publication_tables
|
||||||
|
- list_tablespaces
|
||||||
|
- list_pg_settings
|
||||||
|
- list_database_stats
|
||||||
|
- list_roles
|
||||||
|
- list_table_stats
|
||||||
|
- list_stored_procedure
|
||||||
@@ -23,9 +23,9 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util"
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ func (s *Source) Aggregate(ctx context.Context, pipelineString string, canonical
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) Find(ctx context.Context, filterString, database, collection string, opts *options.FindOptions) ([]any, error) {
|
func (s *Source) Find(ctx context.Context, filterString, database, collection string, opts *options.FindOptionsBuilder) ([]any, error) {
|
||||||
var filter = bson.D{}
|
var filter = bson.D{}
|
||||||
err := bson.UnmarshalExtJSON([]byte(filterString), false, &filter)
|
err := bson.UnmarshalExtJSON([]byte(filterString), false, &filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,7 +163,7 @@ func (s *Source) Find(ctx context.Context, filterString, database, collection st
|
|||||||
return parseData(ctx, cur)
|
return parseData(ctx, cur)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) FindOne(ctx context.Context, filterString, database, collection string, opts *options.FindOneOptions) ([]any, error) {
|
func (s *Source) FindOne(ctx context.Context, filterString, database, collection string, opts *options.FindOneOptionsBuilder) ([]any, error) {
|
||||||
var filter = bson.D{}
|
var filter = bson.D{}
|
||||||
err := bson.UnmarshalExtJSON([]byte(filterString), false, &filter)
|
err := bson.UnmarshalExtJSON([]byte(filterString), false, &filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -233,7 +233,7 @@ func (s *Source) UpdateMany(ctx context.Context, filterString string, canonical
|
|||||||
return nil, fmt.Errorf("unable to unmarshal update string: %w", err)
|
return nil, fmt.Errorf("unable to unmarshal update string: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.MongoClient().Database(database).Collection(collection).UpdateMany(ctx, filter, update, options.Update().SetUpsert(upsert))
|
res, err := s.MongoClient().Database(database).Collection(collection).UpdateMany(ctx, filter, update, options.UpdateMany().SetUpsert(upsert))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error updating collection: %w", err)
|
return nil, fmt.Errorf("error updating collection: %w", err)
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ func (s *Source) UpdateOne(ctx context.Context, filterString string, canonical b
|
|||||||
return nil, fmt.Errorf("unable to unmarshal update string: %w", err)
|
return nil, fmt.Errorf("unable to unmarshal update string: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.MongoClient().Database(database).Collection(collection).UpdateOne(ctx, filter, update, options.Update().SetUpsert(upsert))
|
res, err := s.MongoClient().Database(database).Collection(collection).UpdateOne(ctx, filter, update, options.UpdateOne().SetUpsert(upsert))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error updating collection: %w", err)
|
return nil, fmt.Errorf("error updating collection: %w", err)
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ func (s *Source) DeleteMany(ctx context.Context, filterString, database, collect
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.MongoClient().Database(database).Collection(collection).DeleteMany(ctx, filter, options.Delete())
|
res, err := s.MongoClient().Database(database).Collection(collection).DeleteMany(ctx, filter, options.DeleteMany())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -284,7 +284,7 @@ func (s *Source) DeleteOne(ctx context.Context, filterString, database, collecti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.MongoClient().Database(database).Collection(collection).DeleteOne(ctx, filter, options.Delete())
|
res, err := s.MongoClient().Database(database).Collection(collection).DeleteOne(ctx, filter, options.DeleteOne())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func initMongoDBClient(ctx context.Context, tracer trace.Tracer, name, uri strin
|
|||||||
|
|
||||||
// Create a new MongoDB client
|
// Create a new MongoDB client
|
||||||
clientOpts := options.Client().ApplyURI(uri).SetAppName(userAgent)
|
clientOpts := options.Client().ApplyURI(uri).SetAppName(userAgent)
|
||||||
client, err := mongo.Connect(ctx, clientOpts)
|
client, err := mongo.Connect(clientOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create MongoDB client: %w", err)
|
return nil, fmt.Errorf("unable to create MongoDB client: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import (
|
|||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util"
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
@@ -48,7 +48,7 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
|
|||||||
|
|
||||||
type compatibleSource interface {
|
type compatibleSource interface {
|
||||||
MongoClient() *mongo.Client
|
MongoClient() *mongo.Client
|
||||||
Find(context.Context, string, string, string, *options.FindOptions) ([]any, error)
|
Find(context.Context, string, string, string, *options.FindOptionsBuilder) ([]any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -118,7 +118,7 @@ type Tool struct {
|
|||||||
mcpManifest tools.McpManifest
|
mcpManifest tools.McpManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOptions(ctx context.Context, sortParameters parameters.Parameters, projectPayload string, limit int64, paramsMap map[string]any) (*options.FindOptions, error) {
|
func getOptions(ctx context.Context, sortParameters parameters.Parameters, projectPayload string, limit int64, paramsMap map[string]any) (*options.FindOptionsBuilder, error) {
|
||||||
logger, err := util.LoggerFromContext(ctx)
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
@@ -47,7 +47,7 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
|
|||||||
|
|
||||||
type compatibleSource interface {
|
type compatibleSource interface {
|
||||||
MongoClient() *mongo.Client
|
MongoClient() *mongo.Client
|
||||||
FindOne(context.Context, string, string, string, *options.FindOneOptions) ([]any, error)
|
FindOne(context.Context, string, string, string, *options.FindOneOptionsBuilder) ([]any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const resourceType string = "mongodb-insert-many"
|
const resourceType string = "mongodb-insert-many"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const resourceType string = "mongodb-insert-one"
|
const resourceType string = "mongodb-insert-one"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const resourceType string = "mongodb-update-many"
|
const resourceType string = "mongodb-update-many"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const resourceType string = "mongodb-update-one"
|
const resourceType string = "mongodb-update-one"
|
||||||
|
|||||||
157
tests/alloydbomni/alloydb_omni_integration_test.go
Normal file
157
tests/alloydbomni/alloydb_omni_integration_test.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package alloydbomni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/tests"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AlloyDBUser = "postgres"
|
||||||
|
AlloyDBPass = "mysecretpassword"
|
||||||
|
AlloyDBDatabase = "postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copied over from postgres.go
|
||||||
|
func initPostgresConnectionPool(host, port, user, pass, dbname string) (*pgxpool.Pool, error) {
|
||||||
|
// urlExample := "postgres:dd//username:password@localhost:5432/database_name"
|
||||||
|
url := &url.URL{
|
||||||
|
Scheme: "postgres",
|
||||||
|
User: url.UserPassword(user, pass),
|
||||||
|
Host: fmt.Sprintf("%s:%s", host, port),
|
||||||
|
Path: dbname,
|
||||||
|
}
|
||||||
|
pool, err := pgxpool.New(context.Background(), url.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to create connection pool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAlloyDBContainer(ctx context.Context, t *testing.T) (string, string, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := testcontainers.ContainerRequest{
|
||||||
|
Image: "google/alloydbomni:16.9.0-ubi9", // Pinning version for stability
|
||||||
|
ExposedPorts: []string{"5432/tcp"},
|
||||||
|
Env: map[string]string{
|
||||||
|
"POSTGRES_PASSWORD": AlloyDBPass,
|
||||||
|
},
|
||||||
|
WaitingFor: wait.ForAll(
|
||||||
|
wait.ForLog("Post Startup: Successfully reinstalled extensions"),
|
||||||
|
wait.ForExposedPort(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||||
|
ContainerRequest: req,
|
||||||
|
Started: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to start alloydb container: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
if err := container.Terminate(ctx); err != nil {
|
||||||
|
t.Fatalf("failed to terminate container: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := container.Host(ctx)
|
||||||
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
|
t.Fatalf("failed to get container host: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedPort, err := container.MappedPort(ctx, "5432")
|
||||||
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
|
t.Fatalf("failed to get container mapped port: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return host, mappedPort.Port(), cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlloyDBOmni(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
AlloyDBHost, AlloyDBPort, containerCleanup := setupAlloyDBContainer(ctx, t)
|
||||||
|
defer containerCleanup()
|
||||||
|
|
||||||
|
os.Setenv("ALLOYDB_OMNI_HOST", AlloyDBHost)
|
||||||
|
os.Setenv("ALLOYDB_OMNI_PORT", AlloyDBPort)
|
||||||
|
os.Setenv("ALLOYDB_OMNI_USER", AlloyDBUser)
|
||||||
|
os.Setenv("ALLOYDB_OMNI_PASSWORD", AlloyDBPass)
|
||||||
|
os.Setenv("ALLOYDB_OMNI_DATABASE", AlloyDBDatabase)
|
||||||
|
|
||||||
|
args := []string{"--prebuilt", "alloydb-omni"}
|
||||||
|
|
||||||
|
pool, err := initPostgresConnectionPool(AlloyDBHost, AlloyDBPort, AlloyDBUser, AlloyDBPass, AlloyDBDatabase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create alloydb connection pool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, cleanup, err := tests.StartCmd(ctx, map[string]any{}, args...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("command initialization returned an error: %s", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Wait for server to be ready
|
||||||
|
waitCtx, waitCancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
|
defer waitCancel()
|
||||||
|
|
||||||
|
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("toolbox command logs: \n%s", out)
|
||||||
|
t.Fatalf("toolbox didn't start successfully: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run Postgres prebuilt tool tests
|
||||||
|
tests.RunPostgresListViewsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListSchemasTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListActiveQueriesTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListAvailableExtensionsTest(t)
|
||||||
|
tests.RunPostgresListInstalledExtensionsTest(t)
|
||||||
|
tests.RunPostgresDatabaseOverviewTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListTriggersTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListIndexesTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListSequencesTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresLongRunningTransactionsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListLocksTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresReplicationStatsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresGetColumnCardinalityTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListTableStatsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListPublicationTablesTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListTableSpacesTest(t)
|
||||||
|
tests.RunPostgresListPgSettingsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListDatabaseStatsTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListRolesTest(t, ctx, pool)
|
||||||
|
tests.RunPostgresListStoredProcedureTest(t, ctx, pool)
|
||||||
|
}
|
||||||
@@ -28,8 +28,8 @@ import (
|
|||||||
|
|
||||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
"github.com/googleapis/genai-toolbox/tests"
|
"github.com/googleapis/genai-toolbox/tests"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -55,7 +55,7 @@ func getMongoDBVars(t *testing.T) map[string]any {
|
|||||||
|
|
||||||
func initMongoDbDatabase(ctx context.Context, uri, database string) (*mongo.Database, error) {
|
func initMongoDbDatabase(ctx context.Context, uri, database string) (*mongo.Database, error) {
|
||||||
// Create a new mongodb Database
|
// Create a new mongodb Database
|
||||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
client, err := mongo.Connect(options.Client().ApplyURI(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to connect to mongodb: %s", err)
|
return nil, fmt.Errorf("unable to connect to mongodb: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user