diff --git a/.claude/skills/adding-a-trigger/SKILL.md b/.claude/skills/adding-a-trigger/SKILL.md new file mode 100644 index 0000000000..c38cc1516d --- /dev/null +++ b/.claude/skills/adding-a-trigger/SKILL.md @@ -0,0 +1,265 @@ +--- +name: adding-a-trigger +description: Checklist for adding a new TriggerCrud-based trigger type to Windmill (Azure, GCP, Kafka, etc.). Use when wiring a new trigger kind across backend, frontend, CLI, and capture infrastructure. +--- + +# Skill: Adding a New Trigger Type + +Use this skill when adding a trigger kind that implements `TriggerCrud` (Kafka, GCP, Azure, MQTT, SQS, NATS, Postgres, Email…). For native triggers (Nextcloud, Google Drive — things wired through `windmill-native-triggers`), use the `native-trigger` skill instead. + +The goal of this doc is to enumerate every file that needs to change. Missing any one of them leads to silent regressions: sync drops the trigger, capture button does nothing, workspace forks lose it, sidebar counters undercount. Follow the checklist top-to-bottom — each section is independent enough to be validated on its own. + +Throughout this doc, substitute `{kind}` for the new trigger kind (`azure`, `kafka`, …), `{Kind}` for PascalCase (`Azure`, `Kafka`), `{KIND}` for SCREAMING (`AZURE`, `KAFKA`). + +## Reference implementations + +- **GCP** — closest analogue to Azure. Has push + pull, OIDC auth, ARM-like resource paths, capture handler. Grep for `gcp_trigger` / `GcpTrigger`. +- **Kafka** — simpler (pull-only, streaming). Good for trivial integrations. +- **Azure** — most recently added (2026). Shared-secret push auth, Event Grid namespaces + basic topics, ARM resource discovery, Namespace-pull data-plane. Grep for `azure_trigger` / `AzureTrigger`. + +## 1. Database migration + +Create a migration: `cargo sqlx migrate add -r add_{kind}_trigger` from `backend/`. Never write timestamps manually. + +The `up.sql` usually defines: +- An optional enum type (e.g. `AZURE_MODE`) if the trigger has sub-kinds +- The `{kind}_trigger` table with at minimum these columns (mirrored from kafka/gcp): + - primary: `(workspace_id, path)` + - `script_path`, `is_flow`, `enabled`, `mode`, `permissioned_as`, `edited_by`, `email` + - `edited_at`, `error`, `server_id`, `last_server_ping` + - `error_handler_path`, `error_handler_args jsonb`, `retry jsonb` + - trigger-specific fields +- Indexes on foreign keys + any frequently-filtered columns +- Foreign key to `workspace` + +Down migration drops the table and any enum types. + +## 2. Backend crate (`windmill-trigger-{kind}`) + +Create a new crate under `backend/windmill-trigger-{kind}/` with: + +- `Cargo.toml`: features `enterprise`, `private` if EE, standard deps +- `src/lib.rs`: `pub use mod_ee::*;` behind `#[cfg(all(feature = "enterprise", feature = "private"))]` +- `src/mod_ee.rs`: core types + helpers +- `src/handler_ee.rs`: `TriggerCrud` impl + route handlers +- `src/listener_ee.rs`: (only if streaming/pull-based) `Listener` trait impl + +Required in `mod_ee.rs`: +- `{Kind}Config` struct (persisted shape, `FromRow`) +- `{Kind}ConfigRequest` struct (what API receives — usually similar to Config but with validation fields) +- `{Kind}Trigger` unit struct (implements the traits) +- `impl TriggerJobArgs for {Kind}Trigger` — sets `TRIGGER_KIND`, `Payload`, `v1_payload_fn` + +Required in `handler_ee.rs`: +- `#[async_trait] impl TriggerCrud for {Kind}Trigger` with: + - `type Trigger = Trigger<{Kind}Config>` + - `type TriggerConfigRequest = {Kind}ConfigRequest` + - `const ROUTE_PREFIX: &'static str = "/{kind}_triggers";` + - `const TABLE_NAME`, `ADDITIONAL_SELECT_FIELDS` + - `get_deployed_object`, `validate_config`, `create_trigger`, `update_trigger`, `delete_trigger`, `test_connection` + - `additional_routes` (optional — mount extra endpoints for things like ARM resource listing, topic discovery) + +Register the crate in `backend/Cargo.toml` as a workspace member and as a dep of `windmill-api` behind the feature flag. + +## 3. Wire into `windmill-api` (feature-gated everywhere) + +**`backend/windmill-api/src/triggers/handler.rs`** — mount the trigger crate: +```rust +#[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))] +{ + use crate::triggers::{kind}::{Kind}Trigger; + router = router.nest({Kind}Trigger::ROUTE_PREFIX, complete_trigger_routes({Kind}Trigger)); +} +``` + +**`backend/windmill-api/src/triggers/{kind}/mod.rs`** — re-export the crate: +```rust +pub use windmill_trigger_{kind}::*; +``` + +**`backend/windmill-api/src/lib.rs`** — if the trigger receives inbound pushes, add a webhook route: +```rust +.nest("/{kind}/w/{workspace_id}", { + #[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))] + { triggers::{kind}::handler_oss::{kind}_push_route_handler() } + #[cfg(not(...))] + { Router::new() } +}) +``` + +## 4. `TriggerKind` enum (`backend/windmill-types/src/triggers.rs`) + +Already has slots for most triggers but verify your variant exists: +- Add `{Kind}` to the `TriggerKind` enum +- Add match arm in `to_key()` +- Add match arm in `from_str` +- Add match arm in `JobTriggerKind` (if jobs need kind tagging) + +## 5. OpenAPI (`backend/windmill-api/openapi.yaml`) + +This file is huge and the single most-forgotten place. Add: + +- `/w/{workspace}/{kind}_triggers/create` + `/update/{path}` + `/delete/{path}` + `/get/{path}` + `/list` + `/exists/{path}` + `/setmode/{path}` + `/test` paths (mirror gcp section) +- Any `additional_routes` your handler exposes (resource discovery, etc.) +- Schemas: `{Kind}Trigger`, `{Kind}TriggerData`, `{Kind}Mode` (if enum), `{Kind}DeliveryConfig`, helper request/response types +- Add `{kind}` to `CaptureTriggerKind` enum +- Add `{kind}_used: boolean` to the `UsedTriggers` response schema + +Regenerate frontend client: `npm run generate-backend-client` from `frontend/`. + +## 6. `UsedTriggers` + workspace export + +**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query. + +**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON). + +## 6.5 Hardcoded trigger-kind arrays (silent-failure hotspots) + +Several files keep **hardcoded arrays** of trigger kind strings. Miss one and ACL checks / user offboarding / trash drop your kind: + +- **`backend/windmill-api-groups/src/granular_acls.rs`** — `KINDS: [&str; N]`. **Increment N** (the compile error is cryptic otherwise). Controls which kinds accept granular ACL operations. +- **`backend/windmill-api-users/src/users.rs`** (`extra_perms_tables`) — which tables get `extra_perms` entries cleaned when a user is deleted. +- **`backend/windmill-api/src/offboarding.rs`** — three separate arrays (enumeration, fork-copy, and delete paths). **All three** need the new kind. +- **`backend/windmill-api/src/trash.rs`** — `valid_tables` for the trash / restore API. +- **`backend/windmill-git-sync/src/lib.rs`** — add a test assertion for `DeployedObject::{Kind}Trigger.get_kind() == "{kind}_trigger"` (the `get_kind` match arm itself lives in the enum impl — already required by the Rust compiler). +- **`backend/windmill-api-auth/src/scopes.rs`** — add the `{Kind}Triggers` variant to `ScopeDomain` enum + `as_str` match + `from_str` match. Required for the OAuth/token system to recognise `{kind}_triggers:read|write` scopes. +- **`backend/windmill-api/src/token.rs`** (`build_trigger_scope_domains` → `TRIGGER_DOMAINS`) — add `("{kind}_triggers", "{Kind display name}")` so the CreateToken UI's scope selector surfaces the `read` / `write` checkboxes. + +**OpenAPI enums** to extend (do NOT forget — generated client will allow it but server rejects as 400): +- `CaptureTriggerKind` enum +- Three `kind` enums under `/w/{workspace}/acls/{get,add,remove}/{kind}/{path}` (yes, same list repeated three times) + +After editing any of these, run a full `cargo check` with your feature flag + `gcp_trigger` + other core flags — the `KINDS: [&str; N]` length mismatch only surfaces when the crate compiles. + +## 7. Capture infrastructure (`backend/windmill-api/src/capture.rs`) + +If the trigger supports push delivery, it also needs a capture endpoint so users can test it: + +- `{Kind}TriggerConfig` struct (gated by feature flags) +- `TriggerConfig::{Kind}` variant +- `set_{kind}_trigger_config` function (creates the subscription/equivalent pointing at the capture URL — use your `manage_{kind}_subscription` helper with `trigger_mode=false`) +- Both real + no-op versions behind feature gates +- `TriggerKind::{Kind} => set_{kind}_trigger_config(...)` arm in `set_config` +- `{kind}_payload` async handler — validates auth (if any), processes payload, calls `insert_capture_payload` +- Route: `.route("/{kind}/{runnable_kind}/{*path}", post({kind}_payload))` inside `workspaced_unauthed_service` — and expand the surrounding `#[cfg(any(...))]` to include your feature flag + +## 8. CLI (`cli/`) — easy to miss, breaks sync silently + +Check all of these: + +**`cli/src/types.ts`:** +- Add `"{kind}"` to `TRIGGER_TYPES` array +- Add `"{kind}_trigger"` to `getTypeStrFromPath` return union +- Add match case in `getTypeStrFromPath`'s `typeEnding ===` chain +- Add `pushTrigger("{kind}", ...)` branch in `pushObj` + +**`cli/src/commands/trigger/trigger.ts`:** +- Import `{Kind}Trigger` type +- Add `{kind}: {Kind}Trigger` to the `Trigger` type map +- Add `{kind}: wmill.get{Kind}Trigger`, `update{Kind}Trigger`, `create{Kind}Trigger` to each function map +- Add `{kind}: { ... }` template to `triggerTemplates` +- Add `list{Kind}Triggers` call + spread in the `list` aggregation +- Update `--kind` option descriptions to mention the new kind + +**`cli/src/commands/sync/sync.ts`:** +- Add `path.endsWith(".{kind}_trigger" + ext)` in the file-type filter +- Add `typ == "{kind}_trigger"` in `getTypeOrder` +- Add `"{kind}_trigger"` to the delete-suffix regex (~line 3092) +- Add a `case "{kind}_trigger"` in the delete switch + +**`cli/src/guidance/skills.ts`** — **DO NOT EDIT DIRECTLY**. It's auto-generated by `system_prompts/generate.py`. Instead: +- Edit `system_prompts/utils.py` → append `('{Kind}Trigger', '{kind}_trigger')` to the `SCHEMA_MAPPINGS['triggers']` list (this is the master list — the one in `generate.py` is duplicated and `utils.py` wins) +- Then run `python3 system_prompts/generate.py` — it regenerates `cli/src/guidance/skills.ts` with the schema extracted from `backend/windmill-api/openapi.yaml` +- Commit the regenerated file + +## 9. Frontend — editor + drawer + +Under `frontend/src/lib/components/triggers/{kind}/`: + +- `{Kind}TriggerPanel.svelte` — the tile shown in the triggers listing +- `{Kind}TriggerEditor.svelte` — outer drawer wrapper +- `{Kind}TriggerEditorInner.svelte` — state + business logic; must expose: + - `openEdit(path, isFlow, defaultValues?)` method + - `isEditor` prop, `onConfigChange` + `onCaptureConfigChange` callbacks + - `get{Kind}Config()` + `get{Kind}CaptureConfig()` helpers + - `captureConfig = $derived.by(untrack(() => isEditor) ? get{Kind}CaptureConfig : () => ({}))` + - `$effect(() => { const args = [captureConfig, isValid] as const; untrack(() => onCaptureConfigChange?.(...args)) })` +- `{Kind}TriggerEditorConfigSection.svelte` — form fields; use design-system components (`TextInput`, `Select`, `Toggle`, `ToggleButtonGroup`), never raw `` +- `{Kind}Capture.svelte` — capture panel; wraps `CaptureSection` with `captureType="{kind}"` +- `utils.ts` — `requestBody` builders and any trigger-type-specific helpers + +## 10. Frontend — global integration + +Easy to miss: + +- **`frontend/src/lib/components/triggers.ts`** — add `'{kind}'` to the `TriggerKind` union +- **`frontend/src/lib/components/triggers/CaptureWrapper.svelte`**: + - Import `{Kind}Capture` + - Add to `isStreamingCapture()` array (streaming = pull-style; push-style is typically `false`) + - Add `{:else if captureType === '{kind}'}` branch with the `<{Kind}Capture>` render +- **`frontend/src/lib/components/sidebar/SidebarContent.svelte`** — import the icon, add the nav entry +- **`frontend/src/lib/components/sidebar/OperatorMenu.svelte`** — add the operator-mode entry +- **`frontend/src/routes/(root)/(logged)/+layout.svelte`** — destructure `{kind}_used` from `/get_used_triggers` response, push `'{kind}'` into `usedKinds` +- **`frontend/src/lib/components/search/GlobalSearchModal.svelte`** — import icon, add "Go to {Kind} ..." entry +- **`frontend/src/lib/components/offboarding-utils.ts`** — add mappings `{kind}_trigger: '{kind}_triggers'` and `{kind}_trigger: '{kind} trigger'` +- **`frontend/src/lib/components/icons/{Kind}Icon.svelte`** — single-path SVG, `fill={color ?? 'currentColor'}`, `size` prop default 16 (match existing icons — don't hardcode colors, don't use `width`/`height` props) +- **`frontend/src/routes/(root)/(logged)/{kind}_triggers/+page.svelte`** — listing page (mirror `gcp_triggers/+page.svelte` for push+pull, `kafka_triggers` for pure streaming) +- **`frontend/src/lib/components/CompareWorkspaces.svelte`** — workspace fork / compare tool. Needs: service import, editor import, `{kind}Editor` `$state`, `case '{kind}'` in `openTriggerDetails()`, entry in `triggerServices` object (list/delete/normalize), and `<{Kind}TriggerEditor bind:this={{kind}Editor} />` in the template + +## 10.5 AI system prompts (`system_prompts/`) + +- **`system_prompts/utils.py`** — append `('{Kind}Trigger', '{kind}_trigger')` to `SCHEMA_MAPPINGS['triggers']` (master list used by code generation + CLI skills) +- **`system_prompts/generate.py`** — also has a duplicated `schema_types` list (~line 903) for the AI `triggers` skill content. Add `('{Kind}Trigger', '{kind}_trigger')` there too +- **`system_prompts/generate.py`** `schema_names` (~line 1192) — add `'{Kind}Trigger'` (add `'New{Kind}Trigger'` only if the OpenAPI declares one; GCP and Azure don't) +- Run `python3 system_prompts/generate.py` — this rewrites `cli/src/guidance/skills.ts` and all `auto-generated/` docs. Commit the regenerated files + +## 11. Validation + +Run all of these before declaring done: + +```bash +# Backend +cd backend +cargo check --features enterprise,{kind}_trigger,private # minimal +cargo check --features enterprise,azure_trigger,private,gcp_trigger,http_trigger,mqtt_trigger,postgres_trigger,sqs_trigger,kafka,nats,smtp,websocket # full + +# SQLx offline data (never run `cargo sqlx prepare` directly — use the wrapper) +./update_sqlx.sh + +# Frontend +cd frontend +npm run generate-backend-client +npm run check:fast +``` + +Smoke test in the UI: create a trigger, save, check it appears in sidebar + search, delete, re-create via CLI `wmill sync`. + +## 12. Common pitfalls + +- **Forgetting feature gates in `workspaced_unauthed_service()`** — the surrounding `#[cfg(any(...))]` expression must include your feature flag, not just the inner `#[cfg]` on the route +- **`.route(path, ...).route(path, ...)` with same path and different methods** — older axum replaced; use `.route(path, post(h1).options(h2))` to chain methods on the same `MethodRouter` +- **`on:event` directives** — legacy Svelte 4, no-op in runes mode. Use callback props (`onSelected`, `onConfigChange`) +- **`$bindable(default_value)` on optional props** — banned by project CLAUDE.md. Use `$bindable()` + `$derived(prop ?? default)` instead +- **CORS layer intercepting OPTIONS** — tower-http CorsLayer short-circuits OPTIONS before reaching your handler. For server-to-server webhook endpoints, drop the CORS layer entirely (CORS is browser-only) +- **DeliveryAttributeMappings / custom headers for auth** — prefer HMAC or sha256-hashed shared secrets over opaque JWTs when the provider doesn't support signed tokens natively. Store only the hash; regenerate secret on every save +- **ARM / API resource-listing cascades** — if the trigger's resource type is deep (Azure: subscription → RG → namespace → topic), offer dropdowns in the UI populated from the provider's APIs using the user's credential resource +- **Clearing stale selections on dependency change** — when a dropdown's underlying data reloads (e.g., user changes SP or edition), clear selections that no longer match the new list +- **Workspace-scoped tag compatibility** — if the trigger has tags, verify forked workspaces handle them (see commit `0773b5bc85` for a historical fix) + +## 13. EE file split + +If the trigger is enterprise-only, the code lives in `windmill-ee-private__worktrees/.../windmill-trigger-{kind}/src/*_ee.rs` and is symlinked into the OSS tree. The `windmill-ee-private__worktrees/` directory holds the real files; changes propagate via symlinks. See `docs/enterprise.md` for the workflow. + +## 14. Final checklist before PR + +- [ ] Migration up/down tested (revert + re-apply) +- [ ] `./update_sqlx.sh` committed the updated `.sqlx/` offline data +- [ ] `cargo check` passes with your feature flag + with all trigger features +- [ ] `npm run check:fast` passes +- [ ] Trigger visible in sidebar with correct icon weight (not oversized/colored — use `currentColor`) +- [ ] Create, edit, delete flow all work in the UI +- [ ] Capture button works (if push-capable) +- [ ] Trigger appears in `/get_used_triggers` → sidebar pulse +- [ ] `wmill sync pull` + `wmill sync push` both round-trip the trigger +- [ ] `wmill trigger list` includes it +- [ ] OpenAPI schemas are complete (no `null` in generated types) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6758b6eb..5d3819dc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## [1.690.0](https://github.com/windmill-labs/windmill/compare/v1.689.0...v1.690.0) (2026-04-23) + + +### Features + +* add ai agent conversation output control ([#8915](https://github.com/windmill-labs/windmill/issues/8915)) ([9a60ff2](https://github.com/windmill-labs/windmill/commit/9a60ff2e77f197786f523755c3a9286a178a245c)) +* add Azure Event Grid triggers ([#8888](https://github.com/windmill-labs/windmill/issues/8888)) ([d6c642b](https://github.com/windmill-labs/windmill/commit/d6c642b170b9547fe1d8db190affa35b305c9c8a)) +* add OTEL_HOST_NAME env override for host.name attribute ([#8923](https://github.com/windmill-labs/windmill/issues/8923)) ([f429cb5](https://github.com/windmill-labs/windmill/commit/f429cb5e486aa5d2bd37f84c1fb30b9d350909e4)) + + +### Bug Fixes + +* **cli:** use wmill.yaml key consistently for workspace-specific items ([#8900](https://github.com/windmill-labs/windmill/issues/8900)) ([1722a7a](https://github.com/windmill-labs/windmill/commit/1722a7a2af5e00beeae204b78e588cd74a3ceb39)) +* correct flow conversation pagination ([#8919](https://github.com/windmill-labs/windmill/issues/8919)) ([7fa924e](https://github.com/windmill-labs/windmill/commit/7fa924e67e212458726a839dbe366798b2709cd6)) +* ensure schema is inferred on script/flow module load ([#8927](https://github.com/windmill-labs/windmill/issues/8927)) ([664d0f8](https://github.com/windmill-labs/windmill/commit/664d0f838d168978d7c27e88d2bb9019531e7ea1)) +* include endpoint descriptions in mcp tools ([#8925](https://github.com/windmill-labs/windmill/issues/8925)) ([07951e8](https://github.com/windmill-labs/windmill/commit/07951e81ae9a1c26e8fe63bcd7a760b80500ca4c)) +* load job metadata on approval page via approval token ([#8924](https://github.com/windmill-labs/windmill/issues/8924)) ([dac29e7](https://github.com/windmill-labs/windmill/commit/dac29e7d23d6e980c2b6fc4dd3a05a0d2e0170b3)) +* slim app ai chat context ([#8922](https://github.com/windmill-labs/windmill/issues/8922)) ([132d8a6](https://github.com/windmill-labs/windmill/commit/132d8a61f9c109b2b447fab1a39565f52864b746)) + +## [1.689.0](https://github.com/windmill-labs/windmill/compare/v1.688.0...v1.689.0) (2026-04-22) + + +### Features + +* add s3 stream progress logs to other DB executors ([#8898](https://github.com/windmill-labs/windmill/issues/8898)) ([2d4fadb](https://github.com/windmill-labs/windmill/commit/2d4fadb590590837412d638192fbd62bdc9331e8)) +* allow hiding catalog picker and raw input on s3 form fields ([#8902](https://github.com/windmill-labs/windmill/issues/8902)) ([05baa4a](https://github.com/windmill-labs/windmill/commit/05baa4ab026a307267d11fb827f8abcc246d1ac0)) +* async dep endpoints and queue-position logs in cli ([#8895](https://github.com/windmill-labs/windmill/issues/8895)) ([aaf3a19](https://github.com/windmill-labs/windmill/commit/aaf3a1974746be451adf463fd1f0e584b2fa995e)) +* auto-strip UTF-8 BOM when reading local files in CLI ([#8911](https://github.com/windmill-labs/windmill/issues/8911)) ([99bc96d](https://github.com/windmill-labs/windmill/commit/99bc96d0b231a2af303b28aa87d5de9141ee5cab)) + + +### Bug Fixes + +* add aws-config to private feature to restore ce build ([18eed92](https://github.com/windmill-labs/windmill/commit/18eed92dd66d635305a72818e2fcf0ee8b9672cc)) +* add flow conversation token scope ([#8903](https://github.com/windmill-labs/windmill/issues/8903)) ([aea7444](https://github.com/windmill-labs/windmill/commit/aea74445a31d30abb8030763db91e1829db772f0)) +* add proxy eval coverage for gemini schemas ([#8897](https://github.com/windmill-labs/windmill/issues/8897)) ([fddd8e2](https://github.com/windmill-labs/windmill/commit/fddd8e288fc0fd7af3b1df1ddd4476fb57694ed3)) +* apply powershell workspace dependencies to deployed scripts ([#8912](https://github.com/windmill-labs/windmill/issues/8912)) ([dc89673](https://github.com/windmill-labs/windmill/commit/dc896737ac1dcd90ab96314b2bc2f044ff833b8a)) +* detect and clearly label OOM in zombie flow alerts ([#8901](https://github.com/windmill-labs/windmill/issues/8901)) ([680c711](https://github.com/windmill-labs/windmill/commit/680c711f9262683c046a78532b22be5f5a4121a8)) +* omit default_permissioned_as from tarball export when empty ([bbb564c](https://github.com/windmill-labs/windmill/commit/bbb564c1420593014d17f352ba38d3ed38c248e1)) +* persist flow groups from AI chat tool calls ([#8906](https://github.com/windmill-labs/windmill/issues/8906)) ([932d183](https://github.com/windmill-labs/windmill/commit/932d18331196ef3e87c45d8a06ae45ac8013bd7a)) +* push parent resource on fileset child add/delete ([#8910](https://github.com/windmill-labs/windmill/issues/8910)) ([f29badc](https://github.com/windmill-labs/windmill/commit/f29badcf368e7c712f1515fa30a6a0e179a4bdc5)) +* rust nsjail RUSTUP_HOME mount and arch-aware cache keys ([#8890](https://github.com/windmill-labs/windmill/issues/8890)) ([f8c916c](https://github.com/windmill-labs/windmill/commit/f8c916cb6073f5566289c395ec39ec3919f449e7)) +* skip opus 4.7 sampling params ([#8904](https://github.com/windmill-labs/windmill/issues/8904)) ([1e83278](https://github.com/windmill-labs/windmill/commit/1e83278fe2ef5a5c6959a9351e7286d9dbf2453a)) +* support windmill chat answer override ([#8909](https://github.com/windmill-labs/windmill/issues/8909)) ([eeb5d12](https://github.com/windmill-labs/windmill/commit/eeb5d12be3ba2aedf2ebc4843d4395e241ecc8d3)) +* track dollar-quoted strings in SQL block splitter ([#8891](https://github.com/windmill-labs/windmill/issues/8891)) ([53badf1](https://github.com/windmill-labs/windmill/commit/53badf1a8cff576bb4ccbc75f045efb457b6a07d)) +* trigger failure_module when branchone predicate throws ([#8905](https://github.com/windmill-labs/windmill/issues/8905)) ([dffb89e](https://github.com/windmill-labs/windmill/commit/dffb89e00632bd4e7bbe9998bccb1f14357d9c07)), closes [#8889](https://github.com/windmill-labs/windmill/issues/8889) + ## [1.688.0](https://github.com/windmill-labs/windmill/compare/v1.687.0...v1.688.0) (2026-04-20) diff --git a/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json b/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json index 8aa1f9b22c..79ef0c0a81 100644 --- a/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json +++ b/backend/.sqlx/query-089d7bc7acdbb97cf477159e111bc7e9ee85289ff5c52af43166928337c257e7.json @@ -33,7 +33,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json b/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json index 733612861e..0e0fbca58e 100644 --- a/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json +++ b/backend/.sqlx/query-0f7e01b613a94b29784aae6d7b17b23d6dcf2e5364852a5e85b3c41c417bace2.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json b/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json index ab6ee24af9..0e966467a9 100644 --- a/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json +++ b/backend/.sqlx/query-16d438374b03a9c515f4c2d638366f38ffe2f3a0958adea53e67757c6ac463ec.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json b/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json index 7ec162bbf8..41520bfc88 100644 --- a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json +++ b/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json @@ -43,7 +43,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-1c3473a0f9f6b6148b2c975f9f05bdefedf8a51c4e6ddf0eca367b9cc778d051.json b/backend/.sqlx/query-1c3473a0f9f6b6148b2c975f9f05bdefedf8a51c4e6ddf0eca367b9cc778d051.json new file mode 100644 index 0000000000..fb27bd9446 --- /dev/null +++ b/backend/.sqlx/query-1c3473a0f9f6b6148b2c975f9f05bdefedf8a51c4e6ddf0eca367b9cc778d051.json @@ -0,0 +1,83 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, created_seq, step_name, success\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at, created_seq, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_seq DESC\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_seq ASC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "conversation_id", + "type_info": "Uuid" + }, + { + "ordinal": 2, + "name": "message_type: MessageType", + "type_info": { + "Custom": { + "name": "message_type", + "kind": { + "Enum": [ + "user", + "assistant", + "tool" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "content", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "job_id", + "type_info": "Uuid" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "created_seq", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "step_name", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "success", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + false, + true, + false + ] + }, + "hash": "1c3473a0f9f6b6148b2c975f9f05bdefedf8a51c4e6ddf0eca367b9cc778d051" +} diff --git a/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json b/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json index 4d6593ef81..a53d131a3f 100644 --- a/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json +++ b/backend/.sqlx/query-212553c83e4dcdc6d045eb2fe2dadbb2860ce52d37a56b2861de1215260ecff8.json @@ -37,7 +37,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } @@ -73,7 +74,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json b/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json index 77ad70147d..49a2977cf8 100644 --- a/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json +++ b/backend/.sqlx/query-22e0e8a1aa48f8b21763452bd36fbe7db4887c4ac5295052c796bd78a7edc50b.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json index 827758156a..5c84181bdb 100644 --- a/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json +++ b/backend/.sqlx/query-23419adcd74c326d716527293eff518b42f4cdb33e034441015494bd26c172d2.json @@ -42,7 +42,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json index ce6afa3841..ad5613b8fd 100644 --- a/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json +++ b/backend/.sqlx/query-42b4b73e9d60348e2d90fcade9dcad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json b/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json index e6cac8ffd7..e61104d62d 100644 --- a/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json +++ b/backend/.sqlx/query-45024b932383199974616bba1fc2f7175cc6f2e02d9c565bb5159cae3e0b6835.json @@ -32,7 +32,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json b/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json index aaf1798f18..99d6e818ea 100644 --- a/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json +++ b/backend/.sqlx/query-4d272cf4a77aab7007a5b35589e08532a1020cabaf5e22325a1e05f0491d785c.json @@ -39,7 +39,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json index 4bbc889b93..dea8f68e4f 100644 --- a/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json +++ b/backend/.sqlx/query-4f547c0fd54f3bc57212ce87810e35adf640d44d607e62a1fb296e38ac3fdd36.json @@ -34,7 +34,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } @@ -74,7 +75,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6.json b/backend/.sqlx/query-5d26d8145464131740172082584b4e2f32987b67a125981e7122ae5b7b88fa46.json similarity index 69% rename from backend/.sqlx/query-a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6.json rename to backend/.sqlx/query-5d26d8145464131740172082584b4e2f32987b67a125981e7122ae5b7b88fa46.json index 7e5e0043ab..3cc4ff7953 100644 --- a/backend/.sqlx/query-a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6.json +++ b/backend/.sqlx/query-5d26d8145464131740172082584b4e2f32987b67a125981e7122ae5b7b88fa46.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3", + "query": "SELECT id, worker_group, event_type::text, desired_workers, reason, (applied_at AT TIME ZONE 'UTC') AS \"applied_at!: chrono::DateTime\" FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3", "describe": { "columns": [ { @@ -30,8 +30,8 @@ }, { "ordinal": 5, - "name": "applied_at", - "type_info": "Timestamp" + "name": "applied_at!: chrono::DateTime", + "type_info": "Timestamptz" } ], "parameters": { @@ -47,8 +47,8 @@ null, false, true, - false + null ] }, - "hash": "a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6" + "hash": "5d26d8145464131740172082584b4e2f32987b67a125981e7122ae5b7b88fa46" } diff --git a/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json b/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json index db16d66df1..68d9a1fc3b 100644 --- a/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json +++ b/backend/.sqlx/query-66a0e51cf149ba532463e29dd361a803e1bced2f8e1a12f8933b7598ee85a147.json @@ -37,7 +37,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-67e25a7c19ea0ffaf7ea5303fcd04af5a7eb488c76f783e690af0c2153b1d6a8.json b/backend/.sqlx/query-67e25a7c19ea0ffaf7ea5303fcd04af5a7eb488c76f783e690af0c2153b1d6a8.json index 6445018040..50e7b53387 100644 --- a/backend/.sqlx/query-67e25a7c19ea0ffaf7ea5303fcd04af5a7eb488c76f783e690af0c2153b1d6a8.json +++ b/backend/.sqlx/query-67e25a7c19ea0ffaf7ea5303fcd04af5a7eb488c76f783e690af0c2153b1d6a8.json @@ -159,7 +159,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json b/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json index ff9a359af6..046d8ad6bd 100644 --- a/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json +++ b/backend/.sqlx/query-7065f23d04e26831664048f2cfc4f412c57af931f80621aee5012e9cb3535626.json @@ -31,7 +31,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json b/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json index a6cf7e0aeb..7b7e68ad45 100644 --- a/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json +++ b/backend/.sqlx/query-73fdd01bad58b8be1a52f89faef8d92a983470adcd3cc850734960c905e61e83.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-756f82b72af07fd690f37b2e16ed2d390604f4fc4cb330842a88d5764cbcf0c6.json b/backend/.sqlx/query-756f82b72af07fd690f37b2e16ed2d390604f4fc4cb330842a88d5764cbcf0c6.json index 7d0847e4fc..8bc7e78a58 100644 --- a/backend/.sqlx/query-756f82b72af07fd690f37b2e16ed2d390604f4fc4cb330842a88d5764cbcf0c6.json +++ b/backend/.sqlx/query-756f82b72af07fd690f37b2e16ed2d390604f4fc4cb330842a88d5764cbcf0c6.json @@ -126,7 +126,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json b/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json index 1710117097..b705130ffe 100644 --- a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json +++ b/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json @@ -43,7 +43,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json index 6b533d7d13..cd5e361f15 100644 --- a/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json +++ b/backend/.sqlx/query-7fbf72d9059fcd77e4c1112fa4fa22e4276c1da653475628889ce17dc904fbaa.json @@ -29,7 +29,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json b/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json index 9f6bdd062b..1c0e5a0f1b 100644 --- a/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json +++ b/backend/.sqlx/query-836bac47d89113d90bd03a471446eb9016207975af1e37042d81df8cb6ae2c53.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-952f244a06950ccfc70651cb48cdfb7766f7d62d34e68e65b975aaed9e104a5a.json b/backend/.sqlx/query-83d79dd52a708da7c0d55a171744214dc62651c8ae4ffa2be8b5aa68d8f8e791.json similarity index 72% rename from backend/.sqlx/query-952f244a06950ccfc70651cb48cdfb7766f7d62d34e68e65b975aaed9e104a5a.json rename to backend/.sqlx/query-83d79dd52a708da7c0d55a171744214dc62651c8ae4ffa2be8b5aa68d8f8e791.json index 0bffda8458..c0e0795349 100644 --- a/backend/.sqlx/query-952f244a06950ccfc70651cb48cdfb7766f7d62d34e68e65b975aaed9e104a5a.json +++ b/backend/.sqlx/query-83d79dd52a708da7c0d55a171744214dc62651c8ae4ffa2be8b5aa68d8f8e791.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\",\n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\",\n EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS \"gcp_used!\",\n EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS \"email_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS \"nextcloud_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS \"google_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'github'::native_trigger_service) AS \"github_used!\"\n ", + "query": "\n SELECT\n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\",\n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\",\n EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS \"gcp_used!\",\n EXISTS(SELECT 1 FROM azure_trigger WHERE workspace_id = $1) AS \"azure_used!\",\n EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS \"email_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS \"nextcloud_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS \"google_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'github'::native_trigger_service) AS \"github_used!\"\n ", "describe": { "columns": [ { @@ -45,21 +45,26 @@ }, { "ordinal": 8, - "name": "email_used!", + "name": "azure_used!", "type_info": "Bool" }, { "ordinal": 9, - "name": "nextcloud_used!", + "name": "email_used!", "type_info": "Bool" }, { "ordinal": 10, - "name": "google_used!", + "name": "nextcloud_used!", "type_info": "Bool" }, { "ordinal": 11, + "name": "google_used!", + "type_info": "Bool" + }, + { + "ordinal": 12, "name": "github_used!", "type_info": "Bool" } @@ -81,8 +86,9 @@ null, null, null, + null, null ] }, - "hash": "952f244a06950ccfc70651cb48cdfb7766f7d62d34e68e65b975aaed9e104a5a" + "hash": "83d79dd52a708da7c0d55a171744214dc62651c8ae4ffa2be8b5aa68d8f8e791" } diff --git a/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json index 906adfd7ed..9b3fd8f205 100644 --- a/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json +++ b/backend/.sqlx/query-87564a196a1662f524407d853db506bf08c28efe82b68b3d44bafbd3d0e91c29.json @@ -37,7 +37,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json b/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json index 19669cf4ac..c76a069f49 100644 --- a/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json +++ b/backend/.sqlx/query-940b6d78bab940a37a42492f030d2393e297043e4e58555d872b5c4dd89c196a.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json index 1c28f6dd59..87655ad8c1 100644 --- a/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json +++ b/backend/.sqlx/query-9c50e3a136a8ee3ec56e083f26d3a960b89e02ec40b292f3b5198baf2a1d3dbf.json @@ -34,7 +34,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json b/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json index ffef694e07..fd32ba2753 100644 --- a/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json +++ b/backend/.sqlx/query-9ecb404e46a4eac55f977f05a3afbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json @@ -33,7 +33,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json b/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json index f3cb2d40b6..f88fbc8a47 100644 --- a/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json +++ b/backend/.sqlx/query-a4b6371d33206010b2f3ffd2b09e33244fe8ab9a803248fc23f334034d24aad4.json @@ -189,7 +189,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-ad25201d0eea65972234cade87a95d8cd99fc26e5bd466942423cbd09efcebe4.json b/backend/.sqlx/query-ad25201d0eea65972234cade87a95d8cd99fc26e5bd466942423cbd09efcebe4.json new file mode 100644 index 0000000000..7b989b68f5 --- /dev/null +++ b/backend/.sqlx/query-ad25201d0eea65972234cade87a95d8cd99fc26e5bd466942423cbd09efcebe4.json @@ -0,0 +1,53 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO azure_trigger (\n azure_resource_path, azure_mode, scope_resource_id, topic_name,\n subscription_name, event_type_filters,\n push_auth_config, workspace_id, path, script_path, is_flow,\n permissioned_as, mode, edited_by, email,\n error_handler_path, error_handler_args, retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7,\n $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + { + "Custom": { + "name": "azure_trigger_mode", + "kind": { + "Enum": [ + "basic_push", + "namespace_push", + "namespace_pull" + ] + } + } + }, + "Text", + "Varchar", + "Varchar", + "Jsonb", + "Jsonb", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Varchar", + { + "Custom": { + "name": "trigger_mode", + "kind": { + "Enum": [ + "enabled", + "disabled", + "suspended" + ] + } + } + }, + "Varchar", + "Varchar", + "Varchar", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "ad25201d0eea65972234cade87a95d8cd99fc26e5bd466942423cbd09efcebe4" +} diff --git a/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json b/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json index 5f0b320ec4..ca95a3bca8 100644 --- a/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json +++ b/backend/.sqlx/query-b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc.json @@ -164,7 +164,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json index 28bbedf55e..7325a2a313 100644 --- a/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json +++ b/backend/.sqlx/query-b3f0595cacba194e08b9a3e244d9e637e9e156cd85b69126c87dfff89a47711d.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json b/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json index 982ae5ef85..a8bb03e1b3 100644 --- a/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json +++ b/backend/.sqlx/query-be6d2c92a62b7b284651c45af809746147aa9b8d0a81642a7b7cb4738a0cad66.json @@ -109,7 +109,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json b/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json index 6708d6f04d..8a6ab29126 100644 --- a/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json +++ b/backend/.sqlx/query-c3b1152b554812d65eb27f95b1fd434f860922fbc021185beffb9827647feb8e.json @@ -33,7 +33,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-c67e81985093ff976f1326ff2254585e850f61a787a5b8f4a8d88f88016f1f2b.json b/backend/.sqlx/query-c67e81985093ff976f1326ff2254585e850f61a787a5b8f4a8d88f88016f1f2b.json deleted file mode 100644 index 04e253ce54..0000000000 --- a/backend/.sqlx/query-c67e81985093ff976f1326ff2254585e850f61a787a5b8f4a8d88f88016f1f2b.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT created_at FROM flow_conversation_message WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false - ] - }, - "hash": "c67e81985093ff976f1326ff2254585e850f61a787a5b8f4a8d88f88016f1f2b" -} diff --git a/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json b/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json index dc50df78d8..f567c462f8 100644 --- a/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json +++ b/backend/.sqlx/query-ccef7a1bde5cac6c362c5fedb6c13f1f882b695f896f94e5cf91d205633355a1.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json b/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json index b797bea7cb..5ed2e53367 100644 --- a/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json +++ b/backend/.sqlx/query-d41ea93fd58381b89e151c965eae1ea2fe96a1b94f5a92953fb1c1642d15c016.json @@ -109,7 +109,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-d4211392e174a0e8f89c7fcebdf120e5b0f629f9f04e08a2982df33ff23ac7a9.json b/backend/.sqlx/query-d4211392e174a0e8f89c7fcebdf120e5b0f629f9f04e08a2982df33ff23ac7a9.json index 6a9e278e37..a27bea8b2a 100644 --- a/backend/.sqlx/query-d4211392e174a0e8f89c7fcebdf120e5b0f629f9f04e08a2982df33ff23ac7a9.json +++ b/backend/.sqlx/query-d4211392e174a0e8f89c7fcebdf120e5b0f629f9f04e08a2982df33ff23ac7a9.json @@ -249,7 +249,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json b/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json index d7d50a2fe3..eb340e6cc7 100644 --- a/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json +++ b/backend/.sqlx/query-d495c94b580fd34d5ae90615ef21a8a9cc35f362197c0766a5787436af141106.json @@ -27,7 +27,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-d9ef1def7044e58722c5c69c31e9b9f6877de8bc7d4714915bbd572946d9270a.json b/backend/.sqlx/query-d9ef1def7044e58722c5c69c31e9b9f6877de8bc7d4714915bbd572946d9270a.json new file mode 100644 index 0000000000..3eb7e86989 --- /dev/null +++ b/backend/.sqlx/query-d9ef1def7044e58722c5c69c31e9b9f6877de8bc7d4714915bbd572946d9270a.json @@ -0,0 +1,53 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE azure_trigger SET\n azure_resource_path = $1,\n azure_mode = $2,\n scope_resource_id = $3,\n topic_name = $4,\n subscription_name = $5,\n event_type_filters = $6,\n push_auth_config = $7,\n is_flow = $8,\n edited_by = $9,\n permissioned_as = $10,\n script_path = $11,\n path = $12,\n mode = $13,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE workspace_id = $17 AND path = $18\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + { + "Custom": { + "name": "azure_trigger_mode", + "kind": { + "Enum": [ + "basic_push", + "namespace_push", + "namespace_pull" + ] + } + } + }, + "Text", + "Varchar", + "Varchar", + "Jsonb", + "Jsonb", + "Bool", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + { + "Custom": { + "name": "trigger_mode", + "kind": { + "Enum": [ + "enabled", + "disabled", + "suspended" + ] + } + } + }, + "Varchar", + "Jsonb", + "Jsonb", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d9ef1def7044e58722c5c69c31e9b9f6877de8bc7d4714915bbd572946d9270a" +} diff --git a/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json b/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json index 895e631348..1a4cc407e0 100644 --- a/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json +++ b/backend/.sqlx/query-e4d71278fb80126a7a9da73f1889352d4d1e3cb3a8a08f1c9c03055a1cab1235.json @@ -189,7 +189,8 @@ "nextcloud", "google", "ci_test", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json b/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json index 99e56e18cd..3f403a8f95 100644 --- a/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json +++ b/backend/.sqlx/query-e80177f3ffd4c1f52cdb4757483f03f72ef81db302d727e18e63a307ac902022.json @@ -33,7 +33,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-a739af2f72e117acc58374f6ed44f8223efa4826d6c10639f98e624474b247a3.json b/backend/.sqlx/query-e8802be9203c1e88a06e337260ccca029380139f89a01a89033e36a6ed9ac082.json similarity index 69% rename from backend/.sqlx/query-a739af2f72e117acc58374f6ed44f8223efa4826d6c10639f98e624474b247a3.json rename to backend/.sqlx/query-e8802be9203c1e88a06e337260ccca029380139f89a01a89033e36a6ed9ac082.json index 8a64d7c0a6..a3374d6cdf 100644 --- a/backend/.sqlx/query-a739af2f72e117acc58374f6ed44f8223efa4826d6c10639f98e624474b247a3.json +++ b/backend/.sqlx/query-e8802be9203c1e88a06e337260ccca029380139f89a01a89033e36a6ed9ac082.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, step_name, success\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n ", + "query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, created_seq, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n AND created_seq > $2\n ORDER BY created_seq ASC\n LIMIT $3\n ", "describe": { "columns": [ { @@ -46,11 +46,16 @@ }, { "ordinal": 6, + "name": "created_seq", + "type_info": "Int8" + }, + { + "ordinal": 7, "name": "step_name", "type_info": "Varchar" }, { - "ordinal": 7, + "ordinal": 8, "name": "success", "type_info": "Bool" } @@ -69,9 +74,10 @@ false, true, false, + false, true, false ] }, - "hash": "a739af2f72e117acc58374f6ed44f8223efa4826d6c10639f98e624474b247a3" + "hash": "e8802be9203c1e88a06e337260ccca029380139f89a01a89033e36a6ed9ac082" } diff --git a/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json index 6e77b92ef2..b48f144f8e 100644 --- a/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json +++ b/backend/.sqlx/query-eac595e19e5c8e70f1514ef29dec35c7342ac9a814c73f6290e1d6ebd3a55423.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json b/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json index ffde077e91..c28997d702 100644 --- a/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json +++ b/backend/.sqlx/query-ed8facbf29ebb670d05fe8aa34b50d6a6935420fbedc83aa3ad1e9be7465c8dd.json @@ -26,7 +26,8 @@ "default_email", "nextcloud", "google", - "github" + "github", + "azure" ] } } diff --git a/backend/.sqlx/query-efdcdf0f8d24a23682bb3792ebaffeb7a8dc5632043b6bf4a3dd680c9b99b5df.json b/backend/.sqlx/query-efdcdf0f8d24a23682bb3792ebaffeb7a8dc5632043b6bf4a3dd680c9b99b5df.json new file mode 100644 index 0000000000..e1641f5bea --- /dev/null +++ b/backend/.sqlx/query-efdcdf0f8d24a23682bb3792ebaffeb7a8dc5632043b6bf4a3dd680c9b99b5df.json @@ -0,0 +1,100 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n azure_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n permissioned_as,\n push_auth_config AS \"push_auth_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n azure_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n azure_mode IN ('basic_push'::AZURE_TRIGGER_MODE, 'namespace_push'::AZURE_TRIGGER_MODE)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "azure_resource_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "mode: _", + "type_info": { + "Custom": { + "name": "trigger_mode", + "kind": { + "Enum": [ + "enabled", + "disabled", + "suspended" + ] + } + } + } + }, + { + "ordinal": 4, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "push_auth_config: _", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "retry: _", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "error_handler_path", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "error_handler_args: _", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true + ] + }, + "hash": "efdcdf0f8d24a23682bb3792ebaffeb7a8dc5632043b6bf4a3dd680c9b99b5df" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index da8ae12243..759cd5ac84 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -39,7 +39,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -65,7 +65,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", "cipher 0.3.0", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", ] @@ -77,7 +77,7 @@ checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher 0.4.4", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -249,7 +249,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -421,7 +421,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.12.0", + "indexmap 2.14.0", "lexical-core", "memchr", "num", @@ -602,7 +602,7 @@ dependencies = [ "futures-core", "libc", "portable-atomic", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "tokio", "tokio-stream", "xattr", @@ -860,9 +860,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -1137,7 +1137,7 @@ dependencies = [ "bytes", "form_urlencoded", "hex", - "hmac", + "hmac 0.12.1", "http 0.2.12", "http 1.4.0", "percent-encoding", @@ -1226,9 +1226,9 @@ dependencies = [ "http 1.4.0", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-rustls 0.24.2", - "hyper-rustls 0.27.7", + "hyper-rustls 0.27.9", "hyper-util", "pin-project-lite", "rustls 0.21.12", @@ -1420,7 +1420,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "itoa", "matchit 0.8.4", @@ -1645,7 +1645,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "shlex", "syn 2.0.117", ] @@ -1665,7 +1665,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "shlex", "syn 2.0.117", ] @@ -1747,16 +1747,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq 0.4.2", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -1784,6 +1784,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-modes" version = "0.8.1" @@ -1823,7 +1832,7 @@ dependencies = [ "hex", "http 1.4.0", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -2123,7 +2132,7 @@ dependencies = [ "rayon", "safetensors", "thiserror 2.0.18", - "yoke 0.8.1", + "yoke 0.8.2", "zip", ] @@ -2204,9 +2213,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -2253,6 +2262,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -2302,7 +2322,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -2319,9 +2339,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -2341,9 +2361,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2368,13 +2388,19 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -2472,6 +2498,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -2500,9 +2532,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", "konst", @@ -2612,6 +2644,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -2747,6 +2788,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "csv" version = "1.3.1" @@ -2777,6 +2827,15 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -2784,7 +2843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto 0.2.9", @@ -3144,7 +3203,7 @@ dependencies = [ "base64 0.22.1", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.14.0", "libc", "log", "object_store", @@ -3323,7 +3382,7 @@ dependencies = [ "datafusion-functions-aggregate-common", "datafusion-functions-window-common", "datafusion-physical-expr-common", - "indexmap 2.12.0", + "indexmap 2.14.0", "paste", "recursive", "serde_json", @@ -3338,7 +3397,7 @@ checksum = "422ac9cf3b22bbbae8cdf8ceb33039107fde1b5492693168f13bd566b1bcc839" dependencies = [ "arrow", "datafusion-common", - "indexmap 2.12.0", + "indexmap 2.14.0", "itertools 0.14.0", "paste", ] @@ -3492,7 +3551,7 @@ dependencies = [ "datafusion-common", "datafusion-expr", "datafusion-physical-expr", - "indexmap 2.12.0", + "indexmap 2.14.0", "itertools 0.14.0", "log", "recursive", @@ -3515,7 +3574,7 @@ dependencies = [ "datafusion-physical-expr-common", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.14.0", "itertools 0.14.0", "log", "paste", @@ -3577,7 +3636,7 @@ dependencies = [ "futures", "half", "hashbrown 0.14.5", - "indexmap 2.12.0", + "indexmap 2.14.0", "itertools 0.14.0", "log", "parking_lot", @@ -3619,7 +3678,7 @@ dependencies = [ "bigdecimal", "datafusion-common", "datafusion-expr", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "recursive", "regex", @@ -3737,7 +3796,7 @@ dependencies = [ "deno_media_type", "deno_path_util", "http 1.4.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "once_cell", "parking_lot", @@ -3778,7 +3837,7 @@ dependencies = [ "glob", "ignore", "import_map", - "indexmap 2.12.0", + "indexmap 2.14.0", "jsonc-parser", "log", "percent-encoding", @@ -3819,7 +3878,7 @@ dependencies = [ "deno_path_util", "deno_unsync", "futures", - "indexmap 2.12.0", + "indexmap 2.14.0", "libc", "memoffset", "parking_lot", @@ -3871,7 +3930,7 @@ dependencies = [ "aes-kw", "base64 0.21.7", "cbc", - "const-oid", + "const-oid 0.9.6", "ctr", "curve25519-dalek", "deno_core", @@ -3946,8 +4005,8 @@ dependencies = [ "hickory-resolver", "http 1.4.0", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "ipnet", "percent-encoding", @@ -4034,7 +4093,7 @@ dependencies = [ "http 1.4.0", "httparse", "hyper 0.14.32", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "itertools 0.10.5", "memmem", @@ -4200,7 +4259,7 @@ dependencies = [ "brotli 6.0.0", "bytes", "cbc", - "const-oid", + "const-oid 0.9.6", "ctr", "data-encoding", "deno_core", @@ -4227,10 +4286,10 @@ dependencies = [ "hkdf", "http 1.4.0", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "idna", - "indexmap 2.12.0", + "indexmap 2.14.0", "ipnetwork", "k256", "lazy-regex", @@ -4306,7 +4365,7 @@ version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d328067139909aa81522a5d90f119368b541fbddd73ab630e4d9f777865f0d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "proc-macro-rules", "proc-macro2", "quote", @@ -4350,7 +4409,7 @@ dependencies = [ "deno_error", "deno_path_util", "deno_semver", - "indexmap 2.12.0", + "indexmap 2.14.0", "serde", "serde_json", "sys_traits", @@ -4498,7 +4557,7 @@ dependencies = [ "http 1.4.0", "http-body-util", "hyper 0.14.32", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "libc", "log", @@ -4552,8 +4611,8 @@ dependencies = [ "deno_error", "deno_tls", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "log", "once_cell", @@ -4682,7 +4741,7 @@ dependencies = [ "h2 0.4.13", "http 1.4.0", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "once_cell", "rustls-tokio-stream", @@ -4787,7 +4846,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "der_derive", "pem-rfc7468", "zeroize", @@ -4973,11 +5032,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", - "crypto-common", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", + "ctutils", +] + [[package]] name = "dirs" version = "4.0.0" @@ -5573,9 +5644,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastwebsockets" @@ -5586,7 +5657,7 @@ dependencies = [ "base64 0.21.7", "bytes", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "pin-project", "rand 0.8.5", @@ -6203,6 +6274,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -6458,9 +6530,9 @@ dependencies = [ [[package]] name = "gzip-header" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +checksum = "86848f4fd157d91041a62c78046fb7b248bcc2dce78376d436a1756e9a038577" dependencies = [ "crc32fast", ] @@ -6477,7 +6549,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -6496,7 +6568,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -6571,11 +6643,18 @@ dependencies = [ ] [[package]] -name = "hashify" -version = "0.2.7" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "149e3ea90eb5a26ad354cfe3cb7f7401b9329032d0235f2687d03a35f30e5d4c" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "hashify" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1246c0e5493286aeb2dde35b1f4eb9c4ce00e628641210a5e553fc001a1f26" dependencies = [ + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -6756,7 +6835,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -6768,6 +6847,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.2", +] + [[package]] name = "home" version = "0.5.12" @@ -6886,7 +6974,7 @@ dependencies = [ "futures", "http 1.4.0", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-rustls 0.26.0", "hyper-tls", "hyper-tungstenite", @@ -6910,6 +6998,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -6936,9 +7033,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -6951,7 +7048,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -6967,8 +7063,8 @@ dependencies = [ "futures-util", "headers", "http 1.4.0", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-tls", "hyper-util", "native-tls", @@ -6987,7 +7083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -7019,7 +7115,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "log", "rustls 0.22.4", @@ -7032,21 +7128,20 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "log", "rustls 0.23.35", "rustls-native-certs 0.8.3", - "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -7055,7 +7150,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -7070,7 +7165,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "native-tls", "tokio", @@ -7085,7 +7180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" dependencies = [ "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -7105,7 +7200,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-body 1.0.1", - "hyper 1.8.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", @@ -7126,7 +7221,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -7159,22 +7254,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", - "yoke 0.8.1", + "utf8_iter", + "yoke 0.8.2", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -7185,9 +7281,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -7199,15 +7295,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -7219,20 +7315,20 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", "writeable", - "yoke 0.8.1", + "yoke 0.8.2", "zerofrom", "zerotrie", "zerovec", @@ -7314,7 +7410,7 @@ checksum = "1215d4d92511fbbdaea50e750e91f2429598ef817f02b579158e92803b52c00a" dependencies = [ "boxed_error", "deno_error", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "percent-encoding", "serde", @@ -7336,12 +7432,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -7397,18 +7493,18 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "inventory" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] [[package]] name = "io-uring" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -7445,9 +7541,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -7698,7 +7794,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -7707,7 +7803,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", ] [[package]] @@ -7729,9 +7825,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "konst" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" dependencies = [ "konst_macro_rules", ] @@ -7790,9 +7886,9 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-http-proxy", - "hyper-rustls 0.27.7", + "hyper-rustls 0.27.9", "hyper-timeout", "hyper-util", "jsonpath-rust", @@ -7981,9 +8077,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libffi" @@ -8065,14 +8161,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags 2.9.4", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -8109,9 +8205,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -8139,9 +8235,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -8191,18 +8287,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.14.0" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "lru" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.0", ] @@ -8448,6 +8535,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.2", +] + [[package]] name = "md4" version = "0.10.2" @@ -8620,9 +8717,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", @@ -8726,9 +8823,9 @@ dependencies = [ [[package]] name = "mysql_async" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277ce2f2459b2af4cc6d0a0b7892381f80800832f57c533f03e2845f4ea331ea" +checksum = "d1d9585dc9058886ff3a1f48a23024dd1d054264dee7c5ae0e4bd640c953bee5" dependencies = [ "bytes", "crossbeam-queue", @@ -8737,7 +8834,7 @@ dependencies = [ "futures-sink", "futures-util", "keyed_priority_queue", - "lru 0.14.0", + "lru 0.16.4", "mysql_common", "native-tls", "pem 3.0.6", @@ -8793,7 +8890,7 @@ dependencies = [ "bitflags 2.9.4", "codespan-reporting", "hexf-parse", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "num-traits", "rustc-hash 1.1.0", @@ -9084,7 +9181,7 @@ dependencies = [ "dirs-sys 0.4.1", "fancy-regex 0.14.0", "heck 0.5.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "lru 0.12.5", "miette", @@ -9349,7 +9446,7 @@ dependencies = [ "http-body-util", "httparse", "humantime", - "hyper 1.8.1", + "hyper 1.9.0", "itertools 0.14.0", "md-5 0.10.6", "parking_lot", @@ -9460,7 +9557,7 @@ dependencies = [ "chrono", "dyn-clone", "ed25519-dalek", - "hmac", + "hmac 0.12.1", "http 1.4.0", "itertools 0.10.5", "log", @@ -9483,9 +9580,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -9521,18 +9618,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -9768,9 +9865,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0218004a4aae742209bee9c3cef05672f6b2708be36a50add8eb613b1f2a4008" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" dependencies = [ "num-traits", ] @@ -9945,9 +10042,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pastey" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" [[package]] name = "path-clean" @@ -9968,7 +10065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -10067,7 +10164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.12.0", + "indexmap 2.14.0", ] [[package]] @@ -10233,9 +10330,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -10263,7 +10360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -10306,7 +10403,7 @@ dependencies = [ "byteorder", "bytes", "fallible-iterator 0.2.0", - "hmac", + "hmac 0.12.1", "md-5 0.10.6", "memchr", "rand 0.8.5", @@ -10316,19 +10413,19 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee9dd5fe15055d2b6806f4736aa0c9637217074e224bbec46d4041b91bb9491" +checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc" dependencies = [ "base64 0.22.1", "byteorder", "bytes", "fallible-iterator 0.2.0", - "hmac", - "md-5 0.10.6", + "hmac 0.13.0", + "md-5 0.11.0", "memchr", - "rand 0.9.0", - "sha2 0.10.9", + "rand 0.10.1", + "sha2 0.11.0", "stringprep", ] @@ -10353,7 +10450,7 @@ dependencies = [ "bytes", "chrono", "fallible-iterator 0.2.0", - "postgres-protocol 0.6.10", + "postgres-protocol 0.6.11", "serde", "serde_json", "uuid", @@ -10361,9 +10458,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -10502,7 +10599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" dependencies = [ "futures", - "indexmap 2.12.0", + "indexmap 2.14.0", "nix 0.30.1", "tokio", "tracing", @@ -10608,9 +10705,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" dependencies = [ "ar_archive_writer", "cc", @@ -10720,7 +10817,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustls 0.23.35", "socket2 0.6.3", "thiserror 2.0.18", @@ -10741,7 +10838,7 @@ dependencies = [ "lru-slab", "rand 0.9.0", "ring 0.17.14", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustls 0.23.35", "rustls-pki-types", "slab", @@ -10837,6 +10934,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -10894,6 +11002,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_distr" version = "0.5.1" @@ -10936,9 +11050,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -11049,9 +11163,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags 2.9.4", ] @@ -11173,8 +11287,8 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-tls", "hyper-util", "js-sys", @@ -11203,7 +11317,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -11221,8 +11335,8 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "js-sys", "log", @@ -11277,7 +11391,7 @@ dependencies = [ "futures", "getrandom 0.2.17", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "reqwest 0.13.1", "reqwest-middleware", "retry-policies", @@ -11308,7 +11422,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -11470,7 +11584,7 @@ dependencies = [ "convert_case 0.10.0", "fnv", "ident_case", - "indexmap 2.12.0", + "indexmap 2.14.0", "proc-macro-crate", "proc-macro2", "quote", @@ -11493,7 +11607,7 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", + "const-oid 0.9.6", "digest 0.10.7", "num-bigint-dig", "num-integer", @@ -11588,9 +11702,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" dependencies = [ "arrayvec", "borsh", @@ -11601,6 +11715,7 @@ dependencies = [ "rkyv", "serde", "serde_json", + "wasm-bindgen", ] [[package]] @@ -11617,9 +11732,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -11636,7 +11751,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.28", ] [[package]] @@ -11711,7 +11826,7 @@ dependencies = [ "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -11795,10 +11910,10 @@ dependencies = [ "rustls 0.23.35", "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "security-framework 3.6.0", "security-framework-sys", - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.7", "windows-sys 0.61.2", ] @@ -11843,9 +11958,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring 0.17.14", @@ -12230,9 +12345,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -12336,7 +12451,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -12429,7 +12544,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde", @@ -12457,7 +12572,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -12470,7 +12585,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "itoa", "libyml", "memchr", @@ -12522,7 +12637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -12534,7 +12649,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -12546,15 +12661,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] [[package]] -name = "sha3" -version = "0.10.8" +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -12647,9 +12773,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd-json" @@ -12828,7 +12954,7 @@ dependencies = [ "data-encoding", "debugid", "if_chain", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_json", "unicode-id-start", @@ -12963,7 +13089,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink 0.10.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "memchr", "once_cell", @@ -13044,7 +13170,7 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "itoa", "log", "md-5 0.10.6", @@ -13085,7 +13211,7 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "home", "itoa", "log", @@ -13134,9 +13260,9 @@ dependencies = [ [[package]] name = "sse-stream" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" +checksum = "2c5e6deb40826033bd7b11c7ef25ef71193fabd71f680f40dd16538a2704d2f4" dependencies = [ "bytes", "futures-util", @@ -13153,15 +13279,15 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -13363,7 +13489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4740e53eaf68b101203c1df0937d5161a29f3c13bceed0836ddfe245b72dd000" dependencies = [ "anyhow", - "indexmap 2.12.0", + "indexmap 2.14.0", "serde", "serde_json", "swc_cached", @@ -13475,7 +13601,7 @@ checksum = "65f21494e75d0bd8ef42010b47cabab9caaed8f2207570e809f6f4eb51a710d1" dependencies = [ "better_scoped_tls", "bitflags 2.9.4", - "indexmap 2.12.0", + "indexmap 2.14.0", "once_cell", "phf 0.11.3", "rustc-hash 1.1.0", @@ -13544,7 +13670,7 @@ checksum = "76c76d8b9792ce51401d38da0fa62158d61f6d80d16d68fe5b03ce4bf5fba383" dependencies = [ "base64 0.21.7", "dashmap 5.5.3", - "indexmap 2.12.0", + "indexmap 2.14.0", "once_cell", "serde", "sha1", @@ -13584,7 +13710,7 @@ version = "0.134.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029eec7dd485923a75b5a45befd04510288870250270292fc2c1b3a9e7547408" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "num_cpus", "once_cell", "rustc-hash 1.1.0", @@ -13657,6 +13783,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -13822,7 +13954,7 @@ dependencies = [ "itertools 0.14.0", "levenshtein_automata", "log", - "lru 0.16.3", + "lru 0.16.4", "lz4_flex 0.13.0", "measure_time", "memmap2 0.9.10", @@ -13831,7 +13963,7 @@ dependencies = [ "rayon", "regex", "rust-stemmers", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_json", "sketches-ddsketch", @@ -13904,7 +14036,7 @@ source = "git+https://github.com/windmill-labs/tantivy?rev=6ae7c70bc603b8e69e27f dependencies = [ "fnv", "nom 7.1.3", - "ordered-float 5.2.0", + "ordered-float 5.3.0", "serde", "serde_json", ] @@ -14181,9 +14313,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -14262,7 +14394,7 @@ dependencies = [ "bytes", "io-uring", "libc", - "mio 1.1.1", + "mio 1.2.0", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -14371,7 +14503,7 @@ dependencies = [ "percent-encoding", "phf 0.11.3", "pin-project-lite", - "postgres-protocol 0.6.10", + "postgres-protocol 0.6.11", "postgres-types 0.2.9", "rand 0.9.0", "socket2 0.5.10", @@ -14550,7 +14682,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -14563,7 +14695,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.0", "toml_datetime 0.7.0", "toml_parser", "winnow 0.7.15", @@ -14571,11 +14703,11 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] @@ -14594,7 +14726,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -14626,7 +14758,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -14671,7 +14803,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -14750,11 +14882,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber", @@ -15013,9 +15146,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "typetag" @@ -15184,9 +15317,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -15240,7 +15373,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -15386,7 +15519,7 @@ checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6" dependencies = [ "bitflags 2.9.4", "encoding_rs", - "indexmap 2.12.0", + "indexmap 2.14.0", "num-bigint", "serde", "thiserror 1.0.69", @@ -15471,11 +15604,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -15484,7 +15617,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -15502,6 +15635,7 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -15606,7 +15740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.12.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -15642,8 +15776,8 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.9.4", "hashbrown 0.15.5", - "indexmap 2.12.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -15686,14 +15820,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.7", ] [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -15704,14 +15838,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -15728,7 +15862,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "document-features", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "naga", "once_cell", @@ -15886,7 +16020,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-nats", @@ -15950,6 +16084,7 @@ dependencies = [ "windmill-runtime-nativets", "windmill-test-utils", "windmill-trigger", + "windmill-trigger-azure", "windmill-trigger-gcp", "windmill-trigger-kafka", "windmill-trigger-mqtt", @@ -15966,7 +16101,7 @@ dependencies = [ [[package]] name = "windmill-ai" -version = "1.688.0" +version = "1.690.0" dependencies = [ "async-trait", "aws-config", @@ -15990,7 +16125,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16003,7 +16138,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "argon2", @@ -16036,10 +16171,10 @@ dependencies = [ "futures", "git-version", "hex", - "hmac", + "hmac 0.12.1", "http 1.4.0", - "hyper 1.8.1", - "indexmap 2.12.0", + "hyper 1.9.0", + "indexmap 2.14.0", "itertools 0.14.0", "jsonwebtoken 8.3.0", "lazy_static", @@ -16129,6 +16264,7 @@ dependencies = [ "windmill-queue", "windmill-store", "windmill-trigger", + "windmill-trigger-azure", "windmill-trigger-email", "windmill-trigger-gcp", "windmill-trigger-http", @@ -16145,12 +16281,12 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "lazy_static", "quick_cache", "serde", @@ -16168,7 +16304,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16181,7 +16317,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", @@ -16207,7 +16343,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.688.0" +version = "1.690.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16217,7 +16353,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16234,7 +16370,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "base64 0.22.1", @@ -16242,7 +16378,6 @@ dependencies = [ "ed25519-dalek", "hex", "lazy_static", - "rand 0.9.0", "serde", "serde_json", "sha2 0.10.9", @@ -16257,7 +16392,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", @@ -16280,7 +16415,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16296,11 +16431,11 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", - "hyper 1.8.1", + "hyper 1.9.0", "serde", "serde_json", "sql-builder", @@ -16317,7 +16452,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16338,7 +16473,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16352,7 +16487,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-nats", @@ -16383,14 +16518,14 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", "base64 0.22.1", "chrono", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "lazy_static", "serde", "serde_json", @@ -16408,7 +16543,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "flate2", @@ -16426,12 +16561,12 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", "http 1.4.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "itertools 0.14.0", "lazy_static", "serde", @@ -16448,7 +16583,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16468,13 +16603,13 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", "futures", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "itertools 0.14.0", "lazy_static", "quick_cache", @@ -16498,7 +16633,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", @@ -16526,7 +16661,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.688.0" +version = "1.690.0" dependencies = [ "lazy_static", "serde", @@ -16538,14 +16673,14 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.688.0" +version = "1.690.0" dependencies = [ "argon2", "axum 0.8.4", "chrono", "dashmap 6.1.0", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "lazy_static", "serde", "serde_json", @@ -16563,7 +16698,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", @@ -16577,13 +16712,13 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.688.0" +version = "1.690.0" dependencies = [ "axum 0.8.4", "chrono", "hex", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "lazy_static", "magic-crypt", "regex", @@ -16610,7 +16745,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.688.0" +version = "1.690.0" dependencies = [ "chrono", "lazy_static", @@ -16624,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "axum 0.8.4", @@ -16643,7 +16778,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.688.0" +version = "1.690.0" dependencies = [ "aes-gcm", "aho-corasick", @@ -16678,9 +16813,9 @@ dependencies = [ "git-version", "globset", "hex", - "hmac", - "hyper 1.8.1", - "indexmap 2.12.0", + "hmac 0.12.1", + "hyper 1.9.0", + "indexmap 2.14.0", "itertools 0.14.0", "jsonwebtoken 8.3.0", "lazy_static", @@ -16707,7 +16842,7 @@ dependencies = [ "reqwest-retry", "rsa", "schemars 0.8.22", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_json", "serde_yml", @@ -16744,7 +16879,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.688.0" +version = "1.690.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16763,7 +16898,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.688.0" +version = "1.690.0" dependencies = [ "regex", "serde", @@ -16778,7 +16913,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16802,7 +16937,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "futures", @@ -16819,7 +16954,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.688.0" +version = "1.690.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16835,7 +16970,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -16856,7 +16991,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -16864,7 +16999,7 @@ dependencies = [ "backon", "base64 0.22.1", "chrono", - "hmac", + "hmac 0.12.1", "http 1.4.0", "itertools 0.14.0", "lazy_static", @@ -16887,7 +17022,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "arc-swap", @@ -16896,7 +17031,7 @@ dependencies = [ "base64 0.22.1", "chrono", "hex", - "hmac", + "hmac 0.12.1", "itertools 0.14.0", "lazy_static", "reqwest 0.12.28", @@ -16912,7 +17047,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-stream", @@ -16946,7 +17081,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "futures", @@ -16964,7 +17099,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.688.0" +version = "1.690.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16973,7 +17108,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -16985,7 +17120,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -16997,7 +17132,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "gosyn", @@ -17009,7 +17144,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -17021,7 +17156,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -17033,7 +17168,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "nu-parser", @@ -17044,7 +17179,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -17055,7 +17190,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -17067,7 +17202,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "rustpython-ast", @@ -17078,7 +17213,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-recursion", @@ -17100,7 +17235,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -17112,7 +17247,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -17126,7 +17261,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -17143,7 +17278,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -17156,7 +17291,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde", @@ -17168,7 +17303,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -17186,7 +17321,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -17202,7 +17337,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "rustpython-ast", @@ -17218,7 +17353,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde", @@ -17229,7 +17364,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-recursion", @@ -17242,7 +17377,7 @@ dependencies = [ "futures", "futures-core", "hex", - "hmac", + "hmac 0.12.1", "itertools 0.14.0", "lazy_static", "once_cell", @@ -17266,7 +17401,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "const_format", @@ -17304,7 +17439,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.688.0" +version = "1.690.0" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17315,7 +17450,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-recursion", @@ -17323,7 +17458,7 @@ dependencies = [ "chrono", "futures", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "lazy_static", "quick_cache", "reqwest 0.13.1", @@ -17345,7 +17480,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17369,14 +17504,14 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", "axum 0.8.4", "chrono", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "itertools 0.14.0", "lazy_static", "rand 0.9.0", @@ -17400,9 +17535,42 @@ dependencies = [ "windmill-queue", ] +[[package]] +name = "windmill-trigger-azure" +version = "1.690.0" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.8.4", + "base64 0.22.1", + "bytes", + "chrono", + "constant_time_eq 0.3.1", + "hex", + "http 1.4.0", + "itertools 0.14.0", + "lazy_static", + "quick_cache", + "rand 0.9.0", + "reqwest 0.13.1", + "serde", + "serde_json", + "sha2 0.10.9", + "sqlx", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "windmill-api-auth", + "windmill-common", + "windmill-git-sync", + "windmill-store", + "windmill-trigger", +] + [[package]] name = "windmill-trigger-email" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17422,7 +17590,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17456,7 +17624,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17466,9 +17634,9 @@ dependencies = [ "constant_time_eq 0.3.1", "futures", "hex", - "hmac", + "hmac 0.12.1", "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "itertools 0.14.0", "lazy_static", "matchit 0.7.3", @@ -17492,7 +17660,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17515,7 +17683,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17539,7 +17707,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-nats", @@ -17563,7 +17731,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17598,7 +17766,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17626,7 +17794,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-trait", @@ -17649,7 +17817,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "bitflags 2.9.4", @@ -17668,7 +17836,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-once-cell", @@ -17696,7 +17864,7 @@ dependencies = [ "gcp_auth", "git-version", "hex", - "hmac", + "hmac 0.12.1", "hudsucker", "hyper-http-proxy", "hyper-tls", @@ -17780,7 +17948,7 @@ dependencies = [ [[package]] name = "windmill-worker-volumes" -version = "1.688.0" +version = "1.690.0" dependencies = [ "bytes", "futures", @@ -18380,9 +18548,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "winsafe" @@ -18399,6 +18567,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -18418,7 +18592,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.12.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -18449,7 +18623,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.9.4", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -18468,9 +18642,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.12.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -18480,9 +18654,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wtf8" @@ -18615,12 +18789,12 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", - "yoke-derive 0.8.1", + "yoke-derive 0.8.2", "zerofrom", ] @@ -18638,9 +18812,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -18650,18 +18824,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -18670,18 +18844,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -18711,31 +18885,31 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", - "yoke 0.8.1", + "yoke 0.8.2", "zerofrom", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "yoke 0.8.1", + "yoke 0.8.2", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -18749,7 +18923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "crc32fast", - "indexmap 2.12.0", + "indexmap 2.14.0", "memchr", "typed-path", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ca9dc25e9b..5c39ed2979 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.688.0" +version = "1.690.0" authors.workspace = true edition.workspace = true @@ -27,6 +27,7 @@ members = [ "./windmill-trigger-nats", "./windmill-trigger-sqs", "./windmill-trigger-gcp", + "./windmill-trigger-azure", "./windmill-trigger-http", "./windmill-native-triggers", "./windmill-alerting", @@ -86,7 +87,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"] [workspace.package] -version = "1.688.0" +version = "1.690.0" authors = ["Ruben Fiszel "] edition = "2021" @@ -142,6 +143,7 @@ mqtt_trigger = ["windmill-api/mqtt_trigger"] native_trigger = ["windmill-api/native_trigger"] sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"] gcp_trigger = ["windmill-api/gcp_trigger"] +azure_trigger = ["windmill-api/azure_trigger"] smtp = ["windmill-api/smtp", "windmill-common/smtp", "windmill-queue/smtp"] license = ["windmill-api/license", "windmill-api-settings/license"] oauth2 = ["windmill-api/oauth2"] @@ -183,7 +185,7 @@ oss_core = [ ce_core = ["oss_core", "private", "operator"] ee_core = [ "enterprise", "stripe", "prometheus", "cloud", - "kafka", "sqs_trigger", "nats", "gcp_trigger", + "kafka", "sqs_trigger", "nats", "gcp_trigger", "azure_trigger", "jemalloc", "otel", "operator" ] ee_server = ["enterprise_saml", "tantivy", "agent_worker_server", "local_reports"] @@ -197,7 +199,7 @@ ee_rhel = ["ce_core", "ee_core", "kafka-gssapi", "all_languages"] ee_windows = ["ce_core", "ee_core", "all_languages_windows"] all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing", "openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "kafka-gssapi", "nats", "otel", "dind", "websocket", "http_trigger", - "postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe", + "postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "azure_trigger", "smtp", "stripe", "license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs", "windmill-git-sync/all_sqlx_features"] @@ -277,6 +279,7 @@ windmill-trigger-kafka.workspace = true windmill-trigger-nats.workspace = true windmill-trigger-sqs.workspace = true windmill-trigger-gcp.workspace = true +windmill-trigger-azure.workspace = true windmill-api-auth.workspace = true axum.workspace = true serde.workspace = true @@ -327,6 +330,7 @@ windmill-trigger-email = { path = "./windmill-trigger-email" } windmill-trigger-nats = { path = "./windmill-trigger-nats" } windmill-trigger-sqs = { path = "./windmill-trigger-sqs" } windmill-trigger-gcp = { path = "./windmill-trigger-gcp" } +windmill-trigger-azure = { path = "./windmill-trigger-azure" } windmill-trigger-http = { path = "./windmill-trigger-http" } windmill-native-triggers = { path = "./windmill-native-triggers" } windmill-alerting = { path = "./windmill-alerting" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 998c232e11..094b01685a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -2c2b8dc99689f54b8cd916fb9472fd5698b09478 +4128203739a973330599dacfb054203cf9832f3a \ No newline at end of file diff --git a/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py index 7bcf125b8e..b120a00d39 100644 --- a/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py +++ b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py @@ -384,6 +384,19 @@ def schema_to_rust_value(schema: Optional[Dict[str, Any]]) -> str: return "None" return f"Some(serde_json::json!({json.dumps(schema, indent=8)}))" +def build_tool_description(operation: Dict[str, Any], method: str, path: str) -> str: + """Build the MCP tool description from OpenAPI summary and description.""" + summary = operation.get('summary', '').strip() + description = operation.get('description', '').strip() + + if summary and description: + return f"{summary}: {description}".rstrip('.!? ') + if summary: + return summary + if description: + return description.rstrip('.!? ') + return f'{method.upper()} {path}' + def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]: """Find all endpoints marked with x-mcp-tool: true.""" tools = [] @@ -395,7 +408,7 @@ def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]: # Extract tool information tool = { 'name': operation.get('operationId', f"{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"), - 'description': operation.get('summary', operation.get('description', f'{method.upper()} {path}')), + 'description': build_tool_description(operation, method, path), 'instructions': operation.get('x-mcp-instructions', ''), 'path': path, 'method': method.upper(), @@ -607,4 +620,4 @@ def main(): print("Done!") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/backend/migrations/20260420162905_add_azure_trigger.down.sql b/backend/migrations/20260420162905_add_azure_trigger.down.sql new file mode 100644 index 0000000000..c83cbb3adb --- /dev/null +++ b/backend/migrations/20260420162905_add_azure_trigger.down.sql @@ -0,0 +1,5 @@ +-- Add down migration script here +DROP TABLE IF EXISTS azure_trigger; +DROP TYPE IF EXISTS AZURE_TRIGGER_MODE; +-- Note: TRIGGER_KIND and JOB_TRIGGER_KIND enums cannot have values removed +-- so we leave 'azure' in place as a dead value (matches other trigger migrations) diff --git a/backend/migrations/20260420162905_add_azure_trigger.up.sql b/backend/migrations/20260420162905_add_azure_trigger.up.sql new file mode 100644 index 0000000000..bf57bd4702 --- /dev/null +++ b/backend/migrations/20260420162905_add_azure_trigger.up.sql @@ -0,0 +1,95 @@ +-- Add up migration script here + +ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'azure'; +ALTER TYPE JOB_TRIGGER_KIND ADD VALUE IF NOT EXISTS 'azure'; + +CREATE TYPE AZURE_TRIGGER_MODE AS ENUM ('basic_push', 'namespace_push', 'namespace_pull'); + +CREATE TABLE azure_trigger ( + azure_resource_path VARCHAR(255) NOT NULL, + azure_mode AZURE_TRIGGER_MODE NOT NULL, + scope_resource_id TEXT NOT NULL, + topic_name VARCHAR(255), + subscription_name VARCHAR(255) NOT NULL, + event_type_filters JSONB, + push_auth_config JSONB, + path VARCHAR(255) NOT NULL, + script_path VARCHAR(255) NOT NULL, + is_flow BOOLEAN NOT NULL, + workspace_id VARCHAR(50) NOT NULL, + edited_by VARCHAR(50) NOT NULL, + email VARCHAR(255) NOT NULL, + edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + extra_perms JSONB NOT NULL DEFAULT '{}', + server_id VARCHAR(50), + last_server_ping TIMESTAMPTZ, + error TEXT, + mode TRIGGER_MODE NOT NULL DEFAULT 'enabled', + permissioned_as VARCHAR(255) NOT NULL, + error_handler_path VARCHAR(255), + error_handler_args JSONB, + retry JSONB, + labels TEXT[], + PRIMARY KEY (path, workspace_id), + CONSTRAINT azure_topic_name_matches_mode CHECK ( + (azure_mode = 'basic_push' AND topic_name IS NULL) OR + (azure_mode IN ('namespace_push', 'namespace_pull') AND topic_name IS NOT NULL) + ), + CONSTRAINT azure_push_auth_config_matches_mode CHECK ( + (azure_mode IN ('basic_push', 'namespace_push') AND push_auth_config IS NOT NULL) OR + (azure_mode = 'namespace_pull' AND push_auth_config IS NULL) + ) +); + +CREATE UNIQUE INDEX unique_subscription_per_azure_scope +ON azure_trigger (subscription_name, scope_resource_id, workspace_id); + +CREATE INDEX idx_azure_trigger_labels ON azure_trigger USING GIN (labels) WHERE labels IS NOT NULL; + +GRANT ALL ON azure_trigger TO windmill_user; +GRANT ALL ON azure_trigger TO windmill_admin; + +ALTER TABLE azure_trigger ENABLE ROW LEVEL SECURITY; + +CREATE POLICY admin_policy ON azure_trigger FOR ALL TO windmill_admin USING (true); + +CREATE POLICY see_folder_extra_perms_user_select ON azure_trigger FOR SELECT TO windmill_user +USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_insert ON azure_trigger FOR INSERT TO windmill_user +WITH CHECK (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_update ON azure_trigger FOR UPDATE TO windmill_user +USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_delete ON azure_trigger FOR DELETE TO windmill_user +USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); + +CREATE POLICY see_own ON azure_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'u' AND SPLIT_PART(azure_trigger.path, '/', 2) = current_setting('session.user')); +CREATE POLICY see_member ON azure_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'g' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[])); + +CREATE POLICY see_extra_perms_user_select ON azure_trigger FOR SELECT TO windmill_user +USING (extra_perms ? CONCAT('u/', current_setting('session.user'))); +CREATE POLICY see_extra_perms_user_insert ON azure_trigger FOR INSERT TO windmill_user +WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_update ON azure_trigger FOR UPDATE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_delete ON azure_trigger FOR DELETE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); + +CREATE POLICY see_extra_perms_groups_select ON azure_trigger FOR SELECT TO windmill_user +USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]); +CREATE POLICY see_extra_perms_groups_insert ON azure_trigger FOR INSERT TO windmill_user +WITH CHECK (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_update ON azure_trigger FOR UPDATE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_delete ON azure_trigger FOR DELETE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); diff --git a/backend/migrations/20260423132025_flow_conversation_message_created_seq.down.sql b/backend/migrations/20260423132025_flow_conversation_message_created_seq.down.sql new file mode 100644 index 0000000000..9ace9fae5f --- /dev/null +++ b/backend/migrations/20260423132025_flow_conversation_message_created_seq.down.sql @@ -0,0 +1,10 @@ +DROP INDEX IF EXISTS idx_conversation_message_conversation_created_seq; + +CREATE INDEX IF NOT EXISTS idx_conversation_message_conversation_time + ON flow_conversation_message(conversation_id, created_at DESC); + +ALTER TABLE flow_conversation_message + DROP CONSTRAINT IF EXISTS flow_conversation_message_created_seq_key; + +ALTER TABLE flow_conversation_message + DROP COLUMN IF EXISTS created_seq; diff --git a/backend/migrations/20260423132025_flow_conversation_message_created_seq.up.sql b/backend/migrations/20260423132025_flow_conversation_message_created_seq.up.sql new file mode 100644 index 0000000000..ef4d02ee07 --- /dev/null +++ b/backend/migrations/20260423132025_flow_conversation_message_created_seq.up.sql @@ -0,0 +1,10 @@ +ALTER TABLE flow_conversation_message + ADD COLUMN created_seq BIGINT GENERATED ALWAYS AS IDENTITY; + +ALTER TABLE flow_conversation_message + ADD CONSTRAINT flow_conversation_message_created_seq_key UNIQUE (created_seq); + +CREATE INDEX idx_conversation_message_conversation_created_seq + ON flow_conversation_message(conversation_id, created_seq); + +DROP INDEX IF EXISTS idx_conversation_message_conversation_time; diff --git a/backend/parsers/windmill-parser-wasm/Cargo.lock b/backend/parsers/windmill-parser-wasm/Cargo.lock index 00875379fb..c5fdd6d086 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.lock +++ b/backend/parsers/windmill-parser-wasm/Cargo.lock @@ -6183,7 +6183,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill-common" -version = "1.688.0" +version = "1.690.0" dependencies = [ "aho-corasick", "anyhow", @@ -6263,7 +6263,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.688.0" +version = "1.690.0" dependencies = [ "proc-macro2", "quote", @@ -6275,7 +6275,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.688.0" +version = "1.690.0" dependencies = [ "convert_case", "serde", @@ -6284,7 +6284,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -6296,7 +6296,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -6308,7 +6308,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "gosyn", @@ -6320,7 +6320,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -6332,7 +6332,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -6344,7 +6344,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "nu-parser", @@ -6355,7 +6355,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6366,7 +6366,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "rustpython-ast", @@ -6389,7 +6389,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "async-recursion", @@ -6411,7 +6411,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde_json", @@ -6423,7 +6423,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -6437,7 +6437,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "convert_case", @@ -6454,7 +6454,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -6467,7 +6467,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "lazy_static", @@ -6497,7 +6497,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -6513,7 +6513,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "rustpython-ast", @@ -6529,7 +6529,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "getrandom 0.2.17", @@ -6561,7 +6561,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "serde", @@ -6572,7 +6572,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.688.0" +version = "1.690.0" dependencies = [ "anyhow", "bitflags", diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index a99de1d8fe..7fdb4a98cf 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" members = ["."] [workspace.package] -version = "1.688.0" +version = "1.690.0" edition = "2021" authors = ["Ruben Fiszel "] diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 175965e4ad..c360bb7b8d 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -3879,6 +3879,10 @@ pub async fn reload_jwt_secret_setting(db: &DB) -> error::Result<()> { JWT_SECRET.store(std::sync::Arc::new(jwt_secret)); + // The debug signing key is derived from JWT_SECRET, so re-derive it here so + // rotation propagates to /api/debug/* signing without requiring a restart. + windmill_api::reload_debug_signing_key().await; + Ok(()) } diff --git a/backend/summarized_schema.txt b/backend/summarized_schema.txt index 1517a0e780..6553abb5cc 100644 --- a/backend/summarized_schema.txt +++ b/backend/summarized_schema.txt @@ -80,7 +80,7 @@ flow: workspace_id(char), path(char), summary(text), description(text), value(js FK: (workspace_id) -> workspace(id) flow_conversation: id(uuid), workspace_id(char), flow_path(char), title(char), created_at(ts), updated_at(ts), created_by(char) FK: (workspace_id) -> workspace(id) -flow_conversation_message: id(uuid), conversation_id(uuid), message_type(message_type), content(text), job_id(uuid), created_at(ts), step_name(char), success(bool) +flow_conversation_message: id(uuid), conversation_id(uuid), message_type(message_type), content(text), job_id(uuid), created_at(ts), created_seq(int8), step_name(char), success(bool) FK: (conversation_id) -> flow_conversation(id) | (job_id) -> v2_job(id) flow_iterator_data: job_id(uuid), itered(jsonb) flow_node: id(bigint), workspace_id(char), hash(bigint), path(char), lock(text), code(text), flow(jsonb), hash_v2(char(64)) diff --git a/backend/windmill-api-auth/src/scopes.rs b/backend/windmill-api-auth/src/scopes.rs index b8f73fb95a..42bca309c7 100644 --- a/backend/windmill-api-auth/src/scopes.rs +++ b/backend/windmill-api-auth/src/scopes.rs @@ -259,6 +259,7 @@ pub enum ScopeDomain { MqttTriggers, SqsTriggers, GcpTriggers, + AzureTriggers, PostgresTriggers, EmailTriggers, @@ -317,6 +318,7 @@ impl ScopeDomain { Self::MqttTriggers => "mqtt_triggers", Self::SqsTriggers => "sqs_triggers", Self::GcpTriggers => "gcp_triggers", + Self::AzureTriggers => "azure_triggers", Self::PostgresTriggers => "postgres_triggers", Self::EmailTriggers => "email_triggers", Self::NativeTriggers => "native_triggers", @@ -366,6 +368,7 @@ impl ScopeDomain { "mqtt_triggers" => Some(Self::MqttTriggers), "sqs_triggers" => Some(Self::SqsTriggers), "gcp_triggers" => Some(Self::GcpTriggers), + "azure_triggers" => Some(Self::AzureTriggers), "postgres_triggers" => Some(Self::PostgresTriggers), "email_triggers" => Some(Self::EmailTriggers), "audit" => Some(Self::Audit), diff --git a/backend/windmill-api-configs/src/lib.rs b/backend/windmill-api-configs/src/lib.rs index fd77ff52bb..0901e73e67 100644 --- a/backend/windmill-api-configs/src/lib.rs +++ b/backend/windmill-api-configs/src/lib.rs @@ -247,7 +247,7 @@ struct AutoscalingEvent { event_type: Option, desired_workers: i32, reason: Option, - applied_at: chrono::NaiveDateTime, + applied_at: chrono::DateTime, } async fn list_autoscaling_events( @@ -260,9 +260,12 @@ async fn list_autoscaling_events( } let (per_page, offset) = windmill_common::utils::paginate(pagination); + // applied_at is a naive TIMESTAMP; reinterpret it as UTC so the response + // includes a timezone (otherwise the browser parses it as local time and + // TimeAgo clamps future timestamps to "0s ago"). let events = sqlx::query_as!( AutoscalingEvent, - "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3", + r#"SELECT id, worker_group, event_type::text, desired_workers, reason, (applied_at AT TIME ZONE 'UTC') AS "applied_at!: chrono::DateTime" FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3"#, worker_group, per_page as i64, offset as i64 diff --git a/backend/windmill-api-debug/Cargo.toml b/backend/windmill-api-debug/Cargo.toml index de94a3aba3..8531407416 100644 --- a/backend/windmill-api-debug/Cargo.toml +++ b/backend/windmill-api-debug/Cargo.toml @@ -18,7 +18,6 @@ chrono.workspace = true ed25519-dalek.workspace = true hex.workspace = true lazy_static.workspace = true -rand.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true diff --git a/backend/windmill-api-debug/src/lib.rs b/backend/windmill-api-debug/src/lib.rs index baece9e8e9..61ec59be75 100644 --- a/backend/windmill-api-debug/src/lib.rs +++ b/backend/windmill-api-debug/src/lib.rs @@ -37,7 +37,7 @@ use tokio::sync::RwLock; use uuid::Uuid; use windmill_audit::{audit_oss::audit_log, ActionKind}; use windmill_common::{ - db::UserDB, error::JsonResult, jobs::JobKind, scripts::ScriptLang, + db::UserDB, error::JsonResult, jobs::JobKind, jwt::JWT_SECRET, scripts::ScriptLang, users::username_to_permissioned_as, }; @@ -48,35 +48,67 @@ pub const DEBUG_TOKEN_TTL_SECS: i64 = 60; lazy_static::lazy_static! { /// Ed25519 signing key for debug tokens. - /// Generated at startup if not provided via environment variable. + /// + /// Derived deterministically from the instance `JWT_SECRET` so all API + /// replicas agree on the same key without coordination. Refreshed via + /// [`reload_debug_signing_key`] when `JWT_SECRET` is reloaded. static ref DEBUG_SIGNING_KEY: Arc>> = Arc::new(RwLock::new(None)); } -/// Initialize the debug signing key. -/// Call this at server startup. -pub async fn init_debug_signing_key() { - let mut key_guard = DEBUG_SIGNING_KEY.write().await; +/// Domain-separation tag so the debug Ed25519 seed cannot be confused with +/// any other HMAC/HS256 usage of `JWT_SECRET`. +const DEBUG_KEY_DERIVATION_TAG: &[u8] = b"windmill-debug-signing-key:v1:"; - // Check if key is provided via environment variable (base64-encoded seed) +fn derive_signing_key_from_jwt_secret(jwt_secret: &str) -> SigningKey { + let mut hasher = Sha256::new(); + hasher.update(DEBUG_KEY_DERIVATION_TAG); + hasher.update(jwt_secret.as_bytes()); + let seed: [u8; 32] = hasher.finalize().into(); + SigningKey::from_bytes(&seed) +} + +fn compute_debug_signing_key() -> Option { + // Env var override: base64url-encoded 32-byte seed. Useful for tests or + // advanced deployments that want to pin the key independently. if let Ok(seed_b64) = std::env::var("DEBUG_SIGNING_KEY_SEED") { - if let Ok(seed_bytes) = URL_SAFE_NO_PAD.decode(&seed_b64) { - if seed_bytes.len() >= 32 { + match URL_SAFE_NO_PAD.decode(&seed_b64) { + Ok(seed_bytes) if seed_bytes.len() >= 32 => { let mut seed = [0u8; 32]; seed.copy_from_slice(&seed_bytes[..32]); - *key_guard = Some(SigningKey::from_bytes(&seed)); - tracing::info!("Debug signing key loaded from environment"); - return; + tracing::info!("Debug signing key loaded from DEBUG_SIGNING_KEY_SEED"); + return Some(SigningKey::from_bytes(&seed)); } + _ => tracing::warn!( + "Invalid DEBUG_SIGNING_KEY_SEED (expect base64url-encoded 32+ bytes); falling back to JWT_SECRET derivation" + ), } - tracing::warn!("Invalid DEBUG_SIGNING_KEY_SEED, generating new key"); } - // Generate a new random key using rand - let mut seed = [0u8; 32]; - rand::Rng::fill(&mut rand::rng(), &mut seed); - let signing_key = SigningKey::from_bytes(&seed); - tracing::info!("Generated new debug signing key"); - *key_guard = Some(signing_key); + let jwt_secret = JWT_SECRET.load(); + if jwt_secret.is_empty() { + return None; + } + Some(derive_signing_key_from_jwt_secret(&jwt_secret)) +} + +/// Initialize the debug signing key. Call once at server startup, after +/// `reload_jwt_secret_setting` so `JWT_SECRET` is populated. +pub async fn init_debug_signing_key() { + reload_debug_signing_key().await; +} + +/// Recompute and store the debug signing key. Call after `JWT_SECRET` is +/// (re)loaded so rotation propagates without a pod restart. +pub async fn reload_debug_signing_key() { + let key = compute_debug_signing_key(); + if key.is_none() { + tracing::warn!( + "Debug signing key not initialized: JWT_SECRET is empty and DEBUG_SIGNING_KEY_SEED is not set. /api/debug/* endpoints will return an error." + ); + } else { + tracing::info!("Debug signing key initialized from JWT_SECRET"); + } + *DEBUG_SIGNING_KEY.write().await = key; } pub fn global_service() -> Router { diff --git a/backend/windmill-api-flow-conversations/src/lib.rs b/backend/windmill-api-flow-conversations/src/lib.rs index 70c96d1405..e85af5b83b 100644 --- a/backend/windmill-api-flow-conversations/src/lib.rs +++ b/backend/windmill-api-flow-conversations/src/lib.rs @@ -33,6 +33,7 @@ pub struct FlowConversationMessage { pub content: String, pub job_id: Option, pub created_at: DateTime, + pub created_seq: i64, pub step_name: Option, pub success: bool, } @@ -40,7 +41,11 @@ pub struct FlowConversationMessage { #[derive(Deserialize)] pub struct ListConversationsQuery { pub flow_path: Option, - pub after_id: Option, +} + +#[derive(Deserialize)] +pub struct ListMessagesQuery { + pub after_seq: Option, } async fn list_conversations( @@ -68,15 +73,6 @@ async fn list_conversations( if let Some(flow_path) = &query.flow_path { sqlb.and_where_eq("flow_path", "?".bind(flow_path)); } - if let Some(after_id) = &query.after_id { - let message_id_created_at = sqlx::query_scalar!( - "SELECT created_at FROM flow_conversation_message WHERE id = $1", - after_id - ) - .fetch_one(&mut *tx) - .await?; - sqlb.and_where_gt("created_at", "?".bind(&message_id_created_at.to_rfc3339())); - } sqlb.order_by("updated_at", true) .limit(per_page as i64) @@ -157,6 +153,7 @@ async fn list_messages( Extension(user_db): Extension, Path((w_id, conversation_id)): Path<(String, Uuid)>, Query(pagination): Query, + Query(query): Query, ) -> JsonResult> { let (per_page, offset) = paginate(pagination); let mut tx = user_db.clone().begin(&authed).await?; @@ -178,25 +175,43 @@ async fn list_messages( ))); } - // Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend - let messages = sqlx::query_as!( - FlowConversationMessage, - r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, step_name, success - FROM ( - SELECT id, conversation_id, message_type, content, job_id, created_at, step_name, success - FROM flow_conversation_message - WHERE conversation_id = $1 - ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END - LIMIT $2 OFFSET $3 - ) AS messages - ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END - "#, - conversation_id, - per_page as i64, - offset as i64 - ) - .fetch_all(&mut *tx) - .await?; + let messages = if let Some(after_seq) = query.after_seq { + sqlx::query_as!( + FlowConversationMessage, + r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, created_seq, step_name, success + FROM flow_conversation_message + WHERE conversation_id = $1 + AND created_seq > $2 + ORDER BY created_seq ASC + LIMIT $3 + "#, + conversation_id, + after_seq, + per_page as i64 + ) + .fetch_all(&mut *tx) + .await? + } else { + // Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend + sqlx::query_as!( + FlowConversationMessage, + r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, created_seq, step_name, success + FROM ( + SELECT id, conversation_id, message_type, content, job_id, created_at, created_seq, step_name, success + FROM flow_conversation_message + WHERE conversation_id = $1 + ORDER BY created_seq DESC + LIMIT $2 OFFSET $3 + ) AS messages + ORDER BY created_seq ASC + "#, + conversation_id, + per_page as i64, + offset as i64 + ) + .fetch_all(&mut *tx) + .await? + }; tx.commit().await?; Ok(Json(messages)) diff --git a/backend/windmill-api-groups/src/granular_acls.rs b/backend/windmill-api-groups/src/granular_acls.rs index d7ea8418f9..eac532dc78 100644 --- a/backend/windmill-api-groups/src/granular_acls.rs +++ b/backend/windmill-api-groups/src/granular_acls.rs @@ -24,7 +24,7 @@ use windmill_common::{ utils::{not_found_if_none, StripPath}, }; -const KINDS: [&str; 19] = [ +const KINDS: [&str; 20] = [ "script", "group_", "resource", @@ -41,6 +41,7 @@ const KINDS: [&str; 19] = [ "postgres_trigger", "mqtt_trigger", "gcp_trigger", + "azure_trigger", "sqs_trigger", "email_trigger", "volume", diff --git a/backend/windmill-api-scripts/src/scripts.rs b/backend/windmill-api-scripts/src/scripts.rs index c4b928bcb5..35ca6c5282 100644 --- a/backend/windmill-api-scripts/src/scripts.rs +++ b/backend/windmill-api-scripts/src/scripts.rs @@ -883,6 +883,7 @@ async fn create_script_internal<'c>( || ns.language == ScriptLang::Java || ns.language == ScriptLang::Ruby || ns.language == ScriptLang::Rlang + || ns.language == ScriptLang::Powershell // for related places search: ADD_NEW_LANG ) { Some(String::new()) diff --git a/backend/windmill-api-users/src/users.rs b/backend/windmill-api-users/src/users.rs index 9cef7e6996..2ee32bd96c 100644 --- a/backend/windmill-api-users/src/users.rs +++ b/backend/windmill-api-users/src/users.rs @@ -1690,6 +1690,7 @@ pub async fn delete_workspace_user_internal( "nats_trigger", "sqs_trigger", "gcp_trigger", + "azure_trigger", "email_trigger", ]; for table in &extra_perms_tables { diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index cf2d850b2c..22a6ed3881 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -3138,6 +3138,7 @@ struct UsedTriggers { pub mqtt_used: bool, pub sqs_used: bool, pub gcp_used: bool, + pub azure_used: bool, pub email_used: bool, pub nextcloud_used: bool, pub google_used: bool, @@ -3162,6 +3163,7 @@ async fn get_used_triggers( EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS "mqtt_used!", EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS "sqs_used!", EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS "gcp_used!", + EXISTS(SELECT 1 FROM azure_trigger WHERE workspace_id = $1) AS "azure_used!", EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS "email_used!", EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS "nextcloud_used!", EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS "google_used!", diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 7f73830c56..2af5e17406 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -10,8 +10,8 @@ path = "src/lib.rs" [features] default = [] -private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private", "windmill-git-sync/private", "windmill-autoscaling?/private"] -enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-schedule/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] +private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-azure?/private", "windmill-trigger-email?/private", "windmill-git-sync/private", "windmill-autoscaling?/private"] +enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-schedule/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-azure?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] stripe = [] run_inline = ["dep:windmill-worker", "windmill-api-configs/run_inline"] agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"] @@ -37,6 +37,7 @@ mqtt_trigger = ["dep:windmill-trigger-mqtt", "windmill-store/mqtt_trigger"] native_trigger = ["dep:windmill-native-triggers", "windmill-native-triggers/native_trigger", "dep:strum", "oauth2"] sqs_trigger = ["dep:windmill-trigger-sqs", "windmill-store/sqs_trigger"] gcp_trigger = ["dep:windmill-trigger-gcp", "windmill-store/gcp_trigger"] +azure_trigger = ["dep:windmill-trigger-azure", "windmill-store/azure_trigger"] cloud = ["windmill-common/cloud", "windmill-api-auth/cloud", "windmill-store/cloud", "windmill-api-workspaces/cloud"] mcp = ["dep:windmill-mcp", "windmill-mcp/server", "windmill-mcp/auth", "windmill-api-auth/mcp", "windmill-store/mcp"] bedrock = ["windmill-ai/bedrock", "dep:aws-sdk-bedrock", "dep:aws-sdk-bedrockruntime", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-types"] @@ -145,6 +146,7 @@ windmill-trigger-email = { workspace = true, optional = true } windmill-trigger-nats = { workspace = true, optional = true } windmill-trigger-sqs = { workspace = true, optional = true } windmill-trigger-gcp = { workspace = true, optional = true } +windmill-trigger-azure = { workspace = true, optional = true } windmill-trigger-http = { workspace = true, optional = true } windmill-native-triggers = { workspace = true, optional = true } windmill-alerting.workspace = true diff --git a/backend/windmill-api/openapi-deref.json b/backend/windmill-api/openapi-deref.json index 7c37aa953a..25febc37c6 100644 --- a/backend/windmill-api/openapi-deref.json +++ b/backend/windmill-api/openapi-deref.json @@ -1,7 +1,7 @@ { "openapi": "3.0.3", "info": { - "version": "1.685.0", + "version": "1.689.0", "title": "Windmill API", "contact": { "name": "Windmill Team", @@ -446,6 +446,28 @@ } } }, + "/auth/is_password_login_disabled": { + "get": { + "security": [], + "summary": "check if password login is disabled instance-wide", + "operationId": "isPasswordLoginDisabled", + "tags": [ + "user" + ], + "responses": { + "200": { + "description": "returns true if password login is disabled", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, "/auth/request_password_reset": { "post": { "security": [], @@ -3693,6 +3715,10 @@ }, "client_id": { "type": "string" + }, + "app_owner": { + "type": "string", + "nullable": true } }, "required": [ @@ -7050,6 +7076,9 @@ "gcp_used": { "type": "boolean" }, + "azure_used": { + "type": "boolean" + }, "sqs_used": { "type": "boolean" }, @@ -7061,6 +7090,9 @@ }, "google_used": { "type": "boolean" + }, + "github_used": { + "type": "boolean" } }, "required": [ @@ -7071,10 +7103,12 @@ "postgres_used", "mqtt_used", "gcp_used", + "azure_used", "sqs_used", "email_used", "nextcloud_used", - "google_used" + "google_used", + "github_used" ] } } @@ -14707,7 +14741,7 @@ "summary": "list flow conversations", "operationId": "listFlowConversations", "tags": [ - "flow_conversation" + "flow_conversations" ], "parameters": [ { @@ -14750,7 +14784,7 @@ "summary": "delete flow conversation", "operationId": "deleteFlowConversation", "tags": [ - "flow_conversation" + "flow_conversations" ], "parameters": [ { @@ -14786,7 +14820,7 @@ "summary": "list conversation messages", "operationId": "listConversationMessages", "tags": [ - "flow_conversation" + "flow_conversations" ], "parameters": [ { @@ -16971,6 +17005,109 @@ } } }, + "/w/{workspace}/jobs/run/dependencies_async": { + "post": { + "summary": "queue a one-off dependencies job and return the job uuid", + "operationId": "runRawScriptDependenciesAsync", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "raw script content", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "raw_scripts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RawScriptForDependencies" + } + }, + "entrypoint": { + "type": "string" + } + }, + "required": [ + "entrypoint", + "raw_scripts" + ] + } + } + } + }, + "responses": { + "201": { + "description": "dependency job created", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + }, + "/w/{workspace}/jobs/run/flow_dependencies_async": { + "post": { + "summary": "queue a one-off flow dependencies job and return the job uuid", + "operationId": "runFlowDependenciesAsync", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "flow value and path", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "flow_value": { + "$ref": "#/components/schemas/FlowValue" + } + }, + "required": [ + "path", + "flow_value" + ] + } + } + } + }, + "responses": { + "201": { + "description": "flow dependencies job created", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + }, "/w/{workspace}/jobs/run/preview_flow": { "post": { "summary": "run flow preview", @@ -17644,6 +17781,13 @@ "schema": { "type": "boolean" } + }, + { + "name": "all_workspaces", + "in": "query", + "schema": { + "type": "boolean" + } } ], "requestBody": { @@ -23289,6 +23433,40 @@ } } }, + "/w/{workspace}/native_triggers/github/repos": { + "get": { + "summary": "list GitHub repositories accessible to the user", + "operationId": "listGithubRepos", + "tags": [ + "native_trigger" + ], + "parameters": [ + { + "name": "workspace", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "list of GitHub repositories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GithubRepoEntry" + } + } + } + } + } + } + } + }, "/native_triggers/{service_name}/w/{workspace_id}/webhook/{internal_id}": { "post": { "summary": "receive webhook from external native trigger service", @@ -24118,6 +24296,495 @@ } } }, + "/w/{workspace}/azure_triggers/create": { + "post": { + "summary": "create an Azure Event Grid trigger", + "operationId": "createAzureTrigger", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureTriggerData" + } + } + } + }, + "responses": { + "201": { + "description": "azure trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/update/{path}": { + "post": { + "summary": "update an Azure Event Grid trigger", + "operationId": "updateAzureTrigger", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureTriggerData" + } + } + } + }, + "responses": { + "200": { + "description": "azure trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/delete/{path}": { + "delete": { + "summary": "delete an Azure Event Grid trigger", + "operationId": "deleteAzureTrigger", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "azure trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/get/{path}": { + "get": { + "summary": "get an Azure Event Grid trigger", + "operationId": "getAzureTrigger", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "azure trigger", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/list": { + "get": { + "summary": "list azure triggers", + "operationId": "listAzureTriggers", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by exact path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "azure trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AzureTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/exists/{path}": { + "get": { + "summary": "check whether an azure trigger exists", + "operationId": "existsAzureTrigger", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "true/false", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/setmode/{path}": { + "post": { + "summary": "set azure trigger mode", + "operationId": "setAzureTriggerMode", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/components/schemas/TriggerMode" + } + }, + "required": [ + "mode" + ] + } + } + } + }, + "responses": { + "200": { + "description": "trigger mode updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/test": { + "post": { + "summary": "test Azure service principal connection", + "operationId": "testAzureConnection", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestAzureConnection" + } + } + } + }, + "responses": { + "200": { + "description": "connection successful", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/namespaces/topics/list/{path}": { + "post": { + "summary": "list topics under an Event Grid Namespace", + "operationId": "listAzureNamespaceTopics", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureListTopics" + } + } + } + }, + "responses": { + "200": { + "description": "topic list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/namespaces/subscriptions/list/{path}": { + "post": { + "summary": "list subscriptions under a Namespace topic", + "operationId": "listAzureNamespaceSubscriptions", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureListSubscriptions" + } + } + } + }, + "responses": { + "200": { + "description": "subscription list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/subscriptions/delete/{path}": { + "delete": { + "summary": "delete an Event Grid subscription on Azure", + "operationId": "deleteAzureSubscription", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AzureDeleteSubscription" + } + } + } + }, + "responses": { + "200": { + "description": "subscription deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/namespaces/list/{path}": { + "post": { + "summary": "list Event Grid Namespaces the service principal can access", + "operationId": "listAzureNamespaces", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "namespace list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AzureArmResource" + } + } + } + } + } + } + } + }, + "/w/{workspace}/azure_triggers/basic/topics/list/{path}": { + "post": { + "summary": "list Basic Event Grid topics + system topics the service principal can access", + "operationId": "listAzureBasicTopics", + "tags": [ + "azure_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "topic list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AzureArmResource" + } + } + } + } + } + } + } + }, "/w/{workspace}/postgres_triggers/postgres/version/{path}": { "get": { "summary": "get postgres version", @@ -27140,6 +27807,7 @@ "postgres_trigger", "mqtt_trigger", "gcp_trigger", + "azure_trigger", "sqs_trigger", "email_trigger", "volume" @@ -27201,6 +27869,7 @@ "postgres_trigger", "mqtt_trigger", "gcp_trigger", + "azure_trigger", "sqs_trigger", "email_trigger", "volume" @@ -27281,6 +27950,7 @@ "postgres_trigger", "mqtt_trigger", "gcp_trigger", + "azure_trigger", "sqs_trigger", "email_trigger", "volume" @@ -36900,7 +37570,9 @@ "mqtt", "sqs", "gcp", - "google" + "azure", + "google", + "github" ] }, "TriggerMode": { @@ -37541,6 +38213,9 @@ "gcp_count": { "type": "number" }, + "azure_count": { + "type": "number" + }, "sqs_count": { "type": "number" }, @@ -37549,6 +38224,9 @@ }, "google_count": { "type": "number" + }, + "github_count": { + "type": "number" } } }, @@ -38436,6 +39114,233 @@ "subscription_id" ] }, + "AzureMode": { + "type": "string", + "enum": [ + "basic_push", + "namespace_push", + "namespace_pull" + ], + "description": "Azure Event Grid trigger mode." + }, + "AzureArmResource": { + "type": "object", + "description": "An ARM resource the service principal can see.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "AzureDeleteSubscription": { + "type": "object", + "properties": { + "azure_mode": { + "$ref": "#/components/schemas/AzureMode" + }, + "scope_resource_id": { + "type": "string" + }, + "topic_name": { + "type": "string", + "nullable": true + }, + "subscription_name": { + "type": "string" + } + }, + "required": [ + "azure_mode", + "scope_resource_id", + "subscription_name" + ] + }, + "AzureTrigger": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggerExtraProperty" + } + ], + "type": "object", + "description": "An Azure Event Grid trigger that executes a script or flow when events arrive.", + "properties": { + "azure_resource_path": { + "type": "string" + }, + "azure_mode": { + "$ref": "#/components/schemas/AzureMode" + }, + "scope_resource_id": { + "type": "string", + "description": "ARM resource ID of the topic (basic) or namespace (namespace modes)." + }, + "topic_name": { + "type": "string", + "nullable": true, + "description": "Topic name within the namespace (namespace modes only)." + }, + "subscription_name": { + "type": "string" + }, + "event_type_filters": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" + } + }, + "required": [ + "azure_resource_path", + "azure_mode", + "scope_resource_id", + "subscription_name" + ] + }, + "AzureTriggerData": { + "type": "object", + "description": "Data for creating or updating an Azure Event Grid trigger.", + "properties": { + "azure_resource_path": { + "type": "string" + }, + "azure_mode": { + "$ref": "#/components/schemas/AzureMode" + }, + "scope_resource_id": { + "type": "string" + }, + "topic_name": { + "type": "string", + "nullable": true + }, + "subscription_name": { + "type": "string" + }, + "base_endpoint": { + "type": "string", + "description": "Base URL for push delivery endpoints (push modes only)." + }, + "event_type_filters": { + "type": "array", + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "mode": { + "$ref": "#/components/schemas/TriggerMode" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" + }, + "permissioned_as": { + "type": "string" + }, + "preserve_permissioned_as": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "path", + "script_path", + "is_flow", + "azure_resource_path", + "azure_mode", + "scope_resource_id", + "subscription_name" + ] + }, + "TestAzureConnection": { + "type": "object", + "properties": { + "azure_resource_path": { + "type": "string" + } + }, + "required": [ + "azure_resource_path" + ] + }, + "AzureListTopics": { + "type": "object", + "properties": { + "scope_resource_id": { + "type": "string" + } + }, + "required": [ + "scope_resource_id" + ] + }, + "AzureListSubscriptions": { + "type": "object", + "properties": { + "scope_resource_id": { + "type": "string" + }, + "topic_name": { + "type": "string" + } + }, + "required": [ + "scope_resource_id", + "topic_name" + ] + }, "AwsAuthResourceType": { "type": "string", "enum": [ @@ -41604,6 +42509,7 @@ "sqs", "mqtt", "gcp", + "azure", "email" ] }, @@ -42327,7 +43233,8 @@ "type": "string", "enum": [ "nextcloud", - "google" + "google", + "github" ] }, "NativeTrigger": { @@ -42689,6 +43596,29 @@ "name" ] }, + "GithubRepoEntry": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "required": [ + "full_name", + "name", + "owner", + "private" + ] + }, "schemas-StaticTransform": { "type": "object", "description": "Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'", diff --git a/backend/windmill-api/openapi-deref.yaml b/backend/windmill-api/openapi-deref.yaml index 4783545b60..8f24919fe5 100644 --- a/backend/windmill-api/openapi-deref.yaml +++ b/backend/windmill-api/openapi-deref.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 1.685.0 + version: 1.689.0 title: Windmill API contact: name: Windmill Team @@ -146,18 +146,18 @@ paths: checks: type: object description: Detailed health checks - required: &ref_336 + required: &ref_346 - database - readiness - properties: &ref_337 + properties: &ref_347 database: type: object description: Database health status - required: &ref_338 + required: &ref_348 - healthy - latency_ms - pool - properties: &ref_339 + properties: &ref_349 healthy: type: boolean description: Whether the database is reachable @@ -168,11 +168,11 @@ paths: pool: type: object description: Database connection pool statistics - required: &ref_340 + required: &ref_350 - size - idle - max_connections - properties: &ref_341 + properties: &ref_351 size: type: integer description: Current number of connections in the pool @@ -186,13 +186,13 @@ paths: description: Workers health status nullable: true type: object - required: &ref_342 + required: &ref_352 - healthy - active_count - worker_groups - min_version - versions - properties: &ref_343 + properties: &ref_353 healthy: type: boolean description: Whether any workers are active @@ -219,10 +219,10 @@ paths: description: Job queue status nullable: true type: object - required: &ref_344 + required: &ref_354 - pending_jobs - running_jobs - properties: &ref_345 + properties: &ref_355 pending_jobs: type: integer format: int64 @@ -234,9 +234,9 @@ paths: readiness: type: object description: Server readiness status - required: &ref_346 + required: &ref_356 - healthy - properties: &ref_347 + properties: &ref_357 healthy: type: boolean description: Whether the server is ready to accept requests @@ -488,24 +488,24 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: &ref_288 + schema: &ref_298 type: string format: date-time - name: after description: filter on created after (exclusive) timestamp in: query - schema: &ref_289 + schema: &ref_299 type: string format: date-time - name: username description: filter on exact username of user in: query - schema: &ref_297 + schema: &ref_307 type: string - name: operation description: filter on exact or prefix name of operation in: query - schema: &ref_298 + schema: &ref_308 type: string - name: operations in: query @@ -520,12 +520,12 @@ paths: - name: resource description: filter on exact or prefix name of resource in: query - schema: &ref_299 + schema: &ref_309 type: string - name: action_kind description: filter on type of operation in: query - schema: &ref_300 + schema: &ref_310 type: string enum: - Create @@ -562,12 +562,12 @@ paths: application/json: schema: type: object - properties: &ref_386 + properties: &ref_396 email: type: string password: type: string - required: &ref_387 + required: &ref_397 - email - password responses: @@ -618,6 +618,20 @@ paths: application/json: schema: type: boolean + /auth/is_password_login_disabled: + get: + security: [] + summary: check if password login is disabled instance-wide + operationId: isPasswordLoginDisabled + tags: + - user + responses: + '200': + description: returns true if password login is disabled + content: + application/json: + schema: + type: boolean /auth/request_password_reset: post: security: [] @@ -743,7 +757,7 @@ paths: nullable: true allOf: - type: object - properties: &ref_383 + properties: &ref_393 source: type: string enum: @@ -761,7 +775,7 @@ paths: description: >- The instance group name (when source is 'instance_group') - required: &ref_384 + required: &ref_394 - source is_service_account: type: boolean @@ -799,7 +813,7 @@ paths: application/json: schema: type: object - properties: &ref_388 + properties: &ref_398 is_admin: type: boolean operator: @@ -1173,7 +1187,7 @@ paths: type: array items: type: object - properties: &ref_402 + properties: &ref_412 jwt_hash: type: integer format: int64 @@ -1196,7 +1210,7 @@ paths: last_used_at: type: string format: date-time - required: &ref_403 + required: &ref_413 - jwt_hash - email - username @@ -1328,7 +1342,7 @@ paths: type: array items: type: object - properties: &ref_389 + properties: &ref_399 label: type: string scopes: @@ -1337,7 +1351,7 @@ paths: type: string expiration: type: string - required: &ref_390 + required: &ref_400 - label - scopes description: Tokens owned by this user (will be deleted) @@ -1381,7 +1395,7 @@ paths: application/json: schema: type: object - properties: &ref_391 + properties: &ref_401 reassign_to: type: string description: 'Target for reassignment: ''u/{username}'' or ''f/{folder}''' @@ -1395,7 +1409,7 @@ paths: type: boolean default: true description: Whether to also remove the user from the workspace - required: &ref_392 + required: &ref_402 - reassign_to responses: '200': @@ -1414,7 +1428,7 @@ paths: on success. summary: type: object - properties: &ref_393 + properties: &ref_403 scripts_reassigned: type: integer flows_reassigned: @@ -1431,7 +1445,7 @@ paths: type: integer drafts_deleted: type: integer - required: &ref_394 + required: &ref_404 - scripts_reassigned - flows_reassigned - apps_reassigned @@ -1461,12 +1475,12 @@ paths: application/json: schema: type: object - properties: &ref_395 + properties: &ref_405 workspaces: type: array items: type: object - properties: &ref_397 + properties: &ref_407 workspace_id: type: string username: @@ -1475,11 +1489,11 @@ paths: type: object properties: *ref_12 required: *ref_13 - required: &ref_398 + required: &ref_408 - workspace_id - username - preview - required: &ref_396 + required: &ref_406 - workspaces /users/offboard/{email}: post: @@ -1501,12 +1515,12 @@ paths: application/json: schema: type: object - properties: &ref_399 + properties: &ref_409 reassignments: type: object additionalProperties: type: object - properties: &ref_400 + properties: &ref_410 reassign_to: type: string description: 'Target: ''u/{username}'' or ''f/{folder}''' @@ -1515,7 +1529,7 @@ paths: description: >- Required when reassign_to is a folder. Username to use as permissioned_as. - required: &ref_401 + required: &ref_411 - reassign_to description: Map of workspace_id to reassignment config delete_user: @@ -1575,7 +1589,7 @@ paths: application/json: schema: type: array - items: &ref_542 + items: &ref_558 type: object properties: workspace_id: @@ -1673,7 +1687,7 @@ paths: application/json: schema: type: object - properties: &ref_483 + properties: &ref_501 email: type: string workspaces: @@ -1748,7 +1762,7 @@ paths: - username - color - disabled - required: &ref_484 + required: &ref_502 - email - workspaces /w/{workspace}/workspaces/get_as_superadmin: @@ -1810,7 +1824,7 @@ paths: application/json: schema: type: object - properties: &ref_485 + properties: &ref_503 id: type: string name: @@ -1819,7 +1833,7 @@ paths: type: string color: type: string - required: &ref_486 + required: &ref_504 - id - name responses: @@ -1995,7 +2009,7 @@ paths: properties: &ref_24 logs: type: object - properties: &ref_451 + properties: &ref_469 super_admin: type: string enum: &ref_21 @@ -2689,11 +2703,11 @@ paths: type: array items: type: object - properties: &ref_525 + properties: &ref_541 name: type: string value: {} - required: &ref_526 + required: &ref_542 - name - value /settings/instance_config: @@ -2791,9 +2805,9 @@ paths: application/json: schema: type: object - required: &ref_359 + required: &ref_369 - keys - properties: &ref_360 + properties: &ref_370 keys: type: array items: @@ -2895,11 +2909,11 @@ paths: type: array items: type: object - required: &ref_357 + required: &ref_367 - workspace_id - path - error - properties: &ref_358 + properties: &ref_368 workspace_id: type: string description: Workspace ID where the secret is located @@ -3546,6 +3560,9 @@ paths: type: string client_id: type: string + app_owner: + type: string + nullable: true required: - base_url - app_slug @@ -4064,13 +4081,13 @@ paths: application/json: schema: type: object - required: &ref_534 + required: &ref_550 - all_ahead_items_visible - all_behind_items_visible - skipped_comparison - diffs - summary - properties: &ref_535 + properties: &ref_551 all_ahead_items_visible: type: boolean description: >- @@ -4091,7 +4108,7 @@ paths: description: List of differences found between workspaces items: type: object - required: &ref_536 + required: &ref_552 - kind - path - ahead @@ -4099,7 +4116,7 @@ paths: - has_changes - exists_in_source - exists_in_fork - properties: &ref_537 + properties: &ref_553 kind: type: string enum: @@ -4132,7 +4149,7 @@ paths: summary: description: Summary statistics of the comparison type: object - required: &ref_538 + required: &ref_554 - total_diffs - total_ahead - total_behind @@ -4144,7 +4161,7 @@ paths: - resource_types_changed - folders_changed - conflicts - properties: &ref_539 + properties: &ref_555 total_diffs: type: integer description: Total number of items with differences @@ -4318,7 +4335,7 @@ paths: auto_invite: type: object description: Configuration for auto-inviting users to the workspace - properties: &ref_348 + properties: &ref_358 enabled: type: boolean default: false @@ -4359,14 +4376,14 @@ paths: type: object additionalProperties: type: object - properties: &ref_367 + properties: &ref_377 resource_path: type: string models: type: array items: type: string - required: &ref_368 + required: &ref_378 - resource_path - models default_model: @@ -4408,7 +4425,7 @@ paths: error_handler: type: object description: Configuration for the workspace error handler - properties: &ref_349 + properties: &ref_359 path: type: string description: Path to the error handler script or flow @@ -4425,7 +4442,7 @@ paths: success_handler: type: object description: Configuration for the workspace success handler - properties: &ref_350 + properties: &ref_360 path: type: string description: Path to the success handler script or flow @@ -4456,12 +4473,12 @@ paths: type: array items: type: object - properties: &ref_509 + properties: &ref_527 pattern: type: string allow: type: string - required: &ref_510 + required: &ref_528 - pattern - allow secondary_storage: @@ -4832,7 +4849,7 @@ paths: type: array items: type: object - properties: &ref_488 + properties: &ref_506 importer_path: type: string importer_kind: @@ -4846,7 +4863,7 @@ paths: items: type: string nullable: true - required: &ref_489 + required: &ref_507 - importer_path - importer_kind /w/{workspace}/workspaces/get_imports/{importer_path}: @@ -4904,13 +4921,13 @@ paths: type: array items: type: object - properties: &ref_490 + properties: &ref_508 imported_path: type: string count: type: integer format: int64 - required: &ref_491 + required: &ref_509 - imported_path - count /w/{workspace}/workspaces/get_dependency_map: @@ -4933,7 +4950,7 @@ paths: type: array items: type: object - properties: &ref_487 + properties: &ref_505 workspace_id: type: string nullable: true @@ -5432,7 +5449,7 @@ paths: type: array items: type: object - properties: &ref_369 + properties: &ref_379 provider: type: string enum: *ref_47 @@ -5440,7 +5457,7 @@ paths: type: array items: type: string - required: &ref_370 + required: &ref_380 - provider - models default_model: @@ -5527,10 +5544,10 @@ paths: Request body for editing the workspace error handler. Accepts both new grouped format and legacy flat format for backward compatibility. - oneOf: &ref_351 + oneOf: &ref_361 - type: object description: New grouped format for editing error handler - properties: &ref_352 + properties: &ref_362 path: type: string description: Path to the error handler script or flow @@ -5548,7 +5565,7 @@ paths: description: >- Legacy flat format for editing error handler (deprecated, use new format) - properties: &ref_353 + properties: &ref_363 error_handler: type: string description: Path to the error handler script or flow @@ -5587,10 +5604,10 @@ paths: Request body for editing the workspace success handler. Accepts both new grouped format and legacy flat format for backward compatibility. - oneOf: &ref_354 + oneOf: &ref_364 - type: object description: New grouped format for editing success handler - properties: &ref_355 + properties: &ref_365 path: type: string description: Path to the success handler script or flow @@ -5602,7 +5619,7 @@ paths: description: >- Legacy flat format for editing success handler (deprecated, use new format) - properties: &ref_356 + properties: &ref_366 success_handler: type: string description: Path to the success handler script or flow @@ -5719,10 +5736,10 @@ paths: type: array items: type: object - required: &ref_505 + required: &ref_523 - datatable_name - schemas - properties: &ref_506 + properties: &ref_524 datatable_name: type: string schemas: @@ -6424,6 +6441,8 @@ paths: type: boolean gcp_used: type: boolean + azure_used: + type: boolean sqs_used: type: boolean email_used: @@ -6432,6 +6451,8 @@ paths: type: boolean google_used: type: boolean + github_used: + type: boolean required: - http_routes_used - websocket_used @@ -6440,10 +6461,12 @@ paths: - postgres_used - mqtt_used - gcp_used + - azure_used - sqs_used - email_used - nextcloud_used - google_used + - github_used /w/{workspace}/users/list: get: summary: list users @@ -6486,7 +6509,7 @@ paths: type: array items: type: object - properties: &ref_385 + properties: &ref_395 email: type: string executions: @@ -6549,7 +6572,7 @@ paths: type: array items: type: object - properties: &ref_500 + properties: &ref_518 name: type: string description: @@ -6559,7 +6582,7 @@ paths: type: array items: type: object - properties: &ref_498 + properties: &ref_516 value: type: string label: @@ -6569,11 +6592,11 @@ paths: nullable: true requires_resource_path: type: boolean - required: &ref_499 + required: &ref_517 - value - label - requires_resource_path - required: &ref_501 + required: &ref_519 - name - scopes /users/tokens/create: @@ -6589,7 +6612,7 @@ paths: application/json: schema: type: object - properties: &ref_404 + properties: &ref_414 label: type: string expiration: @@ -6621,7 +6644,7 @@ paths: application/json: schema: type: object - properties: &ref_405 + properties: &ref_415 label: type: string expiration: @@ -6631,7 +6654,7 @@ paths: type: string workspace_id: type: string - required: &ref_406 + required: &ref_416 - impersonate_email responses: '201': @@ -6762,7 +6785,7 @@ paths: application/json: schema: type: object - properties: &ref_409 + properties: &ref_419 path: type: string description: The path to the variable @@ -6789,7 +6812,7 @@ paths: type: array items: type: string - required: &ref_410 + required: &ref_420 - path - value - is_secret @@ -6911,7 +6934,7 @@ paths: application/json: schema: type: object - properties: &ref_411 + properties: &ref_421 path: type: string description: The path to the variable @@ -7147,7 +7170,7 @@ paths: type: array items: type: object - properties: &ref_407 + properties: &ref_417 name: type: string value: @@ -7156,7 +7179,7 @@ paths: type: string is_custom: type: boolean - required: &ref_408 + required: &ref_418 - name - value - description @@ -7343,12 +7366,12 @@ paths: description: >- A workspace protection rule defining restrictions and bypass permissions - required: &ref_545 + required: &ref_561 - name - rules - bypass_groups - bypass_users - properties: &ref_546 + properties: &ref_562 name: type: string description: Unique name for the protection rule @@ -7360,7 +7383,7 @@ paths: description: Configuration of protection restrictions items: &ref_64 type: string - enum: &ref_547 + enum: &ref_563 - DisableDirectDeployment - DisableWorkspaceForking - RestrictDeployToDeployers @@ -7520,11 +7543,11 @@ paths: type: array items: type: object - required: &ref_548 + required: &ref_564 - username - email - is_admin - properties: &ref_549 + properties: &ref_565 username: type: string email: @@ -7579,10 +7602,10 @@ paths: type: array items: type: object - required: &ref_550 + required: &ref_566 - username - email - properties: &ref_551 + properties: &ref_567 username: type: string email: @@ -8411,7 +8434,7 @@ paths: application/json: schema: type: object - properties: &ref_416 + properties: &ref_426 path: type: string description: The path to the resource @@ -8426,7 +8449,7 @@ paths: type: array items: type: string - required: &ref_417 + required: &ref_427 - path - value - resource_type @@ -8517,7 +8540,7 @@ paths: application/json: schema: type: object - properties: &ref_418 + properties: &ref_428 path: type: string description: The path to the resource @@ -8593,7 +8616,7 @@ paths: application/json: schema: type: object - properties: &ref_419 + properties: &ref_429 workspace_id: type: string path: @@ -8618,7 +8641,7 @@ paths: type: array items: type: string - required: &ref_420 + required: &ref_430 - path - resource_type - is_oauth @@ -8801,7 +8824,7 @@ paths: type: array items: type: object - properties: &ref_421 + properties: &ref_431 workspace_id: type: string path: @@ -8836,7 +8859,7 @@ paths: type: array items: type: string - required: &ref_422 + required: &ref_432 - path - resource_type - is_oauth @@ -8917,7 +8940,7 @@ paths: - name: name in: path required: true - schema: &ref_262 + schema: &ref_272 type: string responses: '200': @@ -9050,7 +9073,7 @@ paths: application/json: schema: type: object - properties: &ref_423 + properties: &ref_433 schema: {} description: type: string @@ -9439,7 +9462,7 @@ paths: description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: &ref_588 + properties: &ref_606 modules: type: array description: >- @@ -9471,7 +9494,7 @@ paths: in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms - properties: &ref_312 + properties: &ref_322 input_transforms: type: object description: >- @@ -9649,7 +9672,7 @@ paths: - r - w - rw - required: &ref_313 + required: &ref_323 - type - content - language @@ -9659,7 +9682,7 @@ paths: Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code - properties: &ref_314 + properties: &ref_324 input_transforms: type: object description: >- @@ -9699,7 +9722,7 @@ paths: description: >- If true, this script is a trigger that can start the flow - required: &ref_315 + required: &ref_325 - type - path - input_transforms @@ -9708,7 +9731,7 @@ paths: Reference to an existing flow by path. Use this to call another flow as a subflow - properties: &ref_316 + properties: &ref_326 input_transforms: type: object description: >- @@ -9733,7 +9756,7 @@ paths: type: string enum: - flow - required: &ref_317 + required: &ref_327 - type - path - input_transforms @@ -9746,7 +9769,7 @@ paths: 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations - properties: &ref_318 + properties: &ref_328 modules: type: array description: >- @@ -9795,7 +9818,7 @@ paths: discriminator: *ref_81 squash: type: boolean - required: &ref_319 + required: &ref_329 - modules - iterator - skip_failures @@ -9807,7 +9830,7 @@ paths: condition after each iteration. Use stop_after_if on modules to control loop termination - properties: &ref_320 + properties: &ref_330 modules: type: array description: >- @@ -9845,7 +9868,7 @@ paths: discriminator: *ref_81 squash: type: boolean - required: &ref_321 + required: &ref_331 - modules - skip_failures - type @@ -9857,7 +9880,7 @@ paths: one with a true expression runs. If no branches match, the default branch executes - properties: &ref_322 + properties: &ref_332 branches: type: array description: >- @@ -9909,7 +9932,7 @@ paths: type: string enum: - branchone - required: &ref_323 + required: &ref_333 - branches - default - type @@ -9920,7 +9943,7 @@ paths: BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently - properties: &ref_324 + properties: &ref_334 branches: type: array description: >- @@ -9961,7 +9984,7 @@ paths: If true, all branches execute concurrently. If false, they execute sequentially - required: &ref_325 + required: &ref_335 - branches - type - type: object @@ -9969,7 +9992,7 @@ paths: Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder - properties: &ref_326 + properties: &ref_336 type: type: string enum: @@ -9979,7 +10002,7 @@ paths: description: >- If true, marks this as a flow identity (special handling) - required: &ref_327 + required: &ref_337 - type - type: object description: >- @@ -9987,7 +10010,7 @@ paths: accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task - properties: &ref_328 + properties: &ref_338 input_transforms: type: object description: >- @@ -9999,22 +10022,22 @@ paths: Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined - oneOf: &ref_330 + oneOf: &ref_340 - type: object description: >- Static provider configuration passed directly to the AI agent - properties: &ref_573 + properties: &ref_591 value: type: object description: >- Complete AI provider configuration with resource reference and model selection - properties: &ref_571 + properties: &ref_589 kind: type: string description: Supported AI provider types - enum: &ref_305 + enum: &ref_315 - openai - azure_openai - anthropic @@ -10037,7 +10060,7 @@ paths: description: >- Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro') - required: &ref_572 + required: &ref_590 - kind - resource - model @@ -10045,7 +10068,7 @@ paths: type: string enum: - static - required: &ref_574 + required: &ref_592 - type - value - type: object @@ -10065,7 +10088,7 @@ paths: satisfy the parameter. properties: *ref_86 required: *ref_87 - discriminator: &ref_331 + discriminator: &ref_341 propertyName: type mapping: static: >- @@ -10135,27 +10158,27 @@ paths: Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined - oneOf: &ref_332 + oneOf: &ref_342 - type: object description: >- Static memory configuration passed directly to the AI agent - properties: &ref_579 + properties: &ref_597 value: description: Conversation memory configuration - oneOf: &ref_577 + oneOf: &ref_595 - type: object description: No conversation memory/context - properties: &ref_306 + properties: &ref_316 kind: type: string enum: - 'off' - required: &ref_307 + required: &ref_317 - kind - type: object description: Automatic context management - properties: &ref_308 + properties: &ref_318 kind: type: string enum: @@ -10170,11 +10193,11 @@ paths: description: >- Identifier for persistent memory across agent invocations - required: &ref_309 + required: &ref_319 - kind - type: object description: Explicit message history - properties: &ref_310 + properties: &ref_320 kind: type: string enum: @@ -10184,7 +10207,7 @@ paths: items: type: object description: A single message in conversation history - properties: &ref_575 + properties: &ref_593 role: type: string enum: @@ -10193,13 +10216,13 @@ paths: - system content: type: string - required: &ref_576 + required: &ref_594 - role - content - required: &ref_311 + required: &ref_321 - kind - messages - discriminator: &ref_578 + discriminator: &ref_596 propertyName: kind mapping: 'off': '#/components/schemas/MemoryOff' @@ -10209,7 +10232,7 @@ paths: type: string enum: - static - required: &ref_580 + required: &ref_598 - type - value - type: object @@ -10229,7 +10252,7 @@ paths: satisfy the parameter. properties: *ref_86 required: *ref_87 - discriminator: &ref_333 + discriminator: &ref_343 propertyName: type mapping: static: >- @@ -10328,7 +10351,7 @@ paths: A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool - properties: &ref_334 + properties: &ref_344 id: type: string description: >- @@ -10346,12 +10369,12 @@ paths: The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference - oneOf: &ref_586 + oneOf: &ref_604 - description: >- A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module - allOf: &ref_581 + allOf: &ref_599 - type: object properties: tool_type: @@ -10384,7 +10407,7 @@ paths: Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers - properties: &ref_582 + properties: &ref_600 tool_type: type: string enum: @@ -10408,7 +10431,7 @@ paths: MCP server items: type: string - required: &ref_583 + required: &ref_601 - tool_type - resource_path - type: object @@ -10416,20 +10439,20 @@ paths: A tool implemented as a websearch tool. The AI can call this like any other websearch tool - properties: &ref_584 + properties: &ref_602 tool_type: type: string enum: - websearch - required: &ref_585 + required: &ref_603 - tool_type - discriminator: &ref_587 + discriminator: &ref_605 propertyName: tool_type mapping: flowmodule: '#/components/schemas/FlowModuleTool' mcp: '#/components/schemas/McpToolValue' websearch: '#/components/schemas/WebsearchToolValue' - required: &ref_335 + required: &ref_345 - id - value type: @@ -10441,7 +10464,7 @@ paths: description: >- If true, the agent can execute multiple tool calls in parallel - required: &ref_329 + required: &ref_339 - tools - type - input_transforms @@ -10605,7 +10628,7 @@ paths: Retry configuration for failed module executions type: object - properties: &ref_304 + properties: &ref_314 constant: type: object description: >- @@ -10646,14 +10669,14 @@ paths: description: >- Conditional retry based on error or result - properties: &ref_192 + properties: &ref_194 expr: type: string description: >- JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables - required: &ref_193 + required: &ref_195 - expr debouncing: description: >- @@ -10787,7 +10810,7 @@ paths: description: >- A sticky note attached to a flow for documentation and annotation - properties: &ref_143 + properties: &ref_145 id: type: string description: Unique identifier for the note @@ -10846,7 +10869,7 @@ paths: description: >- For group notes, the IDs of nodes contained within this group - required: &ref_144 + required: &ref_146 - id - text - color @@ -10866,7 +10889,7 @@ paths: collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id. - properties: &ref_145 + properties: &ref_147 summary: type: string description: Display name for this group @@ -10892,10 +10915,10 @@ paths: color: type: string description: Color for the group in the flow editor - required: &ref_146 + required: &ref_148 - start_id - end_id - required: &ref_589 + required: &ref_607 - modules schema: type: object @@ -11929,7 +11952,7 @@ paths: type: string kind: type: string - enum: &ref_290 + enum: &ref_300 - s3object - resource - ducklake @@ -12093,7 +12116,7 @@ paths: application/json: schema: type: object - properties: &ref_374 + properties: &ref_384 workspace_id: type: string language: @@ -12105,7 +12128,7 @@ paths: type: string content: type: string - required: &ref_375 + required: &ref_385 - workspace_id - language - content @@ -12462,12 +12485,16 @@ paths: type: number gcp_count: type: number + azure_count: + type: number sqs_count: type: number nextcloud_count: type: number google_count: type: number + github_count: + type: number /w/{workspace}/scripts/list_tokens/{path}: get: summary: get tokens with script scope @@ -12515,7 +12542,7 @@ paths: content: application/json: schema: - allOf: &ref_376 + allOf: &ref_386 - type: object properties: *ref_104 required: *ref_105 @@ -12738,7 +12765,7 @@ paths: - name: token in: path required: true - schema: &ref_294 + schema: &ref_304 type: string - name: path in: path @@ -14316,7 +14343,7 @@ paths: properties: *ref_120 required: *ref_121 - type: object - properties: &ref_493 + properties: &ref_511 workspace_id: type: string path: @@ -14330,7 +14357,7 @@ paths: type: boolean extra_perms: type: object - additionalProperties: &ref_492 + additionalProperties: &ref_510 type: boolean starred: type: boolean @@ -14355,7 +14382,7 @@ paths: items: type: string default: [] - required: &ref_494 + required: &ref_512 - path - edited_by - edited_at @@ -14902,7 +14929,7 @@ paths: summary: list flow conversations operationId: listFlowConversations tags: - - flow_conversation + - flow_conversations parameters: - name: workspace in: path @@ -14930,14 +14957,14 @@ paths: type: array items: type: object - required: &ref_361 + required: &ref_371 - id - workspace_id - flow_path - created_at - updated_at - created_by - properties: &ref_362 + properties: &ref_372 id: type: string format: uuid @@ -14968,7 +14995,7 @@ paths: summary: delete flow conversation operationId: deleteFlowConversation tags: - - flow_conversation + - flow_conversations parameters: - name: workspace in: path @@ -14993,7 +15020,7 @@ paths: summary: list conversation messages operationId: listConversationMessages tags: - - flow_conversation + - flow_conversations parameters: - name: workspace in: path @@ -15030,13 +15057,13 @@ paths: type: array items: type: object - required: &ref_363 + required: &ref_373 - id - conversation_id - message_type - content - created_at - properties: &ref_364 + properties: &ref_374 id: type: string format: uuid @@ -15169,7 +15196,7 @@ paths: type: array items: type: object - properties: &ref_502 + properties: &ref_520 workspace_id: type: string path: @@ -15192,7 +15219,7 @@ paths: items: type: string default: [] - required: &ref_503 + required: &ref_521 - workspace_id - path - summary @@ -15325,7 +15352,7 @@ paths: type: array items: type: object - properties: &ref_496 + properties: &ref_514 id: type: integer workspace_id: @@ -15358,7 +15385,7 @@ paths: items: type: string default: [] - required: &ref_497 + required: &ref_515 - id - workspace_id - path @@ -15590,7 +15617,7 @@ paths: content: application/json: schema: - allOf: &ref_504 + allOf: &ref_522 - type: object properties: *ref_128 required: *ref_129 @@ -15700,7 +15727,7 @@ paths: - name: version in: path required: true - schema: &ref_295 + schema: &ref_305 type: integer requestBody: description: App deployment message @@ -16496,7 +16523,7 @@ paths: - name: id in: path required: true - schema: &ref_169 + schema: &ref_171 type: string format: uuid - name: scheduled_for @@ -16764,7 +16791,7 @@ paths: application/json: schema: type: object - properties: &ref_412 + properties: &ref_422 content: type: string description: The code to run @@ -16775,7 +16802,7 @@ paths: language: type: string enum: *ref_94 - required: &ref_413 + required: &ref_423 - content - args - language @@ -16910,12 +16937,12 @@ paths: application/json: schema: type: object - properties: &ref_414 + properties: &ref_424 args: type: object description: The arguments to pass to the script or flow additionalProperties: true - required: &ref_415 + required: &ref_425 - args responses: '201': @@ -16948,7 +16975,7 @@ paths: type: array items: type: object - properties: &ref_519 + properties: &ref_143 raw_code: type: string path: @@ -16956,7 +16983,7 @@ paths: language: type: string enum: *ref_94 - required: &ref_520 + required: &ref_144 - raw_code - path - language @@ -16977,10 +17004,10 @@ paths: type: string required: - lock - /w/{workspace}/jobs/run/preview_flow: + /w/{workspace}/jobs/run/dependencies_async: post: - summary: run flow preview - operationId: runFlowPreview + summary: queue a one-off dependencies job and return the job uuid + operationId: runRawScriptDependenciesAsync tags: - job parameters: @@ -16988,48 +17015,60 @@ paths: in: path required: true schema: *ref_4 - - name: include_header - description: > - List of headers's keys (separated with ',') whove value are added to - the args - - Header's key lowercased and '-'' replaced to '_' such that - 'Content-Type' becomes the 'content_type' arg key - in: query - schema: *ref_114 - - name: invisible_to_owner - description: make the run invisible to the the script owner (default false) - in: query - schema: - type: boolean - - name: job_id - description: >- - The job id to assign to the created job. if missing, job is chosen - randomly using the ULID scheme. If a job id already exists in the - queue or as a completed job, the request to create one will fail - (Bad Request) - in: query - schema: *ref_113 - - name: memory_id - description: memory ID for chat-enabled flows - in: query - schema: - type: string - format: uuid requestBody: - description: preview + description: raw script content required: true content: application/json: schema: type: object - properties: &ref_147 - value: + properties: + raw_scripts: + type: array + items: + type: object + properties: *ref_143 + required: *ref_144 + entrypoint: + type: string + required: + - entrypoint + - raw_scripts + responses: + '201': + description: dependency job created + content: + text/plain: + schema: + type: string + format: uuid + /w/{workspace}/jobs/run/flow_dependencies_async: + post: + summary: queue a one-off flow dependencies job and return the job uuid + operationId: runFlowDependenciesAsync + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + requestBody: + description: flow value and path + required: true + content: + application/json: + schema: + type: object + properties: + path: + type: string + flow_value: type: object description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: &ref_151 + properties: &ref_149 modules: type: array description: >- @@ -17131,8 +17170,8 @@ paths: description: >- A sticky note attached to a flow for documentation and annotation - properties: *ref_143 - required: *ref_144 + properties: *ref_145 + required: *ref_146 groups: type: array description: Semantic groups of modules for organizational purposes @@ -17145,10 +17184,75 @@ paths: naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id. - properties: *ref_145 - required: *ref_146 - required: &ref_152 + properties: *ref_147 + required: *ref_148 + required: &ref_150 - modules + required: + - path + - flow_value + responses: + '201': + description: flow dependencies job created + content: + text/plain: + schema: + type: string + format: uuid + /w/{workspace}/jobs/run/preview_flow: + post: + summary: run flow preview + operationId: runFlowPreview + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: include_header + description: > + List of headers's keys (separated with ',') whove value are added to + the args + + Header's key lowercased and '-'' replaced to '_' such that + 'Content-Type' becomes the 'content_type' arg key + in: query + schema: *ref_114 + - name: invisible_to_owner + description: make the run invisible to the the script owner (default false) + in: query + schema: + type: boolean + - name: job_id + description: >- + The job id to assign to the created job. if missing, job is chosen + randomly using the ULID scheme. If a job id already exists in the + queue or as a completed job, the request to create one will fail + (Bad Request) + in: query + schema: *ref_113 + - name: memory_id + description: memory ID for chat-enabled flows + in: query + schema: + type: string + format: uuid + requestBody: + description: preview + required: true + content: + application/json: + schema: + type: object + properties: &ref_151 + value: + type: object + description: >- + The flow structure containing modules and optional + preprocessor/failure handlers + properties: *ref_149 + required: *ref_150 path: type: string args: @@ -17159,7 +17263,7 @@ paths: type: string restarted_from: type: object - properties: &ref_495 + properties: &ref_513 flow_job_id: type: string format: uuid @@ -17169,7 +17273,7 @@ paths: type: integer flow_version: type: integer - required: &ref_148 + required: &ref_152 - value - content - args @@ -17205,8 +17309,8 @@ paths: application/json: schema: type: object - properties: *ref_147 - required: *ref_148 + properties: *ref_151 + required: *ref_152 responses: '200': description: job result @@ -17231,7 +17335,7 @@ paths: application/json: schema: type: object - properties: &ref_507 + properties: &ref_525 entrypoint_function: type: string description: Name of the function to execute for dynamic select @@ -17252,7 +17356,7 @@ paths: description: Path to the deployed script or flow runnable_kind: type: string - enum: &ref_197 + enum: &ref_199 - script - flow required: @@ -17274,7 +17378,7 @@ paths: required: - source - code - required: &ref_508 + required: &ref_526 - entrypoint_function - runnable_ref responses: @@ -17320,7 +17424,7 @@ paths: (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2') in: query - schema: &ref_153 + schema: &ref_155 type: string - name: script_path_exact description: >- @@ -17328,7 +17432,7 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: &ref_154 + schema: &ref_156 type: string - name: script_path_start description: >- @@ -17336,12 +17440,12 @@ paths: 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: &ref_155 + schema: &ref_157 type: string - name: schedule_path description: mask to filter by schedule path in: query - schema: &ref_156 + schema: &ref_158 type: string - name: trigger_path description: >- @@ -17349,7 +17453,7 @@ paths: 'f/trigger1,f/trigger2') and negation by prefixing all values with '!' (e.g. '!f/trigger1,!f/trigger2') in: query - schema: &ref_296 + schema: &ref_306 type: string - name: trigger_kind description: >- @@ -17358,34 +17462,34 @@ paths: (e.g. '!schedule,!webhook') in: query x-go-name: JobTriggerKindParam - schema: &ref_185 + schema: &ref_187 type: string - name: script_hash description: mask to filter exact matching path in: query - schema: &ref_157 + schema: &ref_159 type: string - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: &ref_158 + schema: &ref_160 type: string format: date-time - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: &ref_159 + schema: &ref_161 type: string format: date-time - name: success description: filter on successful jobs in: query - schema: &ref_167 + schema: &ref_169 type: boolean - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: &ref_161 + schema: &ref_163 type: boolean - name: job_kinds description: >- @@ -17393,36 +17497,36 @@ paths: ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: &ref_162 + schema: &ref_164 type: string - name: suspended description: filter on suspended jobs in: query - schema: &ref_163 + schema: &ref_165 type: boolean - name: running description: filter on running jobs in: query - schema: &ref_160 + schema: &ref_162 type: boolean - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: &ref_164 + schema: &ref_166 type: string - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: &ref_166 + schema: &ref_168 type: string - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: &ref_168 + schema: &ref_170 type: boolean - name: tag description: >- @@ -17430,7 +17534,7 @@ paths: 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: &ref_165 + schema: &ref_167 type: string - name: page description: which page to return (start at 1, default 1) @@ -17461,7 +17565,7 @@ paths: type: array items: type: object - properties: &ref_188 + properties: &ref_190 workspace_id: type: string id: @@ -17536,14 +17640,14 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: &ref_172 + properties: &ref_174 step: type: integer modules: type: array items: type: object - properties: &ref_149 + properties: &ref_153 type: type: string enum: @@ -17697,20 +17801,20 @@ paths: type: array items: type: boolean - required: &ref_150 + required: &ref_154 - type user_states: additionalProperties: true preprocessor_module: allOf: - type: object - properties: *ref_149 - required: *ref_150 + properties: *ref_153 + required: *ref_154 failure_module: allOf: - type: object - properties: *ref_149 - required: *ref_150 + properties: *ref_153 + required: *ref_154 - type: object properties: parent_module: @@ -17725,13 +17829,13 @@ paths: items: type: string format: uuid - required: &ref_173 + required: &ref_175 - step - modules - failure_module workflow_as_code_status: type: object - properties: &ref_174 + properties: &ref_176 scheduled_for: type: string format: date-time @@ -17747,8 +17851,8 @@ paths: description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: *ref_151 - required: *ref_152 + properties: *ref_149 + required: *ref_150 is_flow_step: type: boolean language: @@ -17774,7 +17878,7 @@ paths: type: boolean worker: type: string - required: &ref_189 + required: &ref_191 - id - running - canceled @@ -17897,7 +18001,7 @@ paths: (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release') in: query - schema: &ref_171 + schema: &ref_173 type: string - name: worker description: >- @@ -17905,7 +18009,7 @@ paths: (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2') in: query - schema: *ref_153 + schema: *ref_155 - name: parent_job description: >- The parent job that is at the origin and responsible for the @@ -17918,104 +18022,104 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 - name: script_path_start description: >- filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: &ref_179 + schema: &ref_181 type: string format: date-time - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: &ref_180 + schema: &ref_182 type: string format: date-time - name: completed_before description: filter on started before (inclusive) timestamp in: query - schema: &ref_181 + schema: &ref_183 type: string format: date-time - name: completed_after description: filter on started after (exclusive) timestamp in: query - schema: &ref_182 + schema: &ref_184 type: string format: date-time - name: created_before_queue description: filter on jobs created before X for jobs in the queue only in: query - schema: &ref_183 + schema: &ref_185 type: string format: date-time - name: created_after_queue description: filter on jobs created after X for jobs in the queue only in: query - schema: &ref_184 + schema: &ref_186 type: string format: date-time - name: running description: filter on running jobs in: query - schema: *ref_160 + schema: *ref_162 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_161 + schema: *ref_163 - name: job_kinds description: >- filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 - name: suspended description: filter on suspended jobs in: query - schema: *ref_163 + schema: *ref_165 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: tag description: >- filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 - name: page description: which page to return (start at 1, default 1) in: query @@ -18099,76 +18203,76 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 - name: script_path_start description: >- filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 - name: success description: filter on successful jobs in: query - schema: *ref_167 + schema: *ref_169 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_161 + schema: *ref_163 - name: job_kinds description: >- filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 - name: suspended description: filter on suspended jobs in: query - schema: *ref_163 + schema: *ref_165 - name: running description: filter on running jobs in: query - schema: *ref_160 + schema: *ref_162 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_168 + schema: *ref_170 - name: tag description: >- filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 - name: page description: which page to return (start at 1, default 1) in: query @@ -18218,6 +18322,10 @@ paths: in: query schema: type: boolean + - name: all_workspaces + in: query + schema: + type: boolean requestBody: description: uuids of the jobs to cancel required: true @@ -18250,7 +18358,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: list of OTEL Span objects (compatible with OpenTelemetry Span proto) @@ -18278,7 +18386,7 @@ paths: schema: description: job trigger kind (schedule, http, websocket...) type: string - enum: &ref_170 + enum: &ref_172 - webhook - default_email - email @@ -18291,7 +18399,9 @@ paths: - mqtt - sqs - gcp + - azure - google + - github - name: trigger_path description: The path of the trigger (can contain forward slashes) in: path @@ -18342,7 +18452,7 @@ paths: schema: description: job trigger kind (schedule, http, websocket...) type: string - enum: *ref_170 + enum: *ref_172 - name: trigger_path description: The path of the trigger (can contain forward slashes) in: path @@ -18403,14 +18513,14 @@ paths: (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release') in: query - schema: *ref_171 + schema: *ref_173 - name: worker description: >- filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2') in: query - schema: *ref_153 + schema: *ref_155 - name: parent_job description: >- The parent job that is at the origin and responsible for the @@ -18423,64 +18533,64 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 - name: script_path_start description: >- filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 - name: success description: filter on successful jobs in: query - schema: *ref_167 + schema: *ref_169 - name: job_kinds description: >- filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_168 + schema: *ref_170 - name: tag description: >- filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 - name: page description: which page to return (start at 1, default 1) in: query @@ -18518,7 +18628,7 @@ paths: type: array items: type: object - properties: &ref_186 + properties: &ref_188 workspace_id: type: string id: @@ -18595,18 +18705,18 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: *ref_172 - required: *ref_173 + properties: *ref_174 + required: *ref_175 workflow_as_code_status: type: object - properties: *ref_174 + properties: *ref_176 raw_flow: type: object description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: *ref_151 - required: *ref_152 + properties: *ref_149 + required: *ref_150 is_flow_step: type: boolean language: @@ -18636,7 +18746,7 @@ paths: type: boolean worker: type: string - required: &ref_187 + required: &ref_189 - id - created_by - duration_ms @@ -18680,7 +18790,7 @@ paths: items: type: object description: Completed job with full data for export/import operations - properties: &ref_175 + properties: &ref_177 id: type: string format: uuid @@ -18818,7 +18928,7 @@ paths: status: type: string description: Actual job status from database - required: &ref_176 + required: &ref_178 - id - created_by - created_at @@ -18846,8 +18956,8 @@ paths: items: type: object description: Completed job with full data for export/import operations - properties: *ref_175 - required: *ref_176 + properties: *ref_177 + required: *ref_178 responses: '200': description: Successfully imported completed jobs @@ -18884,7 +18994,7 @@ paths: items: type: object description: Queued job with full data for export/import operations - properties: &ref_177 + properties: &ref_179 id: type: string format: uuid @@ -19015,7 +19125,7 @@ paths: suspend_until: type: string format: date-time - required: &ref_178 + required: &ref_180 - id - created_by - created_at @@ -19043,8 +19153,8 @@ paths: items: type: object description: Queued job with full data for export/import operations - properties: *ref_177 - required: *ref_178 + properties: *ref_179 + required: *ref_180 responses: '200': description: Successfully imported queued jobs @@ -19106,14 +19216,14 @@ paths: (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release') in: query - schema: *ref_171 + schema: *ref_173 - name: worker description: >- filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2') in: query - schema: *ref_153 + schema: *ref_155 - name: parent_job description: >- The parent job that is at the origin and responsible for the @@ -19126,96 +19236,96 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 - name: script_path_start description: >- filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_179 + schema: *ref_181 - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_180 + schema: *ref_182 - name: completed_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_181 + schema: *ref_183 - name: completed_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_182 + schema: *ref_184 - name: created_before_queue description: filter on jobs created before X for jobs in the queue only in: query - schema: *ref_183 + schema: *ref_185 - name: created_after_queue description: filter on jobs created after X for jobs in the queue only in: query - schema: *ref_184 + schema: *ref_186 - name: running description: filter on running jobs in: query - schema: *ref_160 + schema: *ref_162 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_161 + schema: *ref_163 - name: job_kinds description: >- filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 - name: suspended description: filter on suspended jobs in: query - schema: *ref_163 + schema: *ref_165 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: tag description: >- filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_168 + schema: *ref_170 - name: per_page description: number of items to return for a given page (default 30, max 100) in: query @@ -19227,7 +19337,7 @@ paths: (e.g. '!schedule,!webhook') in: query x-go-name: JobTriggerKindParam - schema: *ref_185 + schema: *ref_187 - name: is_skipped description: is the job skipped in: query @@ -19275,28 +19385,28 @@ paths: schema: type: array items: - oneOf: &ref_190 - - allOf: - - type: object - properties: *ref_186 - required: *ref_187 - - type: object - properties: - type: - type: string - enum: - - CompletedJob + oneOf: &ref_192 - allOf: - type: object properties: *ref_188 required: *ref_189 + - type: object + properties: + type: + type: string + enum: + - CompletedJob + - allOf: + - type: object + properties: *ref_190 + required: *ref_191 - type: object properties: type: type: string enum: - QueuedJob - discriminator: &ref_191 + discriminator: &ref_193 propertyName: type /jobs/db_clock: get: @@ -19364,7 +19474,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: no_logs in: query schema: @@ -19379,8 +19489,8 @@ paths: content: application/json: schema: - oneOf: *ref_190 - discriminator: *ref_191 + oneOf: *ref_192 + discriminator: *ref_193 /w/{workspace}/jobs_u/get_root_job_id/{id}: get: summary: get root job id @@ -19395,7 +19505,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: get root job id @@ -19418,7 +19528,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: remove_ansi_warnings in: query schema: @@ -19444,7 +19554,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: concatenated logs of all flow steps @@ -19466,7 +19576,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: completed job logs tail @@ -19488,7 +19598,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: job args @@ -19539,7 +19649,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: running in: query schema: @@ -19586,11 +19696,11 @@ paths: type: string flow_status: type: object - properties: *ref_172 - required: *ref_173 + properties: *ref_174 + required: *ref_175 workflow_as_code_status: type: object - properties: *ref_174 + properties: *ref_176 /w/{workspace}/jobs_u/getupdate_sse/{id}: get: summary: get job updates via server-sent events @@ -19605,7 +19715,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: running in: query schema: @@ -19678,7 +19788,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: flow debug info details @@ -19699,7 +19809,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: job details @@ -19707,8 +19817,8 @@ paths: application/json: schema: type: object - properties: *ref_186 - required: *ref_187 + properties: *ref_188 + required: *ref_189 /w/{workspace}/jobs_u/completed/get_result/{id}: get: summary: get completed job result @@ -19723,7 +19833,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: suspended_job in: query schema: @@ -19760,10 +19870,10 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: get_started in: query - schema: &ref_302 + schema: &ref_312 type: boolean responses: '200': @@ -19797,7 +19907,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: job timing details @@ -19830,7 +19940,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: job details @@ -19838,8 +19948,8 @@ paths: application/json: schema: type: object - properties: *ref_186 - required: *ref_187 + properties: *ref_188 + required: *ref_189 /w/{workspace}/jobs_u/queue/cancel/{id}: post: summary: cancel queued or running job @@ -19854,7 +19964,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 requestBody: description: reason required: true @@ -19918,7 +20028,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 requestBody: description: reason required: true @@ -19980,7 +20090,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: scheduled for timestamp @@ -20002,7 +20112,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20033,7 +20143,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20083,7 +20193,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: approver in: query schema: @@ -20144,7 +20254,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: approver in: query schema: @@ -20332,7 +20442,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -20375,7 +20485,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20417,7 +20527,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: key in: path required: true @@ -20449,7 +20559,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: key in: path required: true @@ -20475,7 +20585,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 requestBody: required: true content: @@ -20503,7 +20613,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20538,7 +20648,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20580,7 +20690,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 - name: resume_id in: path required: true @@ -20604,8 +20714,8 @@ paths: type: object properties: job: - oneOf: *ref_190 - discriminator: *ref_191 + oneOf: *ref_192 + discriminator: *ref_193 approvers: type: array items: @@ -20680,7 +20790,7 @@ paths: application/json: schema: type: object - properties: &ref_425 + properties: &ref_435 path: type: string description: The unique path identifier for this schedule @@ -20771,7 +20881,7 @@ paths: nullable: true type: object description: Retry configuration for failed module executions - properties: &ref_194 + properties: &ref_196 constant: type: object description: Retry with constant delay between attempts @@ -20806,8 +20916,8 @@ paths: retry_if: type: object description: Conditional retry based on error or result - properties: *ref_192 - required: *ref_193 + properties: *ref_194 + required: *ref_195 no_flow_overlap: type: boolean description: >- @@ -20860,7 +20970,7 @@ paths: type: array items: type: string - required: &ref_426 + required: &ref_436 - path - schedule - timezone @@ -20904,7 +21014,7 @@ paths: application/json: schema: type: object - properties: &ref_427 + properties: &ref_437 schedule: type: string description: >- @@ -20979,7 +21089,7 @@ paths: nullable: true type: object description: Retry configuration for failed module executions - properties: *ref_194 + properties: *ref_196 no_flow_overlap: type: boolean description: >- @@ -21035,7 +21145,7 @@ paths: type: array items: type: string - required: &ref_428 + required: &ref_438 - schedule - timezone - args @@ -21126,7 +21236,7 @@ paths: application/json: schema: type: object - properties: &ref_195 + properties: &ref_197 path: type: string description: The unique path identifier for this schedule @@ -21245,7 +21355,7 @@ paths: nullable: true type: object description: Retry configuration for failed module executions - properties: *ref_194 + properties: *ref_196 summary: type: string nullable: true @@ -21288,7 +21398,7 @@ paths: items: type: string default: [] - required: &ref_196 + required: &ref_198 - path - edited_by - edited_at @@ -21347,7 +21457,7 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: path description: filter by path (script path) in: query @@ -21400,8 +21510,8 @@ paths: type: array items: type: object - properties: *ref_195 - required: *ref_196 + properties: *ref_197 + required: *ref_198 /w/{workspace}/schedules/list_with_jobs: get: summary: list schedules with last 20 jobs @@ -21429,10 +21539,10 @@ paths: schema: type: array items: - allOf: &ref_424 + allOf: &ref_434 - type: object - properties: *ref_195 - required: *ref_196 + properties: *ref_197 + required: *ref_198 - type: object properties: jobs: @@ -21510,10 +21620,10 @@ paths: application/json: schema: type: object - properties: &ref_198 + properties: &ref_200 info: type: object - properties: &ref_434 + properties: &ref_444 title: type: string version: @@ -21542,28 +21652,28 @@ paths: type: string required: - name - required: &ref_435 + required: &ref_445 - title - version url: type: string openapi_spec_format: type: string - enum: &ref_429 + enum: &ref_439 - yaml - json http_route_filters: type: array items: type: object - properties: &ref_430 + properties: &ref_440 folder_regex: type: string path_regex: type: string route_path_regex: type: string - required: &ref_431 + required: &ref_441 - folder_regex - path_regex - route_path_regex @@ -21571,7 +21681,7 @@ paths: type: array items: type: object - properties: &ref_432 + properties: &ref_442 user_or_folder_regex: type: string enum: @@ -21584,8 +21694,8 @@ paths: type: string runnable_kind: type: string - enum: *ref_197 - required: &ref_433 + enum: *ref_199 + required: &ref_443 - user_or_folder_regex - user_or_folder_regex_value - path @@ -21614,7 +21724,7 @@ paths: application/json: schema: type: object - properties: *ref_198 + properties: *ref_200 responses: '200': description: Downloaded OpenAPI spec @@ -21643,7 +21753,7 @@ paths: type: array items: type: object - properties: &ref_199 + properties: &ref_201 path: type: string description: The unique path identifier for this trigger @@ -21694,7 +21804,7 @@ paths: HTTP method (get, post, put, delete, patch) that triggers this endpoint type: string - enum: &ref_201 + enum: &ref_203 - get - post - put @@ -21716,7 +21826,7 @@ paths: 'async' returns job ID immediately, 'sync_sse' streams results via Server-Sent Events type: string - enum: &ref_202 + enum: &ref_204 - sync - async - sync_sse @@ -21726,7 +21836,7 @@ paths: 'windmill' (Windmill token), 'api_key', 'basic_http', 'custom_script', 'signature' type: string - enum: &ref_203 + enum: &ref_205 - none - windmill - api_key @@ -21744,7 +21854,7 @@ paths: mode: description: job trigger mode type: string - enum: &ref_204 + enum: &ref_206 - enabled - disabled - suspended @@ -21765,7 +21875,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -21781,7 +21891,7 @@ paths: type: array items: type: string - required: &ref_200 + required: &ref_202 - path - script_path - route_path @@ -21814,8 +21924,8 @@ paths: application/json: schema: type: object - properties: *ref_199 - required: *ref_200 + properties: *ref_201 + required: *ref_202 responses: '201': description: http trigger created @@ -21845,7 +21955,7 @@ paths: application/json: schema: type: object - properties: &ref_436 + properties: &ref_446 path: type: string description: The unique path identifier for this trigger @@ -21902,7 +22012,7 @@ paths: HTTP method (get, post, put, delete, patch) that triggers this endpoint type: string - enum: *ref_201 + enum: *ref_203 is_async: type: boolean description: Deprecated, use request_type instead @@ -21912,14 +22022,14 @@ paths: 'async' returns job ID immediately, 'sync_sse' streams results via Server-Sent Events type: string - enum: *ref_202 + enum: *ref_204 authentication_method: description: >- How requests are authenticated - 'none' (public), 'windmill' (Windmill token), 'api_key', 'basic_http', 'custom_script', 'signature' type: string - enum: *ref_203 + enum: *ref_205 is_static_website: type: boolean description: >- @@ -21943,7 +22053,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -21959,7 +22069,7 @@ paths: type: array items: type: string - required: &ref_437 + required: &ref_447 - path - script_path - is_flow @@ -22017,9 +22127,9 @@ paths: content: application/json: schema: - allOf: &ref_205 + allOf: &ref_207 - type: object - properties: &ref_211 + properties: &ref_213 path: type: string description: The unique path identifier for this trigger @@ -22054,13 +22164,13 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 labels: type: array items: type: string default: [] - required: &ref_212 + required: &ref_214 - path - script_path - permissioned_as @@ -22071,7 +22181,7 @@ paths: - is_flow - mode type: object - properties: &ref_206 + properties: &ref_208 route_path: type: string description: >- @@ -22100,7 +22210,7 @@ paths: HTTP method (get, post, put, delete, patch) that triggers this endpoint type: string - enum: *ref_201 + enum: *ref_203 authentication_resource_path: type: string nullable: true @@ -22122,14 +22232,14 @@ paths: 'async' returns job ID immediately, 'sync_sse' streams results via Server-Sent Events type: string - enum: *ref_202 + enum: *ref_204 authentication_method: description: >- How requests are authenticated - 'none' (public), 'windmill' (Windmill token), 'api_key', 'basic_http', 'custom_script', 'signature' type: string - enum: *ref_203 + enum: *ref_205 is_static_website: type: boolean description: >- @@ -22158,8 +22268,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_207 + properties: *ref_196 + required: &ref_209 - route_path - request_type - authentication_method @@ -22214,10 +22324,10 @@ paths: schema: type: array items: - allOf: *ref_205 + allOf: *ref_207 type: object - properties: *ref_206 - required: *ref_207 + properties: *ref_208 + required: *ref_209 /w/{workspace}/http_triggers/exists/{path}: get: summary: does http trigger exists @@ -22263,7 +22373,7 @@ paths: type: string http_method: type: string - enum: *ref_201 + enum: *ref_203 trigger_path: type: string workspaced_route: @@ -22303,7 +22413,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -22331,7 +22441,7 @@ paths: application/json: schema: type: object - properties: &ref_438 + properties: &ref_448 path: type: string description: The unique path identifier for this trigger @@ -22353,7 +22463,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 filters: type: array description: >- @@ -22384,7 +22494,7 @@ paths: Messages to send immediately after connecting (can be raw strings or computed by runnables) items: - anyOf: &ref_208 + anyOf: &ref_210 - type: object properties: raw_message: @@ -22427,7 +22537,7 @@ paths: nullable: true description: Optional periodic heartbeat message configuration type: object - properties: &ref_209 + properties: &ref_211 interval_secs: type: integer minimum: 1 @@ -22444,7 +22554,7 @@ paths: Optional. Top-level JSON field to extract from incoming messages. The extracted value replaces {{state}} in the heartbeat message. - required: &ref_210 + required: &ref_212 - interval_secs - message error_handler_path: @@ -22457,7 +22567,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -22473,7 +22583,7 @@ paths: type: array items: type: string - required: &ref_439 + required: &ref_449 - path - script_path - url @@ -22510,7 +22620,7 @@ paths: application/json: schema: type: object - properties: &ref_440 + properties: &ref_450 url: type: string description: >- @@ -22559,7 +22669,7 @@ paths: Messages to send immediately after connecting (can be raw strings or computed by runnables) items: - anyOf: *ref_208 + anyOf: *ref_210 url_runnable_args: description: The arguments to pass to the script or flow nullable: true @@ -22577,8 +22687,8 @@ paths: nullable: true description: Optional periodic heartbeat message configuration type: object - properties: *ref_209 - required: *ref_210 + properties: *ref_211 + required: *ref_212 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -22589,7 +22699,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -22605,7 +22715,7 @@ paths: type: array items: type: string - required: &ref_441 + required: &ref_451 - path - script_path - url @@ -22663,12 +22773,12 @@ paths: content: application/json: schema: - allOf: &ref_213 + allOf: &ref_215 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_214 + properties: &ref_216 url: type: string description: >- @@ -22716,7 +22826,7 @@ paths: Messages to send immediately after connecting (can be raw strings or computed by runnables) items: - anyOf: *ref_208 + anyOf: *ref_210 url_runnable_args: description: The arguments to pass to the script or flow nullable: true @@ -22734,8 +22844,8 @@ paths: nullable: true description: Optional periodic heartbeat message configuration type: object - properties: *ref_209 - required: *ref_210 + properties: *ref_211 + required: *ref_212 error_handler_path: type: string description: >- @@ -22748,8 +22858,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_215 + properties: *ref_196 + required: &ref_217 - url - filters - can_return_message @@ -22800,10 +22910,10 @@ paths: schema: type: array items: - allOf: *ref_213 + allOf: *ref_215 type: object - properties: *ref_214 - required: *ref_215 + properties: *ref_216 + required: *ref_217 /w/{workspace}/websocket_triggers/exists/{path}: get: summary: does websocket trigger exists @@ -22852,7 +22962,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -22917,7 +23027,7 @@ paths: application/json: schema: type: object - properties: &ref_465 + properties: &ref_483 path: type: string description: The unique path identifier for this trigger @@ -22983,7 +23093,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -22994,7 +23104,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -23010,7 +23120,7 @@ paths: type: array items: type: string - required: &ref_466 + required: &ref_484 - path - script_path - is_flow @@ -23047,7 +23157,7 @@ paths: application/json: schema: type: object - properties: &ref_467 + properties: &ref_485 kafka_resource_path: type: string description: >- @@ -23120,7 +23230,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -23136,7 +23246,7 @@ paths: type: array items: type: string - required: &ref_468 + required: &ref_486 - path - script_path - kafka_resource_path @@ -23194,12 +23304,12 @@ paths: content: application/json: schema: - allOf: &ref_216 + allOf: &ref_218 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_217 + properties: &ref_219 kafka_resource_path: type: string description: >- @@ -23274,8 +23384,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_218 + properties: *ref_196 + required: &ref_220 - kafka_resource_path - group_id - topics @@ -23326,10 +23436,10 @@ paths: schema: type: array items: - allOf: *ref_216 + allOf: *ref_218 type: object - properties: *ref_217 - required: *ref_218 + properties: *ref_219 + required: *ref_220 /w/{workspace}/kafka_triggers/exists/{path}: get: summary: does kafka trigger exists @@ -23378,7 +23488,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -23492,7 +23602,7 @@ paths: application/json: schema: type: object - properties: &ref_469 + properties: &ref_487 path: type: string description: The unique path identifier for this trigger @@ -23532,7 +23642,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -23543,7 +23653,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -23559,7 +23669,7 @@ paths: type: array items: type: string - required: &ref_470 + required: &ref_488 - path - script_path - is_flow @@ -23595,7 +23705,7 @@ paths: application/json: schema: type: object - properties: &ref_471 + properties: &ref_489 nats_resource_path: type: string description: >- @@ -23642,7 +23752,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -23658,7 +23768,7 @@ paths: type: array items: type: string - required: &ref_472 + required: &ref_490 - path - script_path - nats_resource_path @@ -23715,12 +23825,12 @@ paths: content: application/json: schema: - allOf: &ref_219 + allOf: &ref_221 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_220 + properties: &ref_222 nats_resource_path: type: string description: >- @@ -23770,8 +23880,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_221 + properties: *ref_196 + required: &ref_223 - nats_resource_path - use_jetstream - subjects @@ -23821,10 +23931,10 @@ paths: schema: type: array items: - allOf: *ref_219 + allOf: *ref_221 type: object - properties: *ref_220 - required: *ref_221 + properties: *ref_222 + required: *ref_223 /w/{workspace}/nats_triggers/exists/{path}: get: summary: does nats trigger exists @@ -23873,7 +23983,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -23931,7 +24041,7 @@ paths: application/json: schema: type: object - properties: &ref_452 + properties: &ref_470 queue_url: type: string description: The full URL of the AWS SQS queue to poll for messages @@ -23940,7 +24050,7 @@ paths: Authentication type - 'credentials' for access key/secret, 'oidc' for OpenID Connect type: string - enum: &ref_222 + enum: &ref_224 - oidc - credentials aws_resource_path: @@ -23972,7 +24082,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -23983,7 +24093,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -23999,7 +24109,7 @@ paths: type: array items: type: string - required: &ref_453 + required: &ref_471 - queue_url - aws_resource_path - path @@ -24035,7 +24145,7 @@ paths: application/json: schema: type: object - properties: &ref_454 + properties: &ref_472 queue_url: type: string description: The full URL of the AWS SQS queue to poll for messages @@ -24044,7 +24154,7 @@ paths: Authentication type - 'credentials' for access key/secret, 'oidc' for OpenID Connect type: string - enum: *ref_222 + enum: *ref_224 aws_resource_path: type: string description: >- @@ -24074,7 +24184,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -24085,7 +24195,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -24101,7 +24211,7 @@ paths: type: array items: type: string - required: &ref_455 + required: &ref_473 - queue_url - aws_resource_path - path @@ -24159,12 +24269,12 @@ paths: content: application/json: schema: - allOf: &ref_223 + allOf: &ref_225 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_224 + properties: &ref_226 queue_url: type: string description: The full URL of the AWS SQS queue to poll for messages @@ -24173,7 +24283,7 @@ paths: Authentication type - 'credentials' for access key/secret, 'oidc' for OpenID Connect type: string - enum: *ref_222 + enum: *ref_224 aws_resource_path: type: string description: >- @@ -24211,8 +24321,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_225 + properties: *ref_196 + required: &ref_227 - queue_url - aws_resource_path - aws_auth_resource_type @@ -24262,10 +24372,10 @@ paths: schema: type: array items: - allOf: *ref_223 + allOf: *ref_225 type: object - properties: *ref_224 - required: *ref_225 + properties: *ref_226 + required: *ref_227 /w/{workspace}/sqs_triggers/exists/{path}: get: summary: does sqs trigger exists @@ -24314,7 +24424,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -24374,16 +24484,17 @@ paths: type: array items: type: object - properties: &ref_556 + properties: &ref_572 service_name: type: string - enum: &ref_226 + enum: &ref_228 - nextcloud - google + - github oauth_data: nullable: true type: object - properties: &ref_227 + properties: &ref_229 client_id: type: string description: The OAuth client ID for the workspace @@ -24398,7 +24509,7 @@ paths: type: string format: uri description: The OAuth redirect URI - required: &ref_228 + required: &ref_230 - client_id - client_secret - base_url @@ -24407,7 +24518,7 @@ paths: type: string nullable: true description: Path to the resource storing the OAuth token - required: &ref_557 + required: &ref_573 - service_name /w/{workspace}/native_triggers/integrations/{service_name}/exists: get: @@ -24425,7 +24536,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 responses: '200': description: integration exists @@ -24449,7 +24560,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 requestBody: description: new native trigger service required: true @@ -24457,8 +24568,8 @@ paths: application/json: schema: type: object - properties: *ref_227 - required: *ref_228 + properties: *ref_229 + required: *ref_230 responses: '201': description: native trigger service created @@ -24482,7 +24593,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 requestBody: description: redirect_uri required: true @@ -24490,10 +24601,10 @@ paths: application/json: schema: type: object - properties: &ref_229 + properties: &ref_231 redirect_uri: type: string - required: &ref_230 + required: &ref_232 - redirect_uri responses: '200': @@ -24518,7 +24629,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 responses: '200': description: whether instance sharing is available @@ -24542,7 +24653,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 requestBody: description: redirect_uri required: true @@ -24550,8 +24661,8 @@ paths: application/json: schema: type: object - properties: *ref_229 - required: *ref_230 + properties: *ref_231 + required: *ref_232 responses: '200': description: authorization URL using instance credentials @@ -24575,7 +24686,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 responses: '200': description: native trigger service deleted @@ -24599,7 +24710,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 requestBody: description: OAuth callback data required: true @@ -24648,7 +24759,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 requestBody: description: new native trigger configuration required: true @@ -24657,7 +24768,7 @@ paths: schema: type: object description: Data for creating or updating a native trigger - properties: &ref_231 + properties: &ref_233 script_path: type: string description: The path to the script or flow that will be triggered @@ -24674,7 +24785,7 @@ paths: type: string nullable: true description: Short summary to be displayed when listed - required: &ref_232 + required: &ref_234 - script_path - is_flow - service_config @@ -24686,13 +24797,13 @@ paths: schema: type: object description: Response returned when a native trigger is created - properties: &ref_559 + properties: &ref_575 external_id: type: string description: >- The external ID of the created trigger from the external service - required: &ref_560 + required: &ref_576 - external_id /w/{workspace}/native_triggers/{service_name}/update/{external_id}: post: @@ -24715,7 +24826,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: external_id in: path required: true @@ -24730,8 +24841,8 @@ paths: schema: type: object description: Data for creating or updating a native trigger - properties: *ref_231 - required: *ref_232 + properties: *ref_233 + required: *ref_234 responses: '200': description: native trigger updated @@ -24760,7 +24871,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: external_id in: path required: true @@ -24777,7 +24888,7 @@ paths: description: >- Full trigger response containing both Windmill data and external service data - properties: &ref_554 + properties: &ref_570 external_id: type: string description: The unique identifier from the external service @@ -24786,7 +24897,7 @@ paths: description: The workspace this trigger belongs to service_name: type: string - enum: *ref_226 + enum: *ref_228 script_path: type: string description: The path to the script or flow that will be triggered @@ -24813,7 +24924,7 @@ paths: type: object description: Configuration data from the external service additionalProperties: true - required: &ref_555 + required: &ref_571 - external_id - workspace_id - service_name @@ -24842,7 +24953,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: external_id in: path required: true @@ -24873,7 +24984,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: page description: which page to return (start at 1, default 1) in: query @@ -24908,7 +25019,7 @@ paths: items: type: object description: A native trigger stored in Windmill - properties: &ref_552 + properties: &ref_568 external_id: type: string description: The unique identifier from the external service @@ -24917,7 +25028,7 @@ paths: description: The workspace this trigger belongs to service_name: type: string - enum: *ref_226 + enum: *ref_228 script_path: type: string description: The path to the script or flow that will be triggered @@ -24940,7 +25051,7 @@ paths: type: string nullable: true description: Short summary to be displayed when listed - required: &ref_553 + required: &ref_569 - external_id - workspace_id - service_name @@ -24964,7 +25075,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: external_id in: path required: true @@ -24994,7 +25105,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 responses: '200': description: sync completed successfully @@ -25019,7 +25130,7 @@ paths: type: array items: type: object - properties: &ref_561 + properties: &ref_577 id: type: string name: @@ -25030,7 +25141,7 @@ paths: type: string path: type: string - required: &ref_562 + required: &ref_578 - id - name - path @@ -25055,7 +25166,7 @@ paths: type: array items: type: object - properties: &ref_563 + properties: &ref_579 id: type: string summary: @@ -25063,7 +25174,7 @@ paths: primary: type: boolean default: false - required: &ref_564 + required: &ref_580 - id - summary /w/{workspace}/native_triggers/google/drive/files: @@ -25106,12 +25217,12 @@ paths: application/json: schema: type: object - properties: &ref_567 + properties: &ref_583 files: type: array items: type: object - properties: &ref_565 + properties: &ref_581 id: type: string name: @@ -25121,13 +25232,13 @@ paths: is_folder: type: boolean default: false - required: &ref_566 + required: &ref_582 - id - name - mime_type next_page_token: type: string - required: &ref_568 + required: &ref_584 - files /w/{workspace}/native_triggers/google/drive/shared_drives: get: @@ -25150,14 +25261,49 @@ paths: type: array items: type: object - properties: &ref_569 + properties: &ref_585 id: type: string name: type: string - required: &ref_570 + required: &ref_586 - id - name + /w/{workspace}/native_triggers/github/repos: + get: + summary: list GitHub repositories accessible to the user + operationId: listGithubRepos + tags: + - native_trigger + parameters: + - name: workspace + in: path + required: true + schema: + type: string + responses: + '200': + description: list of GitHub repositories + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_587 + full_name: + type: string + name: + type: string + owner: + type: string + private: + type: boolean + required: &ref_588 + - full_name + - name + - owner + - private /native_triggers/{service_name}/w/{workspace_id}/webhook/{internal_id}: post: summary: receive webhook from external native trigger service @@ -25170,7 +25316,7 @@ paths: required: true schema: type: string - enum: *ref_226 + enum: *ref_228 - name: workspace_id in: path required: true @@ -25219,7 +25365,7 @@ paths: application/json: schema: type: object - properties: &ref_443 + properties: &ref_453 mqtt_resource_path: type: string description: >- @@ -25229,16 +25375,16 @@ paths: type: array items: type: object - properties: &ref_233 + properties: &ref_235 qos: type: string - enum: &ref_442 + enum: &ref_452 - qos0 - qos1 - qos2 topic: type: string - required: &ref_234 + required: &ref_236 - qos - topic description: >- @@ -25252,7 +25398,7 @@ paths: nullable: true description: MQTT v3 specific configuration (clean_session) type: object - properties: &ref_235 + properties: &ref_237 clean_session: type: boolean v5_config: @@ -25261,7 +25407,7 @@ paths: MQTT v5 specific configuration (clean_start, topic_alias_maximum, session_expiry_interval) type: object - properties: &ref_236 + properties: &ref_238 clean_start: type: boolean topic_alias_maximum: @@ -25272,7 +25418,7 @@ paths: nullable: true description: MQTT protocol version ('v3' or 'v5') type: string - enum: &ref_237 + enum: &ref_239 - v3 - v5 path: @@ -25291,7 +25437,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -25302,7 +25448,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -25318,7 +25464,7 @@ paths: type: array items: type: string - required: &ref_444 + required: &ref_454 - path - script_path - is_flow @@ -25353,7 +25499,7 @@ paths: application/json: schema: type: object - properties: &ref_445 + properties: &ref_455 mqtt_resource_path: type: string description: >- @@ -25363,8 +25509,8 @@ paths: type: array items: type: object - properties: *ref_233 - required: *ref_234 + properties: *ref_235 + required: *ref_236 description: >- Array of MQTT topics to subscribe to, each with topic name and QoS level @@ -25376,19 +25522,19 @@ paths: nullable: true description: MQTT v3 specific configuration (clean_session) type: object - properties: *ref_235 + properties: *ref_237 v5_config: nullable: true description: >- MQTT v5 specific configuration (clean_start, topic_alias_maximum, session_expiry_interval) type: object - properties: *ref_236 + properties: *ref_238 client_version: nullable: true description: MQTT protocol version ('v3' or 'v5') type: string - enum: *ref_237 + enum: *ref_239 path: type: string description: The unique path identifier for this trigger @@ -25405,7 +25551,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -25416,7 +25562,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -25432,7 +25578,7 @@ paths: type: array items: type: string - required: &ref_446 + required: &ref_456 - path - script_path - is_flow @@ -25489,12 +25635,12 @@ paths: content: application/json: schema: - allOf: &ref_238 + allOf: &ref_240 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_239 + properties: &ref_241 mqtt_resource_path: type: string description: >- @@ -25504,8 +25650,8 @@ paths: type: array items: type: object - properties: *ref_233 - required: *ref_234 + properties: *ref_235 + required: *ref_236 description: >- Array of MQTT topics to subscribe to, each with topic name and QoS level @@ -25513,14 +25659,14 @@ paths: nullable: true description: MQTT v3 specific configuration (clean_session) type: object - properties: *ref_235 + properties: *ref_237 v5_config: nullable: true description: >- MQTT v5 specific configuration (clean_start, topic_alias_maximum, session_expiry_interval) type: object - properties: *ref_236 + properties: *ref_238 client_id: type: string nullable: true @@ -25529,7 +25675,7 @@ paths: nullable: true description: MQTT protocol version ('v3' or 'v5') type: string - enum: *ref_237 + enum: *ref_239 server_id: type: string description: >- @@ -25554,8 +25700,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_240 + properties: *ref_196 + required: &ref_242 - subscribe_topics - mqtt_resource_path /w/{workspace}/mqtt_triggers/list: @@ -25604,10 +25750,10 @@ paths: schema: type: array items: - allOf: *ref_238 + allOf: *ref_240 type: object - properties: *ref_239 - required: *ref_240 + properties: *ref_241 + required: *ref_242 /w/{workspace}/mqtt_triggers/exists/{path}: get: summary: does mqtt trigger exists @@ -25656,7 +25802,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -25715,7 +25861,7 @@ paths: schema: type: object description: Data for creating or updating a Google Cloud Pub/Sub trigger. - properties: &ref_241 + properties: &ref_243 gcp_resource_path: type: string description: >- @@ -25723,7 +25869,7 @@ paths: credentials for authentication. subscription_mode: type: string - enum: &ref_246 + enum: &ref_248 - existing - create_update description: >- @@ -25741,7 +25887,7 @@ paths: description: Base URL for push delivery endpoint. delivery_type: type: string - enum: &ref_243 + enum: &ref_245 - push - pull description: >- @@ -25752,7 +25898,7 @@ paths: nullable: true type: object description: Configuration for push delivery mode. - properties: &ref_244 + properties: &ref_246 audience: type: string description: >- @@ -25763,7 +25909,7 @@ paths: description: >- If true, push messages will include OIDC authentication tokens. - required: &ref_245 + required: &ref_247 - authenticate - base_endpoint path: @@ -25782,7 +25928,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 auto_acknowledge_msg: type: boolean description: >- @@ -25809,7 +25955,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -25825,7 +25971,7 @@ paths: type: array items: type: string - required: &ref_242 + required: &ref_244 - path - script_path - is_flow @@ -25862,8 +26008,8 @@ paths: schema: type: object description: Data for creating or updating a Google Cloud Pub/Sub trigger. - properties: *ref_241 - required: *ref_242 + properties: *ref_243 + required: *ref_244 responses: '200': description: gcp trigger updated @@ -25914,15 +26060,15 @@ paths: content: application/json: schema: - allOf: &ref_247 + allOf: &ref_249 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object description: >- A Google Cloud Pub/Sub trigger that executes a script or flow when messages are received. - properties: &ref_248 + properties: &ref_250 gcp_resource_path: type: string description: >- @@ -25941,7 +26087,7 @@ paths: use). delivery_type: type: string - enum: *ref_243 + enum: *ref_245 description: >- Delivery mode for messages. 'push' for HTTP push delivery where messages are sent to a webhook endpoint, 'pull' for @@ -25950,11 +26096,11 @@ paths: nullable: true type: object description: Configuration for push delivery mode. - properties: *ref_244 - required: *ref_245 + properties: *ref_246 + required: *ref_247 subscription_mode: type: string - enum: *ref_246 + enum: *ref_248 description: >- The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves @@ -25978,8 +26124,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_249 + properties: *ref_196 + required: &ref_251 - gcp_resource_path - topic_id - subscription_id @@ -26031,13 +26177,13 @@ paths: schema: type: array items: - allOf: *ref_247 + allOf: *ref_249 type: object description: >- A Google Cloud Pub/Sub trigger that executes a script or flow when messages are received. - properties: *ref_248 - required: *ref_249 + properties: *ref_250 + required: *ref_251 /w/{workspace}/gcp_triggers/exists/{path}: get: summary: does gcp trigger exists @@ -26086,7 +26232,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -26148,10 +26294,10 @@ paths: application/json: schema: type: object - properties: &ref_449 + properties: &ref_459 subscription_id: type: string - required: &ref_450 + required: &ref_460 - subscription_id responses: '200': @@ -26206,10 +26352,10 @@ paths: application/json: schema: type: object - properties: &ref_447 + properties: &ref_457 topic_id: type: string - required: &ref_448 + required: &ref_458 - topic_id responses: '200': @@ -26220,6 +26366,535 @@ paths: type: array items: type: string + /w/{workspace}/azure_triggers/create: + post: + summary: create an Azure Event Grid trigger + operationId: createAzureTrigger + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + requestBody: + required: true + content: + application/json: + schema: + type: object + description: Data for creating or updating an Azure Event Grid trigger. + properties: &ref_252 + azure_resource_path: + type: string + azure_mode: + type: string + enum: &ref_254 + - basic_push + - namespace_push + - namespace_pull + description: Azure Event Grid trigger mode. + scope_resource_id: + type: string + topic_name: + type: string + nullable: true + subscription_name: + type: string + base_endpoint: + type: string + description: Base URL for push delivery endpoints (push modes only). + event_type_filters: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + mode: + description: job trigger mode + type: string + enum: *ref_206 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: true + retry: + type: object + description: Retry configuration for failed module executions + properties: *ref_196 + permissioned_as: + type: string + preserve_permissioned_as: + type: boolean + labels: + type: array + items: + type: string + required: &ref_253 + - path + - script_path + - is_flow + - azure_resource_path + - azure_mode + - scope_resource_id + - subscription_name + responses: + '201': + description: azure trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/update/{path}: + post: + summary: update an Azure Event Grid trigger + operationId: updateAzureTrigger + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + requestBody: + required: true + content: + application/json: + schema: + type: object + description: Data for creating or updating an Azure Event Grid trigger. + properties: *ref_252 + required: *ref_253 + responses: + '200': + description: azure trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/delete/{path}: + delete: + summary: delete an Azure Event Grid trigger + operationId: deleteAzureTrigger + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + responses: + '200': + description: azure trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/get/{path}: + get: + summary: get an Azure Event Grid trigger + operationId: getAzureTrigger + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + responses: + '200': + description: azure trigger + content: + application/json: + schema: + allOf: &ref_255 + - type: object + properties: *ref_213 + required: *ref_214 + type: object + description: >- + An Azure Event Grid trigger that executes a script or flow + when events arrive. + properties: &ref_256 + azure_resource_path: + type: string + azure_mode: + type: string + enum: *ref_254 + description: Azure Event Grid trigger mode. + scope_resource_id: + type: string + description: >- + ARM resource ID of the topic (basic) or namespace + (namespace modes). + topic_name: + type: string + nullable: true + description: Topic name within the namespace (namespace modes only). + subscription_name: + type: string + event_type_filters: + type: array + items: + type: string + nullable: true + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: true + retry: + type: object + description: Retry configuration for failed module executions + properties: *ref_196 + required: &ref_257 + - azure_resource_path + - azure_mode + - scope_resource_id + - subscription_name + /w/{workspace}/azure_triggers/list: + get: + summary: list azure triggers + operationId: listAzureTriggers + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_17 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_18 + - name: path + description: filter by exact path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: azure trigger list + content: + application/json: + schema: + type: array + items: + allOf: *ref_255 + type: object + description: >- + An Azure Event Grid trigger that executes a script or flow + when events arrive. + properties: *ref_256 + required: *ref_257 + /w/{workspace}/azure_triggers/exists/{path}: + get: + summary: check whether an azure trigger exists + operationId: existsAzureTrigger + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + responses: + '200': + description: true/false + content: + application/json: + schema: + type: boolean + /w/{workspace}/azure_triggers/setmode/{path}: + post: + summary: set azure trigger mode + operationId: setAzureTriggerMode + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + mode: + description: job trigger mode + type: string + enum: *ref_206 + required: + - mode + responses: + '200': + description: trigger mode updated + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/test: + post: + summary: test Azure service principal connection + operationId: testAzureConnection + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: &ref_463 + azure_resource_path: + type: string + required: &ref_464 + - azure_resource_path + responses: + '200': + description: connection successful + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/namespaces/topics/list/{path}: + post: + summary: list topics under an Event Grid Namespace + operationId: listAzureNamespaceTopics + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: &ref_465 + scope_resource_id: + type: string + required: &ref_466 + - scope_resource_id + responses: + '200': + description: topic list + content: + application/json: + schema: + type: array + items: + type: object + /w/{workspace}/azure_triggers/namespaces/subscriptions/list/{path}: + post: + summary: list subscriptions under a Namespace topic + operationId: listAzureNamespaceSubscriptions + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: &ref_467 + scope_resource_id: + type: string + topic_name: + type: string + required: &ref_468 + - scope_resource_id + - topic_name + responses: + '200': + description: subscription list + content: + application/json: + schema: + type: array + items: + type: object + /w/{workspace}/azure_triggers/subscriptions/delete/{path}: + delete: + summary: delete an Event Grid subscription on Azure + operationId: deleteAzureSubscription + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: &ref_461 + azure_mode: + type: string + enum: *ref_254 + description: Azure Event Grid trigger mode. + scope_resource_id: + type: string + topic_name: + type: string + nullable: true + subscription_name: + type: string + required: &ref_462 + - azure_mode + - scope_resource_id + - subscription_name + responses: + '200': + description: subscription deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/azure_triggers/namespaces/list/{path}: + post: + summary: list Event Grid Namespaces the service principal can access + operationId: listAzureNamespaces + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + responses: + '200': + description: namespace list + content: + application/json: + schema: + type: array + items: + type: object + description: An ARM resource the service principal can see. + properties: &ref_258 + id: + type: string + name: + type: string + location: + type: string + type: + type: string + required: &ref_259 + - id + - name + - type + /w/{workspace}/azure_triggers/basic/topics/list/{path}: + post: + summary: >- + list Basic Event Grid topics + system topics the service principal can + access + operationId: listAzureBasicTopics + tags: + - azure_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_4 + - name: path + in: path + required: true + schema: *ref_60 + responses: + '200': + description: topic list + content: + application/json: + schema: + type: array + items: + type: object + description: An ARM resource the service principal can see. + properties: *ref_258 + required: *ref_259 /w/{workspace}/postgres_triggers/postgres/version/{path}: get: summary: get postgres version @@ -26282,19 +26957,19 @@ paths: application/json: schema: type: object - properties: &ref_459 + properties: &ref_477 postgres_resource_path: type: string relations: type: array items: type: object - properties: &ref_251 + properties: &ref_261 schema_name: type: string table_to_track: type: array - items: &ref_457 + items: &ref_475 type: object properties: table_name: @@ -26307,14 +26982,14 @@ paths: type: string required: - table_name - required: &ref_252 + required: &ref_262 - schema_name - table_to_track language: type: string - enum: &ref_458 + enum: &ref_476 - Typescript - required: &ref_460 + required: &ref_478 - postgres_resource_path - relations - language @@ -26339,7 +27014,7 @@ paths: - name: id in: path required: true - schema: &ref_293 + schema: &ref_303 type: string responses: '200': @@ -26372,7 +27047,7 @@ paths: type: array items: type: object - properties: &ref_456 + properties: &ref_474 slot_name: type: string active: @@ -26399,7 +27074,7 @@ paths: application/json: schema: type: object - properties: &ref_250 + properties: &ref_260 name: type: string responses: @@ -26431,7 +27106,7 @@ paths: application/json: schema: type: object - properties: *ref_250 + properties: *ref_260 responses: '200': description: postgres replication slot deleted @@ -26482,7 +27157,7 @@ paths: in: path required: true description: The name of the publication - schema: &ref_253 + schema: &ref_263 type: string responses: '200': @@ -26491,18 +27166,18 @@ paths: application/json: schema: type: object - properties: &ref_254 + properties: &ref_264 table_to_track: type: array items: type: object - properties: *ref_251 - required: *ref_252 + properties: *ref_261 + required: *ref_262 transaction_to_track: type: array items: type: string - required: &ref_255 + required: &ref_265 - transaction_to_track /w/{workspace}/postgres_triggers/publication/create/{publication}/{path}: post: @@ -26523,7 +27198,7 @@ paths: in: path required: true description: The name of the publication - schema: *ref_253 + schema: *ref_263 requestBody: description: new publication for postgres required: true @@ -26531,8 +27206,8 @@ paths: application/json: schema: type: object - properties: *ref_254 - required: *ref_255 + properties: *ref_264 + required: *ref_265 responses: '201': description: publication created @@ -26559,7 +27234,7 @@ paths: in: path required: true description: The name of the publication - schema: *ref_253 + schema: *ref_263 requestBody: description: update publication for postgres required: true @@ -26567,8 +27242,8 @@ paths: application/json: schema: type: object - properties: *ref_254 - required: *ref_255 + properties: *ref_264 + required: *ref_265 responses: '201': description: publication updated @@ -26595,7 +27270,7 @@ paths: in: path required: true description: The name of the publication - schema: *ref_253 + schema: *ref_263 responses: '200': description: postgres publication deleted @@ -26621,7 +27296,7 @@ paths: application/json: schema: type: object - properties: &ref_461 + properties: &ref_479 replication_slot_name: type: string description: Name of the PostgreSQL logical replication slot to use @@ -26646,7 +27321,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 postgres_resource_path: type: string description: >- @@ -26657,8 +27332,8 @@ paths: Configuration for creating/managing the publication (tables, operations) type: object - properties: *ref_254 - required: *ref_255 + properties: *ref_264 + required: *ref_265 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -26669,7 +27344,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -26685,7 +27360,7 @@ paths: type: array items: type: string - required: &ref_462 + required: &ref_480 - path - script_path - is_flow @@ -26720,7 +27395,7 @@ paths: application/json: schema: type: object - properties: &ref_463 + properties: &ref_481 replication_slot_name: type: string description: Name of the PostgreSQL logical replication slot to use @@ -26745,7 +27420,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 postgres_resource_path: type: string description: >- @@ -26756,8 +27431,8 @@ paths: Configuration for creating/managing the publication (tables, operations) type: object - properties: *ref_254 - required: *ref_255 + properties: *ref_264 + required: *ref_265 error_handler_path: type: string description: Path to a script or flow to run when the triggered job fails @@ -26768,7 +27443,7 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -26784,7 +27459,7 @@ paths: type: array items: type: string - required: &ref_464 + required: &ref_482 - path - script_path - is_flow @@ -26842,12 +27517,12 @@ paths: content: application/json: schema: - allOf: &ref_256 + allOf: &ref_266 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_257 + properties: &ref_267 postgres_resource_path: type: string description: >- @@ -26885,8 +27560,8 @@ paths: retry: description: Retry configuration for failed module executions type: object - properties: *ref_194 - required: &ref_258 + properties: *ref_196 + required: &ref_268 - postgres_resource_path - replication_slot_name - publication_name @@ -26936,10 +27611,10 @@ paths: schema: type: array items: - allOf: *ref_256 + allOf: *ref_266 type: object - properties: *ref_257 - required: *ref_258 + properties: *ref_267 + required: *ref_268 /w/{workspace}/postgres_triggers/exists/{path}: get: summary: does postgres trigger exists @@ -26988,7 +27663,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -27046,7 +27721,7 @@ paths: application/json: schema: type: object - properties: &ref_473 + properties: &ref_491 path: type: string script_path: @@ -27066,11 +27741,11 @@ paths: retry: type: object description: Retry configuration for failed module executions - properties: *ref_194 + properties: *ref_196 mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 permissioned_as: type: string description: >- @@ -27086,7 +27761,7 @@ paths: type: array items: type: string - required: &ref_474 + required: &ref_492 - path - script_path - local_part @@ -27120,7 +27795,7 @@ paths: application/json: schema: type: object - properties: &ref_475 + properties: &ref_493 path: type: string script_path: @@ -27140,7 +27815,7 @@ paths: retry: type: object description: Retry configuration for failed module executions - properties: *ref_194 + properties: *ref_196 permissioned_as: type: string description: >- @@ -27156,7 +27831,7 @@ paths: type: array items: type: string - required: &ref_476 + required: &ref_494 - path - script_path - is_flow @@ -27210,12 +27885,12 @@ paths: content: application/json: schema: - allOf: &ref_259 + allOf: &ref_269 - type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 type: object - properties: &ref_260 + properties: &ref_270 local_part: type: string workspaced_local_part: @@ -27229,8 +27904,8 @@ paths: retry: type: object description: Retry configuration for failed module executions - properties: *ref_194 - required: &ref_261 + properties: *ref_196 + required: &ref_271 - local_part /w/{workspace}/email_triggers/list: get: @@ -27278,10 +27953,10 @@ paths: schema: type: array items: - allOf: *ref_259 + allOf: *ref_269 type: object - properties: *ref_260 - required: *ref_261 + properties: *ref_270 + required: *ref_271 /w/{workspace}/email_triggers/exists/{path}: get: summary: does email trigger exists @@ -27363,7 +28038,7 @@ paths: mode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 required: - mode responses: @@ -27388,9 +28063,9 @@ paths: type: array items: type: object - required: &ref_477 + required: &ref_495 - name - properties: &ref_478 + properties: &ref_496 name: type: string summary: @@ -27420,9 +28095,9 @@ paths: type: array items: type: object - required: &ref_263 + required: &ref_273 - name - properties: &ref_264 + properties: &ref_274 name: type: string summary: @@ -27441,14 +28116,14 @@ paths: type: array items: type: object - properties: &ref_479 + properties: &ref_497 workspace_id: type: string workspace_name: type: string role: type: string - required: &ref_480 + required: &ref_498 - name /groups/get/{name}: get: @@ -27460,7 +28135,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: instance group @@ -27468,8 +28143,8 @@ paths: application/json: schema: type: object - required: *ref_263 - properties: *ref_264 + required: *ref_273 + properties: *ref_274 /groups/create: post: summary: create instance group @@ -27507,7 +28182,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: update instance group required: true @@ -27543,7 +28218,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: instance group deleted @@ -27561,7 +28236,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: user to add to instance group required: true @@ -27591,7 +28266,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: user to remove from instance group required: true @@ -27626,7 +28301,7 @@ paths: type: array items: type: object - properties: &ref_265 + properties: &ref_275 name: type: string summary: @@ -27647,7 +28322,7 @@ paths: enum: - superadmin - devops - required: &ref_266 + required: &ref_276 - name /groups/overwrite: post: @@ -27664,8 +28339,8 @@ paths: type: array items: type: object - properties: *ref_265 - required: *ref_266 + properties: *ref_275 + required: *ref_276 responses: '200': description: success message @@ -27701,7 +28376,7 @@ paths: type: array items: type: object - properties: &ref_267 + properties: &ref_277 name: type: string summary: @@ -27714,7 +28389,7 @@ paths: type: object additionalProperties: type: boolean - required: &ref_268 + required: &ref_278 - name /w/{workspace}/groups/listnames: get: @@ -27787,7 +28462,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: updated group required: true @@ -27819,7 +28494,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: group deleted @@ -27841,7 +28516,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: group @@ -27849,8 +28524,8 @@ paths: application/json: schema: type: object - properties: *ref_267 - required: *ref_268 + properties: *ref_277 + required: *ref_278 /w/{workspace}/groups/adduser/{name}: post: summary: add user to group @@ -27865,7 +28540,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: added user to group required: true @@ -27897,7 +28572,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: added user to group required: true @@ -27929,7 +28604,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 - name: page description: which page to return (start at 1, default 1) in: query @@ -27988,7 +28663,7 @@ paths: type: array items: type: object - properties: &ref_270 + properties: &ref_280 name: type: string owners: @@ -28014,7 +28689,7 @@ paths: (relative to the folder root) wins, and its `permissioned_as` is used as the default. type: array - items: &ref_269 + items: &ref_279 type: object required: - path_glob @@ -28035,7 +28710,7 @@ paths: permissioned as. Must be `u/`, `g/`, or an email that exists in this workspace. - required: &ref_271 + required: &ref_281 - name - owners - extra_perms @@ -28102,7 +28777,7 @@ paths: to the folder root) wins, and its `permissioned_as` is used as the default. type: array - items: *ref_269 + items: *ref_279 required: - name responses: @@ -28126,7 +28801,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: update folder required: true @@ -28152,7 +28827,7 @@ paths: to the folder root) wins, and its `permissioned_as` is used as the default. type: array - items: *ref_269 + items: *ref_279 responses: '200': description: folder updated @@ -28174,7 +28849,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: folder deleted @@ -28196,7 +28871,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: folder @@ -28204,8 +28879,8 @@ paths: application/json: schema: type: object - properties: *ref_270 - required: *ref_271 + properties: *ref_280 + required: *ref_281 /w/{workspace}/folders/exists/{name}: get: summary: exists folder @@ -28220,7 +28895,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: folder exists @@ -28242,7 +28917,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: folder @@ -28284,7 +28959,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: owner user to folder required: true @@ -28318,7 +28993,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: added owner to folder required: true @@ -28354,7 +29029,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 - name: page description: which page to return (start at 1, default 1) in: query @@ -28418,7 +29093,7 @@ paths: type: array items: type: object - properties: &ref_481 + properties: &ref_499 worker: type: string worker_instance: @@ -28464,7 +29139,7 @@ paths: type: string native_mode: type: boolean - required: &ref_482 + required: &ref_500 - worker - worker_instance - ping_at @@ -28598,7 +29273,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: a config @@ -28607,12 +29282,12 @@ paths: schema: type: object nullable: true - properties: &ref_373 + properties: &ref_383 alerts: type: array items: type: object - properties: &ref_371 + properties: &ref_381 name: type: string tags_to_monitor: @@ -28625,7 +29300,7 @@ paths: type: integer alert_time_threshold_seconds: type: integer - required: &ref_372 + required: &ref_382 - name - tags_to_monitor - jobs_num_threshold @@ -28641,7 +29316,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 requestBody: description: worker group required: true @@ -28664,7 +29339,7 @@ paths: - name: name in: path required: true - schema: *ref_262 + schema: *ref_272 responses: '200': description: Delete config @@ -28687,12 +29362,12 @@ paths: type: array items: type: object - properties: &ref_527 + properties: &ref_543 name: type: string config: type: object - required: &ref_528 + required: &ref_544 - name /configs/list_autoscaling_events/{worker_group}: get: @@ -28723,7 +29398,7 @@ paths: type: array items: type: object - properties: &ref_531 + properties: &ref_547 id: type: integer format: int64 @@ -29000,6 +29675,7 @@ paths: - postgres_trigger - mqtt_trigger - gcp_trigger + - azure_trigger - sqs_trigger - email_trigger - volume @@ -29049,6 +29725,7 @@ paths: - postgres_trigger - mqtt_trigger - gcp_trigger + - azure_trigger - sqs_trigger - email_trigger - volume @@ -29110,6 +29787,7 @@ paths: - postgres_trigger - mqtt_trigger - gcp_trigger + - azure_trigger - sqs_trigger - email_trigger - volume @@ -29153,7 +29831,7 @@ paths: properties: trigger_kind: type: string - enum: &ref_272 + enum: &ref_282 - webhook - http - websocket @@ -29164,6 +29842,7 @@ paths: - sqs - mqtt - gcp + - azure - email path: type: string @@ -29198,7 +29877,7 @@ paths: required: true schema: type: string - enum: *ref_272 + enum: *ref_282 - name: runnable_kind in: path required: true @@ -29238,17 +29917,17 @@ paths: type: array items: type: object - properties: &ref_532 + properties: &ref_548 trigger_config: {} trigger_kind: type: string - enum: *ref_272 + enum: *ref_282 error: type: string last_server_ping: type: string format: date-time - required: &ref_533 + required: &ref_549 - trigger_kind /w/{workspace}/capture/list/{runnable_kind}/{path}: get: @@ -29273,7 +29952,7 @@ paths: in: query schema: type: string - enum: *ref_272 + enum: *ref_282 - name: page description: which page to return (start at 1, default 1) in: query @@ -29291,10 +29970,10 @@ paths: type: array items: type: object - properties: &ref_273 + properties: &ref_283 trigger_kind: type: string - enum: *ref_272 + enum: *ref_282 main_args: {} preprocessor_args: {} id: @@ -29302,7 +29981,7 @@ paths: created_at: type: string format: date-time - required: &ref_274 + required: &ref_284 - trigger_kind - main_args - preprocessor_args @@ -29367,8 +30046,8 @@ paths: application/json: schema: type: object - properties: *ref_273 - required: *ref_274 + properties: *ref_283 + required: *ref_284 delete: summary: delete a capture operationId: deleteCapture @@ -29460,13 +30139,13 @@ paths: schema: *ref_4 - name: runnable_id in: query - schema: &ref_275 + schema: &ref_285 type: string - name: runnable_type in: query - schema: &ref_276 + schema: &ref_286 type: string - enum: &ref_381 + enum: &ref_391 - ScriptHash - ScriptPath - FlowPath @@ -29483,7 +30162,7 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: include_preview in: query schema: @@ -29497,7 +30176,7 @@ paths: type: array items: type: object - properties: &ref_277 + properties: &ref_287 id: type: string name: @@ -29511,7 +30190,7 @@ paths: type: boolean success: type: boolean - required: &ref_278 + required: &ref_288 - id - name - args @@ -29561,10 +30240,10 @@ paths: schema: *ref_4 - name: runnable_id in: query - schema: *ref_275 + schema: *ref_285 - name: runnable_type in: query - schema: *ref_276 + schema: *ref_286 - name: page description: which page to return (start at 1, default 1) in: query @@ -29582,8 +30261,8 @@ paths: type: array items: type: object - properties: *ref_277 - required: *ref_278 + properties: *ref_287 + required: *ref_288 /w/{workspace}/inputs/create: post: summary: Create an Input for future use in a script or flow @@ -29597,10 +30276,10 @@ paths: schema: *ref_4 - name: runnable_id in: query - schema: *ref_275 + schema: *ref_285 - name: runnable_type in: query - schema: *ref_276 + schema: *ref_286 requestBody: description: Input required: true @@ -29608,12 +30287,12 @@ paths: application/json: schema: type: object - properties: &ref_377 + properties: &ref_387 name: type: string args: type: object - required: &ref_378 + required: &ref_388 - name - args - created_by @@ -29643,14 +30322,14 @@ paths: application/json: schema: type: object - properties: &ref_379 + properties: &ref_389 id: type: string name: type: string is_public: type: boolean - required: &ref_380 + required: &ref_390 - id - name - is_public @@ -29676,7 +30355,7 @@ paths: - name: input in: path required: true - schema: &ref_301 + schema: &ref_311 type: string responses: '200': @@ -29709,7 +30388,7 @@ paths: properties: s3_resource: type: object - properties: &ref_279 + properties: &ref_289 bucket: type: string region: @@ -29724,7 +30403,7 @@ paths: type: string pathStyle: type: boolean - required: &ref_280 + required: &ref_290 - bucket - region - endPoint @@ -29802,8 +30481,8 @@ paths: properties: s3_resource: type: object - properties: *ref_279 - required: *ref_280 + properties: *ref_289 + required: *ref_290 responses: '200': description: Connection settings @@ -29824,10 +30503,10 @@ paths: type: boolean client_kwargs: type: object - properties: &ref_281 + properties: &ref_291 region_name: type: string - required: &ref_282 + required: &ref_292 - region_name required: - endpoint_url @@ -29882,8 +30561,8 @@ paths: type: boolean client_kwargs: type: object - properties: *ref_281 - required: *ref_282 + properties: *ref_291 + required: *ref_292 required: - endpoint_url - use_ssl @@ -29941,8 +30620,8 @@ paths: application/json: schema: type: object - properties: *ref_279 - required: *ref_280 + properties: *ref_289 + required: *ref_290 /w/{workspace}/job_helpers/test_connection: get: summary: Test connection to the workspace object storage @@ -30006,10 +30685,10 @@ paths: type: array items: type: object - properties: &ref_283 + properties: &ref_293 s3: type: string - required: &ref_284 + required: &ref_294 - s3 restricted_access: type: boolean @@ -30042,7 +30721,7 @@ paths: application/json: schema: type: object - properties: &ref_287 + properties: &ref_297 mime_type: type: string size_in_bytes: @@ -30106,7 +30785,7 @@ paths: application/json: schema: type: object - properties: &ref_285 + properties: &ref_295 msg: type: string content: @@ -30118,7 +30797,7 @@ paths: - Csv - Parquet - Unknown - required: &ref_286 + required: &ref_296 - content_type /w/{workspace}/job_helpers/list_git_repo_files: get: @@ -30166,8 +30845,8 @@ paths: type: array items: type: object - properties: *ref_283 - required: *ref_284 + properties: *ref_293 + required: *ref_294 restricted_access: type: boolean required: @@ -30226,8 +30905,8 @@ paths: application/json: schema: type: object - properties: *ref_285 - required: *ref_286 + properties: *ref_295 + required: *ref_296 /w/{workspace}/job_helpers/load_git_repo_file_metadata: get: summary: >- @@ -30258,7 +30937,7 @@ paths: application/json: schema: type: object - properties: *ref_287 + properties: *ref_297 /w/{workspace}/job_helpers/check_s3_folder_exists: get: summary: Check if S3 path exists and is a folder @@ -30700,7 +31379,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 requestBody: description: parameters for statistics retrieval required: true @@ -30729,46 +31408,46 @@ paths: type: array items: type: object - properties: &ref_511 + properties: &ref_529 id: type: string name: type: string - required: &ref_512 + required: &ref_530 - id scalar_metrics: type: array items: type: object - properties: &ref_513 + properties: &ref_531 metric_id: type: string value: type: number - required: &ref_514 + required: &ref_532 - id - value timeseries_metrics: type: array items: type: object - properties: &ref_515 + properties: &ref_533 metric_id: type: string values: type: array items: type: object - properties: &ref_517 + properties: &ref_535 timestamp: type: string format: date-time value: type: number - required: &ref_518 + required: &ref_536 - timestamp - value - required: &ref_516 + required: &ref_534 - id - values /w/{workspace}/job_metrics/set_progress/{id}: @@ -30785,7 +31464,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 requestBody: description: parameters for statistics retrieval required: true @@ -30819,7 +31498,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: job progress between 0 and 99 @@ -30837,11 +31516,11 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_288 + schema: *ref_298 - name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_289 + schema: *ref_299 - name: with_error in: query required: false @@ -30913,12 +31592,12 @@ paths: type: array items: type: object - properties: &ref_521 + properties: &ref_537 concurrency_key: type: string total_running: type: number - required: &ref_522 + required: &ref_538 - concurrency_key - total_running /concurrency_groups/prune/{concurrency_id}: @@ -30931,7 +31610,7 @@ paths: - name: concurrency_id in: path required: true - schema: &ref_303 + schema: &ref_313 type: string responses: '200': @@ -30951,7 +31630,7 @@ paths: - name: id in: path required: true - schema: *ref_169 + schema: *ref_171 responses: '200': description: concurrency key for given job @@ -30994,7 +31673,7 @@ paths: (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release') in: query - schema: *ref_171 + schema: *ref_173 - name: parent_job description: >- The parent job that is at the origin and responsible for the @@ -31007,84 +31686,84 @@ paths: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 - name: script_path_start description: >- filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 - name: running description: filter on running jobs in: query - schema: *ref_160 + schema: *ref_162 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_161 + schema: *ref_163 - name: completed_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_181 + schema: *ref_183 - name: completed_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_182 + schema: *ref_184 - name: created_before_queue description: filter on jobs created before X for jobs in the queue only in: query - schema: *ref_183 + schema: *ref_185 - name: created_after_queue description: filter on jobs created after X for jobs in the queue only in: query - schema: *ref_184 + schema: *ref_186 - name: job_kinds description: >- filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 - name: tag description: >- filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_168 + schema: *ref_170 - name: page description: which page to return (start at 1, default 1) in: query @@ -31100,7 +31779,7 @@ paths: (e.g. '!schedule,!webhook') in: query x-go-name: JobTriggerKindParam - schema: *ref_185 + schema: *ref_187 - name: is_skipped description: is the job skipped in: query @@ -31140,17 +31819,17 @@ paths: application/json: schema: type: object - properties: &ref_523 + properties: &ref_539 jobs: type: array items: - oneOf: *ref_190 - discriminator: *ref_191 + oneOf: *ref_192 + discriminator: *ref_193 obscured_jobs: type: array items: type: object - properties: &ref_382 + properties: &ref_392 typ: type: string started_at: @@ -31163,7 +31842,7 @@ paths: Obscured jobs omitted for security because of too specific filtering type: boolean - required: &ref_524 + required: &ref_540 - jobs - obscured_jobs /srch/w/{workspace}/index/search/job: @@ -31207,7 +31886,7 @@ paths: type: array items: type: object - properties: &ref_529 + properties: &ref_545 dancer: type: string hit_count: @@ -31286,7 +31965,7 @@ paths: type: array items: type: object - properties: &ref_530 + properties: &ref_546 dancer: type: string /srch/index/search/count_service_logs: @@ -31549,7 +32228,7 @@ paths: type: string kind: type: string - enum: *ref_290 + enum: *ref_300 usages: type: array items: @@ -31562,13 +32241,13 @@ paths: type: string kind: type: string - enum: &ref_292 + enum: &ref_302 - script - flow - job access_type: type: string - enum: &ref_291 + enum: &ref_301 - r - w - rw @@ -31578,7 +32257,7 @@ paths: description: The columns used (for tables) additionalProperties: type: string - enum: *ref_291 + enum: *ref_301 nullable: true created_at: type: string @@ -31651,7 +32330,7 @@ paths: type: string kind: type: string - enum: *ref_292 + enum: *ref_302 responses: '200': description: all assets used by the given usage paths, in the same order @@ -31671,10 +32350,10 @@ paths: type: string kind: type: string - enum: *ref_290 + enum: *ref_300 access_type: type: string - enum: *ref_291 + enum: *ref_301 nullable: true /w/{workspace}/assets/list_favorites: get: @@ -31722,13 +32401,13 @@ paths: type: array items: type: object - required: &ref_543 + required: &ref_559 - name - size_bytes - file_count - created_at - created_by - properties: &ref_544 + properties: &ref_560 name: type: string size_bytes: @@ -31843,13 +32522,13 @@ paths: type: array items: type: object - required: &ref_365 + required: &ref_375 - name - description - instructions - path - method - properties: &ref_366 + properties: &ref_376 name: type: string description: The tool name/operation ID @@ -31986,7 +32665,7 @@ components: name: id in: path required: true - schema: *ref_293 + schema: *ref_303 Key: name: key in: path @@ -32002,7 +32681,7 @@ components: in: path required: true description: The name of the publication - schema: *ref_253 + schema: *ref_263 VersionId: name: version in: path @@ -32013,7 +32692,7 @@ components: name: token in: path required: true - schema: *ref_294 + schema: *ref_304 AccountId: name: id in: path @@ -32038,7 +32717,7 @@ components: name: id in: path required: true - schema: *ref_169 + schema: *ref_171 Path: name: path in: path @@ -32058,12 +32737,12 @@ components: name: version in: path required: true - schema: *ref_295 + schema: *ref_305 Name: name: name in: path required: true - schema: *ref_262 + schema: *ref_272 Page: name: page description: which page to return (start at 1, default 1) @@ -32082,7 +32761,7 @@ components: '!schedule,!webhook') in: query x-go-name: JobTriggerKindParam - schema: *ref_185 + schema: *ref_187 OrderDesc: name: order_desc description: order by desc order (default true) @@ -32103,7 +32782,7 @@ components: 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release') in: query - schema: *ref_171 + schema: *ref_173 Worker: name: worker description: >- @@ -32111,7 +32790,7 @@ components: 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2') in: query - schema: *ref_153 + schema: *ref_155 ParentJob: name: parent_job description: >- @@ -32177,12 +32856,12 @@ components: 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2') in: query - schema: *ref_155 + schema: *ref_157 SchedulePath: name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_156 + schema: *ref_158 TriggerPath: name: trigger_path description: >- @@ -32190,7 +32869,7 @@ components: 'f/trigger1,f/trigger2') and negation by prefixing all values with '!' (e.g. '!f/trigger1,!f/trigger2') in: query - schema: *ref_296 + schema: *ref_306 ScriptExactPath: name: script_path_exact description: >- @@ -32198,87 +32877,87 @@ components: (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2') in: query - schema: *ref_154 + schema: *ref_156 ScriptExactHash: name: script_hash description: mask to filter exact matching path in: query - schema: *ref_157 + schema: *ref_159 CreatedBefore: name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_179 + schema: *ref_181 CreatedAfter: name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_180 + schema: *ref_182 StartedBefore: name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_158 + schema: *ref_160 StartedAfter: name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_159 + schema: *ref_161 Before: name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_288 + schema: *ref_298 CompletedBefore: name: completed_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_181 + schema: *ref_183 CompletedAfter: name: completed_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_182 + schema: *ref_184 CreatedAfterQueue: name: created_after_queue description: filter on jobs created after X for jobs in the queue only in: query - schema: *ref_184 + schema: *ref_186 CreatedBeforeQueue: name: created_before_queue description: filter on jobs created before X for jobs in the queue only in: query - schema: *ref_183 + schema: *ref_185 Success: name: success description: filter on successful jobs in: query - schema: *ref_167 + schema: *ref_169 ScheduledForBeforeNow: name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_161 + schema: *ref_163 Suspended: name: suspended description: filter on suspended jobs in: query - schema: *ref_163 + schema: *ref_165 Running: name: running description: filter on running jobs in: query - schema: *ref_160 + schema: *ref_162 AllowWildcards: name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_168 + schema: *ref_170 ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_164 + schema: *ref_166 Tag: name: tag description: >- @@ -32286,37 +32965,37 @@ components: 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem') in: query - schema: *ref_165 + schema: *ref_167 ResultFilter: name: result description: filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_166 + schema: *ref_168 After: name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_289 + schema: *ref_299 Username: name: username description: filter on exact username of user in: query - schema: *ref_297 + schema: *ref_307 Operation: name: operation description: filter on exact or prefix name of operation in: query - schema: *ref_298 + schema: *ref_308 ResourceName: name: resource description: filter on exact or prefix name of resource in: query - schema: *ref_299 + schema: *ref_309 ActionKind: name: action_kind description: filter on type of operation in: query - schema: *ref_300 + schema: *ref_310 JobKinds: name: job_kinds description: >- @@ -32324,29 +33003,29 @@ components: 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies') in: query - schema: *ref_162 + schema: *ref_164 RunnableId: name: runnable_id in: query - schema: *ref_275 + schema: *ref_285 RunnableTypeQuery: name: runnable_type in: query - schema: *ref_276 + schema: *ref_286 InputId: name: input in: path required: true - schema: *ref_301 + schema: *ref_311 GetStarted: name: get_started in: query - schema: *ref_302 + schema: *ref_312 ConcurrencyId: name: concurrency_id in: path required: true - schema: *ref_303 + schema: *ref_313 RunnableKind: name: runnable_kind in: path @@ -32365,12 +33044,12 @@ components: description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: *ref_151 - required: *ref_152 + properties: *ref_149 + required: *ref_150 Retry: type: object description: Retry configuration for failed module executions - properties: *ref_194 + properties: *ref_196 StopAfterIf: type: object description: Early termination condition for a module @@ -32513,7 +33192,7 @@ components: retry: description: Retry configuration for failed module executions type: object - properties: *ref_304 + properties: *ref_314 debouncing: description: Debounce configuration for this step (EE only) type: object @@ -32604,7 +33283,7 @@ components: kind: type: string description: Supported AI provider types - enum: *ref_305 + enum: *ref_315 resource: type: string description: >- @@ -32624,16 +33303,16 @@ components: oneOf: - type: object description: No conversation memory/context - properties: *ref_306 - required: *ref_307 + properties: *ref_316 + required: *ref_317 - type: object description: Automatic context management - properties: *ref_308 - required: *ref_309 + properties: *ref_318 + required: *ref_319 - type: object description: Explicit message history - properties: *ref_310 - required: *ref_311 + properties: *ref_320 + required: *ref_321 discriminator: propertyName: kind mapping: @@ -32650,62 +33329,62 @@ components: Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms - properties: *ref_312 - required: *ref_313 + properties: *ref_322 + required: *ref_323 - type: object description: >- Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code - properties: *ref_314 - required: *ref_315 + properties: *ref_324 + required: *ref_325 - type: object description: >- Reference to an existing flow by path. Use this to call another flow as a subflow - properties: *ref_316 - required: *ref_317 + properties: *ref_326 + required: *ref_327 - type: object description: >- Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations - properties: *ref_318 - required: *ref_319 + properties: *ref_328 + required: *ref_329 - type: object description: >- Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination - properties: *ref_320 - required: *ref_321 + properties: *ref_330 + required: *ref_331 - type: object description: >- Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes - properties: *ref_322 - required: *ref_323 + properties: *ref_332 + required: *ref_333 - type: object description: >- Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently - properties: *ref_324 - required: *ref_325 + properties: *ref_334 + required: *ref_335 - type: object description: >- Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder - properties: *ref_326 - required: *ref_327 + properties: *ref_336 + required: *ref_337 - type: object description: >- AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task - properties: *ref_328 - required: *ref_329 + properties: *ref_338 + required: *ref_339 discriminator: propertyName: type mapping: @@ -33111,8 +33790,8 @@ components: description: >- Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined - oneOf: *ref_330 - discriminator: *ref_331 + oneOf: *ref_340 + discriminator: *ref_341 output_type: allOf: - description: >- @@ -33165,8 +33844,8 @@ components: description: >- Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined - oneOf: *ref_332 - discriminator: *ref_333 + oneOf: *ref_342 + discriminator: *ref_343 output_schema: allOf: - description: >- @@ -33242,8 +33921,8 @@ components: description: >- A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool - properties: *ref_334 - required: *ref_335 + properties: *ref_344 + required: *ref_345 type: type: string enum: @@ -33272,8 +33951,8 @@ components: - type FlowStatus: type: object - properties: *ref_172 - required: *ref_173 + properties: *ref_174 + required: *ref_175 FlowStatusModule: type: object properties: @@ -33510,75 +34189,75 @@ components: HealthChecks: type: object description: Detailed health checks - required: *ref_336 - properties: *ref_337 + required: *ref_346 + properties: *ref_347 DatabaseHealth: type: object description: Database health status - required: *ref_338 - properties: *ref_339 + required: *ref_348 + properties: *ref_349 PoolStats: type: object description: Database connection pool statistics - required: *ref_340 - properties: *ref_341 + required: *ref_350 + properties: *ref_351 WorkersHealth: type: object description: Workers health status - required: *ref_342 - properties: *ref_343 + required: *ref_352 + properties: *ref_353 QueueHealth: type: object description: Job queue status - required: *ref_344 - properties: *ref_345 + required: *ref_354 + properties: *ref_355 ReadinessHealth: type: object description: Server readiness status - required: *ref_346 - properties: *ref_347 + required: *ref_356 + properties: *ref_357 AutoInviteConfig: type: object description: Configuration for auto-inviting users to the workspace - properties: *ref_348 + properties: *ref_358 ErrorHandlerConfig: type: object description: Configuration for the workspace error handler - properties: *ref_349 + properties: *ref_359 SuccessHandlerConfig: type: object description: Configuration for the workspace success handler - properties: *ref_350 + properties: *ref_360 EditErrorHandler: description: >- Request body for editing the workspace error handler. Accepts both new grouped format and legacy flat format for backward compatibility. - oneOf: *ref_351 + oneOf: *ref_361 EditErrorHandlerNew: type: object description: New grouped format for editing error handler - properties: *ref_352 + properties: *ref_362 EditErrorHandlerLegacy: type: object description: >- Legacy flat format for editing error handler (deprecated, use new format) - properties: *ref_353 + properties: *ref_363 EditSuccessHandler: description: >- Request body for editing the workspace success handler. Accepts both new grouped format and legacy flat format for backward compatibility. - oneOf: *ref_354 + oneOf: *ref_364 EditSuccessHandlerNew: type: object description: New grouped format for editing success handler - properties: *ref_355 + properties: *ref_365 EditSuccessHandlerLegacy: type: object description: >- Legacy flat format for editing success handler (deprecated, use new format) - properties: *ref_356 + properties: *ref_366 VaultSettings: type: object required: *ref_27 @@ -33593,28 +34272,28 @@ components: properties: *ref_34 SecretMigrationFailure: type: object - required: *ref_357 - properties: *ref_358 + required: *ref_367 + properties: *ref_368 SecretMigrationReport: type: object required: *ref_29 properties: *ref_30 JwksResponse: type: object - required: *ref_359 - properties: *ref_360 + required: *ref_369 + properties: *ref_370 FlowConversation: type: object - required: *ref_361 - properties: *ref_362 + required: *ref_371 + properties: *ref_372 FlowConversationMessage: type: object - required: *ref_363 - properties: *ref_364 + required: *ref_373 + properties: *ref_374 EndpointTool: type: object - required: *ref_365 - properties: *ref_366 + required: *ref_375 + properties: *ref_376 AIProvider: type: string enum: *ref_47 @@ -33627,35 +34306,35 @@ components: required: *ref_44 AIProviderConfig: type: object - properties: *ref_367 - required: *ref_368 + properties: *ref_377 + required: *ref_378 AIConfig: type: object properties: *ref_46 InstanceAIProviderSummary: type: object - properties: *ref_369 - required: *ref_370 + properties: *ref_379 + required: *ref_380 InstanceAISummary: type: object properties: *ref_48 required: *ref_49 Alert: type: object - properties: *ref_371 - required: *ref_372 + properties: *ref_381 + required: *ref_382 Configs: type: object nullable: true - properties: *ref_373 + properties: *ref_383 WorkspaceDependencies: type: object properties: *ref_97 required: *ref_98 NewWorkspaceDependencies: type: object - properties: *ref_374 - required: *ref_375 + properties: *ref_384 + required: *ref_385 Script: type: object properties: *ref_99 @@ -33665,7 +34344,7 @@ components: properties: *ref_104 required: *ref_105 NewScriptWithDraft: - allOf: *ref_376 + allOf: *ref_386 ScriptHistory: type: object properties: *ref_106 @@ -33676,65 +34355,65 @@ components: additionalProperties: true Input: type: object - properties: *ref_277 - required: *ref_278 + properties: *ref_287 + required: *ref_288 CreateInput: type: object - properties: *ref_377 - required: *ref_378 + properties: *ref_387 + required: *ref_388 UpdateInput: type: object - properties: *ref_379 - required: *ref_380 + properties: *ref_389 + required: *ref_390 RunnableType: type: string - enum: *ref_381 + enum: *ref_391 QueuedJob: + type: object + properties: *ref_190 + required: *ref_191 + CompletedJob: type: object properties: *ref_188 required: *ref_189 - CompletedJob: - type: object - properties: *ref_186 - required: *ref_187 ExportableCompletedJob: type: object description: Completed job with full data for export/import operations - properties: *ref_175 - required: *ref_176 + properties: *ref_177 + required: *ref_178 ExportableQueuedJob: type: object description: Queued job with full data for export/import operations - properties: *ref_177 - required: *ref_178 + properties: *ref_179 + required: *ref_180 ObscuredJob: type: object - properties: *ref_382 + properties: *ref_392 Job: - oneOf: *ref_190 - discriminator: *ref_191 + oneOf: *ref_192 + discriminator: *ref_193 User: type: object properties: *ref_35 required: *ref_36 UserSource: type: object - properties: *ref_383 - required: *ref_384 + properties: *ref_393 + required: *ref_394 UserUsage: type: object - properties: *ref_385 + properties: *ref_395 Login: type: object - properties: *ref_386 - required: *ref_387 + properties: *ref_396 + required: *ref_397 PasswordResetResponse: type: object properties: *ref_7 required: *ref_8 EditWorkspaceUser: type: object - properties: *ref_388 + properties: *ref_398 OffboardAffectedPaths: type: object properties: *ref_11 @@ -33744,64 +34423,64 @@ components: required: *ref_13 OffboardTokenInfo: type: object - properties: *ref_389 - required: *ref_390 + properties: *ref_399 + required: *ref_400 OffboardRequest: type: object - properties: *ref_391 - required: *ref_392 + properties: *ref_401 + required: *ref_402 OffboardResponse: type: object properties: *ref_14 OffboardSummary: type: object - properties: *ref_393 - required: *ref_394 + properties: *ref_403 + required: *ref_404 GlobalOffboardPreview: type: object - properties: *ref_395 - required: *ref_396 + properties: *ref_405 + required: *ref_406 WorkspaceOffboardPreview: type: object - properties: *ref_397 - required: *ref_398 + properties: *ref_407 + required: *ref_408 GlobalOffboardRequest: type: object - properties: *ref_399 + properties: *ref_409 WorkspaceReassignment: type: object - properties: *ref_400 - required: *ref_401 + properties: *ref_410 + required: *ref_411 TruncatedToken: type: object properties: *ref_102 required: *ref_103 ExternalJwtToken: type: object - properties: *ref_402 - required: *ref_403 + properties: *ref_412 + required: *ref_413 NewToken: type: object - properties: *ref_404 + properties: *ref_414 NewTokenImpersonate: type: object - properties: *ref_405 - required: *ref_406 + properties: *ref_415 + required: *ref_416 ListableVariable: type: object properties: *ref_61 required: *ref_62 ContextualVariable: type: object - properties: *ref_407 - required: *ref_408 + properties: *ref_417 + required: *ref_418 CreateVariable: type: object - properties: *ref_409 - required: *ref_410 + properties: *ref_419 + required: *ref_420 EditVariable: type: object - properties: *ref_411 + properties: *ref_421 AuditLog: type: object properties: *ref_5 @@ -33950,51 +34629,51 @@ components: required: *ref_142 PreviewInline: type: object - properties: *ref_412 - required: *ref_413 + properties: *ref_422 + required: *ref_423 InlineScriptArgs: type: object properties: *ref_140 WorkflowTask: type: object - properties: *ref_414 - required: *ref_415 + properties: *ref_424 + required: *ref_425 WorkflowStatusRecord: type: object additionalProperties: type: object - properties: *ref_174 + properties: *ref_176 WorkflowStatus: type: object - properties: *ref_174 + properties: *ref_176 CreateResource: type: object - properties: *ref_416 - required: *ref_417 + properties: *ref_426 + required: *ref_427 EditResource: type: object - properties: *ref_418 + properties: *ref_428 Resource: type: object - properties: *ref_419 - required: *ref_420 + properties: *ref_429 + required: *ref_430 ListableResource: type: object - properties: *ref_421 - required: *ref_422 + properties: *ref_431 + required: *ref_432 ResourceType: type: object properties: *ref_77 required: *ref_78 EditResourceType: type: object - properties: *ref_423 + properties: *ref_433 Schedule: type: object - properties: *ref_195 - required: *ref_196 + properties: *ref_197 + required: *ref_198 ScheduleWJobs: - allOf: *ref_424 + allOf: *ref_434 ErrorHandler: type: string enum: @@ -34004,121 +34683,121 @@ components: - email NewSchedule: type: object - properties: *ref_425 - required: *ref_426 + properties: *ref_435 + required: *ref_436 EditSchedule: type: object - properties: *ref_427 - required: *ref_428 + properties: *ref_437 + required: *ref_438 JobTriggerKind: description: job trigger kind (schedule, http, websocket...) type: string - enum: *ref_170 + enum: *ref_172 TriggerMode: description: job trigger mode type: string - enum: *ref_204 + enum: *ref_206 TriggerExtraProperty: type: object - properties: *ref_211 - required: *ref_212 + properties: *ref_213 + required: *ref_214 AuthenticationMethod: type: string - enum: *ref_203 + enum: *ref_205 RunnableKind: type: string - enum: *ref_197 + enum: *ref_199 OpenapiSpecFormat: type: string - enum: *ref_429 + enum: *ref_439 OpenapiHttpRouteFilters: type: object - properties: *ref_430 - required: *ref_431 + properties: *ref_440 + required: *ref_441 WebhookFilters: type: object - properties: *ref_432 - required: *ref_433 + properties: *ref_442 + required: *ref_443 OpenapiV3Info: type: object - properties: *ref_434 - required: *ref_435 + properties: *ref_444 + required: *ref_445 GenerateOpenapiSpec: type: object - properties: *ref_198 + properties: *ref_200 HttpMethod: type: string - enum: *ref_201 + enum: *ref_203 HttpRequestType: type: string - enum: *ref_202 + enum: *ref_204 HttpTrigger: - allOf: *ref_205 + allOf: *ref_207 type: object - properties: *ref_206 - required: *ref_207 + properties: *ref_208 + required: *ref_209 NewHttpTrigger: type: object - properties: *ref_199 - required: *ref_200 + properties: *ref_201 + required: *ref_202 EditHttpTrigger: type: object - properties: *ref_436 - required: *ref_437 + properties: *ref_446 + required: *ref_447 TriggersCount: type: object properties: *ref_125 WebsocketHeartbeat: type: object - properties: *ref_209 - required: *ref_210 + properties: *ref_211 + required: *ref_212 WebsocketTrigger: - allOf: *ref_213 + allOf: *ref_215 type: object - properties: *ref_214 - required: *ref_215 + properties: *ref_216 + required: *ref_217 NewWebsocketTrigger: type: object - properties: *ref_438 - required: *ref_439 + properties: *ref_448 + required: *ref_449 EditWebsocketTrigger: type: object - properties: *ref_440 - required: *ref_441 + properties: *ref_450 + required: *ref_451 WebsocketTriggerInitialMessage: - anyOf: *ref_208 + anyOf: *ref_210 MqttQoS: type: string - enum: *ref_442 + enum: *ref_452 MqttV3Config: type: object - properties: *ref_235 + properties: *ref_237 MqttV5Config: type: object - properties: *ref_236 + properties: *ref_238 MqttSubscribeTopic: type: object - properties: *ref_233 - required: *ref_234 + properties: *ref_235 + required: *ref_236 MqttClientVersion: type: string - enum: *ref_237 + enum: *ref_239 MqttTrigger: - allOf: *ref_238 + allOf: *ref_240 type: object - properties: *ref_239 - required: *ref_240 + properties: *ref_241 + required: *ref_242 NewMqttTrigger: type: object - properties: *ref_443 - required: *ref_444 + properties: *ref_453 + required: *ref_454 EditMqttTrigger: type: object - properties: *ref_445 - required: *ref_446 + properties: *ref_455 + required: *ref_456 DeliveryType: type: string - enum: *ref_243 + enum: *ref_245 description: >- Delivery mode for messages. 'push' for HTTP push delivery where messages are sent to a webhook endpoint, 'pull' for polling where the trigger @@ -34126,19 +34805,19 @@ components: PushConfig: type: object description: Configuration for push delivery mode. - properties: *ref_244 - required: *ref_245 + properties: *ref_246 + required: *ref_247 GcpTrigger: - allOf: *ref_247 + allOf: *ref_249 type: object description: >- A Google Cloud Pub/Sub trigger that executes a script or flow when messages are received. - properties: *ref_248 - required: *ref_249 + properties: *ref_250 + required: *ref_251 SubscriptionMode: type: string - enum: *ref_246 + enum: *ref_248 description: >- The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new @@ -34146,30 +34825,68 @@ components: GcpTriggerData: type: object description: Data for creating or updating a Google Cloud Pub/Sub trigger. - properties: *ref_241 - required: *ref_242 + properties: *ref_243 + required: *ref_244 GetAllTopicSubscription: type: object - properties: *ref_447 - required: *ref_448 + properties: *ref_457 + required: *ref_458 DeleteGcpSubscription: type: object - properties: *ref_449 - required: *ref_450 + properties: *ref_459 + required: *ref_460 + AzureMode: + type: string + enum: *ref_254 + description: Azure Event Grid trigger mode. + AzureArmResource: + type: object + description: An ARM resource the service principal can see. + properties: *ref_258 + required: *ref_259 + AzureDeleteSubscription: + type: object + properties: *ref_461 + required: *ref_462 + AzureTrigger: + allOf: *ref_255 + type: object + description: >- + An Azure Event Grid trigger that executes a script or flow when events + arrive. + properties: *ref_256 + required: *ref_257 + AzureTriggerData: + type: object + description: Data for creating or updating an Azure Event Grid trigger. + properties: *ref_252 + required: *ref_253 + TestAzureConnection: + type: object + properties: *ref_463 + required: *ref_464 + AzureListTopics: + type: object + properties: *ref_465 + required: *ref_466 + AzureListSubscriptions: + type: object + properties: *ref_467 + required: *ref_468 AwsAuthResourceType: type: string - enum: *ref_222 + enum: *ref_224 SqsTrigger: - allOf: *ref_223 + allOf: *ref_225 type: object - properties: *ref_224 - required: *ref_225 + properties: *ref_226 + required: *ref_227 LoggedWizardStatus: type: string enum: *ref_21 CustomInstanceDbLogs: type: object - properties: *ref_451 + properties: *ref_469 CustomInstanceDbTag: type: string enum: *ref_22 @@ -34179,108 +34896,108 @@ components: properties: *ref_24 NewSqsTrigger: type: object - properties: *ref_452 - required: *ref_453 + properties: *ref_470 + required: *ref_471 EditSqsTrigger: type: object - properties: *ref_454 - required: *ref_455 + properties: *ref_472 + required: *ref_473 Slot: type: object - properties: *ref_250 + properties: *ref_260 SlotList: type: object - properties: *ref_456 + properties: *ref_474 PublicationData: type: object - properties: *ref_254 - required: *ref_255 + properties: *ref_264 + required: *ref_265 TableToTrack: type: array - items: *ref_457 + items: *ref_475 Relations: type: object - properties: *ref_251 - required: *ref_252 + properties: *ref_261 + required: *ref_262 Language: type: string - enum: *ref_458 + enum: *ref_476 TemplateScript: type: object - properties: *ref_459 - required: *ref_460 + properties: *ref_477 + required: *ref_478 PostgresTrigger: - allOf: *ref_256 - type: object - properties: *ref_257 - required: *ref_258 - NewPostgresTrigger: - type: object - properties: *ref_461 - required: *ref_462 - EditPostgresTrigger: - type: object - properties: *ref_463 - required: *ref_464 - KafkaTrigger: - allOf: *ref_216 - type: object - properties: *ref_217 - required: *ref_218 - NewKafkaTrigger: - type: object - properties: *ref_465 - required: *ref_466 - EditKafkaTrigger: - type: object - properties: *ref_467 - required: *ref_468 - NatsTrigger: - allOf: *ref_219 - type: object - properties: *ref_220 - required: *ref_221 - NewNatsTrigger: - type: object - properties: *ref_469 - required: *ref_470 - EditNatsTrigger: - type: object - properties: *ref_471 - required: *ref_472 - EmailTrigger: - allOf: *ref_259 - type: object - properties: *ref_260 - required: *ref_261 - NewEmailTrigger: - type: object - properties: *ref_473 - required: *ref_474 - EditEmailTrigger: - type: object - properties: *ref_475 - required: *ref_476 - Group: + allOf: *ref_266 type: object properties: *ref_267 required: *ref_268 - InstanceGroup: - type: object - required: *ref_477 - properties: *ref_478 - InstanceGroupWithWorkspaces: - type: object - required: *ref_263 - properties: *ref_264 - WorkspaceInfo: + NewPostgresTrigger: type: object properties: *ref_479 required: *ref_480 - Folder: + EditPostgresTrigger: + type: object + properties: *ref_481 + required: *ref_482 + KafkaTrigger: + allOf: *ref_218 + type: object + properties: *ref_219 + required: *ref_220 + NewKafkaTrigger: + type: object + properties: *ref_483 + required: *ref_484 + EditKafkaTrigger: + type: object + properties: *ref_485 + required: *ref_486 + NatsTrigger: + allOf: *ref_221 + type: object + properties: *ref_222 + required: *ref_223 + NewNatsTrigger: + type: object + properties: *ref_487 + required: *ref_488 + EditNatsTrigger: + type: object + properties: *ref_489 + required: *ref_490 + EmailTrigger: + allOf: *ref_269 type: object properties: *ref_270 required: *ref_271 + NewEmailTrigger: + type: object + properties: *ref_491 + required: *ref_492 + EditEmailTrigger: + type: object + properties: *ref_493 + required: *ref_494 + Group: + type: object + properties: *ref_277 + required: *ref_278 + InstanceGroup: + type: object + required: *ref_495 + properties: *ref_496 + InstanceGroupWithWorkspaces: + type: object + required: *ref_273 + properties: *ref_274 + WorkspaceInfo: + type: object + properties: *ref_497 + required: *ref_498 + Folder: + type: object + properties: *ref_280 + required: *ref_281 FolderDefaultPermissionedAs: description: > Ordered list of rules applied at create-time when admins or @@ -34288,19 +35005,19 @@ components: `path_glob` matches the item path (relative to the folder root) wins, and its `permissioned_as` is used as the default. type: array - items: *ref_269 + items: *ref_279 WorkerPing: type: object - properties: *ref_481 - required: *ref_482 + properties: *ref_499 + required: *ref_500 UserWorkspaceList: type: object - properties: *ref_483 - required: *ref_484 + properties: *ref_501 + required: *ref_502 CreateWorkspace: type: object - properties: *ref_485 - required: *ref_486 + properties: *ref_503 + required: *ref_504 CreateWorkspaceFork: type: object properties: *ref_19 @@ -34311,15 +35028,15 @@ components: required: *ref_16 DependencyMap: type: object - properties: *ref_487 + properties: *ref_505 DependencyDependent: type: object - properties: *ref_488 - required: *ref_489 + properties: *ref_506 + required: *ref_507 DependentsAmount: type: object - properties: *ref_490 - required: *ref_491 + properties: *ref_508 + required: *ref_509 WorkspaceInvite: type: object properties: *ref_41 @@ -34332,45 +35049,45 @@ components: allOf: *ref_124 ExtraPerms: type: object - additionalProperties: *ref_492 + additionalProperties: *ref_510 FlowMetadata: type: object - properties: *ref_493 - required: *ref_494 + properties: *ref_511 + required: *ref_512 OpenFlowWPath: allOf: *ref_126 FlowPreview: type: object - properties: *ref_147 - required: *ref_148 + properties: *ref_151 + required: *ref_152 RestartedFrom: type: object - properties: *ref_495 + properties: *ref_513 Policy: type: object properties: *ref_127 ListableApp: type: object - properties: *ref_496 - required: *ref_497 + properties: *ref_514 + required: *ref_515 ScopeDefinition: type: object - properties: *ref_498 - required: *ref_499 + properties: *ref_516 + required: *ref_517 ScopeDomain: type: object - properties: *ref_500 - required: *ref_501 + properties: *ref_518 + required: *ref_519 ListableRawApp: type: object - properties: *ref_502 - required: *ref_503 + properties: *ref_520 + required: *ref_521 AppWithLastVersion: type: object properties: *ref_128 required: *ref_129 AppWithLastVersionWDraft: - allOf: *ref_504 + allOf: *ref_522 AppHistory: type: object properties: *ref_130 @@ -34405,8 +35122,8 @@ components: enum: *ref_93 PolarsClientKwargs: type: object - properties: *ref_281 - required: *ref_282 + properties: *ref_291 + required: *ref_292 LargeFileStorage: type: object properties: *ref_50 @@ -34420,27 +35137,27 @@ components: properties: *ref_54 DataTableSchema: type: object - required: *ref_505 - properties: *ref_506 + required: *ref_523 + properties: *ref_524 DynamicInputData: type: object - properties: *ref_507 - required: *ref_508 + properties: *ref_525 + required: *ref_526 WindmillLargeFile: type: object - properties: *ref_283 - required: *ref_284 + properties: *ref_293 + required: *ref_294 WindmillFileMetadata: type: object - properties: *ref_287 + properties: *ref_297 WindmillFilePreview: type: object - properties: *ref_285 - required: *ref_286 + properties: *ref_295 + required: *ref_296 S3Resource: type: object - properties: *ref_279 - required: *ref_280 + properties: *ref_289 + required: *ref_290 WorkspaceGitSyncSettings: type: object properties: *ref_55 @@ -34452,48 +35169,48 @@ components: properties: *ref_59 S3PermissionRule: type: object - properties: *ref_509 - required: *ref_510 + properties: *ref_527 + required: *ref_528 GitRepositorySettings: type: object properties: *ref_56 required: *ref_57 MetricMetadata: type: object - properties: *ref_511 - required: *ref_512 + properties: *ref_529 + required: *ref_530 ScalarMetric: type: object - properties: *ref_513 - required: *ref_514 + properties: *ref_531 + required: *ref_532 TimeseriesMetric: type: object - properties: *ref_515 - required: *ref_516 + properties: *ref_533 + required: *ref_534 MetricDataPoint: type: object - properties: *ref_517 - required: *ref_518 + properties: *ref_535 + required: *ref_536 RawScriptForDependencies: type: object - properties: *ref_519 - required: *ref_520 + properties: *ref_143 + required: *ref_144 ConcurrencyGroup: type: object - properties: *ref_521 - required: *ref_522 + properties: *ref_537 + required: *ref_538 ExtendedJobs: type: object - properties: *ref_523 - required: *ref_524 + properties: *ref_539 + required: *ref_540 ExportedUser: type: object properties: *ref_9 required: *ref_10 GlobalSetting: type: object - properties: *ref_525 - required: *ref_526 + properties: *ref_541 + required: *ref_542 InstanceConfig: type: object description: >- @@ -34502,35 +35219,35 @@ components: properties: *ref_26 Config: type: object - properties: *ref_527 - required: *ref_528 + properties: *ref_543 + required: *ref_544 ExportedInstanceGroup: type: object - properties: *ref_265 - required: *ref_266 + properties: *ref_275 + required: *ref_276 JobSearchHit: type: object - properties: *ref_529 + properties: *ref_545 LogSearchHit: type: object - properties: *ref_530 + properties: *ref_546 AutoscalingEvent: type: object - properties: *ref_531 + properties: *ref_547 CriticalAlert: type: object properties: *ref_63 CaptureTriggerKind: type: string - enum: *ref_272 + enum: *ref_282 Capture: type: object - properties: *ref_273 - required: *ref_274 + properties: *ref_283 + required: *ref_284 CaptureConfig: type: object - properties: *ref_532 - required: *ref_533 + properties: *ref_548 + required: *ref_549 OperatorSettings: nullable: true type: object @@ -34538,16 +35255,16 @@ components: properties: *ref_38 WorkspaceComparison: type: object - required: *ref_534 - properties: *ref_535 + required: *ref_550 + properties: *ref_551 WorkspaceItemDiff: type: object - required: *ref_536 - properties: *ref_537 + required: *ref_552 + properties: *ref_553 CompareSummary: type: object - required: *ref_538 - properties: *ref_539 + required: *ref_554 + properties: *ref_555 TeamInfo: type: object required: @@ -34568,12 +35285,12 @@ components: description: List of channels within the team items: type: object - required: &ref_540 + required: &ref_556 - channel_id - channel_name - tenant_id - service_url - properties: &ref_541 + properties: &ref_557 channel_id: type: string description: The unique identifier of the channel @@ -34593,11 +35310,11 @@ components: https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/ ChannelInfo: type: object - required: *ref_540 - properties: *ref_541 + required: *ref_556 + properties: *ref_557 GithubInstallations: type: array - items: *ref_542 + items: *ref_558 WorkspaceGithubInstallation: type: object properties: @@ -34638,14 +35355,14 @@ components: minLength: 1 AssetUsageKind: type: string - enum: *ref_292 + enum: *ref_302 AssetUsageAccessType: type: string - enum: *ref_291 + enum: *ref_301 nullable: true AssetKind: type: string - enum: *ref_290 + enum: *ref_300 Asset: type: object properties: @@ -34653,26 +35370,26 @@ components: type: string kind: type: string - enum: *ref_290 + enum: *ref_300 required: - path - kind Volume: type: object - required: *ref_543 - properties: *ref_544 + required: *ref_559 + properties: *ref_560 ProtectionRuleset: type: object description: A workspace protection rule defining restrictions and bypass permissions - required: *ref_545 - properties: *ref_546 + required: *ref_561 + properties: *ref_562 ProtectionRules: type: array description: Configuration of protection restrictions items: *ref_64 ProtectionRuleKind: type: string - enum: *ref_547 + enum: *ref_563 RuleBypasserGroups: type: array description: Groups that can bypass this ruleset @@ -34683,12 +35400,12 @@ components: items: *ref_66 DeploymentRequestEligibleDeployer: type: object - required: *ref_548 - properties: *ref_549 + required: *ref_564 + properties: *ref_565 DeploymentRequestAssignee: type: object - required: *ref_550 - properties: *ref_551 + required: *ref_566 + properties: *ref_567 DeploymentRequestComment: type: object required: *ref_69 @@ -34703,27 +35420,27 @@ components: required: *ref_72 NativeServiceName: type: string - enum: *ref_226 + enum: *ref_228 NativeTrigger: type: object description: A native trigger stored in Windmill - properties: *ref_552 - required: *ref_553 + properties: *ref_568 + required: *ref_569 NativeTriggerWithExternal: type: object description: >- Full trigger response containing both Windmill data and external service data - properties: *ref_554 - required: *ref_555 + properties: *ref_570 + required: *ref_571 WorkspaceIntegrations: type: object - properties: *ref_556 - required: *ref_557 + properties: *ref_572 + required: *ref_573 WorkspaceOAuthConfig: type: object - properties: *ref_227 - required: *ref_228 + properties: *ref_229 + required: *ref_230 WebhookEvent: type: object properties: @@ -34734,7 +35451,7 @@ components: request_type: type: string description: The type of webhook request (define possible values here) - enum: &ref_558 + enum: &ref_574 - async - sync required: @@ -34743,21 +35460,21 @@ components: WebhookRequestType: type: string description: The type of webhook request (define possible values here) - enum: *ref_558 + enum: *ref_574 RedirectUri: type: object - properties: *ref_229 - required: *ref_230 + properties: *ref_231 + required: *ref_232 NativeTriggerData: type: object description: Data for creating or updating a native trigger - properties: *ref_231 - required: *ref_232 + properties: *ref_233 + required: *ref_234 CreateTriggerResponse: type: object description: Response returned when a native trigger is created - properties: *ref_559 - required: *ref_560 + properties: *ref_575 + required: *ref_576 SyncResult: type: object properties: @@ -34781,24 +35498,28 @@ components: - total_windmill NextCloudEventType: type: object - properties: *ref_561 - required: *ref_562 + properties: *ref_577 + required: *ref_578 GoogleCalendarEntry: type: object - properties: *ref_563 - required: *ref_564 + properties: *ref_579 + required: *ref_580 GoogleDriveFile: type: object - properties: *ref_565 - required: *ref_566 + properties: *ref_581 + required: *ref_582 GoogleDriveFilesResponse: type: object - properties: *ref_567 - required: *ref_568 + properties: *ref_583 + required: *ref_584 SharedDriveEntry: type: object - properties: *ref_569 - required: *ref_570 + properties: *ref_585 + required: *ref_586 + GithubRepoEntry: + type: object + properties: *ref_587 + required: *ref_588 schemas-StaticTransform: type: object description: >- @@ -34834,22 +35555,22 @@ components: Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms - properties: *ref_312 - required: *ref_313 + properties: *ref_322 + required: *ref_323 schemas-PathScript: type: object description: >- Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code - properties: *ref_314 - required: *ref_315 + properties: *ref_324 + required: *ref_325 schemas-PathFlow: type: object description: >- Reference to an existing flow by path. Use this to call another flow as a subflow - properties: *ref_316 - required: *ref_317 + properties: *ref_326 + required: *ref_327 schemas-FlowModule: type: object description: A single step in a flow. Can be a script, subflow, loop, or branch @@ -34862,96 +35583,96 @@ components: 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations - properties: *ref_318 - required: *ref_319 + properties: *ref_328 + required: *ref_329 schemas-WhileloopFlow: type: object description: >- Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination - properties: *ref_320 - required: *ref_321 + properties: *ref_330 + required: *ref_331 schemas-BranchOne: type: object description: >- Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes - properties: *ref_322 - required: *ref_323 + properties: *ref_332 + required: *ref_333 schemas-BranchAll: type: object description: >- Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently - properties: *ref_324 - required: *ref_325 + properties: *ref_334 + required: *ref_335 schemas-Identity: type: object description: >- Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder - properties: *ref_326 - required: *ref_327 + properties: *ref_336 + required: *ref_337 AIProviderKind: type: string description: Supported AI provider types - enum: *ref_305 + enum: *ref_315 schemas-ProviderConfig: type: object description: >- Complete AI provider configuration with resource reference and model selection - properties: *ref_571 - required: *ref_572 + properties: *ref_589 + required: *ref_590 StaticProviderTransform: type: object description: Static provider configuration passed directly to the AI agent - properties: *ref_573 - required: *ref_574 + properties: *ref_591 + required: *ref_592 ProviderTransform: description: >- Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined - oneOf: *ref_330 - discriminator: *ref_331 + oneOf: *ref_340 + discriminator: *ref_341 MemoryOff: type: object description: No conversation memory/context - properties: *ref_306 - required: *ref_307 + properties: *ref_316 + required: *ref_317 MemoryAuto: type: object description: Automatic context management - properties: *ref_308 - required: *ref_309 + properties: *ref_318 + required: *ref_319 MemoryMessage: type: object description: A single message in conversation history - properties: *ref_575 - required: *ref_576 + properties: *ref_593 + required: *ref_594 MemoryManual: type: object description: Explicit message history - properties: *ref_310 - required: *ref_311 + properties: *ref_320 + required: *ref_321 schemas-MemoryConfig: description: Conversation memory configuration - oneOf: *ref_577 - discriminator: *ref_578 + oneOf: *ref_595 + discriminator: *ref_596 StaticMemoryTransform: type: object description: Static memory configuration passed directly to the AI agent - properties: *ref_579 - required: *ref_580 + properties: *ref_597 + required: *ref_598 MemoryTransform: description: >- Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined - oneOf: *ref_332 - discriminator: *ref_333 + oneOf: *ref_342 + discriminator: *ref_343 schemas-FlowModuleValue: description: >- The actual implementation of a flow step. Can be a script (inline or @@ -34962,41 +35683,41 @@ components: description: >- A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module - allOf: *ref_581 + allOf: *ref_599 McpToolValue: type: object description: >- Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers - properties: *ref_582 - required: *ref_583 + properties: *ref_600 + required: *ref_601 WebsearchToolValue: type: object description: >- A tool implemented as a websearch tool. The AI can call this like any other websearch tool - properties: *ref_584 - required: *ref_585 + properties: *ref_602 + required: *ref_603 ToolValue: description: >- The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference - oneOf: *ref_586 - discriminator: *ref_587 + oneOf: *ref_604 + discriminator: *ref_605 AgentTool: type: object description: >- A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool - properties: *ref_334 - required: *ref_335 + properties: *ref_344 + required: *ref_345 schemas-AiAgent: type: object description: >- AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task - properties: *ref_328 - required: *ref_329 + properties: *ref_338 + required: *ref_339 schemas-StopAfterIf: type: object description: Early termination condition for a module @@ -35005,17 +35726,17 @@ components: RetryIf: type: object description: Conditional retry based on error or result - properties: *ref_192 - required: *ref_193 + properties: *ref_194 + required: *ref_195 schemas-Retry: type: object description: Retry configuration for failed module executions - properties: *ref_304 + properties: *ref_314 schemas-FlowNote: type: object description: A sticky note attached to a flow for documentation and annotation - properties: *ref_143 - required: *ref_144 + properties: *ref_145 + required: *ref_146 FlowGroup: type: object description: >- @@ -35024,16 +35745,16 @@ components: flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id. - properties: *ref_145 - required: *ref_146 + properties: *ref_147 + required: *ref_148 schemas-FlowValue: type: object description: >- The flow structure containing modules and optional preprocessor/failure handlers - properties: *ref_588 - required: *ref_589 + properties: *ref_606 + required: *ref_607 schemas-FlowStatusModule: type: object - properties: *ref_149 - required: *ref_150 + properties: *ref_153 + required: *ref_154 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 5f688d23b2..f1318e8569 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.688.0 + version: 1.690.0 title: Windmill API contact: @@ -4575,6 +4575,8 @@ paths: type: boolean gcp_used: type: boolean + azure_used: + type: boolean sqs_used: type: boolean email_used: @@ -4593,6 +4595,7 @@ paths: - postgres_used - mqtt_used - gcp_used + - azure_used - sqs_used - email_used - nextcloud_used @@ -5963,6 +5966,9 @@ paths: - type saml: type: string + auto_login: + type: string + description: provider type to auto-redirect to on login (oauth key or "saml") required: - oauth @@ -7406,6 +7412,9 @@ paths: /w/{workspace}/scripts/create: post: summary: create script + description: | + Creates a new script when the path does not already exist. + Creates a new version of an existing script when called with the same path and the current `parent_hash`. operationId: createScript x-mcp-tool: true x-mcp-instructions: "To create a script, specify the path (e.g., 'f/my_folder/my_script'), the content (source code), and the language. For TypeScript, use 'bun' unless deno-specific APIs are needed." @@ -9451,13 +9460,13 @@ paths: schema: type: string format: uuid - - name: after_id - description: id to fetch only the messages after that id + - name: after_seq + description: Message sequence cursor to fetch only the messages after that cursor in: query required: false schema: - type: string - format: uuid + type: integer + format: int64 responses: "200": description: conversation messages @@ -11687,6 +11696,11 @@ paths: in: query schema: type: boolean + - name: approval_token + in: query + description: Approval token granting read access to the job when not logged in. The token must be the one issued for this job's flow (i.e. the flow id used when generating the approval URL). + schema: + type: string responses: "200": description: job details @@ -15211,6 +15225,296 @@ paths: items: type: string + /w/{workspace}/azure_triggers/create: + post: + summary: create an Azure Event Grid trigger + operationId: createAzureTrigger + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AzureTriggerData" + responses: + "201": + description: azure trigger created + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/update/{path}: + post: + summary: update an Azure Event Grid trigger + operationId: updateAzureTrigger + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AzureTriggerData" + responses: + "200": + description: azure trigger updated + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/delete/{path}: + delete: + summary: delete an Azure Event Grid trigger + operationId: deleteAzureTrigger + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: azure trigger deleted + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/get/{path}: + get: + summary: get an Azure Event Grid trigger + operationId: getAzureTrigger + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: azure trigger + content: + application/json: + schema: + $ref: "#/components/schemas/AzureTrigger" + + /w/{workspace}/azure_triggers/list: + get: + summary: list azure triggers + operationId: listAzureTriggers + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + - name: path + description: filter by exact path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + "200": + description: azure trigger list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AzureTrigger" + + /w/{workspace}/azure_triggers/exists/{path}: + get: + summary: check whether an azure trigger exists + operationId: existsAzureTrigger + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: true/false + content: + application/json: + schema: + type: boolean + + /w/{workspace}/azure_triggers/setmode/{path}: + post: + summary: set azure trigger mode + operationId: setAzureTriggerMode + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + mode: + $ref: "#/components/schemas/TriggerMode" + required: + - mode + responses: + "200": + description: trigger mode updated + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/test: + post: + summary: test Azure service principal connection + operationId: testAzureConnection + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TestAzureConnection" + responses: + "200": + description: connection successful + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/namespaces/topics/list/{path}: + post: + summary: list topics under an Event Grid Namespace + operationId: listAzureNamespaceTopics + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AzureListTopics" + responses: + "200": + description: topic list + content: + application/json: + schema: + type: array + items: + type: object + + /w/{workspace}/azure_triggers/namespaces/subscriptions/list/{path}: + post: + summary: list subscriptions under a Namespace topic + operationId: listAzureNamespaceSubscriptions + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AzureListSubscriptions" + responses: + "200": + description: subscription list + content: + application/json: + schema: + type: array + items: + type: object + + /w/{workspace}/azure_triggers/subscriptions/delete/{path}: + delete: + summary: delete an Event Grid subscription on Azure + operationId: deleteAzureSubscription + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AzureDeleteSubscription" + responses: + "200": + description: subscription deleted + content: + text/plain: + schema: + type: string + + /w/{workspace}/azure_triggers/namespaces/list/{path}: + post: + summary: list Event Grid Namespaces the service principal can access + operationId: listAzureNamespaces + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: namespace list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AzureArmResource" + + /w/{workspace}/azure_triggers/basic/topics/list/{path}: + post: + summary: list Basic Event Grid topics + system topics the service principal can access + operationId: listAzureBasicTopics + tags: + - azure_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: topic list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AzureArmResource" + /w/{workspace}/postgres_triggers/postgres/version/{path}: get: summary: get postgres version @@ -17091,6 +17395,7 @@ paths: postgres_trigger, mqtt_trigger, gcp_trigger, + azure_trigger, sqs_trigger, email_trigger, volume, @@ -17137,6 +17442,7 @@ paths: postgres_trigger, mqtt_trigger, gcp_trigger, + azure_trigger, sqs_trigger, email_trigger, volume, @@ -17194,6 +17500,7 @@ paths: postgres_trigger, mqtt_trigger, gcp_trigger, + azure_trigger, sqs_trigger, email_trigger, volume, @@ -20341,7 +20648,7 @@ components: FlowConversationMessage: type: object - required: [id, conversation_id, message_type, content, created_at] + required: [id, conversation_id, message_type, content, created_at, created_seq] properties: id: type: string @@ -20367,6 +20674,10 @@ components: type: string format: date-time description: When the message was created + created_seq: + type: integer + format: int64 + description: Monotonic cursor assigned when the message is inserted step_name: type: string description: The step name that produced that message @@ -22887,6 +23198,7 @@ components: - mqtt - sqs - gcp + - azure - google - github @@ -23374,6 +23686,8 @@ components: type: number gcp_count: type: number + azure_count: + type: number sqs_count: type: number nextcloud_count: @@ -24048,6 +24362,171 @@ components: required: - subscription_id + AzureMode: + type: string + enum: + - basic_push + - namespace_push + - namespace_pull + description: "Azure Event Grid trigger mode." + + AzureArmResource: + type: object + description: "An ARM resource the service principal can see." + properties: + id: + type: string + name: + type: string + location: + type: string + type: + type: string + required: + - id + - name + - type + + AzureDeleteSubscription: + type: object + properties: + azure_mode: + $ref: "#/components/schemas/AzureMode" + scope_resource_id: + type: string + topic_name: + type: string + nullable: true + subscription_name: + type: string + required: + - azure_mode + - scope_resource_id + - subscription_name + + AzureTrigger: + allOf: + - $ref: "#/components/schemas/TriggerExtraProperty" + type: object + description: "An Azure Event Grid trigger that executes a script or flow when events arrive." + properties: + azure_resource_path: + type: string + azure_mode: + $ref: "#/components/schemas/AzureMode" + scope_resource_id: + type: string + description: "ARM resource ID of the topic (basic) or namespace (namespace modes)." + topic_name: + type: string + nullable: true + description: "Topic name within the namespace (namespace modes only)." + subscription_name: + type: string + event_type_filters: + type: array + items: + type: string + nullable: true + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + error_handler_path: + type: string + error_handler_args: + $ref: "#/components/schemas/ScriptArgs" + retry: + $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" + required: + - azure_resource_path + - azure_mode + - scope_resource_id + - subscription_name + + AzureTriggerData: + type: object + description: "Data for creating or updating an Azure Event Grid trigger." + properties: + azure_resource_path: + type: string + azure_mode: + $ref: "#/components/schemas/AzureMode" + scope_resource_id: + type: string + topic_name: + type: string + nullable: true + subscription_name: + type: string + base_endpoint: + type: string + description: "Base URL for push delivery endpoints (push modes only)." + event_type_filters: + type: array + items: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + mode: + $ref: "#/components/schemas/TriggerMode" + error_handler_path: + type: string + error_handler_args: + $ref: "#/components/schemas/ScriptArgs" + retry: + $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" + permissioned_as: + type: string + preserve_permissioned_as: + type: boolean + labels: + type: array + items: + type: string + required: + - path + - script_path + - is_flow + - azure_resource_path + - azure_mode + - scope_resource_id + - subscription_name + + TestAzureConnection: + type: object + properties: + azure_resource_path: + type: string + required: + - azure_resource_path + + AzureListTopics: + type: object + properties: + scope_resource_id: + type: string + required: + - scope_resource_id + + AzureListSubscriptions: + type: object + properties: + scope_resource_id: + type: string + topic_name: + type: string + required: + - scope_resource_id + - topic_name + AwsAuthResourceType: type: string enum: @@ -26349,6 +26828,7 @@ components: sqs, mqtt, gcp, + azure, email, ] diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index c8ddea37d2..bf98ae460d 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -114,7 +114,8 @@ pub fn workspaced_unauthed_service() -> Router { #[cfg(any( feature = "http_trigger", - all(feature = "enterprise", feature = "gcp_trigger") + all(feature = "enterprise", feature = "gcp_trigger"), + all(feature = "enterprise", feature = "azure_trigger") ))] { #[cfg(feature = "http_trigger")] @@ -125,12 +126,16 @@ pub fn workspaced_unauthed_service() -> Router { #[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))] let router = router.route("/gcp/{runnable_kind}/{*path}", post(gcp_payload)); + #[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] + let router = router.route("/azure/{runnable_kind}/{*path}", post(azure_payload)); + router } #[cfg(not(any( feature = "http_trigger", - all(feature = "enterprise", feature = "gcp_trigger") + all(feature = "enterprise", feature = "gcp_trigger"), + all(feature = "enterprise", feature = "azure_trigger") )))] { router @@ -170,6 +175,26 @@ pub struct SqsTriggerConfig { pub aws_auth_resource_type: AwsAuthResourceType, } +#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] +#[derive(Debug, Serialize, Deserialize)] +pub struct AzureTriggerConfig { + pub azure_resource_path: String, + pub azure_mode: crate::triggers::azure::AzureMode, + pub scope_resource_id: String, + #[serde(default, deserialize_with = "empty_as_none")] + pub topic_name: Option, + pub subscription_name: String, + #[serde(default, deserialize_with = "empty_as_none")] + pub base_endpoint: Option, + #[serde(default)] + pub event_type_filters: Option>, + /// Server-managed. Populated by `set_azure_trigger_config` after + /// `manage_azure_subscription` regenerates the secret; skipped on + /// (de)serialization so clients never see or send it. + #[serde(skip, default)] + pub push_auth_config: Option, +} + #[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))] #[derive(Debug, Serialize, Deserialize)] pub struct GcpTriggerConfig { @@ -248,6 +273,8 @@ enum TriggerConfig { Mqtt(MqttTriggerConfig), #[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))] Gcp(GcpTriggerConfig), + #[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] + Azure(AzureTriggerConfig), #[cfg(all(feature = "enterprise", feature = "smtp", feature = "private"))] Email(EmailTriggerConfig), } @@ -419,6 +446,78 @@ async fn set_gcp_trigger_config( Ok(capture_config) } +#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] +async fn set_azure_trigger_config( + w_id: &str, + authed: ApiAuthed, + db: &DB, + mut capture_config: NewCaptureConfig, +) -> Result { + use crate::triggers::azure::{manage_azure_subscription, AzureConfigRequest}; + + let Some(TriggerConfig::Azure(azure_config)) = capture_config.trigger_config else { + return Err(Error::BadRequest( + "Invalid Azure Event Grid config".to_string(), + )); + }; + + // Suffix subscription name so capture never clobbers the deployed trigger's + // subscription. Azure allows [A-Za-z0-9-]{3,50}; reserve 11 chars for + // "-wm-capture" (mirrors Kafka's `_wm_capture` convention — hyphen since + // Azure names disallow underscores). + let mut sub_name = azure_config.subscription_name; + if sub_name.len() > 39 { + sub_name.truncate(39); + } + sub_name.push_str("-wm-capture"); + + let mut req = AzureConfigRequest { + azure_resource_path: azure_config.azure_resource_path, + azure_mode: azure_config.azure_mode, + scope_resource_id: azure_config.scope_resource_id, + topic_name: azure_config.topic_name, + subscription_name: sub_name, + base_endpoint: azure_config.base_endpoint, + event_type_filters: azure_config.event_type_filters, + push_auth_config: azure_config.push_auth_config, + }; + + manage_azure_subscription( + authed, + db, + w_id, + &mut req, + &capture_config.path, + capture_config.is_flow, + false, + ) + .await?; + + capture_config.trigger_config = Some(TriggerConfig::Azure(AzureTriggerConfig { + azure_resource_path: req.azure_resource_path, + azure_mode: req.azure_mode, + scope_resource_id: req.scope_resource_id, + topic_name: req.topic_name, + subscription_name: req.subscription_name, + base_endpoint: req.base_endpoint, + event_type_filters: req.event_type_filters, + push_auth_config: req.push_auth_config, + })); + + Ok(capture_config) +} + +#[inline] +#[cfg(not(all(feature = "enterprise", feature = "azure_trigger", feature = "private")))] +async fn set_azure_trigger_config( + _w_id: &str, + _authed: ApiAuthed, + _db: &DB, + capture_config: NewCaptureConfig, +) -> Result { + Ok(capture_config) +} + async fn set_config( authed: ApiAuthed, Extension(user_db): Extension, @@ -431,6 +530,7 @@ async fn set_config( set_postgres_trigger_config(&w_id, authed.clone(), &db, user_db.clone(), nc).await? } TriggerKind::Gcp => set_gcp_trigger_config(&w_id, authed.clone(), &db, nc).await?, + TriggerKind::Azure => set_azure_trigger_config(&w_id, authed.clone(), &db, nc).await?, _ => nc, }; @@ -960,6 +1060,70 @@ async fn gcp_payload( Ok(StatusCode::NO_CONTENT) } +#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] +async fn azure_payload( + Extension(db): Extension, + Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, String)>, + headers: HeaderMap, + request: Request, +) -> Result { + use crate::triggers::azure::{ + cloud_event_to_args, process_azure_push_request, validate_push_secret, AzureTrigger, + PushOutcome, + }; + use crate::triggers::trigger_helpers::TriggerJobArgs; + + let is_flow = matches!(runnable_kind, RunnableKind::Flow); + let (azure_trigger_config, owner, _email): (AzureTriggerConfig, _, _) = + get_capture_trigger_config_and_owner(&db, &w_id, &path, is_flow, &TriggerKind::Azure) + .await?; + + // Basic handshake posts don't carry the secret header; let them through. + let is_classic_handshake = headers + .get("aeg-event-type") + .and_then(|h| h.to_str().ok()) + .map(|v| v.eq_ignore_ascii_case("SubscriptionValidation")) + .unwrap_or(false); + + if !is_classic_handshake { + let dc = azure_trigger_config + .push_auth_config + .as_ref() + .ok_or_else(|| { + Error::NotAuthorized("azure capture missing push_auth_config".to_string()) + })?; + validate_push_secret(&headers, dc)?; + } + + let outcome = process_azure_push_request(headers, request).await?; + let (cloud_events, headers_map) = match outcome { + PushOutcome::Handshake(_) => { + // Capture path doesn't need to echo validation response — return 200. + return Ok(StatusCode::OK); + } + PushOutcome::Events { cloud_events, headers } => (cloud_events, headers), + }; + + for event in cloud_events { + let (payload, trigger_info) = cloud_event_to_args(&event, &headers_map); + let (main_args, preprocessor_args) = + AzureTrigger::build_capture_payloads(&payload, trigger_info); + let _ = insert_capture_payload( + &db, + &w_id, + &path, + is_flow, + &TriggerKind::Azure, + main_args, + preprocessor_args, + &owner, + ) + .await?; + } + + Ok(StatusCode::NO_CONTENT) +} + #[cfg(feature = "http_trigger")] async fn http_payload( Extension(db): Extension, diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 096aaeb969..984761510b 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -935,6 +935,7 @@ async fn list_selected_job_groups( struct GetJobQuery { pub no_logs: Option, pub no_code: Option, + pub approval_token: Option, } async fn get_job( @@ -942,16 +943,36 @@ async fn get_job( opt_tokened: OptTokened, Extension(db): Extension, Path((w_id, id)): Path<(String, Uuid)>, - Query(GetJobQuery { no_logs, no_code }): Query, + Query(GetJobQuery { no_logs, no_code, approval_token }): Query, ) -> error::Result { let tags = opt_authed .as_ref() .map(|authed| get_scope_tags(authed)) .flatten(); - let mut get = GetQuery::new() - .with_auth(&opt_authed) - .with_in_tags(tags.as_ref()); + // A valid approval token on the same (workspace, flow) grants read access + // so the approval page can render job metadata without login. The approval + // URL usually carries the flow id directly — try that first and only + // resolve the parent flow if the direct check fails. + let has_valid_approval_token = if let Some(ref token) = approval_token { + if validate_approval_token(&db, token, id, &w_id).await.is_ok() { + true + } else if let Ok(flow_id) = get_flow_id_for_job(&db, id).await { + flow_id != id + && validate_approval_token(&db, token, flow_id, &w_id) + .await + .is_ok() + } else { + false + } + } else { + false + }; + + let mut get = GetQuery::new().with_in_tags(tags.as_ref()); + if !has_valid_approval_token { + get = get.with_auth(&opt_authed); + } if no_code.unwrap_or(false) { get = get.without_code(); diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index c11f89a1c4..6cebf7aa15 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -216,6 +216,8 @@ pub use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT; pub use windmill_common::utils::{COOKIE_DOMAIN, IS_SECURE}; +pub use windmill_api_debug::reload_debug_signing_key; + #[cfg(feature = "oauth2")] pub use windmill_oauth::OAUTH_CLIENTS; @@ -898,6 +900,24 @@ pub async fn run_server( Router::new() } }) + .nest("/azure/w/{workspace_id}", { + #[cfg(all( + feature = "enterprise", + feature = "azure_trigger", + feature = "private" + ))] + { + triggers::azure::handler_oss::azure_push_route_handler() + } + #[cfg(not(all( + feature = "enterprise", + feature = "azure_trigger", + feature = "private" + )))] + { + Router::new() + } + }) .route("/version", get(git_v)) .nest("/health/status", health::status_service()) .route("/min_keep_alive_version", get(min_keep_alive_version)) diff --git a/backend/windmill-api/src/mcp/auto_generated_endpoints.rs b/backend/windmill-api/src/mcp/auto_generated_endpoints.rs index c9295ea8e9..fc3b1164f5 100644 --- a/backend/windmill-api/src/mcp/auto_generated_endpoints.rs +++ b/backend/windmill-api/src/mcp/auto_generated_endpoints.rs @@ -78,6 +78,12 @@ pub fn all_tools() -> Vec { "type": "string", "description": "The expiration date of the variable", "format": "date-time" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -157,6 +163,12 @@ pub fn all_tools() -> Vec { "type": "string", "description": "The new description of the variable" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, "path__body": { "type": "string", "description": "The path to the variable (body parameter)" @@ -244,6 +256,10 @@ pub fn all_tools() -> Vec { "per_page": { "type": "integer", "description": "number of items to return for a given page (default 30, max 100)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -287,6 +303,12 @@ pub fn all_tools() -> Vec { "resource_type": { "type": "string", "description": "The resource_type associated with the resource" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -355,6 +377,12 @@ pub fn all_tools() -> Vec { "type": "string", "description": "The new resource_type to be associated with the resource" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, "path__body": { "type": "string", "description": "The path to the resource (body parameter)" @@ -437,6 +465,10 @@ pub fn all_tools() -> Vec { "broad_filter": { "type": "string", "description": "broad search across multiple fields (case-insensitive substring match)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -544,6 +576,10 @@ pub fn all_tools() -> Vec { "dedicated_worker": { "type": "boolean", "description": "(default regardless)\nIf true, show only scripts with dedicated_worker enabled.\nIf false, show only scripts with dedicated_worker disabled.\n" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -555,7 +591,8 @@ pub fn all_tools() -> Vec { }, EndpointTool { name: Cow::Borrowed("createScript"), - description: Cow::Borrowed("create script"), + description: Cow::Borrowed("create script: Creates a new script when the path does not already exist. +Creates a new version of an existing script when called with the same path and the current `parent_hash`"), instructions: Cow::Borrowed("To create a script, specify the path (e.g., 'f/my_folder/my_script'), the content (source code), and the language. For TypeScript, use 'bun' unless deno-specific APIs are needed."), path: Cow::Borrowed("/w/{workspace}/scripts/create"), method: Cow::Borrowed("POST"), @@ -578,7 +615,7 @@ pub fn all_tools() -> Vec { }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "kind": { "type": "string", @@ -772,6 +809,10 @@ pub fn all_tools() -> Vec { "dedicated_worker": { "type": "boolean", "description": "(default regardless)\nIf true, show only flows with dedicated_worker enabled.\nIf false, show only flows with dedicated_worker disabled.\n" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -1095,7 +1136,7 @@ pub fn all_tools() -> Vec { }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "tag": { "type": "string" @@ -1127,7 +1168,7 @@ pub fn all_tools() -> Vec { }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "lock": { "type": "string", @@ -1691,6 +1732,12 @@ You should get the schema of the script or flow before creating the schedule to "preserve_permissioned_as": { "type": "boolean", "description": "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -1894,6 +1941,12 @@ You should get the schema of the script or flow before updating the schedule to "type": "boolean", "nullable": true, "description": "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -2001,6 +2054,10 @@ You should get the schema of the script or flow before updating the schedule to "broad_filter": { "type": "string", "description": "broad search across multiple fields (case-insensitive substring match)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] diff --git a/backend/windmill-api/src/oauth2_oss.rs b/backend/windmill-api/src/oauth2_oss.rs index 3eaf179634..abdcc42548 100644 --- a/backend/windmill-api/src/oauth2_oss.rs +++ b/backend/windmill-api/src/oauth2_oss.rs @@ -92,11 +92,12 @@ pub struct TokenResponse { struct Logins { oauth: Vec, saml: Option, + auto_login: Option, } #[cfg(not(feature = "private"))] async fn list_logins() -> error::JsonResult { // Implementation is not open source - return Ok(Json(Logins { oauth: vec![], saml: None })); + return Ok(Json(Logins { oauth: vec![], saml: None, auto_login: None })); } #[allow(unused)] diff --git a/backend/windmill-api/src/offboarding.rs b/backend/windmill-api/src/offboarding.rs index aeee13c5f5..a5d15165a2 100644 --- a/backend/windmill-api/src/offboarding.rs +++ b/backend/windmill-api/src/offboarding.rs @@ -189,6 +189,7 @@ async fn get_offboard_preview( "nats_trigger", "sqs_trigger", "gcp_trigger", + "azure_trigger", "email_trigger", ]; let mut triggers = HashMap::new(); @@ -748,6 +749,7 @@ async fn check_path_conflicts( "nats_trigger", "sqs_trigger", "gcp_trigger", + "azure_trigger", "email_trigger", ]; @@ -1020,6 +1022,7 @@ async fn offboard_user_from_workspace<'c>( "nats_trigger", "sqs_trigger", "gcp_trigger", + "azure_trigger", "email_trigger", ]; diff --git a/backend/windmill-api/src/token.rs b/backend/windmill-api/src/token.rs index 61baf5e3c4..ac4da6497e 100644 --- a/backend/windmill-api/src/token.rs +++ b/backend/windmill-api/src/token.rs @@ -26,6 +26,7 @@ fn build_trigger_scope_domains() -> Vec { ("mqtt_triggers", "MQTT"), ("sqs_triggers", "AWS SQS"), ("gcp_triggers", "GCP Pub/Sub"), + ("azure_triggers", "Azure Event Grid"), ("postgres_triggers", "PostgreSQL"), ("email_triggers", "Email"), ]; diff --git a/backend/windmill-api/src/trash.rs b/backend/windmill-api/src/trash.rs index a4bc105238..83e607a7dc 100644 --- a/backend/windmill-api/src/trash.rs +++ b/backend/windmill-api/src/trash.rs @@ -469,6 +469,7 @@ async fn restore_trigger(tx: &mut sqlx::PgConnection, item: &TrashItemWithData) "mqtt_trigger", "sqs_trigger", "gcp_trigger", + "azure_trigger", "email_trigger", ]; diff --git a/backend/windmill-api/src/triggers/azure/mod.rs b/backend/windmill-api/src/triggers/azure/mod.rs new file mode 100644 index 0000000000..9928a53a1a --- /dev/null +++ b/backend/windmill-api/src/triggers/azure/mod.rs @@ -0,0 +1 @@ +pub use windmill_trigger_azure::*; diff --git a/backend/windmill-api/src/triggers/handler.rs b/backend/windmill-api/src/triggers/handler.rs index c199734794..35b025965d 100644 --- a/backend/windmill-api/src/triggers/handler.rs +++ b/backend/windmill-api/src/triggers/handler.rs @@ -81,6 +81,16 @@ pub fn generate_trigger_routers() -> Router { ); } + #[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] + { + use crate::triggers::azure::AzureTrigger; + + router = router.nest( + AzureTrigger::ROUTE_PREFIX, + complete_trigger_routes(AzureTrigger), + ); + } + #[cfg(feature = "postgres_trigger")] { use crate::triggers::postgres::PostgresTrigger; @@ -135,6 +145,7 @@ pub struct TriggersCount { mqtt_count: i64, sqs_count: i64, gcp_count: i64, + azure_count: i64, nextcloud_count: i64, google_count: i64, github_count: i64, @@ -252,6 +263,17 @@ pub async fn get_triggers_count_internal( #[cfg(not(all(feature = "gcp_trigger", feature = "enterprise", feature = "private")))] let gcp_count = 0; + #[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))] + let azure_count = { + use crate::triggers::azure::AzureTrigger; + let count = AzureTrigger + .trigger_count(&mut tx, w_id, is_flow, path) + .await; + count + }; + #[cfg(not(all(feature = "azure_trigger", feature = "enterprise", feature = "private")))] + let azure_count = 0; + #[cfg(all(feature = "smtp", feature = "enterprise", feature = "private"))] let email_count = { use crate::triggers::email::EmailTrigger; @@ -341,6 +363,7 @@ pub async fn get_triggers_count_internal( postgres_count, mqtt_count, gcp_count, + azure_count, sqs_count, nextcloud_count, google_count, diff --git a/backend/windmill-api/src/triggers/listener.rs b/backend/windmill-api/src/triggers/listener.rs index 3e67f36109..43cabf13e3 100644 --- a/backend/windmill-api/src/triggers/listener.rs +++ b/backend/windmill-api/src/triggers/listener.rs @@ -67,6 +67,14 @@ pub fn start_all_listeners(db: DB, killpill_rx: &tokio::sync::broadcast::Receive listen_to(GcpTrigger, db.clone(), gcp_killpill_rx); } + #[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))] + { + let azure_killpill_rx = killpill_rx.resubscribe(); + use crate::triggers::azure::AzureTrigger; + + listen_to(AzureTrigger, db.clone(), azure_killpill_rx); + } + #[cfg(all(feature = "sqs_trigger", feature = "enterprise", feature = "private"))] { let gcp_killpill_rx = killpill_rx.resubscribe(); diff --git a/backend/windmill-api/src/triggers/mod.rs b/backend/windmill-api/src/triggers/mod.rs index 94c5039775..bc69c66b2b 100644 --- a/backend/windmill-api/src/triggers/mod.rs +++ b/backend/windmill-api/src/triggers/mod.rs @@ -1,4 +1,6 @@ // Concrete trigger submodules (feature-gated) +#[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))] +pub mod azure; #[cfg(all(feature = "smtp", feature = "private"))] pub mod email; #[cfg(all(feature = "gcp_trigger", feature = "enterprise", feature = "private"))] diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 98d46b1421..9673537804 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -23,6 +23,7 @@ use crate::{apps::AppWithLastVersion, db::DB, folders::Folder}; feature = "kafka", feature = "sqs_trigger", feature = "gcp_trigger", + feature = "azure_trigger", feature = "nats", feature = "smtp", ), @@ -237,6 +238,13 @@ where if !preserve_extra_perms && obj.contains_key("extra_perms") { obj.remove("extra_perms"); } + if obj + .get("default_permissioned_as") + .and_then(|v| v.as_array()) + .is_some_and(|a| a.is_empty()) + { + obj.remove("default_permissioned_as"); + } serde_json::to_string_pretty(&obj).ok() }) @@ -761,6 +769,23 @@ pub(crate) async fn tarball_workspace( } } + #[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))] + { + use crate::triggers::azure::AzureTrigger; + let handler = AzureTrigger; + let azure_triggers = handler.list_triggers(&mut *tx, &w_id, None).await?; + + for trigger in azure_triggers { + let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap(); + archive + .write_to_archive( + &trigger_str, + &format!("{}.azure_trigger.json", trigger.base.path), + ) + .await?; + } + } + #[cfg(all(feature = "enterprise", feature = "nats", feature = "private"))] { use crate::triggers::nats::NatsTrigger; diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index f225ec0c20..88a7706b88 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -9,7 +9,7 @@ default = [] enterprise = ["dep:aws-config"] instance_config_schema = ["dep:schemars"] local_reports = ["dep:rsa", "dep:aes-gcm"] -private = ["dep:aws-sdk-rds", "dep:aws-sdk-secretsmanager"] +private = ["dep:aws-sdk-rds", "dep:aws-sdk-secretsmanager", "dep:aws-config"] jemalloc = ["dep:tikv-jemalloc-ctl"] tantivy = [] prometheus = ["dep:prometheus"] diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 836d37971e..6e6486666c 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -50,6 +50,7 @@ pub const HUB_API_SECRET_SETTING: &str = "hub_api_secret"; pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation"; pub const DISABLE_PASSWORD_LOGIN_SETTING: &str = "disable_password_login"; +pub const AUTO_LOGIN_PROVIDER_SETTING: &str = "auto_login_provider"; pub const HUB_BASE_URL_SETTING: &str = "hub_base_url"; pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url"; pub const DISABLE_HUB_SETTING: &str = "disable_hub"; diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 0db5e133ee..5dab417f95 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -157,7 +157,7 @@ pub enum ObjectType { WorkspaceDependencies, } -pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28191/sync-script-to-git-repo-windmill"; +pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28213/sync-script-to-git-repo-windmill"; /// Prefix used to identify fork workspaces. A workspace whose id starts with this string is a /// fork of another workspace. diff --git a/backend/windmill-git-sync/src/lib.rs b/backend/windmill-git-sync/src/lib.rs index dcbcd5bcb2..efbc562cf9 100644 --- a/backend/windmill-git-sync/src/lib.rs +++ b/backend/windmill-git-sync/src/lib.rs @@ -39,6 +39,7 @@ pub enum DeployedObject { MqttTrigger { path: String, parent_path: Option }, SqsTrigger { path: String, parent_path: Option }, GcpTrigger { path: String, parent_path: Option }, + AzureTrigger { path: String, parent_path: Option }, EmailTrigger { path: String, parent_path: Option }, Settings { setting_type: String }, Key { key_type: String }, @@ -67,6 +68,7 @@ impl DeployedObject { DeployedObject::MqttTrigger { path, .. } => path.to_owned(), DeployedObject::SqsTrigger { path, .. } => path.to_owned(), DeployedObject::GcpTrigger { path, .. } => path.to_owned(), + DeployedObject::AzureTrigger { path, .. } => path.to_owned(), DeployedObject::EmailTrigger { path, .. } => path.to_owned(), DeployedObject::Settings { .. } => "settings.yaml".to_string(), DeployedObject::Key { .. } => "encryption_key.yaml".to_string(), @@ -107,6 +109,7 @@ impl DeployedObject { DeployedObject::MqttTrigger { parent_path, .. } => parent_path.to_owned(), DeployedObject::SqsTrigger { parent_path, .. } => parent_path.to_owned(), DeployedObject::GcpTrigger { parent_path, .. } => parent_path.to_owned(), + DeployedObject::AzureTrigger { parent_path, .. } => parent_path.to_owned(), DeployedObject::EmailTrigger { parent_path, .. } => parent_path.to_owned(), DeployedObject::Settings { .. } => None, DeployedObject::Key { .. } => None, @@ -135,6 +138,7 @@ impl DeployedObject { DeployedObject::MqttTrigger { .. } => "mqtt_trigger", DeployedObject::SqsTrigger { .. } => "sqs_trigger", DeployedObject::GcpTrigger { .. } => "gcp_trigger", + DeployedObject::AzureTrigger { .. } => "azure_trigger", DeployedObject::EmailTrigger { .. } => "email_trigger", DeployedObject::Settings { .. } => "settings", DeployedObject::Key { .. } => "key", @@ -369,6 +373,10 @@ mod tests { DeployedObject::GcpTrigger { path: "t".to_string(), parent_path: None }.get_kind(), "gcp_trigger" ); + assert_eq!( + DeployedObject::AzureTrigger { path: "t".to_string(), parent_path: None }.get_kind(), + "azure_trigger" + ); assert_eq!( DeployedObject::EmailTrigger { path: "t".to_string(), parent_path: None }.get_kind(), "email_trigger" diff --git a/backend/windmill-store/Cargo.toml b/backend/windmill-store/Cargo.toml index 9fee538ef6..cb1e41ad05 100644 --- a/backend/windmill-store/Cargo.toml +++ b/backend/windmill-store/Cargo.toml @@ -21,6 +21,7 @@ postgres_trigger = [] mqtt_trigger = [] sqs_trigger = [] gcp_trigger = [] +azure_trigger = [] kafka = [] nats = [] openidconnect = ["windmill-common/openidconnect"] diff --git a/backend/windmill-store/src/resources.rs b/backend/windmill-store/src/resources.rs index 9f9ea7b22b..c8e5eb555d 100644 --- a/backend/windmill-store/src/resources.rs +++ b/backend/windmill-store/src/resources.rs @@ -1820,6 +1820,7 @@ async fn update_resource_type( any( feature = "sqs_trigger", feature = "gcp_trigger", + feature = "azure_trigger", feature = "kafka", feature = "nats" ) diff --git a/backend/windmill-trigger-azure/Cargo.toml b/backend/windmill-trigger-azure/Cargo.toml new file mode 100644 index 0000000000..31fb9a9fc7 --- /dev/null +++ b/backend/windmill-trigger-azure/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "windmill-trigger-azure" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_trigger_azure" +path = "src/lib.rs" + +[features] +default = [] +enterprise = ["windmill-common/enterprise", "windmill-store/enterprise", "windmill-trigger/enterprise"] +private = ["windmill-common/private", "windmill-store/private"] + +[dependencies] +windmill-common = { workspace = true, default-features = false } +windmill-api-auth.workspace = true +windmill-store = { workspace = true, features = ["azure_trigger"] } +windmill-trigger.workspace = true +windmill-git-sync.workspace = true +axum.workspace = true +serde.workspace = true +serde_json.workspace = true +sqlx.workspace = true +tokio.workspace = true +tokio-util.workspace = true +tracing.workspace = true +async-trait.workspace = true +itertools.workspace = true +anyhow.workspace = true +base64.workspace = true +bytes.workspace = true +http.workspace = true +quick_cache.workspace = true +lazy_static.workspace = true +reqwest.workspace = true +chrono.workspace = true +thiserror.workspace = true +sha2.workspace = true +rand.workspace = true +hex.workspace = true +constant_time_eq.workspace = true diff --git a/backend/windmill-trigger-azure/src/handler_oss.rs b/backend/windmill-trigger-azure/src/handler_oss.rs new file mode 100644 index 0000000000..e56c76a2fd --- /dev/null +++ b/backend/windmill-trigger-azure/src/handler_oss.rs @@ -0,0 +1,65 @@ +#[allow(unused)] +#[cfg(feature = "private")] +pub use super::handler_ee::*; + +#[cfg(not(feature = "private"))] +use { + super::AzureTrigger, + async_trait::async_trait, + sqlx::PgConnection, + windmill_api_auth::ApiAuthed, + windmill_common::{ + error::{Error, Result}, + DB, + }, + windmill_git_sync::DeployedObject, + windmill_trigger::{TriggerCrud, TriggerData}, +}; + +#[cfg(not(feature = "private"))] +#[async_trait] +impl TriggerCrud for AzureTrigger { + type Trigger = (); + type TriggerConfig = (); + type TriggerConfigRequest = (); + type TestConnectionConfig = (); + + const TABLE_NAME: &'static str = ""; + const TRIGGER_TYPE: &'static str = ""; + const SUPPORTS_SERVER_STATE: bool = false; + const SUPPORTS_TEST_CONNECTION: bool = false; + const ROUTE_PREFIX: &'static str = "/azure_triggers"; + const DEPLOYMENT_NAME: &'static str = ""; + const IS_ALLOWED_ON_CLOUD: bool = false; + + fn get_deployed_object(path: String, parent_path: Option) -> DeployedObject { + DeployedObject::AzureTrigger { path, parent_path } + } + + async fn create_trigger( + &self, + _db: &DB, + _executor: &mut PgConnection, + _authed: &ApiAuthed, + _w_id: &str, + _trigger: TriggerData, + ) -> Result<()> { + Err(Error::BadRequest( + "Azure triggers are not available in open source version".to_string(), + )) + } + + async fn update_trigger( + &self, + _db: &DB, + _executor: &mut PgConnection, + _authed: &ApiAuthed, + _workspace_id: &str, + _path: &str, + _trigger: TriggerData, + ) -> Result<()> { + Err(Error::BadRequest( + "Azure triggers are not available in open source version".to_string(), + )) + } +} diff --git a/backend/windmill-trigger-azure/src/lib.rs b/backend/windmill-trigger-azure/src/lib.rs new file mode 100644 index 0000000000..a151f3e949 --- /dev/null +++ b/backend/windmill-trigger-azure/src/lib.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "private")] +mod handler_ee; +pub mod handler_oss; + +#[cfg(feature = "private")] +mod listener_ee; +pub mod listener_oss; + +#[cfg(feature = "private")] +mod mod_ee; +#[cfg(feature = "private")] +pub use mod_ee::*; + +#[derive(Clone, Copy)] +pub struct AzureTrigger; diff --git a/backend/windmill-trigger-azure/src/listener_oss.rs b/backend/windmill-trigger-azure/src/listener_oss.rs new file mode 100644 index 0000000000..cc68b74f83 --- /dev/null +++ b/backend/windmill-trigger-azure/src/listener_oss.rs @@ -0,0 +1,52 @@ +#[allow(unused)] +#[cfg(feature = "private")] +pub use super::listener_ee::*; + +#[cfg(not(feature = "private"))] +use { + super::AzureTrigger, + serde_json::{value::RawValue, Value}, + std::{collections::HashMap, sync::Arc}, + tokio::sync::RwLock, + windmill_common::{error::Result, jobs::JobTriggerKind, triggers::TriggerKind, DB}, + windmill_trigger::{listener::ListeningTrigger, trigger_helpers::TriggerJobArgs, Listener}, +}; + +#[cfg(not(feature = "private"))] +impl TriggerJobArgs for AzureTrigger { + type Payload = Value; + const TRIGGER_KIND: TriggerKind = TriggerKind::Azure; + fn v1_payload_fn(_payload: &Self::Payload) -> HashMap> { + HashMap::new() + } +} + +#[cfg(not(feature = "private"))] +#[async_trait::async_trait] +impl Listener for AzureTrigger { + type Consumer = (); + type Extra = (); + type ExtraState = (); + const JOB_TRIGGER_KIND: JobTriggerKind = JobTriggerKind::Azure; + + async fn get_consumer( + &self, + _db: &DB, + _listening_trigger: &ListeningTrigger, + _err_message: Arc>>, + _killpill_rx: tokio::sync::broadcast::Receiver<()>, + ) -> Result> { + Ok(None) + } + async fn consume( + &self, + _db: &DB, + _consumer: Self::Consumer, + _listening_trigger: &ListeningTrigger, + _err_message: Arc>>, + _killpill_rx: tokio::sync::broadcast::Receiver<()>, + _extra_state: Option<&Self::ExtraState>, + ) { + () + } +} diff --git a/backend/windmill-types/src/flows.rs b/backend/windmill-types/src/flows.rs index 14bcf6d8d5..8b60eb0d44 100644 --- a/backend/windmill-types/src/flows.rs +++ b/backend/windmill-types/src/flows.rs @@ -63,6 +63,10 @@ pub fn is_none_or_false(b: &Option) -> bool { b.is_none() || !b.unwrap() } +fn is_false(b: &bool) -> bool { + !*b +} + #[derive(Serialize, sqlx::FromRow)] pub struct ListableFlow { pub workspace_id: String, @@ -921,6 +925,8 @@ pub enum FlowModuleValue { AIAgent { input_transforms: HashMap, tools: Vec, + #[serde(default, skip_serializing_if = "is_false")] + omit_output_from_conversation: bool, }, } @@ -955,6 +961,7 @@ struct UntaggedFlowModuleValue { modules_node: Option, assets: Option>, tools: Option>, + omit_output_from_conversation: Option, pass_flow_input_directly: Option, squash: Option, #[serde(flatten)] @@ -1056,6 +1063,9 @@ impl<'de> Deserialize<'de> for FlowModuleValue { tools: untagged .tools .ok_or_else(|| serde::de::Error::missing_field("tools"))?, + omit_output_from_conversation: untagged + .omit_output_from_conversation + .unwrap_or(false), }), other => Err(serde::de::Error::unknown_variant( other, @@ -1173,4 +1183,37 @@ mod tests { let val: FlowValue = serde_json::from_value(input).unwrap(); assert_eq!(val.modules.len(), 1); } + + #[test] + fn ai_agent_omit_output_from_conversation_defaults_to_false() { + let input = json!({ + "type": "aiagent", + "tools": [], + "input_transforms": {} + }); + + let val: FlowModuleValue = serde_json::from_value(input).unwrap(); + let FlowModuleValue::AIAgent { omit_output_from_conversation, .. } = val else { + panic!("expected aiagent module"); + }; + + assert!(!omit_output_from_conversation); + } + + #[test] + fn ai_agent_omit_output_from_conversation_preserves_true() { + let input = json!({ + "type": "aiagent", + "tools": [], + "input_transforms": {}, + "omit_output_from_conversation": true + }); + + let val: FlowModuleValue = serde_json::from_value(input).unwrap(); + let FlowModuleValue::AIAgent { omit_output_from_conversation, .. } = val else { + panic!("expected aiagent module"); + }; + + assert!(omit_output_from_conversation); + } } diff --git a/backend/windmill-types/src/jobs.rs b/backend/windmill-types/src/jobs.rs index 5d374ca75d..d84e80c902 100644 --- a/backend/windmill-types/src/jobs.rs +++ b/backend/windmill-types/src/jobs.rs @@ -36,6 +36,7 @@ pub enum JobTriggerKind { Postgres, Schedule, Gcp, + Azure, Nextcloud, Google, Github, @@ -58,6 +59,7 @@ impl std::fmt::Display for JobTriggerKind { JobTriggerKind::Postgres => "postgres", JobTriggerKind::Schedule => "schedule", JobTriggerKind::Gcp => "gcp", + JobTriggerKind::Azure => "azure", JobTriggerKind::Nextcloud => "nextcloud", JobTriggerKind::Google => "google", JobTriggerKind::Github => "github", diff --git a/backend/windmill-types/src/triggers.rs b/backend/windmill-types/src/triggers.rs index 71c7221086..8ad1d5e8a7 100644 --- a/backend/windmill-types/src/triggers.rs +++ b/backend/windmill-types/src/triggers.rs @@ -19,6 +19,7 @@ pub enum TriggerKind { Sqs, Postgres, Gcp, + Azure, Nextcloud, Google, Github, @@ -38,6 +39,7 @@ impl TriggerKind { TriggerKind::Sqs => "sqs".to_string(), TriggerKind::Postgres => "postgres".to_string(), TriggerKind::Gcp => "gcp".to_string(), + TriggerKind::Azure => "azure".to_string(), TriggerKind::Nextcloud => "nextcloud".to_string(), TriggerKind::Google => "google".to_string(), TriggerKind::Github => "github".to_string(), @@ -59,6 +61,7 @@ impl fmt::Display for TriggerKind { TriggerKind::Sqs => "sqs", TriggerKind::Postgres => "postgres", TriggerKind::Gcp => "gcp", + TriggerKind::Azure => "azure", TriggerKind::Nextcloud => "nextcloud", TriggerKind::Google => "google", TriggerKind::Github => "github", diff --git a/backend/windmill-worker/src/ai/tools.rs b/backend/windmill-worker/src/ai/tools.rs index a58cab06fd..54b3bf6fa3 100644 --- a/backend/windmill-worker/src/ai/tools.rs +++ b/backend/windmill-worker/src/ai/tools.rs @@ -75,6 +75,7 @@ pub struct ToolExecutionContext<'a> { // Optional streaming & chat pub stream_event_processor: Option<&'a StreamEventProcessor>, pub flow_context: &'a mut FlowContext, + pub omit_output_from_conversation: bool, pub previous_result: &'a Option>, pub id_context: &'a Option, @@ -780,6 +781,10 @@ async fn add_tool_message_to_chat( content: &str, success: bool, ) { + if ctx.omit_output_from_conversation { + return; + } + let chat_enabled = ctx .flow_context .flow_status diff --git a/backend/windmill-worker/src/ai_executor.rs b/backend/windmill-worker/src/ai_executor.rs index 2fcb86f3de..03153df0a4 100644 --- a/backend/windmill-worker/src/ai_executor.rs +++ b/backend/windmill-worker/src/ai_executor.rs @@ -275,7 +275,9 @@ pub async fn handle_ai_agent_job( let summary = module.summary.clone(); - let FlowModuleValue::AIAgent { tools, .. } = module.get_value()? else { + let FlowModuleValue::AIAgent { tools, omit_output_from_conversation, .. } = + module.get_value()? + else { return Err(Error::internal_err( "AI agent module is not an AI agent".to_string(), )); @@ -504,6 +506,7 @@ pub async fn handle_ai_agent_job( killpill_rx, has_stream, has_websearch, + omit_output_from_conversation, cancel_rx, tool_abort_handles.clone(), ); @@ -600,6 +603,7 @@ pub async fn run_agent( killpill_rx: &mut tokio::sync::broadcast::Receiver<()>, has_stream: &mut bool, has_websearch: bool, + omit_output_from_conversation: bool, // cancellation signal from parent cancel_rx: tokio::sync::watch::Receiver, @@ -860,6 +864,7 @@ pub async fn run_agent( .as_ref() .and_then(|fs| fs.chat_input_enabled) .unwrap_or(false); + let persist_output_to_conversation = chat_enabled && !omit_output_from_conversation; let step_name = get_step_name_from_flow(summary.as_deref(), effective_flow_step_id); @@ -1061,7 +1066,7 @@ pub async fn run_agent( agent_action: Some(AgentAction::WebSearch {}), ..Default::default() }); - if chat_enabled { + if persist_output_to_conversation { if let Some(memory_id) = memory_id { let agent_job_id = job.id; let db_clone = db.clone(); @@ -1113,7 +1118,7 @@ pub async fn run_agent( content = Some(OpenAIContent::Text(response_content.clone())); // Add assistant message to conversation if chat_input_enabled - if chat_enabled && !response_content.is_empty() { + if persist_output_to_conversation && !response_content.is_empty() { if let Some(memory_id) = memory_id { let agent_job_id = job.id; let db_clone = db.clone(); @@ -1195,6 +1200,7 @@ pub async fn run_agent( killpill_rx, stream_event_processor: stream_event_processor.as_ref(), flow_context: &mut flow_context, + omit_output_from_conversation, previous_result: &previous_result, id_context: &id_context, tool_abort_handles: tool_abort_handles.clone(), @@ -1230,7 +1236,7 @@ pub async fn run_agent( let content = to_raw_value(&s3_object); // Add assistant message to conversation if chat_input_enabled - if chat_enabled { + if persist_output_to_conversation { if let Some(memory_id) = memory_id { let agent_job_id = job.id; let db_clone = db.clone(); diff --git a/backend/windmill-worker/src/pwsh_executor.rs b/backend/windmill-worker/src/pwsh_executor.rs index 9924913ad0..b5b8444f56 100644 --- a/backend/windmill-worker/src/pwsh_executor.rs +++ b/backend/windmill-worker/src/pwsh_executor.rs @@ -6,7 +6,9 @@ use sqlx::types::Json; use tokio::process::Command; use windmill_common::client::AuthedClient; use windmill_common::error::Error; +use windmill_common::scripts::ScriptLang; use windmill_common::worker::{to_raw_value, write_file, Connection}; +use windmill_common::workspace_dependencies::clean_lock_from_annotations; use windmill_queue::{ append_logs, CanceledBy, MiniPulledJob, INIT_SCRIPT_PATH_PREFIX, PERIODIC_SCRIPT_PATH_PREFIX, }; @@ -417,10 +419,16 @@ pub async fn handle_powershell_job( // Resolve modules from workspace dependencies and/or script imports let all_modules = match &maybe_lock { MaybeLock::Resolved { lock } if !lock.is_empty() => { - // Deployed script with lock: parse workspace deps from lock, merge with script imports - let ws_modules = parse_modules_json(lock)?; + // Deployed script with lock: strip workspace-dependencies annotation header, + // parse the modules.json body, and merge with script imports. + let cleaned = clean_lock_from_annotations(lock, ScriptLang::Powershell); let script_modules = parse_script_imports(content); - merge_module_requests(ws_modules, script_modules) + if cleaned.trim().is_empty() { + script_modules + } else { + let ws_modules = parse_modules_json(&cleaned)?; + merge_module_requests(ws_modules, script_modules) + } } MaybeLock::Unresolved { workspace_dependencies } => { let script_modules = parse_script_imports(content); @@ -1070,6 +1078,19 @@ mod tests { assert!(parse_modules_json("not json").is_err()); } + #[test] + fn test_parse_modules_json_after_cleaning_header() { + // Simulates the deployed-script lock: workspace-dependencies header + // prepended to the modules.json content. `clean_lock_from_annotations` + // strips header lines so the remaining body can be JSON-parsed. + let lock = "# workspace-dependencies-mode: manual\n# workspace-dependencies: default:abc123\n{\"modules\": {\"PSWriteColor\": \"1.0.0\"}}"; + let cleaned = clean_lock_from_annotations(lock, ScriptLang::Powershell); + let modules = parse_modules_json(&cleaned).unwrap(); + assert_eq!(modules.len(), 1); + assert_eq!(modules[0].name, "PSWriteColor"); + assert_eq!(modules[0].version, Some("1.0.0".to_string())); + } + // --- parse_script_imports tests --- #[test] diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 7b9289c56a..dfbf6739dd 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -53,7 +53,7 @@ use windmill_common::runnable_settings::{ use windmill_common::scripts::{ScriptHash, ScriptRunnableSettingsInline}; use windmill_common::users::username_to_permissioned_as; use windmill_common::utils::WarnAfterExt; -use windmill_common::worker::{to_raw_value, Connection}; +use windmill_common::worker::{error_to_value, to_raw_value, Connection}; use windmill_common::{ add_time, get_latest_flow_version_info_for_path, get_script_info_for_hash, FlowVersionInfo, ScriptHashInfo, DB, @@ -405,6 +405,7 @@ pub async fn update_flow_status_after_job_completion_internal( chat_input_enabled: bool, conversation_id: Option, is_ai_agent_step: bool, + omit_output_from_conversation: bool, } let ( should_continue_flow, @@ -1609,10 +1610,22 @@ pub async fn update_flow_status_after_job_completion_internal( current_module_id = %current_module.map(|x| x.id.clone()).unwrap_or_default(), continue_on_error = %continue_on_error, should_continue_flow = %should_continue_flow, "computed if flow should continue"); + let is_ai_agent_step = current_module.is_some_and(|m| m.is_ai_agent()); + let omit_output_from_conversation = match (is_ai_agent_step, current_module) { + (true, Some(module)) => match module.get_value()? { + FlowModuleValue::AIAgent { omit_output_from_conversation, .. } => { + omit_output_from_conversation + } + _ => false, + }, + _ => false, + }; + let chat_ai_info = ChatAiInfo { chat_input_enabled: old_status.chat_input_enabled.unwrap_or(false), conversation_id: old_status.memory_id, - is_ai_agent_step: current_module.is_some_and(|m| m.is_ai_agent()), + is_ai_agent_step, + omit_output_from_conversation, }; ( should_continue_flow, @@ -1843,6 +1856,7 @@ pub async fn update_flow_status_after_job_completion_internal( success, skipped, chat_ai_info.is_ai_agent_step, + chat_ai_info.omit_output_from_conversation, &nresult, chat_ai_info.chat_input_enabled, chat_ai_info.conversation_id, @@ -1990,16 +2004,60 @@ fn find_flow_job_index(flow_jobs: &Vec, job_id_for_status: &Uuid) -> Optio flow_jobs.iter().position(|x| x == job_id_for_status) } +fn format_chat_message_value(value: &Value) -> String { + match value { + Value::Null => "null".to_string(), + Value::Bool(_) | Value::Number(_) => value.to_string(), + Value::String(text) => text.clone(), + Value::Array(_) | Value::Object(_) => serde_json::to_string_pretty(value) + .unwrap_or_else(|e| format!("Failed to serialize result: {e}")), + } +} + +/// Selects the assistant message persisted for the final non-AI step in a chat-enabled flow. +/// `windmill_chat_answer` is the explicit override contract: +/// - `null` suppresses the assistant message +/// - any other JSON value is rendered as the chat message +fn extract_chat_message_from_flow_result(result: &RawValue) -> error::Result> { + let value: Value = serde_json::from_str(result.get()) + .map_err(|e| Error::internal_err(format!("Failed to parse flow result: {e}")))?; + + match value { + Value::Object(map) => { + match map.get("windmill_chat_answer") { + Some(Value::Null) => return Ok(None), + Some(answer) => return Ok(Some(format_chat_message_value(answer))), + _ => {} + } + + Ok(Some( + serde_json::to_string_pretty(&Value::Object(map)) + .unwrap_or_else(|e| format!("Failed to serialize result: {e}")), + )) + } + Value::String(content) => Ok(Some(content)), + value => Ok(Some( + serde_json::to_string_pretty(&value) + .unwrap_or_else(|e| format!("Failed to serialize result: {e}")), + )), + } +} + async fn add_tool_message_to_conversation( db: &DB, job_id: &Uuid, success: bool, skipped: bool, is_ai_agent_step: bool, + omit_output_from_conversation: bool, result: &Box, chat_input_enabled: bool, conversation_id: Option, ) -> error::Result<()> { + if is_ai_agent_step && omit_output_from_conversation { + return Ok(()); + } + // Create assistant message if it's a flow and it's done, but only if last module is not an AI agent if !skipped && chat_input_enabled { // Get conversation_id from flow_status.memory_id @@ -2007,28 +2065,8 @@ async fn add_tool_message_to_conversation( if let Some(conversation_id) = conversation_id { // Only create assistant message if last module is NOT an AI agent, or there was an error if !is_ai_agent_step || success == false { - let value = serde_json::to_value(result.get()) - .map_err(|e| Error::internal_err(format!("Failed to serialize result: {e}")))?; - - let content = match value { - // If it's an Object with "output" key AND the output is a String, return it - serde_json::Value::Object(mut map) - if map.contains_key("output") - && matches!(map.get("output"), Some(serde_json::Value::String(_))) => - { - if let Some(serde_json::Value::String(s)) = map.remove("output") { - s - } else { - // prettify the whole result - serde_json::to_string_pretty(&map) - .unwrap_or_else(|e| format!("Failed to serialize result: {e}")) - } - } - // Otherwise, if the whole value is a String, return it - serde_json::Value::String(s) => s, - // Otherwise, prettify the whole result - v => serde_json::to_string_pretty(&v) - .unwrap_or_else(|e| format!("Failed to serialize result: {e}")), + let Some(content) = extract_chat_message_from_flow_result(result.as_ref())? else { + return Ok(()); }; // Insert new assistant message @@ -3540,6 +3578,32 @@ async fn push_next_flow_job( )); } } + NextFlowTransform::StepFailure { error } => { + let result = Arc::new(to_raw_value(&WrappedError { error })); + update_flow_status_after_job_completion( + db, + client, + flow_job.id, + &Uuid::nil(), + &flow_job.workspace_id, + false, + None, + result, + None, + false, + same_worker_tx, + worker_dir, + None, + worker_name, + job_completed_tx, + flow_runners, + killpill_rx, + #[cfg(feature = "benchmark")] + &mut BenchmarkIter::new(), + ) + .await?; + return Ok(PushNextFlowJob::Done(None)); + } }; // only start runners if we're not already in a squash for loop @@ -4396,6 +4460,10 @@ enum ContinuePayload { enum NextFlowTransform { EmptyInnerFlows { branch_chosen: Option }, Continue(ContinuePayload, NextStatus), + // The current module failed in-place (e.g. a BranchOne predicate threw). + // The error is reported as the step's result so the normal flow machinery + // (including `failure_module` and surrounding `skip_failures`) applies. + StepFailure { error: serde_json::Value }, } fn insert_iter_arg( @@ -4793,6 +4861,7 @@ async fn compute_next_flow_transform( | FlowStatusModule::WaitingForExecutor { .. } => { let mut branch_chosen = BranchChosen::Default; let idcontext = get_transform_context(&flow_job, previous_id, &status); + let mut predicate_err: Option = None; for (i, b) in branches.iter().enumerate() { let pred_res = compute_bool_from_expr( &b.expr, @@ -4818,13 +4887,22 @@ async fn compute_next_flow_transform( ) .await; } - let pred = pred_res?; + let pred = match pred_res { + Ok(p) => p, + Err(e) => { + predicate_err = Some(e); + break; + } + }; if pred { branch_chosen = BranchChosen::Branch { branch: i }; break; } } + if let Some(e) = predicate_err { + return Ok(NextFlowTransform::StepFailure { error: error_to_value(&e) }); + } branch_chosen } _ => Err(Error::BadRequest(format!( @@ -5574,3 +5652,84 @@ pub async fn get_previous_job_result( _ => Ok(None), } } + +#[cfg(test)] +mod tests { + use super::extract_chat_message_from_flow_result; + use serde_json::{json, value::to_raw_value}; + + #[test] + fn pretty_prints_full_result_when_no_override_is_present() { + let value = json!({ + "output": "final answer", + "metadata": { "foo": "bar" } + }); + let result = to_raw_value(&value).unwrap(); + + let message = extract_chat_message_from_flow_result(result.as_ref()).unwrap(); + + assert_eq!(message, Some(serde_json::to_string_pretty(&value).unwrap())); + } + + #[test] + fn uses_windmill_chat_answer_when_it_is_a_string() { + let result = to_raw_value(&json!({ + "windmill_chat_answer": "chat-visible answer", + "output": "ignored output", + "metadata": { "foo": "bar" } + })) + .unwrap(); + + let message = extract_chat_message_from_flow_result(result.as_ref()).unwrap(); + + assert_eq!(message, Some("chat-visible answer".to_string())); + } + + #[test] + fn skips_persisting_when_windmill_chat_answer_is_null() { + let result = to_raw_value(&json!({ + "windmill_chat_answer": null, + "output": "should not be stored" + })) + .unwrap(); + + let message = extract_chat_message_from_flow_result(result.as_ref()).unwrap(); + + assert_eq!(message, None); + } + + #[test] + fn coerces_scalar_windmill_chat_answer_to_a_string() { + let result = to_raw_value(&json!({ + "windmill_chat_answer": 42, + "output": "ignored output", + "metadata": { "foo": "bar" } + })) + .unwrap(); + + let message = extract_chat_message_from_flow_result(result.as_ref()).unwrap(); + + assert_eq!(message, Some("42".to_string())); + } + + #[test] + fn pretty_prints_structured_windmill_chat_answer_only() { + let override_value = json!({ + "text": "chat-visible answer", + "meta": ["a", "b"] + }); + let result = to_raw_value(&json!({ + "windmill_chat_answer": override_value, + "output": "ignored output", + "metadata": { "foo": "bar" } + })) + .unwrap(); + + let message = extract_chat_message_from_flow_result(result.as_ref()).unwrap(); + + assert_eq!( + message, + Some(serde_json::to_string_pretty(&override_value).unwrap()) + ); + } +} diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 4275bb7014..407efbb717 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -1184,7 +1184,11 @@ async fn lock_modules<'c>( .execute(&mut *tx) .await?; } - FlowModuleValue::AIAgent { input_transforms, mut tools } => { + FlowModuleValue::AIAgent { + input_transforms, + mut tools, + omit_output_from_conversation, + } => { // Extract FlowModules from tools and track their original indices // MCP tools don't need locking, so we filter them out let mut flow_modules = Vec::new(); @@ -1232,7 +1236,12 @@ async fn lock_modules<'c>( tools[idx] = locked.into(); } - e.value = FlowModuleValue::AIAgent { input_transforms, tools }.into(); + e.value = FlowModuleValue::AIAgent { + input_transforms, + tools, + omit_output_from_conversation, + } + .into(); } _ => (), }; @@ -2881,6 +2890,7 @@ async fn capture_dependency_job( ) .await? } + ScriptLang::Powershell => workspace_dependencies.get_powershell()?.unwrap_or_default(), // for related places search: ADD_NEW_LANG _ => "".to_owned(), }; diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 3d050361c2..0cb8e2c624 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -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.688.0"; +export const VERSION = "v1.690.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/bun.lock b/cli/bun.lock index ddcf459725..9d9bfe0e4f 100644 --- a/cli/bun.lock +++ b/cli/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "wmill-dev", diff --git a/cli/src/commands/app/app_metadata.ts b/cli/src/commands/app/app_metadata.ts index 37965a6c60..2589bf2fca 100644 --- a/cli/src/commands/app/app_metadata.ts +++ b/cli/src/commands/app/app_metadata.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { readFile, mkdir, readdir } from "node:fs/promises"; +import { mkdir, readdir } from "node:fs/promises"; import { colors } from "@cliffy/ansi/colors"; import * as log from "../../core/log.ts"; import { sep as SEP } from "node:path"; @@ -23,7 +23,7 @@ import { ScriptLanguage, workspaceDependenciesLanguages, } from "../../utils/script_common.ts"; -import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.ts"; +import { generateHash, getHeaders, readTextFile, writeIfChanged } from "../../utils/utils.ts"; import { exts } from "../script/script.ts"; import { FSFSElement, yamlOptions } from "../sync/sync.ts"; import { Workspace } from "../workspace/workspace.ts"; @@ -178,7 +178,7 @@ export async function generateAppLocksInternal( if (typeof content === "string" && content.startsWith("!inline ")) { const filePath = appFolder + SEP + content.replace("!inline ", ""); try { - content = await readFile(filePath, "utf-8"); + content = await readTextFile(filePath); } catch { return inlineScript; } @@ -893,7 +893,7 @@ export async function inferRunnableSchemaFromFile( ); let content: string; try { - content = await readFile(fullFilePath, "utf-8"); + content = await readTextFile(fullFilePath); } catch { log.warn(colors.yellow(`Could not read file: ${fullFilePath}`)); return undefined; diff --git a/cli/src/commands/app/bundle.ts b/cli/src/commands/app/bundle.ts index 8d15488491..0f82f7b8c4 100644 --- a/cli/src/commands/app/bundle.ts +++ b/cli/src/commands/app/bundle.ts @@ -5,6 +5,7 @@ import { spawn } from "node:child_process"; import * as log from "../../core/log.ts"; import { colors } from "@cliffy/ansi/colors"; import * as windmillUtils from "@windmill-labs/shared-utils"; +import { readTextFile, readTextFileSync } from "../../utils/utils.ts"; export interface BundleOptions { entryPoint?: string; outDir?: string; @@ -41,7 +42,7 @@ export function detectFrameworks(appDir: string): { svelte: boolean; vue: boolea } try { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + const packageJson = JSON.parse(readTextFileSync(packageJsonPath)); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, @@ -69,7 +70,7 @@ function createSveltePlugin(appDir: string): any { const svelte = await import("svelte/compiler"); // Load the file from the file system - const source = await fs.promises.readFile(args.path, "utf8"); + const source = await readTextFile(args.path); const filename = path.relative(process.cwd(), args.path); // This converts a message in Svelte's format to esbuild's format @@ -269,9 +270,9 @@ export async function createBundle( throw new Error(`Expected JS bundle at ${jsPath} but file not found`); } - const jsContent = fs.readFileSync(jsPath, "utf-8"); + const jsContent = readTextFileSync(jsPath); const cssContent = fs.existsSync(cssPath) - ? fs.readFileSync(cssPath, "utf-8") + ? readTextFileSync(cssPath) : ""; try { diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts index c9fe8f67ec..628849c375 100644 --- a/cli/src/commands/app/dev.ts +++ b/cli/src/commands/app/dev.ts @@ -13,7 +13,7 @@ import * as path from "node:path"; import process from "node:process"; import { Buffer } from "node:buffer"; import { writeFileSync } from "node:fs"; -import { readFile } from "node:fs/promises"; +import { readTextFile } from "../../utils/utils.ts"; import { WebSocket, WebSocketServer } from "ws"; import { createFrameworkPlugins, @@ -800,7 +800,7 @@ async function dev(opts: DevOptions, appFolder?: string) { const fileName = path.basename(filePath); try { - const sqlContent = await readFile(filePath, "utf-8"); + const sqlContent = await readTextFile(filePath); if (!sqlContent.trim()) { log.info(colors.gray(`Skipping empty file: ${fileName}`)); @@ -856,7 +856,7 @@ async function dev(opts: DevOptions, appFolder?: string) { // If there's a current SQL file being shown, send it to the new client if (currentSqlFile && fs.existsSync(currentSqlFile)) { try { - const sqlContent = await readFile(currentSqlFile, "utf-8"); + const sqlContent = await readTextFile(currentSqlFile); const datatable = await getDatatableConfig(); const fileName = path.basename(currentSqlFile); diff --git a/cli/src/commands/app/raw_apps.ts b/cli/src/commands/app/raw_apps.ts index 6e71a3bf30..3cd5837e4d 100644 --- a/cli/src/commands/app/raw_apps.ts +++ b/cli/src/commands/app/raw_apps.ts @@ -9,10 +9,10 @@ import { stringify as yamlStringify } from "yaml"; import * as wmill from "../../../gen/services.gen.ts"; import { Policy } from "../../../gen/types.gen.ts"; import path from "node:path"; -import { readFile, readdir } from "node:fs/promises"; +import { readdir } from "node:fs/promises"; import { GlobalOptions, isSuperset } from "../../types.ts"; -import { deepEqual } from "../../utils/utils.ts"; +import { deepEqual, readTextFile } from "../../utils/utils.ts"; import { replaceInlineScripts, repopulateFields } from "./app.ts"; import { createBundle, detectFrameworks } from "./bundle.ts"; @@ -65,8 +65,8 @@ async function findRunnableContentFile( // Check if this is a recognized extension if (EXTENSION_TO_LANGUAGE[ext]) { try { - const content = await readFile( - path.join(backendPath, fileName), "utf-8", + const content = await readTextFile( + path.join(backendPath, fileName), ); return { ext, content }; } catch { @@ -164,9 +164,8 @@ export async function loadRunnablesFromBackend( // Try to load lock file let lock: string | undefined; try { - lock = await readFile( + lock = await readTextFile( path.join(backendPath, `${runnableId}.lock`), - "utf-8", ); } catch { // No lock file, that's fine @@ -226,8 +225,8 @@ export async function loadRunnablesFromBackend( // Try to load lock file let lock: string | undefined; try { - lock = await readFile( - path.join(backendPath, `${runnableId}.lock`), "utf-8", + lock = await readTextFile( + path.join(backendPath, `${runnableId}.lock`), ); } catch { // No lock file, that's fine @@ -319,7 +318,7 @@ async function collectAppFiles( ) { continue; } - const content = await readFile(fullPath, "utf-8"); + const content = await readTextFile(fullPath); files[relativePath] = content; } } diff --git a/cli/src/commands/dependencies/dependencies.ts b/cli/src/commands/dependencies/dependencies.ts index cde4fe256c..2daf2d180a 100644 --- a/cli/src/commands/dependencies/dependencies.ts +++ b/cli/src/commands/dependencies/dependencies.ts @@ -7,6 +7,7 @@ import * as log from "../../core/log.ts"; import * as wmill from "../../../gen/services.gen.ts"; import fs from "node:fs"; import { workspaceDependenciesPathToLanguageAndFilename } from "../../utils/metadata.ts"; +import { readTextFileSync } from "../../utils/utils.ts"; async function push( opts: GlobalOptions, @@ -19,7 +20,7 @@ async function push( throw new Error(`File not found: ${filePath}`); } - const content = fs.readFileSync(filePath, "utf8"); + const content = readTextFileSync(filePath); // Use the existing pushWorkspaceDependencies function await pushWorkspaceDependencies( diff --git a/cli/src/commands/dev/dev.ts b/cli/src/commands/dev/dev.ts index e1d6ed00cc..c1a7df5a4c 100644 --- a/cli/src/commands/dev/dev.ts +++ b/cli/src/commands/dev/dev.ts @@ -9,7 +9,8 @@ import * as getPort from "get-port"; import * as http from "node:http"; import * as https from "node:https"; import * as open from "open"; -import { access, readFile, readdir, realpath, unlink, writeFile } from "node:fs/promises"; +import { access, readdir, realpath, unlink, writeFile } from "node:fs/promises"; +import { readTextFile } from "../../utils/utils.ts"; import { watch } from "node:fs"; import { getTypeStrFromPath, GlobalOptions } from "../../types.ts"; import { ignoreF } from "../sync/sync.ts"; @@ -327,7 +328,7 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { )) as FlowFile; await replaceInlineScripts( localFlow.value.modules, - async (path: string) => await readFile(localPath + path, "utf-8"), + async (path: string) => await readTextFile(localPath + path), log, localPath, SEP, @@ -353,7 +354,7 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { } else if (typ == "script") { const splitted = cpath.split("."); const wmPath = splitted[0]; - const content = await readFile(cpath, "utf-8"); + const content = await readTextFile(cpath); const lang = inferContentTypeFromFilePath(cpath, opts.defaultTs); const typed = (await parseMetadataFile( @@ -425,7 +426,7 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { const localFlow = (await yamlParseFile(flowYaml)) as FlowFile; await replaceInlineScripts( localFlow.value.modules, - async (p: string) => await readFile(flowDir + p, "utf-8"), + async (p: string) => await readTextFile(flowDir + p), log, flowDir, SEP, @@ -456,7 +457,7 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { const filePath = wmPath + ext; try { await access(filePath); - const content = await readFile(filePath, "utf-8"); + const content = await readTextFile(filePath); const lang = inferContentTypeFromFilePath(filePath, opts.defaultTs); const typed = (await parseMetadataFile(removeExtensionToPath(filePath), undefined))?.payload; const edit: LastEditScript = { @@ -515,7 +516,7 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { const filePath = flowDir + s.path; let needsWrite = true; try { - const existing = await readFile(filePath, "utf-8"); + const existing = await readTextFile(filePath); if (existing === s.content) needsWrite = false; } catch { // File doesn't exist diff --git a/cli/src/commands/flow/flow.ts b/cli/src/commands/flow/flow.ts index 76f41cc860..f22a81630b 100644 --- a/cli/src/commands/flow/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -8,9 +8,8 @@ import { sep as SEP, join as pathJoin, resolve as pathResolve } from "node:path" import { execSync } from "node:child_process"; import { stringify as yamlStringify } from "yaml"; import { yamlParseFile } from "../../utils/yaml.ts"; -import { validateRequiredArgs } from "../../utils/utils.ts"; +import { readTextFile, validateRequiredArgs } from "../../utils/utils.ts"; import * as wmill from "../../../gen/services.gen.ts"; -import { readFile } from "node:fs/promises"; import { mkdirSync, writeFileSync } from "node:fs"; import { buildFolderPath, getMetadataFileName, loadNonDottedPathsSetting } from "../../utils/resource_folders.ts"; @@ -158,7 +157,7 @@ export async function pushFlow( } const localFlow = (await yamlParseFile(localPath + "flow.yaml")) as FlowFile; - const fileReader = async (path: string) => await readFile(localPath + path, "utf-8"); + const fileReader = async (path: string) => await readTextFile(localPath + path); const missingFiles: string[] = []; await replaceInlineScripts( localFlow.value.modules, @@ -546,7 +545,7 @@ async function preview( const localFlow = (await yamlParseFile(flowPath + "flow.yaml")) as FlowFile; // Replace inline scripts with their actual content - const fileReader = async (path: string) => await readFile(flowPath + path, "utf-8"); + const fileReader = async (path: string) => await readTextFile(flowPath + path); await replaceInlineScripts( localFlow.value.modules, fileReader, diff --git a/cli/src/commands/flow/flow_metadata.ts b/cli/src/commands/flow/flow_metadata.ts index 3c1c8cbd15..4110004959 100644 --- a/cli/src/commands/flow/flow_metadata.ts +++ b/cli/src/commands/flow/flow_metadata.ts @@ -4,7 +4,6 @@ import * as path from "node:path"; import { sep as SEP } from "node:path"; import { stringify as yamlStringify } from "yaml"; import { yamlParseFile } from "../../utils/yaml.ts"; -import { readFile } from "node:fs/promises"; import { GlobalOptions } from "../../types.ts"; import { readLockfile, @@ -21,7 +20,7 @@ import { ScriptLanguage } from "../../utils/script_common.ts"; import { extractInlineScripts as extractInlineScriptsForFlows, extractCurrentMapping } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; import { newPathAssigner } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; -import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.ts"; +import { generateHash, getHeaders, readTextFile, writeIfChanged } from "../../utils/utils.ts"; import { exts } from "../script/script.ts"; import { FSFSElement, yamlOptions } from "../sync/sync.ts"; import { Workspace } from "../workspace/workspace.ts"; @@ -109,7 +108,7 @@ export async function generateFlowLockInternal( if (content.startsWith("!inline ")) { const filePath = folder + SEP + content.replace("!inline ", ""); try { - content = await readFile(filePath, "utf-8"); + content = await readTextFile(filePath); } catch { continue; } @@ -192,7 +191,7 @@ export async function generateFlowLockInternal( if (!noStaleMessage) { log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`); } - const fileReader = async (path: string) => await readFile(folder + SEP + path, "utf-8"); + const fileReader = async (path: string) => await readTextFile(folder + SEP + path); // Capture existing module-ID-to-file-path mapping before replaceInlineScripts // overwrites the !inline references with actual file content. This preserves diff --git a/cli/src/commands/instance/instance.ts b/cli/src/commands/instance/instance.ts index 3f21189074..6289dd2b01 100644 --- a/cli/src/commands/instance/instance.ts +++ b/cli/src/commands/instance/instance.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, readdir, mkdir, rm, stat } from "node:fs/promises"; +import { writeFile, readdir, mkdir, rm, stat } from "node:fs/promises"; import { appendFile } from "node:fs/promises"; import { colors } from "@cliffy/ansi/colors"; import { Command } from "@cliffy/command"; @@ -39,7 +39,7 @@ import { pushInstanceSettings, type SimplifiedSettings, } from "../../core/settings.ts"; -import { deepEqual } from "../../utils/utils.ts"; +import { deepEqual, readTextFile } from "../../utils/utils.ts"; import { getActiveWorkspace } from "../workspace/workspace.ts"; export interface Instance { @@ -52,7 +52,7 @@ export interface Instance { export async function allInstances(): Promise { try { const file = await getInstancesConfigFilePath(); - const txt = await readFile(file, "utf-8"); + const txt = await readTextFile(file); return txt .split("\n") .map((line) => { @@ -658,7 +658,7 @@ export async function getActiveInstance(opts: { return opts.instance; } try { - return await readFile(await getActiveInstanceFilePath(), "utf-8"); + return await readTextFile(await getActiveInstanceFilePath()); } catch { return undefined; } diff --git a/cli/src/commands/jobs/jobs.ts b/cli/src/commands/jobs/jobs.ts index 17a58d11f2..e7b421ab9d 100644 --- a/cli/src/commands/jobs/jobs.ts +++ b/cli/src/commands/jobs/jobs.ts @@ -7,6 +7,7 @@ import { Confirm } from "@cliffy/prompt/confirm"; import * as log from "../../core/log.ts"; import { mergeConfigWithConfigFile } from "../../core/conf.ts"; import * as fs from "node:fs/promises"; +import { readTextFile } from "../../utils/utils.ts"; import * as wmill from "../../../gen/services.gen.ts"; async function pullJobs( @@ -190,7 +191,7 @@ async function pushJobs( // Push completed jobs const completedPath = opts.completedFile || "completed_jobs.json"; try { - const completedContent = await fs.readFile(completedPath, "utf-8"); + const completedContent = await readTextFile(completedPath); const completedJobs = JSON.parse(completedContent); if (!Array.isArray(completedJobs)) { @@ -218,7 +219,7 @@ async function pushJobs( // Push queued jobs const queuedPath = opts.queuedFile || "queued_jobs.json"; try { - const queuedContent = await fs.readFile(queuedPath, "utf-8"); + const queuedContent = await readTextFile(queuedPath); const queuedJobs = JSON.parse(queuedContent); if (!Array.isArray(queuedJobs)) { diff --git a/cli/src/commands/resource/resource.ts b/cli/src/commands/resource/resource.ts index 054ed91d06..f8a8713928 100644 --- a/cli/src/commands/resource/resource.ts +++ b/cli/src/commands/resource/resource.ts @@ -1,4 +1,4 @@ -import { mkdir, stat, writeFile, readdir, readFile } from "node:fs/promises"; +import { mkdir, stat, writeFile, readdir } from "node:fs/promises"; import { stringify as yamlStringify } from "yaml"; import nodePath from "node:path"; @@ -17,7 +17,7 @@ import * as log from "../../core/log.ts"; import { sep as SEP } from "node:path"; import * as wmill from "../../../gen/services.gen.ts"; import { Resource } from "../../../gen/types.gen.ts"; -import { readInlinePathSync } from "../../utils/utils.ts"; +import { readInlinePathSync, readTextFile } from "../../utils/utils.ts"; import { isWorkspaceSpecificFile } from "../../core/specific_items.ts"; import { getCurrentGitBranch } from "../../utils/git.ts"; @@ -38,7 +38,7 @@ async function readFilesetDirectory(dirPath: string): Promise { const remotePath = removeExtensionToPath(filePath).replaceAll(SEP, "/"); const metadataWithType = await parseMetadataFile(remotePath, undefined); - const metadataContent = await readFile(metadataWithType.path, "utf-8"); + const metadataContent = await readTextFile(metadataWithType.path); return await generateScriptHash({}, content, metadataContent); } @@ -141,7 +141,7 @@ async function push(opts: PushOptions, filePath: string) { // Warn about metadata state before pushing try { - const content = await readFile(filePath, "utf-8"); + const content = await readTextFile(filePath); const remotePath = removeExtensionToPath(filePath).replaceAll(SEP, "/"); const contentHash = await computePushMetadataHash(filePath, content); const conf = await readLockfile(); @@ -180,20 +180,25 @@ export async function findResourceFile(path: string) { let contentBasePathJSON = splitPath[0] + "." + splitPath[1] + ".json"; let contentBasePathYAML = splitPath[0] + "." + splitPath[1] + ".yaml"; - // Check for branch-specific metadata files first + // Check for workspace-specific metadata files first, using the wmill.yaml + // config key for the current git branch as the filename suffix (falls back + // to the branch name when no matching workspace entry exists). const currentBranch = getCurrentGitBranch(); + const wsName = currentBranch + ? await specificItems.resolveWsNameForGitBranch(currentBranch) + : null; const candidates = [contentBasePathJSON, contentBasePathYAML]; - if (currentBranch) { - // Add branch-specific candidates at the beginning (higher priority) + if (wsName) { + // Add workspace-specific candidates at the beginning (higher priority) const branchSpecificJSON = specificItems.toWorkspaceSpecificPath( contentBasePathJSON, - currentBranch + wsName ); const branchSpecificYAML = specificItems.toWorkspaceSpecificPath( contentBasePathYAML, - currentBranch + wsName ); candidates.unshift(branchSpecificJSON, branchSpecificYAML); } @@ -432,7 +437,7 @@ export async function handleFile( } catch { log.debug(`Script ${remotePath} does not exist on remote`); } - const content = await readFile(path, "utf-8"); + const content = await readTextFile(path); if (opts?.skipScriptsMetadata) { // if (codebase) { @@ -619,7 +624,7 @@ export async function readModulesFromDisk( } else if (entry.isFile() && !entry.name.endsWith(".lock") && !isEntryPointFile(entry.name, isTopLevel)) { // Skip lock files — they're handled as the `lock` field on ScriptModule if (exts.some((ext) => entry.name.endsWith(ext))) { - const content = fs.readFileSync(fullPath, "utf-8"); + const content = readTextFileSync(fullPath); const language = inferContentTypeFromFilePath(entry.name, defaultTs); // Check for an accompanying lock file (helper.lock) @@ -627,7 +632,7 @@ export async function readModulesFromDisk( const lockPath = path.join(dirPath, baseName + ".lock"); let lock: string | undefined; if (fs.existsSync(lockPath)) { - lock = fs.readFileSync(lockPath, "utf-8"); + lock = readTextFileSync(lockPath); } modules[relPath] = { @@ -958,7 +963,7 @@ export async function resolve(input: string): Promise> { input = new TextDecoder().decode(Buffer.concat(chunks)); } if (input[0] == "@") { - input = await readFile(input.substring(1), "utf-8"); + input = await readTextFile(input.substring(1)); } try { return JSON.parse(input); @@ -1404,7 +1409,7 @@ async function preview( const codebases = await listSyncCodebases(opts); const language = inferContentTypeFromFilePath(filePath, opts?.defaultTs); - const content = await readFile(filePath, "utf-8"); + const content = await readTextFile(filePath); const input = opts.data ? await resolve(opts.data) : {}; // Read modules from __mod/ folder if present diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 5cc7ba5ee9..d273baf28f 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -1,6 +1,6 @@ import { requireLogin } from "../../core/auth.ts"; import { fetchVersion, resolveWorkspace } from "../../core/context.ts"; -import { readFile, writeFile, readdir, stat, rm, copyFile, mkdir } from "node:fs/promises"; +import { writeFile, readdir, stat, rm, copyFile, mkdir } from "node:fs/promises"; import { mkdirSync, writeFileSync } from "node:fs"; import { colors } from "@cliffy/ansi/colors"; import { Command } from "@cliffy/command"; @@ -43,9 +43,11 @@ import { isFilesetResource, isRawAppFile, isWorkspaceDependencies, + readTextFile, } from "../../utils/utils.ts"; import { getEffectiveSettings, + getWorkspaceNames, mergeConfigWithConfigFile, parseSyncBehavior, SyncOptions, @@ -122,6 +124,27 @@ function resolveWsNameFromBranch(opts: SyncOptions, branchName: string): string return match ? match[0] : branchName; } +// Resolve wsNameForConfig from CLI flags. Prefers --branch → matching config key, +// then --workspace → matching config key (incl. when --base-url is set). Returns +// undefined when no flag-based resolution applies; callers then fall back to +// inferWsNameFromProfile on the resolved workspace profile. +export function resolveWsNameForConfigFromFlags( + opts: SyncOptions & { branch?: string; workspace?: string }, +): string | undefined { + if (opts.branch) { + return resolveWsNameFromBranch(opts, opts.branch); + } + if (opts.workspace) { + // Use getWorkspaceNames so reserved keys (e.g. commonSpecificItems) are filtered out, + // matching the behavior of findWorkspaceByGitBranch / inferWsNameFromProfile. + const validKeys = getWorkspaceNames(opts.workspaces); + if (validKeys.includes(opts.workspace)) { + return opts.workspace; + } + } + return undefined; +} + // Warn if --workspace overrides auto-detected branch or if workspace not in config. function warnWorkspaceOverride(opts: SyncOptions, wsNameForConfig: string | undefined): void { if (!wsNameForConfig || !opts.workspaces) return; @@ -326,7 +349,7 @@ export async function FSFSElement( } }, async getContentText(): Promise { - const content = await readFile(localP, "utf-8"); + const content = await readTextFile(localP); const itemPath = localP.substring(p.length + 1); const r = await addCodebaseDigestIfRelevant( itemPath, @@ -609,6 +632,48 @@ async function findFilesetResourceFile(changePath: string): Promise { throw new Error(`No resource metadata file found for fileset resource: ${changePath}`); } +type FilesetPushResult = + | { status: "pushed"; resourceFilePath: string } + | { status: "already-synced"; resourceFilePath: string } + | { status: "parent-missing" }; + +async function pushFilesetParentResource( + childPath: string, + workspaceId: string, + alreadySynced: string[], + cachedWsName: string | null, +): Promise { + let resourceFilePath: string; + try { + resourceFilePath = await findFilesetResourceFile(childPath); + } catch { + return { status: "parent-missing" }; + } + if (alreadySynced.includes(resourceFilePath)) { + return { status: "already-synced", resourceFilePath }; + } + alreadySynced.push(resourceFilePath); + + const newObj = parseFromPath( + resourceFilePath, + await readTextFile(resourceFilePath), + ); + + let serverPath = resourceFilePath; + if (cachedWsName && isWorkspaceSpecificFile(resourceFilePath)) { + serverPath = fromWorkspaceSpecificPath(resourceFilePath, cachedWsName); + } + + await pushResource( + workspaceId, + serverPath, + undefined, + newObj, + resourceFilePath, + ); + return { status: "pushed", resourceFilePath }; +} + function ZipFSElement( zip: JSZip, useYaml: boolean, @@ -1453,6 +1518,7 @@ export async function elementsToMap( path.endsWith(".mqtt_trigger" + ext) || path.endsWith(".sqs_trigger" + ext) || path.endsWith(".gcp_trigger" + ext) || + path.endsWith(".azure_trigger" + ext) || path.endsWith(".email_trigger" + ext) || path.endsWith("_native_trigger" + ext)) ) { @@ -1831,6 +1897,7 @@ function getOrderFromPath(p: string) { typ == "mqtt_trigger" || typ == "sqs_trigger" || typ == "gcp_trigger" || + typ == "azure_trigger" || typ == "email_trigger" || typ == "native_trigger" ) { @@ -2137,27 +2204,29 @@ export async function pull( // Resolve workspace name for config lookups. // --branch resolves git branch → workspace name (deprecated but still supported). - // --workspace (without --base-url) selects a workspace config entry by name. - // When --base-url is used with --workspace, --workspace is a profile selector only; - // --branch should still drive config lookups. + // --workspace selects a workspace config entry by name when it matches one, + // regardless of --base-url. If it doesn't match any entry it's treated as a + // profile/credential selector only. const hasExplicitCredentials = !!opts.baseUrl; let wsNameForConfig: string | undefined; - if (opts.branch) { - if (!hasExplicitCredentials && !branchDeprecationWarned) { - log.warn("⚠️ --branch/--env is deprecated. Use --workspace instead."); - branchDeprecationWarned = true; - } - wsNameForConfig = resolveWsNameFromBranch(opts, opts.branch); - } else if (opts.workspace && !hasExplicitCredentials) { - // --workspace without --base-url: use as workspace config name - wsNameForConfig = opts.workspace; - warnWorkspaceOverride(opts, wsNameForConfig); + if (opts.branch && !hasExplicitCredentials && !branchDeprecationWarned) { + log.warn("⚠️ --branch/--env is deprecated. Use --workspace instead."); + branchDeprecationWarned = true; } - // Validate workspace configuration early (skipped when override is used) + wsNameForConfig = resolveWsNameForConfigFromFlags(opts); + + if (!opts.branch && opts.workspace && !hasExplicitCredentials) { + // Warn if override doesn't match a config key, or mismatches the auto-detected branch + warnWorkspaceOverride(opts, opts.workspace); + } + + // Validate workspace configuration early. Skip when ANY explicit flag is set + // (even a --workspace value that doesn't match a config key — the user opted + // out of branch-based auto-detection). try { - await validateBranchConfiguration(opts, wsNameForConfig); + await validateBranchConfiguration(opts, wsNameForConfig ?? opts.workspace); } catch (error) { if (error instanceof Error && error.message.includes("overrides")) { log.error(error.message); @@ -2332,7 +2401,7 @@ export async function pull( if (change.name === "edited") { if (opts.stateful) { try { - const currentLocal = await readFile(target, "utf-8"); + const currentLocal = await readTextFile(target); if ( currentLocal !== change.before && currentLocal !== change.after @@ -2761,20 +2830,23 @@ export async function push( const hasExplicitCredentials = !!opts.baseUrl; let wsNameForConfig: string | undefined; - if (opts.branch) { - if (!hasExplicitCredentials && !branchDeprecationWarned) { - log.warn("⚠️ --branch/--env is deprecated. Use --workspace instead."); - branchDeprecationWarned = true; - } - wsNameForConfig = resolveWsNameFromBranch(opts, opts.branch); - } else if (opts.workspace && !hasExplicitCredentials) { - wsNameForConfig = opts.workspace; - warnWorkspaceOverride(opts, wsNameForConfig); + if (opts.branch && !hasExplicitCredentials && !branchDeprecationWarned) { + log.warn("⚠️ --branch/--env is deprecated. Use --workspace instead."); + branchDeprecationWarned = true; } - // Validate workspace configuration early (skipped when override is used) + wsNameForConfig = resolveWsNameForConfigFromFlags(opts); + + if (!opts.branch && opts.workspace && !hasExplicitCredentials) { + // Warn if override doesn't match a config key, or mismatches the auto-detected branch + warnWorkspaceOverride(opts, opts.workspace); + } + + // Validate workspace configuration early. Skip when ANY explicit flag is set + // (even a --workspace value that doesn't match a config key — the user opted + // out of branch-based auto-detection). try { - await validateBranchConfiguration(opts, wsNameForConfig); + await validateBranchConfiguration(opts, wsNameForConfig ?? opts.workspace); } catch (error) { if (error instanceof Error && error.message.includes("overrides")) { log.error(error.message); @@ -3172,7 +3244,7 @@ export async function push( } } const rules = folderRulesCache.get(folderName)!; - const remotePath = change.path.replace(/\.(script|schedule|http_trigger|websocket_trigger|kafka_trigger|nats_trigger|postgres_trigger|mqtt_trigger|sqs_trigger|gcp_trigger|email_trigger)\.(yaml|json)$/, "").replace(/(\.flow|__flow)\/flow\.(yaml|json)$/, "").replace(/\.(app|raw_app)(\/app\.(yaml|json))?$/, ""); + const remotePath = change.path.replace(/\.(script|schedule|http_trigger|websocket_trigger|kafka_trigger|nats_trigger|postgres_trigger|mqtt_trigger|sqs_trigger|gcp_trigger|azure_trigger|email_trigger)\.(yaml|json)$/, "").replace(/(\.flow|__flow)\/flow\.(yaml|json)$/, "").replace(/\.(app|raw_app)(\/app\.(yaml|json))?$/, ""); const relative = remotePath.slice(`f/${folderName}/`.length); if (!relative) continue; for (const rule of rules) { @@ -3390,7 +3462,7 @@ export async function push( const newObj = parseFromPath( resourceFilePath, - await readFile(resourceFilePath, "utf-8"), + await readTextFile(resourceFilePath), ); // For branch-specific resources, push to the base path on the workspace server @@ -3419,37 +3491,24 @@ export async function push( } } if (isFilesetResource(change.path)) { - const resourceFilePath = await findFilesetResourceFile(change.path); - if (!alreadySynced.includes(resourceFilePath)) { - alreadySynced.push(resourceFilePath); - - const newObj = parseFromPath( - resourceFilePath, - await readFile(resourceFilePath, "utf-8"), - ); - - let serverPath = resourceFilePath; - const currentBranch = cachedWsNameForPush; - - if (currentBranch && isWorkspaceSpecificFile(resourceFilePath)) { - serverPath = fromWorkspaceSpecificPath( - resourceFilePath, - currentBranch, - ); - } - - await pushResource( - workspace.workspaceId, - serverPath, - undefined, - newObj, - resourceFilePath, + const result = await pushFilesetParentResource( + change.path, + workspace.workspaceId, + alreadySynced, + cachedWsNameForPush, + ); + if (result.status === "parent-missing") { + throw new Error( + `No resource metadata file found for fileset resource: ${change.path}`, ); + } + if (result.status === "pushed") { if (stateTarget) { await writeFile(stateTarget, change.after, "utf-8"); } continue; } + // "already-synced": fall through (pre-existing behavior). } const oldObj = parseFromPath(change.path, change.before); const newObj = parseFromPath(change.path, change.after); @@ -3480,12 +3539,23 @@ export async function push( await writeFile(stateTarget, change.after, "utf-8"); } } else if (change.name === "added") { + if (isFilesetResource(change.path)) { + // Re-push the parent resource (guarded by alreadySynced). + // Parent-missing means the parent itself is also being added and + // its own change will push the full fileset — safe to skip. + await pushFilesetParentResource( + change.path, + workspace.workspaceId, + alreadySynced, + cachedWsNameForPush, + ); + continue; + } if ( change.path.endsWith(".script.json") || change.path.endsWith(".script.yaml") || change.path.endsWith(".lock") || - isFileResource(change.path) || - isFilesetResource(change.path) + isFileResource(change.path) ) { continue; } else if ( @@ -3567,6 +3637,18 @@ export async function push( ); continue; } + if (isFilesetResource(change.path)) { + // Re-push the parent resource (guarded by alreadySynced). + // Parent-missing means the parent itself is also being deleted + // and its own "deleted" change removes the whole resource. + await pushFilesetParentResource( + change.path, + workspace.workspaceId, + alreadySynced, + cachedWsNameForPush, + ); + continue; + } const typ = getTypeStrFromPath(change.path); if (typ == "script") { @@ -3795,6 +3877,12 @@ export async function push( path: removeSuffix(target, ".gcp_trigger.json"), }); break; + case "azure_trigger": + await wmill.deleteAzureTrigger({ + workspace: workspaceId, + path: removeSuffix(target, ".azure_trigger.json"), + }); + break; case "email_trigger": await wmill.deleteEmailTrigger({ workspace: workspaceId, diff --git a/cli/src/commands/trigger/trigger.ts b/cli/src/commands/trigger/trigger.ts index df2657427c..1cadc8d9d2 100644 --- a/cli/src/commands/trigger/trigger.ts +++ b/cli/src/commands/trigger/trigger.ts @@ -5,6 +5,7 @@ import { stringify as yamlStringify } from "yaml"; import * as wmill from "../../../gen/services.gen.ts"; import { GcpTrigger, + AzureTrigger, HttpTrigger, KafkaTrigger, MqttTrigger, @@ -33,6 +34,7 @@ import { import { fromWorkspaceSpecificPath, isWorkspaceSpecificFile, + resolveWsNameForGitBranch, } from "../../core/specific_items.ts"; import { getCurrentGitBranch } from "../../utils/git.ts"; import { requireLogin } from "../../core/auth.ts"; @@ -48,6 +50,7 @@ type Trigger = { mqtt: MqttTrigger; sqs: SqsTrigger; gcp: GcpTrigger; + azure: AzureTrigger; email: EmailTrigger; }; @@ -83,6 +86,7 @@ async function getTrigger( mqtt: wmill.getMqttTrigger, sqs: wmill.getSqsTrigger, gcp: wmill.getGcpTrigger, + azure: wmill.getAzureTrigger, email: wmill.getEmailTrigger, }; const triggerFunction = triggerFunctions[triggerType]; @@ -112,6 +116,7 @@ async function updateTrigger( mqtt: wmill.updateMqttTrigger, sqs: wmill.updateSqsTrigger, gcp: wmill.updateGcpTrigger, + azure: wmill.updateAzureTrigger, email: wmill.updateEmailTrigger, }; const triggerFunction = triggerFunctions[triggerType]; @@ -139,6 +144,7 @@ async function createTrigger( mqtt: wmill.createMqttTrigger, sqs: wmill.createSqsTrigger, gcp: wmill.createGcpTrigger, + azure: wmill.createAzureTrigger, email: wmill.createEmailTrigger, }; const triggerFunction = triggerFunctions[triggerType]; @@ -393,6 +399,15 @@ const triggerTemplates: Record> = { subscription_mode: "create_update", enabled: false, }, + azure: { + script_path: "", + is_flow: false, + azure_resource_path: "", + azure_mode: "namespace_pull", + scope_resource_id: "", + subscription_name: "", + enabled: false, + }, email: { script_path: "", is_flow: false, @@ -523,6 +538,7 @@ async function list(opts: GlobalOptions & { json?: boolean }) { mqttTriggers, sqsTriggers, gcpTriggers, + azureTriggers, emailTriggers, ] = await Promise.all([ listOrEmpty(() => wmill.listHttpTriggers({ workspace: ws })), @@ -533,6 +549,7 @@ async function list(opts: GlobalOptions & { json?: boolean }) { listOrEmpty(() => wmill.listMqttTriggers({ workspace: ws })), listOrEmpty(() => wmill.listSqsTriggers({ workspace: ws })), listOrEmpty(() => wmill.listGcpTriggers({ workspace: ws })), + listOrEmpty(() => wmill.listAzureTriggers({ workspace: ws })), listOrEmpty(() => wmill.listEmailTriggers({ workspace: ws })), ]); const triggers = [ @@ -544,6 +561,7 @@ async function list(opts: GlobalOptions & { json?: boolean }) { ...mqttTriggers.map((x) => ({ path: x.path, kind: "mqtt" })), ...sqsTriggers.map((x) => ({ path: x.path, kind: "sqs" })), ...gcpTriggers.map((x) => ({ path: x.path, kind: "gcp" })), + ...azureTriggers.map((x) => ({ path: x.path, kind: "azure" })), ...emailTriggers.map((x) => ({ path: x.path, kind: "email" })), ]; @@ -567,14 +585,17 @@ function checkIfValidTrigger(kind: string | undefined): kind is TriggerType { } } -function extractTriggerKindFromPath(filePath: string): string | undefined { +async function extractTriggerKindFromPath(filePath: string): Promise { let pathToAnalyze = filePath; - // If this is a branch-specific file, convert it to the base path first + // If this is a workspace-specific file, convert it to the base path first. + // Resolve the wmill.yaml config key for the current branch (falls back to + // the branch name when no matching workspace entry exists). if (isWorkspaceSpecificFile(filePath)) { const currentBranch = getCurrentGitBranch(); if (currentBranch) { - pathToAnalyze = fromWorkspaceSpecificPath(filePath, currentBranch); + const wsName = await resolveWsNameForGitBranch(currentBranch); + pathToAnalyze = fromWorkspaceSpecificPath(filePath, wsName); } } @@ -598,7 +619,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { console.log(colors.bold.yellow("Pushing trigger...")); - const triggerKind = extractTriggerKindFromPath(filePath); + const triggerKind = await extractTriggerKindFromPath(filePath); if (!checkIfValidTrigger(triggerKind)) { throw new Error("Invalid trigger kind: " + triggerKind); } @@ -622,11 +643,11 @@ const command = new Command() .command("get", "get a trigger's details") .arguments("") .option("--json", "Output as JSON (for piping to jq)") - .option("--kind ", "Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup") + .option("--kind ", "Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email). Recommended for faster lookup") .action(get as any) .command("new", "create a new trigger locally") .arguments("") - .option("--kind ", "Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email)") + .option("--kind ", "Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email)") .action(newTrigger as any) .command( "push", @@ -641,7 +662,7 @@ const command = new Command() .arguments(" ") .option( "--kind ", - "Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email)" + "Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email)" ) .action((async (opts: any, triggerPath: string, email: string) => { const workspace = await resolveWorkspace(opts); diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index d2bc2a5bbc..d8cc16b01b 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -1,4 +1,5 @@ -import { readFile, writeFile, open as fsOpen } from "node:fs/promises"; +import { writeFile, open as fsOpen } from "node:fs/promises"; +import { readTextFile } from "../../utils/utils.ts"; import process from "node:process"; import { GlobalOptions } from "../../types.ts"; import { @@ -31,7 +32,7 @@ export async function allWorkspaces( ): Promise { try { const file = await getWorkspaceConfigFilePath(configDirOverride); - const txt = await readFile(file, "utf-8"); + const txt = await readTextFile(file); return txt .split("\n") .map((line) => { @@ -55,7 +56,7 @@ async function getActiveWorkspaceName( } try { const file = await getActiveWorkspaceConfigFilePath(opts?.configDir); - return await readFile(file, "utf-8"); + return await readTextFile(file); } catch { return undefined; } diff --git a/cli/src/core/branch-profiles.ts b/cli/src/core/branch-profiles.ts index 80c72b0705..bacbcc51aa 100644 --- a/cli/src/core/branch-profiles.ts +++ b/cli/src/core/branch-profiles.ts @@ -1,5 +1,6 @@ import * as log from "./log.ts"; -import { readFile, writeFile } from "node:fs/promises"; +import { writeFile } from "node:fs/promises"; +import { readTextFile } from "../utils/utils.ts"; import { getStore } from "./store.ts"; export interface BranchProfileMapping { @@ -17,7 +18,7 @@ export async function getBranchProfilesPath(configDirOverride?: string): Promise export async function loadBranchProfiles(configDirOverride?: string): Promise { try { const path = await getBranchProfilesPath(configDirOverride); - const content = await readFile(path, "utf-8"); + const content = await readTextFile(path); return JSON.parse(content); } catch { // File doesn't exist or invalid JSON - return empty mapping diff --git a/cli/src/core/specific_items.ts b/cli/src/core/specific_items.ts index 3d0d9df248..fbdffad153 100644 --- a/cli/src/core/specific_items.ts +++ b/cli/src/core/specific_items.ts @@ -4,10 +4,22 @@ import { isFileResource, isFilesetResource } from "../utils/utils.ts"; import { SyncOptions, findWorkspaceByGitBranch, + readConfigFile, WorkspaceEntryConfig, } from "./conf.ts"; import { TRIGGER_TYPES } from "../types.ts"; +/** + * Resolve the effective workspace name (wmill.yaml config key) for a given + * git branch. Falls back to the branch name itself when no matching workspace + * entry exists (legacy behavior). + */ +export async function resolveWsNameForGitBranch(branchName: string): Promise { + const config = await readConfigFile({ warnIfMissing: false }); + const match = findWorkspaceByGitBranch(config.workspaces, branchName); + return match ? match[0] : branchName; +} + export interface SpecificItemsConfig { variables?: string[]; resources?: string[]; diff --git a/cli/src/guidance/skills.ts b/cli/src/guidance/skills.ts index 78048646d8..6b881baa58 100644 --- a/cli/src/guidance/skills.ts +++ b/cli/src/guidance/skills.ts @@ -4659,7 +4659,7 @@ Reference a specific resource using \`$res:\` prefix: ## OpenFlow Schema -{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \\"yellow\\", \\"#ffff00\\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \\u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\\nValid values: 'text' (default) - plain text response, 'image' - image generation\\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\\nFormat: Array<{ bucket: string, key: string }> - S3 object references\\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\\nRange: 0.0 to 2.0 (provider-dependent)\\n- 0.0 = deterministic, focused responses\\n- 0.7 = balanced (common default)\\n- 1.0+ = more creative/random\\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}}`, +{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \\"yellow\\", \\"#ffff00\\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \\u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\\nValid values: 'text' (default) - plain text response, 'image' - image generation\\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\\nFormat: Array<{ bucket: string, key: string }> - S3 object references\\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\\nRange: 0.0 to 2.0 (provider-dependent)\\n- 0.0 = deterministic, focused responses\\n- 0.7 = balanced (common default)\\n- 1.0+ = more creative/random\\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"omit_output_from_conversation":{"type":"boolean","default":false,"description":"If true, this AI agent step does not persist its assistant or tool messages to the flow conversation when chat mode is enabled."},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}}`, "raw-app": `--- name: raw-app description: MUST use when creating raw apps. @@ -5763,12 +5763,12 @@ trigger related commands - \`--json\` - Output as JSON (for piping to jq) - \`trigger get \` - get a trigger's details - \`--json\` - Output as JSON (for piping to jq) - - \`--kind \` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup + - \`--kind \` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email). Recommended for faster lookup - \`trigger new \` - create a new trigger locally - - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) - \`trigger push \` - push a local trigger spec. This overrides any remote versions. - \`trigger set-permissioned-as \` - Set the email (run-as user) for a trigger (requires admin or wm_deployers group) - - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) ### user @@ -5876,6 +5876,91 @@ workspace related commands // YAML schema content for triggers and schedules export const SCHEMAS: Record = { + "azure_trigger": `type: object +properties: + script_path: + type: string + description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) + is_flow: + type: boolean + description: True if script_path points to a flow, false if it points to a script + labels: + type: array + items: + type: string + azure_resource_path: + type: string + azure_mode: + type: string + enum: + - basic_push + - namespace_push + - namespace_pull + description: Azure Event Grid trigger mode. + scope_resource_id: + type: string + description: ARM resource ID of the topic (basic) or namespace (namespace modes). + topic_name: + type: string + description: Topic name within the namespace (namespace modes only). + subscription_name: + type: string + event_type_filters: + type: array + items: + type: string + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + retry: + type: object + properties: + constant: + type: object + description: Retry with constant delay between attempts + properties: + attempts: + type: integer + description: Number of retry attempts + seconds: + type: integer + description: Seconds to wait between retries + exponential: + type: object + description: Retry with exponential backoff (delay doubles each time) + properties: + attempts: + type: integer + description: Number of retry attempts + multiplier: + type: integer + description: Multiplier for exponential backoff + seconds: + type: integer + minimum: 1 + description: Initial delay in seconds + random_factor: + type: integer + minimum: 0 + maximum: 100 + description: Random jitter percentage (0-100) to avoid thundering herd + retry_if: + $ref: '#/components/schemas/RetryIf' + description: Retry configuration for failed module executions +required: +- script_path +- permissioned_as +- is_flow +- azure_resource_path +- azure_mode +- scope_resource_id +- subscription_name +`, "gcp_trigger": `type: object properties: script_path: @@ -6810,6 +6895,7 @@ export const SCHEMA_MAPPINGS: Record = { { name: "MqttTrigger", schemaKey: "mqtt_trigger", filePattern: "*.mqtt_trigger.yaml" }, { name: "SqsTrigger", schemaKey: "sqs_trigger", filePattern: "*.sqs_trigger.yaml" }, { name: "GcpTrigger", schemaKey: "gcp_trigger", filePattern: "*.gcp_trigger.yaml" }, + { name: "AzureTrigger", schemaKey: "azure_trigger", filePattern: "*.azure_trigger.yaml" }, ], "schedules": [ { name: "Schedule", schemaKey: "schedule", filePattern: "*.schedule.yaml" }, diff --git a/cli/src/guidance/writer.ts b/cli/src/guidance/writer.ts index 9f694bc039..2ac31a0095 100644 --- a/cli/src/guidance/writer.ts +++ b/cli/src/guidance/writer.ts @@ -1,4 +1,5 @@ -import { cp, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises"; +import { cp, mkdir, readdir, stat, writeFile } from "node:fs/promises"; +import { readTextFile } from "../utils/utils.ts"; import { join } from "node:path"; import { generateAgentsMdContent } from "./core.ts"; import { @@ -52,7 +53,7 @@ export async function writeAiGuidanceFiles( overwrite: options.overwriteProjectGuidance ?? false, content: options.agentsSourcePath != null - ? await readFile(options.agentsSourcePath, "utf8") + ? await readTextFile(options.agentsSourcePath) : generateAgentsMdContent(buildSkillsReference(skillMetadata)), }); @@ -63,7 +64,7 @@ export async function writeAiGuidanceFiles( overwrite: options.overwriteProjectGuidance ?? false, content: options.claudeSourcePath != null - ? await readFile(options.claudeSourcePath, "utf8") + ? await readTextFile(options.claudeSourcePath) : CLAUDE_MD_DEFAULT, }); @@ -211,7 +212,7 @@ async function readSkillMetadataFromDirectory(skillsDir: string): Promise { try { - return await readFile(scriptPath + ".script.lock", "utf-8"); + return await readTextFile(scriptPath + ".script.lock"); } catch { return undefined; } @@ -138,7 +139,7 @@ export async function resolvePreviewLocalScriptState( return { filePath, - content: await readFile(filePath, "utf-8"), + content: await readTextFile(filePath), language, lock: normalizeOptionalLock(rawLock), tag: metadata?.payload?.tag, diff --git a/cli/src/utils/metadata.ts b/cli/src/utils/metadata.ts index 26e050d5b2..e4032d4857 100644 --- a/cli/src/utils/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -4,7 +4,7 @@ import { colors } from "@cliffy/ansi/colors"; import * as log from "../core/log.ts"; import { stringify as yamlStringify } from "yaml"; import { yamlParseFile } from "./yaml.ts"; -import { readFile, writeFile, stat, rm, readdir } from "node:fs/promises"; +import { writeFile, stat, rm, readdir } from "node:fs/promises"; import { readFileSync, existsSync, readdirSync, statSync, mkdirSync, writeFileSync } from "node:fs"; import * as path from "node:path"; import { createRequire } from "node:module"; @@ -21,7 +21,7 @@ import { import { inferContentTypeFromFilePath } from "./script_common.ts"; import { getModuleFolderSuffix, isModuleEntryPoint, getScriptBasePathFromModulePath } from "./resource_folders.ts"; import { findCodebase, yamlOptions } from "../commands/sync/sync.ts"; -import { generateHash, readInlinePathSync, getHeaders } from "./utils.ts"; +import { generateHash, readInlinePathSync, getHeaders, readTextFile, readTextFileSync } from "./utils.ts"; import { SyncCodebase } from "./codebase.ts"; import { argSigToJsonSchemaType } from "../../windmill-utils-internal/src/parse/parse-schema.ts"; @@ -66,7 +66,7 @@ export async function getRawWorkspaceDependencies(legacyBehaviour: boolean): Pro if (entry.isDirectory()) continue; const filePath = `dependencies/${entry.name}`; - const content = await readFile(filePath, "utf-8"); + const content = await readTextFile(filePath); // Find matching language for (const lang of workspaceDependenciesLanguages) { @@ -153,7 +153,7 @@ export async function filterWorkspaceDependenciesForScripts( if (content.startsWith("!inline ")) { const filePath = folder + sep + content.replace("!inline ", ""); try { - content = await readFile(filePath, "utf-8"); + content = await readTextFile(filePath); } catch { continue; } @@ -212,8 +212,8 @@ export async function generateScriptMetadataInternal( ); // read script content - const scriptContent = await readFile(scriptPath, "utf-8"); - const metadataContent = await readFile(metadataWithType.path, "utf-8"); + const scriptContent = await readTextFile(scriptPath); + const metadataContent = await readTextFile(metadataWithType.path); const filteredRawWorkspaceDependencies = filterWorkspaceDependencies( rawWorkspaceDependencies, @@ -744,7 +744,7 @@ async function updateModuleLocks( if (!changedModules.includes(normalizedRelPath)) continue; } - const moduleContent = readFileSync(fullPath, "utf-8"); + const moduleContent = readTextFileSync(fullPath); const moduleRemotePath = scriptRemotePath + "/" + relPath; log.debug(`Generating lock for module ${relPath}`); @@ -986,7 +986,7 @@ export async function parseMetadataFileIfExists( let metadataFilePath = scriptPath + ".script.json"; try { await stat(metadataFilePath); - const payload = JSON.parse(await readFile(metadataFilePath, "utf-8")); + const payload = JSON.parse(await readTextFile(metadataFilePath)); replaceLock(payload); return { path: metadataFilePath, @@ -1028,7 +1028,7 @@ export async function parseMetadataFile( await stat(metadataFilePath); return { path: metadataFilePath, - payload: JSON.parse(await readFile(metadataFilePath, "utf-8")), + payload: JSON.parse(await readTextFile(metadataFilePath)), isJson: true, }; } catch { @@ -1051,7 +1051,7 @@ export async function parseMetadataFile( await stat(metadataFilePath); return { path: metadataFilePath, - payload: JSON.parse(await readFile(metadataFilePath, "utf-8")), + payload: JSON.parse(await readTextFile(metadataFilePath)), isJson: true, }; } catch { @@ -1229,7 +1229,7 @@ async function computeModuleHashes( } catch { continue; } - const content = readFileSync(fullPath, "utf-8"); + const content = readTextFileSync(fullPath); const normalizedPath = normalizeLockPath(relPath); hashes[normalizedPath] = await generateHash( content + JSON.stringify(rawWorkspaceDependencies) diff --git a/cli/src/utils/utils.ts b/cli/src/utils/utils.ts index eb8babfff3..ff9fe9b78c 100644 --- a/cli/src/utils/utils.ts +++ b/cli/src/utils/utils.ts @@ -131,9 +131,53 @@ export async function generateHashFromBuffer( return Buffer.from(hashBuffer).toString("hex"); } +function decodeBufferAsUtf8(buf: Buffer, path: string | URL): string { + if (buf.length >= 2) { + if (buf[0] === 0xff && buf[1] === 0xfe) { + if (buf.length >= 4 && buf[2] === 0x00 && buf[3] === 0x00) { + throw new Error( + `File ${path} is encoded as UTF-32 LE, which is not supported. Please convert it to UTF-8.` + ); + } + throw new Error( + `File ${path} is encoded as UTF-16 LE, which is not supported. Please convert it to UTF-8.` + ); + } + if (buf[0] === 0xfe && buf[1] === 0xff) { + throw new Error( + `File ${path} is encoded as UTF-16 BE, which is not supported. Please convert it to UTF-8.` + ); + } + if (buf.length >= 4 && buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0xfe && buf[3] === 0xff) { + throw new Error( + `File ${path} is encoded as UTF-32 BE, which is not supported. Please convert it to UTF-8.` + ); + } + } + if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) { + return buf.subarray(3).toString("utf-8"); + } + return buf.toString("utf-8"); +} + +export function stripBom(content: string): string { + if (content.charCodeAt(0) === 0xfeff) { + return content.slice(1); + } + return content; +} + +export async function readTextFile(path: string | URL): Promise { + return decodeBufferAsUtf8(await readFile(path), path); +} + +export function readTextFileSync(path: string | URL): string { + return decodeBufferAsUtf8(readFileSync(path), path); +} + export function readInlinePathSync(path: string): string { try { - return readFileSync(path.replaceAll("/", SEP), "utf-8"); + return readTextFileSync(path.replaceAll("/", SEP)); } catch (error) { log.warn(`Error reading inline path: ${path}, ${error}`); return ""; @@ -253,7 +297,7 @@ export async function getIsWin(): Promise { */ export function writeIfChanged(path: string, content: string): boolean { try { - const existing = readFileSync(path, "utf-8"); + const existing = readTextFileSync(path); if (existing === content) { return false; // Content unchanged, skip write } diff --git a/cli/src/utils/yaml.ts b/cli/src/utils/yaml.ts index 52ec682067..566537d58e 100644 --- a/cli/src/utils/yaml.ts +++ b/cli/src/utils/yaml.ts @@ -1,6 +1,6 @@ import { parse as yamlParse } from "yaml"; import type { ParseOptions, DocumentOptions, SchemaOptions, ToJSOptions, ScalarTag } from "yaml"; -import { readFile } from "node:fs/promises"; +import { readTextFile } from "./utils.ts"; // Custom YAML tags that resolve `!inline value` and `!inline_fileset value` // back to their string-prefix form ("!inline value"). @@ -26,7 +26,7 @@ type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOpt export async function yamlParseFile(path: string, options: YamlParseOptions = {}) { try { - return yamlParse(await readFile(path, "utf-8"), { + return yamlParse(await readTextFile(path), { ...options, customTags: [...WINDMILL_CUSTOM_TAGS, ...((options.customTags as ScalarTag[] | undefined) ?? [])], }); diff --git a/cli/test/folder_default_permissioned_as.test.ts b/cli/test/folder_default_permissioned_as.test.ts index 5af16d3355..7116fb9fa2 100644 --- a/cli/test/folder_default_permissioned_as.test.ts +++ b/cli/test/folder_default_permissioned_as.test.ts @@ -216,8 +216,8 @@ describe("folder default_permissioned_as", () => { // Step 3: Locally edit folder.meta.yaml to add default_permissioned_as rules const metaPath = join(tempDir, "f", folderName, "folder.meta.yaml"); const metaContent = await readFile(metaPath, "utf-8"); - // Backend always emits the field; initially it's an empty array - expect(metaContent).toContain("default_permissioned_as: []"); + // Backend omits the field when the rule list is empty + expect(metaContent).not.toContain("default_permissioned_as:"); const newMeta = `display_name: ${folderName} owners: diff --git a/cli/test/list_get_new_commands.test.ts b/cli/test/list_get_new_commands.test.ts index df3d109630..bd574b1998 100644 --- a/cli/test/list_get_new_commands.test.ts +++ b/cli/test/list_get_new_commands.test.ts @@ -668,4 +668,32 @@ describe("new command", () => { expect(content).toContain("topics"); }); }); + + test("trigger new --kind azure creates azure trigger yaml template", async () => { + await withTestBackend(async (backend, tempDir) => { + await setupWorkspaceProfile(backend); + + await mkdir(join(tempDir, "f", "test"), { recursive: true }); + + const result = await backend.runCLICommand( + ["trigger", "new", "f/test/azure_trigger", "--kind", "azure"], + tempDir + ); + + expect(result.code).toEqual(0); + + const filePath = join( + tempDir, + "f/test/azure_trigger.azure_trigger.yaml" + ); + const fileStat = await stat(filePath); + expect(fileStat.isFile()).toBe(true); + + const content = await readFile(filePath, "utf-8"); + expect(content).toContain("azure_resource_path"); + expect(content).toContain("azure_mode"); + expect(content).toContain("scope_resource_id"); + expect(content).toContain("subscription_name"); + }); + }); }); diff --git a/cli/test/utils_unit.test.ts b/cli/test/utils_unit.test.ts index e5bcea4b92..de95848524 100644 --- a/cli/test/utils_unit.test.ts +++ b/cli/test/utils_unit.test.ts @@ -4,7 +4,10 @@ */ import { expect, test, describe } from "bun:test"; -import { deepEqual, isFileResource, isFilesetResource, toCamel, capitalize, validateRequiredArgs } from "../src/utils/utils.ts"; +import { deepEqual, isFileResource, isFilesetResource, toCamel, capitalize, validateRequiredArgs, stripBom, readTextFile, readTextFileSync } from "../src/utils/utils.ts"; +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { getTypeStrFromPath, removeType, @@ -634,6 +637,71 @@ describe("validateRequiredArgs", () => { }); }); +// ============================================================================= +// BOM handling +// ============================================================================= + +describe("stripBom", () => { + test("strips UTF-8 BOM", () => { + expect(stripBom("hello")).toBe("hello"); + }); + + test("returns input unchanged when no BOM", () => { + expect(stripBom("hello")).toBe("hello"); + expect(stripBom("")).toBe(""); + }); +}); + +describe("readTextFile / readTextFileSync", () => { + const tmp = mkdtempSync(join(tmpdir(), "wmill-bom-")); + + test("reads plain UTF-8 file", async () => { + const f = join(tmp, "plain.txt"); + writeFileSync(f, Buffer.from("hello world", "utf-8")); + expect(await readTextFile(f)).toBe("hello world"); + expect(readTextFileSync(f)).toBe("hello world"); + }); + + test("strips UTF-8 BOM", async () => { + const f = join(tmp, "bom.txt"); + writeFileSync(f, Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from("hello", "utf-8")])); + expect(await readTextFile(f)).toBe("hello"); + expect(readTextFileSync(f)).toBe("hello"); + }); + + test("throws on UTF-16 LE BOM", async () => { + const f = join(tmp, "utf16le.txt"); + writeFileSync(f, Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from("hello", "utf16le")])); + await expect(readTextFile(f)).rejects.toThrow(/UTF-16 LE/); + expect(() => readTextFileSync(f)).toThrow(/UTF-16 LE/); + }); + + test("throws on UTF-16 BE BOM", async () => { + const f = join(tmp, "utf16be.txt"); + writeFileSync(f, Buffer.from([0xfe, 0xff, 0x00, 0x68])); + await expect(readTextFile(f)).rejects.toThrow(/UTF-16 BE/); + expect(() => readTextFileSync(f)).toThrow(/UTF-16 BE/); + }); + + test("throws on UTF-32 LE BOM", async () => { + const f = join(tmp, "utf32le.txt"); + writeFileSync(f, Buffer.from([0xff, 0xfe, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00])); + await expect(readTextFile(f)).rejects.toThrow(/UTF-32 LE/); + }); + + test("empty file reads as empty string", async () => { + const f = join(tmp, "empty.txt"); + writeFileSync(f, Buffer.alloc(0)); + expect(await readTextFile(f)).toBe(""); + expect(readTextFileSync(f)).toBe(""); + }); + + // cleanup + test("cleanup", () => { + rmSync(tmp, { recursive: true, force: true }); + }); +}); + // ============================================================================= // TarAsZip adapter // ============================================================================= diff --git a/cli/test/workspace_key_filename_integration.test.ts b/cli/test/workspace_key_filename_integration.test.ts new file mode 100644 index 0000000000..ae22f191bf --- /dev/null +++ b/cli/test/workspace_key_filename_integration.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, test } from "bun:test"; +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { execSync } from "node:child_process"; +import os from "node:os"; +import path from "node:path"; +import { stringify as yamlStringify } from "yaml"; + +import { resolveWsNameForGitBranch } from "../src/core/specific_items.ts"; +import { findResourceFile } from "../src/commands/script/script.ts"; +import { resolveWsNameForConfigFromFlags } from "../src/commands/sync/sync.ts"; +import type { SyncOptions } from "../src/core/conf.ts"; + +// Integration tests covering the bug where workspace-specific filenames used +// the raw git branch name instead of the wmill.yaml workspace config key. +// A per-item helper (findResourceFile) now resolves the effective wsName from +// wmill.yaml before falling back to the branch name. + +async function withGitRepoAndConfig( + config: unknown, + branch: string, + fn: (tempDir: string) => Promise, +): Promise { + const tempDir = await mkdtemp(path.join(os.tmpdir(), "wmill_wskey_")); + const originalCwd = process.cwd(); + try { + // Set up a minimal git repo on the target branch so getCurrentGitBranch() + // returns the expected value. An initial commit is needed so HEAD points + // somewhere and `git rev-parse --abbrev-ref HEAD` succeeds. + execSync(`git init -q -b ${branch}`, { cwd: tempDir }); + execSync(`git config user.email test@example.com`, { cwd: tempDir }); + execSync(`git config user.name test`, { cwd: tempDir }); + + await writeFile( + path.join(tempDir, "wmill.yaml"), + yamlStringify(config), + ); + + execSync(`git add wmill.yaml && git commit -q -m init`, { cwd: tempDir }); + + process.chdir(tempDir); + await fn(tempDir); + } finally { + process.chdir(originalCwd); + await rm(tempDir, { recursive: true, force: true }); + } +} + +describe("resolveWsNameForGitBranch", () => { + test("returns the wmill.yaml config key for a branch matched via gitBranch field", async () => { + await withGitRepoAndConfig( + { + workspaces: { + myKey: { gitBranch: "main", workspaceId: "prod" }, + }, + }, + "main", + async () => { + const wsName = await resolveWsNameForGitBranch("main"); + expect(wsName).toEqual("myKey"); + }, + ); + }); + + test("returns the wmill.yaml config key when the branch equals the key and gitBranch is not set", async () => { + await withGitRepoAndConfig( + { + workspaces: { + staging: { workspaceId: "stg_workspace" }, + }, + }, + "staging", + async () => { + const wsName = await resolveWsNameForGitBranch("staging"); + expect(wsName).toEqual("staging"); + }, + ); + }); + + test("falls back to the branch name when no matching workspace entry exists", async () => { + await withGitRepoAndConfig( + { + workspaces: { + production: { gitBranch: "main" }, + }, + }, + "feature-x", + async () => { + const wsName = await resolveWsNameForGitBranch("feature-x"); + expect(wsName).toEqual("feature-x"); + }, + ); + }); +}); + +describe("resolveWsNameForConfigFromFlags", () => { + test("--workspace matching a config key resolves, even with --base-url", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + workspace: "test", + baseUrl: "http://127.0.0.1:8080/", + workspaces: { + test: { gitBranch: "main" }, + prod: { gitBranch: "main" }, + }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toEqual("test"); + }); + + test("--workspace not in config with --base-url returns undefined (treat as ad-hoc credential)", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + workspace: "adhocWorkspaceId", + baseUrl: "https://other.windmill.dev/", + workspaces: { + test: { gitBranch: "main" }, + }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toBeUndefined(); + }); + + test("--workspace matching config key resolves even without --base-url", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + workspace: "prod", + workspaces: { test: {}, prod: {} }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toEqual("prod"); + }); + + test("--branch takes precedence and looks up by gitBranch", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + branch: "main", + workspace: "someOtherKey", + workspaces: { + test: { gitBranch: "main" }, + prod: { gitBranch: "release" }, + }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toEqual("test"); + }); + + test("no flags returns undefined", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + workspaces: { test: {} }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toBeUndefined(); + }); + + test("reserved key 'commonSpecificItems' is not accepted as a config key", () => { + const opts: SyncOptions & { branch?: string; workspace?: string } = { + workspace: "commonSpecificItems", + workspaces: { + test: {}, + commonSpecificItems: { variables: ["f/**"] } as any, + }, + } as any; + expect(resolveWsNameForConfigFromFlags(opts)).toBeUndefined(); + }); +}); + +describe("findResourceFile picks the wsName-named file, not the branch-named file", () => { + test("finds the workspace-specific resource file using the config key as suffix", async () => { + await withGitRepoAndConfig( + { + workspaces: { + myKey: { gitBranch: "main", workspaceId: "prod" }, + }, + }, + "main", + async (tempDir) => { + // Only the config-key-named file exists on disk. A wmill without this + // fix would look for `f/foo.main.resource.yaml` (the branch name) and + // either miss this file or pick a stale branch-named file. + await mkdir(path.join(tempDir, "f"), { recursive: true }); + await writeFile( + path.join(tempDir, "f/foo.myKey.resource.yaml"), + "value: {}\nresource_type: text\n", + ); + + const found = await findResourceFile("f/foo.resource.file.txt"); + expect(found).toEqual("f/foo.myKey.resource.yaml"); + }, + ); + }); +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 21885edc01..66fa2daca1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.688.0", + "version": "1.690.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.688.0", + "version": "1.690.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -844,6 +844,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -855,6 +856,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -865,6 +867,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1354,6 +1357,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1510,6 +1514,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1526,6 +1531,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1542,6 +1548,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1558,6 +1565,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1574,6 +1582,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1590,6 +1599,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1606,6 +1616,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1622,6 +1633,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1638,6 +1650,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1654,6 +1667,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1670,6 +1684,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1686,6 +1701,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1702,6 +1718,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1718,6 +1735,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1734,6 +1752,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2039,6 +2058,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6814,7 +6834,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -7313,6 +7333,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7333,6 +7354,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7353,6 +7375,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7373,6 +7396,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7393,6 +7417,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7413,6 +7438,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7433,6 +7459,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7453,6 +7480,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7473,6 +7501,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7493,6 +7522,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7513,6 +7543,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -12081,6 +12112,21 @@ } } }, + "node_modules/svelte-check/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -12811,7 +12857,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/frontend/package.json b/frontend/package.json index a09dcf7c8f..3519262416 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.688.0", + "version": "1.690.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/CompareWorkspaces.svelte b/frontend/src/lib/components/CompareWorkspaces.svelte index 3ace24d1fb..d9e94264a9 100644 --- a/frontend/src/lib/components/CompareWorkspaces.svelte +++ b/frontend/src/lib/components/CompareWorkspaces.svelte @@ -25,6 +25,7 @@ EmailTriggerService, FlowService, FolderService, + AzureTriggerService, GcpTriggerService, HttpTriggerService, KafkaTriggerService, @@ -53,6 +54,7 @@ import MqttTriggerEditor from './triggers/mqtt/MqttTriggerEditor.svelte' import SqsTriggerEditor from './triggers/sqs/SqsTriggerEditor.svelte' import GcpTriggerEditor from './triggers/gcp/GcpTriggerEditor.svelte' + import AzureTriggerEditor from './triggers/azure/AzureTriggerEditor.svelte' import EmailTriggerEditor from './triggers/email/EmailTriggerEditor.svelte' import { userWorkspaces, workspaceStore } from '$lib/stores' @@ -640,6 +642,7 @@ let mqttEditor: MqttTriggerEditor | undefined = $state() let sqsEditor: SqsTriggerEditor | undefined = $state() let gcpEditor: GcpTriggerEditor | undefined = $state() + let azureEditor: AzureTriggerEditor | undefined = $state() let emailEditor: EmailTriggerEditor | undefined = $state() function openTriggerDetails(trigger: ForkTrigger) { @@ -672,6 +675,9 @@ case 'gcp': gcpEditor?.openEdit(trigger.path, isFlow) break + case 'azure': + azureEditor?.openEdit(trigger.path, isFlow) + break case 'emails': emailEditor?.openEdit(trigger.path, isFlow) break @@ -794,6 +800,19 @@ extraLabel: item.topic_id }) }, + azure: { + list: (ws: string) => AzureTriggerService.listAzureTriggers({ workspace: ws }), + delete: (ws: string, path: string) => + AzureTriggerService.deleteAzureTrigger({ workspace: ws, path }), + normalize: (item: any): ForkTrigger => ({ + path: item.path, + triggerKind: 'azure', + scriptPath: item.script_path, + isFlow: item.is_flow, + enabled: item.mode === 'enabled', + extraLabel: item.topic_name ?? item.scope_resource_id + }) + }, emails: { list: (ws: string) => EmailTriggerService.listEmailTriggers({ workspace: ws }), delete: (ws: string, path: string) => @@ -1316,6 +1335,7 @@ + void } @@ -40,6 +41,7 @@ error = undefined, popup = false, firstTime = false, + autoRedirect = true, onLoginSuccess = undefined }: Props = $props() @@ -93,6 +95,7 @@ let saml: string | undefined = $state(undefined) let smtpConfigured: boolean | undefined = $state(undefined) let disablePasswordLogin = $state(false) + let autoRedirecting = $state(false) type OAuthLogin = { type: string @@ -201,12 +204,14 @@ console.error('Could not load password login setting', disabledResult.reason) } + let autoLogin: string | undefined = undefined if (loginsResult.status === 'fulfilled') { logins = loginsResult.value.oauth.map((login) => ({ type: login.type, displayName: login.display_name || login.type })) saml = loginsResult.value.saml + autoLogin = loginsResult.value.auto_login } else { logins = [] saml = undefined @@ -216,6 +221,28 @@ showPassword = !disablePasswordLogin && ((logins?.length === 0 && !saml) || (email != undefined && email.length > 0)) + + if (autoRedirect && autoLogin && !error && !shouldSkipAutoRedirect()) { + if (autoLogin === 'saml' && saml) { + autoRedirecting = true + if (!redirectSaml()) autoRedirecting = false + } else if (logins?.some((l) => l.type === autoLogin)) { + autoRedirecting = true + if (!storeRedirect(autoLogin)) { + autoRedirecting = false + sendUserToast('Popup blocked — please click the sign-in button to continue.', true) + } + } + } + } + + function shouldSkipAutoRedirect(): boolean { + try { + const params = new URLSearchParams(window.location.search) + return params.get('no_sso') === '1' + } catch { + return false + } } loadLogins() @@ -299,7 +326,7 @@ window.removeEventListener('storage', handleStorageEvent) }) - function storeRedirect(provider: string) { + function persistRd() { if (rd) { try { localStorage.setItem('rd', rd) @@ -307,6 +334,10 @@ console.error('Could not persist redirection to local storage', e) } } + } + + function storeRedirect(provider: string): boolean { + persistRd() let url = base + '/api/oauth/login/' + provider + (popup ? '?close=true' : '') console.log('storeRedirect', popup, url) @@ -314,20 +345,44 @@ localStorage.setItem('closeUponLogin', 'true') window.addEventListener('message', popupListener) window.addEventListener('storage', handleStorageEvent) - window.open(url, '_blank', 'popup') + const win = window.open(url, '_blank', 'popup') + if (!win) { + window.removeEventListener('message', popupListener) + window.removeEventListener('storage', handleStorageEvent) + return false + } + return true } else { localStorage.setItem('closeUponLogin', 'false') window.location.href = url + return true } } + function redirectSaml(): boolean { + if (!saml) { + sendUserToast('No SAML login available', true) + return false + } + persistRd() + window.location.href = saml + return true + } + $effect(() => { error && sendUserToast(escapeHtml(error), true) })
-
+ {#if autoRedirecting} +

Signing you in…

+ {/if} +
{#if !logins} {#each Array(4) as _} @@ -355,29 +410,10 @@ {/each} {/if} {#if saml} - + {/if}
- {#if !disablePasswordLogin && (saml || (logins && logins.length > 0))} + {#if !autoRedirecting && !disablePasswordLogin && (saml || (logins && logins.length > 0))}
0 ? 'mt-6' : '')}> -
- {:else if appContext.type === 'backend' && appContext.backendKey && !appContext.selectionExcluded} -
- - {appContext.backendKey} - -
- {/if} + {#if aiChatManager.mode === AIMode.APP && appContext && (appContext.inspectorElement || appContext.codeSelection)} {#if appContext.inspectorElement}
0 && messages.filter((m) => m.role === 'user').length === 0 && !disabled}
- {#each suggestions as suggestion} + {#each suggestions as suggestion (suggestion)} ', + textContent: 'Save', + styles: {} + }, + codeSelection: { + type: 'app_code_selection', + source: '/index.tsx', + sourceType: 'frontend', + title: '/index.tsx:3-4', + content: 'const selectedCode = true', + startLine: 3, + endLine: 4, + startColumn: 1, + endColumn: 25 + } + } as unknown as SelectedContext + + const message = prepareAppUserMessage('Change this selected area', selectedContext) + + const content = message.content as string + expect(content).toContain('The user has selected an element in the app preview') + expect(content).toContain('body > button.primary') + expect(content).toContain('### CODE SELECTION:') + expect(content).toContain('const selectedCode = true') + }) + + it('serializes explicit mentions with lightweight file context', () => { + const additionalContext: ContextElement[] = [ + { + type: 'app_frontend_file', + path: '/index.tsx', + title: '/index.tsx', + content: 'const fullFrontendContent = true' + }, + { + type: 'app_backend_runnable', + key: 'loadUsers', + title: 'loadUsers', + runnable: { + name: 'Load users', + type: 'inline', + staticInputs: { admin: true }, + inlineScript: { + language: 'bun', + content: 'export async function main() { return "secret" }' + } + } + }, + { + type: 'app_datatable', + datatableName: 'main', + schemaName: 'public', + tableName: 'users', + title: 'main/users', + columns: { + id: 'int4', + email: 'text' + } + } + ] + + const message = prepareAppUserMessage('Wire these together', undefined, additionalContext) + + const content = message.content as string + expect(content).toContain('- Frontend file: /index.tsx') + expect(content).toContain('- Backend runnable: loadUsers') + expect(content).not.toContain('fullFrontendContent') + expect(content).not.toContain('export async function main') + expect(content).not.toContain('Static inputs') + expect(content).not.toContain('Load users') + expect(content).toContain('**Table: main/users**') + expect(content).toContain('"id": "int4"') + expect(content).toContain('"email": "text"') + }) +}) diff --git a/frontend/src/lib/components/copilot/chat/app/core.ts b/frontend/src/lib/components/copilot/chat/app/core.ts index 0f421d04a1..95586df7a0 100644 --- a/frontend/src/lib/components/copilot/chat/app/core.ts +++ b/frontend/src/lib/components/copilot/chat/app/core.ts @@ -14,8 +14,6 @@ import { getDatatableSdkReference } from '$system_prompts' import { aiChatManager } from '../AIChatManager.svelte' import type { ContextElement, - AppFrontendFileElement, - AppBackendRunnableElement, AppCodeSelectionElement, AppDatatableElement } from '../context' @@ -82,28 +80,12 @@ export interface InspectorElementInfo { styles: Record } -/** Context about the currently selected file or runnable in the app editor */ +/** App editor context that is implicitly attached to app-mode AI messages. */ export interface SelectedContext { - /** Type of selection: 'frontend' for frontend files, 'backend' for backend runnables, or 'none' if nothing is selected */ - type: 'frontend' | 'backend' | 'none' - /** The path of the selected frontend file (when type is 'frontend') */ - frontendPath?: string - /** The content of the selected frontend file */ - frontendContent?: string - /** The key of the selected backend runnable (when type is 'backend') */ - backendKey?: string - /** The configuration of the selected backend runnable */ - backendRunnable?: BackendRunnable /** Inspector-selected element info (when user has used the inspector tool) */ inspectorElement?: InspectorElementInfo - /** Whether the file/runnable selection is excluded from being sent to the AI prompt */ - selectionExcluded?: boolean - /** Function to toggle whether the selection is excluded from the prompt */ - toggleSelectionExcluded?: () => void /** Function to clear the inspector selection */ clearInspector?: () => void - /** Function to clear the runnable selection (go back to frontend view) */ - clearRunnable?: () => void /** Code selection from the editor (either frontend or backend) */ codeSelection?: AppCodeSelectionElement /** Function to clear the code selection */ @@ -436,17 +418,6 @@ const getExecDatatableSqlToolDef = memo(() => ) ) -// ============= Selected Context Tool ============= - -const getGetSelectedContextSchema = memo(() => z.object({})) -const getGetSelectedContextToolDef = memo(() => - createToolDef( - getGetSelectedContextSchema(), - 'get_selected_context', - 'Get information about what is currently selected in the app editor. Returns the type of selection (frontend file or backend runnable) and the path/key of the selected item.' - ) -) - // ============= Lint Result Formatting ============= function formatLintMessages(messages: Record): string { @@ -560,22 +531,6 @@ export const getAppTools = memo((): Tool[] => [ return result } }, - // Selected context tool - { - def: getGetSelectedContextToolDef(), - fn: async ({ helpers, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, { content: 'Getting selected context...' }) - const context = helpers.getSelectedContext() - const statusMsg = - context.type === 'frontend' - ? `Frontend file selected: ${context.frontendPath}` - : context.type === 'backend' - ? `Backend runnable selected: ${context.backendKey}` - : 'No selection' - toolCallbacks.setToolStatus(toolId, { content: statusMsg }) - return JSON.stringify(context, null, 2) - } - }, // Frontend tools { def: getGetFrontendFileToolDef(), @@ -1114,6 +1069,7 @@ When you are using the windmill-client, do not forget that as id for variables o 4. Use \`lint()\` at the end to check for and fix any remaining errors When creating a new app, use \`search_workspace\` or \`search_hub_scripts\` to find existing scripts/flows to reuse. +When the user mentions frontend files or backend runnables in context, only their identifiers are included. Use \`get_frontend_file\` or \`get_backend_runnable\` to inspect their contents before editing them or relying on implementation details. ` @@ -1158,60 +1114,12 @@ export function prepareAppUserMessage( // Check if we have any context to add const hasSelectedContext = - selectedContext && (selectedContext.type !== 'none' || selectedContext.inspectorElement) + selectedContext && (selectedContext.inspectorElement || selectedContext.codeSelection) const hasAdditionalContext = additionalContext && additionalContext.length > 0 if (hasSelectedContext || hasAdditionalContext) { content += `## SELECTED CONTEXT:\n` - // Add frontend file context with content (unless excluded) - if ( - selectedContext && - selectedContext.type === 'frontend' && - selectedContext.frontendPath && - !selectedContext.selectionExcluded - ) { - content += `The user is currently viewing the frontend file: **${selectedContext.frontendPath}**\n` - if (selectedContext.frontendContent) { - const truncatedContent = - selectedContext.frontendContent.length > MAX_CONTEXT_CONTENT_LENGTH - ? selectedContext.frontendContent.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + - '\n... [TRUNCATED]' - : selectedContext.frontendContent - content += `\n\`\`\`\n${truncatedContent}\n\`\`\`\n` - } - } - - // Add backend runnable context with content (unless excluded) - if ( - selectedContext && - selectedContext.type === 'backend' && - selectedContext.backendKey && - !selectedContext.selectionExcluded - ) { - content += `The user is currently viewing the backend runnable: **${selectedContext.backendKey}**\n` - if (selectedContext.backendRunnable) { - const runnable = selectedContext.backendRunnable - content += `- **Name**: ${runnable.name}\n` - content += `- **Type**: ${runnable.type}\n` - if (runnable.path) { - content += `- **Path**: ${runnable.path}\n` - } - if (runnable.inlineScript) { - const truncatedCode = - runnable.inlineScript.content.length > MAX_CONTEXT_CONTENT_LENGTH - ? runnable.inlineScript.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + - '\n... [TRUNCATED]' - : runnable.inlineScript.content - content += `- **Language**: ${runnable.inlineScript.language}\n` - content += `- **Code**:\n\`\`\`${runnable.inlineScript.language === 'bun' ? 'typescript' : 'python'}\n${truncatedCode}\n\`\`\`\n` - } - if (runnable.staticInputs && Object.keys(runnable.staticInputs).length > 0) { - content += `- **Static inputs**: ${JSON.stringify(runnable.staticInputs)}\n` - } - } - } - // Add inspector element context if available if (selectedContext?.inspectorElement) { const el = selectedContext.inspectorElement @@ -1249,34 +1157,9 @@ export function prepareAppUserMessage( for (const ctx of additionalContext) { if (ctx.type === 'app_frontend_file') { - const fileCtx = ctx as AppFrontendFileElement - content += `\n**Frontend File: ${fileCtx.path}**\n` - const truncatedContent = - fileCtx.content.length > MAX_CONTEXT_CONTENT_LENGTH - ? fileCtx.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + '\n... [TRUNCATED]' - : fileCtx.content - content += `\`\`\`\n${truncatedContent}\n\`\`\`\n` + content += `\n- Frontend file: ${ctx.path}\n` } else if (ctx.type === 'app_backend_runnable') { - const runnableCtx = ctx as AppBackendRunnableElement - const runnable = runnableCtx.runnable - content += `\n**Backend Runnable: ${runnableCtx.key}**\n` - content += `- **Name**: ${runnable.name}\n` - content += `- **Type**: ${runnable.type}\n` - if (runnable.path) { - content += `- **Path**: ${runnable.path}\n` - } - if (runnable.inlineScript) { - const truncatedCode = - runnable.inlineScript.content.length > MAX_CONTEXT_CONTENT_LENGTH - ? runnable.inlineScript.content.slice(0, MAX_CONTEXT_CONTENT_LENGTH) + - '\n... [TRUNCATED]' - : runnable.inlineScript.content - content += `- **Language**: ${runnable.inlineScript.language}\n` - content += `- **Code**:\n\`\`\`${runnable.inlineScript.language === 'bun' ? 'typescript' : 'python'}\n${truncatedCode}\n\`\`\`\n` - } - if (runnable.staticInputs && Object.keys(runnable.staticInputs).length > 0) { - content += `- **Static inputs**: ${JSON.stringify(runnable.staticInputs)}\n` - } + content += `\n- Backend runnable: ${ctx.key}\n` } else if (ctx.type === 'app_datatable') { const datatableCtx = ctx as AppDatatableElement const tableRef = diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index 4e8dd3cc85..d8ca5858ea 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -177,13 +177,14 @@ return { errorCount: 0, warningCount: 0, errors: [], warnings: [] } }, - setFlowJson: async ({ modules, schema, preprocessorModule, failureModule }) => { + setFlowJson: async ({ modules, schema, preprocessorModule, failureModule, groups }) => { try { if ( modules !== undefined || schema !== undefined || preprocessorModule !== undefined || - failureModule !== undefined + failureModule !== undefined || + groups !== undefined ) { // Take snapshot of current flowStore and set as beforeFlow if (!diffManager?.hasPendingChanges) { @@ -197,7 +198,8 @@ modules, schema, preprocessorModule, - failureModule + failureModule, + groups }) // Refresh the state store to update UI diff --git a/frontend/src/lib/components/copilot/chat/flow/core.ts b/frontend/src/lib/components/copilot/chat/flow/core.ts index 23065085fd..9a877ff07e 100644 --- a/frontend/src/lib/components/copilot/chat/flow/core.ts +++ b/frontend/src/lib/components/copilot/chat/flow/core.ts @@ -35,7 +35,7 @@ import type { ContextElement } from '../context' import type { ExtendedOpenFlow } from '$lib/components/flows/types' import { findModuleInFlow, findModuleInModules } from '$lib/components/flows/flowTree' import { createInlineScriptSession, type InlineScriptSession } from './inlineScriptsUtils' -import type { FlowJsonUpdateResult } from './helperUtils' +import { validateFlowGroups, type FlowGroup, type FlowJsonUpdateResult } from './helperUtils' import { flowModuleSchema, flowModulesSchema } from './openFlowZod' import { collectAllFlowModuleIdsFromModules } from '$lib/components/flows/flowTree' import { FLOW_CHAT_SPECIAL_MODULES, getFlowPrompt } from '$system_prompts' @@ -268,6 +268,7 @@ type FlowJsonUpdate = { schema?: Record | null preprocessorModule?: FlowModule | null failureModule?: FlowModule | null + groups?: FlowGroup[] | null } type EditableFlowJson = { @@ -275,6 +276,7 @@ type EditableFlowJson = { schema: Record | null preprocessor_module: FlowModule | null failure_module: FlowModule | null + groups: FlowGroup[] | null } function formatEmptyInlineScriptWarning({ @@ -386,12 +388,19 @@ function validateEditableFlowJson(rawFlow: unknown): EditableFlowJson { 'preprocessor_module' ) const failureModule = validateOptionalFlowModule(flow.failure_module, 'failure_module') + const groupModuleIds = new Set(collectAllFlowModuleIdsFromModules(modules)) + const groups = validateFlowGroups(flow.groups, groupModuleIds) if (preprocessorModule) { if (preprocessorModule.id !== SPECIAL_MODULE_IDS.PREPROCESSOR) { - throw new Error(`Invalid preprocessor_module: id must be "${SPECIAL_MODULE_IDS.PREPROCESSOR}"`) + throw new Error( + `Invalid preprocessor_module: id must be "${SPECIAL_MODULE_IDS.PREPROCESSOR}"` + ) } - if (preprocessorModule.value.type !== 'rawscript' && preprocessorModule.value.type !== 'script') { + if ( + preprocessorModule.value.type !== 'rawscript' && + preprocessorModule.value.type !== 'script' + ) { throw new Error( 'Invalid preprocessor_module: only "rawscript" and "script" modules are supported' ) @@ -402,9 +411,7 @@ function validateEditableFlowJson(rawFlow: unknown): EditableFlowJson { throw new Error(`Invalid failure_module: id must be "${SPECIAL_MODULE_IDS.FAILURE}"`) } if (failureModule.value.type !== 'rawscript' && failureModule.value.type !== 'script') { - throw new Error( - 'Invalid failure_module: only "rawscript" and "script" modules are supported' - ) + throw new Error('Invalid failure_module: only "rawscript" and "script" modules are supported') } } @@ -423,7 +430,8 @@ function validateEditableFlowJson(rawFlow: unknown): EditableFlowJson { modules, schema, preprocessor_module: preprocessorModule, - failure_module: failureModule + failure_module: failureModule, + groups } } @@ -455,7 +463,11 @@ function buildEditableFlowJson( } let failureModule = flow.value.failure_module - if (failureModule?.value?.type === 'rawscript' && failureModule.value.content && inlineScriptSession) { + if ( + failureModule?.value?.type === 'rawscript' && + failureModule.value.content && + inlineScriptSession + ) { inlineScriptSession.set(failureModule.id, failureModule.value.content) failureModule = { ...failureModule, @@ -470,7 +482,8 @@ function buildEditableFlowJson( modules, schema: flow.schema ?? null, preprocessor_module: preprocessorModule ?? null, - failure_module: failureModule ?? null + failure_module: failureModule ?? null, + groups: flow.value.groups ?? null } } @@ -563,13 +576,20 @@ const setFlowJsonToolSchema = z.object({ .string() .optional() .nullable() - .describe('JSON string containing the optional failure module') + .describe('JSON string containing the optional failure module'), + groups: z + .string() + .optional() + .nullable() + .describe( + 'JSON string containing the optional array of semantic flow groups (summary, note, autocollapse, start_id, end_id, color). Pass null to clear groups.' + ) }) const setFlowJsonToolDef = createToolDef( setFlowJsonToolSchema, 'set_flow_json', - 'Set the complete flow modules array and optionally the flow input schema, preprocessor module, and failure module.', + 'Set the complete flow modules array and optionally the flow input schema, preprocessor module, failure module, and semantic groups.', { strict: false } ) @@ -644,10 +664,7 @@ function validateSpecialFlowModule( } const patchFlowJsonSchema = z.object({ - old_string: z - .string() - .min(1) - .describe('Exact text to find in the current compact flow JSON'), + old_string: z.string().min(1).describe('Exact text to find in the current compact flow JSON'), new_string: z.string().describe('Replacement JSON text'), replace_all: z .boolean() @@ -864,13 +881,13 @@ export const flowTools: Tool[] = [ // Test script step - need to get the script content const script = moduleValue.hash ? await ScriptService.getScriptByHash({ - workspace: workspace, - hash: moduleValue.hash - }) + workspace: workspace, + hash: moduleValue.hash + }) : await ScriptService.getScriptByPath({ - workspace: workspace, - path: moduleValue.path - }) + workspace: workspace, + path: moduleValue.path + }) return executeTestRun({ jobStarter: () => @@ -1023,7 +1040,8 @@ export const flowTools: Tool[] = [ modules: parsedFlow.modules, schema: parsedFlow.schema, preprocessorModule: parsedFlow.preprocessor_module, - failureModule: parsedFlow.failure_module + failureModule: parsedFlow.failure_module, + groups: parsedFlow.groups }) const warning = formatEmptyInlineScriptWarning(updateResult) @@ -1058,7 +1076,9 @@ export const flowTools: Tool[] = [ toolCallbacks.setToolStatus(toolId, { content: - parsedModule === null ? 'Removing preprocessor module...' : 'Setting preprocessor module...' + parsedModule === null + ? 'Removing preprocessor module...' + : 'Setting preprocessor module...' }) const updateResult = await helpers.setFlowJson({ preprocessorModule: parsedModule }) const warning = formatEmptyInlineScriptWarning(updateResult) @@ -1073,7 +1093,8 @@ export const flowTools: Tool[] = [ } toolCallbacks.setToolStatus(toolId, { - content: parsedModule === null ? 'Preprocessor module removed' : 'Preprocessor module updated', + content: + parsedModule === null ? 'Preprocessor module removed' : 'Preprocessor module updated', result: 'Success' }) return parsedModule === null @@ -1123,16 +1144,20 @@ export const flowTools: Tool[] = [ showDetails: true, showFade: true, fn: async ({ args, helpers, toolId, toolCallbacks }) => { - const { modules, schema, preprocessor_module, failure_module } = args + const { modules, schema, preprocessor_module, failure_module, groups } = args let parsedModules: FlowModule[] | null | undefined let parsedSchema: Record | null | undefined let parsedPreprocessorModule: FlowModule | null | undefined let parsedFailureModule: FlowModule | null | undefined + let parsedGroups: FlowGroup[] | null | undefined // Parse JSON strings parsedModules = parseOptionalJsonArg(modules, 'modules') as FlowModule[] | null | undefined - parsedSchema = parseOptionalJsonArg(schema, 'schema') as Record | null | undefined + parsedSchema = parseOptionalJsonArg(schema, 'schema') as + | Record + | null + | undefined parsedPreprocessorModule = parseOptionalJsonArg( preprocessor_module, 'preprocessor_module' @@ -1141,6 +1166,7 @@ export const flowTools: Tool[] = [ | FlowModule | null | undefined + parsedGroups = parseOptionalJsonArg(groups, 'groups') as FlowGroup[] | null | undefined if (parsedModules === null) { parsedModules = undefined } @@ -1151,8 +1177,7 @@ export const flowTools: Tool[] = [ if (parsedModules !== undefined) { parsedModules = validateFlowModules(parsedModules) const reservedIds = collectAllFlowModuleIdsFromModules(parsedModules).filter( - (id) => - id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE + (id) => id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE ) if (reservedIds.length > 0) { throw new Error( @@ -1170,12 +1195,18 @@ export const flowTools: Tool[] = [ ) parsedFailureModule = validateSpecialFlowModule(parsedFailureModule, 'failure_module') + if (parsedGroups !== undefined) { + const effectiveModules = + parsedModules ?? helpers.getFlowAndSelectedId().flow.value.modules ?? [] + const moduleIdsForGroups = new Set(collectAllFlowModuleIdsFromModules(effectiveModules)) + parsedGroups = validateFlowGroups(parsedGroups, moduleIdsForGroups) + } + const ids = [ ...(parsedModules ? collectAllFlowModuleIdsFromModules(parsedModules) : []), - ...([parsedPreprocessorModule, parsedFailureModule].filter( - (module): module is FlowModule => module !== undefined && module !== null - ) - .map((module) => module.id)) + ...[parsedPreprocessorModule, parsedFailureModule] + .filter((module): module is FlowModule => module !== undefined && module !== null) + .map((module) => module.id) ] if (ids.length !== new Set(ids).size) { throw new Error('Duplicate module IDs found in flow') @@ -1190,7 +1221,8 @@ export const flowTools: Tool[] = [ ...(parsedPreprocessorModule !== undefined ? { preprocessorModule: parsedPreprocessorModule } : {}), - ...(parsedFailureModule !== undefined ? { failureModule: parsedFailureModule } : {}) + ...(parsedFailureModule !== undefined ? { failureModule: parsedFailureModule } : {}), + ...(parsedGroups !== undefined ? { groups: parsedGroups } : {}) }) const warning = formatEmptyInlineScriptWarning(updateResult) @@ -1203,9 +1235,9 @@ export const flowTools: Tool[] = [ const { selectedId } = helpers.getFlowAndSelectedId() const selectedModule = selectedId === SPECIAL_MODULE_IDS.PREPROCESSOR - ? parsedPreprocessorModule ?? undefined + ? (parsedPreprocessorModule ?? undefined) : selectedId === SPECIAL_MODULE_IDS.FAILURE - ? parsedFailureModule ?? undefined + ? (parsedFailureModule ?? undefined) : parsedModules ? findModuleInModules(parsedModules, selectedId) : undefined @@ -1290,7 +1322,7 @@ export function prepareFlowSystemMessage(customPrompt?: string): ChatCompletionS Use \`patch_flow_json\` for small, localized changes when you can target an exact snippet from the \`CURRENT FLOW JSON COMPACT\` block below. Always copy the exact search text from the \`CURRENT FLOW JSON COMPACT\` block below. -The compact JSON is a single object with \`modules\`, \`schema\`, \`preprocessor_module\`, and \`failure_module\` keys. +The compact JSON is a single object with \`modules\`, \`schema\`, \`preprocessor_module\`, \`failure_module\`, and \`groups\` keys. **Parameters:** - \`old_string\`: Exact JSON text to find @@ -1311,13 +1343,14 @@ ${FLOW_CHAT_SPECIAL_MODULES} ## Flow Modification with set_flow_json -Use the \`set_flow_json\` tool to set the entire flow structure at once. Provide the complete modules array and optionally the flow input schema, \`preprocessor_module\`, and \`failure_module\`. +Use the \`set_flow_json\` tool to set the entire flow structure at once. Provide the complete modules array and optionally the flow input schema, \`preprocessor_module\`, \`failure_module\`, and \`groups\`. **Parameters:** - \`modules\`: Array of flow modules (required) - \`schema\`: Flow input schema in JSON Schema format (optional) - \`preprocessor_module\`: Special module that runs before \`modules\` (optional, separate from \`modules\`) - \`failure_module\`: Special module that runs on failure (optional, separate from \`modules\`) +- \`groups\`: Array of semantic groups for organizing modules in the editor (optional). Each group has \`summary\` (display name), \`note\` (markdown description shown below the group header — attached directly to the group, not a separate sticky note), \`autocollapse\`, \`start_id\`, \`end_id\`, and \`color\`. \`start_id\` and \`end_id\` must reference existing module IDs in the flow (not \`preprocessor\` or \`failure\`). Groups do not affect execution — they provide naming and collapsibility in the editor. Pass \`null\` to clear existing groups. **Example - Simple flow:** \`\`\`javascript diff --git a/frontend/src/lib/components/copilot/chat/flow/helperUtils.test.ts b/frontend/src/lib/components/copilot/chat/flow/helperUtils.test.ts index dc8234e361..f17abdd901 100644 --- a/frontend/src/lib/components/copilot/chat/flow/helperUtils.test.ts +++ b/frontend/src/lib/components/copilot/chat/flow/helperUtils.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it, vi } from 'vitest' import type { FlowModule } from '$lib/gen' -import { applyFlowJsonUpdate, updateRawScriptModuleContent } from './helperUtils' +import { + applyFlowJsonUpdate, + updateRawScriptModuleContent, + validateFlowGroups +} from './helperUtils' import { createInlineScriptSession } from './inlineScriptsUtils' vi.mock('../shared', () => ({ @@ -63,7 +67,9 @@ describe('applyFlowJsonUpdate', () => { makeRawScriptModule('validate_data', 'inline_script.validate_data') ] }) - const [processDataModule, validateDataModule] = flow.value.modules as Array + const [processDataModule, validateDataModule] = flow.value.modules as Array< + FlowModule & { value: any } + > expect(result.emptyInlineScriptModuleIds).toEqual(['validate_data']) expect(inlineScriptSession.has('validate_data')).toBe(false) @@ -85,7 +91,7 @@ describe('applyFlowJsonUpdate', () => { applyFlowJsonUpdate(flow as any, inlineScriptSession, { modules: [makeRawScriptModule('validate_data', 'inline_script.other_module')] }) - ).toThrow('Unresolved inline script references: other_module') + ).toThrow('Unresolved inline script references: other_module') }) it('keeps the inline script session unchanged after a failed update so retries still warn', () => { @@ -124,6 +130,98 @@ describe('applyFlowJsonUpdate', () => { expect(inlineScriptSession.has('validate_data')).toBe(false) }) + it('persists groups passed in the flow json update', () => { + const flow = { + value: { + modules: [ + makeRawScriptModule('fetch_data', 'existing code'), + makeRawScriptModule('process_data', 'existing code') + ] + } + } + const inlineScriptSession = createInlineScriptSession() + inlineScriptSession.set('fetch_data', 'existing code') + inlineScriptSession.set('process_data', 'existing code') + + applyFlowJsonUpdate(flow as any, inlineScriptSession, { + groups: [ + { + summary: 'Data Ingestion', + note: 'Fetches and processes data', + start_id: 'fetch_data', + end_id: 'process_data' + } + ] + }) + + expect((flow.value as any).groups).toEqual([ + { + summary: 'Data Ingestion', + note: 'Fetches and processes data', + start_id: 'fetch_data', + end_id: 'process_data' + } + ]) + }) + + it('clears groups when an empty array is passed', () => { + const flow = { + value: { + modules: [], + groups: [ + { + summary: 'existing', + start_id: 'a', + end_id: 'b' + } + ] + } + } + const inlineScriptSession = createInlineScriptSession() + + applyFlowJsonUpdate(flow as any, inlineScriptSession, { groups: [] }) + + expect((flow.value as any).groups).toBeUndefined() + }) + + it('clears groups when null is passed', () => { + const flow = { + value: { + modules: [], + groups: [ + { + summary: 'existing', + start_id: 'a', + end_id: 'b' + } + ] + } + } + const inlineScriptSession = createInlineScriptSession() + + applyFlowJsonUpdate(flow as any, inlineScriptSession, { groups: null }) + + expect((flow.value as any).groups).toBeUndefined() + }) + + it('leaves groups untouched when not provided in the update', () => { + const existingGroups = [{ summary: 'existing', start_id: 'a', end_id: 'b' }] + const flow = { + value: { + modules: [makeRawScriptModule('a', 'existing code')], + groups: existingGroups + } + } + const inlineScriptSession = createInlineScriptSession() + inlineScriptSession.set('a', 'existing code') + + applyFlowJsonUpdate(flow as any, inlineScriptSession, { + modules: [makeRawScriptModule('a', 'inline_script.a')] + }) + + expect((flow.value as any).groups).toEqual(existingGroups) + }) + it('updates ai agent rawscript tools in place when changing module code', () => { const flow = { value: { @@ -145,3 +243,60 @@ describe('applyFlowJsonUpdate', () => { ) }) }) + +describe('validateFlowGroups', () => { + it('returns null for null input', () => { + expect(validateFlowGroups(null)).toBeNull() + expect(validateFlowGroups(undefined)).toBeNull() + }) + + it('rejects non-array input', () => { + expect(() => validateFlowGroups({})).toThrow('Flow groups must be an array') + expect(() => validateFlowGroups('not an array')).toThrow('Flow groups must be an array') + }) + + it('rejects a group that is not an object', () => { + expect(() => validateFlowGroups(['nope'])).toThrow( + 'Invalid group at index 0: must be an object' + ) + }) + + it('rejects a group with a missing or non-string start_id', () => { + expect(() => validateFlowGroups([{ end_id: 'b' }])).toThrow( + 'Invalid group at index 0: start_id must be a non-empty string' + ) + expect(() => validateFlowGroups([{ start_id: '', end_id: 'b' }])).toThrow( + 'Invalid group at index 0: start_id must be a non-empty string' + ) + expect(() => validateFlowGroups([{ start_id: 42, end_id: 'b' }])).toThrow( + 'Invalid group at index 0: start_id must be a non-empty string' + ) + }) + + it('rejects a group with a missing end_id', () => { + expect(() => validateFlowGroups([{ start_id: 'a' }])).toThrow( + 'Invalid group at index 0: end_id must be a non-empty string' + ) + }) + + it('accepts a valid group with no moduleIds set', () => { + const result = validateFlowGroups([{ summary: 'G', start_id: 'a', end_id: 'b' }]) + expect(result).toEqual([{ summary: 'G', start_id: 'a', end_id: 'b' }]) + }) + + it('rejects start_id or end_id that are not in the moduleIds set', () => { + const moduleIds = new Set(['a', 'b']) + expect(() => validateFlowGroups([{ start_id: 'missing', end_id: 'b' }], moduleIds)).toThrow( + 'Invalid group at index 0: start_id "missing" does not match any flow module' + ) + expect(() => validateFlowGroups([{ start_id: 'a', end_id: 'missing' }], moduleIds)).toThrow( + 'Invalid group at index 0: end_id "missing" does not match any flow module' + ) + }) + + it('accepts groups whose ids are all in the moduleIds set', () => { + const moduleIds = new Set(['a', 'b', 'c']) + const result = validateFlowGroups([{ start_id: 'a', end_id: 'c', summary: 'G' }], moduleIds) + expect(result).toEqual([{ start_id: 'a', end_id: 'c', summary: 'G' }]) + }) +}) diff --git a/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts b/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts index 19f2e984a7..45c7c7d5fe 100644 --- a/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts +++ b/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts @@ -1,4 +1,4 @@ -import type { FlowModule, OpenFlow, RawScript } from '$lib/gen' +import type { FlowModule, FlowValue, OpenFlow, RawScript } from '$lib/gen' import { forEachFlowModule } from '$lib/components/flows/dfs' import { findModuleInFlow } from '$lib/components/flows/flowTree' import type { InlineScriptSession } from './inlineScriptsUtils' @@ -7,11 +7,14 @@ type FlowLike = Pick & { schema?: Record } +export type FlowGroup = NonNullable[number] + export interface FlowJsonUpdate { modules?: FlowModule[] schema?: Record | null preprocessorModule?: FlowModule | null failureModule?: FlowModule | null + groups?: FlowGroup[] | null } export interface FlowJsonUpdateResult { @@ -33,10 +36,49 @@ export function updateRawScriptModuleContent( return rawScriptModule } +export function validateFlowGroups( + rawGroups: unknown, + moduleIds?: Set +): FlowGroup[] | null { + if (rawGroups == null) { + return null + } + + if (!Array.isArray(rawGroups)) { + throw new Error('Flow groups must be an array') + } + + return rawGroups.map((group, index) => { + if (!group || typeof group !== 'object' || Array.isArray(group)) { + throw new Error(`Invalid group at index ${index}: must be an object`) + } + const g = group as Record + if (typeof g.start_id !== 'string' || !g.start_id) { + throw new Error(`Invalid group at index ${index}: start_id must be a non-empty string`) + } + if (typeof g.end_id !== 'string' || !g.end_id) { + throw new Error(`Invalid group at index ${index}: end_id must be a non-empty string`) + } + if (moduleIds) { + if (!moduleIds.has(g.start_id)) { + throw new Error( + `Invalid group at index ${index}: start_id "${g.start_id}" does not match any flow module` + ) + } + if (!moduleIds.has(g.end_id)) { + throw new Error( + `Invalid group at index ${index}: end_id "${g.end_id}" does not match any flow module` + ) + } + } + return g as unknown as FlowGroup + }) +} + export function applyFlowJsonUpdate( flow: FlowLike, inlineScriptSession: InlineScriptSession, - { modules, schema, preprocessorModule, failureModule }: FlowJsonUpdate + { modules, schema, preprocessorModule, failureModule, groups }: FlowJsonUpdate ): FlowJsonUpdateResult { const emptyInlineScriptModuleIds = new Set() @@ -66,6 +108,10 @@ export function applyFlowJsonUpdate( : restoreFlowModule(failureModule, inlineScriptSession, emptyInlineScriptModuleIds) } + if (groups !== undefined) { + flow.value.groups = groups == null || groups.length === 0 ? undefined : groups + } + return { emptyInlineScriptModuleIds: Array.from(emptyInlineScriptModuleIds) } diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 64a7682eae..e978c9487f 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -156,6 +156,12 @@ }) let selected = $state(untrack(() => preprocessorModule) ? 'test' : 'inputs') + let canShowChatTab = $derived( + !preprocessorModule && + Boolean(flowStore.val.value?.chat_input_enabled) && + flowModule.value.type === 'aiagent' + ) + let visibleSelected = $derived(selected === 'chat' && !canShowChatTab ? 'inputs' : selected) let advancedSelected = $state('retries') let advancedRuntimeSelected = $state('concurrency') let s3Kind = $state('s3_client') @@ -239,6 +245,18 @@ advancedSelected = subtab } + function setOmitOutputFromConversation(omit: boolean) { + if (flowModule.value.type !== 'aiagent') { + return + } + + if (omit) { + flowModule.value.omit_output_from_conversation = true + } else { + delete flowModule.value.omit_output_from_conversation + } + } + let forceReload = $state(0) let editorPanelSize = $state( untrack(() => noEditor) ? 0 : flowModule.value.type == 'script' ? 30 : 50 @@ -775,7 +793,7 @@ const [module, state] = await createScriptFromInlineScript( flowModule, selectedId, - flowStateStore.val[flowModule.id].schema, + flowStateStore.val[flowModule.id]?.schema, $pathStore ) if (flowModule.value.type == 'rawscript') { @@ -1025,16 +1043,29 @@
- + { + selected = event.detail + }} + wrapperClass="shrink-0" + > {#if !preprocessorModule} {/if} + {#if canShowChatTab && flowModule.value.type === 'aiagent'} + + {/if} {#if !preprocessorModule && !isAgentTool} {/if} - {#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow' || flowModule.value.type == 'aiagent')} + {#if visibleSelected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow' || flowModule.value.type == 'aiagent')}
- {:else if selected === 'test'} + {:else if visibleSelected === 'test'} {#if debugMode && isDebuggableScript}
- {:else if selected === 'advanced'} + {:else if visibleSelected === 'chat' && canShowChatTab && flowModule.value.type === 'aiagent'} +
+
+ { + setOmitOutputFromConversation(event.detail) + }} + options={{ + right: 'Omit assistant and tool messages from the flow conversation', + rightTooltip: + 'When enabled, this AI agent still runs normally, but its assistant response and tool-use messages are not stored in chat-mode conversation history.' + }} + /> +
+
+ {:else if visibleSelected === 'advanced'} = 0; i--) { + const message = this.messages[i] + if (!message.id.startsWith('temp-')) { + return message.created_seq + } + } + + return undefined + } + // Polling private async pollJobResult(jobId: string) { try { @@ -338,13 +349,13 @@ export class FlowChatManager { if (!get(workspaceStore)) return try { - const lastId = this.messages[this.messages.length - 1].id + const lastSeq = this.getLastPersistedMessageSeq() const response = await FlowConversationsService.listConversationMessages({ workspace: get(workspaceStore)!, conversationId: conversationId, page: 1, perPage: 50, - afterId: lastId + afterSeq: lastSeq }) if (options?.isNewConversation) { @@ -352,8 +363,6 @@ export class FlowChatManager { } const filteredResponse = response.filter((msg) => msg.message_type !== 'user') - - // Add any new intermediate messages not already present for (const msg of filteredResponse) { if (!this.messages.find((m) => m.id === msg.id)) { this.messages = [...this.messages, msg] @@ -363,7 +372,9 @@ export class FlowChatManager { // Only remove temporary messages when explicitly requested (e.g., after job completion) // During streaming, we keep temp messages to avoid them disappearing due to race conditions if (options?.removeTempMessages) { - this.messages = this.messages.filter((msg) => !msg.id.startsWith('temp-')) + this.messages = this.messages.filter( + (msg) => !msg.id.startsWith('temp-') || msg.message_type === 'user' + ) } } catch (error) { console.error('Polling error:', error) @@ -415,9 +426,10 @@ export class FlowChatManager { delete this.#conversationsCache[currentConversationId] const userMessage: ChatMessage = { - id: randomUUID(), + id: `temp-${randomUUID()}`, content: this.inputMessage.trim(), created_at: new Date().toISOString(), + created_seq: 0, message_type: 'user', conversation_id: currentConversationId } @@ -570,6 +582,7 @@ export class FlowChatManager { id: 'temp-' + randomUUID(), content: newContent, created_at: new Date().toISOString(), + created_seq: 0, message_type: 'tool', conversation_id: currentConversationId, job_id: '', @@ -596,6 +609,7 @@ export class FlowChatManager { id: assistantMessageId, content: accumulatedContent, created_at: new Date().toISOString(), + created_seq: 0, message_type: 'assistant', conversation_id: currentConversationId, job_id: '', diff --git a/frontend/src/lib/components/flows/flowStateUtils.svelte.ts b/frontend/src/lib/components/flows/flowStateUtils.svelte.ts index d142bb34d5..04db00b63d 100644 --- a/frontend/src/lib/components/flows/flowStateUtils.svelte.ts +++ b/frontend/src/lib/components/flows/flowStateUtils.svelte.ts @@ -42,7 +42,11 @@ export async function loadFlowModuleState(flowModule: FlowModule): Promise interface Props { - height?: string; - width?: string; + size?: number + color?: string | undefined + class?: string } - let { height = '24px', width = '24px' }: Props = $props(); + let { size = 16, color = undefined, class: clazz = '' }: Props = $props() - + + diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 5baffb425d..c2869beafd 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -388,6 +388,15 @@ export const settings: Record = { key: 'disable_password_login', fieldType: 'boolean', storage: 'setting' + }, + { + label: 'Auto-login SSO provider', + description: + 'If set, the login page redirects automatically to this provider. Use the OAuth provider key (e.g. "okta", "google") or "saml". The provider must be configured; otherwise the setting is ignored. Visit /user/login?no_sso=1 to bypass the redirect and fall back to the normal login form.', + key: 'auto_login_provider', + fieldType: 'text', + placeholder: 'okta', + storage: 'setting' } ], 'DB Health': [], diff --git a/frontend/src/lib/components/offboarding-utils.ts b/frontend/src/lib/components/offboarding-utils.ts index 45cb8cb4b2..791d65e8d6 100644 --- a/frontend/src/lib/components/offboarding-utils.ts +++ b/frontend/src/lib/components/offboarding-utils.ts @@ -31,6 +31,7 @@ const TRIGGER_TABLE_TO_ROUTE: Record = { nats_trigger: 'nats_triggers', sqs_trigger: 'sqs_triggers', gcp_trigger: 'gcp_triggers', + azure_trigger: 'azure_triggers', email_trigger: 'email_triggers' } @@ -43,6 +44,7 @@ const TRIGGER_TABLE_TO_LABEL: Record = { nats_trigger: 'nats trigger', sqs_trigger: 'sqs trigger', gcp_trigger: 'gcp trigger', + azure_trigger: 'azure trigger', email_trigger: 'email trigger' } diff --git a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte index 24860fb0a6..2ccd238eec 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte @@ -416,38 +416,14 @@ } }, getSelectedContext: () => { - const baseContext = { + return { inspectorElement: inspectorElement, - selectionExcluded: selectionExcludedFromPrompt, - toggleSelectionExcluded: toggleSelectionExcluded, clearInspector: clearInspectorSelection, - clearRunnable: handleClearRunnable, codeSelection: codeSelection, clearCodeSelection: () => { codeSelection = undefined } } - if (selectedRunnable) { - const runnable = convertToBackendRunnable(selectedRunnable, runnables[selectedRunnable]) - return { - type: 'backend' as const, - backendKey: selectedRunnable, - backendRunnable: runnable, - ...baseContext - } - } - if (selectedDocument) { - return { - type: 'frontend' as const, - frontendPath: selectedDocument, - frontendContent: files?.[selectedDocument], - ...baseContext - } - } - return { - type: 'none' as const, - ...baseContext - } }, snapshot: () => { // Force create snapshot for AI - it needs a restore point @@ -608,13 +584,8 @@ let selectedRunnable: string | undefined = $state(undefined) let selectedDocument: string | undefined = $state(undefined) let inspectorElement: InspectorElementInfo | undefined = $state(undefined) - let selectionExcludedFromPrompt: boolean = $state(false) let codeSelection: AppCodeSelectionElement | undefined = $state(undefined) - function toggleSelectionExcluded() { - selectionExcludedFromPrompt = !selectionExcludedFromPrompt - } - let modules = $state({}) as Modules // Normalize Windows-style path separators to Linux-style @@ -722,23 +693,17 @@ ) } - function handleClearRunnable() { - selectedRunnable = undefined - } - // Track previous values for change detection let prevSelectedRunnable: string | undefined = undefined let prevSelectedDocument: string | undefined = undefined - // Clear inspector and reset exclusion when selection changes + // Clear inspector when selection changes $effect(() => { if (selectedRunnable !== prevSelectedRunnable || selectedDocument !== prevSelectedDocument) { // Only clear if we're actually switching to something different if (prevSelectedRunnable !== undefined || prevSelectedDocument !== undefined) { clearInspectorSelection() } - // Reset exclusion when switching files/runnables - selectionExcludedFromPrompt = false prevSelectedRunnable = selectedRunnable prevSelectedDocument = selectedDocument } diff --git a/frontend/src/lib/components/search/GlobalSearchModal.svelte b/frontend/src/lib/components/search/GlobalSearchModal.svelte index 3fa32d6db9..21b79ea6b9 100644 --- a/frontend/src/lib/components/search/GlobalSearchModal.svelte +++ b/frontend/src/lib/components/search/GlobalSearchModal.svelte @@ -40,7 +40,7 @@ import { Alert } from '../common' import Popover from '../Popover.svelte' import Logs from 'lucide-svelte/icons/logs' - import { AwsIcon, GoogleCloudIcon, KafkaIcon, MqttIcon, NatsIcon } from '../icons' + import { AwsIcon, AzureIcon, GoogleCloudIcon, KafkaIcon, MqttIcon, NatsIcon } from '../icons' import RunsSearch from './RunsSearch.svelte' import AskAiButton from '../copilot/AskAiButton.svelte' @@ -138,6 +138,13 @@ icon: GoogleCloudIcon, disabled: $userStore?.operator }, + { + search_id: 'nav:azure_event_grid', + label: 'Go to Azure Event Grid' + (!$enterpriseLicense ? '' : ' (EE)'), + action: (newtab: boolean = false) => gotoPage('/azure_triggers', newtab), + icon: AzureIcon, + disabled: $userStore?.operator + }, { search_id: 'nav:mqtt_triggers', label: 'Go to MQTT triggers', diff --git a/frontend/src/lib/components/sidebar/OperatorMenu.svelte b/frontend/src/lib/components/sidebar/OperatorMenu.svelte index 2b916550ff..b3b30aa710 100644 --- a/frontend/src/lib/components/sidebar/OperatorMenu.svelte +++ b/frontend/src/lib/components/sidebar/OperatorMenu.svelte @@ -151,6 +151,12 @@ href: `${base}/gcp_triggers`, kind: 'gcp' }, + { + label: 'Azure Event Grid triggers', + id: 'triggers', + href: `${base}/azure_triggers`, + kind: 'azure' + }, { label: 'MQTT triggers', id: 'triggers', href: `${base}/mqtt_triggers`, kind: 'mqtt' }, { label: 'Email triggers', id: 'triggers', href: `${base}/email_triggers`, kind: 'email' } ] as TriggerMenuLink[] diff --git a/frontend/src/lib/components/sidebar/SidebarContent.svelte b/frontend/src/lib/components/sidebar/SidebarContent.svelte index 91e71ff191..8ec3076b40 100644 --- a/frontend/src/lib/components/sidebar/SidebarContent.svelte +++ b/frontend/src/lib/components/sidebar/SidebarContent.svelte @@ -74,6 +74,7 @@ } from '$lib/components/meltComponents' import MenuButton from './MenuButton.svelte' import GoogleCloudIcon from '../icons/GoogleCloudIcon.svelte' + import AzureIcon from '../icons/AzureIcon.svelte' async function leaveWorkspace() { await WorkspaceService.leaveWorkspace({ workspace: $workspaceStore ?? '' }) @@ -365,6 +366,15 @@ aiId: 'sidebar-menu-link-gcp', aiDescription: 'Button to navigate to GCP Pub/Sub triggers' }, + { + label: 'Azure Event Grid' + ($enterpriseLicense ? '' : ' (EE)'), + href: '/azure_triggers', + icon: AzureIcon, + disabled: $userStore?.operator || !$enterpriseLicense, + kind: 'azure', + aiId: 'sidebar-menu-link-azure', + aiDescription: 'Button to navigate to Azure Event Grid triggers' + }, { label: 'MQTT', href: '/mqtt_triggers', diff --git a/frontend/src/lib/components/triggers.ts b/frontend/src/lib/components/triggers.ts index 988df6149b..a870b9a622 100644 --- a/frontend/src/lib/components/triggers.ts +++ b/frontend/src/lib/components/triggers.ts @@ -56,6 +56,7 @@ export type TriggerKind = | 'mqtt' | 'sqs' | 'gcp' + | 'azure' | 'nextcloud' | 'google' | 'github' diff --git a/frontend/src/lib/components/triggers/AddTriggersButton.svelte b/frontend/src/lib/components/triggers/AddTriggersButton.svelte index ea381a6ccc..19daa64489 100644 --- a/frontend/src/lib/components/triggers/AddTriggersButton.svelte +++ b/frontend/src/lib/components/triggers/AddTriggersButton.svelte @@ -104,6 +104,12 @@ icon: triggerIconMap.gcp, extra: cloudHosted ? extra : undefined }, + { + displayName: 'Azure Event Grid', + action: () => onAddDraftTrigger?.('azure'), + icon: triggerIconMap.azure, + extra: cloudHosted ? extra : undefined + }, { displayName: 'Email', action: () => onAddDraftTrigger?.('email'), diff --git a/frontend/src/lib/components/triggers/CaptureWrapper.svelte b/frontend/src/lib/components/triggers/CaptureWrapper.svelte index 0c763987a9..2c56681230 100644 --- a/frontend/src/lib/components/triggers/CaptureWrapper.svelte +++ b/frontend/src/lib/components/triggers/CaptureWrapper.svelte @@ -15,6 +15,7 @@ import MqttCapture from './mqtt/MqttCapture.svelte' import SqsCapture from './sqs/SqsCapture.svelte' import GcpCapture from './gcp/GcpCapture.svelte' + import AzureCapture from './azure/AzureCapture.svelte' import EmailCapture from './email/EmailCapture.svelte' interface Props { @@ -74,7 +75,12 @@ if (captureType === 'gcp' && args.delivery_type === 'push') { return false } - return ['mqtt', 'sqs', 'websocket', 'postgres', 'kafka', 'nats', 'gcp'].includes(captureType) + if (captureType === 'azure' && args.azure_mode !== 'namespace_pull') { + return false + } + return ['mqtt', 'sqs', 'websocket', 'postgres', 'kafka', 'nats', 'gcp', 'azure'].includes( + captureType + ) } async function getCaptureConfigs() { @@ -324,6 +330,20 @@ on:captureToggle={handleCapture} on:testWithArgs /> + {:else if captureType === 'azure'} + {:else if captureType === 'email'} KafkaTriggerService.deleteKafkaTrigger, nats: () => NatsTriggerService.deleteNatsTrigger, gcp: () => GcpTriggerService.deleteGcpTrigger, + azure: () => AzureTriggerService.deleteAzureTrigger, sqs: () => SqsTriggerService.deleteSqsTrigger, mqtt: () => MqttTriggerService.deleteMqttTrigger, http: () => HttpTriggerService.deleteHttpTrigger, @@ -229,6 +231,14 @@ isFlow, $userStore ) + } else if (triggerType === 'azure') { + await triggersState.fetchAzureTriggers( + triggersCount, + $workspaceStore, + currentPath, + isFlow, + $userStore + ) } else if (triggerType === 'sqs') { await triggersState.fetchSqsTriggers( triggersCount, diff --git a/frontend/src/lib/components/triggers/TriggersWrapper.svelte b/frontend/src/lib/components/triggers/TriggersWrapper.svelte index 490ff02b53..6b9399c6a2 100644 --- a/frontend/src/lib/components/triggers/TriggersWrapper.svelte +++ b/frontend/src/lib/components/triggers/TriggersWrapper.svelte @@ -10,6 +10,7 @@ import MqttTriggerPanel from './mqtt/MqttTriggersPanel.svelte' import SqsTriggerPanel from './sqs/SqsTriggerPanel.svelte' import GcpTriggerPanel from './gcp/GcpTriggerPanel.svelte' + import AzureTriggerPanel from './azure/AzureTriggerPanel.svelte' import ScheduledPollPanel from './scheduled/ScheduledPollPanel.svelte' import WebsocketTriggersPanel from './websocket/WebsocketTriggersPanel.svelte' import { triggerIconMap, type Trigger } from './utils' @@ -160,6 +161,15 @@ {customLabel} {...props} /> +{:else if selectedTrigger.type === 'azure'} + {:else if selectedTrigger.type === 'email'} + import type { CaptureInfo } from '../CaptureSection.svelte' + import CaptureSection from '../CaptureSection.svelte' + import { Url } from '$lib/components/common' + import { fade } from 'svelte/transition' + + interface Props { + captureInfo?: CaptureInfo | undefined + isValid?: boolean | undefined + hasPreprocessor?: boolean + isFlow?: boolean + captureLoading?: boolean + subscriptionName?: string | undefined + } + + let { + captureInfo = undefined, + isValid = undefined, + hasPreprocessor = false, + isFlow = false, + captureLoading = false, + subscriptionName = undefined + }: Props = $props() + + // Mirror the backend suffix in set_azure_trigger_config: truncate to 39 then + // append "-wm-capture". Azure rule: [A-Za-z0-9-]{3,50}. + const captureSubscriptionName = $derived( + subscriptionName + ? `${subscriptionName.length > 39 ? subscriptionName.slice(0, 39) : subscriptionName}-wm-capture` + : undefined + ) + + +{#if captureInfo} + + {#snippet description()} + {#if captureInfo.active} +

+ Listening to Azure Event Grid events... +

+ {:else} +

+ Start capturing to listen to Azure Event Grid events. +

+ {/if} + {/snippet} + {#if captureSubscriptionName} + + {/if} +
+{/if} diff --git a/frontend/src/lib/components/triggers/azure/AzureTriggerEditor.svelte b/frontend/src/lib/components/triggers/azure/AzureTriggerEditor.svelte new file mode 100644 index 0000000000..bf7dec9d8b --- /dev/null +++ b/frontend/src/lib/components/triggers/azure/AzureTriggerEditor.svelte @@ -0,0 +1,29 @@ + + +{#if open} + +{/if} diff --git a/frontend/src/lib/components/triggers/azure/AzureTriggerEditorConfigSection.svelte b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorConfigSection.svelte new file mode 100644 index 0000000000..992fab62b6 --- /dev/null +++ b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorConfigSection.svelte @@ -0,0 +1,351 @@ + + +
+
+ + + + + {#if has_sp} + + setEdition(v as Edition)} + disabled={!can_write} + > + {#snippet children({ item })} + + + {/snippet} + + + + {#if is_namespace} + + setDelivery(v as Delivery)} + disabled={!can_write} + > + {#snippet children({ item })} + + + {/snippet} + + + {/if} + + +
+
+ +
+
+ {#if topicsError} +

{topicsError}

+ {/if} +
+ {/if} + + {#if config_ready} + + + {#if subscriptionNameError} +

{subscriptionNameError}

+ {/if} +
+ + If a subscription with this name already exists with an incompatible delivery mode + (Push↔Pull) or endpoint type, Windmill will delete and recreate it — any in-flight + events in its queue will be dropped. + +
+
+ + + setFilterText(e.currentTarget.value) + }} + /> + + {/if} +
+
diff --git a/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte new file mode 100644 index 0000000000..87149dea68 --- /dev/null +++ b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte @@ -0,0 +1,480 @@ + + +{#if mode === 'suspended'} + +{/if} + +{#if useDrawer} + + + {#snippet actions()} + {@render actionsButtons()} + {/snippet} + {@render config()} + + +{:else} +
+ {#snippet header()} + {#if customLabel} + {@render customLabel()} + {/if} + {/snippet} + {#snippet action()} + {@render actionsButtons()} + {/snippet} + {@render config()} +
+{/if} + +{#snippet actionsButtons()} + {#if !drawerLoading && can_write} + + {/if} +{/snippet} + +{#snippet config()} + {#if drawerLoading} +
+ +

Loading...

+
+ {:else} + { + selectedPermissionedAs = pa + preservePermissionedAs = preserve + }} + /> +
+ {#if mode === 'suspended'} + + {/if} + {#if description} + {@render description()} + {/if} + {#if !hideTooltips} + + {#if edit} + Changes can take up to 30 seconds to take effect. + {:else} + New Azure triggers can take up to 30 seconds to start listening. + {/if} + + {/if} +
+
+
+ +
+ + {#if !hideTarget} +
+

+ Pick a script or flow to be triggered +

+
+ + {#if emptyString(script_path)} + + {/if} +
+
+ {/if} + + + +
+ {#snippet header()} + + {/snippet} +
+ + + + +
+ +
+
+
+
+
+ {/if} +{/snippet} diff --git a/frontend/src/lib/components/triggers/azure/AzureTriggerPanel.svelte b/frontend/src/lib/components/triggers/azure/AzureTriggerPanel.svelte new file mode 100644 index 0000000000..c5d55957ad --- /dev/null +++ b/frontend/src/lib/components/triggers/azure/AzureTriggerPanel.svelte @@ -0,0 +1,67 @@ + + +{#if !$enterpriseLicense} + + Azure Event Grid triggers are an enterprise only feature. + +{:else} +
+ + {#snippet description()} + {#if cloudDisabled} + + Azure Event Grid triggers are disabled in the multi-tenant cloud. + + {:else} + + Azure Event Grid triggers execute scripts and flows in response to events from Azure + Event Grid (basic) or Event Grid Namespaces (push or pull). + + {/if} + {/snippet} + +
+{/if} diff --git a/frontend/src/lib/components/triggers/azure/utils.ts b/frontend/src/lib/components/triggers/azure/utils.ts new file mode 100644 index 0000000000..4f9e7faf6c --- /dev/null +++ b/frontend/src/lib/components/triggers/azure/utils.ts @@ -0,0 +1,63 @@ +import { AzureTriggerService, type AzureTriggerData } from '$lib/gen' +import { sendUserToast } from '$lib/toast' +import { get, type Writable } from 'svelte/store' + +export async function saveAzureTriggerFromCfg( + initialPath: string, + cfg: Record, + edit: boolean, + workspace: string, + usedTriggerKinds: Writable +): Promise { + try { + const errorHandlerAndRetries = !cfg.is_flow + ? { + error_handler_path: cfg.error_handler_path, + error_handler_args: cfg.error_handler_path ? cfg.error_handler_args : undefined, + retry: cfg.retry + } + : {} + + const requestBody: AzureTriggerData = { + azure_resource_path: cfg.azure_resource_path, + azure_mode: cfg.azure_mode, + scope_resource_id: cfg.scope_resource_id, + topic_name: cfg.topic_name, + subscription_name: cfg.subscription_name, + base_endpoint: cfg.base_endpoint, + event_type_filters: cfg.event_type_filters, + path: cfg.path, + script_path: cfg.script_path, + mode: cfg.mode, + is_flow: cfg.is_flow, + permissioned_as: cfg.permissioned_as, + preserve_permissioned_as: cfg.preserve_permissioned_as, + ...errorHandlerAndRetries + } + if (edit) { + await AzureTriggerService.updateAzureTrigger({ + workspace, + path: initialPath, + requestBody + }) + sendUserToast(`Azure Event Grid trigger ${cfg.path} updated`) + } else { + await AzureTriggerService.createAzureTrigger({ + workspace, + requestBody: { + ...requestBody, + mode: 'enabled' + } + }) + sendUserToast(`Azure Event Grid trigger ${cfg.path} created`) + } + + if (!get(usedTriggerKinds).includes('azure')) { + usedTriggerKinds.update((t) => [...t, 'azure']) + } + return true + } catch (error) { + sendUserToast(error.body || error.message, true) + return false + } +} diff --git a/frontend/src/lib/components/triggers/triggers.svelte.ts b/frontend/src/lib/components/triggers/triggers.svelte.ts index 2852baa244..c10907b8d3 100644 --- a/frontend/src/lib/components/triggers/triggers.svelte.ts +++ b/frontend/src/lib/components/triggers/triggers.svelte.ts @@ -8,6 +8,7 @@ import { WebsocketTriggerService, NativeTriggerService, type GcpTrigger, + type AzureTrigger, type KafkaTrigger, type PostgresTrigger, type Schedule, @@ -15,6 +16,7 @@ import { type HttpTrigger, HttpTriggerService, GcpTriggerService, + AzureTriggerService, type EmailTrigger, EmailTriggerService, type NativeTrigger, @@ -412,6 +414,32 @@ export class Triggers { } } + async fetchAzureTriggers( + triggersCountStore: Writable, + workspaceId: string | undefined, + path: string, + isFlow: boolean, + user: UserExt | undefined = undefined + ): Promise { + if (!workspaceId) return + try { + const azureTriggers: AzureTrigger[] = await AzureTriggerService.listAzureTriggers({ + workspace: workspaceId, + path, + isFlow + }) + const azureCount = this.updateTriggers(azureTriggers, 'azure', user) + triggersCountStore.update((triggersCount) => { + return { + ...(triggersCount ?? {}), + azure_count: azureCount + } + }) + } catch (error) { + console.error('Failed to fetch Azure triggers:', error) + } + } + async fetchHttpTriggers( triggersCountStore: Writable, workspaceId: string | undefined, @@ -526,6 +554,7 @@ export class Triggers { this.fetchKafkaTriggers(triggersCountStore, workspaceId, path, isFlow, user), this.fetchSqsTriggers(triggersCountStore, workspaceId, path, isFlow, user), this.fetchGcpTriggers(triggersCountStore, workspaceId, path, isFlow, user), + this.fetchAzureTriggers(triggersCountStore, workspaceId, path, isFlow, user), this.fetchEmailTriggers(triggersCountStore, workspaceId, path, isFlow, user), this.fetchNatsTriggers(triggersCountStore, workspaceId, path, isFlow, user) ] diff --git a/frontend/src/lib/components/triggers/utils.ts b/frontend/src/lib/components/triggers/utils.ts index 20a28060da..711d84aea9 100644 --- a/frontend/src/lib/components/triggers/utils.ts +++ b/frontend/src/lib/components/triggers/utils.ts @@ -4,6 +4,7 @@ import NatsIcon from '$lib/components/icons/NatsIcon.svelte' import MqttIcon from '$lib/components/icons/MqttIcon.svelte' import AwsIcon from '$lib/components/icons/AwsIcon.svelte' import GoogleCloudIcon from '$lib/components/icons/GoogleCloudIcon.svelte' +import AzureIcon from '$lib/components/icons/AzureIcon.svelte' import type { CaptureTriggerKind, ErrorHandler, @@ -24,6 +25,7 @@ import { saveSqsTriggerFromCfg } from './sqs/utils' import { saveNatsTriggerFromCfg } from './nats/utils' import { saveMqttTriggerFromCfg } from './mqtt/utils' import { saveGcpTriggerFromCfg } from './gcp/utils' +import { saveAzureTriggerFromCfg } from './azure/utils' import type { Triggers } from './triggers.svelte' import { emptyString } from '$lib/utils' import { saveEmailTriggerFromCfg } from './email/utils' @@ -38,6 +40,7 @@ export const CLOUD_DISABLED_TRIGGER_TYPES = [ 'sqs', 'mqtt', 'gcp', + 'azure', 'websocket', 'postgres' ] @@ -55,6 +58,7 @@ export type TriggerType = | 'mqtt' | 'sqs' | 'gcp' + | 'azure' | 'email' | 'poll' | 'cli' @@ -75,6 +79,7 @@ export const jobTriggerKinds: JobTriggerKind[] = [ 'postgres', 'schedule', 'gcp', + 'azure', 'google', 'github' ] @@ -106,6 +111,7 @@ export const triggerIconMap = { mqtt: MqttIcon, sqs: AwsIcon, gcp: GoogleCloudIcon, + azure: AzureIcon, primary_schedule: Calendar, poll: SchedulePollIcon, cli: Terminal, @@ -124,6 +130,7 @@ export const triggerDisplayNamesMap = { mqtt: 'MQTT', sqs: 'SQS', gcp: 'GCP Pub/Sub', + azure: 'Azure Event Grid', email: 'Email', poll: 'Scheduled Poll', webhook: 'Webhook', @@ -153,6 +160,7 @@ export function triggerTypeToCaptureKind(triggerType: TriggerType): CaptureTrigg 'mqtt', 'sqs', 'gcp', + 'azure', 'cli' ] @@ -183,6 +191,7 @@ export function updateTriggersCount( mqtt: 'mqtt_count', sqs: 'sqs_count', gcp: 'gcp_count', + azure: 'azure_count', email: 'email_count', poll: undefined, cli: undefined, @@ -255,6 +264,8 @@ export function triggerKindToTriggerType(kind: TriggerKind): TriggerType | undef return 'sqs' case 'gcp': return 'gcp' + case 'azure': + return 'azure' case 'scheduledPoll': return 'poll' default: @@ -360,6 +371,14 @@ export async function deployTriggers( workspaceId, usedTriggerKinds ), + azure: (trigger: Trigger) => + saveAzureTriggerFromCfg( + trigger.path ?? trigger.draftConfig?.path ?? '', + trigger.draftConfig ?? {}, + !trigger.isDraft, + workspaceId, + usedTriggerKinds + ), email: (trigger: Trigger) => saveEmailTriggerFromCfg( trigger.path ?? trigger.draftConfig?.path ?? '', @@ -493,6 +512,13 @@ export function getLightConfig( return { queue_url: trigger.queue_url } } else if (triggerType === 'gcp') { return { gcp_resource_path: trigger.gcp_resource_path, topic: trigger.topic } + } else if (triggerType === 'azure') { + return { + azure_resource_path: trigger.azure_resource_path, + azure_mode: trigger.azure_mode, + scope_resource_id: trigger.scope_resource_id, + topic_name: trigger.topic_name + } } else if (triggerType === 'email') { return { local_part: trigger.local_part } } else if (triggerType === 'nextcloud') { @@ -590,6 +616,7 @@ export function sortTriggers(triggers: Trigger[]): Trigger[] { 'mqtt', 'sqs', 'gcp', + 'azure', 'email', 'nextcloud', 'google', diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index df7d0b952d..1c5ec7557e 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -63,60 +63,50 @@ import { workspaceStore } from './stores.js' import { argSigToJsonSchemaType } from 'windmill-utils-internal' import { type AssetWithAccessType } from './components/assets/lib.js' -const loadSchemaLastRun = - writable<[string | undefined, MainArgSignature | undefined, string | undefined]>(undefined) +const loadSchemaLastRun = writable< + | [ + string | undefined, + MainArgSignature | undefined, + string | undefined, + SupportedLanguage | 'bunnative' | undefined + ] + | undefined +>(undefined) -let initializeTsPromise: Promise | undefined = undefined -export async function initWasmTs() { - if (initializeTsPromise == undefined) { - initializeTsPromise = initTsParser(wasmUrlTs) +// Memoize each WASM parser's init promise. Without this, concurrent callers +// (e.g. Promise.all in initFlowState across modules of the same language) +// would each invoke the initializer and race — if any one of them rejected +// transiently, the schema for that module would come back empty and the user +// had to modify the code to trigger a second inference. +function memoize(init: () => Promise): () => Promise { + let promise: Promise | undefined + return () => { + if (promise == undefined) { + promise = init().catch((e) => { + // Allow a subsequent call to retry after a failed init + promise = undefined + throw e + }) + } + return promise } - await initializeTsPromise -} -async function initWasmRegex() { - await initRegexParsers(wasmUrlRegex) -} -async function initWasmPython() { - await initPythonParser(wasmUrlPy) -} -async function initWasmPhp() { - await initPhpParser(wasmUrlPhp) -} -async function initWasmRust() { - await initRustParser(wasmUrlRust) -} -async function initWasmGo() { - await initGoParser(wasmUrlGo) -} -async function initWasmYaml() { - await initYamlParser(wasmUrlYaml) -} -async function initWasmCSharp() { - await initCSharpParser(wasmUrlCSharp) -} -async function initWasmNu() { - await initNuParser(wasmUrlNu) -} -async function initWasmJava() { - await initJavaParser(wasmUrlJava) -} -async function initWasmRuby() { - await initRubyParser(wasmUrlRuby) -} -async function initWasmR() { - await initRParser(wasmUrlR) -} -async function initWasmAsset() { - await initAssetParser(wasmUrlAsset) -} -let initializeWacPromise: Promise | undefined = undefined -async function initWasmWac() { - if (initializeWacPromise == undefined) { - initializeWacPromise = initWacParser(wasmUrlWac) - } - await initializeWacPromise } +export const initWasmTs = memoize(() => initTsParser(wasmUrlTs)) +const initWasmRegex = memoize(() => initRegexParsers(wasmUrlRegex)) +const initWasmPython = memoize(() => initPythonParser(wasmUrlPy)) +const initWasmPhp = memoize(() => initPhpParser(wasmUrlPhp)) +const initWasmRust = memoize(() => initRustParser(wasmUrlRust)) +const initWasmGo = memoize(() => initGoParser(wasmUrlGo)) +const initWasmYaml = memoize(() => initYamlParser(wasmUrlYaml)) +const initWasmCSharp = memoize(() => initCSharpParser(wasmUrlCSharp)) +const initWasmNu = memoize(() => initNuParser(wasmUrlNu)) +const initWasmJava = memoize(() => initJavaParser(wasmUrlJava)) +const initWasmRuby = memoize(() => initRubyParser(wasmUrlRuby)) +const initWasmR = memoize(() => initRParser(wasmUrlR)) +const initWasmAsset = memoize(() => initAssetParser(wasmUrlAsset)) +const initWasmWac = memoize(() => initWacParser(wasmUrlWac)) + export type WacDagNode = { id: string node_type: @@ -307,7 +297,13 @@ export async function inferArgs( } | null> { const lastRun = get(loadSchemaLastRun) let inferedSchema: MainArgSignature - if (lastRun && code == lastRun[0] && lastRun[1] && lastRun[2] == mainOverride) { + if ( + lastRun && + code == lastRun[0] && + lastRun[1] && + lastRun[2] == mainOverride && + lastRun[3] === language + ) { inferedSchema = lastRun[1] } else { if (code == '') { @@ -439,7 +435,7 @@ export async function inferArgs( if (inferedSchema.type == 'Invalid') { throw new Error(inferedSchema.error) } - loadSchemaLastRun.set([code, inferedSchema, mainOverride]) + loadSchemaLastRun.set([code, inferedSchema, mainOverride, language]) } schema.required = [] diff --git a/frontend/src/lib/mcpEndpointTools.ts b/frontend/src/lib/mcpEndpointTools.ts index e60103a65a..b86119477e 100644 --- a/frontend/src/lib/mcpEndpointTools.ts +++ b/frontend/src/lib/mcpEndpointTools.ts @@ -88,6 +88,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "type": "string", "description": "The expiration date of the variable", "format": "date-time" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -167,6 +173,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "type": "string", "description": "The new description of the variable" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, "path__body": { "type": "string", "description": "The path to the variable (body parameter)" @@ -254,6 +266,10 @@ export const mcpEndpointTools: EndpointTool[] = [ "per_page": { "type": "integer", "description": "number of items to return for a given page (default 30, max 100)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -297,6 +313,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "resource_type": { "type": "string", "description": "The resource_type associated with the resource" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -365,6 +387,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "type": "string", "description": "The new resource_type to be associated with the resource" }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, "path__body": { "type": "string", "description": "The path to the resource (body parameter)" @@ -447,6 +475,10 @@ export const mcpEndpointTools: EndpointTool[] = [ "broad_filter": { "type": "string", "description": "broad search across multiple fields (case-insensitive substring match)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -554,6 +586,10 @@ export const mcpEndpointTools: EndpointTool[] = [ "dedicated_worker": { "type": "boolean", "description": "(default regardless)\nIf true, show only scripts with dedicated_worker enabled.\nIf false, show only scripts with dedicated_worker disabled.\n" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -565,7 +601,7 @@ export const mcpEndpointTools: EndpointTool[] = [ }, { name: "createScript", - description: "create script", + description: "create script: Creates a new script when the path does not already exist.\nCreates a new version of an existing script when called with the same path and the current `parent_hash`", instructions: "To create a script, specify the path (e.g., 'f/my_folder/my_script'), the content (source code), and the language. For TypeScript, use 'bun' unless deno-specific APIs are needed.", path: "/w/{workspace}/scripts/create", method: "POST", @@ -588,7 +624,7 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "kind": { "type": "string", @@ -782,6 +818,10 @@ export const mcpEndpointTools: EndpointTool[] = [ "dedicated_worker": { "type": "boolean", "description": "(default regardless)\nIf true, show only flows with dedicated_worker enabled.\nIf false, show only flows with dedicated_worker disabled.\n" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] @@ -1105,7 +1145,7 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "tag": { "type": "string" @@ -1137,7 +1177,7 @@ export const mcpEndpointTools: EndpointTool[] = [ }, "language": { "type": "string", - "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative" + "description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative" }, "lock": { "type": "string", @@ -1698,6 +1738,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "preserve_permissioned_as": { "type": "boolean", "description": "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -1898,6 +1944,12 @@ export const mcpEndpointTools: EndpointTool[] = [ "type": "boolean", "nullable": true, "description": "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -2005,6 +2057,10 @@ export const mcpEndpointTools: EndpointTool[] = [ "broad_filter": { "type": "string", "description": "broad search across multiple fields (case-insensitive substring match)" + }, + "label": { + "type": "string", + "description": "Filter by label" } }, "required": [] diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index eb27b57109..38c226e4b8 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -254,6 +254,7 @@ sqs_used, mqtt_used, gcp_used, + azure_used, email_used, nextcloud_used, google_used, @@ -285,6 +286,9 @@ if (gcp_used) { usedKinds.push('gcp') } + if (azure_used) { + usedKinds.push('azure') + } if (email_used) { usedKinds.push('email') } diff --git a/frontend/src/routes/(root)/(logged)/azure_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/azure_triggers/+page.svelte new file mode 100644 index 0000000000..f987d6cb1a --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/azure_triggers/+page.svelte @@ -0,0 +1,635 @@ + + + { + deleteAzureTriggerCallback = undefined + }} + on:confirmed={async () => { + if (deleteAzureTriggerCallback) { + isDeleting = true + if (deleteSubscription && subscriptionDeletePayload) { + try { + const msg = await AzureTriggerService.deleteAzureSubscription({ + workspace: $workspaceStore ?? '', + path: subscriptionDeletePayload.azure_resource_path, + requestBody: { + azure_mode: subscriptionDeletePayload.azure_mode, + scope_resource_id: subscriptionDeletePayload.scope_resource_id, + topic_name: subscriptionDeletePayload.topic_name, + subscription_name: subscriptionDeletePayload.subscription_name + } + }) + sendUserToast(msg) + } catch (error) { + isDeleting = false + sendUserToast(error.body || error.message, true) + return + } + } + await deleteAzureTriggerCallback() + } + deleteAzureTriggerCallback = undefined + subscriptionToDelete = undefined + subscriptionDeletePayload = undefined + }} +> +
+ Are you sure you want to remove this trigger? + + {#if subscriptionToDelete} + + {/if} +
+
+ + + + + (x.summary ?? '') + ' ' + x.path + ' (' + x.script_path + ')'} +/> + + + + + + + {#if isCloudHosted()} + + Azure Event Grid triggers are disabled in the multi-tenant cloud. + + {/if} +
+
+ +
+
Filter by path of
+ + {#snippet children({ item })} + + + {/snippet} + +
+ + +
+ {#if $userStore?.is_super_admin && $userStore.username.includes('@')} + + {:else if $userStore?.is_admin || $userStore?.is_super_admin} + + {/if} +
+
+ {#if loading} + {#each new Array(6) as _} + + {/each} + {:else if !triggers?.length} +
No Azure Event Grid triggers
+ {:else if items?.length} +
+ {#each items.slice(0, nbDisplayed) as { azure_resource_path, azure_mode, scope_resource_id, topic_name, subscription_name, workspace_id, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, retry, error_handler_path, error_handler_args } (path)} + {@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`} + {@const ping = new Date()} + {@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000} + {@const enabled = mode === 'enabled' || mode === 'suspended'} + {@const is_pull = azure_mode === 'namespace_pull'} + {@const topic_label = topic_name + ? topic_name + : (scope_resource_id?.split('/')?.pop() ?? '')} + +
+
+ + + azureTriggerEditor?.openEdit(path, is_flow)} + class="min-w-0 grow hover:underline decoration-gray-400" + > +
+ {path} - {topic_label} ({azure_mode}, {subscription_name}) +
+
+ runnable: {script_path} +
+
+ + + + {#if is_pull} +
+ {#if (enabled && (!pinging || error)) || (!enabled && error) || (enabled && !server_id)} + + + + + + {#snippet text()} +
+ {#if enabled} + {#if !server_id} + Azure Event Grid trigger is starting... + {:else} + Could not connect to Azure Event Grid{error ? ': ' + error : ''} + {/if} + {:else} + Disabled because of an error: {error} + {/if} +
+ {/snippet} +
+ {:else if enabled} + + + + + {#snippet text()} +
+ Connected to Azure Event Grid{!server_id + ? ' (shutting down...)' + : ''}
+ {/snippet} +
+ {/if} +
+ {/if} + + {#if is_pull} + onToggleMode(path, newMode)} + triggerMode={mode} + includeModalConfig={{ + triggerPath: path, + triggerKind: 'azure', + runnableConfig: { + path: script_path, + kind: is_flow ? 'flow' : 'script', + retry, + errorHandlerPath: error_handler_path, + errorHandlerArgs: error_handler_args + } + }} + {canWrite} + hideToggleLabels + hideDropdown + /> + {/if} + +
+ {#if !is_pull} + + {/if} + + { + goto(href) + } + }, + ...(canWrite && mode !== 'suspended' + ? [ + { + displayName: 'Suspend job execution', + icon: Pause, + action: () => { + onToggleMode(path, 'suspended') + } + } + ] + : []), + { + displayName: canWrite ? 'Edit' : 'View', + icon: canWrite ? Pen : Eye, + action: () => { + azureTriggerEditor?.openEdit(path, is_flow) + } + }, + ...(isDeployable('trigger', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to prod/staging', + icon: FileUp, + action: () => { + deploymentDrawer?.openDrawer(path, 'trigger', { + triggers: { + kind: 'azure' + } + }) + } + } + ] + : []), + { + displayName: 'Audit logs', + icon: Eye, + href: `${base}/audit_logs?resource=${path}` + }, + { + displayName: 'Permissions', + icon: Shield, + action: () => { + shareModal?.openDrawer(path, 'azure_trigger') + } + }, + { + displayName: 'Delete', + type: 'delete', + icon: Trash, + disabled: !canWrite, + action: async () => { + isDeleting = false + deleteSubscription = false + subscriptionToDelete = subscription_name + subscriptionDeletePayload = { + azure_mode, + scope_resource_id, + topic_name: topic_name ?? undefined, + subscription_name, + azure_resource_path + } + deleteAzureTriggerCallback = async () => { + const message = await AzureTriggerService.deleteAzureTrigger({ + workspace: $workspaceStore ?? '', + path + }) + sendUserToast(message) + loadTriggers() + } + } + } + ]} + /> +
+
+
+
edited by {edited_by}
the {displayDate(edited_at)}
+
+ {/each} +
+ {:else} + + {/if} +
+ {#if items && items?.length > 15 && nbDisplayed < items.length} + {nbDisplayed} items out of {items.length} + + {/if} +
+ + { + loadTriggers() + }} +/> diff --git a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte index e2cd1097c9..f8ccbe9c7e 100644 --- a/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/run/[...run]/+page.svelte @@ -712,7 +712,7 @@ {/if} {/snippet} -
+
diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte index 5964af7fec..af23f0a034 100644 --- a/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte @@ -152,6 +152,6 @@
- +
diff --git a/frontend/src/routes/approve/[workspace]/[job]/+page.svelte b/frontend/src/routes/approve/[workspace]/[job]/+page.svelte index 4055b4e62b..9ecdcaebb5 100644 --- a/frontend/src/routes/approve/[workspace]/[job]/+page.svelte +++ b/frontend/src/routes/approve/[workspace]/[job]/+page.svelte @@ -79,7 +79,10 @@ try { job = (await JobService.getJob({ workspace: page.params.workspace ?? '', - id: page.params.job ?? '' + id: page.params.job ?? '', + approvalToken: token, + noCode: true, + noLogs: true })) as Job completed = job?.type === 'CompletedJob' if (completed) { diff --git a/lsp/Pipfile b/lsp/Pipfile index e04f65b74d..b50d7f0eb0 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.688.0" +wmill = ">=1.690.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index d575c2ca3b..d0b387608a 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.688.0 + version: 1.690.0 title: OpenFlow Spec contact: name: Ruben Fiszel @@ -1066,6 +1066,10 @@ components: type: string enum: - aiagent + omit_output_from_conversation: + type: boolean + default: false + description: If true, this AI agent step does not persist its assistant or tool messages to the flow conversation when chat mode is enabled. parallel: type: boolean description: If true, the agent can execute multiple tool calls in parallel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index f5679bf04e..a58831606b 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.688.0' + ModuleVersion = '1.690.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 82b4c639b4..981003b261 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.688.0" +version = "1.690.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/rust-client/src/client.rs b/rust-client/src/client.rs index 23c1b5b065..5cf152f520 100644 --- a/rust-client/src/client.rs +++ b/rust-client/src/client.rs @@ -866,6 +866,7 @@ impl Windmill { job_id, Some(true), Some(true), + None, ) .await?; @@ -973,6 +974,7 @@ impl Windmill { &job_id, Some(true), Some(true), + None, ) .await?; diff --git a/system_prompts/README.md b/system_prompts/README.md index 05736dc55c..d3cd0b1e41 100644 --- a/system_prompts/README.md +++ b/system_prompts/README.md @@ -25,6 +25,17 @@ When SDK methods or the OpenFlow schema change, run: python system_prompts/generate.py ``` +To also refresh the standalone skills in a Claude plugin checkout: + +```bash +python system_prompts/generate.py --plugin-dir ~/windmill-claude-plugin +``` + +`--plugin-dir` accepts: +- the `windmill-claude-plugin` repo root +- a plugin root such as `plugins/windmill-code-plugin` +- a direct `skills/` directory + This will: 1. Parse TypeScript and Python SDK files to extract function signatures @@ -32,6 +43,7 @@ This will: 3. Parse the CLI commands 4. Assemble complete prompts from markdown files 5. Generate TypeScript exports in `auto-generated/` +6. Optionally refresh plugin-ready standalone `SKILL.md` files in the target directory ### Scope diff --git a/system_prompts/auto-generated/cli/cli-commands.md b/system_prompts/auto-generated/cli/cli-commands.md index d7e49e0a8f..efc538fec0 100644 --- a/system_prompts/auto-generated/cli/cli-commands.md +++ b/system_prompts/auto-generated/cli/cli-commands.md @@ -505,12 +505,12 @@ trigger related commands - `--json` - Output as JSON (for piping to jq) - `trigger get ` - get a trigger's details - `--json` - Output as JSON (for piping to jq) - - `--kind ` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup + - `--kind ` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email). Recommended for faster lookup - `trigger new ` - create a new trigger locally - - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) - `trigger push ` - push a local trigger spec. This overrides any remote versions. - `trigger set-permissioned-as ` - Set the email (run-as user) for a trigger (requires admin or wm_deployers group) - - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) ### user diff --git a/system_prompts/auto-generated/flow.md b/system_prompts/auto-generated/flow.md index 42b4a3f386..49e2f73970 100644 --- a/system_prompts/auto-generated/flow.md +++ b/system_prompts/auto-generated/flow.md @@ -305,4 +305,4 @@ Reference a specific resource using `$res:` prefix: ## OpenFlow Schema -{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \"yellow\", \"#ffff00\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\nValid values: 'text' (default) - plain text response, 'image' - image generation\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\nFormat: Array<{ bucket: string, key: string }> - S3 object references\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\nRange: 0.0 to 2.0 (provider-dependent)\n- 0.0 = deterministic, focused responses\n- 0.7 = balanced (common default)\n- 1.0+ = more creative/random\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}} \ No newline at end of file +{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \"yellow\", \"#ffff00\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\nValid values: 'text' (default) - plain text response, 'image' - image generation\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\nFormat: Array<{ bucket: string, key: string }> - S3 object references\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\nRange: 0.0 to 2.0 (provider-dependent)\n- 0.0 = deterministic, focused responses\n- 0.7 = balanced (common default)\n- 1.0+ = more creative/random\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"omit_output_from_conversation":{"type":"boolean","default":false,"description":"If true, this AI agent step does not persist its assistant or tool messages to the flow conversation when chat mode is enabled."},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}} \ No newline at end of file diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index 90a437ae0b..f46c1e5ce3 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -1755,7 +1755,7 @@ class SqlQuery: export const OPENFLOW_SCHEMA = `## OpenFlow Schema -{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \\"yellow\\", \\"#ffff00\\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \\u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\\nValid values: 'text' (default) - plain text response, 'image' - image generation\\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\\nFormat: Array<{ bucket: string, key: string }> - S3 object references\\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\\nRange: 0.0 to 2.0 (provider-dependent)\\n- 0.0 = deterministic, focused responses\\n- 0.7 = balanced (common default)\\n- 1.0+ = more creative/random\\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}}`; +{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \\"yellow\\", \\"#ffff00\\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \\u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\\nValid values: 'text' (default) - plain text response, 'image' - image generation\\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\\nFormat: Array<{ bucket: string, key: string }> - S3 object references\\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\\nRange: 0.0 to 2.0 (provider-dependent)\\n- 0.0 = deterministic, focused responses\\n- 0.7 = balanced (common default)\\n- 1.0+ = more creative/random\\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"omit_output_from_conversation":{"type":"boolean","default":false,"description":"If true, this AI agent step does not persist its assistant or tool messages to the flow conversation when chat mode is enabled."},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}}`; export const CLI_COMMANDS = `# Windmill CLI Commands @@ -2264,12 +2264,12 @@ trigger related commands - \`--json\` - Output as JSON (for piping to jq) - \`trigger get \` - get a trigger's details - \`--json\` - Output as JSON (for piping to jq) - - \`--kind \` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup + - \`--kind \` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email). Recommended for faster lookup - \`trigger new \` - create a new trigger locally - - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) - \`trigger push \` - push a local trigger spec. This overrides any remote versions. - \`trigger set-permissioned-as \` - Set the email (run-as user) for a trigger (requires admin or wm_deployers group) - - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - \`--kind \` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) ### user diff --git a/system_prompts/auto-generated/schemas/azure_trigger.schema.yaml b/system_prompts/auto-generated/schemas/azure_trigger.schema.yaml new file mode 100644 index 0000000000..12612b1be5 --- /dev/null +++ b/system_prompts/auto-generated/schemas/azure_trigger.schema.yaml @@ -0,0 +1,84 @@ +type: object +properties: + script_path: + type: string + description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) + is_flow: + type: boolean + description: True if script_path points to a flow, false if it points to a script + labels: + type: array + items: + type: string + azure_resource_path: + type: string + azure_mode: + type: string + enum: + - basic_push + - namespace_push + - namespace_pull + description: Azure Event Grid trigger mode. + scope_resource_id: + type: string + description: ARM resource ID of the topic (basic) or namespace (namespace modes). + topic_name: + type: string + description: Topic name within the namespace (namespace modes only). + subscription_name: + type: string + event_type_filters: + type: array + items: + type: string + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + retry: + type: object + properties: + constant: + type: object + description: Retry with constant delay between attempts + properties: + attempts: + type: integer + description: Number of retry attempts + seconds: + type: integer + description: Seconds to wait between retries + exponential: + type: object + description: Retry with exponential backoff (delay doubles each time) + properties: + attempts: + type: integer + description: Number of retry attempts + multiplier: + type: integer + description: Multiplier for exponential backoff + seconds: + type: integer + minimum: 1 + description: Initial delay in seconds + random_factor: + type: integer + minimum: 0 + maximum: 100 + description: Random jitter percentage (0-100) to avoid thundering herd + retry_if: + $ref: '#/components/schemas/RetryIf' + description: Retry configuration for failed module executions +required: +- script_path +- permissioned_as +- is_flow +- azure_resource_path +- azure_mode +- scope_resource_id +- subscription_name diff --git a/system_prompts/auto-generated/skills/cli-commands/SKILL.md b/system_prompts/auto-generated/skills/cli-commands/SKILL.md index bfcfb161d7..da17059284 100644 --- a/system_prompts/auto-generated/skills/cli-commands/SKILL.md +++ b/system_prompts/auto-generated/skills/cli-commands/SKILL.md @@ -510,12 +510,12 @@ trigger related commands - `--json` - Output as JSON (for piping to jq) - `trigger get ` - get a trigger's details - `--json` - Output as JSON (for piping to jq) - - `--kind ` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup + - `--kind ` - Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email). Recommended for faster lookup - `trigger new ` - create a new trigger locally - - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) - `trigger push ` - push a local trigger spec. This overrides any remote versions. - `trigger set-permissioned-as ` - Set the email (run-as user) for a trigger (requires admin or wm_deployers group) - - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email) + - `--kind ` - Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, azure, email) ### user diff --git a/system_prompts/auto-generated/skills/write-flow/SKILL.md b/system_prompts/auto-generated/skills/write-flow/SKILL.md index 0978d8fd60..1d1398f888 100644 --- a/system_prompts/auto-generated/skills/write-flow/SKILL.md +++ b/system_prompts/auto-generated/skills/write-flow/SKILL.md @@ -310,4 +310,4 @@ Reference a specific resource using `$res:` prefix: ## OpenFlow Schema -{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \"yellow\", \"#ffff00\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\nValid values: 'text' (default) - plain text response, 'image' - image generation\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\nFormat: Array<{ bucket: string, key: string }> - S3 object references\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\nRange: 0.0 to 2.0 (provider-dependent)\n- 0.0 = deterministic, focused responses\n- 0.7 = balanced (common default)\n- 1.0+ = more creative/random\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}} \ No newline at end of file +{"OpenFlow":{"type":"object","description":"Top-level flow definition containing metadata, configuration, and the flow structure","properties":{"summary":{"type":"string","description":"Short description of what this flow does"},"description":{"type":"string","description":"Detailed documentation for this flow"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"JSON Schema for flow inputs. Use this to define input parameters, their types, defaults, and validation. For resource inputs, set type to 'object' and format to 'resource-' (e.g., 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"The flow will be run with the permissions of the user with this email."}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"The flow structure containing modules and optional preprocessor/failure handlers","properties":{"modules":{"type":"array","description":"Array of steps that execute in sequence. Each step can be a script, subflow, loop, or branch","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"Special module that executes when the flow fails. Receives error object with message, name, stack, and step_id. Must have id 'failure'. Only supports script/rawscript types","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"Special module that runs before the first step on external triggers. Must have id 'preprocessor'. Only supports script/rawscript types. Cannot reference other step results","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"If true, all steps run on the same worker for better performance"},"concurrent_limit":{"type":"number","description":"Maximum number of concurrent executions of this flow"},"concurrency_key":{"type":"string","description":"Expression to group concurrent executions (e.g., by user ID)"},"concurrency_time_window_s":{"type":"number","description":"Time window in seconds for concurrent_limit"},"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce flow executions"},"debounce_key":{"type":"string","description":"Expression to group debounced executions"},"debounce_args_to_accumulate":{"type":"array","description":"Arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds that a job can be debounced"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of times a job can be debounced"},"skip_expr":{"type":"string","description":"JavaScript expression to conditionally skip the entire flow"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for flow results"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"If set, delete the flow job's args, result and logs after this many seconds following job completion"},"flow_env":{"type":"object","description":"Environment variables available to all steps. Values can be strings, JSON values, or special references: '$var:path' (workspace variable) or '$res:path' (resource).","additionalProperties":{}},"priority":{"type":"number","description":"Execution priority (higher numbers run first)"},"early_return":{"type":"string","description":"JavaScript expression to return early from the flow"},"chat_input_enabled":{"type":"boolean","description":"Whether this flow accepts chat-style input"},"notes":{"type":"array","description":"Sticky notes attached to the flow","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"Semantic groups of modules for organizational purposes","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"Retry configuration for failed module executions","properties":{"constant":{"type":"object","description":"Retry with constant delay between attempts","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"seconds":{"type":"integer","description":"Seconds to wait between retries"}}},"exponential":{"type":"object","description":"Retry with exponential backoff (delay doubles each time)","properties":{"attempts":{"type":"integer","description":"Number of retry attempts"},"multiplier":{"type":"integer","description":"Multiplier for exponential backoff"},"seconds":{"type":"integer","minimum":1,"description":"Initial delay in seconds"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"Random jitter percentage (0-100) to avoid thundering herd"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"A sticky note attached to a flow for documentation and annotation","properties":{"id":{"type":"string","description":"Unique identifier for the note"},"text":{"type":"string","description":"Content of the note"},"position":{"type":"object","description":"Position of the note in the flow editor","properties":{"x":{"type":"number","description":"X coordinate"},"y":{"type":"number","description":"Y coordinate"}},"required":["x","y"]},"size":{"type":"object","description":"Size of the note in the flow editor","properties":{"width":{"type":"number","description":"Width in pixels"},"height":{"type":"number","description":"Height in pixels"}},"required":["width","height"]},"color":{"type":"string","description":"Color of the note (e.g., \"yellow\", \"#ffff00\")"},"type":{"type":"string","enum":["free","group"],"description":"Type of note - 'free' for standalone notes, 'group' for notes that group other nodes"},"locked":{"type":"boolean","default":false,"description":"Whether the note is locked and cannot be edited or moved"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"For group notes, the IDs of nodes contained within this group"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"A semantic group of flow modules for organizational purposes. Does not affect execution \u2014 modules remain in their original position in the flow. Groups provide naming and collapsibility in the editor. Members are computed dynamically from all nodes on paths between start_id and end_id.","properties":{"summary":{"type":"string","description":"Display name for this group"},"note":{"type":"string","description":"Markdown note shown below the group header"},"autocollapse":{"type":"boolean","default":false,"description":"If true, this group is collapsed by default in the flow editor. UI hint only."},"start_id":{"type":"string","description":"ID of the first flow module in this group (topological entry point)"},"end_id":{"type":"string","description":"ID of the last flow module in this group (topological exit point)"},"color":{"type":"string","description":"Color for the group in the flow editor"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"Conditional retry based on error or result","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to retry. Has access to 'result' and 'error' variables"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"Early termination condition for a module","properties":{"skip_if_stopped":{"type":"boolean","description":"If true, following steps are skipped when this condition triggers"},"expr":{"type":"string","description":"JavaScript expression evaluated after the module runs. Can use 'result' (step's result) or 'flow_input'. Return true to stop"},"error_message":{"type":"string","nullable":true,"description":"Custom error message when stopping with an error. Mutually exclusive with skip_if_stopped. If set to a non-empty string, the flow stops with this error. If empty string, a default error message is used. If null or omitted, no error is raised."}},"required":["expr"]},"FlowModule":{"type":"object","description":"A single step in a flow. Can be a script, subflow, loop, or branch","properties":{"id":{"type":"string","description":"Unique identifier for this step. Used to reference results via 'results.step_id'. Must be a valid identifier (alphanumeric, underscore, hyphen)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"Early termination condition evaluated after this step completes","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"For loops only - early termination condition evaluated after all iterations complete","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"Conditionally skip this step based on previous results or flow inputs","properties":{"expr":{"type":"string","description":"JavaScript expression that returns true to skip. Can use 'flow_input' or 'results.'"}},"required":["expr"]},"sleep":{"description":"Delay before executing this step (in seconds or as expression)","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"Cache duration in seconds for this step's results"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"Maximum execution time in seconds (static value or expression)","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"If set, delete the step's args, result and logs after this many seconds following job completion"},"summary":{"type":"string","description":"Short description of what this step does"},"mock":{"type":"object","description":"Mock configuration for testing without executing the actual step","properties":{"enabled":{"type":"boolean","description":"If true, return mock value instead of executing"},"return_value":{"description":"Value to return when mocked"}}},"suspend":{"type":"object","description":"Configuration for approval/resume steps that wait for user input","properties":{"required_events":{"type":"integer","description":"Number of approvals required before continuing"},"timeout":{"type":"integer","description":"Timeout in seconds before auto-continuing or canceling"},"resume_form":{"type":"object","description":"Form schema for collecting input when resuming","properties":{"schema":{"type":"object","description":"JSON Schema for the resume form"}}},"user_auth_required":{"type":"boolean","description":"If true, only authenticated users can approve"},"user_groups_required":{"description":"Expression or list of groups that can approve","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"If true, the user who started the flow cannot approve"},"hide_cancel":{"type":"boolean","description":"If true, hide the cancel button on the approval form"},"continue_on_disapprove_timeout":{"type":"boolean","description":"If true, continue flow on timeout instead of canceling"}}},"priority":{"type":"number","description":"Execution priority for this step (higher numbers run first)"},"continue_on_error":{"type":"boolean","description":"If true, flow continues even if this step fails"},"retry":{"description":"Retry configuration if this step fails","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"Debounce configuration for this step (EE only)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"Delay in seconds to debounce this step's executions across flow runs"},"debounce_key":{"type":"string","description":"Expression to group debounced executions. Supports $workspace and $args[name]. Default: $workspace/flow/-"},"debounce_args_to_accumulate":{"type":"array","description":"Array-type arguments to accumulate across debounced executions","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"Maximum total time in seconds before forced execution"},"max_total_debounces_amount":{"type":"integer","description":"Maximum number of debounces before forced execution"}}}},"required":["value","id"]},"InputTransform":{"description":"Maps input parameters for a step. Can be a static value or a JavaScript expression that references previous results or flow inputs","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"Static value passed directly to the step. Use for hardcoded values or resource references like '$res:path/to/resource'","properties":{"value":{"description":"The static value. For resources, use format '$res:path/to/resource'"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"JavaScript expression evaluated at runtime. Can reference previous step results via 'results.step_id' or flow inputs via 'flow_input.property'. Inside loops, use 'flow_input.iter.value' for the current iteration value","properties":{"expr":{"type":"string","description":"JavaScript expression returning the value. Available variables - results (object with all previous step results), flow_input (flow inputs), flow_input.iter (in loops)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"Value resolved by the AI runtime for this input. The AI engine decides how to satisfy the parameter.","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"Supported AI provider types","enum":["openai","azure_openai","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock"]},"ProviderConfig":{"type":"object","description":"Complete AI provider configuration with resource reference and model selection","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"Resource reference in format '$res:{resource_path}' pointing to provider credentials"},"model":{"type":"string","description":"Model identifier (e.g., 'gpt-4', 'claude-3-opus-20240229', 'gemini-pro')"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"Static provider configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"Provider configuration - can be static (ProviderConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"No conversation memory/context","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryAuto":{"type":"object","description":"Automatic context management","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"Maximum number of messages to retain in context"},"memory_id":{"type":"string","description":"Identifier for persistent memory across agent invocations"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"A single message in conversation history","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","description":"Explicit message history","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"Conversation memory configuration","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"Static memory configuration passed directly to the AI agent","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"Memory configuration - can be static (MemoryConfig), JavaScript expression, or AI-determined","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"The actual implementation of a flow step. Can be a script (inline or referenced), subflow, loop, branch, or special module type","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent"}}},"RawScript":{"type":"object","description":"Inline script with code defined directly in the flow. Use 'bun' as default language if unspecified. The script receives arguments from input_transforms","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"The script source code. Should export a 'main' function"},"language":{"type":"string","description":"Programming language for this script","enum":["deno","bun","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"Optional path for saving this script"},"lock":{"type":"string","description":"Lock file content for dependencies"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"Worker group tag for execution routing"},"concurrent_limit":{"type":"number","description":"Maximum concurrent executions of this script"},"concurrency_time_window_s":{"type":"number","description":"Time window for concurrent_limit"},"custom_concurrency_key":{"type":"string","description":"Custom key for grouping concurrent executions"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"},"assets":{"type":"array","description":"External resources this script accesses (S3 objects, resources, etc.)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"Path to the asset"},"kind":{"type":"string","description":"Type of asset","enum":["s3object","resource","ducklake","datatable","volume"]},"access_type":{"type":"string","nullable":true,"description":"Access level for this asset","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"Alternative access level","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"Reference to an existing script by path. Use this when calling a previously saved script instead of writing inline code","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the script's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the script in the workspace (e.g., 'f/scripts/send_email')"},"hash":{"type":"string","description":"Optional specific version hash of the script to use"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"Override the script's default worker group tag"},"is_trigger":{"type":"boolean","description":"If true, this script is a trigger that can start the flow"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"Reference to an existing flow by path. Use this to call another flow as a subflow","properties":{"input_transforms":{"type":"object","description":"Map of parameter names to their values (static or JavaScript expressions). These become the subflow's input arguments","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"Path to the flow in the workspace (e.g., 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"Executes nested modules in a loop over an iterator. Inside the loop, use 'flow_input.iter.value' to access the current iteration value, and 'flow_input.iter.index' for the index. Supports parallel execution for better performance on I/O-bound operations","properties":{"modules":{"type":"array","description":"Steps to execute for each iteration. These can reference the iteration value via 'flow_input.iter.value'","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"JavaScript expression that returns an array to iterate over. Can reference 'results.step_id' or 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (faster for I/O-bound operations). Use with parallelism to control concurrency"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true. Limits resource usage. Can be static number or expression","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"Executes nested modules repeatedly while a condition is true. The loop checks the condition after each iteration. Use stop_after_if on modules to control loop termination","properties":{"modules":{"type":"array","description":"Steps to execute in each iteration. Use stop_after_if to control when the loop ends","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"If true, iteration failures don't stop the loop. Failed iterations return null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"If true, iterations run concurrently (use with caution in while loops)"},"parallelism":{"description":"Maximum number of concurrent iterations when parallel=true","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"Conditional branching where only the first matching branch executes. Branches are evaluated in order, and the first one with a true expression runs. If no branches match, the default branch executes","properties":{"branches":{"type":"array","description":"Array of branches to evaluate in order. The first branch with expr evaluating to true executes","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch condition"},"expr":{"type":"string","description":"JavaScript expression that returns boolean. Can use 'results.step_id' or 'flow_input'. First true expr wins"},"modules":{"type":"array","description":"Steps to execute if this branch's expr is true","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"Steps to execute if no branch expressions match","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"Parallel branching where all branches execute simultaneously. Unlike BranchOne, all branches run regardless of conditions. Useful for executing independent tasks concurrently","properties":{"branches":{"type":"array","description":"Array of branches that all execute (either in parallel or sequentially)","items":{"type":"object","properties":{"summary":{"type":"string","description":"Short description of this branch's purpose"},"skip_failure":{"type":"boolean","description":"If true, failure in this branch doesn't fail the entire flow"},"modules":{"type":"array","description":"Steps to execute in this branch","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"If true, all branches execute concurrently. If false, they execute sequentially"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"A tool available to an AI agent. Can be a flow module or an external MCP (Model Context Protocol) tool","properties":{"id":{"type":"string","description":"Unique identifier for this tool. Cannot contain spaces - use underscores instead (e.g., 'get_user_data' not 'get user data')"},"summary":{"type":"string","description":"Short description of what this tool does (shown to the AI)"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"The implementation of a tool. Can be a flow module (script/flow) or an MCP tool reference","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue"}}},"FlowModuleTool":{"description":"A tool implemented as a flow module (script, flow, etc.). The AI can call this like any other flow module","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"A tool implemented as a websearch tool. The AI can call this like any other websearch tool","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"Reference to an external MCP (Model Context Protocol) tool. The AI can call tools from MCP servers","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"Path to the MCP resource/server configuration"},"include_tools":{"type":"array","description":"Whitelist of specific tools to include from this MCP server","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"Blacklist of tools to exclude from this MCP server","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"AI agent step that can use tools to accomplish tasks. The agent receives inputs and can call any of its configured tools to complete the task","properties":{"input_transforms":{"type":"object","description":"Input parameters for the AI agent mapped to their values","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Output format type.\nValid values: 'text' (default) - plain text response, 'image' - image generation\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"The user's prompt/message to the AI agent. Supports variable interpolation with flow.input syntax."},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"System instructions that guide the AI's behavior, persona, and response style. Optional."},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Boolean. If true, stream the AI response incrementally.\nStreaming events include: token_delta, tool_call, tool_call_arguments, tool_execution, tool_result\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"JSON Schema object defining structured output format. Used when you need the AI to return data in a specific shape.\nSupports standard JSON Schema properties: type, properties, required, items, enum, pattern, minLength, maxLength, minimum, maximum, etc.\nExample: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Array of file references (images or PDFs) for the AI agent.\nFormat: Array<{ bucket: string, key: string }> - S3 object references\nExample: [{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Integer. Maximum number of tokens the AI will generate in its response.\nRange: 1 to 4,294,967,295. Typical values: 256-4096 for most use cases.\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"Float. Controls randomness/creativity of responses.\nRange: 0.0 to 2.0 (provider-dependent)\n- 0.0 = deterministic, focused responses\n- 0.7 = balanced (common default)\n- 1.0+ = more creative/random\n"}},"required":["provider","user_message","output_type"]},"tools":{"type":"array","description":"Array of tools the agent can use. The agent decides which tools to call based on the task","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"omit_output_from_conversation":{"type":"boolean","default":false,"description":"If true, this AI agent step does not persist its assistant or tool messages to the flow conversation when chat mode is enabled."},"parallel":{"type":"boolean","description":"If true, the agent can execute multiple tool calls in parallel"}},"required":["tools","type","input_transforms"]},"Identity":{"type":"object","description":"Pass-through module that returns its input unchanged. Useful for flow structure or as a placeholder","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"If true, marks this as a flow identity (special handling)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}} \ No newline at end of file diff --git a/system_prompts/generate.py b/system_prompts/generate.py index b3e7588b97..aa9cf2b85b 100644 --- a/system_prompts/generate.py +++ b/system_prompts/generate.py @@ -11,11 +11,14 @@ This script: Usage: python generate.py + python generate.py --plugin-dir /path/to/windmill-claude-plugin """ +import argparse import ast import json import re +import shutil from pathlib import Path import yaml @@ -909,6 +912,7 @@ SKILL_DEFINITIONS = [ ('MqttTrigger', 'mqtt_trigger'), ('SqsTrigger', 'sqs_trigger'), ('GcpTrigger', 'gcp_trigger'), + ('AzureTrigger', 'azure_trigger'), ], }, { @@ -1104,13 +1108,121 @@ def generate_skills_ts_export(skills: list[str], schema_yaml_content: dict[str, return ts +def format_schema_for_markdown(schema_yaml: str, schema_name: str, file_pattern: str) -> str: + """Format a standalone schema block for plugin skill files.""" + return f"""## {schema_name} (`{file_pattern}`) + +Must be a YAML file that adheres to the following schema: + +```yaml +{schema_yaml.strip()} +```""" + + +def render_plugin_skill_content(skill_name: str, schema_yaml_content: dict[str, str]) -> str: + """Render plugin-ready skill content from generated base skill files.""" + skill_path = OUTPUT_SKILLS_DIR / skill_name / "SKILL.md" + if not skill_path.exists(): + raise FileNotFoundError(f"Missing generated skill content for {skill_name}: {skill_path}") + + skill_content = skill_path.read_text() + schema_mappings = SCHEMA_MAPPINGS.get(skill_name, []) + if not schema_mappings: + return skill_content + + schema_docs = [] + for schema_name, schema_key in schema_mappings: + schema_yaml = schema_yaml_content.get(schema_key) + if not schema_yaml: + continue + schema_docs.append( + format_schema_for_markdown( + schema_yaml=schema_yaml, + schema_name=schema_name, + file_pattern=f"*.{schema_key}.yaml", + ) + ) + + if not schema_docs: + return skill_content + + return f"{skill_content}\n\n" + "\n\n".join(schema_docs) + + +def resolve_plugin_skills_dir(plugin_dir: Path) -> Path: + """Resolve the plugin skills directory from a repo root, plugin root, or skills dir.""" + plugin_dir = plugin_dir.expanduser().resolve() + + repo_skills_dir = plugin_dir / "plugins" / "windmill-code-plugin" / "skills" + repo_plugin_json = plugin_dir / "plugins" / "windmill-code-plugin" / ".claude-plugin" / "plugin.json" + if repo_plugin_json.exists(): + return repo_skills_dir + + plugin_skills_dir = plugin_dir / "skills" + plugin_json = plugin_dir / ".claude-plugin" / "plugin.json" + if plugin_json.exists(): + return plugin_skills_dir + + if plugin_dir.name == "skills": + return plugin_dir + + return plugin_skills_dir + + +def generate_plugin_skills( + plugin_dir: Path, + skills: list[str], + schema_yaml_content: dict[str, str], +) -> Path: + """Generate standalone skills in a Claude plugin checkout.""" + skills_dir = resolve_plugin_skills_dir(plugin_dir) + skills_dir.mkdir(parents=True, exist_ok=True) + + expected_skills = set(skills) + for existing in skills_dir.iterdir(): + if existing.is_dir() and existing.name not in expected_skills: + shutil.rmtree(existing) + + for skill_name in skills: + skill_dir = skills_dir / skill_name + skill_dir.mkdir(parents=True, exist_ok=True) + (skill_dir / "SKILL.md").write_text( + render_plugin_skill_content(skill_name, schema_yaml_content) + ) + + print(f"\nGenerated for plugin:") + print(f" - {skills_dir} ({len(skills)} skills)") + return skills_dir + + # ============================================================================= # Main Entry Point # ============================================================================= +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description=( + "Generate Windmill system prompts, CLI guidance, and optionally " + "plugin-ready standalone skills." + ) + ) + parser.add_argument( + "--plugin-dir", + type=Path, + help=( + "Optional plugin target. Accepts a windmill-claude-plugin repo root, " + "a plugin root, or a skills directory, and refreshes standalone skills there." + ), + ) + return parser.parse_args() + + def main(): """Main generation function.""" + args = parse_args() + print("Generating system prompts documentation...") # Ensure output directories exist @@ -1196,6 +1308,7 @@ def main(): 'MqttTrigger', 'NewMqttTrigger', 'SqsTrigger', 'NewSqsTrigger', 'GcpTrigger', + 'AzureTrigger', ] for schema_name in schema_names: if schema_name in backend_schemas: @@ -1344,6 +1457,10 @@ export function getDatatableSdkReference(): string { print(f" - auto-generated/schemas/ ({len(schema_yaml_content)} schema files)") print(f"\nGenerated for CLI:") print(f" - cli/src/guidance/skills.ts") + + if args.plugin_dir: + generate_plugin_skills(args.plugin_dir, skills, schema_yaml_content) + print("\nDone!") diff --git a/system_prompts/utils.py b/system_prompts/utils.py index 4318b81642..779fbc56bb 100644 --- a/system_prompts/utils.py +++ b/system_prompts/utils.py @@ -48,6 +48,7 @@ SCHEMA_MAPPINGS = { ('MqttTrigger', 'mqtt_trigger'), ('SqsTrigger', 'sqs_trigger'), ('GcpTrigger', 'gcp_trigger'), + ('AzureTrigger', 'azure_trigger'), ], 'schedules': [ ('Schedule', 'schedule'), diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 156f34b2cc..cff5681336 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.688.0", + "version": "1.690.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index c4d1ffb180..91902eff0e 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.688.0", + "version": "1.690.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 7ede926357..f096da6db0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.688.0 +1.690.0