mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-11 23:55:07 -05:00
This PR introduces a significant update to the Toolbox configuration file format, which is one of the primary **breaking changes** required for the implementation of the Advanced Control Plane. # Summary of Changes The configuration schema has been updated to enforce resource isolation and facilitate atomic, incremental updates. * Resource Isolation: Resource definitions are now separated into individual blocks, using a distinct structure for each resource type (Source, Tool, Toolset, etc.). This improves readability, management, and auditing of configuration files. * Field Name Modification: Internal field names have been modified to align with declarative methodologies. Specifically, the configuration now separates kind (general resource type, e.g., Source) from type (specific implementation, e.g., Postgres). # User Impact Existing tools.yaml configuration files are now in an outdated format. Users must eventually update their files to the new YAML format. # Mitigation & Compatibility Backward compatibility is maintained during this transition to ensure no immediate user action is required for existing files. * Immediate Backward Compatibility: The source code includes a pre-processing layer that automatically detects outdated configuration files (v1 format) and converts them to the new v2 format under the hood. * [COMING SOON] Migration Support: The new toolbox migrate subcommand will be introduced to allow users to automatically convert their old configuration files to the latest format. # Example Example for config file v2: ``` kind: sources name: my-pg-instance type: cloud-sql-postgres project: my-project region: my-region instance: my-instance database: my_db user: my_user password: my_pass --- kind: authServices name: my-google-auth type: google clientId: testing-id --- kind: tools name: example_tool type: postgres-sql source: my-pg-instance description: some description statement: SELECT * FROM SQL_STATEMENT; parameters: - name: country type: string description: some description --- kind: tools name: example_tool_2 type: postgres-sql source: my-pg-instance description: returning the number one statement: SELECT 1; --- kind: toolsets name: example_toolset tools: - example_tool ``` --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Averi Kitsch <akitsch@google.com>
605 lines
18 KiB
Go
605 lines
18 KiB
Go
// Copyright 2025 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 http
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
|
"github.com/googleapis/genai-toolbox/tests"
|
|
)
|
|
|
|
var (
|
|
HttpSourceType = "http"
|
|
HttpToolType = "http"
|
|
)
|
|
|
|
func getHTTPSourceConfig(t *testing.T) map[string]any {
|
|
idToken, err := tests.GetGoogleIdToken(tests.ClientId)
|
|
if err != nil {
|
|
t.Fatalf("error getting ID token: %s", err)
|
|
}
|
|
idToken = "Bearer " + idToken
|
|
|
|
return map[string]any{
|
|
"type": HttpSourceType,
|
|
"headers": map[string]string{"Authorization": idToken},
|
|
}
|
|
}
|
|
|
|
// handler function for the test server
|
|
func multiTool(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
path = strings.TrimPrefix(path, "/") // Remove leading slash
|
|
|
|
switch path {
|
|
case "tool0":
|
|
handleTool0(w, r)
|
|
case "tool1":
|
|
handleTool1(w, r)
|
|
case "tool1id":
|
|
handleTool1Id(w, r)
|
|
case "tool1name":
|
|
handleTool1Name(w, r)
|
|
case "tool2":
|
|
handleTool2(w, r)
|
|
case "tool3":
|
|
handleTool3(w, r)
|
|
case "toolQueryTest":
|
|
handleQueryTest(w, r)
|
|
default:
|
|
http.NotFound(w, r) // Return 404 for unknown paths
|
|
}
|
|
}
|
|
|
|
// handleQueryTest simply returns the raw query string it received so the test
|
|
// can verify it's formatted correctly.
|
|
func handleQueryTest(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
enc := json.NewEncoder(w)
|
|
enc.SetEscapeHTML(false)
|
|
|
|
err := enc.Encode(r.URL.RawQuery)
|
|
if err != nil {
|
|
http.Error(w, "Failed to write response", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool0(w http.ResponseWriter, r *http.Request) {
|
|
// expect POST method
|
|
if r.Method != http.MethodPost {
|
|
errorMessage := fmt.Sprintf("expected POST method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
response := "hello world"
|
|
err := json.NewEncoder(w).Encode(response)
|
|
if err != nil {
|
|
http.Error(w, "Failed to encode JSON", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool1(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
// Parse request body
|
|
var requestBody map[string]interface{}
|
|
bodyBytes, readErr := io.ReadAll(r.Body)
|
|
if readErr != nil {
|
|
http.Error(w, "Bad Request: Failed to read request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
err := json.Unmarshal(bodyBytes, &requestBody)
|
|
if err != nil {
|
|
errorMessage := fmt.Sprintf("Bad Request: Error unmarshalling request body: %s, Raw body: %s", err, string(bodyBytes))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Extract name
|
|
name, ok := requestBody["name"].(string)
|
|
if !ok || name == "" {
|
|
http.Error(w, "Bad Request: Missing or invalid name", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if name == "Alice" {
|
|
response := `[{"id":1,"name":"Alice"},{"id":3,"name":"Sid"}]`
|
|
_, err := w.Write([]byte(response))
|
|
if err != nil {
|
|
http.Error(w, "Failed to write response", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool1Id(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
id := r.URL.Query().Get("id")
|
|
if id == "4" {
|
|
response := `[{"id":4,"name":null}]`
|
|
_, err := w.Write([]byte(response))
|
|
if err != nil {
|
|
http.Error(w, "Failed to write response", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool1Name(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if !r.URL.Query().Has("name") {
|
|
response := "null"
|
|
_, err := w.Write([]byte(response))
|
|
if err != nil {
|
|
http.Error(w, "Failed to write response", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool2(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
email := r.URL.Query().Get("email")
|
|
if email != "" {
|
|
response := `[{"name":"Alice"}]`
|
|
_, err := w.Write([]byte(response))
|
|
if err != nil {
|
|
http.Error(w, "Failed to write response", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
// handler function for the test server
|
|
func handleTool3(w http.ResponseWriter, r *http.Request) {
|
|
// expect GET method
|
|
if r.Method != http.MethodGet {
|
|
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Check request headers
|
|
expectedHeaders := map[string]string{
|
|
"Content-Type": "application/json",
|
|
"X-Custom-Header": "example",
|
|
"X-Other-Header": "test",
|
|
}
|
|
for header, expectedValue := range expectedHeaders {
|
|
if r.Header.Get(header) != expectedValue {
|
|
errorMessage := fmt.Sprintf("Bad Request: Missing or incorrect header: %s", header)
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check query parameters
|
|
expectedQueryParams := map[string][]string{
|
|
"id": []string{"2", "1", "3"},
|
|
"country": []string{"US"},
|
|
}
|
|
query := r.URL.Query()
|
|
for param, expectedValueSlice := range expectedQueryParams {
|
|
values, ok := query[param]
|
|
if ok {
|
|
if !reflect.DeepEqual(expectedValueSlice, values) {
|
|
errorMessage := fmt.Sprintf("Bad Request: Incorrect query parameter: %s, actual: %s", param, query[param])
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
} else {
|
|
errorMessage := fmt.Sprintf("Bad Request: Missing query parameter: %s, actual: %s", param, query[param])
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Parse request body
|
|
var requestBody map[string]interface{}
|
|
bodyBytes, readErr := io.ReadAll(r.Body)
|
|
if readErr != nil {
|
|
http.Error(w, "Bad Request: Failed to read request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
err := json.Unmarshal(bodyBytes, &requestBody)
|
|
if err != nil {
|
|
errorMessage := fmt.Sprintf("Bad Request: Error unmarshalling request body: %s, Raw body: %s", err, string(bodyBytes))
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Check request body
|
|
expectedBody := map[string]interface{}{
|
|
"place": "zoo",
|
|
"animals": []any{"rabbit", "ostrich", "whale"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(requestBody, expectedBody) {
|
|
errorMessage := fmt.Sprintf("Bad Request: Incorrect request body. Expected: %v, Got: %v", expectedBody, requestBody)
|
|
http.Error(w, errorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
response := "hello world"
|
|
err = json.NewEncoder(w).Encode(response)
|
|
if err != nil {
|
|
http.Error(w, "Failed to encode JSON", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestHttpToolEndpoints(t *testing.T) {
|
|
// start a test server
|
|
server := httptest.NewServer(http.HandlerFunc(multiTool))
|
|
defer server.Close()
|
|
|
|
sourceConfig := getHTTPSourceConfig(t)
|
|
sourceConfig["baseUrl"] = server.URL
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancel()
|
|
|
|
var args []string
|
|
|
|
toolsFile := getHTTPToolsConfig(sourceConfig, HttpToolType)
|
|
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
|
|
if err != nil {
|
|
t.Fatalf("command initialization returned an error: %s", err)
|
|
}
|
|
defer cleanup()
|
|
|
|
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
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 tests
|
|
tests.RunToolGetTest(t)
|
|
tests.RunToolInvokeTest(t, `"hello world"`, tests.DisableArrayTest())
|
|
runAdvancedHTTPInvokeTest(t)
|
|
runQueryParamInvokeTest(t)
|
|
}
|
|
|
|
// runQueryParamInvokeTest runs the tool invoke endpoint for the query param test tool
|
|
func runQueryParamInvokeTest(t *testing.T) {
|
|
invokeTcs := []struct {
|
|
name string
|
|
api string
|
|
requestBody io.Reader
|
|
want string
|
|
isErr bool
|
|
}{
|
|
{
|
|
name: "invoke query-param-tool (optional omitted)",
|
|
api: "http://127.0.0.1:5000/api/tool/my-query-param-tool/invoke",
|
|
requestBody: bytes.NewBuffer([]byte(`{"reqId": "test1"}`)),
|
|
want: `"reqId=test1"`,
|
|
},
|
|
{
|
|
name: "invoke query-param-tool (some optional nil)",
|
|
api: "http://127.0.0.1:5000/api/tool/my-query-param-tool/invoke",
|
|
requestBody: bytes.NewBuffer([]byte(`{"reqId": "test2", "page": "5", "filter": null}`)),
|
|
want: `"page=5\u0026reqId=test2"`, // 'filter' omitted
|
|
},
|
|
{
|
|
name: "invoke query-param-tool (some optional absent)",
|
|
api: "http://127.0.0.1:5000/api/tool/my-query-param-tool/invoke",
|
|
requestBody: bytes.NewBuffer([]byte(`{"reqId": "test2", "page": "5"}`)),
|
|
want: `"page=5\u0026reqId=test2"`, // 'filter' omitted
|
|
},
|
|
{
|
|
name: "invoke query-param-tool (required param nil)",
|
|
api: "http://127.0.0.1:5000/api/tool/my-query-param-tool/invoke",
|
|
requestBody: bytes.NewBuffer([]byte(`{"reqId": null, "page": "1"}`)),
|
|
want: `"page=1\u0026reqId="`, // reqId becomes "",
|
|
},
|
|
}
|
|
for _, tc := range invokeTcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Send Tool invocation request
|
|
req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
|
|
if err != nil {
|
|
t.Fatalf("unable to create request: %s", err)
|
|
}
|
|
req.Header.Add("Content-type", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("unable to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
// Check response body
|
|
var body map[string]interface{}
|
|
err = json.NewDecoder(resp.Body).Decode(&body)
|
|
if err != nil {
|
|
t.Fatalf("error parsing response body: %v", err)
|
|
}
|
|
got, ok := body["result"].(string)
|
|
if !ok {
|
|
bodyBytes, _ := json.Marshal(body)
|
|
t.Fatalf("unable to find result in response body, got: %s", string(bodyBytes))
|
|
}
|
|
|
|
if got != tc.want {
|
|
t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// runToolInvoke runs the tool invoke endpoint
|
|
func runAdvancedHTTPInvokeTest(t *testing.T) {
|
|
// Test HTTP tool invoke endpoint
|
|
invokeTcs := []struct {
|
|
name string
|
|
api string
|
|
requestHeader map[string]string
|
|
requestBody io.Reader
|
|
want string
|
|
isErr bool
|
|
}{
|
|
{
|
|
name: "invoke my-advanced-tool",
|
|
api: "http://127.0.0.1:5000/api/tool/my-advanced-tool/invoke",
|
|
requestHeader: map[string]string{},
|
|
requestBody: bytes.NewBuffer([]byte(`{"animalArray": ["rabbit", "ostrich", "whale"], "id": 3, "path": "tool3", "country": "US", "X-Other-Header": "test"}`)),
|
|
want: `"hello world"`,
|
|
isErr: false,
|
|
},
|
|
{
|
|
name: "invoke my-advanced-tool with wrong params",
|
|
api: "http://127.0.0.1:5000/api/tool/my-advanced-tool/invoke",
|
|
requestHeader: map[string]string{},
|
|
requestBody: bytes.NewBuffer([]byte(`{"animalArray": ["rabbit", "ostrich", "whale"], "id": 4, "path": "tool3", "country": "US", "X-Other-Header": "test"}`)),
|
|
isErr: true,
|
|
},
|
|
}
|
|
for _, tc := range invokeTcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Send Tool invocation request
|
|
req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
|
|
if err != nil {
|
|
t.Fatalf("unable to create request: %s", err)
|
|
}
|
|
req.Header.Add("Content-type", "application/json")
|
|
for k, v := range tc.requestHeader {
|
|
req.Header.Add(k, v)
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("unable to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
if tc.isErr == true {
|
|
return
|
|
}
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
// Check response body
|
|
var body map[string]interface{}
|
|
err = json.NewDecoder(resp.Body).Decode(&body)
|
|
if err != nil {
|
|
t.Fatalf("error parsing response body")
|
|
}
|
|
got, ok := body["result"].(string)
|
|
if !ok {
|
|
t.Fatalf("unable to find result in response body")
|
|
}
|
|
|
|
if got != tc.want {
|
|
t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// getHTTPToolsConfig returns a mock HTTP tool's config file
|
|
func getHTTPToolsConfig(sourceConfig map[string]any, toolType string) map[string]any {
|
|
// Write config into a file and pass it to command
|
|
otherSourceConfig := make(map[string]any)
|
|
for k, v := range sourceConfig {
|
|
otherSourceConfig[k] = v
|
|
}
|
|
otherSourceConfig["headers"] = map[string]string{"X-Custom-Header": "unexpected", "Content-Type": "application/json"}
|
|
otherSourceConfig["queryParams"] = map[string]any{"id": 1, "name": "Sid"}
|
|
|
|
toolsFile := map[string]any{
|
|
"sources": map[string]any{
|
|
"my-instance": sourceConfig,
|
|
"other-instance": otherSourceConfig,
|
|
},
|
|
"authServices": map[string]any{
|
|
"my-google-auth": map[string]any{
|
|
"type": "google",
|
|
"clientId": tests.ClientId,
|
|
},
|
|
},
|
|
"tools": map[string]any{
|
|
"my-simple-tool": map[string]any{
|
|
"type": toolType,
|
|
"path": "/tool0",
|
|
"method": "POST",
|
|
"source": "my-instance",
|
|
"requestBody": "{}",
|
|
"description": "Simple tool to test end to end functionality.",
|
|
},
|
|
"my-tool": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "GET",
|
|
"path": "/tool1",
|
|
"description": "some description",
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewIntParameter("id", "user ID")},
|
|
"requestBody": `{
|
|
"age": 36,
|
|
"name": "{{.name}}"
|
|
}
|
|
`,
|
|
"bodyParams": []parameters.Parameter{parameters.NewStringParameter("name", "user name")},
|
|
"headers": map[string]string{"Content-Type": "application/json"},
|
|
},
|
|
"my-tool-by-id": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "GET",
|
|
"path": "/tool1id",
|
|
"description": "some description",
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewIntParameter("id", "user ID")},
|
|
"headers": map[string]string{"Content-Type": "application/json"},
|
|
},
|
|
"my-tool-by-name": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "GET",
|
|
"path": "/tool1name",
|
|
"description": "some description",
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewStringParameterWithRequired("name", "user name", false)},
|
|
"headers": map[string]string{"Content-Type": "application/json"},
|
|
},
|
|
"my-query-param-tool": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "GET",
|
|
"path": "/toolQueryTest",
|
|
"description": "Tool to test optional query parameters.",
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewStringParameterWithRequired("reqId", "required ID", true),
|
|
parameters.NewStringParameterWithRequired("page", "optional page number", false),
|
|
parameters.NewStringParameterWithRequired("filter", "optional filter string", false),
|
|
},
|
|
},
|
|
"my-auth-tool": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "GET",
|
|
"path": "/tool2",
|
|
"description": "some description",
|
|
"requestBody": "{}",
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewStringParameterWithAuth("email", "some description",
|
|
[]parameters.ParamAuthService{{Name: "my-google-auth", Field: "email"}}),
|
|
},
|
|
},
|
|
"my-auth-required-tool": map[string]any{
|
|
"type": toolType,
|
|
"source": "my-instance",
|
|
"method": "POST",
|
|
"path": "/tool0",
|
|
"description": "some description",
|
|
"requestBody": "{}",
|
|
"authRequired": []string{"my-google-auth"},
|
|
},
|
|
"my-advanced-tool": map[string]any{
|
|
"type": toolType,
|
|
"source": "other-instance",
|
|
"method": "get",
|
|
"path": "/{{.path}}?id=2",
|
|
"description": "some description",
|
|
"headers": map[string]string{
|
|
"X-Custom-Header": "example",
|
|
},
|
|
"pathParams": []parameters.Parameter{
|
|
¶meters.StringParameter{
|
|
CommonParameter: parameters.CommonParameter{Name: "path", Type: "string", Desc: "path param"},
|
|
},
|
|
},
|
|
"queryParams": []parameters.Parameter{
|
|
parameters.NewIntParameter("id", "user ID"), parameters.NewStringParameter("country", "country"),
|
|
},
|
|
"requestBody": `{
|
|
"place": "zoo",
|
|
"animals": {{json .animalArray }}
|
|
}
|
|
`,
|
|
"bodyParams": []parameters.Parameter{parameters.NewArrayParameter("animalArray", "animals in the zoo", parameters.NewStringParameter("animals", "desc"))},
|
|
"headerParams": []parameters.Parameter{parameters.NewStringParameter("X-Other-Header", "custom header")},
|
|
},
|
|
},
|
|
}
|
|
return toolsFile
|
|
}
|