Compare commits

...

14 Commits

Author SHA1 Message Date
Alex Talreja
2b09b69d2e adjust UI design to use icons and better match style 2025-08-06 17:15:54 +00:00
Alex Talreja
3ebc3d0907 feat: upload yaml and provide source config templates 2025-08-05 23:43:06 +00:00
AlexTalreja
d91bdfcbdc feat: add login with google button for automatic id token retrieval (#1044)
Add `Sign in with Google` button within Toolbox UI's `Edit Header` modal
that automatically retrieves a valid ID token for users based on an
input clientID.

This should make it significantly easier/faster for users to format
request headers properly and test tools that use auth.
2025-08-05 15:55:28 -07:00
Mend Renovate
c60a9601b4 chore(deps): update module google.golang.org/api to v0.245.0 (#1081)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[google.golang.org/api](https://redirect.github.com/googleapis/google-api-go-client)
| `v0.244.0` -> `v0.245.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fapi/v0.245.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fapi/v0.244.0/v0.245.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>googleapis/google-api-go-client
(google.golang.org/api)</summary>

###
[`v0.245.0`](https://redirect.github.com/googleapis/google-api-go-client/releases/tag/v0.245.0)

[Compare
Source](https://redirect.github.com/googleapis/google-api-go-client/compare/v0.244.0...v0.245.0)

##### Features

- **all:** Auto-regenerate discovery clients
([#&#8203;3252](https://redirect.github.com/googleapis/google-api-go-client/issues/3252))
([0716728](07167280e3))
- **all:** Auto-regenerate discovery clients
([#&#8203;3254](https://redirect.github.com/googleapis/google-api-go-client/issues/3254))
([702998a](702998a9a8))
- **all:** Auto-regenerate discovery clients
([#&#8203;3255](https://redirect.github.com/googleapis/google-api-go-client/issues/3255))
([0f10366](0f103667e9))
- **all:** Auto-regenerate discovery clients
([#&#8203;3256](https://redirect.github.com/googleapis/google-api-go-client/issues/3256))
([83176a9](83176a94b6))
- **all:** Auto-regenerate discovery clients
([#&#8203;3257](https://redirect.github.com/googleapis/google-api-go-client/issues/3257))
([efc3371](efc3371674))
- **all:** Auto-regenerate discovery clients
([#&#8203;3259](https://redirect.github.com/googleapis/google-api-go-client/issues/3259))
([bf38d3a](bf38d3ad99))

##### Bug Fixes

- **gensupport:** Fix transferChunk race condition by returning response
with non-cancelled context.
([#&#8203;3258](https://redirect.github.com/googleapis/google-api-go-client/issues/3258))
([091d422](091d42217a))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-08-05 15:25:44 -07:00
Anushka Saxena
bff528093d docs: add pre-built configurations for ide connection to toolbox using mcp (#962)
### Description
To provide clear, accessible documentation for each of these pre-built
tools, add a new heading for `Pre-built Tools` under the "Available
Tools" section on each Source page.

###  Related Issues
Address and resolves #960

---------

Signed-off-by: Anushka Saxena <anushkasaxenaa@google.com>
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 15:13:09 -07:00
Mend Renovate
34f78bd89d chore(deps): update module github.com/redis/go-redis/v9 to v9.12.0 (#1078)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/redis/go-redis/v9](https://redirect.github.com/redis/go-redis)
| `v9.11.0` -> `v9.12.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fredis%2fgo-redis%2fv9/v9.12.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fredis%2fgo-redis%2fv9/v9.11.0/v9.12.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>redis/go-redis (github.com/redis/go-redis/v9)</summary>

###
[`v9.12.0`](https://redirect.github.com/redis/go-redis/releases/tag/v9.12.0):
9.12.0

[Compare
Source](https://redirect.github.com/redis/go-redis/compare/v9.11.0...v9.12.0)

#### 🚀 Highlights

- This release includes support for [Redis
8.2](https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.2-release-notes/).
- Introduces an experimental Query Builders for `FTSearch`,
`FTAggregate` and other search commands.
- Adds support for `EPSILON` option in `FT.VSIM`.
- Includes bug fixes and improvements contributed by the community
related to ring and
[redisotel](https://redirect.github.com/redis/go-redis/tree/master/extra/redisotel).

#### Changes

- Improve stale issue workflow
([#&#8203;3458](https://redirect.github.com/redis/go-redis/pull/3458))
- chore(ci): Add 8.2 rc2 pre build for CI
([#&#8203;3459](https://redirect.github.com/redis/go-redis/pull/3459))
- Added new stream commands
([#&#8203;3450](https://redirect.github.com/redis/go-redis/pull/3450))
- feat: Add "skip\_verify" to Sentinel
([#&#8203;3428](https://redirect.github.com/redis/go-redis/pull/3428))
- fix: `errors.Join` requires Go 1.20 or later
([#&#8203;3442](https://redirect.github.com/redis/go-redis/pull/3442))
- DOC-4344 document quickstart examples
([#&#8203;3426](https://redirect.github.com/redis/go-redis/pull/3426))
- feat(bitop): add support for the new bitop operations
([#&#8203;3409](https://redirect.github.com/redis/go-redis/pull/3409))

#### 🚀 New Features

- feat: recover addIdleConn may occur panic
([#&#8203;2445](https://redirect.github.com/redis/go-redis/pull/2445))
- feat(ring): specify custom health check func via HeartbeatFn option
([#&#8203;2940](https://redirect.github.com/redis/go-redis/pull/2940))
- Add Query Builder for RediSearch commands
([#&#8203;3436](https://redirect.github.com/redis/go-redis/pull/3436))
- add configurable buffer sizes for Redis connections
([#&#8203;3453](https://redirect.github.com/redis/go-redis/pull/3453))
- Add VAMANA vector type to RediSearch
([#&#8203;3449](https://redirect.github.com/redis/go-redis/pull/3449))
- VSIM add `EPSILON` option
([#&#8203;3454](https://redirect.github.com/redis/go-redis/pull/3454))
- Add closing support to otel metrics instrumentation
([#&#8203;3444](https://redirect.github.com/redis/go-redis/pull/3444))

#### 🐛 Bug Fixes

- fix(redisotel): fix buggy append in reportPoolStats
([#&#8203;3122](https://redirect.github.com/redis/go-redis/pull/3122))
- fix(search): return results even if doc is empty
([#&#8203;3457](https://redirect.github.com/redis/go-redis/pull/3457))
- \[ISSUE-3402]: Ring.Pipelined return dial timeout error
([#&#8203;3403](https://redirect.github.com/redis/go-redis/pull/3403))

#### 🧰 Maintenance

- Merges stale issues jobs into one job with two steps
([#&#8203;3463](https://redirect.github.com/redis/go-redis/pull/3463))
- improve code readability
([#&#8203;3446](https://redirect.github.com/redis/go-redis/pull/3446))
- chore(release): 9.12.0-beta.1
([#&#8203;3460](https://redirect.github.com/redis/go-redis/pull/3460))
- DOC-5472 time series doc examples
([#&#8203;3443](https://redirect.github.com/redis/go-redis/pull/3443))
- Add VAMANA compression algorithm tests
([#&#8203;3461](https://redirect.github.com/redis/go-redis/pull/3461))
- bumped redis 8.2 version used in the CI/CD
([#&#8203;3451](https://redirect.github.com/redis/go-redis/pull/3451))

#### Contributors

We'd like to thank all the contributors who worked on this release!


[@&#8203;andy-stark-redis](https://redirect.github.com/andy-stark-redis),
[@&#8203;cxljs](https://redirect.github.com/cxljs),
[@&#8203;elena-kolevska](https://redirect.github.com/elena-kolevska),
[@&#8203;htemelski-redis](https://redirect.github.com/htemelski-redis),
[@&#8203;jouir](https://redirect.github.com/jouir),
[@&#8203;monkey92t](https://redirect.github.com/monkey92t),
[@&#8203;ndyakov](https://redirect.github.com/ndyakov),
[@&#8203;ofekshenawa](https://redirect.github.com/ofekshenawa),
[@&#8203;rokn](https://redirect.github.com/rokn),
[@&#8203;smnvdev](https://redirect.github.com/smnvdev),
[@&#8203;strobil](https://redirect.github.com/strobil) and
[@&#8203;wzy9607](https://redirect.github.com/wzy9607)

#### New Contributors

- [@&#8203;htemelski-redis](https://redirect.github.com/htemelski-redis)
made their first contribution in
[#&#8203;3409](https://redirect.github.com/redis/go-redis/pull/3409)
- [@&#8203;smnvdev](https://redirect.github.com/smnvdev) made their
first contribution in
[#&#8203;3403](https://redirect.github.com/redis/go-redis/pull/3403)
- [@&#8203;rokn](https://redirect.github.com/rokn) made their first
contribution in
[#&#8203;3444](https://redirect.github.com/redis/go-redis/pull/3444)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 15:00:26 -07:00
Mend Renovate
000d6ada38 chore(deps): update module cloud.google.com/go/spanner to v1.84.0 (#1073)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[cloud.google.com/go/spanner](https://redirect.github.com/googleapis/google-cloud-go)
| `v1.83.0` -> `v1.84.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fspanner/v1.84.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fspanner/v1.83.0/v1.84.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 14:48:18 -07:00
release-please[bot]
a09f628b52 chore(main): release 0.11.0 (#1071)
🤖 I have created a release *beep* *boop*
---


##
[0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.11.0...v0.11.0)
(2025-08-05)


### ⚠ BREAKING CHANGES

* **tools/bigquery-sql:** Ensure invoke always returns a non-null value
([#1020](https://github.com/googleapis/genai-toolbox/issues/1020))
([9af55b6](9af55b651d))
* **tools/bigquery-execute-sql:** Update the return messages
([#1034](https://github.com/googleapis/genai-toolbox/issues/1034))
([051e686](051e686476))

### Features

* Add TiDB source and tool
([#829](https://github.com/googleapis/genai-toolbox/issues/829))
([6eaf36a](6eaf36ac85))
* Interactive web UI for Toolbox
([#1065](https://github.com/googleapis/genai-toolbox/issues/1065))
([8749b03](8749b03003))
* **prebuiltconfigs/cloud-sql-postgres:** Introduce additional parameter
to limit context in list tables
([#1062](https://github.com/googleapis/genai-toolbox/issues/1062))
([c3a58e1](c3a58e1d16))
* **tools/looker-query-url:** Add support for `looker-query-url` tool
([#1015](https://github.com/googleapis/genai-toolbox/issues/1015))
([327ddf0](327ddf0439))
* **tools/dataplex-lookup-entry:** Add support for
`dataplex-lookup-entry` tool
([#1009](https://github.com/googleapis/genai-toolbox/issues/1009))
([5fa1660](5fa1660fc8))

### Bug Fixes

* **tools/bigquery,mssql,mysql,postgres,spanner,tidb:** Add query
logging to execute-sql tools
([#1069](https://github.com/googleapis/genai-toolbox/issues/1069))
([0527532]([0527532bd7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 14:00:26 -07:00
Yuan Teoh
80a8ebfa0b chore: release 0.11.0 (#1082)
Release-As: 0.11.0
2025-08-05 20:17:01 +00:00
Yuan Teoh
0588e178d6 chore: include new docs in release please config (#1070) 2025-08-05 19:09:30 +00:00
Averi Kitsch
f80f18aaf6 ci(schedule_reporter): add continuous release to failure notification (#1080) 2025-08-05 11:56:35 -07:00
Averi Kitsch
f79cdd6144 revert: 'feat: add support for DuckDB' (#1079)
DuckDB requires an overhaul of the build system, see
https://github.com/marcboeker/go-duckdb/issues/94
2025-08-05 11:44:52 -07:00
prernakakkar-google
c3a58e1d16 feat(prebuiltconfigs/cloud-sql-postgres): Introduce additional parameter to limit context in list tables (#1062) 2025-08-05 17:13:03 +00:00
Twisha Bansal
c7b443d94a docs: update alloydb ai nl docs to enable parameterized_views (#1074)
Clarifies fix for
https://github.com/googleapis/genai-toolbox/issues/1041
2025-08-05 21:55:01 +05:30
52 changed files with 1291 additions and 973 deletions

View File

@@ -62,7 +62,6 @@ steps:
postgressql \
postgresexecutesql
- id: "alloydb-pg"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -121,8 +120,7 @@ steps:
- "BIGTABLE_PROJECT=$PROJECT_ID"
- "BIGTABLE_INSTANCE=$_BIGTABLE_INSTANCE"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv:
["CLIENT_ID"]
secretEnv: ["CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -153,7 +151,7 @@ steps:
"BigQuery" \
bigquery \
bigquery
- id: "dataplex"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -274,8 +272,7 @@ steps:
- "CLOUD_SQL_MYSQL_DATABASE=$_DATABASE_NAME"
- "CLOUD_SQL_MYSQL_REGION=$_REGION"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv:
["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
secretEnv: ["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -387,7 +384,7 @@ steps:
sqlite
- id: "couchbase"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -395,7 +392,8 @@ steps:
- "COUCHBASE_SCOPE=$_COUCHBASE_SCOPE"
- "COUCHBASE_BUCKET=$_COUCHBASE_BUCKET"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv: ["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
secretEnv:
["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -408,7 +406,7 @@ steps:
couchbase
- id: "redis"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -425,9 +423,9 @@ steps:
"Redis" \
redis \
redis
- id: "valkey"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -475,7 +473,13 @@ steps:
- "FIRESTORE_PROJECT=$PROJECT_ID"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
- "LOOKER_VERIFY_SSL=$_LOOKER_VERIFY_SSL"
secretEnv: ["CLIENT_ID", "LOOKER_BASE_URL", "LOOKER_CLIENT_ID", "LOOKER_CLIENT_SECRET"]
secretEnv:
[
"CLIENT_ID",
"LOOKER_BASE_URL",
"LOOKER_CLIENT_ID",
"LOOKER_CLIENT_SECRET",
]
volumes:
- name: "go"
path: "/gopath"
@@ -487,25 +491,6 @@ steps:
looker \
looker
- id: "duckdb"
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
- "GOPATH=/gopath"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
volumes:
- name: "go"
path: "/gopath"
secretEnv: ["CLIENT_ID"]
args:
- -c
- |
.ci/test_with_coverage.sh \
"DuckDB" \
duckdb \
duckdb
- id: "alloydbwaitforoperation"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -545,8 +530,8 @@ steps:
.ci/test_with_coverage.sh \
"TiDB" \
tidb \
tidbsql tidbexecutesql
tidbsql tidbexecutesql
availableSecrets:
secretManager:
- versionName: projects/$PROJECT_ID/secrets/cloud_sql_pg_user/versions/latest

View File

@@ -24,6 +24,7 @@ extraFiles: [
"docs/en/getting-started/local_quickstart_js.md",
"docs/en/getting-started/local_quickstart_go.md",
"docs/en/getting-started/mcp_quickstart/_index.md",
"docs/en/samples/alloydb/_index.md",
"docs/en/samples/bigquery/local_quickstart.md",
"docs/en/samples/bigquery/mcp_quickstart/_index.md",
"docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb",

View File

@@ -26,4 +26,4 @@ jobs:
contents: 'read'
uses: ./.github/workflows/cloud_build_failure_reporter.yml
with:
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge"
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge,toolbox-continuous-release"

View File

@@ -1,6 +1,6 @@
# Changelog
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.10.0...v0.11.0) (2025-08-04)
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.11.0...v0.11.0) (2025-08-05)
### ⚠ BREAKING CHANGES
@@ -10,12 +10,16 @@
### Features
* Add DuckDB source and tool ([#879](https://github.com/googleapis/genai-toolbox/pull/879)) ([fd14933](https://github.com/googleapis/genai-toolbox/commit/fd149337e9fa8e912e8699962a7104d51cdffc5d))
* Add TiDB source and tool ([#829](https://github.com/googleapis/genai-toolbox/issues/829)) ([6eaf36a](https://github.com/googleapis/genai-toolbox/commit/6eaf36ac8505d523fa4f5a4ac3c97209fd688cef))
* Interactive web UI for Toolbox ([#1065](https://github.com/googleapis/genai-toolbox/issues/1065)) ([8749b03](https://github.com/googleapis/genai-toolbox/commit/8749b030035e65361047c4ead13dfacb8e9a9b59))
* **prebuiltconfigs/cloud-sql-postgres:** Introduce additional parameter to limit context in list tables ([#1062](https://github.com/googleapis/genai-toolbox/issues/1062)) ([c3a58e1](https://github.com/googleapis/genai-toolbox/commit/c3a58e1d1678dc14d8de5006511df597fd75faa3))
* **tools/looker-query-url:** Add support for `looker-query-url` tool ([#1015](https://github.com/googleapis/genai-toolbox/issues/1015)) ([327ddf0](https://github.com/googleapis/genai-toolbox/commit/327ddf0439058aa5ecd2c7ae8251fcde6aeff18c))
* **tools/dataplex-lookup-entry:** Add support for `dataplex-lookup-entry` tool ([#1009](https://github.com/googleapis/genai-toolbox/issues/1009)) ([5fa1660](https://github.com/googleapis/genai-toolbox/commit/5fa1660fc8631989b4d13abea205b6426bb506a5))
### Bug Fixes
* **tools/bigquery,mssql,mysql,postgres,spanner,tidb:** Add query logging to execute-sql tools ([#1069](https://github.com/googleapis/genai-toolbox/issues/1069)) ([0527532]([https://github.com/googleapis/genai-toolbox/commit/0527532bd7085ef9eb8f9c30f430a2f2f35cef32))
## [0.10.0](https://github.com/googleapis/genai-toolbox/compare/v0.9.0...v0.10.0) (2025-07-25)

View File

@@ -114,7 +114,7 @@ To install Toolbox as a binary:
<!-- {x-release-please-start-version} -->
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -127,7 +127,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -151,7 +151,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.10.0
go install github.com/googleapis/genai-toolbox@v0.11.0
```
<!-- {x-release-please-end} -->

View File

@@ -54,7 +54,6 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexlookupentry"
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexsearchentries"
_ "github.com/googleapis/genai-toolbox/internal/tools/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoredeletedocuments"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetdocuments"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetrules"
@@ -112,7 +111,6 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/sources/couchbase"
_ "github.com/googleapis/genai-toolbox/internal/sources/dataplex"
_ "github.com/googleapis/genai-toolbox/internal/sources/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/sources/duckdb"
_ "github.com/googleapis/genai-toolbox/internal/sources/firestore"
_ "github.com/googleapis/genai-toolbox/internal/sources/http"
_ "github.com/googleapis/genai-toolbox/internal/sources/looker"

View File

@@ -86,7 +86,7 @@ To install Toolbox as a binary:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -97,7 +97,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -115,7 +115,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.10.0
go install github.com/googleapis/genai-toolbox@v0.11.0
```
{{% /tab %}}

View File

@@ -171,7 +171,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -62,19 +62,19 @@ to expose your developer assistant tools to a Firestore instance:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -46,19 +46,19 @@ to expose your developer assistant tools to a Looker instance:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -52,19 +52,19 @@ Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -33,6 +33,14 @@ cluster][alloydb-free-trial].
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in AlloyDB Postgres.
### Pre-built Configurations
- [AlloyDB using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_mcp/)
Connect your IDE to AlloyDB using Toolbox.
- [AlloyDB Admin API using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_admin_mcp/)
Create your AlloyDB database with MCP Toolbox.
## Requirements
### IAM Permissions

View File

@@ -54,6 +54,11 @@ avoiding full table scans or complex filters.
- [`bigquery-list-table-ids`](../tools/bigquery/bigquery-list-table-ids.md)
List tables in a given dataset.
### Pre-built Configurations
- [BigQuery using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/bigquery_mcp/)
Connect your IDE to BigQuery using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -27,6 +27,11 @@ to a database by following these instructions][csql-mssql-connect].
- [`mssql-execute-sql`](../tools/mssql/mssql-execute-sql.md)
Run parameterized SQL Server queries in Cloud SQL for SQL Server.
### Pre-built Configurations
- [Cloud SQL for SQL Server using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mssql_mcp/)
Connect your IDE to Cloud SQL for SQL Server using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -28,6 +28,11 @@ to a database by following these instructions][csql-mysql-quickstart].
- [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md)
Run parameterized SQL queries in Cloud SQL for MySQL.
### Pre-built Configurations
- [Cloud SQL for MySQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mysql_mcp/)
Connect your IDE to Cloud SQL for MySQL using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -28,6 +28,12 @@ to a database by following these instructions][csql-pg-quickstart].
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in PostgreSQL.
### Pre-built Configurations
- [Cloud SQL for Postgres using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_pg_mcp/)
Connect your IDE to Cloud SQL for Postgres using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -1,73 +0,0 @@
---
title: DuckDB
linkTitle: DuckDB
type: docs
weight: 1
description: >
DuckDB is an in-process SQL OLAP database management system designed for analytical query processing.
---
## About
[DuckDB](https://duckdb.org/) is an embedded analytical database management system that runs in-process with the client application. It is optimized for analytical workloads, providing high performance for complex queries with minimal setup.
DuckDB has the following notable characteristics:
- In-process, serverless database engine
- Supports complex SQL queries for analytical processing
- Can operate on in-memory or persistent storage
- Zero-configuration - no external dependencies or server setup required
- Highly optimized for columnar data storage and query execution
For more details, refer to the [DuckDB Documentation](https://duckdb.org/).
## Available Tools
- [`duckdb-sql`](../tools/duckdb/duckdb-sql.md)
Execute pre-defined prepared SQL queries in DuckDB.
## Requirements
### Database File
To use DuckDB, you can either:
- Specify a file path for a persistent database stored on the filesystem
- Omit the file path to use an in-memory database
## Example
For a persistent DuckDB database:
```yaml
sources:
my-duckdb:
kind: "duckdb"
dbFilePath: "/path/to/database.db"
configuration:
memory_limit: "2GB"
threads: "4"
```
For an in-memory DuckDB database:
```yaml
sources:
my-duckdb-memory:
name: "my-duckdb-memory"
kind: "duckdb"
```
## Reference
### Configuration Fields
| **field** | **type** | **required** | **description** |
|-------------------|:-----------------:|:------------:|---------------------------------------------------------------------------------|
| kind | string | true | Must be "duckdb". |
| dbFilePath | string | false | Path to the DuckDB database file. Omit for an in-memory database. |
| configuration | map[string]string | false | Additional DuckDB configuration options (e.g., `memory_limit`, `threads`). |
For a complete list of available configuration options, refer to the [DuckDB Configuration Documentation](https://duckdb.org/docs/stable/configuration/overview.html#local-configuration-options).
For more details on the Go implementation, see the [go-duckdb package documentation](https://pkg.go.dev/github.com/scottlepp/go-duckdb#section-readme).

View File

@@ -23,6 +23,11 @@ reputation for reliability, feature robustness, and performance.
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in PostgreSQL.
### Pre-built Configurations
- [PostgreSQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/postgres_mcp/)
Connect your IDE to PostgreSQL using Toolbox.
## Requirements
### Database User

View File

@@ -31,6 +31,11 @@ the Google Cloud console][spanner-quickstart].
- [`spanner-execute-sql`](../tools/spanner/spanner-execute-sql.md)
Run structured and parameterized queries on Spanner.
### Pre-built Configurations
- [Spanner using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/spanner_mcp/)
Connect your IDE to Spanner using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -75,6 +75,12 @@ visible to the LLM.
[alloydb-psv]: https://cloud.google.com/alloydb/docs/parameterized-secure-views-overview
{{< notice tip >}} Make sure to enable the `parameterized_views` extension before running this tool. You can do so by running this command in the AlloyDB studio:
```sql
CREATE EXTENSION IF NOT EXISTS parameterized_views;
```
{{< /notice >}}
## Example
```yaml
@@ -95,7 +101,6 @@ tools:
- name: my_google_service
field: email
```
## Reference
| **field** | **type** | **required** | **description** |

View File

@@ -1,7 +0,0 @@
---
title: "DuckDB"
type: docs
weight: 1
description: >
Tools that work with DuckDB Sources.
---

View File

@@ -1,80 +0,0 @@
---
title: "duckdb-sql"
type: docs
weight: 1
description: >
Execute SQL statements against a DuckDB database using the DuckDB SQL tools configuration.
aliases:
- /resources/tools/duckdb-sql
---
## About
A `duckdb-sql` tool executes a pre-defined SQL statement against a [DuckDB](https://duckdb.org/) database. It is compatible with any DuckDB source configuration as defined in the [DuckDB source documentation](../../sources/duckdb.md).
The specified SQL statement is executed as a prepared statement, and parameters are inserted according to their position: e.g., `$1` is the first parameter, `$2` is the second, and so on. If template parameters are included, they are resolved before execution of the prepared statement.
DuckDB's SQL dialect closely follows the conventions of the PostgreSQL dialect, with a few exceptions listed in the [DuckDB PostgreSQL Compatibility documentation](https://duckdb.org/docs/stable/sql/dialect/postgresql_compatibility.html). For an introduction to DuckDB's SQL dialect, refer to the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
### Concepts
DuckDB is a relational database management system (RDBMS). Data is stored in relations (tables), where each table is a named collection of rows. Each row in a table has the same set of named columns, each with a specific data type. Tables are stored within schemas, and a collection of schemas constitutes the entire database.
For more details, see the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
## Example
> **Note:** This tool uses parameterized queries to prevent SQL injections. Query parameters can be used as substitutes for arbitrary expressions but cannot be used for identifiers, column names, table names, or other parts of the query.
```yaml
tools:
search-users:
kind: duckdb-sql
source: my-duckdb
description: Search users by name and age
statement: SELECT * FROM users WHERE name LIKE $1 AND age >= $2
parameters:
- name: name
type: string
description: The name to search for
- name: min_age
type: integer
description: Minimum age
```
## Example with Template Parameters
> **Note:** Template parameters allow direct modifications to the SQL statement, including identifiers, column names, and table names, which makes them more vulnerable to SQL injections. Using basic parameters (see above) is recommended for performance and safety. For more details, see the [templateParameters](../#template-parameters) section.
```yaml
tools:
list_table:
kind: duckdb-sql
source: my-duckdb
statement: |
SELECT * FROM {{.tableName}};
description: |
Use this tool to list all information from a specific table.
Example:
{{
"tableName": "flights",
}}
templateParameters:
- name: tableName
type: string
description: Table to select from
```
## Reference
### Configuration Fields
| **field** | **type** | **required** | **description** |
|--------------------|:-------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "duckdb-sql". |
| source | string | true | Name of the DuckDB source configuration (see [DuckDB source documentation](../../sources/duckdb.md)). |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | The SQL statement to execute. |
| authRequired | []string | false | List of authentication requirements for the tool (if any). |
| parameters | [parameters](../#specifying-parameters) | false | List of parameters that will be inserted into the SQL statement |
| templateParameters | [templateParameters](../#template-parameters) | false | List of template parameters that will be inserted into the SQL statement before executing the prepared statement. |

View File

@@ -114,7 +114,7 @@ In this section, we will download and install the Toolbox binary.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
export VERSION="0.10.0"
export VERSION="0.11.0"
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -179,7 +179,7 @@ to use BigQuery, and then run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -98,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

22
go.mod
View File

@@ -11,7 +11,7 @@ require (
cloud.google.com/go/cloudsqlconn v1.17.3
cloud.google.com/go/dataplex v1.26.0
cloud.google.com/go/firestore v1.18.0
cloud.google.com/go/spanner v1.83.0
cloud.google.com/go/spanner v1.84.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0
github.com/couchbase/gocb/v2 v2.10.1
@@ -31,7 +31,7 @@ require (
github.com/looker-open-source/sdk-codegen/go v0.25.10
github.com/microsoft/go-mssqldb v1.9.2
github.com/neo4j/neo4j-go-driver/v5 v5.28.1
github.com/redis/go-redis/v9 v9.11.0
github.com/redis/go-redis/v9 v9.12.0
github.com/spf13/cobra v1.9.1
github.com/thlib/go-timezone-local v0.0.7
github.com/valkey-io/valkey-go v1.0.63
@@ -45,26 +45,19 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/oauth2 v0.30.0
google.golang.org/api v0.244.0
google.golang.org/api v0.245.0
modernc.org/sqlite v1.38.2
)
require (
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/duckdb/duckdb-go-bindings v0.1.17 // indirect
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 // indirect
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 // indirect
github.com/marcboeker/go-duckdb/mapping v0.0.11 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
gonum.org/v1/gonum v0.16.0 // indirect
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.2 // indirect
cloud.google.com/go v0.121.4 // indirect
cloud.google.com/go/alloydb v1.18.0 // indirect
cloud.google.com/go/auth v0.16.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -79,7 +72,6 @@ require (
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/apache/arrow-go/v18 v18.4.0 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -100,7 +92,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -120,7 +111,6 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/marcboeker/go-duckdb/v2 v2.3.4
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -160,7 +150,7 @@ require (
golang.org/x/tools v0.34.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect

53
go.sum
View File

@@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -544,8 +544,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
cloud.google.com/go/spanner v1.83.0 h1:AH3QIoSIa01l3WbeTppkwCEYFNK1AER6drcYhPmwhxY=
cloud.google.com/go/spanner v1.83.0/go.mod h1:QSWcjxszT0WRHNd8zyGI0WctrYA1N7j0yTFsWyol9Yw=
cloud.google.com/go/spanner v1.84.0 h1:McPTc6g7hmZfvBlY2mwxl4nJ3+oVtDOl3yGvxIyDTKk=
cloud.google.com/go/spanner v1.84.0/go.mod h1:Klo9oQcfxr32vh+2iuA34c6rHjjSpMcU23Jy6DArz7I=
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
@@ -563,8 +563,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
cloud.google.com/go/storage v1.53.0 h1:gg0ERZwL17pJ+Cz3cD2qS60w1WMDnwcm5YPAIQBHUAw=
cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
@@ -674,20 +674,14 @@ github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0=
github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14=
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -749,18 +743,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/duckdb/duckdb-go-bindings v0.1.17 h1:SjpRwrJ7v0vqnIvLeVFHlhuS72+Lp8xxQ5jIER2LZP4=
github.com/duckdb/duckdb-go-bindings v0.1.17/go.mod h1:pBnfviMzANT/9hi4bg+zW4ykRZZPCXlVuvBWEcZofkc=
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 h1:8CLBnsq9YDhi2Gmt3sjSUeXxMzyMQAKefjqUy9zVPFk=
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12/go.mod h1:Ezo7IbAfB8NP7CqPIN8XEHKUg5xdRRQhcPPlCXImXYA=
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 h1:wjO4I0GhMh2xIpiUgRpzuyOT4KxXLoUS/rjU7UUVvCE=
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12/go.mod h1:eS7m/mLnPQgVF4za1+xTyorKRBuK0/BA44Oy6DgrGXI=
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 h1:HzKQi2C+1jzmwANsPuYH6x9Sfw62SQTjNAEq3OySKFI=
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12/go.mod h1:1GOuk1PixiESxLaCGFhag+oFi7aP+9W8byymRAvunBk=
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 h1:YGSR7AFLw2gJ7IbgLE6DkKYmgKv1LaRSd/ZKF1yh2oE=
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12/go.mod h1:o7crKMpT2eOIi5/FY6HPqaXcvieeLSqdXXaXbruGX7w=
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 h1:2aduW6fnFnT2Q45PlIgHbatsPOxV9WSZ5B2HzFfxaxA=
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12/go.mod h1:IlOhJdVKUJCAPj3QsDszUo8DVdvp1nBFp4TUJVdw99s=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -836,8 +818,6 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
@@ -1021,7 +1001,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -1046,12 +1025,6 @@ github.com/looker-open-source/sdk-codegen/go v0.25.10/go.mod h1:YM/IYSsTPk7I54j4
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/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 h1:G1W+GVnUefR8uy7jHdNO+CRMsmFG5mFPIHVAespfFCA=
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10/go.mod h1:jccUb8TYD0p5TsEEeN4SXuslNJHo23QaKOqKD+U6uFU=
github.com/marcboeker/go-duckdb/mapping v0.0.11 h1:fusN1b1l7Myxafifp596I6dNLNhN5Uv/rw31qAqBwqw=
github.com/marcboeker/go-duckdb/mapping v0.0.11/go.mod h1:aYBjFLgfKO0aJIbDtXPiaL5/avRQISveX/j9tMf9JhU=
github.com/marcboeker/go-duckdb/v2 v2.3.4 h1:o98wrefPbH0IdJRix4pF0+jZiXoFQ+FSR8InMsCUZD0=
github.com/marcboeker/go-duckdb/v2 v2.3.4/go.mod h1:8adNrftF4Ye29XMrpIl5NYNosTVsZu1mz3C82WdVvrk=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -1059,9 +1032,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -1098,8 +1069,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
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.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU=
github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -1696,8 +1667,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE=
google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
google.golang.org/api v0.245.0 h1:YliGvz1rjXB+sTLNIST6Ffeji9WlRdLQ+LPl9ruSa5Y=
google.golang.org/api v0.245.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1840,8 +1811,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View File

@@ -72,26 +72,36 @@ tools:
SELECT
ti.schema_name,
ti.table_name AS object_name,
json_build_object(
'schema_name', ti.schema_name,
'object_name', ti.table_name,
'object_type', CASE ti.object_kind
WHEN 'r' THEN 'TABLE'
WHEN 'p' THEN 'PARTITIONED TABLE'
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
END,
'owner', ti.table_owner,
'comment', ti.table_comment,
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
) AS object_details
CASE
WHEN $2 = 'simple' THEN
-- IF format is 'simple', return basic JSON
json_build_object('name', ti.table_name)
ELSE
json_build_object(
'schema_name', ti.schema_name,
'object_name', ti.table_name,
'object_type', CASE ti.object_kind
WHEN 'r' THEN 'TABLE'
WHEN 'p' THEN 'PARTITIONED TABLE'
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
END,
'owner', ti.table_owner,
'comment', ti.table_comment,
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
)
END AS object_details
FROM table_info ti ORDER BY ti.schema_name, ti.table_name;
parameters:
- name: table_names
type: string
description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed."
- name: output_format
type: string
description: "Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema."
default: "detailed"
toolsets:
cloud-sql-postgres-database-tools:

View File

@@ -4,6 +4,7 @@
--text-secondary-gray: #6e6e6e;
--button-primary: var(--toolbox-blue);
--button-secondary: #616161;
--section-border: #e0e0e0;
}
body {
@@ -40,6 +41,7 @@ body {
display: flex;
flex-direction: column;
padding: 15px;
padding-bottom: 30px;
align-items: center;
width: 100%;
height: 100%;
@@ -161,6 +163,44 @@ body {
background-color: var(--button-secondary)
}
.btn--setup-gis {
background-color: white;
color: var(--text-primary-gray);
border: 2px solid var(--text-primary-gray);
}
.btn--clear-yaml {
background-color: transparent;
border: 2px solid var(--button-secondary);
border-radius: 5px;
color: var(--button-secondary)
}
.btn--download-yaml {
background-color: var(--button-secondary);
border-radius: 5px;
}
.btn--yaml-tab {
background-color: transparent;
color: var(--text-secondary-gray);
border: 2px solid var(--text-secondary-gray);
border-radius: 10px;
text-decoration: none;
font-size: 1.2em;
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
font-weight: bold;
width: 100%;
&.active {
background-color: #d0d0d0;
&:hover {
background-color: #d0d0d0;
}
}
}
.tool-button {
display: flex;
align-items: center;
@@ -379,7 +419,7 @@ body {
}
.auth-param-input {
background-color: #e0e0e0;
background-color: var(--section-border);
cursor: not-allowed;
}
@@ -513,6 +553,89 @@ body {
}
}
.auth-method-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
}
.auth-method-label {
font-weight: 500;
color: var(--text-primary-gray);
word-break: break-word;
}
.auth-helper-section {
border: 1px solid var(--section-border);
background-color: transparent;
padding: 16px;
border-radius: 8px;
margin-top: 20px;
width: 80%;
}
.auth-method-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.auth-method-details {
padding: 16px;
border: 1px solid var(--section-border);
border-radius: 4px;
margin-bottom: 16px;
background-color: #fff;
overflow-x: auto;
}
.auth-controls {
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.auth-input-row {
display: flex;
flex-direction: column;
gap: 6px;
& label {
font-size: 14px;
color: var(--text-primary-gray);
margin-bottom: 4px;
}
}
.auth-input {
padding: 8px 8px;
border: 1px solid #bdc1c6;
border-radius: 4px;
font-size: 14px;
width: 100%;
box-sizing: border-box;
&:focus {
outline: none;
border-color: var(--toolbox-blue);
box-shadow: 0 0 0 1px #1a73e8;
}
}
.auth-method-actions {
display: flex;
align-items: center;
gap: 12px;
}
.auth-instructions {
font-size: 0.8em;
margin-top: 5px;
color: var(--text-secondary-gray);
}
.tool-response {
margin: 20px 0 0 0;
@@ -576,5 +699,178 @@ body {
}
}
}
.editor-container {
display: flex;
flex-direction: row;
flex-grow: 1;
min-height: 0;
width: 100%;
gap: 20px;
}
#yaml-editor-area {
flex: 3;
padding: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-x: auto;
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 8px;
}
#source-adder {
flex: 1;
padding: 20px;
border: 1px solid #ddd;
background-color: #f9f9f9;
border-radius: 8px;
overflow-y: auto;
h3 {
margin-top: 0;
margin-bottom: 15px;
color: var(--text-primary-gray);
}
}
#yaml-uploader {
margin-bottom: 15px;
flex-shrink: 0;
}
#yaml-uploader h3 {
margin-top: 0;
margin-bottom: 10px;
color: var(--text-primary-gray);
}
#yaml-uploader p {
margin-top: 0;
margin-bottom: 10px;
font-size: 0.9em;
color: var(--text-secondary-gray);
}
#controls {
display: flex;
justify-content: space-between;
gap: 20px;
align-items: center;
margin-bottom: 10px;
}
.yaml-output-container {
display: flex;
flex-direction: column;
flex-grow: 1;
min-height: 0;
}
.editor-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
flex-shrink: 0;
h3 {
margin: 0;
color: var(--text-primary-gray);
}
}
.header-actions {
display: flex;
gap: 6px;
}
.icon-button {
background: none;
border: none;
padding: 6px;
cursor: pointer;
color: var(--text-secondary-gray);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease, color 0.2s ease;
&:hover {
background-color: #f0f0f0;
color: var(--text-primary-gray);
}
}
.icon-button .material-icons {
font-size: 22px;
}
#yamlOutput {
width: 100%;
flex-grow: 1;
font-family: monospace;
display: block;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
resize: none;
overflow-y: auto;
box-sizing: border-box;
min-height: 200px;
}
#sourceTypeSelect {
width: 100%;
padding: 10px;
margin-top: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.hidden-file-input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
#loadedFileName {
margin-top: 8px;
font-style: italic;
color: #555;
font-size: 0.9em;
word-break: break-all;
}
#fileError {
color: red;
margin-top: 8px;
font-size: 0.9em;
}
.upload-yaml-tab {
padding-top: 15px;
margin-top: auto;
width: 100%;
}
.toggle-details-tab {
background-color: transparent;
color: var(--toolbox-blue);
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
font-weight: bold;
&:hover {
opacity: 0.8;
}
}

View File

@@ -0,0 +1,140 @@
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"reflect"
"strings"
)
const (
sourcesDir = "internal/sources"
outputFile = "internal/server/static/data/source_templates.json"
)
func main() {
log.SetFlags(0)
results := make(map[string][]string)
absSourcesDir, err := filepath.Abs(sourcesDir)
if err != nil {
log.Fatalf("Failed to get absolute path for sources_dir: %v", err)
}
entries, err := os.ReadDir(absSourcesDir)
if err != nil {
log.Fatalf("Error reading sources directory %s: %v", absSourcesDir, err)
}
for _, entry := range entries {
if entry.IsDir() {
sourceName := entry.Name()
goFilePath := filepath.Join(absSourcesDir, sourceName, sourceName+".go")
if _, err := os.Stat(goFilePath); err == nil {
log.Printf("Processing: %s", goFilePath)
fields, err := parseConfigFields(goFilePath)
if err != nil {
log.Printf("Error parsing %s: %v", goFilePath, err)
continue
}
if len(fields) > 0 {
results[sourceName] = fields
} else {
log.Printf("Warning: No relevant fields found in 'Config' struct in %s", goFilePath)
}
} else if os.IsNotExist(err) {
// This is fine, not all subdirs might follow the pattern
} else {
log.Printf("Error stating file %s: %v", goFilePath, err)
}
}
}
if len(results) == 0 {
log.Printf("No source configs found matching the pattern in %s", absSourcesDir)
}
jsonData, err := json.MarshalIndent(results, "", " ")
if err != nil {
log.Fatalf("Error marshaling JSON: %v", err)
}
absOutputFile, err := filepath.Abs(outputFile)
if err != nil {
log.Fatalf("Failed to get absolute path for output_file: %v", err)
}
// Ensure the output directory exists
outputDir := filepath.Dir(absOutputFile)
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Fatalf("Error creating output directory %s: %v", outputDir, err)
}
err = os.WriteFile(absOutputFile, jsonData, 0644)
if err != nil {
log.Fatalf("Error writing output file %s: %v", absOutputFile, err)
}
fmt.Printf("Successfully generated %s\n", absOutputFile)
}
func parseConfigFields(filename string) ([]string, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, 0)
if err != nil {
return nil, err
}
var fields []string
foundConfig := false
ast.Inspect(node, func(n ast.Node) bool {
if foundConfig {
return false
}
typeSpec, ok := n.(*ast.TypeSpec)
if !ok || typeSpec.Name.Name != "Config" {
return true
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return true
}
foundConfig = true // Mark as found
for _, field := range structType.Fields.List {
if len(field.Names) == 0 {
continue
}
fieldName := field.Names[0].Name
if fieldName == "Name" {
continue
}
yamlTagName := ""
if field.Tag != nil {
tagVal := strings.Trim(field.Tag.Value, "`")
tags := reflect.StructTag(tagVal)
if yamlTag, ok := tags.Lookup("yaml"); ok {
yamlTagName = strings.Split(yamlTag, ",")[0]
}
}
if yamlTagName != "" && yamlTagName != "-" {
fields = append(fields, yamlTagName)
}
}
return false
})
return fields, nil
}

View File

@@ -0,0 +1,132 @@
{
"bigquery": [
"kind",
"project",
"location"
],
"bigtable": [
"kind",
"project",
"instance"
],
"couchbase": [
"kind",
"connectionString",
"bucket",
"scope",
"username",
"password",
"clientCert",
"clientCertPassword",
"clientKey",
"clientKeyPassword",
"caCert",
"noSslVerify",
"profile",
"queryScanConsistency"
],
"dataplex": [
"kind",
"project"
],
"dgraph": [
"kind",
"dgraphUrl",
"user",
"password",
"namespace",
"apiKey"
],
"duckdb": [
"kind",
"dbFilePath",
"configuration"
],
"firestore": [
"kind",
"project",
"database"
],
"http": [
"kind",
"baseUrl",
"timeout",
"headers",
"queryParams",
"disableSslVerification"
],
"looker": [
"kind",
"base_url",
"client_id",
"client_secret",
"verify_ssl",
"timeout"
],
"mongodb": [
"kind",
"uri"
],
"mssql": [
"kind",
"host",
"port",
"user",
"password",
"database",
"encrypt"
],
"mysql": [
"kind",
"host",
"port",
"user",
"password",
"database",
"queryTimeout"
],
"neo4j": [
"kind",
"uri",
"user",
"password",
"database"
],
"postgres": [
"kind",
"host",
"port",
"user",
"password",
"database"
],
"redis": [
"kind",
"address",
"username",
"password",
"database",
"useGCPIAM",
"clusterEnabled"
],
"spanner": [
"kind",
"project",
"instance",
"dialect",
"database"
],
"sqlite": [
"kind",
"database"
],
"valkey": [
"kind",
"address",
"username",
"password",
"database",
"useGCPIAM",
"disableCache"
]
}

View File

@@ -0,0 +1,173 @@
// 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.
/**
* Renders the Google Sign-In button using the GIS library.
* @param {string} toolId The ID of the tool.
* @param {string} clientId The Google OAuth Client ID.
* @param {string} authProfileName The name of the auth service in tools file.
*/
function renderGoogleSignInButton(toolId, clientId, authProfileName) {
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const GIS_CONTAINER_ID = `gisContainer-${UNIQUE_ID_BASE}`;
const gisContainer = document.getElementById(GIS_CONTAINER_ID);
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .btn--setup-gis`);
if (!gisContainer) {
console.error('GIS container not found:', GIS_CONTAINER_ID);
return;
}
if (!clientId) {
alert('Please enter an OAuth Client ID first.');
return;
}
// hide the setup button and show the container for the GIS button
if (setupGisBtn) setupGisBtn.style.display = 'none';
gisContainer.innerHTML = '';
gisContainer.style.display = 'flex';
if (window.google && window.google.accounts && window.google.accounts.id) {
try {
const handleResponse = (response) => handleCredentialResponse(response, toolId, authProfileName);
window.google.accounts.id.initialize({
client_id: clientId,
callback: handleResponse,
auto_select: false
});
window.google.accounts.id.renderButton(
gisContainer,
{ theme: "outline", size: "large", text: "signin_with" }
);
} catch (error) {
console.error("Error initializing Google Sign-In:", error);
alert("Error initializing Google Sign-In. Check the Client ID and browser console.");
gisContainer.innerHTML = '<p style="color: red;">Error loading Sign-In button.</p>';
if (setupGisBtn) setupGisBtn.style.display = '';
}
} else {
console.error("GIS library not fully loaded yet.");
alert("Google Identity Services library not ready. Please try again in a moment.");
gisContainer.innerHTML = '<p style="color: red;">GIS library not ready.</p>';
if (setupGisBtn) setupGisBtn.style.display = '';
}
}
/**
* Handles the credential response from the Google Sign-In library.
* @param {!CredentialResponse} response The credential response object from GIS.
* @param {string} toolId The ID of the tool.
* @param {string} authProfileName The name of the auth service in tools file.
*/
function handleCredentialResponse(response, toolId, authProfileName) {
console.debug("handleCredentialResponse called with:", { response, toolId, authProfileName });
const headersTextarea = document.getElementById(`headers-textarea-${toolId}`);
if (!headersTextarea) {
console.error('Headers textarea not found for toolId:', toolId);
return;
}
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .setup-gis-btn`);
const gisContainer = document.getElementById(`gisContainer-${UNIQUE_ID_BASE}`);
if (response.credential) {
const idToken = response.credential;
try {
let currentHeaders = {};
if (headersTextarea.value) {
currentHeaders = JSON.parse(headersTextarea.value);
}
const HEADER_KEY = `${authProfileName}_token`;
currentHeaders[HEADER_KEY] = `${idToken}`;
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
if (gisContainer) gisContainer.style.display = 'none';
if (setupGisBtn) setupGisBtn.style.display = '';
} catch (e) {
alert('Headers are not valid JSON. Please correct and try again.');
console.error("Header JSON parse error:", e);
}
} else {
console.error("Error: No credential in response", response);
alert('Error: No ID Token received. Check console for details.');
if (gisContainer) gisContainer.style.display = 'none';
if (setupGisBtn) setupGisBtn.style.display = '';
}
}
// creates the Google Auth method dropdown
export function createGoogleAuthMethodItem(toolId, authProfileName) {
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const item = document.createElement('div');
item.className = 'auth-method-item';
item.innerHTML = `
<div class="auth-method-header">
<span class="auth-method-label">Google ID Token (${authProfileName})</span>
<button class="toggle-details-tab">Auto Setup</button>
</div>
<div class="auth-method-details" id="google-auth-details-${UNIQUE_ID_BASE}" style="display: none;">
<div class="auth-controls">
<div class="auth-input-row">
<label for="clientIdInput-${UNIQUE_ID_BASE}">OAuth Client ID:</label>
<input type="text" id="clientIdInput-${UNIQUE_ID_BASE}" placeholder="Enter Client ID" class="auth-input">
</div>
<div class="auth-instructions">
<strong>Action Required:</strong> Add this URL (e.g., http://localhost:PORT) to the Client ID's <strong>Authorized JavaScript origins</strong> to avoid a 401 error. If using Google OAuth,
navigate to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> for this setting.
</div>
<div class="auth-method-actions">
<button class="btn btn--setup-gis">Continue</button>
<div id="gisContainer-${UNIQUE_ID_BASE}" class="auth-interactive-element gis-container" style="display: none;"></div>
</div>
</div>
</div>
`;
const toggleBtn = item.querySelector('.toggle-details-tab');
const detailsDiv = item.querySelector(`#google-auth-details-${UNIQUE_ID_BASE}`);
const setupGisBtn = item.querySelector('.btn--setup-gis');
const clientIdInput = item.querySelector(`#clientIdInput-${UNIQUE_ID_BASE}`);
const gisContainer = item.querySelector(`#gisContainer-${UNIQUE_ID_BASE}`);
toggleBtn.addEventListener('click', () => {
const isVisible = detailsDiv.style.display === 'flex';
detailsDiv.style.display = isVisible ? 'none' : 'flex';
toggleBtn.textContent = isVisible ? 'Auto Setup' : 'Close';
if (!isVisible) {
if (gisContainer) {
gisContainer.innerHTML = '';
gisContainer.style.display = 'none';
}
if (setupGisBtn) {
setupGisBtn.style.display = '';
}
}
});
setupGisBtn.addEventListener('click', () => {
const clientId = clientIdInput.value;
if (!clientId) {
alert('Please enter an OAuth Client ID first.');
return;
}
renderGoogleSignInButton(toolId, clientId, authProfileName);
});
return item;
}

View File

@@ -128,6 +128,7 @@ async function fetchToolDetails(toolName, toolDisplayArea) {
id: toolName,
name: toolName,
description: toolObject.description || "No description provided.",
authRequired: toolObject.authRequired || [],
parameters: (toolObject.parameters || []).map(param => {
let inputType = 'text';
const apiType = param.type ? param.type.toLowerCase() : 'string';

View File

@@ -35,6 +35,9 @@ function renderNavbar(containerId, activePath) {
<li><a href="/ui/tools">Tools</a></li>
<li><a href="/ui/toolsets">Toolsets</a></li>
</ul>
<div class="upload-yaml-tab">
<a href="/ui/upload" class="btn btn--yaml-tab">YAML Builder</a>
</div>
</nav>
`;
@@ -49,5 +52,15 @@ function renderNavbar(containerId, activePath) {
link.classList.remove('active');
}
});
const yamlButton = navbarContainer.querySelector('.btn--yaml-tab');
if (yamlButton) {
const buttonPath = new URL(yamlButton.href).pathname;
if (buttonPath === activePath) {
yamlButton.classList.add('active');
} else {
yamlButton.classList.remove('active');
}
}
}
}

View File

@@ -0,0 +1,91 @@
// 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.
document.addEventListener('DOMContentLoaded', () => {
const sourceTypeSelect = document.getElementById('sourceTypeSelect');
const yamlOutputTextarea = document.getElementById('yamlOutput');
let dbConfigData = {};
const jsonPath = '/ui/data/source_templates.json';
async function loadDbConfigs() {
try {
const response = await fetch(jsonPath);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} fetching ${jsonPath}`);
}
dbConfigData = await response.json();
populateDropdown();
} catch (error) {
console.error('Could not load database types:', error);
sourceTypeSelect.disabled = true;
let errorOption = sourceTypeSelect.querySelector('option[value=""]');
if (errorOption) {
errorOption.textContent = 'Failed to load types';
}
}
}
function populateDropdown() {
if (!dbConfigData || Object.keys(dbConfigData).length === 0) {
console.warn('No database types found in JSON data.');
return;
}
Object.keys(dbConfigData).sort().forEach(dbType => {
const option = document.createElement('option');
option.value = dbType;
option.textContent = dbType;
sourceTypeSelect.appendChild(option);
});
}
sourceTypeSelect.addEventListener('change', () => {
const selectedType = sourceTypeSelect.value;
if (!selectedType || !dbConfigData[selectedType]) {
return;
}
const fields = dbConfigData[selectedType];
if (!Array.isArray(fields)) {
console.error(`Fields for ${selectedType} is not an array.`);
return;
}
const currentYaml = yamlOutputTextarea.value;
const lines = currentYaml.split('\n');
const sourcesIndex = lines.findIndex(line => line.trim() === 'sources:');
if (sourcesIndex === -1) {
alert('The line "sources:" was not found in the YAML content. Please add it to insert a source.');
sourceTypeSelect.value = "";
return;
}
const NAME_INDENT = ' ';
const FIELD_INDENT = ' ';
let snippetLines = [];
snippetLines.push(`${NAME_INDENT}YOUR_${selectedType.toUpperCase()}_SOURCE_NAME:`);
fields.forEach(field => {
snippetLines.push(`${FIELD_INDENT}${field}:`);
});
lines.splice(sourcesIndex + 1, 0, ...snippetLines);
yamlOutputTextarea.value = lines.join('\n');
yamlOutputTextarea.scrollTop = 0;
sourceTypeSelect.value = "";
});
loadDbConfigs();
});

View File

@@ -13,6 +13,7 @@
// limitations under the License.
import { handleRunTool, displayResults } from './runTool.js';
import { createGoogleAuthMethodItem } from './auth.js'
/**
* Helper function to create form inputs for parameters.
@@ -151,7 +152,7 @@ function createParamInput(param, toolId) {
* parsed. The function receives the updated headers object as its argument.
* @return {!HTMLDivElement} The outermost div element of the created modal.
*/
function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
function createHeaderEditorModal(toolId, currentHeaders, toolParameters, authRequired, saveCallback) {
const MODAL_ID = `header-modal-${toolId}`;
let modal = document.getElementById(MODAL_ID);
@@ -174,9 +175,37 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
headersTextarea.rows = 10;
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
// handle authenticated params
const authProfileNames = new Set();
toolParameters.forEach(param => {
const isAuthParam = param.authServices && param.authServices.length > 0;
if (isAuthParam && param.authServices) {
param.authServices.forEach(name => authProfileNames.add(name));
}
});
// handle authorized invocations
if (authRequired && authRequired.length > 0) {
authRequired.forEach(name => authProfileNames.add(name));
}
modalContent.appendChild(modalHeader);
modalContent.appendChild(headersTextarea);
if (authProfileNames.size > 0 || authRequired.length > 0) {
const authHelperSection = document.createElement('div');
authHelperSection.className = 'auth-helper-section';
const authList = document.createElement('div');
authList.className = 'auth-method-list';
authProfileNames.forEach(profileName => {
const authItem = createGoogleAuthMethodItem(toolId, profileName);
authList.appendChild(authItem);
});
authHelperSection.appendChild(authList);
modalContent.appendChild(authHelperSection);
}
const modalActions = document.createElement('div');
const closeButton = document.createElement('button');
const saveButton = document.createElement('button');
@@ -205,13 +234,6 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
modalContent.appendChild(authTokenDropdown);
modal.appendChild(modalContent);
// Close modal if clicked outside
window.addEventListener('click', (event) => {
if (event.target === modal) {
closeHeaderEditor(toolId);
}
});
return modal;
}
@@ -246,7 +268,7 @@ function createAuthTokenInfoDropdown() {
details.className = 'auth-token-details';
details.appendChild(summary);
summary.textContent = 'How to extract Google OAuth ID Token';
summary.textContent = 'How to extract Google OAuth ID Token manually';
content.className = 'auth-token-content';
// auth instruction dropdown
@@ -321,10 +343,9 @@ export function renderToolInterface(tool, containerElement) {
const updateLastResults = (newResults) => {
lastResults = newResults;
};
const updateCurrentHeaders = (newHeaders) => {
currentHeaders = newHeaders;
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
containerElement.appendChild(newModal);
};
@@ -428,7 +449,7 @@ export function renderToolInterface(tool, containerElement) {
containerElement.appendChild(responseContainer);
// create and append the header editor modal
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
containerElement.appendChild(headerModal);
prettifyCheckbox.addEventListener('change', () => {

View File

@@ -0,0 +1,184 @@
// 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.
document.addEventListener('DOMContentLoaded', () => {
const SESSION_STORAGE_KEY = 'yamlUploaderSessionData';
const yamlFileInput = document.getElementById('yamlFileInput');
const fileError = document.getElementById('fileError');
const yamlOutput = document.getElementById('yamlOutput');
const clearSessionButton = document.getElementById('clearSessionButton');
const loadedFileNameDisplay = document.getElementById('loadedFileName');
const downloadYamlButton = document.getElementById('downloadYamlButton');
function debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function saveToSessionStorage(filename, yamlContent) {
try {
const data = { filename: filename, yamlContent: yamlContent };
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(data));
console.debug('Saved to sessionStorage');
} catch (e) {
console.error("Error saving to sessionStorage:", e);
fileError.textContent = "Failed to save data to session storage.";
}
}
function loadFromSessionStorage() {
try {
const storedData = sessionStorage.getItem(SESSION_STORAGE_KEY);
if (storedData) {
const data = JSON.parse(storedData);
if (data.yamlContent) {
yamlOutput.value = data.yamlContent;
}
if (data.filename) {
loadedFileNameDisplay.textContent = `Current file: ${data.filename}`;
} else {
loadedFileNameDisplay.textContent = "";
}
console.debug('Loaded from sessionStorage');
} else {
console.debug('No data in sessionStorage');
}
} catch (e) {
console.error("Error loading from sessionStorage:", e);
fileError.textContent = "Failed to load data from session storage.";
sessionStorage.removeItem(SESSION_STORAGE_KEY);
}
}
function clearSessionStorage() {
sessionStorage.removeItem(SESSION_STORAGE_KEY);
yamlOutput.value = '';
fileError.textContent = '';
yamlFileInput.value = '';
loadedFileNameDisplay.textContent = '';
console.log("Cleared stored YAML from sessionStorage.");
}
if (yamlFileInput) {
yamlFileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) {
console.log('No file selected.');
return;
}
const fileName = file.name;
if (!fileName.toLowerCase().endsWith('.yaml') && !fileName.toLowerCase().endsWith('.yml')) {
fileError.textContent = 'Invalid file type. Please upload a .yaml or .yml file.';
yamlFileInput.value = '';
return;
}
fileError.textContent = '';
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
try {
if (typeof jsyaml === 'undefined') {
fileError.textContent = 'Error: js-yaml library not loaded.';
return;
}
const parsedYaml = jsyaml.load(content);
const prettyYaml = jsyaml.dump(parsedYaml, { indent: 2, lineWidth: 120, noRefs: true });
yamlOutput.value = prettyYaml;
loadedFileNameDisplay.textContent = `Current file: ${fileName}`;
saveToSessionStorage(fileName, prettyYaml);
} catch (err) {
fileError.textContent = 'Error parsing YAML: ' + err.message;
console.error('YAML Parsing Error:', err);
yamlOutput.value = '';
loadedFileNameDisplay.textContent = '';
sessionStorage.removeItem(SESSION_STORAGE_KEY);
}
};
reader.onerror = () => {
fileError.textContent = 'Error reading file.';
};
reader.readAsText(file);
});
}
if (yamlOutput) {
yamlOutput.addEventListener('input', debounce(() => {
const currentFilename = getDownloadFilename();
saveToSessionStorage(currentFilename, yamlOutput.value);
console.debug("YAML content changes saved to sessionStorage.");
}, 500));
}
if (clearSessionButton) {
clearSessionButton.addEventListener('click', () => {
if (confirm("Clear all content from the YAML editor? This cannot be undone.")) {
clearSessionStorage();
console.debug("User confirmed clearing session.");
} else {
console.debug("User cancelled clearing session.");
}
});
}
function getDownloadFilename() {
const loadedFileName = loadedFileNameDisplay.textContent;
let baseName = 'tools';
if (loadedFileName && loadedFileName.startsWith('Current file: ')) {
const originalFileName = loadedFileName.substring('Current file: '.length);
baseName = originalFileName.replace(/\.ya?ml$/i, '');
}
return `${baseName}_MODIFIED.yaml`;
}
if (downloadYamlButton) {
downloadYamlButton.addEventListener('click', () => {
const yamlContent = yamlOutput.value;
if (!yamlContent) {
alert("Text area is empty. Nothing to download.");
return;
}
const fileName = getDownloadFilename();
const blob = new Blob([yamlContent], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.debug('Download link cleaned up');
}, 100);
});
}
loadFromSessionStorage();
});

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tools View</title>
<link rel="stylesheet" href="/ui/css/style.css">
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/tools"></div>

View File

@@ -6,7 +6,7 @@
<title>Toolsets View</title>
<link rel="stylesheet" href="/ui/css/style.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/toolsets"></div>

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YAML Builder View</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="/ui/css/style.css">
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/upload"></div>
<div id="main-content-container">
<div class="editor-container">
<div id="yaml-editor-area">
<div id="yaml-uploader">
<h3>Upload YAML File</h3>
<p>Please select a file ending with .yaml or .yml</p>
<div id="controls">
<div>
<input type="file" id="yamlFileInput" accept=".yaml,.yml" class="hidden-file-input">
<label for="yamlFileInput" class="btn btn--run">Choose File</label>
</div>
</div>
<div id="fileError"></div>
<div id="loadedFileName"></div>
</div>
<div class="yaml-output-container">
<div class="editor-header">
<h3>Formatted YAML Output:</h3>
<div class="header-actions">
<button id="clearSessionButton" class="icon-button" title="Clear YAML">
<i class="material-icons">delete</i>
</button>
<button id="downloadYamlButton" class="icon-button" title="Download YAML">
<i class="material-icons">file_download</i>
</button>
</div>
</div>
<textarea id="yamlOutput" placeholder="Upload a YAML file or paste content here..."></textarea>
</div>
</div>
<div id="source-adder">
<h3>Add Source Snippet</h3>
<select id="sourceTypeSelect">
<option value="">Select DB Type...</option>
</select>
<p style="font-size: 0.8em; color: #666;">Select a database type to append a template to the YAML output.</p>
</div>
</div>
</div>
<script src="/ui/js/navbar.js"></script>
<script src="/ui/js/uploadYaml.js"></script>
<script src="/ui/js/sourceSelector.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
if (typeof renderNavbar === 'function') {
renderNavbar('navbar-container', activeNav);
} else {
console.error('renderNavbar function not found.');
}
});
</script>
</body>
</html>

View File

@@ -24,6 +24,7 @@ func webRouter() (chi.Router, error) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/index.html") })
r.Get("/tools", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/tools.html") })
r.Get("/toolsets", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/toolsets.html") })
r.Get("/upload", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/uploadYaml.html") })
// handler for all other static files/assets
staticFS, _ := fs.Sub(staticContent, "static")

View File

@@ -73,6 +73,20 @@ func TestWebEndpoint(t *testing.T) {
wantContentType: "text/html",
wantPageTitle: "Toolsets View",
},
{
name: "web yaml builder page",
path: "/ui/upload",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "YAML Builder View",
},
{
name: "web yaml builder page with trailing slash",
path: "/ui/upload/",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "YAML Builder View",
},
}
for _, tc := range testCases {

View File

@@ -1,128 +0,0 @@
// 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 duckdb
import (
"context"
"database/sql"
"fmt"
"net/url"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
_ "github.com/marcboeker/go-duckdb/v2"
"go.opentelemetry.io/otel/trace"
)
const SourceKind string = "duckdb"
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Source struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Db *sql.DB
}
// SourceKind implements sources.Source.
func (s *Source) SourceKind() string {
return SourceKind
}
func (s *Source) DuckDb() *sql.DB {
return s.Db
}
// validate Source
var _ sources.Source = &Source{}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
DatabaseFile string `yaml:"dbFilePath,omitempty"`
Configuration map[string]string `yaml:"configuration,omitempty"`
}
func (r Config) SourceConfigKind() string {
return SourceKind
}
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
db, err := initDuckDbConnection(ctx, tracer, r.Name, r.DatabaseFile, r.Configuration)
if err != nil {
return nil, fmt.Errorf("unable to create db connection: %w", err)
}
err = db.PingContext(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to connect sucessfully: %w", err)
}
s := &Source{
Name: r.Name,
Kind: r.Kind,
Db: db,
}
return s, nil
}
// validate interface
var _ sources.SourceConfig = Config{}
func initDuckDbConnection(ctx context.Context, tracer trace.Tracer, name string, dbFilePath string, duckdbConfiguration map[string]string) (*sql.DB, error) {
//nolint:all // Reassigned ctx
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
defer span.End()
var configStr = getDuckDbConfiguration(dbFilePath, duckdbConfiguration)
//Open database connection
db, err := sql.Open("duckdb", configStr)
if err != nil {
return nil, fmt.Errorf("unable to open duckdb connection: %w", err)
}
return db, nil
}
func getDuckDbConfiguration(dbFilePath string, duckdbConfiguration map[string]string) string {
if len(duckdbConfiguration) == 0 {
return dbFilePath
}
params := url.Values{}
for key, value := range duckdbConfiguration {
params.Set(key, value)
}
var configStr strings.Builder
configStr.WriteString(dbFilePath)
configStr.WriteString("?")
configStr.WriteString(params.Encode())
return configStr.String()
}

View File

@@ -1,84 +0,0 @@
// 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 duckdb_test
import (
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParserFromYamlDuckDb(t *testing.T) {
config := make(map[string]string)
config["access_mode"] = "READ_ONLY"
config["threads"] = "4"
tcs := []struct {
desc string
in string
want server.SourceConfigs
}{
{
desc: "basic example",
in: `
sources:
my-duckdb:
kind: duckdb
`,
want: server.SourceConfigs{
"my-duckdb": duckdb.Config{
Name: "my-duckdb",
Kind: duckdb.SourceKind,
},
},
},
{
desc: "with custom configuration",
in: `
sources:
my-duckdb:
kind: duckdb
configuration:
access_mode: READ_ONLY
threads: 4
`,
want: server.SourceConfigs{
"my-duckdb": duckdb.Config{
Name: "my-duckdb",
Kind: duckdb.SourceKind,
Configuration: config,
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if !cmp.Equal(tc.want, got.Sources) {
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
}
})
}
}

View File

@@ -1,210 +0,0 @@
// 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 duckdbsql
import (
"context"
"database/sql"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
"github.com/googleapis/genai-toolbox/internal/tools"
)
const kind string = "duckdb-sql"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
DuckDb() *sql.DB
}
// validate compatible sources are still compatible
var _ compatibleSource = &duckdb.Source{}
var compatibleSources = [...]string{duckdb.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// Initialize implements tools.ToolConfig.
func (c Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[c.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", c.Source)
}
// verify the source is compatible
s, ok := rawS.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(c.TemplateParameters, c.Parameters)
mcpManifest := tools.McpManifest{
Name: c.Name,
Description: c.Description,
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: c.Name,
Kind: kind,
Parameters: c.Parameters,
TemplateParameters: c.TemplateParameters,
AllParams: allParameters,
Statement: c.Statement,
AuthRequired: c.AuthRequired,
Db: s.DuckDb(),
manifest: tools.Manifest{Description: c.Description, Parameters: paramManifest, AuthRequired: c.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// ToolConfigKind implements tools.ToolConfig.
func (c Config) ToolConfigKind() string {
return kind
}
var _ tools.ToolConfig = Config{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
Db *sql.DB
Statement string `yaml:"statement"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
// Authorized implements tools.Tool.
func (t Tool) Authorized(verifiedAuthSources []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthSources)
}
// Invoke implements tools.Tool.
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
sliceParams := newParams.AsSlice()
// Execute the SQL query with parameters
rows, err := t.Db.QueryContext(ctx, newStatement, sliceParams...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
defer rows.Close()
// Get column names
cols, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("unable to get column names: %w", err)
}
values := make([]any, len(cols))
valuePtrs := make([]any, len(cols))
for i := range values {
valuePtrs[i] = &values[i]
}
// Prepare the result slice
var result []any
// Iterate through the rows
for rows.Next() {
// Scan the row into the value pointers
if err := rows.Scan(valuePtrs...); err != nil {
return nil, fmt.Errorf("unable to scan row: %w", err)
}
// Create a map for this row
rowMap := make(map[string]interface{})
for i, col := range cols {
val := values[i]
// Handle nil values
if val == nil {
rowMap[col] = nil
continue
}
// Store the value in the map
rowMap[col] = val
}
result = append(result, rowMap)
}
if err = rows.Close(); err != nil {
return nil, fmt.Errorf("unable to close rows: %w", err)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating rows: %w", err)
}
return result, nil
}
// Manifest implements tools.Tool.
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
// McpManifest implements tools.Tool.
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
// ParseParams implements tools.Tool.
func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.AllParams, data, claimsMap)
}
var _ tools.Tool = Tool{}

View File

@@ -1,87 +0,0 @@
// 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 duckdbsql_test
import (
"testing"
"github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools"
)
func TestParseFromYamlDuckDb(t *testing.T) {
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: duckdb-sql
source: my-duckdb-instance
description: some description
statement: |
select * from hotel WHERE name = $hotel;
parameters:
- name: hotel
type: string
description: hotel parameter description
`,
want: server.ToolConfigs{
"example_tool": duckdbsql.Config{
Name: "example_tool",
Kind: "duckdb-sql",
Source: "my-duckdb-instance",
Description: "some description",
Statement: "select * from hotel WHERE name = $hotel;\n",
AuthRequired: []string{},
Parameters: []tools.Parameter{
tools.NewStringParameter("hotel", "hotel parameter description"),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Create a context with a logger
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unable to create context with logger: %s", err)
}
// Parse contents with context
err = yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -1,152 +0,0 @@
// 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 duckdb
import (
"context"
"database/sql"
"fmt"
"os"
"regexp"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/tests"
)
var (
DuckDbKind = "duckdb-sql"
dbPath = "/tmp/users.db"
)
func getDuckDbVars() map[string]any {
return map[string]any{
"kind": "duckdb",
"dbFilePath": dbPath,
"configuration": map[string]any{
"access_mode": "READ_WRITE",
},
}
}
func setupDuckDb(t *testing.T, createParamStmt, insertParamStmt, createAuthStmt, insertAuthStmt string, params []any, authparams []any) {
// Remove any existing database file to ensure a clean state
os.Remove(dbPath)
// Open a connection to DuckDB
db, err := sql.Open("duckdb", dbPath)
if err != nil {
t.Fatalf("Failed to open DuckDB connection: %v", err)
}
defer db.Close()
_, err = db.Exec(createParamStmt, params...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.Exec(createAuthStmt, authparams...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.Exec(insertParamStmt, params...)
if err != nil {
t.Fatalf("Failed to insert initial data: %v", err)
}
_, err = db.Exec(insertAuthStmt, authparams...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
}
func TestDuckDb(t *testing.T) {
sourceConfig := getDuckDbVars()
var args []string
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := GetDuckDbParamToolInfo(tableNameParam)
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := GetDuckDbAuthToolInfo(tableNameAuth)
setupDuckDb(t, createParamTableStmt, insertParamTableStmt, createAuthTableStmt, insertAuthTableStmt, paramTestParams, authTestParams)
toolsFile := tests.GetToolsConfig(sourceConfig, DuckDbKind, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, DuckDbKind, tmplSelectCombined, tmplSelectFilterCombined, "")
defer os.Remove(dbPath)
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)
}
tests.RunToolGetTest(t)
select1Want, failInvocationWant, _ := GetDuckDbWants()
_, invokeParamWantNull, nullWant, _ := tests.GetNonSpannerInvokeParamWant()
invokeParamWant := "[{\"name\":\"Alice\"},{\"name\":\"Sid\"}]"
mcpInvokeParamWant := "{\"jsonrpc\":\"2.0\",\"id\":\"my-tool\",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Alice\\\"}\"},{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Sid\\\"}\"}]}}"
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
templateParamTestConfig := tests.NewTemplateParameterTestConfig(
tests.WithSelectAllWant("[{\"age\":21,\"id\":1,\"name\":\"Alex\"},{\"age\":100,\"id\":2,\"name\":\"Alice\"}]"),
tests.WithSelect1Want("[{\"age\":21,\"id\":1,\"name\":\"Alex\"}]"),
tests.WithReplaceNameFieldArray(`["name"]`),
tests.WithReplaceNameColFilter("name"),
tests.WithCreateColArray(`["id INT","name VARCHAR(20)","age INT"]`),
tests.WithInsert1Want("[{\"Count\":1}]"),
)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, templateParamTestConfig)
}
func GetDuckDbParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, $1), (2, $2), (3, $3), (4, $4);", tableName)
toolStatement := fmt.Sprintf("SELECT * EXCLUDE (id) FROM %s WHERE id = $1 OR name = $2 order by id;", tableName)
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id IN (SELECT unnest(list_value($1)) AS id);", tableName)
toolStatement2 := fmt.Sprintf("SELECT name FROM %s WHERE id = list_extract(list_value($1), 1);", tableName)
arrayToolStatement := fmt.Sprintf("SELECT name FROM %s WHERE id = ANY($1) AND name = ANY($2) order by name;", tableName)
params := []any{"Alice", "Jane", "Sid", nil}
return createStatement, insertStatement, toolStatement, idParamStatement, toolStatement2, arrayToolStatement, params
}
// GetDuckDbAuthToolInfo returns statements and param of my-auth-tool for duckdb-sql kind
func GetDuckDbAuthToolInfo(tableName string) (string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT, email TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name, email) VALUES (1, $1, $2), (2, $3, $4)", tableName)
toolStatement := fmt.Sprintf("SELECT name FROM %s WHERE email = $1;", tableName)
params := []any{"Alice", tests.ServiceAccountEmail, "Jane", "janedoe@gmail.com"}
return createStatement, insertStatement, toolStatement, params
}
func GetDuckDbWants() (string, string, string) {
select1Want := "[{\"1\":1}]"
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: Parser Error: syntax error at or near \"SELEC\""}],"isError":true}}`
createTableStatement := `"CREATE TABLE t (id SERIAL PRIMARY KEY, name TEXT)"`
return select1Want, failInvocationWant, createTableStatement
}