mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 16:02:59 +00:00
Merge remote-tracking branch 'origin/main' into glm/session-deployed-runs
This commit is contained in:
@@ -35,7 +35,8 @@ env:
|
||||
GLOBAL_EXTRA_MODELS: ${{ inputs.global_extra_models || 'gpt-6-astra gemini-3.8-flash' }}
|
||||
RUNS: ${{ inputs.runs || '3' }}
|
||||
REASONING: ${{ inputs.reasoning }}
|
||||
INGEST_URL: https://app.windmill.dev/api/r/f/ai/ingest_ai_eval_run
|
||||
# Routes on the cloud instance are prefixed with their workspace.
|
||||
INGEST_URL: https://app.windmill.dev/api/r/windmill-prod/f/ai/ingest_ai_eval_run
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
@@ -156,7 +157,7 @@ jobs:
|
||||
| .cases[].attempts[] |= {attempt, passed, durationMs, toolCallCount, judgeScore,
|
||||
judgeSummary, error, tokenUsage, checks: [.checks[]? | {name, passed}]}),
|
||||
git_ref: $ref, trigger: $trigger, run_url: $url}' "$f" \
|
||||
| curl -sf --retry 3 -X POST "$INGEST_URL" \
|
||||
| curl -sS --fail-with-body --retry 3 -X POST "$INGEST_URL" \
|
||||
-H "Authorization: Bearer $INGEST_TOKEN" -H 'content-type: application/json' \
|
||||
--data-binary @- && echo " <- $f" || echo "::warning::failed to post $f"
|
||||
done
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "1.820.0"
|
||||
".": "1.821.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# Changelog
|
||||
|
||||
## [1.821.0](https://github.com/windmill-labs/windmill/compare/v1.820.0...v1.821.0) (2026-10-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add //no_network annotation for native scripts ([#11445](https://github.com/windmill-labs/windmill/issues/11445)) ([c25bb1e](https://github.com/windmill-labs/windmill/commit/c25bb1ed90d88167df0c36817942427a32fc3205))
|
||||
* add alert actions and reduce alert padding ([#11439](https://github.com/windmill-labs/windmill/issues/11439)) ([52c3188](https://github.com/windmill-labs/windmill/commit/52c31881fc433a03ef13f7a8cde1410da8634e19))
|
||||
* add test key button to AI resource drawers ([#11466](https://github.com/windmill-labs/windmill/issues/11466)) ([26be1d7](https://github.com/windmill-labs/windmill/commit/26be1d7da8be48c2e7d7a6b95d406513d649be4e))
|
||||
* add wmill datatable migrate status ([#11465](https://github.com/windmill-labs/windmill/issues/11465)) ([7f1bead](https://github.com/windmill-labs/windmill/commit/7f1beadf793e8db05333d253f464954510961a5b))
|
||||
* agents as a standalone kind with home listing, detail and editor pages ([#11332](https://github.com/windmill-labs/windmill/issues/11332)) ([8c7dcbb](https://github.com/windmill-labs/windmill/commit/8c7dcbbda58c86743bf3822c422be7faf1e8df51))
|
||||
* dispatch ai agent tools through the worker queue ([#11227](https://github.com/windmill-labs/windmill/issues/11227)) ([039f49e](https://github.com/windmill-labs/windmill/commit/039f49ef124eacefc0ee34dcb011ea5773383845))
|
||||
* group consecutive tool calls in the AI chat ([#11329](https://github.com/windmill-labs/windmill/issues/11329)) ([9613549](https://github.com/windmill-labs/windmill/commit/9613549abc1c1d63abb252e8a675c6a73142b26f))
|
||||
* harden job OIDC tokens with version claims, jti and a lifetime cap ([#11457](https://github.com/windmill-labs/windmill/issues/11457)) ([227e934](https://github.com/windmill-labs/windmill/commit/227e9342108bf7c5be5721a777ff9ec544a44f08))
|
||||
* labelled scope checkboxes in the oauth connect dialog ([#11460](https://github.com/windmill-labs/windmill/issues/11460)) ([840567f](https://github.com/windmill-labs/windmill/commit/840567fbe5c8779ac163b02a6e02e3d8ec1178a8))
|
||||
* let operators compose flows when the workspace grants the right ([#11228](https://github.com/windmill-labs/windmill/issues/11228)) ([ca44043](https://github.com/windmill-labs/windmill/commit/ca44043e12d4713d67ca13eb546eef9d00cc965e))
|
||||
* let slack connects issue a user token via user_scope ([#11452](https://github.com/windmill-labs/windmill/issues/11452)) ([d518aa5](https://github.com/windmill-labs/windmill/commit/d518aa5557910d10ec5a8ca6fff3fd0ad2fa31fc))
|
||||
* make hub integrations usable as examples in global AI chat ([#10599](https://github.com/windmill-labs/windmill/issues/10599)) ([8953ca6](https://github.com/windmill-labs/windmill/commit/8953ca670aac63feab1a04369df50a669c1f40bc))
|
||||
* offer slack write scopes as bot and user scope options ([#11472](https://github.com/windmill-labs/windmill/issues/11472)) ([ab2de83](https://github.com/windmill-labs/windmill/commit/ab2de838d76e2229da0867c93c2f18dc34d0d076))
|
||||
* open chat path pill actions from a hover menu ([#11441](https://github.com/windmill-labs/windmill/issues/11441)) ([083db5e](https://github.com/windmill-labs/windmill/commit/083db5e38855586491920abf678e3247d8c82a43))
|
||||
* rename agent memory options to On and Legacy, explain each ([#11442](https://github.com/windmill-labs/windmill/issues/11442)) ([077709b](https://github.com/windmill-labs/windmill/commit/077709b914b8b58cbd8c99f1048c7fecc2978c6b))
|
||||
* **sessions:** show an item's deployed page in the preview panel ([#11458](https://github.com/windmill-labs/windmill/issues/11458)) ([b10f83c](https://github.com/windmill-labs/windmill/commit/b10f83c763abf49118c05dc08ec2b19570e21475))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apps:** hide custom component host while it is not rendered ([#11434](https://github.com/windmill-labs/windmill/issues/11434)) ([c728346](https://github.com/windmill-labs/windmill/commit/c728346a151e4a0a2d1ecaae397068871186323d))
|
||||
* await async def main in python scripts ([#11437](https://github.com/windmill-labs/windmill/issues/11437)) ([94ac34a](https://github.com/windmill-labs/windmill/commit/94ac34a4891a8572ad2493bdbbbf2f4911236aff))
|
||||
* block runs while a JSON input does not parse ([#11463](https://github.com/windmill-labs/windmill/issues/11463)) ([ddde41b](https://github.com/windmill-labs/windmill/commit/ddde41bc1de6f7dea32b21a61ce2a5bf3bf8bba6))
|
||||
* bump git sync hub scripts to cli 1.820.1 ([#11438](https://github.com/windmill-labs/windmill/issues/11438)) ([7664001](https://github.com/windmill-labs/windmill/commit/7664001b36afc80bd8d26cb12c917c971e24e96f))
|
||||
* **cli:** resolve fork specific items from the parent workspace entry ([#11436](https://github.com/windmill-labs/windmill/issues/11436)) ([1d8d4a1](https://github.com/windmill-labs/windmill/commit/1d8d4a16b4396acb70484c858058353eb1ba9d6f))
|
||||
* collapse run card in ai chat when the run is declined ([#11435](https://github.com/windmill-labs/windmill/issues/11435)) ([ab2a973](https://github.com/windmill-labs/windmill/commit/ab2a973357524c2c3aa81cc4aeb716d8cfd108d1))
|
||||
* embed a VERSIONINFO resource in the Windows binary ([#11433](https://github.com/windmill-labs/windmill/issues/11433)) ([9028432](https://github.com/windmill-labs/windmill/commit/9028432e8a71b9601ff65160a29e683a9cc24989))
|
||||
* ignore edited_at and edited_by in flow and item diffs ([#11455](https://github.com/windmill-labs/windmill/issues/11455)) ([851f6d7](https://github.com/windmill-labs/windmill/commit/851f6d74c98e849b25d6999e3d8ee492c997d5a9))
|
||||
* keep flow graph rendered between deploy and navigation ([#11454](https://github.com/windmill-labs/windmill/issues/11454)) ([c0fb2db](https://github.com/windmill-labs/windmill/commit/c0fb2db24454d17d33fffa41b59e6a4cf61db44f))
|
||||
* lost suspend decrement when concurrent approvals resume a flow ([#11450](https://github.com/windmill-labs/windmill/issues/11450)) ([a8e65ab](https://github.com/windmill-labs/windmill/commit/a8e65ab1e6287805f616f2531dc6a62134134e36))
|
||||
* **npm-proxy:** support _auth and username/_password basic auth from npmrc ([#11467](https://github.com/windmill-labs/windmill/issues/11467)) ([1c6758a](https://github.com/windmill-labs/windmill/commit/1c6758a65651d90088f981317c06b12a19f3b73f))
|
||||
* offer user scopes in the slack scope editor and drop the generated description on type change ([#11464](https://github.com/windmill-labs/windmill/issues/11464)) ([153b02a](https://github.com/windmill-labs/windmill/commit/153b02a5de19c615e6640d52ad48b1c507402851))
|
||||
* restore the save to workspace button on inline flow steps ([#11469](https://github.com/windmill-labs/windmill/issues/11469)) ([88d0cd0](https://github.com/windmill-labs/windmill/commit/88d0cd00e54de7dd9b008f6f233992cb6ae69571))
|
||||
* stop running deleted script versions from worker caches ([#11456](https://github.com/windmill-labs/windmill/issues/11456)) ([d2830cb](https://github.com/windmill-labs/windmill/commit/d2830cb4a0effc369aa5dbebdb32e770a2c87e18))
|
||||
|
||||
## [1.820.0](https://github.com/windmill-labs/windmill/compare/v1.819.0...v1.820.0) (2026-09-29)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
# crane: pulls + flattens images for the sandboxed container runtime (`# sandbox <image>`).
|
||||
# Single static binary — no daemon/store/root needed. See docs/docker-v2-runtime.md.
|
||||
ARG CRANE_VERSION=v0.21.7
|
||||
ARG CRANE_VERSION=v0.22.1
|
||||
RUN arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in amd64) crane_arch=x86_64 ;; arm64) crane_arch=arm64 ;; *) echo >&2 "error: unsupported arch '$arch' for crane"; exit 1 ;; esac; \
|
||||
wget -O /tmp/crane.tgz "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_${crane_arch}.tar.gz" \
|
||||
|
||||
Generated
+89
-89
@@ -7180,9 +7180,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
||||
dependencies = [
|
||||
"spin 0.9.9",
|
||||
]
|
||||
@@ -9321,9 +9321,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113"
|
||||
|
||||
[[package]]
|
||||
name = "pinky-swear"
|
||||
@@ -9870,9 +9870,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.18"
|
||||
version = "0.11.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc"
|
||||
checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
@@ -9893,9 +9893,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||
checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -14792,7 +14792,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -14881,7 +14881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-ai"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -14915,7 +14915,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14928,7 +14928,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15068,7 +15068,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15091,7 +15091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15108,7 +15108,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15134,7 +15134,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -15144,7 +15144,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15161,7 +15161,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"base64 0.22.1",
|
||||
@@ -15183,7 +15183,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15206,7 +15206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15222,7 +15222,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15244,7 +15244,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15266,7 +15266,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15280,7 +15280,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15316,7 +15316,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15341,7 +15341,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15369,7 +15369,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15391,7 +15391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15411,7 +15411,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15453,7 +15453,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15482,7 +15482,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -15494,7 +15494,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.8.9",
|
||||
@@ -15518,7 +15518,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15532,7 +15532,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -15571,7 +15571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -15585,7 +15585,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -15604,7 +15604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aho-corasick",
|
||||
@@ -15717,7 +15717,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures",
|
||||
@@ -15737,7 +15737,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -15754,7 +15754,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -15781,7 +15781,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -15798,7 +15798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -15814,7 +15814,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15835,7 +15835,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15866,7 +15866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -15891,7 +15891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -15927,7 +15927,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -15945,7 +15945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -15954,7 +15954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15966,7 +15966,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15978,7 +15978,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -15990,7 +15990,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16002,7 +16002,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16014,7 +16014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -16025,7 +16025,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16036,7 +16036,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16048,7 +16048,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -16059,7 +16059,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16081,7 +16081,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16093,7 +16093,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16107,7 +16107,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -16124,7 +16124,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16137,7 +16137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -16149,7 +16149,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16167,7 +16167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -16183,7 +16183,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -16199,7 +16199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16213,7 +16213,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16252,7 +16252,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -16292,7 +16292,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -16303,7 +16303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16338,7 +16338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16362,7 +16362,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16395,7 +16395,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-amqp"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16422,7 +16422,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-azure"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16455,7 +16455,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16475,7 +16475,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16509,7 +16509,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16546,7 +16546,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16569,7 +16569,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16593,7 +16593,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16617,7 +16617,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16652,7 +16652,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16680,7 +16680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16705,7 +16705,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.2",
|
||||
@@ -16724,7 +16724,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -16842,7 +16842,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker-volumes"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
@@ -17539,9 +17539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
|
||||
checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -88,7 +88,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"github": {
|
||||
"auth_url": "https://github.com/login/oauth/authorize",
|
||||
"token_url": "https://github.com/login/oauth/access_token",
|
||||
"scopes": ["workflow", "repo"]
|
||||
"scopes": ["workflow", "repo"],
|
||||
"scope_options": ["repo", "workflow", "gist", "read:org"]
|
||||
},
|
||||
"gitlab": {
|
||||
"auth_url": "https://gitlab.com/oauth/authorize",
|
||||
@@ -43,6 +44,22 @@
|
||||
"mpim:read",
|
||||
"files:read"
|
||||
],
|
||||
"scope_options": [
|
||||
{ "value": "reactions:write", "label": "Add emoji reactions" },
|
||||
{ "value": "channels:manage", "label": "Create, archive and manage public channels, invite members, set topics" },
|
||||
{ "value": "groups:write", "label": "Create and manage private channels the app is in" },
|
||||
{ "value": "usergroups:write", "label": "Edit user groups (workspace settings often block bots)" }
|
||||
],
|
||||
"user_scope_options": [
|
||||
{ "value": "reactions:write", "label": "Add emoji reactions" },
|
||||
{ "value": "channels:write", "label": "Create, archive and manage public channels" },
|
||||
{ "value": "channels:write.invites", "label": "Invite members to public channels" },
|
||||
{ "value": "channels:write.topic", "label": "Set public channel topics" },
|
||||
{ "value": "groups:write", "label": "Create and manage private channels" },
|
||||
{ "value": "users.profile:write", "label": "Set your status and edit your profile" },
|
||||
{ "value": "reminders:write", "label": "Create reminders for yourself" },
|
||||
{ "value": "usergroups:write", "label": "Edit user groups" }
|
||||
],
|
||||
"token_response_path": "authed_user"
|
||||
},
|
||||
"supabase_wizard": {
|
||||
@@ -100,6 +117,9 @@
|
||||
"scope_options": [
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.events", "label": "Read and edit events on all calendars" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.events.readonly", "label": "Read events on all calendars" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.calendarlist.readonly", "label": "See the list of calendars you're subscribed to" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.calendars.readonly", "label": "Read calendar properties (title, timezone)" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.freebusy", "label": "See your availability (free/busy)" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar.readonly", "label": "Read all calendars and events" },
|
||||
{ "value": "https://www.googleapis.com/auth/calendar", "label": "Read, edit, share and delete all calendars" }
|
||||
],
|
||||
|
||||
+24
-24
@@ -6216,7 +6216,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -6302,7 +6302,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6314,7 +6314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"serde",
|
||||
@@ -6323,7 +6323,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6335,7 +6335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6347,7 +6347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -6359,7 +6359,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6371,7 +6371,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6383,7 +6383,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -6394,7 +6394,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6405,7 +6405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6417,7 +6417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6428,7 +6428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -6450,7 +6450,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6462,7 +6462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6476,7 +6476,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case",
|
||||
@@ -6493,7 +6493,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6506,7 +6506,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6518,7 +6518,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6536,7 +6536,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -6552,7 +6552,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6568,7 +6568,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.17",
|
||||
@@ -6600,7 +6600,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6614,7 +6614,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
members = ["."]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
edition = "2021"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
|
||||
|
||||
@@ -232,3 +232,77 @@ async fn test_ai_proxy_x_resource_path_enforces_rls(db: Pool<Postgres>) -> anyho
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An unsaved resource value sent in X-Resource-Value is what the upstream is called
|
||||
/// with, and a scoped token cannot use it to read a variable outside its scopes.
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_ai_proxy_x_resource_value(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use axum::{http::HeaderMap, http::StatusCode, routing::post, Json, Router};
|
||||
|
||||
initialize_tracing().await;
|
||||
std::env::set_var("ALLOW_PRIVATE_AI_BASE_URLS", "true");
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Only answers the key carried by the header, so another credential source fails.
|
||||
let app = Router::new().fallback(post(|headers: HeaderMap| async move {
|
||||
if headers.get("authorization").and_then(|v| v.to_str().ok()) == Some("Bearer sk-unsaved") {
|
||||
Ok(Json(json!({
|
||||
"id": "chatcmpl-test",
|
||||
"object": "chat.completion",
|
||||
"choices": [{"message": {"role": "assistant", "content": "hello"}}]
|
||||
})))
|
||||
} else {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}));
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
|
||||
let base_url = format!("http://127.0.0.1:{}/v1", listener.local_addr()?.port());
|
||||
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO token (token_hash, token_prefix, token, email, label, super_admin, scopes) \
|
||||
VALUES (encode(sha256('SCOPED_AI_TOKEN'::bytea), 'hex'), 'SCOPED_AI_', 'SCOPED_AI_TOKEN', \
|
||||
'test@windmill.dev', 'scoped', true, ARRAY['ai:write', 'resources:write'])",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let send = |api_key: &'static str, token: &'static str| {
|
||||
let value = json!({ "api_key": api_key, "base_url": base_url }).to_string();
|
||||
let encoded: String = value.bytes().map(|b| format!("%{b:02X}")).collect();
|
||||
authed_with(
|
||||
client()
|
||||
.post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/ai/proxy/chat/completions"
|
||||
))
|
||||
.header("X-Provider", "openai")
|
||||
.header("X-Resource-Value", encoded)
|
||||
.json(&json!({
|
||||
"model": "gpt-4",
|
||||
"messages": [{"role": "user", "content": "hi"}]
|
||||
})),
|
||||
token,
|
||||
)
|
||||
.send()
|
||||
};
|
||||
|
||||
let resp = send("sk-unsaved", "SECRET_TOKEN").await?;
|
||||
assert_2xx(
|
||||
resp.status().as_u16(),
|
||||
&resp.text().await?,
|
||||
"X-Resource-Value with an unsaved resource",
|
||||
);
|
||||
|
||||
let resp = send("sk-other", "SECRET_TOKEN").await?;
|
||||
assert!(resp.status().as_u16() >= 400, "a different key must reach the upstream as is");
|
||||
|
||||
let resp = send("$var:u/test-user/secret", "SCOPED_AI_TOKEN").await?;
|
||||
let body = resp.text().await?;
|
||||
assert!(
|
||||
body.contains("variables:read:u/test-user/secret"),
|
||||
"a token without variables:read must not resolve a $var: reference, got: {body}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use windmill_common::{
|
||||
global_settings::{
|
||||
load_value_from_global_settings, NPMRC_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
},
|
||||
utils::{parse_npmrc_registry, StripPath},
|
||||
utils::{parse_npmrc_registry, RegistryAuth, StripPath},
|
||||
};
|
||||
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
@@ -149,7 +149,7 @@ pub fn workspaced_service() -> Router {
|
||||
|
||||
fn build_registry_request(
|
||||
url: &str,
|
||||
auth_token: &Option<String>,
|
||||
auth: &Option<RegistryAuth>,
|
||||
registry_base_url: &str,
|
||||
) -> Result<reqwest::RequestBuilder> {
|
||||
let parsed_url =
|
||||
@@ -166,8 +166,17 @@ fn build_registry_request(
|
||||
}
|
||||
|
||||
let mut req = HTTP_CLIENT.get(url);
|
||||
if let Some(token) = auth_token {
|
||||
req = req.bearer_auth(token);
|
||||
// A packument may point a tarball at another port or plain http on the same host;
|
||||
// credentials, often a real password, only go to the registry's own origin.
|
||||
if parsed_url.origin() != parsed_base.origin() {
|
||||
return Ok(req);
|
||||
}
|
||||
match auth {
|
||||
Some(RegistryAuth::Bearer(token)) => req = req.bearer_auth(token),
|
||||
Some(RegistryAuth::Basic(credentials)) => {
|
||||
req = req.header(header::AUTHORIZATION, format!("Basic {credentials}"))
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Ok(req)
|
||||
}
|
||||
@@ -308,12 +317,12 @@ lazy_static::lazy_static! {
|
||||
async fn fetch_package_json(
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
package: &str,
|
||||
) -> Result<(Arc<JsonValue>, String, Option<String>)> {
|
||||
let (registry_url, auth_token) = get_npm_registry(db)
|
||||
) -> Result<(Arc<JsonValue>, String, Option<RegistryAuth>)> {
|
||||
let (registry_url, auth) = get_npm_registry(db)
|
||||
.await?
|
||||
.ok_or_else(|| Error::BadRequest("No private npm registry configured".to_string()))?;
|
||||
let document = fetch_package_json_from(®istry_url, &auth_token, package).await?;
|
||||
Ok((document, registry_url, auth_token))
|
||||
let document = fetch_package_json_from(®istry_url, &auth, package).await?;
|
||||
Ok((document, registry_url, auth))
|
||||
}
|
||||
|
||||
/// The same fetch against a registry the caller has already resolved.
|
||||
@@ -325,7 +334,7 @@ async fn fetch_package_json(
|
||||
/// immutable, so that survives switching back.
|
||||
async fn fetch_package_json_from(
|
||||
registry_url: &str,
|
||||
auth_token: &Option<String>,
|
||||
auth: &Option<RegistryAuth>,
|
||||
package: &str,
|
||||
) -> Result<Arc<JsonValue>> {
|
||||
let registry_url = registry_url.to_string();
|
||||
@@ -340,7 +349,7 @@ async fn fetch_package_json_from(
|
||||
|
||||
tracing::info!("Fetching package metadata from: {}", package_url);
|
||||
|
||||
let response = build_registry_request(&package_url, auth_token, ®istry_url)?
|
||||
let response = build_registry_request(&package_url, auth, ®istry_url)?
|
||||
.header(header::ACCEPT, ABBREVIATED_PACKUMENT)
|
||||
.send()
|
||||
.await
|
||||
@@ -380,7 +389,7 @@ async fn tarball_response(
|
||||
package: &str,
|
||||
version: &str,
|
||||
registry_url: &str,
|
||||
auth_token: &Option<String>,
|
||||
auth: &Option<RegistryAuth>,
|
||||
) -> Result<reqwest::Response> {
|
||||
let tarball_url = package_json
|
||||
.get("versions")
|
||||
@@ -390,7 +399,7 @@ async fn tarball_response(
|
||||
.and_then(|t| t.as_str())
|
||||
.ok_or_else(|| Error::NotFound(format!("Tarball not found for {}@{}", package, version)))?;
|
||||
|
||||
let response = build_registry_request(tarball_url, auth_token, registry_url)?
|
||||
let response = build_registry_request(tarball_url, auth, registry_url)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Failed to download tarball: {}", e)))?;
|
||||
@@ -411,9 +420,9 @@ async fn fetch_tarball(
|
||||
package: &str,
|
||||
version: &str,
|
||||
registry_url: &str,
|
||||
auth_token: &Option<String>,
|
||||
auth: &Option<RegistryAuth>,
|
||||
) -> Result<axum::body::Bytes> {
|
||||
tarball_response(package_json, package, version, registry_url, auth_token)
|
||||
tarball_response(package_json, package, version, registry_url, auth)
|
||||
.await?
|
||||
.bytes()
|
||||
.await
|
||||
@@ -642,10 +651,10 @@ async fn get_package_file(
|
||||
Some(content) => content,
|
||||
// Not a file a type request asks for, so it was never kept: walk the archive for it
|
||||
None => {
|
||||
let (package_json, registry_url, auth_token) =
|
||||
let (package_json, registry_url, auth) =
|
||||
fetch_package_json(&db, &package).await?;
|
||||
let archive =
|
||||
fetch_tarball(&package_json, &package, &version, ®istry_url, &auth_token)
|
||||
fetch_tarball(&package_json, &package, &version, ®istry_url, &auth)
|
||||
.await?;
|
||||
let path = target.clone();
|
||||
blocking(move || read_one_entry(&archive, &path))
|
||||
@@ -668,13 +677,13 @@ async fn get_package_tarball(
|
||||
Extension(db): Extension<sqlx::Pool<sqlx::Postgres>>,
|
||||
) -> Result<Response> {
|
||||
let (package, version) = parse_package_and_version(package_version_path.to_path())?;
|
||||
let (package_json, registry_url, auth_token) = fetch_package_json(&db, &package).await?;
|
||||
let (package_json, registry_url, auth) = fetch_package_json(&db, &package).await?;
|
||||
let response = tarball_response(
|
||||
&package_json,
|
||||
&package,
|
||||
&version,
|
||||
®istry_url,
|
||||
&auth_token,
|
||||
&auth,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -693,11 +702,11 @@ async fn get_package_tarball(
|
||||
.into_response())
|
||||
}
|
||||
|
||||
/// Get the npm registry URL and optional auth token from global settings.
|
||||
/// Get the npm registry URL and optional credentials from global settings.
|
||||
/// Checks the `npmrc` setting first, then falls back to `npm_config_registry`.
|
||||
async fn get_npm_registry(
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
) -> Result<Option<(String, Option<String>)>> {
|
||||
) -> Result<Option<(String, Option<RegistryAuth>)>> {
|
||||
let npmrc = load_value_from_global_settings(db, NPMRC_SETTING)
|
||||
.await?
|
||||
.and_then(|v| v.as_str().map(|s| s.to_string()));
|
||||
@@ -716,7 +725,7 @@ async fn get_npm_registry(
|
||||
let (url, token) = if s.contains(":_authToken=") {
|
||||
let parts: Vec<&str> = s.split(":_authToken=").collect();
|
||||
let url = parts[0].to_string();
|
||||
let token = parts.get(1).map(|t| t.to_string());
|
||||
let token = parts.get(1).map(|t| RegistryAuth::Bearer(t.to_string()));
|
||||
(url, token)
|
||||
} else {
|
||||
(s.clone(), None)
|
||||
@@ -941,7 +950,7 @@ async fn cached_package(
|
||||
) -> Result<PackageFiles> {
|
||||
// Resolving the registry is a settings read; the packument behind it is a round trip,
|
||||
// and a version already on disk needs neither, so it is fetched only on a miss.
|
||||
let (registry_url, auth_token) = get_npm_registry(db)
|
||||
let (registry_url, auth) = get_npm_registry(db)
|
||||
.await?
|
||||
.ok_or_else(|| Error::BadRequest("No private npm registry configured".to_string()))?;
|
||||
|
||||
@@ -963,9 +972,9 @@ async fn cached_package(
|
||||
}
|
||||
|
||||
// The same snapshot the cache key came from, not a second read of the setting.
|
||||
let package_json = fetch_package_json_from(®istry_url, &auth_token, package).await?;
|
||||
let package_json = fetch_package_json_from(®istry_url, &auth, package).await?;
|
||||
let archive =
|
||||
fetch_tarball(&package_json, package, version, ®istry_url, &auth_token).await?;
|
||||
fetch_tarball(&package_json, package, version, ®istry_url, &auth).await?;
|
||||
|
||||
let target = dir.clone();
|
||||
let extracted = blocking(move || {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.820.0
|
||||
version: 1.821.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -1062,6 +1062,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Paths of every `$var:` reference found anywhere in a resource value.
|
||||
fn var_references(value: &serde_json::Value) -> Vec<&str> {
|
||||
match value {
|
||||
serde_json::Value::String(s) => s.strip_prefix("$var:").into_iter().collect(),
|
||||
serde_json::Value::Array(items) => items.iter().flat_map(var_references).collect(),
|
||||
serde_json::Value::Object(fields) => fields.values().flat_map(var_references).collect(),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
async fn global_proxy(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -1220,7 +1230,37 @@ async fn proxy(
|
||||
// Set when serving the request through Windmill's free AI tier (the lent key). Holds
|
||||
// the per-user concurrency lock and drives response metering.
|
||||
let mut free_lease: Option<crate::ai_free_tier_oss::FreeTierLease> = None;
|
||||
// An unsaved resource value (percent-encoded JSON), so a resource can be tested
|
||||
// before it is stored. `$var:` references resolve as the caller, and since the
|
||||
// caller also picks the base URL they are sent to, a scoped token needs the same
|
||||
// scopes as storing the resource and reading each variable would.
|
||||
let inline_resource = headers
|
||||
.get("X-Resource-Value")
|
||||
.map(|v| {
|
||||
let invalid = |e: String| Error::BadRequest(format!("Invalid X-Resource-Value: {e}"));
|
||||
let decoded =
|
||||
urlencoding::decode(v.to_str().unwrap_or("")).map_err(|e| invalid(e.to_string()))?;
|
||||
let value = serde_json::from_str::<serde_json::Value>(&decoded)
|
||||
.map_err(|e| invalid(e.to_string()))?;
|
||||
check_scopes(&authed, || "resources:write".to_string())?;
|
||||
for path in var_references(&value) {
|
||||
check_scopes(&authed, || format!("variables:read:{path}"))?;
|
||||
}
|
||||
serde_json::from_value::<AIResource>(value).map_err(|e| invalid(e.to_string()))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let mut credentials = 'cred: {
|
||||
if let Some(resource) = inline_resource {
|
||||
break 'cred resolve_provider_credentials(
|
||||
&provider,
|
||||
&db,
|
||||
&w_id,
|
||||
resource,
|
||||
Some(&authed),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
match workspace_cache {
|
||||
Some(request_cache)
|
||||
if !request_cache.is_expired() && forced_resource_path.is_none() =>
|
||||
|
||||
@@ -2206,11 +2206,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse .npmrc content to extract the default registry URL and its auth token.
|
||||
/// Returns `Some((registry_url, Option<auth_token>))` if a default registry is found.
|
||||
pub fn parse_npmrc_registry(npmrc_content: &str) -> Option<(String, Option<String>)> {
|
||||
/// Credentials for a registry, as an `Authorization` header expects them.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RegistryAuth {
|
||||
Bearer(String),
|
||||
/// Already base64 encoded `user:password`.
|
||||
Basic(String),
|
||||
}
|
||||
|
||||
/// Parse .npmrc content to extract the default registry URL and its credentials.
|
||||
/// Returns `Some((registry_url, Option<auth>))` if a default registry is found.
|
||||
///
|
||||
/// Credentials follow npm's precedence for a registry: `_authToken`, then `_auth`, then
|
||||
/// `username` with a base64 encoded `_password`.
|
||||
pub fn parse_npmrc_registry(npmrc_content: &str) -> Option<(String, Option<RegistryAuth>)> {
|
||||
use base64::{
|
||||
alphabet,
|
||||
engine::{
|
||||
general_purpose::STANDARD, DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig,
|
||||
},
|
||||
Engine,
|
||||
};
|
||||
|
||||
let mut registry_url: Option<String> = None;
|
||||
let mut auth_tokens: Vec<(String, String)> = Vec::new();
|
||||
let mut scoped_keys: Vec<(String, String, String)> = Vec::new();
|
||||
|
||||
for line in npmrc_content.lines() {
|
||||
let line = line.trim();
|
||||
@@ -2223,8 +2242,15 @@ pub fn parse_npmrc_registry(npmrc_content: &str) -> Option<(String, Option<Strin
|
||||
}
|
||||
|
||||
if line.starts_with("//") {
|
||||
if let Some((prefix, token)) = line.split_once(":_authToken=") {
|
||||
auth_tokens.push((prefix.to_string(), token.to_string()));
|
||||
if let Some((prefix, key, value)) = line
|
||||
.split_once('=')
|
||||
.and_then(|(lhs, value)| lhs.rsplit_once(':').map(|(p, k)| (p, k, value)))
|
||||
{
|
||||
scoped_keys.push((
|
||||
prefix.trim_end_matches('/').to_string(),
|
||||
key.trim().to_string(),
|
||||
value.trim().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2233,20 +2259,65 @@ pub fn parse_npmrc_registry(npmrc_content: &str) -> Option<(String, Option<Strin
|
||||
let url_without_protocol = url.trim_start_matches("https:").trim_start_matches("http:");
|
||||
let url_prefix = url_without_protocol.trim_end_matches('/');
|
||||
|
||||
let token = auth_tokens
|
||||
.iter()
|
||||
.find(|(prefix, _)| {
|
||||
let p = prefix.trim_end_matches('/');
|
||||
p == url_prefix
|
||||
})
|
||||
.map(|(_, token)| token.clone());
|
||||
// As in npm's config, the last assignment wins, and an empty one unsets the key.
|
||||
let get = |prefix: &str, key: &str| {
|
||||
scoped_keys
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(p, k, _)| p == prefix && k == key)
|
||||
.map(|(_, _, value)| value.clone())
|
||||
.filter(|value| !value.is_empty())
|
||||
};
|
||||
let auth_at = |prefix: &str| {
|
||||
if let Some(token) = get(prefix, "_authToken") {
|
||||
Some(RegistryAuth::Bearer(token))
|
||||
} else if let Some(auth) = get(prefix, "_auth") {
|
||||
Some(RegistryAuth::Basic(auth))
|
||||
} else if let (Some(username), Some(password)) =
|
||||
(get(prefix, "username"), get(prefix, "_password"))
|
||||
{
|
||||
let lenient = GeneralPurpose::new(
|
||||
&alphabet::STANDARD,
|
||||
GeneralPurposeConfig::new()
|
||||
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
||||
);
|
||||
match lenient
|
||||
.decode(&password)
|
||||
.ok()
|
||||
.and_then(|p| String::from_utf8(p).ok())
|
||||
{
|
||||
Some(password) => Some(RegistryAuth::Basic(
|
||||
STANDARD.encode(format!("{username}:{password}")),
|
||||
)),
|
||||
None => {
|
||||
tracing::warn!("npmrc _password for {prefix} is not base64, ignoring it");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Some((url, token))
|
||||
// Like npm, the deepest path of the registry URL that carries credentials supplies them,
|
||||
// walking up to the bare host.
|
||||
let mut prefix = url_prefix;
|
||||
let auth = loop {
|
||||
if let Some(auth) = auth_at(prefix) {
|
||||
break Some(auth);
|
||||
}
|
||||
match prefix.rfind('/') {
|
||||
Some(i) if i > 1 => prefix = &prefix[..i],
|
||||
_ => break None,
|
||||
}
|
||||
};
|
||||
|
||||
Some((url, auth))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod npmrc_tests {
|
||||
use super::parse_npmrc_registry;
|
||||
use super::{parse_npmrc_registry, RegistryAuth};
|
||||
|
||||
#[test]
|
||||
fn test_parse_simple_registry() {
|
||||
@@ -2256,7 +2327,7 @@ mod npmrc_tests {
|
||||
result,
|
||||
Some((
|
||||
"https://registry.mycompany.com/".to_string(),
|
||||
Some("secret123".to_string())
|
||||
Some(RegistryAuth::Bearer("secret123".to_string()))
|
||||
))
|
||||
);
|
||||
}
|
||||
@@ -2287,11 +2358,69 @@ mod npmrc_tests {
|
||||
result,
|
||||
Some((
|
||||
"https://r.example.com/".to_string(),
|
||||
Some("tok".to_string())
|
||||
Some(RegistryAuth::Bearer("tok".to_string()))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_username_password() {
|
||||
// "cGFzcw==" is base64("pass"); the header value is base64("user:pass")
|
||||
let npmrc = "registry=https://pkgs.dev.azure.com/org/_packaging/feed/npm/registry/\n\
|
||||
//pkgs.dev.azure.com/org/_packaging/feed/npm/registry/:username=user\n\
|
||||
//pkgs.dev.azure.com/org/_packaging/feed/npm/registry/:_password=cGFzcw==\n\
|
||||
//pkgs.dev.azure.com/org/_packaging/feed/npm/registry/:email=npm requires email\n";
|
||||
let (_, auth) = parse_npmrc_registry(npmrc).unwrap();
|
||||
assert_eq!(auth, Some(RegistryAuth::Basic("dXNlcjpwYXNz".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_auth_and_precedence() {
|
||||
let base = "registry=https://r.example.com/\n";
|
||||
let auth = "//r.example.com/:_auth=dXNlcjpwYXNz\n";
|
||||
let user_pass = "//r.example.com/:username=other\n//r.example.com/:_password=eA==\n";
|
||||
let token = "//r.example.com:_authToken=tok\n";
|
||||
|
||||
let parse = |s: String| parse_npmrc_registry(&s).unwrap().1;
|
||||
assert_eq!(
|
||||
parse(format!("{base}{auth}")),
|
||||
Some(RegistryAuth::Basic("dXNlcjpwYXNz".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse(format!("{base}{user_pass}{auth}")),
|
||||
Some(RegistryAuth::Basic("dXNlcjpwYXNz".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse(format!("{base}{auth}{user_pass}{token}")),
|
||||
Some(RegistryAuth::Bearer("tok".to_string()))
|
||||
);
|
||||
// Credentials for another registry are not sent to the default one
|
||||
assert_eq!(parse(format!("{base}//other.example.com/:_auth=x\n")), None);
|
||||
// An empty value is unset, and a later line overrides an earlier one
|
||||
assert_eq!(
|
||||
parse(format!(
|
||||
"{base}//r.example.com/:_authToken=\n{auth}//r.example.com/:_auth=y\n"
|
||||
)),
|
||||
Some(RegistryAuth::Basic("y".to_string()))
|
||||
);
|
||||
// An empty assignment clears an earlier value rather than being skipped
|
||||
assert_eq!(
|
||||
parse(format!("{base}{auth}//r.example.com/:_auth=\n{user_pass}")),
|
||||
Some(RegistryAuth::Basic("b3RoZXI6eA==".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_auth_from_parent_path() {
|
||||
let npmrc = "registry=https://r.example.com:4873/npm/feed/\n\
|
||||
//r.example.com:4873/:_authToken=host\n\
|
||||
//r.example.com:4873/npm/:username=user\n\
|
||||
//r.example.com:4873/npm/:_password=cGFzcw\n";
|
||||
// The deepest path with credentials wins; the unpadded password still decodes
|
||||
let (_, auth) = parse_npmrc_registry(npmrc).unwrap();
|
||||
assert_eq!(auth, Some(RegistryAuth::Basic("dXNlcjpwYXNz".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty_npmrc() {
|
||||
assert_eq!(parse_npmrc_registry(""), None);
|
||||
|
||||
@@ -92,8 +92,9 @@ pub struct OAuthConfig {
|
||||
/// The registry JSON may also carry frontend-only keys for the connect
|
||||
/// dialog, deliberately not modelled here: `scope_options`, a scope pick
|
||||
/// list, `resource_fields`, the fields of the resource type the dialog
|
||||
/// asks for once the token is in (Snowflake's database and warehouse), and
|
||||
/// `user_scopes`, Slack's user-token scopes, sent as `user_scope`.
|
||||
/// asks for once the token is in (Snowflake's database and warehouse),
|
||||
/// `user_scopes`, Slack's user-token scopes, sent as `user_scope`, and
|
||||
/// `user_scope_options`, the pick list offered alongside `user_scopes`.
|
||||
pub scopes: Option<Vec<String>>,
|
||||
/// Default scopes for the client-credentials (2-legged) flow. These differ
|
||||
/// from the authorization-code `scopes` for most providers (member/consent
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
|
||||
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
|
||||
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
|
||||
|
||||
export const VERSION = "v1.820.0";
|
||||
export const VERSION = "v1.821.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-chat",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-chat",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@ai-sdk/react": "^4.0.102",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-chat",
|
||||
"description": "Build chat interfaces on Windmill flows deployed in chat mode, from any frontend or raw app",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://github.com/windmill-labs/windmill/tree/main/chat-sdk#readme",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { psql as psqlDatatable } from "./psql.ts";
|
||||
import { serve as serveDatatable } from "./serve.ts";
|
||||
import {
|
||||
createMigration,
|
||||
listLocalMigrations,
|
||||
pushLocalMigrations,
|
||||
rollbackMigrations,
|
||||
runMigrations,
|
||||
@@ -83,6 +84,59 @@ async function migrateDown(opts: GlobalOptions & { datatable?: string }) {
|
||||
await rollbackMigrations(workspace.workspaceId, dt);
|
||||
}
|
||||
|
||||
async function migrateStatus(
|
||||
opts: GlobalOptions & { datatable?: string; json?: boolean },
|
||||
) {
|
||||
if (opts.json) log.setSilent(true);
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const dt = opts.datatable ?? DEFAULT_DATATABLE_NAME;
|
||||
const status = await wmill.getDatatableMigrationsStatus({
|
||||
workspace: workspace.workspaceId,
|
||||
datatableName: dt,
|
||||
});
|
||||
// `migrate up` pushes local files before running, so they count as pending too.
|
||||
const remote = new Set(status.migrations.map((m) => m.timestamp));
|
||||
const migrations: { timestamp: number; name: string; status: string }[] = [
|
||||
...status.migrations,
|
||||
...listLocalMigrations(dt)
|
||||
.filter((m) => !remote.has(m.timestamp))
|
||||
.map((m) => ({ ...m, status: "local" })),
|
||||
].sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({ ...status, migrations }));
|
||||
return;
|
||||
}
|
||||
if (!status.enabled) {
|
||||
log.info(`Migrations are not enabled on datatable '${dt}'`);
|
||||
return;
|
||||
}
|
||||
if (status.error) {
|
||||
log.warn(`Could not read applied migrations on '${dt}': ${status.error}`);
|
||||
}
|
||||
if (migrations.length === 0) {
|
||||
log.info(`No migrations on datatable '${dt}'`);
|
||||
return;
|
||||
}
|
||||
const label: Record<string, string> = {
|
||||
not_run: "pending",
|
||||
local: "pending (not pushed)",
|
||||
};
|
||||
new Table()
|
||||
.header(["Timestamp", "Name", "Status"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
migrations.map((m) => [
|
||||
String(m.timestamp),
|
||||
m.name,
|
||||
label[m.status] ?? m.status,
|
||||
]),
|
||||
)
|
||||
.render();
|
||||
}
|
||||
|
||||
const migrateCommand = new Command()
|
||||
.description("manage datatable migrations")
|
||||
.command("new", "scaffold a new migration (.up.sql / .down.sql files)")
|
||||
@@ -109,7 +163,17 @@ const migrateCommand = new Command()
|
||||
"-d --datatable <datatable:string>",
|
||||
"Target datatable (default: main)",
|
||||
)
|
||||
.action(migrateDown as any);
|
||||
.action(migrateDown as any)
|
||||
.command(
|
||||
"status",
|
||||
"show applied and pending migrations on the main datatable (or one via --datatable)",
|
||||
)
|
||||
.option(
|
||||
"-d --datatable <datatable:string>",
|
||||
"Target datatable (default: main)",
|
||||
)
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(migrateStatus as any);
|
||||
|
||||
type DataTableResourceType = "postgresql" | "instance" | "external_instance";
|
||||
|
||||
|
||||
@@ -242,6 +242,23 @@ export async function pushMigrationFromDisk(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The local migrations of a data table, identified by their `.up.sql` file (the
|
||||
* up file is mandatory); files only deleted locally are deliberately ignored.
|
||||
*/
|
||||
export function listLocalMigrations(
|
||||
datatableName: string,
|
||||
): { timestamp: number; name: string }[] {
|
||||
const dir = path.join(process.cwd(), MIGRATIONS_DIR, datatableName);
|
||||
if (!fs.existsSync(dir)) return [];
|
||||
const local: { timestamp: number; name: string }[] = [];
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
const m = file.match(/^(\d+)_(.*)\.up\.sql$/);
|
||||
if (m) local.push({ timestamp: Number(m[1]), name: m[2] });
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert the on-disk migrations of a data table to the workspace, so a freshly
|
||||
* created migration file works with `wmill datatable migrate up` even without a
|
||||
@@ -254,15 +271,7 @@ export async function pushLocalMigrations(
|
||||
datatableName: string,
|
||||
): Promise<void> {
|
||||
const dir = path.join(process.cwd(), MIGRATIONS_DIR, datatableName);
|
||||
if (!fs.existsSync(dir)) return;
|
||||
|
||||
// Local migrations are identified by their `.up.sql` file (the up file is
|
||||
// mandatory); this deliberately ignores files that were only deleted locally.
|
||||
const local: { timestamp: number; name: string }[] = [];
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
const m = file.match(/^(\d+)_(.*)\.up\.sql$/);
|
||||
if (m) local.push({ timestamp: Number(m[1]), name: m[2] });
|
||||
}
|
||||
const local = listLocalMigrations(datatableName);
|
||||
if (local.length === 0) return;
|
||||
|
||||
const remote = await wmill.listDatatableMigrations({ workspace });
|
||||
|
||||
@@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork";
|
||||
// (e.g. utils.ts) can read it without importing main.ts and creating a circular
|
||||
// dependency (main → workspace → utils → main) that triggers a TDZ.
|
||||
// Re-exported from main.ts for backwards compatibility.
|
||||
export const VERSION = "1.820.0";
|
||||
export const VERSION = "1.821.0";
|
||||
|
||||
Generated
+3
@@ -7916,6 +7916,9 @@ datatable related commands
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`datatable migrate down\` - roll back the most recent migration on the main datatable (or one via --datatable)
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`datatable migrate status\` - show applied and pending migrations on the main datatable (or one via --datatable)
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`datatable create [name:string]\` - register a datatable database in the workspace (default: instance-backed 'main') so scripts can use datatable://<name>
|
||||
- \`--resource <resource:string>\` - Back the datatable with an existing postgresql resource path instead of the instance database
|
||||
- \`--force\` - Allow adding to a workspace that already has datatables (fork metadata on existing ones is not preserved)
|
||||
|
||||
@@ -96,7 +96,7 @@ COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
# crane: pulls + flattens images for the sandboxed container runtime (`# sandbox <image>`).
|
||||
# Single static binary — no daemon/store/root needed. See docs/docker-v2-runtime.md.
|
||||
ARG CRANE_VERSION=v0.21.7
|
||||
ARG CRANE_VERSION=v0.22.1
|
||||
RUN arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in amd64) crane_arch=x86_64 ;; arm64) crane_arch=arm64 ;; *) echo >&2 "error: unsupported arch '$arch' for crane"; exit 1 ;; esac; \
|
||||
wget -O /tmp/crane.tgz "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_${crane_arch}.tar.gz" \
|
||||
|
||||
@@ -96,7 +96,7 @@ COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
# crane: pulls + flattens images for the sandboxed container runtime (`# sandbox <image>`).
|
||||
# Single static binary — no daemon/store/root needed. See docs/docker-v2-runtime.md.
|
||||
ARG CRANE_VERSION=v0.21.7
|
||||
ARG CRANE_VERSION=v0.22.1
|
||||
RUN arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in amd64) crane_arch=x86_64 ;; arm64) crane_arch=arm64 ;; *) echo >&2 "error: unsupported arch '$arch' for crane"; exit 1 ;; esac; \
|
||||
wget -O /tmp/crane.tgz "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_${crane_arch}.tar.gz" \
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import TestConnection from './TestConnection.svelte'
|
||||
import TestAiKey from './copilot/TestAIKey.svelte'
|
||||
import { aiResourceProvider } from './copilot/aiResourceProvider'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
import Popover from './meltComponents/Popover.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -51,6 +54,8 @@
|
||||
onSynced = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let aiProvider = $derived(aiResourceProvider(resourceType))
|
||||
|
||||
let schema = $state(emptySchema())
|
||||
let notFound = $state(false)
|
||||
|
||||
@@ -180,6 +185,14 @@
|
||||
isFileset={resourceTypeInfo?.is_fileset ?? false}
|
||||
/>
|
||||
<TestConnection {resourceType} {args} />
|
||||
{#if aiProvider}
|
||||
<TestAiKey
|
||||
{aiProvider}
|
||||
workspace={workspace ?? $operatingWorkspace}
|
||||
resourceValue={args}
|
||||
model={$copilotInfo.aiModels.find((m) => m.provider === aiProvider)?.model}
|
||||
/>
|
||||
{/if}
|
||||
{#if resourceType == 'postgresql'}
|
||||
<Popover
|
||||
floatingConfig={{
|
||||
|
||||
@@ -278,7 +278,9 @@
|
||||
let scopeOptions = $derived(
|
||||
useClientCredentials
|
||||
? defaultCcScopes()
|
||||
: [...(registryEntry()?.scope_options ?? []), ...instanceScopes]
|
||||
: useUserToken
|
||||
? [...(registryEntry()?.user_scope_options ?? []), ...(registryEntry()?.user_scopes ?? [])]
|
||||
: [...(registryEntry()?.scope_options ?? []), ...instanceScopes]
|
||||
)
|
||||
|
||||
/** Their slice of the resource type's schema, so they render with the type's own
|
||||
@@ -334,6 +336,10 @@
|
||||
ccInstance = ''
|
||||
tokenUrl = ''
|
||||
scopes = []
|
||||
if (description === generatedDescription) {
|
||||
description = ''
|
||||
}
|
||||
generatedDescription = ''
|
||||
}
|
||||
|
||||
/** Default scopes for the client-credentials grant. Registry providers use
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
import FilesetEditor from './FilesetEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import TestConnection from './TestConnection.svelte'
|
||||
import TestAiKey from './copilot/TestAIKey.svelte'
|
||||
import { aiResourceProvider } from './copilot/aiResourceProvider'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
import GfmMarkdown from './GfmMarkdown.svelte'
|
||||
import TestTriggerConnection from './triggers/TestTriggerConnection.svelte'
|
||||
import GitHubAppIntegration from './GitHubAppIntegration.svelte'
|
||||
@@ -85,6 +88,7 @@
|
||||
}: Props = $props()
|
||||
|
||||
let ws = $derived(workspace ?? $operatingWorkspace)
|
||||
let aiProvider = $derived(aiResourceProvider(resource_type))
|
||||
|
||||
let rawCode: string | undefined = $state(undefined)
|
||||
let textFileContent: string = $state('')
|
||||
@@ -225,6 +229,14 @@
|
||||
workspaceOverride={workspace}
|
||||
/>
|
||||
{/if}
|
||||
{#if aiProvider}
|
||||
<TestAiKey
|
||||
{aiProvider}
|
||||
workspace={ws}
|
||||
resourceValue={args}
|
||||
model={$copilotInfo.aiModels.find((m) => m.provider === aiProvider)?.model}
|
||||
/>
|
||||
{/if}
|
||||
{#if resource_type === 'git_repository' && ws && (actingUser?.is_admin || actingUser?.is_super_admin)}
|
||||
<GitHubAppIntegration
|
||||
resourceType={resource_type}
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { testKey } from './lib'
|
||||
import TextInput from '../text_input/TextInput.svelte'
|
||||
import { AI_PROVIDERS, testKey } from './lib'
|
||||
import { untrack } from 'svelte'
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean
|
||||
apiKey?: string | undefined
|
||||
workspace?: string | undefined
|
||||
resourcePath?: string | undefined
|
||||
/** An unsaved resource value to test as is, instead of a stored resource. */
|
||||
resourceValue?: Record<string, any> | undefined
|
||||
aiProvider: AIProvider
|
||||
model: string
|
||||
model?: string | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -18,50 +22,69 @@
|
||||
apiKey = undefined,
|
||||
workspace = undefined,
|
||||
resourcePath = undefined,
|
||||
resourceValue = undefined,
|
||||
aiProvider,
|
||||
model
|
||||
model = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let loading = $state(false)
|
||||
// For an unsaved resource, or without a model from the caller, the model is only a
|
||||
// guess: deployment-named providers (Azure, Bedrock, custom endpoints) reject a model
|
||||
// the account lacks, which would read as an invalid key, so the guess stays editable.
|
||||
let typedModel = $state(
|
||||
untrack(() => model ?? AI_PROVIDERS[aiProvider]?.defaultModels[0] ?? '')
|
||||
)
|
||||
let modelEditable = $derived(!!resourceValue || !model)
|
||||
let testedModel = $derived(modelEditable ? typedModel.trim() : model)
|
||||
</script>
|
||||
|
||||
<Button
|
||||
unifiedSize="md"
|
||||
variant="default"
|
||||
{disabled}
|
||||
{loading}
|
||||
on:click={async () => {
|
||||
loading = true
|
||||
try {
|
||||
const abortController = new AbortController()
|
||||
setTimeout(() => {
|
||||
abortController.abort()
|
||||
}, 10000)
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
{#if modelEditable}
|
||||
<TextInput
|
||||
bind:value={typedModel}
|
||||
size="md"
|
||||
inputProps={{ placeholder: 'Model to test', 'aria-label': 'Model to test' }}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
unifiedSize="md"
|
||||
variant="default"
|
||||
disabled={disabled || !testedModel}
|
||||
{loading}
|
||||
onClick={async () => {
|
||||
loading = true
|
||||
try {
|
||||
const abortController = new AbortController()
|
||||
setTimeout(() => {
|
||||
abortController.abort()
|
||||
}, 10000)
|
||||
|
||||
await testKey({
|
||||
apiKey,
|
||||
workspace,
|
||||
resourcePath,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: "this is a test, simply reply with 'ok'"
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider,
|
||||
model
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
} catch (err) {
|
||||
if (err.message === 'Request was aborted.') {
|
||||
sendUserToast('Could not validate key within 10s', true)
|
||||
} else {
|
||||
sendUserToast(`Invalid key: ${err}`, true)
|
||||
await testKey({
|
||||
apiKey,
|
||||
workspace,
|
||||
resourcePath,
|
||||
resourceValue,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: "this is a test, simply reply with 'ok'"
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider,
|
||||
model: testedModel
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
} catch (err) {
|
||||
if (err.message === 'Request was aborted.') {
|
||||
sendUserToast('Could not validate key within 10s', true)
|
||||
} else {
|
||||
sendUserToast(`Invalid key: ${err}`, true)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}}
|
||||
>Test key
|
||||
</Button>
|
||||
}}
|
||||
>Test key
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
import { AI_PROVIDERS } from './lib'
|
||||
|
||||
/** The AI provider a resource of this type holds credentials for, if any. */
|
||||
export function aiResourceProvider(resourceType: string | undefined): AIProvider | undefined {
|
||||
if (resourceType === 'openai_client_credentials_oauth') return 'openai'
|
||||
return resourceType && Object.hasOwn(AI_PROVIDERS, resourceType)
|
||||
? (resourceType as AIProvider)
|
||||
: undefined
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
getAiProxyBaseURL,
|
||||
getProviderAndCompletionConfig,
|
||||
providerSupportsWebSearch,
|
||||
testedCredentialHeaders,
|
||||
usesGlobalAiProxy,
|
||||
workspaceAIClients
|
||||
} from '../lib'
|
||||
import { applyReasoningToConfig } from '../reasoningRegistry'
|
||||
@@ -624,6 +626,7 @@ export async function getNonStreamingOpenAIResponsesCompletion(
|
||||
apiKey?: string
|
||||
workspace?: string
|
||||
resourcePath?: string
|
||||
resourceValue?: Record<string, any>
|
||||
forceModelProvider?: AIProviderModel
|
||||
maxTokensCap?: number
|
||||
}
|
||||
@@ -645,23 +648,12 @@ export async function getNonStreamingOpenAIResponsesCompletion(
|
||||
} = {
|
||||
signal: abortController.signal,
|
||||
headers: {
|
||||
'X-Provider': provider
|
||||
'X-Provider': provider,
|
||||
...testedCredentialHeaders(options ?? {})
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.resourcePath) {
|
||||
fetchOptions.headers = {
|
||||
...fetchOptions.headers,
|
||||
'X-Resource-Path': options.resourcePath
|
||||
}
|
||||
} else if (options?.apiKey) {
|
||||
fetchOptions.headers = {
|
||||
...fetchOptions.headers,
|
||||
'X-API-Key': options.apiKey
|
||||
}
|
||||
}
|
||||
|
||||
const openaiClient = options?.apiKey
|
||||
const openaiClient = usesGlobalAiProxy(options ?? {})
|
||||
? createOpenAIProxyClient(getAiProxyBaseURL())
|
||||
: options?.workspace
|
||||
? workspaceAIClients.createOpenaiClient(options.workspace)
|
||||
|
||||
@@ -581,10 +581,39 @@ class WorkspacedAIClients {
|
||||
|
||||
export const workspaceAIClients = new WorkspacedAIClients()
|
||||
|
||||
interface TestedCredential {
|
||||
apiKey?: string
|
||||
resourcePath?: string
|
||||
resourceValue?: Record<string, any>
|
||||
}
|
||||
|
||||
/** The header naming the credential under test; a resource wins over a bare key. */
|
||||
export function testedCredentialHeaders({
|
||||
apiKey,
|
||||
resourcePath,
|
||||
resourceValue
|
||||
}: TestedCredential): Record<string, string> {
|
||||
if (resourceValue) {
|
||||
// Header values must be ASCII, the resource may not be.
|
||||
return { 'X-Resource-Value': encodeURIComponent(JSON.stringify(resourceValue)) }
|
||||
} else if (resourcePath) {
|
||||
return { 'X-Resource-Path': resourcePath }
|
||||
} else if (apiKey) {
|
||||
return { 'X-API-Key': apiKey }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
/** A bare key has no workspace resource behind it, so it goes through the global proxy. */
|
||||
export function usesGlobalAiProxy({ apiKey, resourcePath, resourceValue }: TestedCredential) {
|
||||
return !!apiKey && !resourcePath && !resourceValue
|
||||
}
|
||||
|
||||
export async function testKey({
|
||||
apiKey,
|
||||
workspace,
|
||||
resourcePath,
|
||||
resourceValue,
|
||||
model,
|
||||
abortController,
|
||||
messages,
|
||||
@@ -593,13 +622,14 @@ export async function testKey({
|
||||
apiKey?: string
|
||||
workspace?: string
|
||||
resourcePath?: string
|
||||
resourceValue?: Record<string, any>
|
||||
model: string | undefined
|
||||
messages: ChatCompletionMessageParam[]
|
||||
abortController: AbortController
|
||||
aiProvider: AIProvider
|
||||
}) {
|
||||
if (!apiKey && !resourcePath) {
|
||||
throw new Error('API key or resource path is required')
|
||||
if (!apiKey && !resourcePath && !resourceValue) {
|
||||
throw new Error('API key, resource path or resource value is required')
|
||||
}
|
||||
const modelToTest = model ?? AI_PROVIDERS[aiProvider].defaultModels[0]
|
||||
|
||||
@@ -617,6 +647,7 @@ export async function testKey({
|
||||
apiKey,
|
||||
workspace,
|
||||
resourcePath,
|
||||
resourceValue,
|
||||
forceModelProvider: {
|
||||
model: modelToTest,
|
||||
provider: aiProvider
|
||||
@@ -638,6 +669,7 @@ interface AnthropicCompletionParams {
|
||||
apiKey?: string
|
||||
workspace?: string
|
||||
resourcePath?: string
|
||||
resourceValue?: Record<string, any>
|
||||
maxTokensCap?: number
|
||||
}
|
||||
|
||||
@@ -647,6 +679,7 @@ function buildAnthropicProxyRequest({
|
||||
apiKey,
|
||||
workspace,
|
||||
resourcePath,
|
||||
resourceValue,
|
||||
maxTokensCap
|
||||
}: Omit<AnthropicCompletionParams, 'abortController'>) {
|
||||
const { system, messages: anthropicMessages } = convertOpenAIToAnthropicMessages(messages)
|
||||
@@ -655,16 +688,11 @@ function buildAnthropicProxyRequest({
|
||||
// resolves the right credentials and Anthropic URL.
|
||||
const headers: Record<string, string> = {
|
||||
'X-Provider': modelProvider.provider,
|
||||
'anthropic-version': '2023-06-01'
|
||||
'anthropic-version': '2023-06-01',
|
||||
...testedCredentialHeaders({ apiKey, resourcePath, resourceValue })
|
||||
}
|
||||
|
||||
if (resourcePath) {
|
||||
headers['X-Resource-Path'] = resourcePath
|
||||
} else if (apiKey) {
|
||||
headers['X-API-Key'] = apiKey
|
||||
}
|
||||
|
||||
const client = apiKey
|
||||
const client = usesGlobalAiProxy({ apiKey, resourcePath, resourceValue })
|
||||
? createAnthropicProxyClient(getAiProxyBaseURL())
|
||||
: workspace
|
||||
? workspaceAIClients.createAnthropicClient(workspace)
|
||||
@@ -959,6 +987,7 @@ export async function getNonStreamingCompletion(
|
||||
options?: {
|
||||
apiKey?: string // testing API KEY using the global ai proxy
|
||||
resourcePath?: string // testing resource path passed as a header to the backend proxy
|
||||
resourceValue?: Record<string, any> // testing an unsaved resource value, same route
|
||||
workspace?: string // use a specific workspace proxy when testing a workspace resource
|
||||
forceModelProvider?: AIProviderModel
|
||||
maxTokensCap?: number // hard ceiling on output tokens (see METADATA_MAX_TOKENS)
|
||||
@@ -974,6 +1003,7 @@ export async function getNonStreamingCompletion(
|
||||
apiKey: options?.apiKey,
|
||||
workspace: options?.workspace,
|
||||
resourcePath: options?.resourcePath,
|
||||
resourceValue: options?.resourceValue,
|
||||
maxTokensCap: options?.maxTokensCap
|
||||
})
|
||||
}
|
||||
@@ -1009,22 +1039,11 @@ export async function getNonStreamingCompletion(
|
||||
'X-Provider': provider
|
||||
}
|
||||
}
|
||||
if (options?.resourcePath) {
|
||||
fetchOptions.headers = {
|
||||
...fetchOptions.headers,
|
||||
'X-Resource-Path': options.resourcePath
|
||||
}
|
||||
} else if (options?.apiKey) {
|
||||
if (provider === 'customai') {
|
||||
throw new Error('Cannot test API key for Custom AI, only resource path is supported')
|
||||
}
|
||||
|
||||
fetchOptions.headers = {
|
||||
...fetchOptions.headers,
|
||||
'X-API-Key': options.apiKey
|
||||
}
|
||||
if (usesGlobalAiProxy(options ?? {}) && provider === 'customai') {
|
||||
throw new Error('Cannot test API key for Custom AI, only resource path is supported')
|
||||
}
|
||||
const openaiClient = options?.apiKey
|
||||
fetchOptions.headers = { ...fetchOptions.headers, ...testedCredentialHeaders(options ?? {}) }
|
||||
const openaiClient = usesGlobalAiProxy(options ?? {})
|
||||
? createOpenAIProxyClient(getAiProxyBaseURL())
|
||||
: options?.workspace
|
||||
? workspaceAIClients.createOpenaiClient(options.workspace)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { type FlowModule } from '$lib/gen'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { Pen, RefreshCcw, Save } from 'lucide-svelte'
|
||||
import DropdownV2 from '../../DropdownV2.svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
|
||||
@@ -83,16 +82,13 @@
|
||||
tag={module.value.tag}
|
||||
on:change={(e) => dispatch('tagChange', e.detail)}
|
||||
/>
|
||||
<DropdownV2
|
||||
size="sm"
|
||||
placement="bottom-end"
|
||||
items={[
|
||||
{
|
||||
displayName: 'Save to workspace',
|
||||
icon: Save,
|
||||
action: () => dispatch('createScriptFromInlineScript')
|
||||
}
|
||||
]}
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="subtle"
|
||||
startIcon={{ icon: Save }}
|
||||
on:click={() => dispatch('createScriptFromInlineScript')}
|
||||
iconOnly={true}
|
||||
title="Save to workspace"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.820.0"
|
||||
wmill = ">=1.821.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: '3.0.3'
|
||||
|
||||
info:
|
||||
version: 1.820.0
|
||||
version: 1.821.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.820.0'
|
||||
ModuleVersion = '1.821.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.820.0"
|
||||
version = "1.821.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -88,6 +88,9 @@ datatable related commands
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `datatable migrate down` - roll back the most recent migration on the main datatable (or one via --datatable)
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `datatable migrate status` - show applied and pending migrations on the main datatable (or one via --datatable)
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `datatable create [name:string]` - register a datatable database in the workspace (default: instance-backed 'main') so scripts can use datatable://<name>
|
||||
- `--resource <resource:string>` - Back the datatable with an existing postgresql resource path instead of the instance database
|
||||
- `--force` - Allow adding to a workspace that already has datatables (fork metadata on existing ones is not preserved)
|
||||
|
||||
@@ -3434,6 +3434,9 @@ datatable related commands
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`datatable migrate down\` - roll back the most recent migration on the main datatable (or one via --datatable)
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`datatable migrate status\` - show applied and pending migrations on the main datatable (or one via --datatable)
|
||||
- \`-d --datatable <datatable:string>\` - Target datatable (default: main)
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`datatable create [name:string]\` - register a datatable database in the workspace (default: instance-backed 'main') so scripts can use datatable://<name>
|
||||
- \`--resource <resource:string>\` - Back the datatable with an existing postgresql resource path instead of the instance database
|
||||
- \`--force\` - Allow adding to a workspace that already has datatables (fork metadata on existing ones is not preserved)
|
||||
|
||||
@@ -93,6 +93,9 @@ datatable related commands
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `datatable migrate down` - roll back the most recent migration on the main datatable (or one via --datatable)
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `datatable migrate status` - show applied and pending migrations on the main datatable (or one via --datatable)
|
||||
- `-d --datatable <datatable:string>` - Target datatable (default: main)
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `datatable create [name:string]` - register a datatable database in the workspace (default: instance-backed 'main') so scripts can use datatable://<name>
|
||||
- `--resource <resource:string>` - Back the datatable with an existing postgresql resource path instead of the instance database
|
||||
- `--force` - Allow adding to a workspace that already has datatables (fork metadata on existing ones is not preserved)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts", "./wacError.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"homepage": "https://github.com/windmill-labs/windmill/tree/main/typescript-client#readme",
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.820.0
|
||||
1.821.0
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@stoplight/yaml": "^4.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.820.0",
|
||||
"version": "1.821.0",
|
||||
"description": "YAML validator for Windmill flow, schedule, and trigger files",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user