mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
Merge branch 'main' into dieri/sharding-v0
This commit is contained in:
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [1.546.1](https://github.com/windmill-labs/windmill/compare/v1.546.0...v1.546.1) (2025-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mcp:** use stateless mode for openai sdk compatibility ([#6656](https://github.com/windmill-labs/windmill/issues/6656)) ([389b692](https://github.com/windmill-labs/windmill/commit/389b692523507a28916e96b481c60f3c49cd31da))
|
||||
|
||||
## [1.546.0](https://github.com/windmill-labs/windmill/compare/v1.545.0...v1.546.0) (2025-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* app builder button tooltip ([#6652](https://github.com/windmill-labs/windmill/issues/6652)) ([08952c6](https://github.com/windmill-labs/windmill/commit/08952c6c6e0afdde8fc941f9f1d17870fe25878a))
|
||||
* dynamically hide tabs in app builder ([#6653](https://github.com/windmill-labs/windmill/issues/6653)) ([de7251d](https://github.com/windmill-labs/windmill/commit/de7251d85734757a1f3e222c715f807ba167d535))
|
||||
* split RUST_LOG into RUST_LOG and RUST_LOG_STDOUT ([7a13e9e](https://github.com/windmill-labs/windmill/commit/7a13e9e98840a456ef6625cea838e3e82def5c4b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add settable poll delay for sse streams ([0392103](https://github.com/windmill-labs/windmill/commit/039210369383bcc3a15d95cba9efb591ee8e9891))
|
||||
* cli path on windows + error_handler_muted_on_cancel ([#6657](https://github.com/windmill-labs/windmill/issues/6657)) ([6ba3a43](https://github.com/windmill-labs/windmill/commit/6ba3a4397e439d40079d524de15507257442c5e1))
|
||||
* improve reliability of exits in case graceful handler didn't exit as expected ([f6dd78c](https://github.com/windmill-labs/windmill/commit/f6dd78cb11ee73408f66b4670c395ade99beedbe))
|
||||
|
||||
## [1.545.0](https://github.com/windmill-labs/windmill/compare/v1.544.2...v1.545.0) (2025-09-20)
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -46,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "hash",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = NULL\n WHERE\n workspace_id = $1\n ",
|
||||
"query": "\n UPDATE\n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = false\n WHERE\n workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -10,5 +10,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a"
|
||||
"hash": "5ad2c883d26f39f3c141806428a329951ef19a1cb3e1b429fcd1abe0e2db45b5"
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887"
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript",
|
||||
"aiagent"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338"
|
||||
}
|
||||
+2
-2
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46"
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies",
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop",
|
||||
"appdependencies",
|
||||
"deploymentcallback",
|
||||
"singlescriptflow",
|
||||
"flowscript",
|
||||
"flownode",
|
||||
"appscript",
|
||||
"aiagent"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
"snowflake",
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative",
|
||||
"rust",
|
||||
"ansible",
|
||||
"csharp",
|
||||
"oracledb",
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238"
|
||||
}
|
||||
Generated
+78
-72
@@ -2027,7 +2027,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2508,7 +2508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -7318,7 +7318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -7525,9 +7525,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958"
|
||||
checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594"
|
||||
dependencies = [
|
||||
"lexical-parse-float",
|
||||
"lexical-parse-integer",
|
||||
@@ -7538,53 +7538,46 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lexical-parse-float"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
|
||||
checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56"
|
||||
dependencies = [
|
||||
"lexical-parse-integer",
|
||||
"lexical-util",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-parse-integer"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
|
||||
checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34"
|
||||
dependencies = [
|
||||
"lexical-util",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-util"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-write-float"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd"
|
||||
checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361"
|
||||
dependencies = [
|
||||
"lexical-util",
|
||||
"lexical-write-integer",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-write-integer"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978"
|
||||
checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df"
|
||||
dependencies = [
|
||||
"lexical-util",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7624,12 +7617,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.53.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10863,9 +10856,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rmcp"
|
||||
version = "0.2.1"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37f2048a81a7ff7e8ef6bc5abced70c3d9114c8f03d85d7aaaafd9fd04f12e9e"
|
||||
checksum = "41ab0892f4938752b34ae47cb53910b1b0921e55e77ddb6e44df666cab17939f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -10878,7 +10871,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"rand 0.9.0",
|
||||
"rmcp-macros",
|
||||
"schemars 0.8.22",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sse-stream",
|
||||
@@ -10893,11 +10886,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rmcp-macros"
|
||||
version = "0.2.1"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72398e694b9f6dbb5de960cf158c8699e6a1854cb5bbaac7de0646b2005763c4"
|
||||
checksum = "1827cd98dab34cade0513243c6fe0351f0f0b2c9d6825460bcf45b42804bdda0"
|
||||
dependencies = [
|
||||
"darling 0.20.11",
|
||||
"darling 0.21.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
@@ -11179,7 +11172,7 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework 3.4.0",
|
||||
"security-framework 3.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11438,9 +11431,8 @@ version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"schemars_derive 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -11463,8 +11455,10 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"schemars_derive 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -11481,6 +11475,18 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -11560,9 +11566,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640"
|
||||
checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"core-foundation 0.10.1",
|
||||
@@ -13298,9 +13304,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.22.0"
|
||||
version = "3.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
@@ -14330,7 +14336,7 @@ checksum = "90b70b37e9074642bc5f60bb23247fd072a84314ca9e71cdf8527593406a0dd3"
|
||||
dependencies = [
|
||||
"gemm 0.18.2",
|
||||
"half",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"memmap2 0.9.8",
|
||||
"num",
|
||||
"num-traits",
|
||||
@@ -15023,7 +15029,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"log",
|
||||
"metal",
|
||||
"naga",
|
||||
@@ -15129,7 +15135,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aws-sdk-config",
|
||||
@@ -15148,7 +15154,7 @@ dependencies = [
|
||||
"k8s-openapi",
|
||||
"kube",
|
||||
"lazy_static",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"memchr",
|
||||
"nom 8.0.0",
|
||||
"object_store",
|
||||
@@ -15189,7 +15195,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15309,7 +15315,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -15324,7 +15330,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -15337,7 +15343,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -15356,7 +15362,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15441,7 +15447,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -15456,7 +15462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -15480,7 +15486,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -15492,7 +15498,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -15501,7 +15507,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15513,7 +15519,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15525,7 +15531,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -15537,7 +15543,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15549,7 +15555,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15561,7 +15567,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -15572,7 +15578,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15583,7 +15589,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15595,7 +15601,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15618,7 +15624,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15632,7 +15638,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -15649,7 +15655,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15663,7 +15669,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15681,7 +15687,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.16",
|
||||
@@ -15706,7 +15712,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15716,7 +15722,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15749,7 +15755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -15759,7 +15765,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -15799,7 +15805,7 @@ dependencies = [
|
||||
"jsonwebtoken 8.3.0",
|
||||
"lazy_static",
|
||||
"libffi-sys",
|
||||
"libloading 0.8.8",
|
||||
"libloading 0.8.9",
|
||||
"mappable-rc",
|
||||
"mime_guess",
|
||||
"mysql_async",
|
||||
@@ -16446,9 +16452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.5.1"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix 1.1.2",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -34,7 +34,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ mcp = ["dep:rmcp"]
|
||||
python = []
|
||||
|
||||
[dependencies]
|
||||
rmcp = { version = "0.2.1", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
|
||||
rmcp = { version = "0.6.4", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.545.0
|
||||
version: 1.546.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -361,6 +361,11 @@ async fn update_folder(
|
||||
);
|
||||
}
|
||||
if let Some(extra_perms) = ng.extra_perms {
|
||||
if !extra_perms.is_object() {
|
||||
return Err(windmill_common::error::Error::BadRequest(format!(
|
||||
"extra_perms must be an object, received {}", extra_perms.to_string()
|
||||
)));
|
||||
}
|
||||
sqlb.set(
|
||||
"extra_perms",
|
||||
"?".bind(&serde_json::to_string(&extra_perms).map_err(to_anyhow)?),
|
||||
|
||||
@@ -4,16 +4,17 @@
|
||||
//! specification. This is a thin orchestration layer that delegates to the appropriate
|
||||
//! modules for tool management, database operations, and schema transformation.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
|
||||
use axum::body::to_bytes;
|
||||
use rmcp::{
|
||||
handler::server::ServerHandler,
|
||||
model::*,
|
||||
service::{RequestContext, RoleServer},
|
||||
Error,
|
||||
transport::StreamableHttpServerConfig,
|
||||
ErrorData,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use tokio::try_join;
|
||||
@@ -26,35 +27,29 @@ use crate::jobs::{
|
||||
run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery,
|
||||
};
|
||||
|
||||
use super::tools::endpoint_tools::{
|
||||
all_endpoint_tools, call_endpoint_tool, endpoint_tools_to_mcp_tools, EndpointTool,
|
||||
};
|
||||
use super::utils::{
|
||||
database::{
|
||||
check_scopes, get_items, get_resources_types, get_scripts_from_hub, get_item_schema, get_hub_script_schema
|
||||
check_scopes, get_hub_script_schema, get_item_schema, get_items, get_resources_types,
|
||||
get_scripts_from_hub,
|
||||
},
|
||||
models::{
|
||||
FlowInfo, ResourceInfo, ResourceType, SchemaType, ScriptInfo, ToolableItem, WorkspaceId,
|
||||
},
|
||||
models::{ScriptInfo, FlowInfo, ResourceInfo, ResourceType, SchemaType, ToolableItem, WorkspaceId},
|
||||
schema::transform_schema_for_resources,
|
||||
transform::{reverse_transform, reverse_transform_key},
|
||||
};
|
||||
use super::tools::{
|
||||
endpoint_tools::{all_endpoint_tools, endpoint_tools_to_mcp_tools, call_endpoint_tool, EndpointTool},
|
||||
};
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::Request,
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
routing::get,
|
||||
Json,
|
||||
Router,
|
||||
extract::Path, http::Request, middleware::Next, response::Response, routing::get, Json, Router,
|
||||
};
|
||||
use rmcp::transport::streamable_http_server::{
|
||||
session::local::LocalSessionManager,
|
||||
SessionManager,
|
||||
StreamableHttpService,
|
||||
session::local::LocalSessionManager, SessionManager, StreamableHttpService,
|
||||
};
|
||||
use windmill_common::error::JsonResult;
|
||||
|
||||
|
||||
/// MCP Server Runner - implements the core MCP protocol handlers
|
||||
#[derive(Clone)]
|
||||
pub struct Runner {}
|
||||
@@ -72,7 +67,7 @@ impl Runner {
|
||||
workspace_id: &str,
|
||||
resources_cache: &mut HashMap<String, Vec<ResourceInfo>>,
|
||||
resources_types: &Vec<ResourceType>,
|
||||
) -> Result<Tool, Error> {
|
||||
) -> Result<Tool, ErrorData> {
|
||||
let is_hub = item.is_hub();
|
||||
let path = item.get_path_or_id();
|
||||
let item_type = item.item_type();
|
||||
@@ -120,52 +115,57 @@ impl Runner {
|
||||
name: Cow::Owned(path),
|
||||
description: Some(Cow::Owned(description)),
|
||||
input_schema: Arc::new(input_schema_map),
|
||||
title: Some(item.get_summary().to_string()),
|
||||
output_schema: None,
|
||||
icons: None,
|
||||
annotations: Some(ToolAnnotations {
|
||||
title: Some(item.get_summary().to_string()),
|
||||
read_only_hint: Some(false), // Can modify environment
|
||||
read_only_hint: Some(false), // Can modify environment
|
||||
destructive_hint: Some(true), // Can potentially be destructive
|
||||
idempotent_hint: Some(false), // Are not guaranteed to be idempotent
|
||||
open_world_hint: Some(true), // Can interact with external services
|
||||
open_world_hint: Some(true), // Can interact with external services
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ServerHandler for Runner {
|
||||
/// Handles the `CallTool` request from the MCP client
|
||||
async fn call_tool(
|
||||
&self,
|
||||
request: CallToolRequestParam,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> Result<CallToolResult, Error> {
|
||||
) -> Result<CallToolResult, ErrorData> {
|
||||
let http_parts = context
|
||||
.extensions
|
||||
.get::<axum::http::request::Parts>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("http::request::Parts not found");
|
||||
Error::internal_error("http::request::Parts not found", None)
|
||||
ErrorData::internal_error("http::request::Parts not found", None)
|
||||
})?;
|
||||
|
||||
let authed = http_parts.extensions.get::<ApiAuthed>().ok_or_else(|| {
|
||||
tracing::error!("ApiAuthed Axum extension not found");
|
||||
Error::internal_error("ApiAuthed Axum extension not found", None)
|
||||
ErrorData::internal_error("ApiAuthed Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
check_scopes(authed)?;
|
||||
|
||||
let db = http_parts.extensions.get::<DB>().ok_or_else(|| {
|
||||
tracing::error!("DB Axum extension not found");
|
||||
Error::internal_error("DB Axum extension not found", None)
|
||||
ErrorData::internal_error("DB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let user_db = http_parts.extensions.get::<UserDB>().ok_or_else(|| {
|
||||
tracing::error!("UserDB Axum extension not found");
|
||||
Error::internal_error("UserDB Axum extension not found", None)
|
||||
ErrorData::internal_error("UserDB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
|
||||
let args = request.arguments.map(Value::Object).ok_or_else(|| {
|
||||
Error::invalid_params("Missing arguments for tool", Some(request.name.clone().into()))
|
||||
ErrorData::invalid_params(
|
||||
"Missing arguments for tool",
|
||||
Some(request.name.clone().into()),
|
||||
)
|
||||
})?;
|
||||
|
||||
let workspace_id = http_parts
|
||||
@@ -173,7 +173,7 @@ impl ServerHandler for Runner {
|
||||
.get::<WorkspaceId>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("WorkspaceId not found");
|
||||
Error::internal_error("WorkspaceId not found", None)
|
||||
ErrorData::internal_error("WorkspaceId not found", None)
|
||||
})
|
||||
.map(|w_id| w_id.0.clone())?;
|
||||
|
||||
@@ -182,18 +182,20 @@ impl ServerHandler for Runner {
|
||||
for endpoint_tool in endpoint_tools {
|
||||
if endpoint_tool.name.as_ref() == request.name {
|
||||
// This is an endpoint tool, forward to the actual HTTP endpoint
|
||||
let result = call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed).await?;
|
||||
let result =
|
||||
call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed)
|
||||
.await?;
|
||||
return Ok(CallToolResult::success(vec![Content::text(
|
||||
serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string())
|
||||
serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string()),
|
||||
)]));
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with script/flow logic
|
||||
let (tool_type, path, is_hub) = reverse_transform(&request.name).map_err(|e| {
|
||||
Error::internal_error(format!("Failed to reverse transform path: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to reverse transform path: {}", e), None)
|
||||
})?;
|
||||
|
||||
|
||||
let item_schema = if is_hub {
|
||||
get_hub_script_schema(&format!("hub/{}", path), db).await?
|
||||
} else {
|
||||
@@ -259,14 +261,20 @@ impl ServerHandler for Runner {
|
||||
let body_bytes = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::internal_error(format!("Failed to read response body: {}", e), None)
|
||||
ErrorData::internal_error(
|
||||
format!("Failed to read response body: {}", e),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| {
|
||||
Error::internal_error(format!("Failed to decode response body: {}", e), None)
|
||||
ErrorData::internal_error(
|
||||
format!("Failed to decode response body: {}", e),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
Ok(CallToolResult::success(vec![Content::text(body_str)]))
|
||||
}
|
||||
Err(e) => Err(Error::internal_error(
|
||||
Err(e) => Err(ErrorData::internal_error(
|
||||
format!("Failed to run script: {}", e),
|
||||
None,
|
||||
)),
|
||||
@@ -278,30 +286,30 @@ impl ServerHandler for Runner {
|
||||
&self,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
mut _context: RequestContext<RoleServer>,
|
||||
) -> Result<ListToolsResult, Error> {
|
||||
) -> Result<ListToolsResult, ErrorData> {
|
||||
let http_parts = _context
|
||||
.extensions
|
||||
.get::<axum::http::request::Parts>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("http::request::Parts not found");
|
||||
Error::internal_error("http::request::Parts not found", None)
|
||||
ErrorData::internal_error("http::request::Parts not found", None)
|
||||
})?;
|
||||
|
||||
let authed = http_parts.extensions.get::<ApiAuthed>().ok_or_else(|| {
|
||||
tracing::error!("ApiAuthed Axum extension not found");
|
||||
Error::internal_error("ApiAuthed Axum extension not found", None)
|
||||
ErrorData::internal_error("ApiAuthed Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
check_scopes(authed)?;
|
||||
|
||||
let db = http_parts.extensions.get::<DB>().ok_or_else(|| {
|
||||
tracing::error!("DB Axum extension not found");
|
||||
Error::internal_error("DB Axum extension not found", None)
|
||||
ErrorData::internal_error("DB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let user_db = http_parts.extensions.get::<UserDB>().ok_or_else(|| {
|
||||
tracing::error!("UserDB Axum extension not found");
|
||||
Error::internal_error("UserDB Axum extension not found", None)
|
||||
ErrorData::internal_error("UserDB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let workspace_id = http_parts
|
||||
@@ -309,7 +317,7 @@ impl ServerHandler for Runner {
|
||||
.get::<WorkspaceId>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("WorkspaceId not found");
|
||||
Error::internal_error("WorkspaceId not found", None)
|
||||
ErrorData::internal_error("WorkspaceId not found", None)
|
||||
})
|
||||
.map(|w_id| w_id.0.clone())?;
|
||||
|
||||
@@ -319,10 +327,18 @@ impl ServerHandler for Runner {
|
||||
.iter()
|
||||
.find(|scope| scope.starts_with("mcp:") && !scope.contains("hub"))
|
||||
});
|
||||
let hub_scope = scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub")));
|
||||
let hub_scope =
|
||||
scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub")));
|
||||
let (scope_type, scope_path) = owned_scope.map_or(("all", None), |scope| {
|
||||
let parts = scope.split(":").collect::<Vec<&str>>();
|
||||
(parts[1], if parts.len() == 3 { Some(parts[2]) } else { None })
|
||||
(
|
||||
parts[1],
|
||||
if parts.len() == 3 {
|
||||
Some(parts[2])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
});
|
||||
let scope_integrations = hub_scope.and_then(|scope| {
|
||||
let parts = scope.split(":").collect::<Vec<&str>>();
|
||||
@@ -341,8 +357,14 @@ impl ServerHandler for Runner {
|
||||
"script",
|
||||
scope_path.as_deref(),
|
||||
);
|
||||
let flows_fn =
|
||||
get_items::<FlowInfo>(user_db, authed, &workspace_id, scope_type, "flow", scope_path.as_deref());
|
||||
let flows_fn = get_items::<FlowInfo>(
|
||||
user_db,
|
||||
authed,
|
||||
&workspace_id,
|
||||
scope_type,
|
||||
"flow",
|
||||
scope_path.as_deref(),
|
||||
);
|
||||
let resources_types_fn = get_resources_types(user_db, authed, &workspace_id);
|
||||
let hub_scripts_fn = get_scripts_from_hub(db, scope_integrations.as_deref());
|
||||
let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() {
|
||||
@@ -410,7 +432,7 @@ impl ServerHandler for Runner {
|
||||
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
protocol_version: Default::default(),
|
||||
protocol_version: ProtocolVersion::default(),
|
||||
capabilities: ServerCapabilities::builder()
|
||||
.enable_tools()
|
||||
.enable_tool_list_changed()
|
||||
@@ -424,7 +446,7 @@ impl ServerHandler for Runner {
|
||||
&self,
|
||||
_request: InitializeRequestParam,
|
||||
_context: RequestContext<RoleServer>,
|
||||
) -> Result<InitializeResult, Error> {
|
||||
) -> Result<InitializeResult, ErrorData> {
|
||||
Ok(self.get_info())
|
||||
}
|
||||
|
||||
@@ -432,7 +454,7 @@ impl ServerHandler for Runner {
|
||||
&self,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_context: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourcesResult, Error> {
|
||||
) -> Result<ListResourcesResult, ErrorData> {
|
||||
Ok(ListResourcesResult { resources: vec![], next_cursor: None })
|
||||
}
|
||||
|
||||
@@ -440,7 +462,7 @@ impl ServerHandler for Runner {
|
||||
&self,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_context: RequestContext<RoleServer>,
|
||||
) -> Result<ListPromptsResult, Error> {
|
||||
) -> Result<ListPromptsResult, ErrorData> {
|
||||
Ok(ListPromptsResult::default())
|
||||
}
|
||||
|
||||
@@ -448,7 +470,7 @@ impl ServerHandler for Runner {
|
||||
&self,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_context: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourceTemplatesResult, Error> {
|
||||
) -> Result<ListResourceTemplatesResult, ErrorData> {
|
||||
Ok(ListResourceTemplatesResult::default())
|
||||
}
|
||||
}
|
||||
@@ -467,7 +489,10 @@ pub async fn extract_and_store_workspace_id(
|
||||
/// Setup the MCP server with HTTP transport
|
||||
pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc<LocalSessionManager>)> {
|
||||
let session_manager = Arc::new(LocalSessionManager::default());
|
||||
let service_config = Default::default();
|
||||
let service_config = StreamableHttpServerConfig {
|
||||
sse_keep_alive: Some(Duration::from_secs(15)),
|
||||
stateful_mode: false,
|
||||
};
|
||||
let service = StreamableHttpService::new(
|
||||
|| Ok(Runner::new()),
|
||||
session_manager.clone(),
|
||||
@@ -513,6 +538,5 @@ async fn list_mcp_tools_handler() -> JsonResult<Vec<EndpointTool>> {
|
||||
|
||||
/// Creates a router service for listing MCP tools
|
||||
pub fn list_tools_service() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(list_mcp_tools_handler))
|
||||
}
|
||||
Router::new().route("/", get(list_mcp_tools_handler))
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
//! Contains the auto-generated endpoint tools and utilities for converting
|
||||
//! them to MCP tools and handling HTTP calls to Windmill API endpoints.
|
||||
|
||||
use rmcp::{model::Tool, Error};
|
||||
use crate::db::ApiAuthed;
|
||||
use rmcp::{model::Tool, ErrorData};
|
||||
use std::sync::Arc;
|
||||
use windmill_common::auth::create_jwt_token;
|
||||
use windmill_common::db::Authed;
|
||||
use windmill_common::BASE_URL;
|
||||
use crate::db::ApiAuthed;
|
||||
|
||||
// Import the auto-generated tools
|
||||
use super::auto_generated_endpoints;
|
||||
pub use auto_generated_endpoints::{EndpointTool, all_tools};
|
||||
pub use auto_generated_endpoints::{all_tools, EndpointTool};
|
||||
|
||||
/// Get all available endpoint tools
|
||||
pub fn all_endpoint_tools() -> Vec<EndpointTool> {
|
||||
@@ -21,25 +21,28 @@ pub fn all_endpoint_tools() -> Vec<EndpointTool> {
|
||||
|
||||
/// Convert endpoint tools to MCP tools
|
||||
pub fn endpoint_tools_to_mcp_tools(endpoint_tools: Vec<EndpointTool>) -> Vec<Tool> {
|
||||
endpoint_tools.into_iter().map(|tool| endpoint_tool_to_mcp_tool(&tool)).collect()
|
||||
endpoint_tools
|
||||
.into_iter()
|
||||
.map(|tool| endpoint_tool_to_mcp_tool(&tool))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert a single endpoint tool to MCP tool
|
||||
pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
|
||||
let mut combined_properties = serde_json::Map::new();
|
||||
let mut combined_required = Vec::new();
|
||||
|
||||
|
||||
// Combine all parameter schemas
|
||||
let schemas = [
|
||||
&tool.path_params_schema,
|
||||
&tool.query_params_schema,
|
||||
&tool.query_params_schema,
|
||||
&tool.body_schema,
|
||||
];
|
||||
|
||||
|
||||
for schema in schemas.iter().filter_map(|s| s.as_ref()) {
|
||||
merge_schema_into(&mut combined_properties, &mut combined_required, schema);
|
||||
}
|
||||
|
||||
|
||||
let combined_schema = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": combined_properties,
|
||||
@@ -47,7 +50,7 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
|
||||
});
|
||||
|
||||
let description = format!("{}. {}", tool.description, tool.instructions);
|
||||
|
||||
|
||||
// Create annotations based on HTTP method and endpoint characteristics
|
||||
let annotations = create_endpoint_annotations(tool);
|
||||
|
||||
@@ -55,6 +58,9 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
|
||||
name: tool.name.clone(),
|
||||
description: Some(description.into()),
|
||||
input_schema: Arc::new(combined_schema.as_object().unwrap().clone()),
|
||||
title: Some(tool.name.to_string()),
|
||||
output_schema: None,
|
||||
icons: None,
|
||||
annotations: Some(annotations),
|
||||
}
|
||||
}
|
||||
@@ -62,15 +68,15 @@ pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool {
|
||||
/// Create appropriate annotations for endpoint tools based on HTTP method
|
||||
fn create_endpoint_annotations(tool: &EndpointTool) -> rmcp::model::ToolAnnotations {
|
||||
let method = tool.method.as_ref();
|
||||
|
||||
|
||||
// Determine characteristics based on HTTP method
|
||||
let (read_only, destructive, idempotent, open_world) = match method {
|
||||
"GET" => (true, false, true, true), // Read-only, safe, idempotent
|
||||
"POST" => (false, true, false, true), // Can modify, potentially destructive, not idempotent
|
||||
"PUT" => (false, false, true, true), // Can modify, typically idempotent updates
|
||||
"DELETE" => (false, true, true, true), // Destructive but idempotent
|
||||
"GET" => (true, false, true, true), // Read-only, safe, idempotent
|
||||
"POST" => (false, true, false, true), // Can modify, potentially destructive, not idempotent
|
||||
"PUT" => (false, false, true, true), // Can modify, typically idempotent updates
|
||||
"DELETE" => (false, true, true, true), // Destructive but idempotent
|
||||
"PATCH" => (false, false, false, true), // Partial updates, not guaranteed idempotent
|
||||
_ => (false, true, false, true), // Default: assume can modify and be destructive
|
||||
_ => (false, true, false, true), // Default: assume can modify and be destructive
|
||||
};
|
||||
|
||||
rmcp::model::ToolAnnotations {
|
||||
@@ -93,7 +99,7 @@ fn merge_schema_into(
|
||||
combined_properties.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let Some(required) = schema.get("required").and_then(|r| r.as_array()) {
|
||||
for req in required.iter().filter_map(|r| r.as_str()) {
|
||||
combined_required.push(req.to_string());
|
||||
@@ -107,34 +113,52 @@ pub async fn call_endpoint_tool(
|
||||
args: serde_json::Value,
|
||||
workspace_id: &str,
|
||||
api_authed: &ApiAuthed,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
) -> Result<serde_json::Value, ErrorData> {
|
||||
let args_map = match &args {
|
||||
serde_json::Value::Object(map) => map,
|
||||
_ => return Err(Error::invalid_params("Arguments must be an object", Some(tool.name.clone().into()))),
|
||||
_ => {
|
||||
return Err(ErrorData::invalid_params(
|
||||
"Arguments must be an object",
|
||||
Some(tool.name.clone().into()),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Build URL with path substitutions
|
||||
let path_template = substitute_path_params(&tool.path, workspace_id, args_map, &tool.path_params_schema)?;
|
||||
let path_template =
|
||||
substitute_path_params(&tool.path, workspace_id, args_map, &tool.path_params_schema)?;
|
||||
let query_string = build_query_string(args_map, &tool.query_params_schema);
|
||||
let full_url = format!("{}/api{}{}", BASE_URL.read().await, path_template, query_string);
|
||||
let full_url = format!(
|
||||
"{}/api{}{}",
|
||||
BASE_URL.read().await,
|
||||
path_template,
|
||||
query_string
|
||||
);
|
||||
|
||||
// Prepare request body
|
||||
let body_json = build_request_body(&tool.method, args_map, &tool.body_schema);
|
||||
|
||||
// Create and execute request
|
||||
let response = create_http_request(&tool.method, &full_url, workspace_id, api_authed, body_json).await?;
|
||||
|
||||
let response =
|
||||
create_http_request(&tool.method, &full_url, workspace_id, api_authed, body_json).await?;
|
||||
|
||||
let status = response.status();
|
||||
let response_text = response.text().await.map_err(|e| {
|
||||
Error::internal_error(format!("Failed to read response text: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to read response text: {}", e), None)
|
||||
})?;
|
||||
|
||||
if status.is_success() {
|
||||
Ok(serde_json::from_str(&response_text).unwrap_or_else(|_| serde_json::Value::String(response_text)))
|
||||
Ok(serde_json::from_str(&response_text)
|
||||
.unwrap_or_else(|_| serde_json::Value::String(response_text)))
|
||||
} else {
|
||||
Err(Error::internal_error(
|
||||
format!("HTTP {} {}: {}", status.as_u16(), status.canonical_reason().unwrap_or(""), response_text),
|
||||
None
|
||||
Err(ErrorData::internal_error(
|
||||
format!(
|
||||
"HTTP {} {}: {}",
|
||||
status.as_u16(),
|
||||
status.canonical_reason().unwrap_or(""),
|
||||
response_text
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -145,9 +169,9 @@ fn substitute_path_params(
|
||||
workspace_id: &str,
|
||||
args_map: &serde_json::Map<String, serde_json::Value>,
|
||||
path_schema: &Option<serde_json::Value>,
|
||||
) -> Result<String, Error> {
|
||||
) -> Result<String, ErrorData> {
|
||||
let mut path_template = path.replace("{workspace}", workspace_id);
|
||||
|
||||
|
||||
if let Some(schema) = path_schema {
|
||||
if let Some(props) = schema.get("properties").and_then(|p| p.as_object()) {
|
||||
for (param_name, _) in props {
|
||||
@@ -157,19 +181,19 @@ fn substitute_path_params(
|
||||
if let Some(str_val) = param_value.as_str() {
|
||||
path_template = path_template.replace(&placeholder, str_val);
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
tracing::warn!("Missing required path parameter: {}", param_name);
|
||||
return Err(Error::invalid_params(
|
||||
return Err(ErrorData::invalid_params(
|
||||
format!("Missing required path parameter: {}", param_name),
|
||||
None
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(path_template)
|
||||
}
|
||||
|
||||
@@ -178,25 +202,31 @@ fn build_query_string(
|
||||
args_map: &serde_json::Map<String, serde_json::Value>,
|
||||
query_schema: &Option<serde_json::Value>,
|
||||
) -> String {
|
||||
let Some(schema) = query_schema else { return String::new() };
|
||||
let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else { return String::new() };
|
||||
|
||||
let Some(schema) = query_schema else {
|
||||
return String::new();
|
||||
};
|
||||
let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
let query_params: Vec<String> = props
|
||||
.keys()
|
||||
.filter_map(|param_name| {
|
||||
args_map.get(param_name)
|
||||
args_map
|
||||
.get(param_name)
|
||||
.filter(|v| !v.is_null())
|
||||
.map(|value| {
|
||||
let value_str = value.to_string();
|
||||
let str_val = value_str.trim_matches('"');
|
||||
format!("{}={}",
|
||||
urlencoding::encode(param_name),
|
||||
format!(
|
||||
"{}={}",
|
||||
urlencoding::encode(param_name),
|
||||
urlencoding::encode(str_val)
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
if query_params.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
@@ -213,18 +243,19 @@ fn build_request_body(
|
||||
if method == "GET" {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
let schema = body_schema.as_ref()?;
|
||||
let props = schema.get("properties")?.as_object()?;
|
||||
|
||||
|
||||
let body_map: serde_json::Map<String, serde_json::Value> = props
|
||||
.keys()
|
||||
.filter_map(|param_name| {
|
||||
args_map.get(param_name)
|
||||
args_map
|
||||
.get(param_name)
|
||||
.map(|value| (param_name.clone(), value.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
if body_map.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -239,7 +270,7 @@ async fn create_http_request(
|
||||
workspace_id: &str,
|
||||
api_authed: &ApiAuthed,
|
||||
body_json: Option<serde_json::Value>,
|
||||
) -> Result<reqwest::Response, Error> {
|
||||
) -> Result<reqwest::Response, ErrorData> {
|
||||
let client = &crate::HTTP_CLIENT;
|
||||
let mut request_builder = match method {
|
||||
"GET" => client.get(url),
|
||||
@@ -247,16 +278,19 @@ async fn create_http_request(
|
||||
"PUT" => client.put(url),
|
||||
"DELETE" => client.delete(url),
|
||||
"PATCH" => client.patch(url),
|
||||
_ => return Err(Error::invalid_params(
|
||||
format!("Unsupported HTTP method: {}", method),
|
||||
None
|
||||
)),
|
||||
_ => {
|
||||
return Err(ErrorData::invalid_params(
|
||||
format!("Unsupported HTTP method: {}", method),
|
||||
None,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Add authorization header
|
||||
let authed = Authed::from(api_authed.clone());
|
||||
let token = create_jwt_token(authed, workspace_id, 3600, None, None, None, None).await
|
||||
.map_err(|e| Error::internal_error(e.to_string(), None))?;
|
||||
let token = create_jwt_token(authed, workspace_id, 3600, None, None, None, None)
|
||||
.await
|
||||
.map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
|
||||
request_builder = request_builder.header("Authorization", format!("Bearer {}", token));
|
||||
|
||||
// Add body if present
|
||||
@@ -266,7 +300,8 @@ async fn create_http_request(
|
||||
.json(&body);
|
||||
}
|
||||
|
||||
request_builder.send().await.map_err(|e| {
|
||||
Error::internal_error(format!("Failed to execute request: {}", e), None)
|
||||
})
|
||||
}
|
||||
request_builder
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| ErrorData::internal_error(format!("Failed to execute request: {}", e), None))
|
||||
}
|
||||
|
||||
@@ -3,28 +3,32 @@
|
||||
//! Contains all database query functions and database-related utilities
|
||||
//! used by the MCP server implementation.
|
||||
|
||||
use rmcp::Error;
|
||||
use rmcp::ErrorData;
|
||||
use sql_builder::prelude::*;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::scripts::{get_full_hub_script_by_path, Schema};
|
||||
use windmill_common::utils::{query_elems_from_hub, StripPath};
|
||||
use windmill_common::{DB, HUB_BASE_URL};
|
||||
|
||||
use super::models::*;
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::HTTP_CLIENT;
|
||||
use super::models::*;
|
||||
|
||||
/// Check if the user has proper MCP scopes
|
||||
pub fn check_scopes(authed: &ApiAuthed) -> Result<(), Error> {
|
||||
pub fn check_scopes(authed: &ApiAuthed) -> Result<(), ErrorData> {
|
||||
let scopes = authed.scopes.as_ref();
|
||||
if scopes.is_none()
|
||||
|| scopes
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|scope| !scope.starts_with("mcp:all") && !scope.starts_with("mcp:favorites") && !scope.starts_with("mcp:hub:"))
|
||||
|| scopes.unwrap().iter().all(|scope| {
|
||||
!scope.starts_with("mcp:all")
|
||||
&& !scope.starts_with("mcp:favorites")
|
||||
&& !scope.starts_with("mcp:hub:")
|
||||
})
|
||||
{
|
||||
tracing::error!("Unauthorized: missing mcp scope");
|
||||
return Err(Error::internal_error("Unauthorized: missing mcp scope".to_string(), None));
|
||||
return Err(ErrorData::internal_error(
|
||||
"Unauthorized: missing mcp scope".to_string(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -36,7 +40,7 @@ pub async fn get_item_schema(
|
||||
authed: &ApiAuthed,
|
||||
workspace_id: &str,
|
||||
item_type: &str,
|
||||
) -> Result<Option<Schema>, Error> {
|
||||
) -> Result<Option<Schema>, ErrorData> {
|
||||
let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type));
|
||||
sqlb.fields(&["o.schema"]);
|
||||
sqlb.and_where("o.path = ?".bind(&path));
|
||||
@@ -45,23 +49,23 @@ pub async fn get_item_schema(
|
||||
sqlb.and_where("o.draft_only IS NOT TRUE");
|
||||
let sql = sqlb.sql().map_err(|_e| {
|
||||
tracing::error!("failed to build sql: {}", _e);
|
||||
Error::internal_error("failed to build sql", None)
|
||||
ErrorData::internal_error("failed to build sql", None)
|
||||
})?;
|
||||
let mut tx = user_db
|
||||
.clone()
|
||||
.begin(authed)
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to begin transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?;
|
||||
let item = sqlx::query_as::<_, ItemSchema>(&sql)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|_e| {
|
||||
tracing::error!("failed to fetch item schema: {}", _e);
|
||||
Error::internal_error("failed to fetch item schema", None)
|
||||
ErrorData::internal_error("failed to fetch item schema", None)
|
||||
})?;
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to commit transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?;
|
||||
Ok(item.schema)
|
||||
}
|
||||
|
||||
@@ -70,29 +74,29 @@ pub async fn get_resources_types(
|
||||
user_db: &UserDB,
|
||||
authed: &ApiAuthed,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<ResourceType>, Error> {
|
||||
) -> Result<Vec<ResourceType>, ErrorData> {
|
||||
let mut sqlb = SqlBuilder::select_from("resource_type as o");
|
||||
sqlb.fields(&["o.name", "o.description"]);
|
||||
sqlb.and_where("o.workspace_id = ?".bind(&workspace_id));
|
||||
let sql = sqlb.sql().map_err(|_e| {
|
||||
tracing::error!("failed to build sql: {}", _e);
|
||||
Error::internal_error("failed to build sql", None)
|
||||
ErrorData::internal_error("failed to build sql", None)
|
||||
})?;
|
||||
let mut tx = user_db
|
||||
.clone()
|
||||
.begin(authed)
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to begin transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?;
|
||||
let rows = sqlx::query_as::<_, ResourceType>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.map_err(|_e| {
|
||||
tracing::error!("Failed to fetch resource types: {}", _e);
|
||||
Error::internal_error("failed to fetch resource types", None)
|
||||
ErrorData::internal_error("failed to fetch resource types", None)
|
||||
})?;
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to commit transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
@@ -102,30 +106,30 @@ pub async fn get_resources(
|
||||
authed: &ApiAuthed,
|
||||
workspace_id: &str,
|
||||
resource_type: &str,
|
||||
) -> Result<Vec<ResourceInfo>, Error> {
|
||||
) -> Result<Vec<ResourceInfo>, ErrorData> {
|
||||
let mut sqlb = SqlBuilder::select_from("resource as o");
|
||||
sqlb.fields(&["o.path", "o.description", "o.resource_type"]);
|
||||
sqlb.and_where("o.workspace_id = ?".bind(&workspace_id));
|
||||
sqlb.and_where("o.resource_type = ?".bind(&resource_type));
|
||||
let sql = sqlb.sql().map_err(|_e| {
|
||||
tracing::error!("failed to build sql: {}", _e);
|
||||
Error::internal_error("failed to build sql", None)
|
||||
ErrorData::internal_error("failed to build sql", None)
|
||||
})?;
|
||||
let mut tx = user_db
|
||||
.clone()
|
||||
.begin(authed)
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to begin transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?;
|
||||
let rows = sqlx::query_as::<_, ResourceInfo>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.map_err(|_e| {
|
||||
tracing::error!("Failed to fetch resources: {}", _e);
|
||||
Error::internal_error("failed to fetch resources", None)
|
||||
ErrorData::internal_error("failed to fetch resources", None)
|
||||
})?;
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to commit transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?;
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
@@ -138,7 +142,7 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
scope_type: &str,
|
||||
item_type: &str,
|
||||
scope_path: Option<&str>,
|
||||
) -> Result<Vec<T>, Error> {
|
||||
) -> Result<Vec<T>, ErrorData> {
|
||||
let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type));
|
||||
let fields = vec!["o.path", "o.summary", "o.description", "o.schema"];
|
||||
sqlb.fields(&fields);
|
||||
@@ -157,9 +161,15 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
|
||||
// scope path is always a folder path, format is f/my_folder/*
|
||||
if let Some(scope_path) = scope_path {
|
||||
if scope_path.split("/").count() != 3 || !scope_path.starts_with("f/") || !scope_path.ends_with("/*") {
|
||||
return Err(Error::internal_error(
|
||||
format!("Invalid folder format: {}, expected format is f/my_folder/*", scope_path),
|
||||
if scope_path.split("/").count() != 3
|
||||
|| !scope_path.starts_with("f/")
|
||||
|| !scope_path.ends_with("/*")
|
||||
{
|
||||
return Err(ErrorData::internal_error(
|
||||
format!(
|
||||
"Invalid folder format: {}, expected format is f/my_folder/*",
|
||||
scope_path
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
@@ -177,23 +187,23 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
.limit(100);
|
||||
let sql = sqlb.sql().map_err(|_e| {
|
||||
tracing::error!("failed to build sql: {}", _e);
|
||||
Error::internal_error("failed to build sql", None)
|
||||
ErrorData::internal_error("failed to build sql", None)
|
||||
})?;
|
||||
let mut tx = user_db
|
||||
.clone()
|
||||
.begin(authed)
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to begin transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to begin transaction", None))?;
|
||||
let rows = sqlx::query_as::<_, T>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
.await
|
||||
.map_err(|_e| {
|
||||
tracing::error!("Failed to fetch {}: {}", item_type, _e);
|
||||
Error::internal_error(format!("failed to fetch {}", item_type), None)
|
||||
ErrorData::internal_error(format!("failed to fetch {}", item_type), None)
|
||||
})?;
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|_e| Error::internal_error("failed to commit transaction", None))?;
|
||||
.map_err(|_e| ErrorData::internal_error("failed to commit transaction", None))?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
@@ -201,7 +211,7 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
pub async fn get_scripts_from_hub(
|
||||
db: &DB,
|
||||
scope_integrations: Option<&str>,
|
||||
) -> Result<Vec<HubScriptInfo>, Error> {
|
||||
) -> Result<Vec<HubScriptInfo>, ErrorData> {
|
||||
let query_params = Some(vec![
|
||||
("limit", "100".to_string()),
|
||||
("with_schema", "true".to_string()),
|
||||
@@ -213,34 +223,34 @@ pub async fn get_scripts_from_hub(
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to get items from hub: {}", e);
|
||||
Error::internal_error(format!("Failed to get items from hub: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to get items from hub: {}", e), None)
|
||||
})?;
|
||||
|
||||
|
||||
use axum::body::to_bytes;
|
||||
let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| {
|
||||
tracing::error!("Failed to read response body: {}", e);
|
||||
Error::internal_error(format!("Failed to read response body: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to read response body: {}", e), None)
|
||||
})?;
|
||||
let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| {
|
||||
tracing::error!("Failed to decode response body: {}", e);
|
||||
Error::internal_error(format!("Failed to decode response body: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to decode response body: {}", e), None)
|
||||
})?;
|
||||
let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| {
|
||||
tracing::error!("Failed to parse hub response: {}", e);
|
||||
Error::internal_error(format!("Failed to parse hub response: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to parse hub response: {}", e), None)
|
||||
})?;
|
||||
|
||||
Ok(hub_response.asks)
|
||||
}
|
||||
|
||||
/// Get the schema for a Hub script
|
||||
pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result<Option<Schema>, Error> {
|
||||
pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result<Option<Schema>, ErrorData> {
|
||||
let strip_path = StripPath(path.to_string());
|
||||
let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to get hub script: {}", e);
|
||||
Error::internal_error(format!("Failed to get hub script: {}", e), None)
|
||||
ErrorData::internal_error(format!("Failed to get hub script: {}", e), None)
|
||||
})?;
|
||||
match serde_json::from_str::<Schema>(res.schema.get()) {
|
||||
Ok(schema) => Ok(Some(schema)),
|
||||
@@ -249,4 +259,4 @@ pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result<Option<Schema>
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
//! Contains functions for transforming Windmill schemas into MCP-compatible formats,
|
||||
//! including resource enrichment and schema conversion utilities.
|
||||
|
||||
use rmcp::Error;
|
||||
use rmcp::ErrorData;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::scripts::Schema;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use super::models::{SchemaType, ResourceInfo, ResourceType};
|
||||
use super::database::get_resources;
|
||||
use super::models::{ResourceInfo, ResourceType, SchemaType};
|
||||
use super::transform::apply_key_transformation;
|
||||
use crate::db::ApiAuthed;
|
||||
|
||||
/// Convert a Windmill Schema to a SchemaType
|
||||
pub fn convert_schema_to_schema_type(schema: Option<Schema>) -> SchemaType {
|
||||
@@ -35,7 +35,7 @@ pub async fn transform_schema_for_resources(
|
||||
w_id: &str,
|
||||
resources_cache: &mut HashMap<String, Vec<ResourceInfo>>,
|
||||
resources_types: &Vec<ResourceType>,
|
||||
) -> Result<SchemaType, Error> {
|
||||
) -> Result<SchemaType, ErrorData> {
|
||||
let mut schema_obj: SchemaType = schema.clone();
|
||||
|
||||
// replace invalid char in property key with underscore
|
||||
@@ -71,24 +71,15 @@ pub async fn transform_schema_for_resources(
|
||||
let resource_type_obj = resource_type.cloned();
|
||||
|
||||
if !resources_cache.contains_key(&resource_type_key) {
|
||||
let available_resources = get_resources(
|
||||
user_db,
|
||||
authed,
|
||||
&w_id,
|
||||
&resource_type_key,
|
||||
)
|
||||
.await;
|
||||
let available_resources =
|
||||
get_resources(user_db, authed, &w_id, &resource_type_key).await;
|
||||
|
||||
match available_resources {
|
||||
Ok(cache_data) => {
|
||||
resources_cache
|
||||
.insert(resource_type_key.clone(), cache_data);
|
||||
resources_cache.insert(resource_type_key.clone(), cache_data);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to fetch resource cache data: {}",
|
||||
e
|
||||
);
|
||||
tracing::error!("Failed to fetch resource cache data: {}", e);
|
||||
continue; // Skip this property if fetching failed
|
||||
}
|
||||
}
|
||||
@@ -111,24 +102,16 @@ pub async fn transform_schema_for_resources(
|
||||
),
|
||||
None => "An object parameter.".to_string()
|
||||
};
|
||||
prop_map.insert(
|
||||
"type".to_string(),
|
||||
Value::String("string".to_string()),
|
||||
);
|
||||
prop_map.insert(
|
||||
"description".to_string(),
|
||||
Value::String(description),
|
||||
);
|
||||
prop_map
|
||||
.insert("type".to_string(), Value::String("string".to_string()));
|
||||
prop_map.insert("description".to_string(), Value::String(description));
|
||||
if resources_count > 0 {
|
||||
let resources_description = resource_cache
|
||||
.iter()
|
||||
.map(|resource| {
|
||||
format!(
|
||||
"{}: $res:{}",
|
||||
resource
|
||||
.description
|
||||
.as_deref()
|
||||
.unwrap_or("No title"),
|
||||
resource.description.as_deref().unwrap_or("No title"),
|
||||
resource.path
|
||||
)
|
||||
})
|
||||
@@ -157,4 +140,4 @@ pub async fn transform_schema_for_resources(
|
||||
}
|
||||
|
||||
Ok(schema_obj)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1808,7 +1808,7 @@ async fn edit_error_handler(
|
||||
SET
|
||||
error_handler = NULL,
|
||||
error_handler_extra_args = NULL,
|
||||
error_handler_muted_on_cancel = NULL
|
||||
error_handler_muted_on_cancel = false
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
"#,
|
||||
|
||||
@@ -2054,6 +2054,13 @@ pub async fn handle_python_reqs(
|
||||
.unwrap_or(Err(anyhow!("Problem by joining handle")))
|
||||
{
|
||||
failed = true;
|
||||
append_logs(
|
||||
&job_id,
|
||||
w_id,
|
||||
format!("\nEnv installation failed: {:?}", e),
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
tracing::warn!(
|
||||
workspace_id = %w_id,
|
||||
"Env installation failed: {:?}",
|
||||
|
||||
+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.545.0";
|
||||
export const VERSION = "v1.546.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -14,7 +14,6 @@ import { FSFSElement, elementsToMap, ignoreF } from "../sync/sync.ts";
|
||||
import { Flow } from "../../../gen/types.gen.ts";
|
||||
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts";
|
||||
|
||||
|
||||
export interface FlowFile {
|
||||
summary: string;
|
||||
description?: string;
|
||||
@@ -55,7 +54,7 @@ export async function pushFlow(
|
||||
async (path: string) => await Deno.readTextFile(localPath + path),
|
||||
log,
|
||||
localPath,
|
||||
SEP,
|
||||
SEP
|
||||
);
|
||||
|
||||
if (flow) {
|
||||
@@ -191,7 +190,7 @@ async function run(
|
||||
workspace: workspace.workspaceId,
|
||||
id,
|
||||
});
|
||||
log.info(jobInfo.result ?? {});
|
||||
log.info(JSON.stringify(jobInfo.result ?? {}, null, 2));
|
||||
}
|
||||
|
||||
async function generateLocks(
|
||||
@@ -201,14 +200,23 @@ async function generateLocks(
|
||||
} & SyncOptions,
|
||||
folder: string | undefined
|
||||
) {
|
||||
const useRawReqs = opts.useRawRequirements || Deno.env.get("USE_RAW_REQUIREMENTS") === "true";
|
||||
const useRawReqs =
|
||||
opts.useRawRequirements || Deno.env.get("USE_RAW_REQUIREMENTS") === "true";
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
opts = await mergeConfigWithConfigFile(opts);
|
||||
if (folder) {
|
||||
// read script metadata file
|
||||
await generateFlowLockInternal(folder, false, workspace, opts, undefined, undefined, useRawReqs);
|
||||
await generateFlowLockInternal(
|
||||
folder,
|
||||
false,
|
||||
workspace,
|
||||
opts,
|
||||
undefined,
|
||||
undefined,
|
||||
useRawReqs
|
||||
);
|
||||
} else {
|
||||
const ignore = await ignoreF(opts);
|
||||
const elems = Object.keys(
|
||||
@@ -229,7 +237,15 @@ async function generateLocks(
|
||||
let hasAny = false;
|
||||
|
||||
for (const folder of elems) {
|
||||
const candidate = await generateFlowLockInternal(folder, true, workspace, opts, undefined, undefined, useRawReqs);
|
||||
const candidate = await generateFlowLockInternal(
|
||||
folder,
|
||||
true,
|
||||
workspace,
|
||||
opts,
|
||||
undefined,
|
||||
undefined,
|
||||
useRawReqs
|
||||
);
|
||||
if (candidate) {
|
||||
hasAny = true;
|
||||
log.info(colors.green(`+ ${candidate}`));
|
||||
@@ -251,7 +267,15 @@ async function generateLocks(
|
||||
return;
|
||||
}
|
||||
for (const folder of elems) {
|
||||
await generateFlowLockInternal(folder, false, workspace, opts,undefined, undefined, useRawReqs);
|
||||
await generateFlowLockInternal(
|
||||
folder,
|
||||
false,
|
||||
workspace,
|
||||
opts,
|
||||
undefined,
|
||||
undefined,
|
||||
useRawReqs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
parseMetadataFile,
|
||||
} from "../../utils/metadata.ts";
|
||||
import {
|
||||
LanguageWithRawReqsSupport,
|
||||
LanguageWithRawReqsSupport,
|
||||
ScriptLanguage,
|
||||
inferContentTypeFromFilePath,
|
||||
languagesWithRawReqsSupport,
|
||||
@@ -114,8 +114,14 @@ export async function findResourceFile(path: string) {
|
||||
|
||||
if (currentBranch) {
|
||||
// Add branch-specific candidates at the beginning (higher priority)
|
||||
const branchSpecificJSON = specificItems.toBranchSpecificPath(contentBasePathJSON, currentBranch);
|
||||
const branchSpecificYAML = specificItems.toBranchSpecificPath(contentBasePathYAML, currentBranch);
|
||||
const branchSpecificJSON = specificItems.toBranchSpecificPath(
|
||||
contentBasePathJSON,
|
||||
currentBranch
|
||||
);
|
||||
const branchSpecificYAML = specificItems.toBranchSpecificPath(
|
||||
contentBasePathYAML,
|
||||
currentBranch
|
||||
);
|
||||
candidates.unshift(branchSpecificJSON, branchSpecificYAML);
|
||||
}
|
||||
|
||||
@@ -624,7 +630,7 @@ export const exts = [
|
||||
".nu",
|
||||
".playbook.yml",
|
||||
".java",
|
||||
".rb"
|
||||
".rb",
|
||||
// for related places search: ADD_NEW_LANG
|
||||
];
|
||||
|
||||
@@ -727,7 +733,7 @@ async function run(
|
||||
if (opts.silent) {
|
||||
console.log(result);
|
||||
} else {
|
||||
log.info(result);
|
||||
log.info(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -885,7 +891,10 @@ async function bootstrap(
|
||||
);
|
||||
}
|
||||
|
||||
export type GlobalDeps = Map<LanguageWithRawReqsSupport, Record<string, string>>;
|
||||
export type GlobalDeps = Map<
|
||||
LanguageWithRawReqsSupport,
|
||||
Record<string, string>
|
||||
>;
|
||||
|
||||
export async function findGlobalDeps(): Promise<GlobalDeps> {
|
||||
var globalDeps: GlobalDeps = new Map();
|
||||
@@ -895,9 +904,8 @@ export async function findGlobalDeps(): Promise<GlobalDeps> {
|
||||
return (
|
||||
!isDir &&
|
||||
// Skip if the filename is not one of lockfile names
|
||||
!(languagesWithRawReqsSupport.some(
|
||||
lockfile =>
|
||||
p.endsWith(SEP + lockfile.rrFilename))
|
||||
!languagesWithRawReqsSupport.some((lockfile) =>
|
||||
p.endsWith(SEP + lockfile.rrFilename)
|
||||
)
|
||||
);
|
||||
}, els)) {
|
||||
@@ -906,9 +914,11 @@ export async function findGlobalDeps(): Promise<GlobalDeps> {
|
||||
|
||||
// Iterate over available languages to find which lockfile
|
||||
languagesWithRawReqsSupport.map((lock) => {
|
||||
if (entry.path.endsWith(lock.rrFilename)){
|
||||
if (entry.path.endsWith(lock.rrFilename)) {
|
||||
const current = globalDeps.get(lock) ?? {};
|
||||
current[entry.path.substring(0, entry.path.length - lock.rrFilename.length)] = content;
|
||||
current[
|
||||
entry.path.substring(0, entry.path.length - lock.rrFilename.length)
|
||||
] = content;
|
||||
globalDeps.set(lock, current);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ export async function pushWorkspaceSettings(
|
||||
error_handler: remoteSettings.error_handler,
|
||||
error_handler_extra_args: remoteSettings.error_handler_extra_args,
|
||||
error_handler_muted_on_cancel:
|
||||
remoteSettings.error_handler_muted_on_cancel,
|
||||
remoteSettings.error_handler_muted_on_cancel ?? false,
|
||||
ai_config: remoteSettings.ai_config,
|
||||
large_file_storage: remoteSettings.large_file_storage,
|
||||
git_sync: remoteSettings.git_sync,
|
||||
@@ -166,8 +166,8 @@ export async function pushWorkspaceSettings(
|
||||
localSettings.error_handler_extra_args,
|
||||
settings.error_handler_extra_args
|
||||
) ||
|
||||
localSettings.error_handler_muted_on_cancel !=
|
||||
settings.error_handler_muted_on_cancel
|
||||
(localSettings.error_handler_muted_on_cancel ?? false) !=
|
||||
(settings.error_handler_muted_on_cancel ?? false)
|
||||
) {
|
||||
log.debug(`Updating error handler...`);
|
||||
await wmill.editErrorHandler({
|
||||
@@ -176,7 +176,7 @@ export async function pushWorkspaceSettings(
|
||||
error_handler: localSettings.error_handler,
|
||||
error_handler_extra_args: localSettings.error_handler_extra_args,
|
||||
error_handler_muted_on_cancel:
|
||||
localSettings.error_handler_muted_on_cancel,
|
||||
localSettings.error_handler_muted_on_cancel ?? false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -68,7 +68,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.545.0";
|
||||
export const VERSION = "1.546.1";
|
||||
|
||||
export const WM_FORK_PREFIX = "wm-fork";
|
||||
|
||||
@@ -187,7 +187,7 @@ async function main() {
|
||||
log.setup({
|
||||
handlers: {
|
||||
console: new log.ConsoleHandler(LOG_LEVEL, {
|
||||
formatter: ({ msg }) => `${msg}`,
|
||||
formatter: ({ msg }) => msg,
|
||||
useColors: isWin ? false : true,
|
||||
}),
|
||||
},
|
||||
|
||||
+17
-5
@@ -301,18 +301,30 @@ export function getTypeStrFromPath(
|
||||
}
|
||||
|
||||
export function removeType(str: string, type: string) {
|
||||
// Normalize path for cross-platform compatibility and convert to forward slashes for API consistency
|
||||
const normalizedStr = path.normalize(str).replaceAll(SEP, "/");
|
||||
|
||||
if (
|
||||
!str.endsWith("." + type + ".yaml") &&
|
||||
!str.endsWith("." + type + ".json")
|
||||
!normalizedStr.endsWith("." + type + ".yaml") &&
|
||||
!normalizedStr.endsWith("." + type + ".json")
|
||||
) {
|
||||
throw new Error(str + " does not end with ." + type + ".(yaml|json)");
|
||||
}
|
||||
return str.slice(0, str.length - type.length - 6);
|
||||
return normalizedStr.slice(0, normalizedStr.length - type.length - 6);
|
||||
}
|
||||
|
||||
export function removePathPrefix(str: string, prefix: string) {
|
||||
if (!str.startsWith(prefix + "/")) {
|
||||
// Normalize paths for cross-platform compatibility and convert to forward slashes for API consistency
|
||||
const normalizedStr = path.normalize(str).replaceAll(SEP, "/");
|
||||
const normalizedPrefix = path.normalize(prefix).replaceAll(SEP, "/");
|
||||
|
||||
// Handle exact match case
|
||||
if (normalizedStr === normalizedPrefix) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!normalizedStr.startsWith(normalizedPrefix + "/")) {
|
||||
throw new Error(str + " does not start with " + prefix);
|
||||
}
|
||||
return str.slice(prefix.length + 1);
|
||||
return normalizedStr.slice(normalizedPrefix.length + 1);
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.545.0",
|
||||
"version": "1.546.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.545.0",
|
||||
"version": "1.546.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.545.0",
|
||||
"version": "1.546.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -36,14 +36,16 @@
|
||||
|
||||
let { value = $bindable(), helperScript, format, otherArgs: otherArgs }: Props = $props()
|
||||
|
||||
const [inputType, entrypoint] = format.includes('-') ? format.split('-', 2) : [format, '']
|
||||
let [inputType, entrypoint] = $derived(format.includes('-') ? format.split('-', 2) : [format, ''])
|
||||
|
||||
const isMultiple = inputType === 'dynmultiselect'
|
||||
const isSelect = inputType === 'dynselect' || inputType === 'dynmultiselect'
|
||||
let isMultiple = $derived(inputType === 'dynmultiselect')
|
||||
let isSelect = $derived(inputType === 'dynselect' || inputType === 'dynmultiselect')
|
||||
|
||||
if (isMultiple && value === undefined) {
|
||||
value = []
|
||||
}
|
||||
$effect.pre(() => {
|
||||
if (isMultiple && value === undefined) {
|
||||
value = []
|
||||
}
|
||||
})
|
||||
|
||||
let resultJobLoader: JobLoader | undefined = $state()
|
||||
let _items = usePromise(getItemsFromOptions, { clearValueOnRefresh: false })
|
||||
@@ -97,7 +99,7 @@
|
||||
|
||||
$effect(() => {
|
||||
if (_items.value && value !== undefined && isSelect) {
|
||||
if (isMultiple && Array.isArray(value)) {
|
||||
if (isMultiple && Array.isArray(value) && Array.isArray(_items.value)) {
|
||||
const availableValues = new Set(_items.value.map((x) => x.value))
|
||||
const filteredValue = value.filter((v) => availableValues.has(v))
|
||||
if (filteredValue.length !== value.length) {
|
||||
|
||||
@@ -372,6 +372,14 @@
|
||||
}
|
||||
|
||||
function updateDynCode(functionName: string, lang: ScriptLang = 'bun') {
|
||||
if (
|
||||
(lang == 'bun' && dynCode?.includes(`function ${functionName}`)) ||
|
||||
(lang == 'python3' && dynCode?.includes(`def ${functionName}`))
|
||||
) {
|
||||
// Don't add the function if it already exists
|
||||
return
|
||||
}
|
||||
|
||||
const generateFn = DynamicInput.getGenerateTemplateFn(lang)
|
||||
const code = generateFn(functionName)
|
||||
dynCode = dynCode ? dynCode.concat(code) : code
|
||||
@@ -701,6 +709,15 @@
|
||||
nullable: undefined,
|
||||
required: undefined
|
||||
}
|
||||
|
||||
if (
|
||||
isDynMultiselect &&
|
||||
args &&
|
||||
!Array.isArray(args?.[argName])
|
||||
) {
|
||||
args[argName] = []
|
||||
}
|
||||
|
||||
if (isS3) {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputCat } from '$lib/utils'
|
||||
import type { InputCat, DynamicInput as DynamicInputTypes } from '$lib/utils'
|
||||
import { createEventDispatcher, getContext, untrack } from 'svelte'
|
||||
import { computeShow } from '$lib/utils'
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
class?: string
|
||||
editor?: SimpleEditor | undefined
|
||||
otherArgs?: Record<string, InputTransform>
|
||||
helperScript?: DynamicInputTypes.HelperScript | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -83,7 +84,8 @@
|
||||
hideHelpButton = false,
|
||||
class: className = '',
|
||||
editor = $bindable(undefined),
|
||||
otherArgs = {}
|
||||
otherArgs = {},
|
||||
helperScript = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let monaco: SimpleEditor | undefined = $state(undefined)
|
||||
@@ -620,6 +622,7 @@
|
||||
</Button>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
disabled={inputCat === 'dynamic'}
|
||||
small
|
||||
light
|
||||
tooltip="JavaScript expression ('flow_input' or 'results')."
|
||||
@@ -632,7 +635,7 @@
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
{#if propPickerWrapperContext}
|
||||
{#if propPickerWrapperContext && inputCat !== 'dynamic'}
|
||||
<FlowPlugConnect
|
||||
id="flow-editor-plug"
|
||||
{connecting}
|
||||
@@ -733,6 +736,13 @@
|
||||
nullable={schema.properties[argName].nullable}
|
||||
bind:title={schema.properties[argName].title}
|
||||
bind:placeholder={schema.properties[argName].placeholder}
|
||||
{helperScript}
|
||||
otherArgs={Object.fromEntries(
|
||||
Object.entries(otherArgs).map(([key, transform]) => [
|
||||
key,
|
||||
transform?.type === 'static' ? transform.value : transform?.expr
|
||||
])
|
||||
)}
|
||||
/>
|
||||
|
||||
{#if shouldShowS3ArrayHelper}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import { VariableService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
|
||||
import { untrack } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import StepInputsGen from './copilot/StepInputsGen.svelte'
|
||||
@@ -23,6 +23,7 @@
|
||||
pickableProperties?: PickableProperties | undefined
|
||||
enableAi?: boolean
|
||||
class?: string
|
||||
helperScript?: DynamicInputTypes.HelperScript
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -35,7 +36,8 @@
|
||||
noDynamicToggle = false,
|
||||
pickableProperties = undefined,
|
||||
enableAi = false,
|
||||
class: clazz = ''
|
||||
class: clazz = '',
|
||||
helperScript = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = $state({})
|
||||
@@ -115,6 +117,7 @@
|
||||
{noDynamicToggle}
|
||||
{pickableProperties}
|
||||
{enableAi}
|
||||
{helperScript}
|
||||
otherArgs={Object.fromEntries(
|
||||
Object.entries(args ?? {}).filter(([key]) => key !== argName)
|
||||
)}
|
||||
|
||||
@@ -81,13 +81,15 @@ ${Object.entries(args)
|
||||
</Head>
|
||||
|
||||
<tbody class="divide-y w-full">
|
||||
{#if args && Object.keys(args).length > 0}
|
||||
{#if args && typeof args === 'object' && Object.keys(args).length > 0}
|
||||
{#each Object.entries(args).sort((a, b) => a[0].localeCompare(b[0])) as [arg, value]}
|
||||
<Row>
|
||||
<Cell first>{arg}</Cell>
|
||||
<Cell><ArgInfo {value} /></Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{:else if args && typeof args !== 'object'}
|
||||
<Row><Cell>Argument is not an object (type: {typeof args})</Cell></Row>
|
||||
{:else if args}
|
||||
<Row><Cell>No arguments</Cell></Row>
|
||||
{:else}
|
||||
|
||||
+14
-4
@@ -21,6 +21,7 @@
|
||||
import InitializeComponent from '../../helpers/InitializeComponent.svelte'
|
||||
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
|
||||
import RunnableComponent from '../../helpers/RunnableComponent.svelte'
|
||||
import { CancelablePromise } from '$lib/gen'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -163,6 +164,8 @@
|
||||
$effect(() => {
|
||||
searchValue !== undefined && untrack(() => updateSearchInOutputs())
|
||||
})
|
||||
|
||||
let ignoreFirst = true
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['aggridinfinitecomponent'].initialData.configuration) as key (key)}
|
||||
@@ -194,10 +197,17 @@
|
||||
bind:result
|
||||
bind:loading
|
||||
bind:runnableComponent
|
||||
on:recompute={() => {
|
||||
console.log('recompute')
|
||||
clear()
|
||||
}}
|
||||
preventDefaultRefresh
|
||||
overrideCallback={() =>
|
||||
new CancelablePromise(async (resolve) => {
|
||||
if (ignoreFirst) {
|
||||
ignoreFirst = false
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
clear()
|
||||
resolve()
|
||||
})}
|
||||
{render}
|
||||
autoRefresh={true}
|
||||
allowConcurentRequests
|
||||
|
||||
@@ -118,11 +118,12 @@
|
||||
}
|
||||
})
|
||||
|
||||
let keys = $derived(Object.keys(result[0] ?? []).filter((x) => x !== '__index'))
|
||||
|
||||
async function syncColumns() {
|
||||
const gridItem = findGridItem($app, id)
|
||||
|
||||
if (gridItem && result) {
|
||||
const keys = Object.keys(result[0] ?? {}) ?? []
|
||||
const conf = gridItem.data.configuration.columnDefs as ColumnDefsConfiguration
|
||||
|
||||
const newColumns: WindmillColumnDef[] = keys.map((key) => ({
|
||||
@@ -171,7 +172,7 @@
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<div class="text-xs"> No columns definition found. Columns found in data: </div>
|
||||
<div class="text-sm flex flex-row gap-2">
|
||||
{#each Object.keys(result[0] ?? []) as key}
|
||||
{#each keys as key}
|
||||
<Badge small color="dark-gray">{key}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -32,13 +32,17 @@
|
||||
color="light"
|
||||
size="xs2"
|
||||
btnClasses={twMerge(loading ? ' bg-blue-100 dark:bg-blue-400' : '', 'transition-all')}
|
||||
on:click={() => {
|
||||
on:click={(e) => {
|
||||
if (buttonHover && loading) {
|
||||
cancelCallbacks?.forEach((cb) => cb.cancel())
|
||||
} else {
|
||||
cancelCallbacks = $runnableComponents[id]?.cb?.map((cb) => cb())
|
||||
}
|
||||
}}
|
||||
on:pointerdown={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
iconOnly
|
||||
/>
|
||||
{#snippet text()}
|
||||
|
||||
@@ -163,48 +163,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// $: sendUserToast('args' + JSON.stringify(runnableInputValues) + Boolean(extraQueryParams) || args)
|
||||
// $: console.log(runnableInputValues)
|
||||
let firstRefresh = true
|
||||
|
||||
// on:started={(e) => {
|
||||
// console.log('started', e.detail)
|
||||
// loading = true
|
||||
// setJobId(e.detail)
|
||||
// dispatch('started', e.detail)
|
||||
// }}
|
||||
// on:done={(e) => {
|
||||
// lastJobId = e.detail.id
|
||||
// setResult(e.detail.result, e.detail.id)
|
||||
// loading = false
|
||||
// dispatch('done', { id: e.detail?.id, result: e.detail?.result })
|
||||
// }}
|
||||
// on:cancel={(e) => {
|
||||
// let jobId = e.detail
|
||||
// console.debug('cancel', jobId)
|
||||
// let job = $jobsById[jobId]
|
||||
// if (job && job.created_at && !job.duration_ms) {
|
||||
// $jobsById[jobId] = {
|
||||
// ...job,
|
||||
// started_at: job.started_at ?? Date.now(),
|
||||
// duration_ms: Date.now() - (job.started_at ?? job.created_at)
|
||||
// }
|
||||
// }
|
||||
// dispatch('cancel', { id: e.detail })
|
||||
// }}
|
||||
// on:running={(e) => {
|
||||
// let jobId = e.detail
|
||||
// let job = $jobsById[jobId]
|
||||
// if (job && !job.started_at) {
|
||||
// $jobsById[jobId] = { ...job, started_at: Date.now() }
|
||||
// }
|
||||
// }}
|
||||
// on:doneError={(e) => {
|
||||
// setResult({ error: e.detail.error }, e.detail.id)
|
||||
// loading = false
|
||||
// dispatch('doneError', { id: e.detail.id, result: e.detail.result })
|
||||
// }}
|
||||
|
||||
type RunnableCallback = {
|
||||
onDone?: (r: any) => void
|
||||
onCancel?: () => void
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
onSuccess?: (result: any) => void
|
||||
children?: import('svelte').Snippet
|
||||
nonRenderedPlaceholder?: import('svelte').Snippet
|
||||
preventDefaultRefresh?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
export let horizontal: boolean = false
|
||||
export let gap: 'none' | 'sm' | 'md' | 'lg' = 'sm'
|
||||
export let justify: 'start' | 'center' | 'end' | 'between' = 'start'
|
||||
export let wFull = true
|
||||
export let hFull = true
|
||||
interface Props {
|
||||
horizontal?: boolean
|
||||
gap?: 'none' | 'sm' | 'md' | 'lg'
|
||||
justify?: 'start' | 'center' | 'end' | 'between'
|
||||
wFull?: boolean
|
||||
hFull?: boolean
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
horizontal = false,
|
||||
gap = 'sm',
|
||||
justify = 'start',
|
||||
wFull = true,
|
||||
hFull = true,
|
||||
children
|
||||
}: Props = $props()
|
||||
|
||||
const gapMap = {
|
||||
none: '',
|
||||
@@ -27,7 +39,7 @@
|
||||
justify
|
||||
]}"
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
@@ -39,6 +51,6 @@
|
||||
justifyMap[justify]
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -570,6 +570,14 @@
|
||||
}
|
||||
extraLib={stepPropPicker.extraLib}
|
||||
{enableAi}
|
||||
helperScript={(flowModule?.value as RawScript).content &&
|
||||
(flowModule?.value as RawScript).language
|
||||
? {
|
||||
source: 'inline',
|
||||
code: (flowModule?.value as RawScript).content ?? '',
|
||||
lang: (flowModule?.value as RawScript).language ?? 'deno'
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
time?: number
|
||||
starred?: boolean
|
||||
has_draft?: boolean
|
||||
hash?: string
|
||||
}
|
||||
|
||||
type TableScript = TableItem<Script, 'script'>
|
||||
@@ -553,7 +554,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<div class="border rounded-md">
|
||||
{#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)}
|
||||
{#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path + (item.hash ? '/' + item.hash : ''))}
|
||||
<Item
|
||||
{item}
|
||||
on:scriptChanged={() => loadScripts(includeWithoutMain)}
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.545.0"
|
||||
wmill_pg = ">=1.545.0"
|
||||
wmill = ">=1.546.1"
|
||||
wmill_pg = ">=1.546.1"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.545.0
|
||||
version: 1.546.1
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.545.0'
|
||||
ModuleVersion = '1.546.1'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill-pg"
|
||||
version = "1.545.0"
|
||||
version = "1.546.1"
|
||||
description = "An extension client for the wmill client library focused on pg"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.545.0",
|
||||
"version": "1.546.1",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.545.0",
|
||||
"version": "1.546.1",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.545.0
|
||||
1.546.1
|
||||
|
||||
Reference in New Issue
Block a user