diff --git a/.agents/skills/add-block-preview/SKILL.md b/.agents/skills/add-block-preview/SKILL.md index c3633e8dbc6..6bc91f76219 100644 --- a/.agents/skills/add-block-preview/SKILL.md +++ b/.agents/skills/add-block-preview/SKILL.md @@ -53,7 +53,7 @@ To pull an already-GA block from discovery surfaces on hosted (incident, depreca - **Clone-not-remove:** gated blocks stay in `getAllBlocks()` output as clones with `hideFromToolbar: true` — `.find`-by-type consumers rely on this. Never filter them out. - **Keys are registry block types.** Never `custom_block_*` (parse drops them — custom blocks have their own enabled/disabled lifecycle). - **The shared hidden-predicate is `isHiddenUnder`** (`apps/sim/blocks/visibility/context.ts`). Never restate the preview/disabled rule inline at a new consumer. -- **Process-global caches stay ungated.** Shared builders such as `getExposedIntegrationTools` (`lib/integrations/tool-catalog.ts`) build the ungated universe; per-viewer filtering happens at consumer time via `isHiddenUnder`. Never move gating into a shared builder. +- **Process-global caches stay ungated.** Shared builders such as `getExposedIntegrationTools` (`apps/sim/lib/integrations/tool-catalog.ts`) build the ungated universe; per-viewer filtering happens at consumer time via `isHiddenUnder`. Never move gating into a shared builder. - Gating is **surface hiding, not secrecy** — the full config ships in the client JS bundle. Anything truly secret cannot be a registered block. ## Tests diff --git a/.agents/skills/add-block/SKILL.md b/.agents/skills/add-block/SKILL.md index c8a83361a6b..d580061b37c 100644 --- a/.agents/skills/add-block/SKILL.md +++ b/.agents/skills/add-block/SKILL.md @@ -310,7 +310,7 @@ When several fields are mutually exclusive alternatives, mark them all `required other paths ever get a chance to supply the value. **Constraints (block-wide):** -- `canonicalParamId` must not equal any subblock `id` in the block. +- `canonicalParamId` may equal only the `id` of a member of its own group, as `channel` does in the canonicalParamId Pattern below; it must never equal any other subblock's `id`. (`blocks.test.ts` enforces the case of a subblock with no `canonicalParamId`.) - One canonical id links exactly one basic/advanced pair for one logical parameter. Groups are keyed by canonical id across every subblock and hold one `basicId`, so two operations that each need a pair need two canonical ids. - All members of a group share the same `required` status. @@ -562,6 +562,11 @@ Use `wandConfig` on fields that are hard to fill by hand — timestamps (`genera ## Tools Configuration +**Write operation ids and tool ids as string literals** in the `operation` dropdown `options` and +`tools.access`, never through constants (`id: SEARCH`, `access: [SEARCH]`). `scripts/generate-docs.ts` +reads them from source with regexes, so a constant parses as empty: the docs page loses its whole +Actions section and the integration catalog ships blank operation descriptions, with no check failing. + **Preferred:** Use tool names directly as dropdown option IDs to avoid switch cases: ```typescript // Dropdown options use tool IDs directly diff --git a/.agents/skills/add-connector/SKILL.md b/.agents/skills/add-connector/SKILL.md index 801ef6ee4fe..d856462fb9c 100644 --- a/.agents/skills/add-connector/SKILL.md +++ b/.agents/skills/add-connector/SKILL.md @@ -214,7 +214,7 @@ The user sees a toggle button (ArrowLeftRight) to switch between the selector dr 1. **Every selector field MUST have a canonical pair** — a corresponding `short-input` (or `dropdown`) field with the same `canonicalParamId` and `mode: 'advanced'`. 2. **`required` must be set identically on both fields** in a pair. If the selector is required, the manual input must also be required. -3. **`canonicalParamId` must match the key the connector expects in `sourceConfig`** (e.g. `baseId`, `channel`, `teamId`). The advanced field's `id` should typically match `canonicalParamId` (connector config fields differ from block subBlocks here; the block rule that `canonicalParamId` must not equal a subblock id does not apply). +3. **`canonicalParamId` must match the key the connector expects in `sourceConfig`** (e.g. `baseId`, `channel`, `teamId`). The advanced field's `id` should typically match `canonicalParamId` (connector config fields differ from block subBlocks here; the block rule that `canonicalParamId` must not equal the id of a subblock without a `canonicalParamId` does not apply). 4. **`dependsOn` references the selector field's `id`**, not the `canonicalParamId`. The modal propagates dependency clearing across canonical siblings automatically — changing either field in a parent pair clears dependent children. ### Selector canonical pair example (Airtable base → table cascade) diff --git a/.agents/skills/add-settings-page/SKILL.md b/.agents/skills/add-settings-page/SKILL.md index c59c187e738..5cddb31b5a9 100644 --- a/.agents/skills/add-settings-page/SKILL.md +++ b/.agents/skills/add-settings-page/SKILL.md @@ -46,11 +46,12 @@ Each grep lists candidates; review every match against the expected ones named b 1. Find hand-rolled shells that should be `SettingsPanel`: `git grep -n "flex h-full flex-col bg-\[var(--bg)\]" -- 'apps/sim/**/settings/**' 'apps/sim/ee/'` - — expected matches: the workspace and organization `settings/layout.tsx` shells, the shared - header shell (`components/settings/settings-header.tsx`), `CredentialDetailLayout` (the - `settings/secrets/[credentialId]` exception), or an entitlement/loading gate. A detail - sub-view is never a match: it passes `back={{ text, icon: ArrowLeft, onSelect }}` to - `SettingsPanel`. Anything else is a violation: render it through `SettingsPanel`. + — expected matches: the workspace and organization `settings/layout.tsx` shells and the + shared header shell (`components/settings/settings-header.tsx`); an entitlement/loading gate + is also fine. `CredentialDetailLayout` (the `settings/secrets/[credentialId]` exception) is + an exempt hand-rolled shell outside these pathspecs. A detail sub-view is never a match: it + passes `back={{ text, icon: ArrowLeft, onSelect }}` to `SettingsPanel`. Anything else is a + violation: render it through `SettingsPanel`. 2. Find hand-rolled title blocks: `git grep -n "text-\[var(--text-body)\] text-lg" -- 'apps/sim/**/settings/**' 'apps/sim/ee/'` — the only title is the `

` in `settings-header.tsx`; a non-heading value at that size diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index a8f44bdf75d..853124a32c9 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -88,9 +88,10 @@ conditions freshly after every push. across all pages has `isResolved: true`, and every check has finished and passed, stop — report the outcome (see "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, resolve it with step 6 (its rebase-based sync flow and - the `/ship` gates; a merge commit would be discarded by that rebase), then steps 7–8: push - with `--force-with-lease` and re-trigger review. +2. **If the PR has a merge conflict**, rebase rather than merge (step 6's rebase would discard a + merge commit): `git fetch origin staging && git rebase origin/staging`, resolve each conflict + and `git rebase --continue` until the rebase finishes. Then run step 6 (the sync check and the + `/ship` gates), then steps 7–8: push with `--force-with-lease` and re-trigger review. 3. **If no review has run yet** (fresh PR, no bot comments): both run automatically on PR open — confirm via `gh pr checks ` (look for `Greptile Review` and `cubic · AI code reviewer`) and diff --git a/.agents/skills/ship/SKILL.md b/.agents/skills/ship/SKILL.md index 66b722b81a8..fdd7e52ad4f 100644 --- a/.agents/skills/ship/SKILL.md +++ b/.agents/skills/ship/SKILL.md @@ -45,7 +45,7 @@ When the user runs `/ship`: - Then run root `bun run test` from the repo root. It chains `test:scripts` (the `scripts/*.test.ts` suite CI runs) before every workspace suite; workspace-scoped runs skip it, which is how a `scripts/check-*.test.ts` failure has reached CI. A failing test aborts ship. 5. **Run migration safety** — only if the diff touches `packages/db/migrations/**` or `packages/db/schema.ts`: - Run `/db-migrate` to review the migration for zero-downtime safety (expand/contract phasing, backward-compatibility with the deployed app version). - - `cd packages/db && bunx drizzle-kit generate && git status --porcelain ./migrations` must print nothing (CI's schema/migration sync step). + - `(cd packages/db && bunx drizzle-kit generate && git status --porcelain ./migrations)` must print nothing (CI's schema/migration sync step). - `bun run check:migrations origin/staging` must pass (staging is the PR base). Do not silence a flagged statement with a `-- migration-safe:` annotation unless `/db-migrate` confirmed the old code no longer depends on it; otherwise split the destructive change into a later deploy. 6. **Run pre-ship checks** from the repo root before staging. This has two phases: first **regenerate** every committed artifact so generated files never drift into a CI failure (this is what catches things like `agent-stream-docs` going stale after a `models.ts` edit), then run the **full audit suite** CI's `Lint and Test` job enforces. Both phases parallelize — but only across commands that write **disjoint** outputs — and a bare `wait` swallows child exit codes, so both phases below explicitly collect each job's status and abort ship if any failed. @@ -78,12 +78,13 @@ When the user runs `/ship`: # Runs every audit CI runs, concurrently, and replays the output of any that fail. # The audit list is derived in scripts/run-audits.ts — do not hand-list audits here. bun run check:audits || { echo "❌ audit(s) failed — do not ship"; exit 1; } + bun run type-check || { echo "❌ type-check failed — do not ship"; exit 1; } # CI's "Verify docs manifest is in sync" step is not a `check:*` script, so the runner above # does not cover it. (CI's "Security audit" `bun audit` step is `continue-on-error` — advisory # only, not a gate — so it is deliberately not run here.) bun run docs-manifest:check || { echo "❌ docs manifest out of sync — do not ship"; exit 1; } ``` - If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here. + If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` is covered by step 5 and is not repeated here. 7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6 8. **Push to origin** using the current branch name — `--force-with-lease` if step 2's sync check did any history rewrite (a clean rebase or a cherry-pick rebuild) on a branch that had diff --git a/.claude/rules/sim-components.md b/.claude/rules/sim-components.md index 194359e448b..2c6ee7a3f9c 100644 --- a/.claude/rules/sim-components.md +++ b/.claude/rules/sim-components.md @@ -40,7 +40,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. +- `js-tosorted-immutable` — won't-fix anywhere; `tsc` rejects the ES2023 array methods, because no tsconfig raises `lib` past ES2022. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.claude/rules/sim-integrations.md b/.claude/rules/sim-integrations.md index 558978c96cd..bb71f32beb4 100644 --- a/.claude/rules/sim-integrations.md +++ b/.claude/rules/sim-integrations.md @@ -17,7 +17,7 @@ The full authoring instructions — tool/block/icon/trigger scaffolding, SubBloc - Tool IDs and the two registration/coercion rules are in the root `CLAUDE.md` → Integrations. `blocks/registry.ts` holds only the accessor functions; triggers register in `triggers/registry.ts`. - Give every subblock a unique `id`: duplicates collide silently (the last definition wins). `blocks.test.ts` fails a duplicate within one condition unless the copies are a basic/advanced mode-swap pair, one basic plus trigger-mode copies, or all carry `canonicalParamId`. The only sanctioned cross-condition reuse is the hosted-key `apiKey` pair (`/add-hosted-key`), where both fields deliberately share one value. - Keep block outputs aligned with what the referenced tools actually return, and block `tools.access` aligned with the registered tool IDs. -- `canonicalParamId` must NOT match the `id` of a subblock that has no `canonicalParamId` (a group member may share it, as the `add-block` skill's `channel` example does), must be unique **block-wide** (groups are keyed by canonical id across every subblock and hold exactly one `basicId`, so two operations that each need a pair need two different canonical ids), and all subblocks in a canonical group must share the same `required` status. The `inputs` section and the params function reference canonical IDs, not raw subblock IDs — the serializer deletes the subblock IDs and republishes the active member's value under the canonical ID. +- `canonicalParamId` may match only the `id` of a member of its own group (as the `add-block` skill's `channel` example does), never any other subblock's `id`, must be unique **block-wide** (groups are keyed by canonical id across every subblock and hold exactly one `basicId`, so two operations that each need a pair need two different canonical ids), and all subblocks in a canonical group must share the same `required` status. The `inputs` section and the params function reference canonical IDs, not raw subblock IDs — the serializer deletes the subblock IDs and republishes the active member's value under the canonical ID. - A canonical pair carries ONE concept. For files that is upload (basic) + file reference (advanced), normalized with `normalizeFileInput`, as in Gmail attachments (`blocks/blocks/gmail.ts`). Never overload the advanced side with alternate identifiers (URL, provider asset ID) — give those their own subblocks, mark mutually exclusive sources `required: false`, and enforce "exactly one" at execution. - A sub-block's option list is EITHER `selectorKey` (a registered selector — the only way to load a remote list, and the only one that works off the canvas) OR `options` (a static array, or a pure function of the block's own values). Never fetch from a block definition, and never read the workflow stores there. A credential sub-block needs `canonicalParamId: 'oauthCredential'` for its dependants' selectors to resolve. A secret must never appear in a selector's `getQueryKey`. `bun run check:fork-dependent-coverage` fails a `dependsOn` under a credential/KB/table anchor that the fork sync modal cannot offer. - Integration blocks (`category: 'tools'`) must set `integrationType` (`integration-catalog:check` fails without it) and export a `{Service}BlockMeta` (with `tags`); set `authMode` and `docsLink` too, which otherwise fall back to a credential-subblock guess and the generated docs page — see the `/add-block` skill's BlockMeta section. `{Service}BlockMeta.skills` must be grounded in operations the block exposes via `tools.access` and sourced from real, popular use cases found online — never invented. diff --git a/.claude/rules/sim-react-performance.md b/.claude/rules/sim-react-performance.md index 668e667a5d8..f37fb447299 100644 --- a/.claude/rules/sim-react-performance.md +++ b/.claude/rules/sim-react-performance.md @@ -77,7 +77,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. Every tsconfig keeps `"lib"` at or below ES2022 so `tsc` rejects these at each call site on a typed receiver (and still accepts OpenTelemetry's `context.with`, which it tells apart by type); `check:utils` fails if a tsconfig raises `lib` past ES2022, which is how they shipped in #5340, and also matches `toSorted`/`toReversed`/`toSpliced` in source, since tsc accepts any method on an `any` receiver. `.with` on an `any` receiver is caught by neither, so type a parsed array before copying from it. Never raise it to make one type-check. ## Run independent awaits in parallel diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index da5447c642c..09393d6ef3b 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -93,14 +93,17 @@ return ( ## Title + description live in navigation metadata `apps/sim/components/settings/navigation.ts` is the single source of truth (the -`settings/navigation.ts` in the route tree is only a re-export shim). Every `SETTINGS_SECTION_REGISTRY` entry carries a one-line `description`; `SettingsPanel` +`settings/navigation.ts` in the route tree is only a re-export shim). Each `SETTINGS_SECTION_REGISTRY` entry's one-line description is +`unified.description` (a plane projection's `planes..description` overrides it where that +plane's scope differs), or, for a section that exists only on a standalone plane, its +`planes..description`; `SettingsPanel` resolves both via `getSettingsSectionMeta(plane, section)` and the `SettingsSectionProvider` the settings shell wraps around the active section. Adding a new settings page: 1. Add the section id to the `UnifiedSettingsSection` union + a `SETTINGS_SECTION_REGISTRY` - entry (with `label` **and** `unified.description`) in `components/settings/navigation.ts`. Keep descriptions verb-first, one line, + entry (with `label` **and** its description, as described above) in `components/settings/navigation.ts`. Keep descriptions verb-first, one line, ~40–55 chars, in the product voice (see `.claude/rules/constitution.md`). 2. Register its module in `SECTION_MODULES` (`settings/section-warmers.ts`) and render it inside the shell's `effectiveSection` switch in `settings/[section]/settings.tsx`. diff --git a/.claude/rules/sim-styling.md b/.claude/rules/sim-styling.md index 264fa106e37..f146e7a547c 100644 --- a/.claude/rules/sim-styling.md +++ b/.claude/rules/sim-styling.md @@ -95,7 +95,7 @@ Draw a line with a real `border-*` utility. Never hand-roll one as `shadow-[inse - **Errors** → `error` prop. Never `className={cn(err && 'border-[var(--text-error)]')}`. - **Leading icon** → `icon` prop (rendered 14px in `--text-icon`). - **Trailing buttons** (reveal/copy/fetch) → `endAdornment`. -- **Inner-input styling** (e.g. `font-mono`, number-spinner reset) → `inputClassName` (ChipInput only). See `app/workspace/[workspaceId]/settings/components/billing/components/usage-limit-field/usage-limit-field.tsx`. +- **Inner-input styling** (e.g. `font-mono`, number-spinner reset) → `inputClassName` (ChipInput only). See `ee/whitelabeling/components/whitelabeling-settings.tsx`. - **`ChipModalField` controls take NO className.** Pass `title`/`value`/`onChange`/`error`/`hint`/`required`/`flush`. The field owns label, control, and error/hint rendering. See `app/workspace/[workspaceId]/skills/components/skill-modal/skill-modal.tsx`. ### What className MAY carry diff --git a/.claude/rules/sim-url-state.md b/.claude/rules/sim-url-state.md index fb93787f16e..6114efc83bc 100644 --- a/.claude/rules/sim-url-state.md +++ b/.claude/rules/sim-url-state.md @@ -19,7 +19,7 @@ Pick exactly one home for each piece of state (table below). Put state in the UR | Home | Trigger | Example | | --- | --- | --- | | **URL (nuqs)** | Client view-state worth a link: tab, filter, search, sort, pagination, selected-entity id, an open "view" modal/drawer that is a destination | `?tab=licenses`, `?category=Communication`, `?page=3`, `?skillId=abc` | -| **React Query** | Server/remote data fetched from an endpoint | `useMcpServers(workspaceId)`, `useSkills(workspaceId)` | +| **React Query** | Server/remote data fetched from an endpoint (hook rules: `.claude/rules/sim-queries.md`) | `useMcpServers(workspaceId)`, `useSkills(workspaceId)` | | **Zustand** | Cross-component client state that must NOT be in the URL: high-frequency, large, ephemeral, socket-synced | canvas pan/zoom, live cursor, drag state, resize widths, unsaved buffers | | **`useState`** | Purely local single-component UI; also the snappy mirror of a debounced URL search | a hover flag, a transient dialog target, the live text of a debounced search box | diff --git a/.cursor/rules/sim-components.mdc b/.cursor/rules/sim-components.mdc index 15be6706173..dc7bb99300c 100644 --- a/.cursor/rules/sim-components.mdc +++ b/.cursor/rules/sim-components.mdc @@ -41,7 +41,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. +- `js-tosorted-immutable` — won't-fix anywhere; `tsc` rejects the ES2023 array methods, because no tsconfig raises `lib` past ES2022. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.cursor/rules/sim-integrations.mdc b/.cursor/rules/sim-integrations.mdc index cfe5ed66d92..624d388b8cf 100644 --- a/.cursor/rules/sim-integrations.mdc +++ b/.cursor/rules/sim-integrations.mdc @@ -16,7 +16,7 @@ The full authoring instructions — tool/block/icon/trigger scaffolding, SubBloc - Tool IDs and the two registration/coercion rules are in the root `CLAUDE.md` → Integrations. `blocks/registry.ts` holds only the accessor functions; triggers register in `triggers/registry.ts`. - Give every subblock a unique `id`: duplicates collide silently (the last definition wins). `blocks.test.ts` fails a duplicate within one condition unless the copies are a basic/advanced mode-swap pair, one basic plus trigger-mode copies, or all carry `canonicalParamId`. The only sanctioned cross-condition reuse is the hosted-key `apiKey` pair (`/add-hosted-key`), where both fields deliberately share one value. - Keep block outputs aligned with what the referenced tools actually return, and block `tools.access` aligned with the registered tool IDs. -- `canonicalParamId` must NOT match the `id` of a subblock that has no `canonicalParamId` (a group member may share it, as the `add-block` skill's `channel` example does), must be unique **block-wide** (groups are keyed by canonical id across every subblock and hold exactly one `basicId`, so two operations that each need a pair need two different canonical ids), and all subblocks in a canonical group must share the same `required` status. The `inputs` section and the params function reference canonical IDs, not raw subblock IDs — the serializer deletes the subblock IDs and republishes the active member's value under the canonical ID. +- `canonicalParamId` may match only the `id` of a member of its own group (as the `add-block` skill's `channel` example does), never any other subblock's `id`, must be unique **block-wide** (groups are keyed by canonical id across every subblock and hold exactly one `basicId`, so two operations that each need a pair need two different canonical ids), and all subblocks in a canonical group must share the same `required` status. The `inputs` section and the params function reference canonical IDs, not raw subblock IDs — the serializer deletes the subblock IDs and republishes the active member's value under the canonical ID. - A canonical pair carries ONE concept. For files that is upload (basic) + file reference (advanced), normalized with `normalizeFileInput`, as in Gmail attachments (`blocks/blocks/gmail.ts`). Never overload the advanced side with alternate identifiers (URL, provider asset ID) — give those their own subblocks, mark mutually exclusive sources `required: false`, and enforce "exactly one" at execution. - A sub-block's option list is EITHER `selectorKey` (a registered selector — the only way to load a remote list, and the only one that works off the canvas) OR `options` (a static array, or a pure function of the block's own values). Never fetch from a block definition, and never read the workflow stores there. A credential sub-block needs `canonicalParamId: 'oauthCredential'` for its dependants' selectors to resolve. A secret must never appear in a selector's `getQueryKey`. `bun run check:fork-dependent-coverage` fails a `dependsOn` under a credential/KB/table anchor that the fork sync modal cannot offer. - Integration blocks (`category: 'tools'`) must set `integrationType` (`integration-catalog:check` fails without it) and export a `{Service}BlockMeta` (with `tags`); set `authMode` and `docsLink` too, which otherwise fall back to a credential-subblock guess and the generated docs page — see the `/add-block` skill's BlockMeta section. `{Service}BlockMeta.skills` must be grounded in operations the block exposes via `tools.access` and sourced from real, popular use cases found online — never invented. diff --git a/.cursor/rules/sim-react-performance.mdc b/.cursor/rules/sim-react-performance.mdc index cf526216c5f..4ad3ca74971 100644 --- a/.cursor/rules/sim-react-performance.mdc +++ b/.cursor/rules/sim-react-performance.mdc @@ -80,7 +80,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. Every tsconfig keeps `"lib"` at or below ES2022 so `tsc` rejects these at each call site on a typed receiver (and still accepts OpenTelemetry's `context.with`, which it tells apart by type); `check:utils` fails if a tsconfig raises `lib` past ES2022, which is how they shipped in #5340, and also matches `toSorted`/`toReversed`/`toSpliced` in source, since tsc accepts any method on an `any` receiver. `.with` on an `any` receiver is caught by neither, so type a parsed array before copying from it. Never raise it to make one type-check. ## Run independent awaits in parallel diff --git a/.cursor/rules/sim-settings-pages.mdc b/.cursor/rules/sim-settings-pages.mdc index c34ff9f5e82..98b9a684370 100644 --- a/.cursor/rules/sim-settings-pages.mdc +++ b/.cursor/rules/sim-settings-pages.mdc @@ -90,14 +90,17 @@ return ( ## Title + description live in navigation metadata `apps/sim/components/settings/navigation.ts` is the single source of truth (the -`settings/navigation.ts` in the route tree is only a re-export shim). Every `SETTINGS_SECTION_REGISTRY` entry carries a one-line `description`; `SettingsPanel` +`settings/navigation.ts` in the route tree is only a re-export shim). Each `SETTINGS_SECTION_REGISTRY` entry's one-line description is +`unified.description` (a plane projection's `planes..description` overrides it where that +plane's scope differs), or, for a section that exists only on a standalone plane, its +`planes..description`; `SettingsPanel` resolves both via `getSettingsSectionMeta(plane, section)` and the `SettingsSectionProvider` the settings shell wraps around the active section. Adding a new settings page: 1. Add the section id to the `UnifiedSettingsSection` union + a `SETTINGS_SECTION_REGISTRY` - entry (with `label` **and** `unified.description`) in `components/settings/navigation.ts`. Keep descriptions verb-first, one line, + entry (with `label` **and** its description, as described above) in `components/settings/navigation.ts`. Keep descriptions verb-first, one line, ~40–55 chars, in the product voice (see `.claude/rules/constitution.md`). 2. Register its module in `SECTION_MODULES` (`settings/section-warmers.ts`) and render it inside the shell's `effectiveSection` switch in `settings/[section]/settings.tsx`. diff --git a/.cursor/rules/sim-styling.mdc b/.cursor/rules/sim-styling.mdc index 54ef96abfb8..3cc6ebcd23b 100644 --- a/.cursor/rules/sim-styling.mdc +++ b/.cursor/rules/sim-styling.mdc @@ -95,7 +95,7 @@ Draw a line with a real `border-*` utility. Never hand-roll one as `shadow-[inse - **Errors** → `error` prop. Never `className={cn(err && 'border-[var(--text-error)]')}`. - **Leading icon** → `icon` prop (rendered 14px in `--text-icon`). - **Trailing buttons** (reveal/copy/fetch) → `endAdornment`. -- **Inner-input styling** (e.g. `font-mono`, number-spinner reset) → `inputClassName` (ChipInput only). See `app/workspace/[workspaceId]/settings/components/billing/components/usage-limit-field/usage-limit-field.tsx`. +- **Inner-input styling** (e.g. `font-mono`, number-spinner reset) → `inputClassName` (ChipInput only). See `ee/whitelabeling/components/whitelabeling-settings.tsx`. - **`ChipModalField` controls take NO className.** Pass `title`/`value`/`onChange`/`error`/`hint`/`required`/`flush`. The field owns label, control, and error/hint rendering. See `app/workspace/[workspaceId]/skills/components/skill-modal/skill-modal.tsx`. ### What className MAY carry diff --git a/.cursor/rules/sim-url-state.mdc b/.cursor/rules/sim-url-state.mdc index 3537973d9aa..1aa10918c8e 100644 --- a/.cursor/rules/sim-url-state.mdc +++ b/.cursor/rules/sim-url-state.mdc @@ -16,7 +16,7 @@ Pick exactly one home for each piece of state (table below). Put state in the UR | Home | Trigger | Example | | --- | --- | --- | | **URL (nuqs)** | Client view-state worth a link: tab, filter, search, sort, pagination, selected-entity id, an open "view" modal/drawer that is a destination | `?tab=licenses`, `?category=Communication`, `?page=3`, `?skillId=abc` | -| **React Query** | Server/remote data fetched from an endpoint | `useMcpServers(workspaceId)`, `useSkills(workspaceId)` | +| **React Query** | Server/remote data fetched from an endpoint (hook rules: `.claude/rules/sim-queries.md`) | `useMcpServers(workspaceId)`, `useSkills(workspaceId)` | | **Zustand** | Cross-component client state that must NOT be in the URL: high-frequency, large, ephemeral, socket-synced | canvas pan/zoom, live cursor, drag state, resize widths, unsaved buffers | | **`useState`** | Purely local single-component UI; also the snappy mirror of a debounced URL search | a hover flag, a transient dialog target, the live text of a debounced search box | diff --git a/CLAUDE.md b/CLAUDE.md index 87172b4fd6a..a3f7da54018 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,10 +82,10 @@ The `'use client'` server boundary, the app/worker runtime env split, and featur ## Code Conventions -- **Naming**: components PascalCase (`WorkflowList`); hooks `use*`; files kebab-case (`workflow-list.tsx`); constants SCREAMING_SNAKE_CASE; interfaces PascalCase with a suffix (`WorkflowListProps`); stores `stores//store.ts`. A file never repeats its folder's name (inside `logs/`, `views.ts` not `log-views.ts`; inside `utils/`, `date.ts` not `date-utils.ts`); `check:file-names` enforces this. +- **Naming**: components PascalCase (`WorkflowList`); hooks `use*`; files kebab-case (`workflow-list.tsx`); constants SCREAMING_SNAKE_CASE; interfaces PascalCase with a suffix (`WorkflowListProps`); stores `stores//store.ts`. Inside `lib/`, `executor/`, `providers/`, `stores/`, `hooks/`, `serializer/`, and a package's `src/`, a file never starts with its folder's name (`logs/views.ts`, not `logs/log-views.ts`), and a file in `utils/` or `helpers/` never repeats that role (`date.ts`, not `date-utils.ts`); `feature/feature.tsx` stays the component convention. `check:file-names` ratchets this. - **Imports**: absolute (`@/...`) only, never relative (a barrel `index.ts` re-exports its own siblings relatively). A folder with 3+ exports gets an `index.ts` barrel; never re-export from a non-barrel file. `import type` for type-only imports. Order and lazy-loading through barrels: `.claude/rules/sim-imports.md`. - **TypeScript**: no `any` and no non-null `!` (use precise types or `unknown` with guards; `check:explicit-any` ratchets both); no export nothing imports (`check:unused-exports`); a props interface for every component; `as const` for constant objects/arrays; explicit ref types (`useRef(null)`). -- **Unused bindings** fail lint (biome `noUnusedVariables`, `noUnusedFunctionParameters`): delete the dead variable, import, or parameter and update callers; write `catch {}` when the error is unused. Prefix `_` only for a parameter that must hold its position because a later one is used. `const { a, ...rest } = obj` to omit keys is allowed. The rules carry no autofix, so `bun run lint` will not rename anything for you. +- **Unused bindings** fail lint (biome `noUnusedVariables`, `noUnusedFunctionParameters`): delete the dead variable, import, or parameter and update callers; write `catch {}` when the error is unused. Prefix `_` only for a parameter that must hold its position because a later one is used, or a type parameter kept for API shape. `const { a, ...rest } = obj` to omit keys is allowed. The rules carry no autofix, so `bun run lint` will not rename anything for you. - **Components**: `'use client'` only for hooks or browser APIs (`check:client-boundary` guards the server boundary). Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()`, never `toSorted()`): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. - **State ownership**: React Query owns server data — never `useState` + `fetch`; shareable client view-state (tabs, filters, search, pagination, selected id) lives in the URL via `nuqs`; Zustand owns global client state; `useState` owns UI-only state. Hooks: `.claude/rules/sim-hooks.md`. Stores (`devtools`, `persist` only with an explicit `partialize` whitelist, workflow value invariants): `.claude/rules/sim-stores.md`. URL state: `.claude/rules/sim-url-state.md`. - **Utils**: inline a helper with one consumer; create `utils.ts` when 2+ files share it — in `lib/` (app-wide) or `feature/utils/` (feature-scoped). Check `lib/` before writing a new one. diff --git a/apps/docs/app/sitemap.ts b/apps/docs/app/sitemap.ts index 5dabe2daa44..17e36be5d4b 100644 --- a/apps/docs/app/sitemap.ts +++ b/apps/docs/app/sitemap.ts @@ -5,8 +5,12 @@ import { DOCS_BASE_URL } from '@/lib/urls' export const revalidate = 3600 export default function sitemap(): MetadataRoute.Sitemap { - return source.getPages().map((page) => ({ - url: `${DOCS_BASE_URL}${page.url}`, - lastModified: 'lastModified' in page.data ? page.data.lastModified : undefined, - })) + // The docs root redirects to /introduction, which is listed on its own. + return source + .getPages() + .filter((page) => page.url !== '/') + .map((page) => ({ + url: `${DOCS_BASE_URL}${page.url}`, + lastModified: 'lastModified' in page.data ? page.data.lastModified : undefined, + })) } diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index ff3d66b7c84..c186ddf787a 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5217,6 +5217,61 @@ export function MicrosoftSharepointIcon(props: SVGProps) { ) } +export function PowerBIIcon(props: SVGProps) { + const id = useId() + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + export function MicrosoftPlannerIcon(props: SVGProps) { return ( @@ -10040,3 +10095,32 @@ export function PlanetScaleIcon(props: SVGProps) { ) } + +export function YouComIcon(props: SVGProps) { + const id = useId() + const gradientId = `youcom_gradient_${id}` + + return ( + + + + + + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 540e8cba8ff..096652e0900 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -216,6 +216,7 @@ import { PolymarketIcon, PostgresIcon, PosthogIcon, + PowerBIIcon, ProfoundIcon, ProspeoIcon, PulseIcon, @@ -305,6 +306,7 @@ import { WorkdayIcon, WorkflowIcon, xIcon, + YouComIcon, YouTubeIcon, ZendeskIcon, ZepIcon, @@ -560,6 +562,7 @@ export const blockTypeToIconMap: Record = { polymarket: PolymarketIcon, postgresql: PostgresIcon, posthog: PosthogIcon, + powerbi: PowerBIIcon, profound: ProfoundIcon, prospeo: ProspeoIcon, pulse: PulseIcon, @@ -675,6 +678,7 @@ export const blockTypeToIconMap: Record = { workflow: WorkflowIcon, workflow_input: WorkflowIcon, x: xIcon, + youcom: YouComIcon, youtube: YouTubeIcon, zendesk: ZendeskIcon, zep: ZepIcon, diff --git a/apps/docs/content/docs/api-reference/python.mdx b/apps/docs/content/docs/api-reference/python.mdx index 5ab91aac6bf..39aa0ed719f 100644 --- a/apps/docs/content/docs/api-reference/python.mdx +++ b/apps/docs/content/docs/api-reference/python.mdx @@ -30,7 +30,7 @@ from simstudio import SimStudioClient # Initialize the client client = SimStudioClient( api_key="your-api-key-here", - base_url="https://www.sim.ai" # optional; the default https://sim.ai redirects to this host + base_url="https://www.sim.ai" # optional, defaults to https://www.sim.ai ) # Execute a workflow @@ -48,12 +48,12 @@ except Exception as error: #### Constructor ```python -SimStudioClient(api_key: str, base_url: str = "https://sim.ai") +SimStudioClient(api_key: str, base_url: str = "https://www.sim.ai") ``` **Parameters:** - `api_key` (str): Your Sim API key -- `base_url` (str, optional): Base URL for the Sim API (defaults to `https://sim.ai`, which redirects to `https://www.sim.ai`; set the `www` host explicitly) +- `base_url` (str, optional): Base URL for the Sim API (defaults to `https://www.sim.ai`; versions before 0.2.1 default to `https://sim.ai`, which redirects to `https://www.sim.ai`, so set the `www` host explicitly on older versions) #### Methods diff --git a/apps/docs/content/docs/api-reference/typescript.mdx b/apps/docs/content/docs/api-reference/typescript.mdx index 88068da4506..9d4e9310bd3 100644 --- a/apps/docs/content/docs/api-reference/typescript.mdx +++ b/apps/docs/content/docs/api-reference/typescript.mdx @@ -44,7 +44,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk'; // Initialize the client const client = new SimStudioClient({ apiKey: 'your-api-key-here', - baseUrl: 'https://www.sim.ai' // optional; the default https://sim.ai redirects to this host + baseUrl: 'https://www.sim.ai' // optional, defaults to https://www.sim.ai }); // Execute a workflow @@ -68,7 +68,7 @@ new SimStudioClient(config: SimStudioConfig) **Configuration:** - `config.apiKey` (string): Your Sim API key -- `config.baseUrl` (string, optional): Base URL for the Sim API (defaults to `https://sim.ai`, which redirects to `https://www.sim.ai`; set the `www` host explicitly) +- `config.baseUrl` (string, optional): Base URL for the Sim API (defaults to `https://www.sim.ai`; versions before 0.2.1 default to `https://sim.ai`, which redirects to `https://www.sim.ai`, so set the `www` host explicitly on older versions) #### Methods diff --git a/apps/docs/content/docs/cli/files.mdx b/apps/docs/content/docs/cli/files.mdx index 0ec32c4991a..853e60ce484 100644 --- a/apps/docs/content/docs/cli/files.mdx +++ b/apps/docs/content/docs/cli/files.mdx @@ -271,7 +271,7 @@ sim files versions read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | -| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--offset ` | No | First line to return, 1-based; 0 also starts at the first line. Absent starts at the first line. | | `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | @@ -497,7 +497,7 @@ sim files read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | -| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--offset ` | No | First line to return, 1-based; 0 also starts at the first line. Absent starts at the first line. | | `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | diff --git a/apps/docs/content/docs/cli/reference.mdx b/apps/docs/content/docs/cli/reference.mdx index 27c734ae675..f7e2a05d058 100644 --- a/apps/docs/content/docs/cli/reference.mdx +++ b/apps/docs/content/docs/cli/reference.mdx @@ -1039,7 +1039,7 @@ sim files versions read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | -| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--offset ` | No | First line to return, 1-based; 0 also starts at the first line. Absent starts at the first line. | | `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | @@ -1281,7 +1281,7 @@ sim files read [options] | Option | Required | Description | | --- | --- | --- | | `--max-bytes ` | No | Optional ceiling on the source bytes fed to the parser, lowering but never raising the server limit. | -| `--offset ` | No | First line to return, 1-based. Absent starts at the first line. | +| `--offset ` | No | First line to return, 1-based; 0 also starts at the first line. Absent starts at the first line. | | `--limit ` | No | How many lines to return from `offset`. Absent reads to the end. | @@ -4142,7 +4142,7 @@ sim selectors get [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Resource identifier. | @@ -4162,7 +4162,7 @@ sim selectors list [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | diff --git a/apps/docs/content/docs/cli/selectors.mdx b/apps/docs/content/docs/cli/selectors.mdx index 573a0a987c9..5e0559877ee 100644 --- a/apps/docs/content/docs/cli/selectors.mdx +++ b/apps/docs/content/docs/cli/selectors.mdx @@ -21,7 +21,7 @@ Get Selector Option (OAuth login or personal API key required) | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Resource identifier. | @@ -41,7 +41,7 @@ List Selector Options (OAuth login or personal API key required) | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `planetscale.databases`, `powerbi.workspaces`, `powerbi.datasets`, `powerbi.reports`, `planetscale.branches`, `planetscale.backups`, `planetscale.deployRequests`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | diff --git a/apps/docs/content/docs/integrations/ashby.mdx b/apps/docs/content/docs/integrations/ashby.mdx index 33686b017b1..9a09169a8b0 100644 --- a/apps/docs/content/docs/integrations/ashby.mdx +++ b/apps/docs/content/docs/integrations/ashby.mdx @@ -1045,6 +1045,15 @@ Lists interview schedules in Ashby, optionally filtered by application or interv | ↳ `status` | string | Schedule status \(NeedsScheduling, WaitingOnCandidateBooking, Scheduled, Complete, Cancelled, OnHold, etc.\) | | ↳ `applicationId` | string | Associated application UUID | | ↳ `interviewStageId` | string | Interview stage UUID | +| ↳ `scheduledBy` | object | User summary | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email | +| ↳ `globalRole` | string | Role | +| ↳ `isEnabled` | boolean | Whether enabled | +| ↳ `updatedAt` | string | Last update timestamp | +| ↳ `managerId` | string | User ID of the user's manager | | ↳ `createdAt` | string | ISO 8601 creation timestamp | | ↳ `updatedAt` | string | ISO 8601 last update timestamp | | ↳ `interviewEvents` | array | Scheduled interview events on this schedule | @@ -1553,43 +1562,7 @@ Sets several custom field values on one Ashby Application, Candidate, Job, or Op | Parameter | Type | Description | | --------- | ---- | ----------- | -| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) | -| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion\) | -| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) | -| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) | -| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) | -| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) | -| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) | -| `customFields` | json | For List Custom Fields, the field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\). For Set Custom Field Values, the field values written to the object \(id, title, isPrivate, valueLabel, value\) | -| `customField` | json | A single custom field value after a write \(id, title, isPrivate, valueLabel, value\) | -| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) | -| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) | -| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) | -| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) | -| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt\) | -| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) | -| `interviewPlans` | json | Interview plans \(id, title, isArchived, createdAt, updatedAt\) | -| `interviewStages` | json | Ordered interview stages for a plan | -| `feedback` | json | Submitted application feedback with form definitions and values | -| `history` | json | Application stage history and allowed actions | -| `tags` | json | List of candidate tags \(id, title, isArchived\) | -| `id` | string | Resource UUID | -| `name` | string | Resource name | -| `title` | string | Job title or job posting title | -| `status` | string | Status | -| `candidate` | json | Candidate summary \(id, name, primaryEmailAddress, primaryPhoneNumber\). For full candidate fields use the candidates list output or the get/create/update candidate operations. | -| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], createdAt, updatedAt\) | -| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) | -| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) | -| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt, job \[included when expandJob=true\]\) | -| `content` | string | Note content | -| `author` | json | Note author \(id, firstName, lastName, email\) | -| `isPrivate` | boolean | Whether the note is private | -| `createdAt` | string | ISO 8601 creation timestamp | -| `applicationId` | string | UUID of the deleted application | -| `moreDataAvailable` | boolean | Whether more pages exist | -| `nextCursor` | string | Pagination cursor for next page | -| `nextSyncCursor` | string | Ashby's opaque token for the next incremental list run, exposed as a cursor so it remains usable in workflow output | +| `customFields` | array | Custom field values | ### Ashby Update Candidate diff --git a/apps/docs/content/docs/integrations/coda.mdx b/apps/docs/content/docs/integrations/coda.mdx index 74ca3e30686..fa6c4054c69 100644 --- a/apps/docs/content/docs/integrations/coda.mdx +++ b/apps/docs/content/docs/integrations/coda.mdx @@ -475,8 +475,6 @@ Get the type and current value of a control in a Coda doc | Parameter | Type | Description | | --------- | ---- | ----------- | | `control` | object | Control details | -| ↳ `controlType` | string | Control type \(aiBlock, button, checkbox, datePicker, dateRangePicker, dateTimePicker, lookup, multiselect, select, scale, slider, reaction, textbox, timePicker\) | -| ↳ `value` | json | Current value \(string, number, boolean, or array of these\) | ### Coda Get Custom Domain Provider @@ -562,7 +560,6 @@ Get the current computed value of a named formula in a Coda doc | Parameter | Type | Description | | --------- | ---- | ----------- | | `formula` | object | Formula details | -| ↳ `value` | json | Computed value \(string, number, boolean, or array of these\) | ### Coda Get Mutation Status diff --git a/apps/docs/content/docs/integrations/context_dev.mdx b/apps/docs/content/docs/integrations/context_dev.mdx index 97f0eaec16d..eb05789b186 100644 --- a/apps/docs/content/docs/integrations/context_dev.mdx +++ b/apps/docs/content/docs/integrations/context_dev.mdx @@ -56,6 +56,8 @@ Scrape any URL and return clean, LLM-ready markdown content. | --------- | ---- | ----------- | | `markdown` | string | Page content as clean markdown | | `url` | string | The scraped URL | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Scrape HTML @@ -80,6 +82,8 @@ Scrape any URL and return the raw HTML content of the page. | `html` | string | Raw HTML content of the page | | `url` | string | The scraped URL | | `type` | string | Detected content type \(html, xml, json, text, csv, markdown, svg, pdf, doc, docx\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Scrape Images @@ -110,6 +114,8 @@ Discover every image asset on a page, with optional dimension and type enrichmen | ↳ `alt` | string | Alt text | | ↳ `enrichment` | json | Optional enrichment \(width, height, mimetype, url, type\) when requested | | `url` | string | The scraped URL | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Screenshot @@ -139,6 +145,8 @@ Capture a screenshot of any web page and store it as a downloadable image file. | `domain` | string | Domain that was captured | | `width` | number | Screenshot width in pixels | | `height` | number | Screenshot height in pixels | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Crawl @@ -170,6 +178,8 @@ Crawl an entire website and return each discovered page as clean markdown. | ↳ `markdown` | string | Page content as markdown | | ↳ `metadata` | json | Page metadata \(url, title, crawlDepth, statusCode\) | | `metadata` | object | Crawl summary \(numUrls, maxCrawlDepth, numSucceeded, numFailed, numSkipped\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Map @@ -192,6 +202,8 @@ Build a sitemap of a domain and return every discovered page URL. | `domain` | string | The domain that was mapped | | `urls` | array | All page URLs discovered from the sitemap | | `meta` | object | Sitemap discovery stats \(sitemapsDiscovered, sitemapsFetched, sitemapsSkipped, errors\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Search @@ -223,6 +235,8 @@ Search the web with natural language and optionally scrape results to markdown. | ↳ `relevance` | string | Relevance rating \(high, medium, low\) | | ↳ `markdown` | json | Scraped markdown for the result \(when markdown scraping is enabled\) | | `query` | string | The query that was searched | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Extract @@ -253,6 +267,8 @@ Crawl a website and extract structured data matching a provided JSON schema. | `urlsAnalyzed` | array | URLs that were analyzed during extraction | | `data` | json | Structured data matching the requested schema | | `metadata` | object | Crawl summary \(numUrls, maxCrawlDepth, numSucceeded, numFailed, numSkipped\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Extract Product @@ -288,6 +304,8 @@ Detect and extract structured product details from a single product page URL. | ↳ `image_url` | string | Primary product image URL | | ↳ `images` | json | Product image URLs | | ↳ `sku` | string | Product SKU | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Extract Products @@ -322,6 +340,8 @@ Extract the product catalog from a brand's website by domain (beta). | ↳ `image_url` | string | Primary product image URL | | ↳ `images` | json | Product image URLs | | ↳ `sku` | string | Product SKU | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Scrape Fonts @@ -351,6 +371,8 @@ Extract the font families, usage stats, and font files used by a domain. | ↳ `percent_words` | number | Percent of words using the font | | ↳ `percent_elements` | number | Percent of elements using the font | | `fontLinks` | json | Font family download links keyed by font name \(type, files, category\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Scrape Styleguide @@ -372,6 +394,8 @@ Extract a domain's design system: colors, typography, spacing, shadows, and UI c | `status` | string | Extraction status | | `domain` | string | The domain that was analyzed | | `styleguide` | json | Design system: mode, colors, typography, elementSpacing, shadows, fontLinks, components | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Classify NAICS @@ -398,6 +422,8 @@ Classify a brand into NAICS industry codes from its domain or company name. | ↳ `code` | string | Industry code | | ↳ `name` | string | Industry name | | ↳ `confidence` | string | Match confidence \(high, medium, low\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Classify SIC @@ -429,6 +455,8 @@ Classify a brand into SIC industry codes from its domain or company name. | ↳ `majorGroup` | string | Major group code \(original_sic only\) | | ↳ `majorGroupName` | string | Major group name \(original_sic only\) | | ↳ `office` | string | SEC office \(latest_sec only\) | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Get Brand @@ -467,6 +495,8 @@ Retrieve brand data for a domain: logos, colors, backdrops, socials, address, an | ↳ `industries` | json | Industry taxonomy \(eic industry/subindustry pairs\) | | ↳ `links` | json | Key brand links \(careers, privacy, terms, blog, pricing, contact\) | | ↳ `primary_language` | string | Primary language of the brand site | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Get Brand by Name @@ -506,6 +536,8 @@ Retrieve brand data by company name: logos, colors, socials, address, and indust | ↳ `industries` | json | Industry taxonomy \(eic industry/subindustry pairs\) | | ↳ `links` | json | Key brand links \(careers, privacy, terms, blog, pricing, contact\) | | ↳ `primary_language` | string | Primary language of the brand site | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Get Brand by Email @@ -544,6 +576,8 @@ Retrieve brand data from a work email address. Free/disposable emails are reject | ↳ `industries` | json | Industry taxonomy \(eic industry/subindustry pairs\) | | ↳ `links` | json | Key brand links \(careers, privacy, terms, blog, pricing, contact\) | | ↳ `primary_language` | string | Primary language of the brand site | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Get Brand by Ticker @@ -583,6 +617,8 @@ Retrieve brand data for a public company by its stock ticker symbol. | ↳ `industries` | json | Industry taxonomy \(eic industry/subindustry pairs\) | | ↳ `links` | json | Key brand links \(careers, privacy, terms, blog, pricing, contact\) | | ↳ `primary_language` | string | Primary language of the brand site | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | ### Context.dev Identify Transaction @@ -625,5 +661,7 @@ Identify the brand behind a raw bank/card transaction descriptor and return its | ↳ `industries` | json | Industry taxonomy \(eic industry/subindustry pairs\) | | ↳ `links` | json | Key brand links \(careers, privacy, terms, blog, pricing, contact\) | | ↳ `primary_language` | string | Primary language of the brand site | +| `creditsConsumed` | number | Credits consumed by this request | +| `creditsRemaining` | number | Credits remaining on the API key | diff --git a/apps/docs/content/docs/integrations/github.mdx b/apps/docs/content/docs/integrations/github.mdx index 605df7d74b5..bd01a1e00a6 100644 --- a/apps/docs/content/docs/integrations/github.mdx +++ b/apps/docs/content/docs/integrations/github.mdx @@ -125,10 +125,10 @@ Create comments on GitHub PRs | `id` | number | Comment ID | | `body` | string | Comment body | | `html_url` | string | GitHub web URL | -| `path` | string | File path \(for file comments\) | -| `line` | number | Line number \(for file comments\) | -| `side` | string | Side \(LEFT/RIGHT for diff comments\) | -| `commit_id` | string | Commit SHA | +| `path` | string | File path \(if file comment\) | +| `line` | number | Line number | +| `side` | string | Diff side | +| `commit_id` | string | Commit ID | | `created_at` | string | Creation timestamp | | `updated_at` | string | Last update timestamp | @@ -916,10 +916,10 @@ Get detailed information about a specific branch in a GitHub repository, includi | Parameter | Type | Description | | --------- | ---- | ----------- | -| `name` | string | Branch name | | `commit` | object | Commit reference info | | ↳ `sha` | string | Commit SHA | | ↳ `url` | string | Commit API URL | +| `name` | string | Branch name | | `protected` | boolean | Whether branch is protected | | `protection` | json | Protection settings object | | `protection_url` | string | URL to protection settings | @@ -1263,7 +1263,7 @@ Get detailed information about a specific issue in a GitHub repository | ↳ `created_at` | string | Creation timestamp | | ↳ `updated_at` | string | Last update timestamp | | ↳ `closed_at` | string | Close timestamp | -| `closed_by` | object | User who closed the issue | +| `closed_by` | object | GitHub user object | | ↳ `login` | string | GitHub username | | ↳ `id` | number | User ID | | ↳ `avatar_url` | string | Avatar image URL | @@ -1303,7 +1303,7 @@ Close an issue in a GitHub repository | `created_at` | string | Creation timestamp | | `updated_at` | string | Last update timestamp | | `closed_at` | string | Close timestamp | -| `state_reason` | string | State reason \(completed/not_planned\) | +| `state_reason` | string | Reason for closing | | `labels` | array | Array of label objects | | ↳ `id` | number | Label ID | | ↳ `node_id` | string | GraphQL node ID | diff --git a/apps/docs/content/docs/integrations/jira.mdx b/apps/docs/content/docs/integrations/jira.mdx index 51b4203f770..fe1e3f73af2 100644 --- a/apps/docs/content/docs/integrations/jira.mdx +++ b/apps/docs/content/docs/integrations/jira.mdx @@ -55,6 +55,13 @@ Retrieve detailed information about a specific Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `timetracking` | object | Time tracking information | +| ↳ `originalEstimate` | string | Original estimate in human-readable format \(e.g., 1w 2d\) | +| ↳ `remainingEstimate` | string | Remaining estimate in human-readable format | +| ↳ `timeSpent` | string | Time spent in human-readable format | +| ↳ `originalEstimateSeconds` | number | Original estimate in seconds | +| ↳ `remainingEstimateSeconds` | number | Remaining estimate in seconds | +| ↳ `timeSpentSeconds` | number | Time spent in seconds | | `id` | string | Issue ID | | `key` | string | Issue key \(e.g., PROJ-123\) | | `self` | string | REST API URL for this issue | @@ -128,13 +135,6 @@ Retrieve detailed information about a specific Jira issue | `created` | string | ISO 8601 timestamp when the issue was created | | `updated` | string | ISO 8601 timestamp when the issue was last updated | | `resolutiondate` | string | ISO 8601 timestamp when the issue was resolved | -| `timetracking` | object | Time tracking information | -| ↳ `originalEstimate` | string | Original estimate in human-readable format \(e.g., 1w 2d\) | -| ↳ `remainingEstimate` | string | Remaining estimate in human-readable format | -| ↳ `timeSpent` | string | Time spent in human-readable format | -| ↳ `originalEstimateSeconds` | number | Original estimate in seconds | -| ↳ `remainingEstimateSeconds` | number | Remaining estimate in seconds | -| ↳ `timeSpentSeconds` | number | Time spent in seconds | | `parent` | object | Parent issue \(for subtasks\) | | ↳ `id` | string | Parent issue ID | | ↳ `key` | string | Parent issue key | diff --git a/apps/docs/content/docs/integrations/meta.json b/apps/docs/content/docs/integrations/meta.json index 3e9e15d234e..0d1a85b2e55 100644 --- a/apps/docs/content/docs/integrations/meta.json +++ b/apps/docs/content/docs/integrations/meta.json @@ -203,6 +203,7 @@ "polymarket", "postgresql", "posthog", + "powerbi", "profound", "prospeo", "pulse", @@ -289,6 +290,7 @@ "wordpress", "workday", "x", + "youcom", "youtube", "zendesk", "zep", diff --git a/apps/docs/content/docs/integrations/mintlify.mdx b/apps/docs/content/docs/integrations/mintlify.mdx index 24ea735f56d..bb3fab4b2cc 100644 --- a/apps/docs/content/docs/integrations/mintlify.mdx +++ b/apps/docs/content/docs/integrations/mintlify.mdx @@ -618,6 +618,10 @@ Export Mintlify per-path and site-wide content view counts for a date range, spl | Parameter | Type | Description | | --------- | ---- | ----------- | +| `totals` | object | Site-wide totals for the date range | +| ↳ `human` | number | Site-wide human traffic | +| ↳ `ai` | number | Site-wide AI bot traffic | +| ↳ `total` | number | Site-wide total | | `views` | array | Per-page content view event counts | | ↳ `path` | string | The documentation page path | | ↳ `human` | number | Content view events from human traffic | @@ -644,6 +648,10 @@ Export Mintlify per-path and site-wide approximate distinct visitor counts for a | Parameter | Type | Description | | --------- | ---- | ----------- | +| `totals` | object | Site-wide totals for the date range | +| ↳ `human` | number | Site-wide human traffic | +| ↳ `ai` | number | Site-wide AI bot traffic | +| ↳ `total` | number | Site-wide total | | `visitors` | array | Per-page unique visitor counts | | ↳ `path` | string | The documentation page path | | ↳ `human` | number | Unique human visitors | diff --git a/apps/docs/content/docs/integrations/powerbi.mdx b/apps/docs/content/docs/integrations/powerbi.mdx new file mode 100644 index 00000000000..75c167a1fff --- /dev/null +++ b/apps/docs/content/docs/integrations/powerbi.mdx @@ -0,0 +1,203 @@ +--- +title: Power BI +description: Query semantic models and manage reports and refreshes +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Connect a home-tenant organizational account through the Power BI Microsoft connection to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. Microsoft Graph connections cannot substitute for this delegated OAuth connection. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, service principals, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version. + + + +## Actions + +### Power BI List Workspaces + +List accessible Power BI workspaces with optional OData filtering and pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `top` | number | No | Maximum number of workspaces to return | +| `skip` | number | No | Number of workspaces to skip | +| `filter` | string | No | OData filter, for example contains\(name,'Sales'\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `workspaces` | array | Accessible workspaces returned by this request | +| ↳ `id` | string | Workspace ID | +| ↳ `name` | string | Workspace name | +| `workspaceCount` | number | Number of workspaces returned by this request | + +### Power BI List Reports + +List reports in a Power BI workspace, including paginated reports. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `reports` | array | Reports in the workspace | +| ↳ `id` | string | Report ID | +| ↳ `name` | string | Report name | +| `reportCount` | number | Number of reports returned | + +### Power BI Get Report + +Get report metadata from a Power BI workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | +| `reportId` | string | Yes | Power BI report ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `report` | object | Report metadata; unavailable fields are null | +| ↳ `id` | string | Report ID | +| ↳ `name` | string | Report name | + +### Power BI List Semantic Models + +List semantic models in a Power BI workspace; available metadata depends on permissions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `datasets` | array | Semantic models in the workspace | +| ↳ `id` | string | Semantic model ID | +| ↳ `name` | string | Semantic model name | +| `datasetCount` | number | Number of semantic models returned | + +### Power BI Get Semantic Model + +Get semantic model metadata from a Power BI workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | +| `datasetId` | string | Yes | Power BI semantic model ID \(dataset ID in the REST API\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `dataset` | object | Semantic model metadata; unavailable fields are null | +| ↳ `id` | string | Semantic model ID | +| ↳ `name` | string | Semantic model name | + +### Power BI Execute DAX Query + +Execute one DAX query returning one table. Requires workspace access, Read/Build permissions, and the tenant Execute Queries setting. Reported errors fail the action even with HTTP 200. Direct tool responses retain partial rows and typed errors; failed workflows expose only the standard error output. Sim limits workflow tool responses to 10 MiB; use summaries or TOPN for larger results. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | +| `datasetId` | string | Yes | Power BI semantic model ID \(dataset ID in the REST API\) | +| `query` | string | Yes | One DAX query returning one table, for example EVALUATE TOPN\(10, Sales\). Power BI caps each query at 100,000 rows or 1,000,000 values, 15 MB of data, and 120 requests per minute per user. Sim applies a stricter 10 MiB workflow tool-response budget. Dynamic column names are preserved; successful queries expose the declared outputs. Connect the error port to handle the standard error when a query fails; failed workflow outputs omit partial rows, errors, and incomplete. | +| `includeNulls` | boolean | No | Include blank values in returned rows; defaults to true | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rows` | array | Returned query rows, preserving provider column keys | +| `rowCount` | number | Number of rows returned | +| `errors` | array | Errors reported at response, query, or table scope | +| ↳ `scope` | string | Error scope: response, query, or table | +| ↳ `details` | json | Dynamic nested provider error details, when supplied | +| `incomplete` | boolean | Whether the provider reported an embedded error; false does not guarantee an unlimited result | +| `informationProtectionLabel` | object | Information protection label, when supplied | + +### Power BI Refresh Semantic Model + +Request a standard asynchronous semantic model refresh and return acceptance immediately. Acceptance does not mean completion; use Get Refresh History separately to inspect status. Shared capacity allows eight refresh requests per day, including scheduled refreshes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | +| `datasetId` | string | Yes | Power BI semantic model ID \(dataset ID in the REST API\) | +| `notifyOption` | string | No | NoNotification, MailOnFailure, or MailOnCompletion; defaults to NoNotification | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `accepted` | boolean | Whether Power BI accepted the refresh request; does not indicate completion | +| `requestId` | string | Provider x-ms-request-id header, when supplied | +| `location` | string | Provider Location header, when supplied | + +### Power BI Get Refresh History + +Get recent semantic model refresh history; requires Write permission on the model. Unknown can mean a refresh is in progress or its completion state is unknown. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `groupId` | string | Yes | Power BI workspace ID | +| `datasetId` | string | Yes | Power BI semantic model ID \(dataset ID in the REST API\) | +| `top` | number | No | Requested number of entries; defaults to the last available 60 | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `refreshes` | array | Recent refresh entries returned by Power BI | +| ↳ `refreshAttempts` | array | Refresh attempts supplied by the provider | +| ↳ `attemptId` | number | Refresh attempt index | +| `refreshCount` | number | Number of refresh entries returned | + +## OAuth Scopes + +Sim requests these scopes when someone connects a Power BI account. On a self-hosted deployment, register your own app with the provider using the settings below. See [Integrations & OAuth](/platform/self-hosting/integrations-oauth) for the full setup. + +| Setting | Value | +| ------- | ----- | +| Redirect URI | `/api/auth/oauth2/callback/microsoft-powerbi` | +| Environment variables | `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET` | + +| Scope | Description | +| ----- | ----------- | +| `https://analysis.windows.net/powerbi/api/Workspace.Read.All` | View Power BI workspaces | +| `https://analysis.windows.net/powerbi/api/Report.Read.All` | View Power BI reports | +| `https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All` | Read, create, update, refresh, and delete Power BI semantic models within your account’s permissions | +| `openid` | Standard authentication | +| `profile` | Access profile information | +| `email` | Access email address | +| `offline_access` | Access account when not using the application | + + diff --git a/apps/docs/content/docs/integrations/quickbooks.mdx b/apps/docs/content/docs/integrations/quickbooks.mdx index 68c696113b1..a215a59eb30 100644 --- a/apps/docs/content/docs/integrations/quickbooks.mdx +++ b/apps/docs/content/docs/integrations/quickbooks.mdx @@ -235,10 +235,6 @@ Create a customer in the connected QuickBooks Online company | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created QuickBooks Customer record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -259,6 +255,10 @@ Create a customer in the connected QuickBooks Online company | ↳ `CurrencyRef` | json | Customer currency reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Customer @@ -285,10 +285,6 @@ Sparse-update a customer in the connected QuickBooks Online company | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated QuickBooks Customer record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -309,6 +305,10 @@ Sparse-update a customer in the connected QuickBooks Online company | ↳ `CurrencyRef` | json | Customer currency reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Employee @@ -332,10 +332,6 @@ Create a non-payroll employee profile in the connected QuickBooks Online company | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created QuickBooks Employee record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -353,6 +349,10 @@ Create a non-payroll employee profile in the connected QuickBooks Online company | ↳ `BillableTime` | boolean | Whether employee time is billable | | ↳ `domain` | string | QuickBooks domain | | ↳ `sparse` | boolean | Whether this is a sparse entity | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Employee @@ -378,10 +378,6 @@ Read, merge, and full-update a non-payroll employee profile | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated QuickBooks Employee record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -399,6 +395,10 @@ Read, merge, and full-update a non-payroll employee profile | ↳ `BillableTime` | boolean | Whether employee time is billable | | ↳ `domain` | string | QuickBooks domain | | ↳ `sparse` | boolean | Whether this is a sparse entity | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Vendor @@ -424,10 +424,6 @@ Create a vendor in the connected QuickBooks Online company | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created QuickBooks Vendor record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -449,6 +445,10 @@ Create a vendor in the connected QuickBooks Online company | ↳ `CurrencyRef` | json | Vendor currency reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Vendor @@ -476,10 +476,6 @@ Read, merge, and full-update a vendor in QuickBooks Online | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated QuickBooks Vendor record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -501,6 +497,10 @@ Read, merge, and full-update a vendor in QuickBooks Online | ↳ `CurrencyRef` | json | Vendor currency reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Item @@ -525,10 +525,6 @@ Create a Service or Non-inventory item in QuickBooks Online | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created QuickBooks Item record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -559,6 +555,10 @@ Create a Service or Non-inventory item in QuickBooks Online | ↳ `ParentRef` | json | Parent item or category reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Item @@ -584,10 +584,6 @@ Read, merge, and full-update a Service or Non-inventory item without changing it | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated QuickBooks Item record | | ↳ `Id` | string | QuickBooks entity ID | | ↳ `SyncToken` | string | Entity sync token | @@ -618,6 +614,10 @@ Read, merge, and full-update a Service or Non-inventory item without changing it | ↳ `ParentRef` | json | Parent item or category reference | | ↳ `value` | string | QuickBooks entity ID | | ↳ `name` | string | QuickBooks entity display name | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Read Sales Transactions @@ -733,10 +733,6 @@ Create an estimate with bounded item and description lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks Estimate | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -769,6 +765,10 @@ Create an estimate with bounded item and description lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Estimate @@ -792,10 +792,6 @@ Sparse-update an estimate using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Estimate | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -828,6 +824,10 @@ Sparse-update an estimate using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Invoice @@ -850,10 +850,6 @@ Create an invoice without emailing or collecting payment | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks Invoice | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -886,6 +882,10 @@ Create an invoice without emailing or collecting payment | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Invoice @@ -909,10 +909,6 @@ Sparse-update an invoice using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Invoice | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -945,6 +941,10 @@ Sparse-update an invoice using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Void Invoice @@ -962,11 +962,6 @@ Void an invoice after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | -| `voided` | boolean | Whether QuickBooks voided the transaction | | `record` | json | Voided native QuickBooks Invoice | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -999,6 +994,11 @@ Void an invoice after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | +| `voided` | boolean | Whether QuickBooks voided the transaction | ### QuickBooks Create Sales Receipt @@ -1023,10 +1023,6 @@ Create a sales receipt for a completed customer sale | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks SalesReceipt | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1059,6 +1055,10 @@ Create a sales receipt for a completed customer sale | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Sales Receipt @@ -1084,10 +1084,6 @@ Sparse-update a sales receipt using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks SalesReceipt | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1120,6 +1116,10 @@ Sparse-update a sales receipt using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Void Sales Receipt @@ -1137,11 +1137,6 @@ Void a sales receipt after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | -| `voided` | boolean | Whether QuickBooks voided the transaction | | `record` | json | Voided native QuickBooks SalesReceipt | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1174,6 +1169,11 @@ Void a sales receipt after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | +| `voided` | boolean | Whether QuickBooks voided the transaction | ### QuickBooks Create Customer Payment @@ -1197,10 +1197,6 @@ Record a customer payment with optional bounded invoice allocations | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks Payment | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1233,6 +1229,10 @@ Record a customer payment with optional bounded invoice allocations | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Customer Payment @@ -1258,10 +1258,6 @@ Read, merge, and full-update a customer payment using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Payment | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1294,6 +1290,10 @@ Read, merge, and full-update a customer payment using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Void Customer Payment @@ -1311,11 +1311,6 @@ Void a customer payment after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | -| `voided` | boolean | Whether QuickBooks voided the transaction | | `record` | json | Voided native QuickBooks Payment | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1348,6 +1343,11 @@ Void a customer payment after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | +| `voided` | boolean | Whether QuickBooks voided the transaction | ### QuickBooks Create Credit Memo @@ -1369,10 +1369,6 @@ Create a customer credit memo with bounded sales lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks CreditMemo | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1405,6 +1401,10 @@ Create a customer credit memo with bounded sales lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Credit Memo @@ -1427,10 +1427,6 @@ Read, merge, and full-update a credit memo using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks CreditMemo | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1463,6 +1459,10 @@ Read, merge, and full-update a credit memo using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Refund Receipt @@ -1487,10 +1487,6 @@ Create a customer refund receipt against a required deposit account | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks RefundReceipt | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1523,6 +1519,10 @@ Create a customer refund receipt against a required deposit account | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Refund Receipt @@ -1548,10 +1548,6 @@ Sparse-update a refund receipt using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks RefundReceipt | | ↳ `Id` | string | QuickBooks sales transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1584,6 +1580,10 @@ Sparse-update a refund receipt using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Read Purchasing Transactions @@ -1737,10 +1737,6 @@ Create a purchase order with bounded expense lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks PurchaseOrder | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1791,6 +1787,10 @@ Create a purchase order with bounded expense lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Purchase Order @@ -1813,10 +1813,6 @@ Read, merge, and full-update purchase-order header fields | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks PurchaseOrder | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1867,6 +1863,10 @@ Read, merge, and full-update purchase-order header fields | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Bill @@ -1891,20 +1891,6 @@ Create a vendor bill with optional Purchase Order line links without paying it | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | -| `linkingRequested` | boolean | Whether any Purchase Order line links were requested | -| `linkingSucceeded` | boolean | Whether QuickBooks returned every requested Purchase Order line link | -| `linkedLines` | array | Requested Purchase Order line links confirmed by QuickBooks | -| ↳ `purchaseOrderId` | string | Requested Purchase Order ID | -| ↳ `purchaseOrderLineId` | string | Requested Purchase Order line ID | -| ↳ `billLineId` | string | Created Bill line ID carrying the confirmed link | -| `missingLinks` | array | Requested Purchase Order line links omitted by QuickBooks | -| ↳ `purchaseOrderId` | string | Requested Purchase Order ID | -| ↳ `purchaseOrderLineId` | string | Requested Purchase Order line ID | -| `linkingWarning` | string | Warning that the Bill was created without every requested Purchase Order link | | `record` | json | Created native QuickBooks Bill | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -1955,6 +1941,20 @@ Create a vendor bill with optional Purchase Order line links without paying it | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | +| `linkingRequested` | boolean | Whether any Purchase Order line links were requested | +| `linkingSucceeded` | boolean | Whether QuickBooks returned every requested Purchase Order line link | +| `linkedLines` | array | Requested Purchase Order line links confirmed by QuickBooks | +| ↳ `purchaseOrderId` | string | Requested Purchase Order ID | +| ↳ `purchaseOrderLineId` | string | Requested Purchase Order line ID | +| ↳ `billLineId` | string | Created Bill line ID carrying the confirmed link | +| `missingLinks` | array | Requested Purchase Order line links omitted by QuickBooks | +| ↳ `purchaseOrderId` | string | Requested Purchase Order ID | +| ↳ `purchaseOrderLineId` | string | Requested Purchase Order line ID | +| `linkingWarning` | string | Warning that the Bill was created without every requested Purchase Order link | ### QuickBooks Update Bill @@ -1977,10 +1977,6 @@ Read, merge, and full-update bill header fields using its current sync token | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Bill | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2031,6 +2027,10 @@ Read, merge, and full-update bill header fields using its current sync token | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Bill Payment @@ -2056,10 +2056,6 @@ Record a check or credit-card payment allocated to one or more bills | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks BillPayment | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2110,6 +2106,10 @@ Record a check or credit-card payment allocated to one or more bills | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Bill Payment @@ -2129,10 +2129,6 @@ Read, merge, and full-update a BillPayment without changing allocations | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks BillPayment | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2183,6 +2179,10 @@ Read, merge, and full-update a BillPayment without changing allocations | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Void Bill Payment @@ -2200,11 +2200,6 @@ Void a bill payment after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | -| `voided` | boolean | Whether QuickBooks voided the transaction | | `record` | json | Voided native QuickBooks BillPayment | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2255,6 +2250,11 @@ Void a bill payment after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | +| `voided` | boolean | Whether QuickBooks voided the transaction | ### QuickBooks Create Vendor Credit @@ -2278,10 +2278,6 @@ Create a vendor credit without applying it to a bill | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks VendorCredit | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2332,6 +2328,10 @@ Create a vendor credit without applying it to a bill | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Vendor Credit @@ -2353,10 +2353,6 @@ Read, merge, and full-update vendor-credit header fields | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks VendorCredit | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2407,6 +2403,10 @@ Read, merge, and full-update vendor-credit header fields | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Purchase @@ -2431,10 +2431,6 @@ Record a cash, check, or credit-card purchase with bounded expense lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks Purchase | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2485,6 +2481,10 @@ Record a cash, check, or credit-card purchase with bounded expense lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Purchase @@ -2505,10 +2505,6 @@ Read, merge, and full-update purchase header fields without changing lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Purchase | | ↳ `Id` | string | QuickBooks purchasing transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2559,6 +2555,10 @@ Read, merge, and full-update purchase header fields without changing lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Read Accounting Transactions @@ -2653,10 +2653,6 @@ Post a balanced journal entry after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks JournalEntry | | ↳ `Id` | string | QuickBooks accounting transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2679,6 +2675,10 @@ Post a balanced journal entry after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Journal Entry @@ -2699,10 +2699,6 @@ Sparse-update journal-entry header fields after explicit confirmation | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks JournalEntry | | ↳ `Id` | string | QuickBooks accounting transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2725,6 +2721,10 @@ Sparse-update journal-entry header fields after explicit confirmation | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Create Deposit @@ -2746,10 +2746,6 @@ Create a deposit with bounded account lines | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Created native QuickBooks Deposit | | ↳ `Id` | string | QuickBooks accounting transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2772,6 +2768,10 @@ Create a deposit with bounded account lines | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Update Deposit @@ -2791,10 +2791,6 @@ Sparse-update deposit header fields using the current sync token and destination | Parameter | Type | Description | | --------- | ---- | ----------- | -| `recordId` | string | ID of the created or updated QuickBooks entity | -| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | -| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | -| `time` | string | QuickBooks response timestamp | | `record` | json | Updated native QuickBooks Deposit | | ↳ `Id` | string | QuickBooks accounting transaction ID | | ↳ `SyncToken` | string | Current transaction sync token | @@ -2817,6 +2813,10 @@ Sparse-update deposit header fields using the current sync token and destination | ↳ `MetaData` | json | Transaction creation and update timestamps | | ↳ `CreateTime` | string | Entity creation timestamp | | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | +| `recordId` | string | ID of the created or updated QuickBooks entity | +| `syncToken` | string | Native QuickBooks SyncToken returned by the mutation | +| `recordVersion` | string | Latest QuickBooks record version required for a subsequent update; this is the native SyncToken under a display-safe name | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Run Financial Report @@ -3017,11 +3017,6 @@ List attachment metadata for a fixed QuickBooks entity or read one attachment by | Parameter | Type | Description | | --------- | ---- | ----------- | -| `startPosition` | number | One-based position of the first item in this response | -| `maxResults` | number | Actual number of items reported for this response | -| `nextStartPosition` | number | Position to use when explicitly requesting the next page | -| `hasMore` | boolean | Conservative indication that another page may exist | -| `time` | string | QuickBooks response timestamp | | `item` | json | Native QuickBooks attachment metadata | | ↳ `Id` | string | QuickBooks attachment ID | | ↳ `SyncToken` | string | Attachment sync token | @@ -3054,6 +3049,11 @@ List attachment metadata for a fixed QuickBooks entity or read one attachment by | ↳ `LastUpdatedTime` | string | Entity last-updated timestamp | | ↳ `domain` | string | QuickBooks domain | | ↳ `sparse` | boolean | Whether this is a sparse entity | +| `startPosition` | number | One-based position of the first item in this response | +| `maxResults` | number | Actual number of items reported for this response | +| `nextStartPosition` | number | Position to use when explicitly requesting the next page | +| `hasMore` | boolean | Conservative indication that another page may exist | +| `time` | string | QuickBooks response timestamp | ### QuickBooks Add Attachment diff --git a/apps/docs/content/docs/integrations/revenuecat.mdx b/apps/docs/content/docs/integrations/revenuecat.mdx index e4f630f5e2a..a18f3cda7b9 100644 --- a/apps/docs/content/docs/integrations/revenuecat.mdx +++ b/apps/docs/content/docs/integrations/revenuecat.mdx @@ -38,7 +38,7 @@ Retrieve subscriber information by app user ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The subscriber object with subscriptions and entitlements | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -120,7 +120,7 @@ Record a purchase (receipt) for a subscriber via the REST API | Parameter | Type | Description | | --------- | ---- | ----------- | | `customer` | object | Customer object returned at the top level of POST /v1/receipts \(first_seen, last_seen, original_app_user_id, original_application_version, original_sdk_version, management_url, entitlements, original_purchase_date, request_date\). Null when the response uses the `value`-wrapped envelope. | -| `subscriber` | object | The updated subscriber object after recording the purchase | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -171,7 +171,7 @@ Grant a promotional entitlement to a subscriber | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The updated subscriber object after granting the entitlement | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -219,7 +219,7 @@ Revoke all promotional entitlements for a specific entitlement identifier | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The updated subscriber object after revoking the entitlement | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -315,7 +315,7 @@ Defer a Google Play subscription by extending its billing date by a number of da | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The updated subscriber object after deferring the Google subscription | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -363,7 +363,7 @@ Refund a specific store transaction by its store transaction identifier and revo | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The updated subscriber object after refunding the Google subscription | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | @@ -411,7 +411,7 @@ Immediately revoke access to a Google Play subscription and issue a refund (Goog | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscriber` | object | The updated subscriber object after revoking the Google subscription | +| `subscriber` | object | RevenueCat subscriber object | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | | ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | diff --git a/apps/docs/content/docs/integrations/salesforce.mdx b/apps/docs/content/docs/integrations/salesforce.mdx index 65fd20473fd..af9474a7f9a 100644 --- a/apps/docs/content/docs/integrations/salesforce.mdx +++ b/apps/docs/content/docs/integrations/salesforce.mdx @@ -673,9 +673,9 @@ Get a list of up to 200 recently viewed reports for the current user | --------- | ---- | ----------- | | `success` | boolean | Operation success status | | `output` | object | Reports data | +| ↳ `reports` | array | Array of report objects | | ↳ `totalReturned` | number | Number of items returned | | ↳ `success` | boolean | Salesforce operation success | -| ↳ `reports` | array | Array of report objects | ### Get Report Metadata from Salesforce @@ -742,9 +742,9 @@ Get a list of available report types | --------- | ---- | ----------- | | `success` | boolean | Operation success status | | `output` | object | Report types data | +| ↳ `reportTypes` | array | Array of report type objects | | ↳ `totalReturned` | number | Number of items returned | | ↳ `success` | boolean | Salesforce operation success | -| ↳ `reportTypes` | array | Array of report type objects | ### List Dashboards from Salesforce @@ -761,9 +761,9 @@ Get a list of recently used dashboards for the current user | --------- | ---- | ----------- | | `success` | boolean | Operation success status | | `output` | object | Dashboards data | +| ↳ `dashboards` | array | Array of dashboard objects | | ↳ `totalReturned` | number | Number of items returned | | ↳ `success` | boolean | Salesforce operation success | -| ↳ `dashboards` | array | Array of dashboard objects | ### Get Dashboard from Salesforce diff --git a/apps/docs/content/docs/integrations/square.mdx b/apps/docs/content/docs/integrations/square.mdx index 016044af8d4..a97a9bcb58d 100644 --- a/apps/docs/content/docs/integrations/square.mdx +++ b/apps/docs/content/docs/integrations/square.mdx @@ -56,7 +56,7 @@ Take a payment using a payment source such as a card nonce or a card on file | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment` | object | The created payment object | +| `payment` | object | Square Payment object | | ↳ `id` | string | Unique ID for the payment | | ↳ `status` | string | Payment status \(APPROVED, PENDING, COMPLETED, CANCELED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -111,7 +111,7 @@ Retrieve details for a single payment by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment` | object | The retrieved payment object | +| `payment` | object | Square Payment object | | ↳ `id` | string | Unique ID for the payment | | ↳ `status` | string | Payment status \(APPROVED, PENDING, COMPLETED, CANCELED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -224,7 +224,7 @@ Cancel (void) an authorized payment that has not been captured | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment` | object | The canceled payment object | +| `payment` | object | Square Payment object | | ↳ `id` | string | Unique ID for the payment | | ↳ `status` | string | Payment status \(APPROVED, PENDING, COMPLETED, CANCELED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -280,7 +280,7 @@ Capture (complete) a payment that was authorized with delayed capture | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment` | object | The completed payment object | +| `payment` | object | Square Payment object | | ↳ `id` | string | Unique ID for the payment | | ↳ `status` | string | Payment status \(APPROVED, PENDING, COMPLETED, CANCELED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -339,7 +339,7 @@ Refund all or part of a completed payment | Parameter | Type | Description | | --------- | ---- | ----------- | -| `refund` | object | The created refund object | +| `refund` | object | Square PaymentRefund object | | ↳ `id` | string | Unique ID for the refund | | ↳ `status` | string | Refund status \(PENDING, COMPLETED, REJECTED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -372,7 +372,7 @@ Retrieve a single payment refund by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `refund` | object | The retrieved refund object | +| `refund` | object | Square PaymentRefund object | | ↳ `id` | string | Unique ID for the refund | | ↳ `status` | string | Refund status \(PENDING, COMPLETED, REJECTED, or FAILED\) | | ↳ `amount_money` | object | Monetary amount with a currency | @@ -452,7 +452,7 @@ Create a new customer profile in the Square customer directory | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The created customer object | +| `customer` | object | Square Customer object | | ↳ `id` | string | Unique ID for the customer | | ↳ `given_name` | string | First name of the customer | | ↳ `family_name` | string | Last name of the customer | @@ -502,7 +502,7 @@ Retrieve a single customer profile by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The retrieved customer object | +| `customer` | object | Square Customer object | | ↳ `id` | string | Unique ID for the customer | | ↳ `given_name` | string | First name of the customer | | ↳ `family_name` | string | Last name of the customer | @@ -663,7 +663,7 @@ Update fields on an existing customer profile | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The updated customer object | +| `customer` | object | Square Customer object | | ↳ `id` | string | Unique ID for the customer | | ↳ `given_name` | string | First name of the customer | | ↳ `family_name` | string | Last name of the customer | @@ -775,7 +775,7 @@ Retrieve a single location by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `location` | object | The retrieved location object | +| `location` | object | Square Location object | | ↳ `id` | string | Unique ID for the location | | ↳ `name` | string | Name of the location | | ↳ `address` | object | Physical address | @@ -822,7 +822,7 @@ Create an order with line items, taxes, discounts, and fulfillments | Parameter | Type | Description | | --------- | ---- | ----------- | -| `order` | object | The created order object | +| `order` | object | Square Order object | | ↳ `id` | string | Unique ID for the order | | ↳ `location_id` | string | ID of the location for the order | | ↳ `reference_id` | string | Optional external reference for the order | @@ -872,7 +872,7 @@ Retrieve a single order by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `order` | object | The retrieved order object | +| `order` | object | Square Order object | | ↳ `id` | string | Unique ID for the order | | ↳ `location_id` | string | ID of the location for the order | | ↳ `reference_id` | string | Optional external reference for the order | @@ -977,7 +977,7 @@ Pay for an order using one or more already-approved payments | Parameter | Type | Description | | --------- | ---- | ----------- | -| `order` | object | The paid order object | +| `order` | object | Square Order object | | ↳ `id` | string | Unique ID for the order | | ↳ `location_id` | string | ID of the location for the order | | ↳ `reference_id` | string | Optional external reference for the order | @@ -1028,7 +1028,7 @@ Create a draft invoice for an existing order and customer | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The created invoice object | +| `invoice` | object | Square Invoice object | | ↳ `id` | string | Unique ID for the invoice | | ↳ `version` | number | Optimistic concurrency version of the invoice | | ↳ `location_id` | string | ID of the location for the invoice | @@ -1068,7 +1068,7 @@ Retrieve a single invoice by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The retrieved invoice object | +| `invoice` | object | Square Invoice object | | ↳ `id` | string | Unique ID for the invoice | | ↳ `version` | number | Optimistic concurrency version of the invoice | | ↳ `location_id` | string | ID of the location for the invoice | @@ -1192,7 +1192,7 @@ Publish a draft invoice so it is sent to the customer and becomes payable | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The published invoice object | +| `invoice` | object | Square Invoice object | | ↳ `id` | string | Unique ID for the invoice | | ↳ `version` | number | Optimistic concurrency version of the invoice | | ↳ `location_id` | string | ID of the location for the invoice | @@ -1233,7 +1233,7 @@ Cancel a published invoice that is unpaid or partially paid | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The canceled invoice object | +| `invoice` | object | Square Invoice object | | ↳ `id` | string | Unique ID for the invoice | | ↳ `version` | number | Optimistic concurrency version of the invoice | | ↳ `location_id` | string | ID of the location for the invoice | @@ -1293,7 +1293,7 @@ Create or update a catalog object such as an item, variation, or category | Parameter | Type | Description | | --------- | ---- | ----------- | -| `object` | object | The created or updated catalog object | +| `object` | object | Square CatalogObject | | ↳ `type` | string | Type of catalog object \(ITEM, ITEM_VARIATION, CATEGORY, IMAGE, etc.\) | | ↳ `id` | string | Unique ID for the catalog object | | ↳ `version` | number | Optimistic concurrency version of the object | @@ -1325,7 +1325,7 @@ Retrieve a single catalog object by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `object` | object | The retrieved catalog object | +| `object` | object | Square CatalogObject | | ↳ `type` | string | Type of catalog object \(ITEM, ITEM_VARIATION, CATEGORY, IMAGE, etc.\) | | ↳ `id` | string | Unique ID for the catalog object | | ↳ `version` | number | Optimistic concurrency version of the object | @@ -1424,7 +1424,7 @@ Upload an image and attach it to the catalog, optionally to a specific item | Parameter | Type | Description | | --------- | ---- | ----------- | -| `object` | object | The created catalog image object | +| `object` | object | Square CatalogObject | | ↳ `type` | string | Type of catalog object \(ITEM, ITEM_VARIATION, CATEGORY, IMAGE, etc.\) | | ↳ `id` | string | Unique ID for the catalog object | | ↳ `version` | number | Optimistic concurrency version of the object | diff --git a/apps/docs/content/docs/integrations/stripe.mdx b/apps/docs/content/docs/integrations/stripe.mdx index 0712d6d6d45..20d80a9dea9 100644 --- a/apps/docs/content/docs/integrations/stripe.mdx +++ b/apps/docs/content/docs/integrations/stripe.mdx @@ -45,7 +45,7 @@ Create a new Payment Intent to process a payment | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The created Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -113,7 +113,7 @@ Retrieve an existing Payment Intent by ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The retrieved Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -186,7 +186,7 @@ Update an existing Payment Intent | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The updated Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -255,7 +255,7 @@ Confirm a Payment Intent to complete the payment | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The confirmed Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -324,7 +324,7 @@ Capture an authorized Payment Intent | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The captured Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -393,7 +393,7 @@ Cancel a Payment Intent | Parameter | Type | Description | | --------- | ---- | ----------- | -| `payment_intent` | object | The canceled Payment Intent object | +| `payment_intent` | object | Stripe Payment Intent object | | ↳ `id` | string | Unique identifier for the Payment Intent | | ↳ `object` | string | String representing the object type \(payment_intent\) | | ↳ `amount` | number | Amount intended to be collected in smallest currency unit | @@ -602,7 +602,7 @@ Create a new customer object | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The created customer object | +| `customer` | object | Stripe Customer object | | ↳ `id` | string | Unique identifier for the customer | | ↳ `object` | string | String representing the object type \(customer\) | | ↳ `address` | object | Address object | @@ -660,7 +660,7 @@ Retrieve an existing customer by ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The retrieved customer object | +| `customer` | object | Stripe Customer object | | ↳ `id` | string | Unique identifier for the customer | | ↳ `object` | string | String representing the object type \(customer\) | | ↳ `address` | object | Address object | @@ -724,7 +724,7 @@ Update an existing customer | Parameter | Type | Description | | --------- | ---- | ----------- | -| `customer` | object | The updated customer object | +| `customer` | object | Stripe Customer object | | ↳ `id` | string | Unique identifier for the customer | | ↳ `object` | string | String representing the object type \(customer\) | | ↳ `address` | object | Address object | @@ -922,7 +922,7 @@ Create a new subscription for a customer | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscription` | object | The created subscription object | +| `subscription` | object | Stripe Subscription object | | ↳ `id` | string | Unique identifier for the subscription | | ↳ `object` | string | String representing the object type \(subscription\) | | ↳ `application` | string | ID of the Connect application that created the subscription | @@ -999,7 +999,7 @@ Retrieve an existing subscription by ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscription` | object | The retrieved subscription object | +| `subscription` | object | Stripe Subscription object | | ↳ `id` | string | Unique identifier for the subscription | | ↳ `object` | string | String representing the object type \(subscription\) | | ↳ `application` | string | ID of the Connect application that created the subscription | @@ -1079,7 +1079,7 @@ Update an existing subscription | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscription` | object | The updated subscription object | +| `subscription` | object | Stripe Subscription object | | ↳ `id` | string | Unique identifier for the subscription | | ↳ `object` | string | String representing the object type \(subscription\) | | ↳ `application` | string | ID of the Connect application that created the subscription | @@ -1158,7 +1158,7 @@ Cancel a subscription | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscription` | object | The canceled subscription object | +| `subscription` | object | Stripe Subscription object | | ↳ `id` | string | Unique identifier for the subscription | | ↳ `object` | string | String representing the object type \(subscription\) | | ↳ `application` | string | ID of the Connect application that created the subscription | @@ -1235,7 +1235,7 @@ Resume a paused subscription | Parameter | Type | Description | | --------- | ---- | ----------- | -| `subscription` | object | The resumed subscription object | +| `subscription` | object | Stripe Subscription object | | ↳ `id` | string | Unique identifier for the subscription | | ↳ `object` | string | String representing the object type \(subscription\) | | ↳ `application` | string | ID of the Connect application that created the subscription | @@ -1476,7 +1476,7 @@ Create a new invoice | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The created invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | @@ -1628,7 +1628,7 @@ Retrieve an existing invoice by ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The retrieved invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | @@ -1783,7 +1783,7 @@ Update an existing invoice | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The updated invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | @@ -1954,7 +1954,7 @@ Finalize a draft invoice | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The finalized invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | @@ -2107,7 +2107,7 @@ Pay an invoice | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The paid invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | @@ -2259,7 +2259,7 @@ Void an invoice | Parameter | Type | Description | | --------- | ---- | ----------- | -| `invoice` | object | The voided invoice object | +| `invoice` | object | Stripe Invoice object | | ↳ `id` | string | Unique identifier for the invoice | | ↳ `object` | string | String representing the object type \(invoice\) | | ↳ `account_country` | string | Country of the business associated with this invoice | diff --git a/apps/docs/content/docs/integrations/youcom.mdx b/apps/docs/content/docs/integrations/youcom.mdx new file mode 100644 index 00000000000..61d6605116a --- /dev/null +++ b/apps/docs/content/docs/integrations/youcom.mdx @@ -0,0 +1,297 @@ +--- +title: You.com +description: Search, read, and research the web with You.com +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[You.com](https://you.com/) provides web search and research APIs that return live, cited results built for AI agents. + +With the You.com block, you can: + +- **Search the web and news**: Get results with short snippets, only the passages that answer your query, or full page content. Filter by date, country, language, and domain. +- **Read pages**: Fetch any URL as Markdown or HTML, with site metadata. +- **Get cited answers**: Ask a question and get a written answer with numbered citations and the supporting excerpts. +- **Run deep research**: Investigate complex or financial questions across many sources, optionally returning structured fields. Long runs can work in the background and be checked later. +- **Find images and track usage**: Search images (early-access keys only) and check your remaining API credit balance. + +Create an API key on the [You.com platform](https://you.com/platform) and paste it into the block. + +In Sim, the You.com block lets your agents answer from the live web, write cited research briefs, monitor news, and load fresh pages into a knowledge base. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate You.com into the workflow. Search the web and news with query-relevant highlights or full page content, fetch page contents, get cited answers, run deep or financial research, search images, and check API credit balance. + + + +## Actions + +### You.com Search + +Search the web and news with You.com, returning LLM-ready results with optional query-relevant highlights or full page content. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | Search query. Supports search operators such as site: and filetype: | +| `count` | number | No | Maximum results to return per section \(web and news\). Defaults to 10 | +| `offset` | number | No | Page offset in multiples of count, from 0 to 9. Defaults to 0 | +| `freshness` | string | No | Result recency: day, week, month, year, or a range like 2025-01-01to2025-12-31 | +| `country` | string | No | Two-letter country code that sets the geographic focus \(e.g., US, GB, DE\) | +| `language` | string | No | BCP 47 language code for results \(e.g., EN, FR, ZH-HANS\). Defaults to EN | +| `safesearch` | string | No | Content moderation filter: off, moderate, or strict. Defaults to moderate | +| `knowledge` | string | No | Set to core to also return knowledge results from licensed data providers | +| `includeDomains` | string | No | Comma-separated domains to restrict results to \(up to 500\). Cannot be combined with exclude or boost domains | +| `excludeDomains` | string | No | Comma-separated domains to remove from results \(up to 500\) | +| `boostDomains` | string | No | Comma-separated domains to rank higher without filtering others \(up to 500\) | +| `extractionMode` | string | No | Content to extract per result: highlights \(query-relevant passages\) or full_page. Omit for snippets only | +| `extractionSource` | string | No | Where full_page content comes from: blend \(cache, then live crawl\), cache, or fetch. Defaults to blend | +| `extractionFormats` | string | No | Comma-separated full_page formats: markdown, html, or both. Defaults to markdown | +| `crawlTimeout` | number | No | Seconds to wait for page content when crawling, from 1 to 60. Defaults to 10 | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `web` | array | Web results | +| ↳ `url` | string | URL of the result | +| ↳ `title` | string | Title of the result | +| ↳ `description` | string | Brief description of the result | +| ↳ `snippets` | array | Short keyword-centered fragments \(omitted when highlights extraction is used\) | +| ↳ `thumbnailUrl` | string | Thumbnail URL | +| ↳ `faviconUrl` | string | Favicon URL of the result's domain | +| ↳ `pageAge` | string | Age of the result | +| ↳ `highlights` | array | Query-relevant passages \(highlights extraction only\) | +| ↳ `html` | string | Full page HTML \(full_page only\) | +| ↳ `markdown` | string | Full page Markdown \(full_page only\) | +| `news` | array | News results | +| ↳ `url` | string | URL of the article | +| ↳ `title` | string | Title of the article | +| ↳ `description` | string | Brief description of the article | +| ↳ `thumbnailUrl` | string | Thumbnail URL | +| ↳ `pageAge` | string | UTC publication timestamp | +| ↳ `html` | string | Full page HTML \(full_page only\) | +| ↳ `markdown` | string | Full page Markdown \(full_page only\) | +| `knowledge` | array | Knowledge results from licensed data providers \(when knowledge is core\) | +| ↳ `type` | string | Kind of knowledge result \(currently answer\) | +| ↳ `title` | string | Title of the knowledge result | +| ↳ `description` | string | Knowledge drawn from licensed data | +| ↳ `asOf` | string | Date the data covers \(YYYY-MM-DD\) | +| ↳ `attribution` | array | Data provider credits | +| ↳ `name` | string | Data provider name | +| ↳ `sourceDescription` | string | Description of the provider | +| `searchUuid` | string | Unique ID of the search | +| `query` | string | Query used to retrieve the results | +| `latency` | number | Search latency in seconds | + +### You.com Get Contents + +Fetch the HTML, Markdown, and page metadata of one or more web pages with You.com. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `urls` | string | Yes | Comma-separated URLs to fetch content from | +| `formats` | string | No | Comma-separated formats to return: markdown, html, metadata \(JSON-LD and OpenGraph site info\) | +| `crawlTimeout` | number | No | Seconds to wait for page content, from 1 to 60. Defaults to 10 | +| `maxAge` | number | No | Maximum age in seconds of cached content before the page is re-fetched. Defaults to no limit | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pages` | array | Content of each fetched page | +| ↳ `url` | string | URL of the fetched page | +| ↳ `title` | string | Title of the page | +| ↳ `html` | string | Page HTML \(html format\) | +| ↳ `markdown` | string | Page Markdown \(markdown format\) | +| ↳ `siteName` | string | OpenGraph site name \(metadata format\) | +| ↳ `faviconUrl` | string | Favicon URL of the page's domain \(metadata format\) | + +### You.com Answer + +Get a synthesized answer to a question with numbered citations and the web results used to write it. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | The question to answer | +| `freshness` | string | No | Result recency: day, week, month, year, or a range like 2025-01-01to2025-12-31 | +| `country` | string | No | Two-letter country code that sets the geographic focus \(e.g., US, GB, DE\) | +| `language` | string | No | BCP 47 language code for results \(e.g., EN, FR, DE\) | +| `safesearch` | string | No | Content moderation filter: off, moderate, or strict. Defaults to moderate | +| `includeDomains` | string | No | Comma-separated domains to restrict results to \(up to 500\). Cannot be combined with exclude or boost domains | +| `excludeDomains` | string | No | Comma-separated domains to remove from results \(up to 500\) | +| `boostDomains` | string | No | Comma-separated domains to rank higher without filtering others \(up to 500\) | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `answer` | string | Synthesized answer with numbered inline citations referencing citations | +| `citations` | array | Sources cited in the answer, in citation order | +| ↳ `source` | string | URL of the cited source | +| ↳ `excerpts` | array | Verbatim excerpts from the source that support the answer | +| `results` | array | All web results considered while writing the answer, cited or not | +| ↳ `url` | string | URL of the source webpage | +| ↳ `title` | string | Title of the source webpage | +| ↳ `snippets` | array | Text snippets previewing the content | +| ↳ `description` | string | Brief description | +| ↳ `thumbnailUrl` | string | Thumbnail URL | +| ↳ `pageAge` | string | Age of the result | + +### You.com Research + +Run multi-step web research on a question and get a thorough, cited answer, or start it as a background task. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `input` | string | Yes | Research question or complex query \(up to 40,000 characters\) | +| `researchEffort` | string | No | Depth of research: lite \(<10s\), standard \(10-30s\), deep \(<120s\), exhaustive \(<300s\), or frontier \(always runs in the background and returns a task ID\). Defaults to standard | +| `outputSchema` | json | No | JSON Schema for structured output in content. Every object needs additionalProperties: false and all properties required. Not supported with lite | +| `background` | boolean | No | Run as a background task and return a task ID to poll with Get Research Task. Always on for frontier | +| `includeDomains` | string | No | Comma-separated domains the research may use \(up to 500\). Cannot be combined with exclude or boost domains | +| `excludeDomains` | string | No | Comma-separated domains the research must not search or visit \(up to 500\) | +| `boostDomains` | string | No | Comma-separated domains to rank higher without filtering others \(up to 500\) | +| `freshness` | string | No | Source recency: day, week, month, year, or a range like 2025-01-01to2025-12-31 | +| `country` | string | No | Two-letter country code that focuses web results \(e.g., US, GB, DE\) | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | json | Markdown answer with numbered inline citations, or an object matching the output schema when one was supplied | +| `contentType` | string | Format of content: text \(Markdown\) or object \(structured output\) | +| `sources` | array | Web sources used to generate the answer | +| ↳ `url` | string | URL of the source webpage | +| ↳ `title` | string | Title of the source webpage | +| ↳ `snippets` | array | Excerpts from the source used to generate the answer | +| `warnings` | array | Warnings raised during research, such as source access issues or partial results | +| `taskId` | string | Background task ID to pass to Get Research Task \(background only\) | +| `status` | string | Background task status: queued, running, completed, failed, or cancelled \(background only\) | +| `streamUrl` | string | URL path of the task progress event stream \(background only\) | +| `createdAt` | string | When the background task was created, RFC 3339 \(background only\) | + +### You.com Get Research Task + +Get the status of a background research task and, once completed, its cited answer and sources. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `taskId` | string | Yes | ID of the background research task | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `taskId` | string | ID of the background research task | +| `taskType` | string | Task type \(research\) | +| `status` | string | Task status: queued, running, completed, failed, or cancelled | +| `createdAt` | string | When the task was created, RFC 3339 | +| `updatedAt` | string | When the task was last updated, RFC 3339 | +| `completedAt` | string | When the task reached a terminal status, RFC 3339 | +| `error` | string | Diagnostic message when failed | +| `taskInput` | json | Original request parameters submitted for the task | +| ↳ `input` | string | Research question that was submitted | +| ↳ `researchEffort` | string | Research effort level submitted | +| ↳ `background` | boolean | Whether background mode was requested | +| ↳ `outputSchema` | json | Structured output schema submitted | +| ↳ `sourceControl` | json | Source control configuration submitted | +| ↳ `type` | string | Task type \(research\) | +| `content` | json | Markdown answer with numbered inline citations, or an object matching the output schema when one was supplied | +| `contentType` | string | Format of content: text \(Markdown\) or object \(structured output\) | +| `sources` | array | Web sources used to generate the answer | +| ↳ `url` | string | URL of the source webpage | +| ↳ `title` | string | Title of the source webpage | +| ↳ `snippets` | array | Excerpts from the source used to generate the answer | +| `warnings` | array | Warnings raised during research, such as source access issues or partial results | + +### You.com Finance Research + +Research a financial question across earnings reports, SEC filings, analyst coverage, and market news, and get a cited answer. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `input` | string | Yes | Financial research question \(up to 40,000 characters\) | +| `researchEffort` | string | No | Depth of research: deep \(<120s\) or exhaustive \(<300s\). Defaults to deep | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Markdown answer with numbered inline citations referencing sources | +| `contentType` | string | Format of content \(text\) | +| `sources` | array | Web sources used to generate the answer | +| ↳ `url` | string | URL of the source webpage | +| ↳ `title` | string | Title of the source webpage | +| ↳ `snippets` | array | Excerpts from the source used to generate the answer | +| `warnings` | array | Warnings raised during research, such as source access issues or partial results | + +### You.com Search Images + +Find image URLs for a query with You.com. Beta, and limited to You.com early-access API keys. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | Image search query. Supports site:, filetype: \(e.g., filetype:png\), and OR | +| `count` | number | No | Maximum number of images to return, from 1 to 100. Defaults to 10 | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `images` | array | Image results | +| ↳ `title` | string | Title of the image result | +| ↳ `pageUrl` | string | URL of the page containing the image | +| ↳ `imageUrl` | string | Direct URL to the image | +| ↳ `thumbnail` | string | Proxy-hosted thumbnail URL | +| ↳ `largeThumbnail` | string | Larger resized image URL; fall back to thumbnail or imageUrl if it fails | +| `query` | string | Query that was submitted | +| `searchUuid` | string | Unique ID of the search | + +### You.com Get Account Balance + +Get the remaining You.com API credit balance for the account or organization that owns the API key. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | You.com API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `accountId` | string | Stable hashed ID of the billing entity \(user or organization\) | +| `accountType` | string | Billing entity type \(account\) | +| `balance` | number | Remaining credit balance in cents \(divide by 100 for USD\) | + + diff --git a/apps/docs/content/docs/integrations/zoominfo.mdx b/apps/docs/content/docs/integrations/zoominfo.mdx index 88c4dfcfe17..9ba190d6d8d 100644 --- a/apps/docs/content/docs/integrations/zoominfo.mdx +++ b/apps/docs/content/docs/integrations/zoominfo.mdx @@ -66,6 +66,9 @@ Search the ZoomInfo company database by name, industry, location, and size. | Parameter | Type | Description | | --------- | ---- | ----------- | | `companies` | array | Matching companies | +| `totalResults` | number | Total number of matching results across all pages | +| `currentPage` | number | Current page number | +| `totalPages` | number | Total number of pages available | ### ZoomInfo Search Contacts @@ -99,6 +102,9 @@ Search ZoomInfo for contacts (people) by name, job title, company, and other fil | Parameter | Type | Description | | --------- | ---- | ----------- | | `contacts` | array | Matching contacts \(without emails or phone numbers\) | +| `totalResults` | number | Total number of matching results across all pages | +| `currentPage` | number | Current page number | +| `totalPages` | number | Total number of pages available | ### ZoomInfo Enrich Companies @@ -168,6 +174,9 @@ Search for companies showing intent signals on specific topics. | Parameter | Type | Description | | --------- | ---- | ----------- | | `signals` | array | Intent signals with topic, score, audience strength, and company | +| `totalResults` | number | Total number of matching results across all pages | +| `currentPage` | number | Current page number | +| `totalPages` | number | Total number of pages available | ### ZoomInfo Search News @@ -191,5 +200,8 @@ Search ZoomInfo news articles by category, URL, or date range. | Parameter | Type | Description | | --------- | ---- | ----------- | | `articles` | array | News articles matching the filters | +| `totalResults` | number | Total number of matching results across all pages | +| `currentPage` | number | Current page number | +| `totalPages` | number | Total number of pages available | diff --git a/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx b/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx index 99c522a3f49..5623dd5bd8b 100644 --- a/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx +++ b/apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx @@ -113,10 +113,12 @@ One app registration in [Entra ID](https://entra.microsoft.com) covers all of th | Environment variables | Provider IDs | |---|---| -| `MICROSOFT_CLIENT_ID`
`MICROSOFT_CLIENT_SECRET` | `outlook`, `onedrive`, `sharepoint`, `microsoft-teams`, `microsoft-excel`, `microsoft-word`, `microsoft-planner`, `microsoft-dataverse`, `microsoft-ad` | +| `MICROSOFT_CLIENT_ID`
`MICROSOFT_CLIENT_SECRET` | `outlook`, `onedrive`, `sharepoint`, `microsoft-teams`, `microsoft-excel`, `microsoft-word`, `microsoft-planner`, `microsoft-powerbi`, `microsoft-dataverse`, `microsoft-ad` | The same variables also power "Sign in with Microsoft". +For Power BI, register `/api/auth/oauth2/callback/microsoft-powerbi` as a **Web** redirect URI. Sim requests the delegated Power BI API scopes `Workspace.Read.All`, `Report.Read.All`, and `Dataset.ReadWrite.All` with the explicit `https://analysis.windows.net/powerbi/api/` resource prefix, plus identity and offline-access scopes. Connect an organizational account from its home tenant. Power BI uses a separate connection from Microsoft Graph integrations; Credential Groups and service-principal authentication are not supported in this version. See the [Power BI integration](/integrations/powerbi) for permissions and tenant settings. + ### GitHub Search Self-hosted GitHub Search uses a GitHub App for personal account connections and optional organization repository boundaries. Register your own App and configure the server variables below. Sim Cloud users use the [GitHub Search setup flow](/search/github#add-a-repository) directly. @@ -323,7 +325,7 @@ When enabling the native Sim Slack trigger, configure all three variables togeth Your deployment must also be reachable from the provider's servers for webhook triggers to fire — a Sim instance on a private network can use polling triggers but not webhook triggers. Polling triggers additionally require the scheduler; see [Background Jobs](/platform/self-hosting/background-jobs). p.authors.map((a) => a.id))) + return [...ids].map((id) => ({ id })) +} + export async function generateMetadata({ params, }: { diff --git a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx index 881ae42aaa7..b913c431682 100644 --- a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx +++ b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx @@ -47,8 +47,11 @@ const MAX_TEMPLATES_SHOWN = 12 const bySlug = new Map(allIntegrations.map((i) => [i.slug, i])) const byType = new Map(allIntegrations.map((i) => [i.type, i])) -/** Unknown slugs reach the section 404 while known pages remain pre-rendered. */ -export const dynamicParams = true +/** + * Unknown params must 404 before rendering: `notFound()` during render streams this segment's + * `loading.tsx` with a 200 status first. + */ +export const dynamicParams = false /** * Returns up to `limit` related integration slugs from the same category, so diff --git a/apps/sim/app/(landing)/library/[slug]/page.tsx b/apps/sim/app/(landing)/library/[slug]/page.tsx index 57df49bb272..8e2e0f8a4f2 100644 --- a/apps/sim/app/(landing)/library/[slug]/page.tsx +++ b/apps/sim/app/(landing)/library/[slug]/page.tsx @@ -6,8 +6,11 @@ import { ComparisonLinks } from '@/app/(landing)/comparisons/components/comparis import { getComparisonsForPost } from '@/app/(landing)/comparisons/library-links' import { ContentPostPage } from '@/app/(landing)/components' -/** Unknown slugs reach the section 404 while known pages remain pre-rendered. */ -export const dynamicParams = true +/** + * Unknown params must 404 before rendering: `notFound()` during render streams this segment's + * `loading.tsx` with a 200 status first. + */ +export const dynamicParams = false export async function generateStaticParams() { const posts = await getAllPostMeta() diff --git a/apps/sim/app/(landing)/library/authors/[id]/page.tsx b/apps/sim/app/(landing)/library/authors/[id]/page.tsx index 7af8169eb9d..ecb57807631 100644 --- a/apps/sim/app/(landing)/library/authors/[id]/page.tsx +++ b/apps/sim/app/(landing)/library/authors/[id]/page.tsx @@ -6,6 +6,17 @@ import { ContentAuthorPage } from '@/app/(landing)/components' export const revalidate = 3600 +/** + * Unknown params must 404 before rendering: `notFound()` during render streams this segment's + * `loading.tsx` with a 200 status first. + */ +export const dynamicParams = false + +export async function generateStaticParams() { + const ids = new Set((await getAllPostMeta()).flatMap((p) => p.authors.map((a) => a.id))) + return [...ids].map((id) => ({ id })) +} + export async function generateMetadata({ params, }: { diff --git a/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx b/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx index 47eb53e18a8..c2eb4c7dc55 100644 --- a/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx +++ b/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx @@ -10,7 +10,6 @@ import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/component import { ShareButton } from '@/app/(landing)/components/share-button' import { FeaturedModelCard, ProviderIcon } from '@/app/(landing)/models/components/model-primitives' import { - ALL_CATALOG_MODELS, buildModelCapabilityFacts, buildModelFaqs, formatPrice, @@ -21,18 +20,21 @@ import { getPricingBounds, getProviderBySlug, getRelatedModels, + MODEL_PROVIDERS_WITH_MODELS, } from '@/app/(landing)/models/utils' const baseUrl = SITE_URL -/** Unknown slugs reach the section 404 while known pages remain pre-rendered. */ -export const dynamicParams = true +/** + * Unknown params must 404 before rendering: `notFound()` during render streams this segment's + * `loading.tsx` with a 200 status first. + */ +export const dynamicParams = false export async function generateStaticParams() { - return ALL_CATALOG_MODELS.map((model) => ({ - provider: model.providerSlug, - model: model.slug, - })) + return MODEL_PROVIDERS_WITH_MODELS.flatMap((provider) => + provider.models.map((model) => ({ provider: provider.slug, model: model.slug })) + ) } export async function generateMetadata({ diff --git a/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx b/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx index 24421550348..a49935c38cc 100644 --- a/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx +++ b/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx @@ -19,16 +19,20 @@ import { formatTokenCount, getProviderBySlug, MODEL_PROVIDERS_WITH_CATALOGS, + MODEL_PROVIDERS_WITH_MODELS, TOP_MODEL_PROVIDERS, } from '@/app/(landing)/models/utils' const baseUrl = SITE_URL -/** Unknown slugs reach the section 404 while known pages remain pre-rendered. */ -export const dynamicParams = true +/** + * Unknown params must 404 before rendering: `notFound()` during render streams this segment's + * `loading.tsx` with a 200 status first. + */ +export const dynamicParams = false export async function generateStaticParams() { - return MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({ + return MODEL_PROVIDERS_WITH_MODELS.map((provider) => ({ provider: provider.slug, })) } diff --git a/apps/sim/app/(landing)/models/utils.ts b/apps/sim/app/(landing)/models/utils.ts index 61a0947dd61..3761baaa8ed 100644 --- a/apps/sim/app/(landing)/models/utils.ts +++ b/apps/sim/app/(landing)/models/utils.ts @@ -578,6 +578,10 @@ function assertUniqueGeneratedRoutes(providers: CatalogProvider[]): void { assertUniqueGeneratedRoutes(rawProviders) export const MODEL_CATALOG_PROVIDERS: CatalogProvider[] = rawProviders +/** Every provider with a page: those with a model catalog, resellers included. */ +export const MODEL_PROVIDERS_WITH_MODELS = MODEL_CATALOG_PROVIDERS.filter( + (provider) => provider.models.length > 0 +) export const MODEL_PROVIDERS_WITH_CATALOGS = MODEL_CATALOG_PROVIDERS.filter( (provider) => provider.models.length > 0 && !provider.isReseller ) diff --git a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts index c0568a6dae3..4e0a2ee3985 100644 --- a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts +++ b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.test.ts @@ -185,6 +185,7 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { queueTableRows(copilotChats, [chat]) queueTableRows(copilotChats, [chat]) queueTableRows(member, [{ role: 'member' }]) + queueTableRows(copilotChats, [{ id: chat.id }]) const attachments = [ { id: 'upload-1', @@ -215,6 +216,26 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { expect(mockAssertActiveWorkspaceAccess).not.toHaveBeenCalled() }) + it('refuses to publish a fork whose source chat was purged while it copied', async () => { + // The fork shares its attachment keys with the source, and cleanup deletes an unreferenced + // key only after deleting the source row: publishing without the source would leave the + // fork pointing at bytes that cleanup is about to delete. + dbChainMockFns.limit.mockReset() + const chat = { ...parentRow, workspaceId: null, organizationId: 'org-1', resources: [] } + queueTableRows(copilotChats, [chat]) + queueTableRows(copilotChats, [chat]) + queueTableRows(member, [{ role: 'member' }]) + queueTableRows(copilotChats, []) + const res = await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(res.status).toBe(404) + expect(mockAppendCopilotChatMessages).not.toHaveBeenCalled() + expect(mockPublishStatusChanged).not.toHaveBeenCalled() + expect(mockFetchGo.mock.calls.map(([url]) => url)).toEqual([ + 'http://mothership.test/api/chats/fork', + 'http://mothership.test/api/tasks/cleanup', + ]) + }) + it.each(['membership', 'capability'] as const)( 'denies organization forks after %s revocation before copying', async (revocation) => { @@ -316,6 +337,12 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { expect(mockPublishStatusChanged).not.toHaveBeenCalled() }) + it.each([404, 409, 413])('passes a worker %i refusal through', async (status) => { + mockFetchGo.mockResolvedValue(Response.json({ error: 'refused' }, { status })) + const res = await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(res.status).toBe(status) + }) + it('surfaces failed blob copies and excludes their metadata from publication', async () => { mockExecuteChatFileBlobCopies.mockResolvedValue({ copied: 1, @@ -336,6 +363,116 @@ describe('POST /api/mothership/chats/[chatId]/fork', () => { expect(dbChainMockFns.delete).not.toHaveBeenCalled() }) + it('discards the worker copy when the fork cannot be published', async () => { + dbChainMockFns.transaction.mockRejectedValueOnce(new Error('connection lost')) + const res = await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(res.status).toBe(500) + const [fork, discard] = mockFetchGo.mock.calls + expect(fork[0]).toBe('http://mothership.test/api/chats/fork') + expect(discard[0]).toBe('http://mothership.test/api/tasks/cleanup') + expect(JSON.parse(discard[1].body)).toEqual({ + chatIds: [JSON.parse(fork[1].body).newChatId], + }) + expect(mockPublishStatusChanged).not.toHaveBeenCalled() + }) + + it('keeps references on the source file when its copy fails', async () => { + const oldKey = 'workspace/ws-1/old-cat.png' + mockListForkableChatFiles.mockResolvedValue([ + { id: OLD_FILE_ID, key: oldKey, messageId: 'msg-1', workspaceId: 'ws-1' }, + ]) + mockPlanChatFileCopies.mockReturnValue({ + idMap: new Map([[OLD_FILE_ID, NEW_FILE_ID]]), + keyMap: new Map([[oldKey, 'workspace/ws-1/new-cat.png']]), + blobTasks: [ + { + copyId: NEW_FILE_ID, + sourceKey: oldKey, + targetKey: 'workspace/ws-1/new-cat.png', + context: 'mothership', + fileName: 'cat.png', + contentType: 'image/png', + }, + ], + }) + mockExecuteChatFileBlobCopies.mockResolvedValue({ + copied: 0, + failed: 1, + failedCopyIds: [NEW_FILE_ID], + }) + const res = await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(res.status).toBe(200) + // The copy is never published, so neither Sim's transcript nor the worker's may name it. + expect(mockAppendCopilotChatMessages.mock.calls[0][1][0].content).toBe( + `See ![cat](/api/files/view/${OLD_FILE_ID})` + ) + const forkRequest = JSON.parse(mockFetchGo.mock.calls[0][1].body) + expect(forkRequest.fileIds).toEqual({}) + expect(forkRequest.fileKeys).toEqual({}) + }) + + it('re-points in-app file links and tool-call arguments at the copied file', async () => { + mockLoadCopilotChatMessages.mockResolvedValue([ + { ...threeMessages[0], content: `Open /workspace/ws-1/files/${OLD_FILE_ID}` }, + { + ...threeMessages[1], + contentBlocks: [ + { + type: 'tool', + toolCall: { + id: 'call-1', + name: 'sim_cli', + state: 'success', + params: { fileId: OLD_FILE_ID, args: ['files', 'read', OLD_FILE_ID], n: 2 }, + display: { title: `Read /api/files/view/${OLD_FILE_ID}` }, + }, + }, + ], + }, + ]) + mockPlanChatFileCopies.mockReturnValue({ + idMap: new Map([[OLD_FILE_ID, NEW_FILE_ID]]), + keyMap: new Map(), + blobTasks: [], + }) + const res = await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(res.status).toBe(200) + const [user, assistant] = mockAppendCopilotChatMessages.mock.calls[0][1] + expect(user.content).toBe(`Open /workspace/ws-1/files/${NEW_FILE_ID}`) + expect(assistant.contentBlocks[0].toolCall).toMatchObject({ + params: { fileId: NEW_FILE_ID, args: ['files', 'read', NEW_FILE_ID], n: 2 }, + display: { title: `Read /api/files/view/${NEW_FILE_ID}` }, + }) + }) + + it('drops a Sources tab whose response is past the cut', async () => { + const kept = { type: 'sources', id: 'cited-sources', title: 'Sources' } + dbChainMockFns.limit.mockResolvedValue([ + { ...parentRow, resources: [{ ...kept, sources: { messageId: 'msg-3' } }] }, + ]) + await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(dbChainMockFns.values).toHaveBeenCalledWith(expect.objectContaining({ resources: [] })) + + dbChainMockFns.values.mockClear() + dbChainMockFns.limit.mockResolvedValue([ + { + ...parentRow, + resources: [{ ...kept, sources: { messageId: 'live-id', requestId: 'req-2' } }], + }, + ]) + mockLoadCopilotChatMessages.mockResolvedValue([ + threeMessages[0], + { ...threeMessages[1], requestId: 'req-2' }, + threeMessages[2], + ]) + await POST(createRequest('chat-1'), createRouteContext({ chatId: 'chat-1' })) + expect(dbChainMockFns.values).toHaveBeenCalledWith( + expect.objectContaining({ + resources: [{ ...kept, sources: { messageId: 'live-id', requestId: 'req-2' } }], + }) + ) + }) + it('copies pre-cut uploads and drops only post-cut ghosts', async () => { // The source chat owns two more uploads (apple pre-cut, banana post-cut) // beside the kept one, plus one shared workspace-file resource. The fork diff --git a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts index ffecce2ca36..312f27b26ce 100644 --- a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts +++ b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { forkMothershipChatContract } from '@/lib/api/contracts/mothership-chats' import { parseRequest } from '@/lib/api/server' -import { asOrchestrationError } from '@/lib/core/orchestration/types' +import { asOrchestrationError, statusForOrchestrationError } from '@/lib/core/orchestration/types' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { forkChat } from '@/lib/mothership/chat/application/fork' import { @@ -38,6 +38,11 @@ export const POST = withRouteHandler( if (classified?.code === 'not_found' || classified?.code === 'forbidden') return NextResponse.json({ error: 'Chat not found' }, { status: 404 }) if (classified?.code === 'validation') return createBadRequestResponse(classified.message) + if (classified?.code === 'conflict' || classified?.code === 'payload_too_large') + return NextResponse.json( + { error: classified.message }, + { status: statusForOrchestrationError(classified.code) } + ) logger.error('Error forking chat:', error) return createInternalServerErrorResponse('Failed to fork chat') } diff --git a/apps/sim/app/api/v2/files/[fileId]/text/route.test.ts b/apps/sim/app/api/v2/files/[fileId]/text/route.test.ts index a0c7b08609d..4f5f9aceafb 100644 --- a/apps/sim/app/api/v2/files/[fileId]/text/route.test.ts +++ b/apps/sim/app/api/v2/files/[fileId]/text/route.test.ts @@ -112,6 +112,16 @@ describe('GET /api/v2/files/[fileId]/text', () => { expect(mocks.readText).not.toHaveBeenCalled() }) + /** + * Callers used to zero-based paging send `offset=0` for the first window. It + * names no other line, so it reads from the first one instead of failing. + */ + it('reads offset 0 as the first line', async () => { + const response = await GET(textRequest(`workspaceId=${WORKSPACE_ID}&offset=0&limit=2`), context) + + expect(response.status).toBe(200) + }) + it('conceals a cross-tenant file as a missing file', async () => { mocks.readText.mockRejectedValueOnce(new NoWorkspaceAccessError()) diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts index 6a314a88e12..ddcf5a134d7 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts @@ -45,9 +45,10 @@ export const GET = defineInternalJsonRoute({ * draft graph. Redacting here would blank OAuth accounts and resource selectors in that viewer * without closing any disclosure boundary, so this surface opts into the raw graph. */ - mapInput: ({ params }) => ({ + mapInput: ({ params, query }) => ({ workflowId: params.id, version: params.version, + expectedDeploymentVersionId: query.expectedDeploymentVersionId, includeCredentialValues: true, representation: 'comparison' as const, }), diff --git a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx index cf99a9d5a72..b82ab88f86d 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx @@ -19,6 +19,7 @@ import { useCopyToClipboard, } from '@sim/emcn' import { useParams, useRouter } from 'next/navigation' +import { isApiClientError } from '@/lib/api/client/errors' import { isLiveAssistantMessageId } from '@/lib/mothership/chat/live-message-id' import { organizationRoutes } from '@/lib/navigation/paths' import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context' @@ -40,6 +41,9 @@ interface MessageActionsProps { messageId?: string } +/** Fork refusals whose message tells the person what to do: the response is unfinished, or the chat is too long. */ +const FORK_REFUSAL_STATUSES = new Set([409, 413]) + export const MessageActions = memo(function MessageActions({ content, getCopyContent, @@ -143,8 +147,12 @@ export const MessageActions = memo(function MessageActions({ useFolderStore.getState().clearChatSelection() router.push(`/workspace/${params.workspaceId}/chat/${result.id}`) } - } catch { - toast.error('Failed to fork chat') + } catch (error) { + toast.error( + isApiClientError(error) && FORK_REFUSAL_STATUSES.has(error.status) + ? error.message + : 'Failed to fork chat' + ) } } diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.test.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.test.ts index e42181d47c2..2b744ca9404 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.test.ts @@ -1,5 +1,7 @@ -import { describe, expect, it, vi } from 'vitest' +import { libDesktopMock, libDesktopMockFns } from '@sim/testing/mocks/lib-desktop.mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' +vi.mock('@/lib/desktop', () => libDesktopMock) vi.mock('@/lib/mothership/resources/extraction', () => ({ isResourceToolName: vi.fn(() => false), extractResourcesFromToolResult: vi.fn(() => []), @@ -48,6 +50,10 @@ function toolNode(ctx: StreamLoopContext, id: string): ToolNode { } describe('tool events (dispatch → model + side effects)', () => { + beforeEach(() => { + libDesktopMockFns.mockGetDesktopBridge.mockReturnValue({}) + }) + it('buffers a result that arrives before its call, then applies it', () => { const ctx = createStreamLoopContext(makeStreamLoopDeps()) dispatchStreamEvent(ctx, toolResult('tc-2', true)) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts index 1d92bcf8ddb..f110ff33b93 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.ts @@ -1,5 +1,6 @@ import { isCurrentBrowserToolName } from '@sim/browser-protocol' import { isTerminalToolName } from '@sim/terminal-protocol' +import { isDesktopApp } from '@/lib/desktop' import { MothershipStreamV1ToolPhase, MothershipStreamV1ToolStatus, @@ -16,6 +17,7 @@ import { } from '@/lib/mothership/resources/extraction' import { isClientExecutedToolCall, + isDesktopExecutedToolCall, isWorkflowToolName, } from '@/lib/mothership/tools/client-executed-tools' import { invalidateResourceQueries } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry' @@ -191,8 +193,11 @@ export function handleToolEvent(ctx: StreamLoopContext, parsed: ToolEvent): void const isPartial = payload.partial === true || payload.status === MothershipStreamV1ToolStatus.generating const args = payload.arguments as Record | undefined + // Every client tailing the chat sees the call. A client without the desktop app leaves desktop + // tools to it: its answer could only be an error, and that error would beat the real result. const shouldStartClientTool = isClientExecutedToolCall(name, args) && + (isDesktopApp() || !isDesktopExecutedToolCall(name, args)) && !isPartial && !deps.options.suppressedWorkflowToolStartIds?.has(rawId) && node?.kind === 'tool' && diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx index 85de6b4709a..bc1e125b79d 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx @@ -87,9 +87,8 @@ export function TableFilter({ if (deferredRule && !isCompleteRule(deferredRule)) { const previouslyAppliedRule = currentRules.find((rule) => rule.id === deferredRule.id) if (previouslyAppliedRule && isCompleteRule(previouslyAppliedRule)) { - const deferredRules = deferredAppliedRules - if (!deferredRules.has(deferredRule.id)) { - deferredRules.set(deferredRule.id, previouslyAppliedRule) + if (!deferredAppliedRules.has(deferredRule.id)) { + deferredAppliedRules.set(deferredRule.id, previouslyAppliedRule) } } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/compare-versions-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/compare-versions-modal.tsx index b8feeb9b28c..c97f786ce96 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/compare-versions-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/compare-versions-modal.tsx @@ -1,21 +1,12 @@ 'use client' -import { useId, useMemo, useState } from 'react' -import { - ChipDropdown, - type ChipDropdownOption, - ChipModal, - ChipModalBody, - ChipModalHeader, -} from '@sim/emcn' +import { useMemo, useState } from 'react' +import { ChipDropdown, type ChipDropdownOption } from '@sim/emcn' import { ArrowRight } from '@sim/emcn/icons' import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils' import { formatVersionLabel } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/format-version-label' import { useDraftWorkflowState } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-draft-workflow-state' -import { - WorkflowDiffSkeleton, - WorkflowDiffView, -} from '@/app/workspace/[workspaceId]/w/components/workflow-diff' +import { WorkflowComparisonModal } from '@/app/workspace/[workspaceId]/w/components/workflow-diff' import { useDeploymentVersionState } from '@/hooks/queries/workflows' /** One side of a comparison: the editor draft or a numbered deployment version. */ @@ -61,7 +52,6 @@ export function CompareVersionsModal({ initialBase, initialTarget, }: CompareVersionsModalProps) { - const descriptionId = useId() const [base, setBase] = useState(initialBase) const [target, setTarget] = useState(initialTarget) /* Subscribed only while the modal is open, and only if a side is the draft. */ @@ -103,15 +93,10 @@ export function CompareVersionsModal({ const loadError = baseQuery.error ?? targetQuery.error return ( - - onOpenChange(false)}> + header={
Compare
-
- -

- Blocks, fields and connections that differ between the two selected versions. -

- {loadError ? ( -
- {loadError.message || 'Could not load one of the versions.'} -
- ) : isLoading || !baseState || !targetState ? ( - - ) : ( - /* One view per picked pair, so selection and folds start fresh when either side changes. */ - - )} -
-
+ } + baseState={baseState} + targetState={targetState} + isLoading={isLoading} + error={loadError} + comparisonKey={`${sideToValue(base)}:${sideToValue(target)}`} + /> ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block/block.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block/block.tsx index 271eab57cfd..e5d7fd54389 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block/block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block/block.tsx @@ -458,7 +458,7 @@ function WorkflowPreviewBlockInner({ id, data }: NodeProps {/* Comparison label above the card */} - {diffStatus && } + {diffStatus && } {/* Selection ring overlay (takes priority over execution rings) */} {isPreviewSelected && (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/diff-label/diff-label.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/diff-label/diff-label.tsx index e07a6dbdad1..7737f04914d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/diff-label/diff-label.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/diff-label/diff-label.tsx @@ -18,15 +18,13 @@ const DIFF_LABEL_CLASS: Record = { interface DiffStatusLabelProps { status: BlockDiffStatus - /** How many fields changed on a modified block; shown after the label when known */ - count?: number } /** * The comparison label floating above a canvas card or container: the same * chrome wherever it appears so a block and a loop read as one system. */ -export function DiffStatusLabel({ status, count }: DiffStatusLabelProps) { +export function DiffStatusLabel({ status }: DiffStatusLabelProps) { return (
{DIFF_LABEL[status]} - {status === 'modified' && count ? ` · ${count}` : null}
) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/index.ts b/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/index.ts index 02eb9a10bc5..4f34fe4a49c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/index.ts @@ -1 +1 @@ -export { WorkflowDiffSkeleton, WorkflowDiffView } from './workflow-diff-view' +export { WorkflowComparisonModal } from './workflow-comparison-modal' diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/workflow-comparison-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/workflow-comparison-modal.tsx new file mode 100644 index 00000000000..540f0522e0a --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/components/workflow-diff/workflow-comparison-modal.tsx @@ -0,0 +1,73 @@ +'use client' + +import { type ReactNode, useId } from 'react' +import { ChipModal, ChipModalBody, ChipModalHeader } from '@sim/emcn' +import { + WorkflowDiffSkeleton, + WorkflowDiffView, +} from '@/app/workspace/[workspaceId]/w/components/workflow-diff/workflow-diff-view' +import type { WorkflowState } from '@/stores/workflows/workflow/types' + +interface WorkflowComparisonModalProps { + open: boolean + onOpenChange: (open: boolean) => void + header: ReactNode + baseState: WorkflowState | null + targetState: WorkflowState | null + isLoading: boolean + error?: Error | null + comparisonKey: string + baseLabel?: string + targetLabel?: string +} + +/** + * Renders caller-loaded snapshots once both are ready, with errors taking precedence. + * Changing comparisonKey resets selection in the shared diff view. + */ +export function WorkflowComparisonModal({ + open, + onOpenChange, + header, + baseState, + targetState, + isLoading, + error, + comparisonKey, + baseLabel, + targetLabel, +}: WorkflowComparisonModalProps) { + const descriptionId = useId() + return ( + + onOpenChange(false)}>{header} + +

+ Blocks, fields and connections that differ between the two selected versions. +

+ {error ? ( +
+ {error.message || 'Could not load one of the versions.'} +
+ ) : isLoading || !baseState || !targetState ? ( + + ) : ( + + )} +
+
+ ) +} diff --git a/apps/sim/blocks/blocks/powerbi.ts b/apps/sim/blocks/blocks/powerbi.ts new file mode 100644 index 00000000000..928ac6a4ade --- /dev/null +++ b/apps/sim/blocks/blocks/powerbi.ts @@ -0,0 +1,559 @@ +import { PowerBIIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { parseOptionalNumberInput } from '@/blocks/utils' + +const WORKSPACE_FIELD = ['workspaceSelector', 'manualWorkspaceId'] as const +const DATASET_FIELD = ['datasetSelector', 'manualDatasetId'] as const +const REPORT_FIELD = ['reportSelector', 'manualReportId'] as const + +/** Parses resolved switch values without treating the string "false" as truthy. */ +function parseIncludeNulls(value: unknown): boolean { + if (value === undefined || value === null || value === '') return true + if (typeof value === 'boolean') return value + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase() + if (normalized === '') return true + if (normalized === 'true') return true + if (normalized === 'false') return false + } + throw new Error('Include nulls must be true or false.') +} + +export const PowerBIBlock: BlockConfig = { + type: 'powerbi', + name: 'Power BI', + description: 'Query semantic models and manage reports and refreshes', + longDescription: `Connect a home-tenant organizational account through the Power BI Microsoft connection to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. Microsoft Graph connections cannot substitute for this delegated OAuth connection. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, service principals, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.`, + docsLink: 'https://docs.sim.ai/integrations/powerbi', + category: 'tools', + integrationType: IntegrationType.Analytics, + authMode: AuthMode.OAuth, + bgColor: '#FFFFFF', + icon: PowerBIIcon, + canvasPresentation: { + defaultTitle: 'Power BI', + sentences: { + byOperation: { + powerbi_list_workspaces: [ + 'List accessible workspaces', + { text: ', matching', field: 'workspaceFilter' }, + ], + powerbi_list_reports: [{ text: 'List reports in', field: WORKSPACE_FIELD, core: true }], + powerbi_get_report: [ + { text: 'Read', field: REPORT_FIELD, core: true }, + { text: 'in', field: WORKSPACE_FIELD, core: true }, + ], + powerbi_list_datasets: [ + { text: 'List semantic models in', field: WORKSPACE_FIELD, core: true }, + ], + powerbi_get_dataset: [ + { text: 'Read', field: DATASET_FIELD, core: true }, + { text: 'in', field: WORKSPACE_FIELD, core: true }, + ], + powerbi_execute_query: [ + { text: 'Query', field: DATASET_FIELD, core: true }, + { text: 'in', field: WORKSPACE_FIELD, core: true }, + ], + powerbi_refresh_dataset: [ + { text: 'Request a refresh of', field: DATASET_FIELD, core: true }, + { text: 'in', field: WORKSPACE_FIELD, core: true }, + ], + powerbi_get_refresh_history: [ + { text: 'Read refresh history of', field: DATASET_FIELD, core: true }, + { text: 'in', field: WORKSPACE_FIELD, core: true }, + ], + }, + }, + }, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Workspaces', id: 'powerbi_list_workspaces' }, + { label: 'List Reports', id: 'powerbi_list_reports' }, + { label: 'Get Report', id: 'powerbi_get_report' }, + { label: 'List Semantic Models', id: 'powerbi_list_datasets' }, + { label: 'Get Semantic Model', id: 'powerbi_get_dataset' }, + { label: 'Execute DAX Query', id: 'powerbi_execute_query' }, + { label: 'Refresh Semantic Model', id: 'powerbi_refresh_dataset' }, + { label: 'Get Refresh History', id: 'powerbi_get_refresh_history' }, + ], + value: () => 'powerbi_execute_query', + }, + { + id: 'credential', + title: 'Power BI Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + serviceId: 'microsoft-powerbi', + requiredScopes: getScopesForService('microsoft-powerbi').filter((scope) => + scope.startsWith('https://analysis.windows.net/powerbi/api/') + ), + placeholder: 'Select Power BI account', + mode: 'basic', + required: true, + paramVisibility: 'user-only', + }, + { + id: 'manualCredential', + title: 'Power BI Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + placeholder: 'Enter Power BI credential ID', + mode: 'advanced', + required: true, + paramVisibility: 'user-only', + }, + { + id: 'workspaceSelector', + title: 'Workspace', + type: 'project-selector', + canonicalParamId: 'groupId', + selectorKey: 'powerbi.workspaces', + serviceId: 'microsoft-powerbi', + placeholder: 'Select workspace', + dependsOn: ['oauthCredential'], + mode: 'basic', + condition: { field: 'operation', value: 'powerbi_list_workspaces', not: true }, + required: true, + }, + { + id: 'manualWorkspaceId', + title: 'Workspace', + type: 'short-input', + canonicalParamId: 'groupId', + placeholder: 'Enter workspace ID', + dependsOn: ['oauthCredential'], + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_list_workspaces', not: true }, + required: true, + }, + { + id: 'reportSelector', + title: 'Report', + type: 'project-selector', + canonicalParamId: 'reportId', + selectorKey: 'powerbi.reports', + serviceId: 'microsoft-powerbi', + placeholder: 'Select report', + dependsOn: ['oauthCredential', 'groupId'], + mode: 'basic', + condition: { field: 'operation', value: 'powerbi_get_report' }, + required: true, + }, + { + id: 'manualReportId', + title: 'Report', + type: 'short-input', + canonicalParamId: 'reportId', + placeholder: 'Enter report ID', + dependsOn: ['oauthCredential', 'groupId'], + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_get_report' }, + required: true, + }, + { + id: 'datasetSelector', + title: 'Semantic Model', + type: 'project-selector', + canonicalParamId: 'datasetId', + selectorKey: 'powerbi.datasets', + serviceId: 'microsoft-powerbi', + placeholder: 'Select semantic model', + dependsOn: ['oauthCredential', 'groupId'], + mode: 'basic', + condition: { + field: 'operation', + value: [ + 'powerbi_get_dataset', + 'powerbi_execute_query', + 'powerbi_refresh_dataset', + 'powerbi_get_refresh_history', + ], + }, + required: true, + }, + { + id: 'manualDatasetId', + title: 'Semantic Model', + type: 'short-input', + canonicalParamId: 'datasetId', + placeholder: 'Enter semantic model ID', + dependsOn: ['oauthCredential', 'groupId'], + mode: 'advanced', + condition: { + field: 'operation', + value: [ + 'powerbi_get_dataset', + 'powerbi_execute_query', + 'powerbi_refresh_dataset', + 'powerbi_get_refresh_history', + ], + }, + required: true, + }, + { + id: 'query', + title: 'DAX Query', + type: 'long-input', + placeholder: 'EVALUATE ROW("KPI", [Your Measure])', + rows: 6, + condition: { field: 'operation', value: 'powerbi_execute_query' }, + required: true, + tooltip: + 'Run one DAX query returning one table. Requires model Read and Build permissions and the tenant Execute Queries setting. SQL, INFO functions, and DMV queries are unsupported.', + wandConfig: { + enabled: true, + prompt: + 'Write one Power BI DAX query returning one table using EVALUATE. Use only the model table, column, and measure names supplied by the user; ask for missing schema instead of inventing names. Prefer summaries or TOPN to keep results small. Do not use SQL, MDX, INFO functions, or DMV queries. Return ONLY the DAX query without markdown or explanation.', + placeholder: 'Describe the measures, filters, and known model fields...', + }, + }, + { + id: 'includeNulls', + title: 'Include Nulls', + type: 'switch', + defaultValue: true, + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + tooltip: 'Include blank values as null in query result rows. Enabled by default.', + }, + { + id: 'notifyOption', + title: 'Refresh Notifications', + type: 'dropdown', + options: [ + { label: 'No Notification', id: 'NoNotification' }, + { label: 'Email on Failure', id: 'MailOnFailure' }, + { label: 'Email on Completion', id: 'MailOnCompletion' }, + ], + value: () => 'NoNotification', + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_refresh_dataset' }, + tooltip: + 'Request a standard refresh and return immediately. Check Get Refresh History separately for completion.', + }, + { + id: 'workspaceTop', + title: 'Maximum Workspaces', + type: 'short-input', + placeholder: 'Defaults to 100', + value: () => '100', + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_list_workspaces' }, + }, + { + id: 'workspaceSkip', + title: 'Workspace Offset', + type: 'short-input', + placeholder: 'Defaults to 0', + value: () => '0', + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_list_workspaces' }, + }, + { + id: 'workspaceFilter', + title: 'Workspace Filter', + type: 'short-input', + placeholder: "name eq 'Sales'", + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_list_workspaces' }, + tooltip: 'Optional OData filter on accessible workspaces.', + wandConfig: { + enabled: true, + generationType: 'odata-expression', + prompt: + "Write an OData $filter expression for the Power BI Get Groups API using workspace names. Use the documented patterns name eq 'Sales', contains(name,'Sales'), or combinations with or. Return ONLY the filter expression, without $filter= or markdown.", + placeholder: 'Describe which workspace names to include...', + }, + }, + { + id: 'refreshHistoryTop', + title: 'Maximum Refreshes', + type: 'short-input', + placeholder: 'Defaults to 60', + value: () => '60', + mode: 'advanced', + condition: { field: 'operation', value: 'powerbi_get_refresh_history' }, + }, + ], + tools: { + access: [ + 'powerbi_list_workspaces', + 'powerbi_list_reports', + 'powerbi_get_report', + 'powerbi_list_datasets', + 'powerbi_get_dataset', + 'powerbi_execute_query', + 'powerbi_refresh_dataset', + 'powerbi_get_refresh_history', + ], + config: { + tool: (params) => params.operation, + params: (params) => { + switch (params.operation) { + case 'powerbi_list_workspaces': + return { + top: + parseOptionalNumberInput(params.workspaceTop, 'Maximum workspaces', { + integer: true, + min: 0, + }) ?? 100, + skip: + parseOptionalNumberInput(params.workspaceSkip, 'Workspace offset', { + integer: true, + min: 0, + }) ?? 0, + filter: params.workspaceFilter, + } + case 'powerbi_get_refresh_history': + return { + top: + parseOptionalNumberInput(params.refreshHistoryTop, 'Maximum refreshes', { + integer: true, + min: 1, + }) ?? 60, + } + case 'powerbi_execute_query': + return { includeNulls: parseIncludeNulls(params.includeNulls) } + case 'powerbi_refresh_dataset': + return { notifyOption: params.notifyOption || 'NoNotification' } + default: + return {} + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Power BI action to perform' }, + oauthCredential: { type: 'string', description: 'Delegated Power BI credential ID' }, + groupId: { type: 'string', description: 'Workspace ID' }, + datasetId: { type: 'string', description: 'Semantic model ID' }, + reportId: { type: 'string', description: 'Report ID' }, + query: { type: 'string', description: 'One DAX query returning one table' }, + includeNulls: { type: 'boolean', description: 'Include null query values; defaults to true' }, + notifyOption: { type: 'string', description: 'Standard refresh notification option' }, + workspaceTop: { type: 'number', description: 'Maximum workspaces; defaults to 100' }, + workspaceSkip: { type: 'number', description: 'Workspace offset; defaults to 0' }, + workspaceFilter: { type: 'string', description: 'Optional OData workspace filter' }, + refreshHistoryTop: { type: 'number', description: 'Maximum refresh entries; defaults to 60' }, + }, + outputs: { + workspaces: { + type: 'json', + description: 'Accessible workspaces with ID, name, read-only state, and capacity metadata', + condition: { field: 'operation', value: 'powerbi_list_workspaces' }, + }, + workspaceCount: { + type: 'number', + description: 'Workspaces in this response page', + condition: { field: 'operation', value: 'powerbi_list_workspaces' }, + }, + reports: { + type: 'json', + description: 'Reports with ID, name, report type, URLs, and optional semantic model ID', + condition: { field: 'operation', value: 'powerbi_list_reports' }, + }, + reportCount: { + type: 'number', + description: 'Reports returned', + condition: { field: 'operation', value: 'powerbi_list_reports' }, + }, + report: { + type: 'json', + description: 'Report details including ID, name, report type, URLs, and semantic model ID', + condition: { field: 'operation', value: 'powerbi_get_report' }, + }, + datasets: { + type: 'json', + description: 'Semantic models with ID, name, and metadata available to the connected user', + condition: { field: 'operation', value: 'powerbi_list_datasets' }, + }, + datasetCount: { + type: 'number', + description: 'Semantic models returned', + condition: { field: 'operation', value: 'powerbi_list_datasets' }, + }, + dataset: { + type: 'json', + description: + 'Semantic model ID, name, owner, creation date, refreshability, and identity metadata', + condition: { field: 'operation', value: 'powerbi_get_dataset' }, + }, + rows: { + type: 'json', + description: 'Rows from a successful query, preserving DAX column names', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + }, + rowCount: { + type: 'number', + description: 'Number of returned query rows', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + }, + errors: { + type: 'json', + description: 'Query errors from response, result, or table levels, with code and message', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + }, + incomplete: { + type: 'boolean', + description: 'Whether query errors mean returned rows are incomplete', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + }, + informationProtectionLabel: { + type: 'json', + description: 'Semantic model information-protection label ID and name, or null', + condition: { field: 'operation', value: 'powerbi_execute_query' }, + }, + accepted: { + type: 'boolean', + description: + 'Whether Power BI accepted the refresh request; this does not confirm completion', + condition: { field: 'operation', value: 'powerbi_refresh_dataset' }, + }, + requestId: { + type: 'string', + description: 'Refresh request ID returned by Power BI, or null', + condition: { field: 'operation', value: 'powerbi_refresh_dataset' }, + }, + location: { + type: 'string', + description: 'Refresh request location returned by Power BI, or null; use history for status', + condition: { field: 'operation', value: 'powerbi_refresh_dataset' }, + }, + refreshes: { + type: 'json', + description: + 'Refresh entries with request ID, type, status, timestamps, errors, and attempts', + condition: { field: 'operation', value: 'powerbi_get_refresh_history' }, + }, + refreshCount: { + type: 'number', + description: 'Refresh history entries returned', + condition: { field: 'operation', value: 'powerbi_get_refresh_history' }, + }, + }, +} + +export const PowerBIBlockMeta = { + tags: ['data-analytics', 'microsoft-365', 'automation'], + url: 'https://www.microsoft.com/en-us/power-platform/products/power-bi', + templates: [ + { + icon: PowerBIIcon, + title: 'Power BI KPI briefing', + prompt: + "Build a scheduled workflow that runs an approved DAX query for daily KPIs in a Power BI semantic model, summarizes successful results, and posts the briefing to Microsoft Teams. Connect the query block's error port to a separate failure notification.", + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['analytics', 'reporting'], + alsoIntegrations: ['microsoft_teams'], + }, + { + icon: PowerBIIcon, + title: 'Power BI sales target alert', + prompt: + 'Create a scheduled workflow that queries approved sales and target measures from a Power BI semantic model, compares complete results with a configured threshold, and alerts the sales team in Slack when the target is missed.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'sales', + tags: ['sales', 'analytics'], + alsoIntegrations: ['slack'], + }, + { + icon: PowerBIIcon, + title: 'Power BI report catalog', + prompt: + 'Build a scheduled workflow that lists accessible Power BI workspaces and their reports, collects report links and semantic model IDs when available, and updates a Sim table as a searchable report catalog.', + modules: ['scheduled', 'tables', 'workflows'], + category: 'operations', + tags: ['inventory', 'reporting'], + }, + { + icon: PowerBIIcon, + title: 'Power BI model inventory', + prompt: + 'Create a scheduled workflow that inventories semantic models in selected accessible workspaces, records names and available owner and refresh metadata in a Sim table, and summarizes missing metadata without assuming read-only responses are complete.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['inventory', 'governance'], + }, + { + icon: PowerBIIcon, + title: 'Power BI pipeline refresh', + prompt: + 'Build a workflow started by an upstream pipeline completion request that asks Power BI to refresh a selected semantic model and records the acceptance and request ID in a Sim table. Notify Microsoft Teams that the refresh was requested, then use a separate scheduled history check to observe completion.', + modules: ['tables', 'workflows', 'scheduled'], + category: 'engineering', + tags: ['automation', 'data-pipeline'], + alsoIntegrations: ['microsoft_teams'], + }, + { + icon: PowerBIIcon, + title: 'Power BI refresh failure monitor', + prompt: + 'Create a scheduled workflow that retrieves refresh history for critical Power BI semantic models, identifies failed requests and their error details, and notifies an operations Slack channel once per request ID using a Sim table to track previously reported failures.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['monitoring', 'incident-management'], + alsoIntegrations: ['slack'], + }, + { + icon: PowerBIIcon, + title: 'Power BI freshness watch', + prompt: + 'Build a scheduled workflow that reads semantic model refresh history, compares the most recent completed refresh with an agreed freshness deadline, and sends Microsoft Teams a stale-data warning when the deadline is missed. Report Unknown statuses separately without claiming a refresh has completed.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['monitoring', 'data-quality'], + alsoIntegrations: ['microsoft_teams'], + }, + ], + skills: [ + { + name: 'summarize-semantic-model-kpis', + description: + 'Run an approved DAX query and turn complete semantic model results into a KPI summary.', + content: + "# Summarize Semantic Model KPIs\n\n## Steps\n1. Select an accessible workspace and semantic model. Ask for known measure names or an approved query; these actions do not discover the model schema.\n2. Use Execute DAX Query with one EVALUATE query returning one table. Keep the result small and include nulls when blanks matter.\n3. Summarize only successful query results. Connect the query block's error port to a failure notification using its error output; partial rows are unavailable on the workflow error path.\n\n## Output\nKPI values and their model context on success, or a separate query failure notification.\n\n## Sources\n[Microsoft DAX queries](https://learn.microsoft.com/en-us/dax/dax-queries) · [Execute Queries API](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/execute-queries-in-group)", + }, + { + name: 'inventory-workspace-reports', + description: + 'Catalog accessible workspace reports and links without assuming every report has a semantic model.', + content: + '# Inventory Workspace Reports\n\n## Steps\n1. Use List Workspaces for the connected user, advancing the numeric offset for subsequent pages when needed.\n2. Use List Reports in each selected workspace. Use Get Report for a specific report.\n3. Record IDs, names, report types, and available URLs. Paginated reports may have no semantic model ID; keep that value null.\n\n## Output\nA catalog limited to the workspaces the account can access.\n\n## Sources\n[Get Groups](https://learn.microsoft.com/en-us/rest/api/power-bi/groups/get-groups) · [Get Reports in Group](https://learn.microsoft.com/en-us/rest/api/power-bi/reports/get-reports-in-group)', + }, + { + name: 'inventory-semantic-models', + description: + 'List semantic models and describe only the metadata available to the connected account.', + content: + '# Inventory Semantic Models\n\n## Steps\n1. Select an accessible workspace and use List Semantic Models.\n2. Use Get Semantic Model for details of a chosen model.\n3. Record model identity and available metadata. A read-only response can contain only ID and name; missing fields do not mean false. The refreshability flag does not prove permission to refresh.\n\n## Output\nA model inventory with explicit unknown metadata.\n\n## Source\n[Get Datasets in Group](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/get-datasets-in-group)', + }, + { + name: 'request-semantic-model-refresh', + description: + 'Request a standard semantic model refresh and record acceptance for later monitoring.', + content: + '# Request Semantic Model Refresh\n\n## Steps\n1. Confirm the workspace and semantic model to refresh.\n2. Use Refresh Semantic Model with the selected notification option. No Notification is the default.\n3. Record accepted, requestId, and location. Acceptance is not completion; use Get Refresh History later. Do not automatically resubmit an ambiguous request failure.\n\n## Output\nRefresh acceptance and the provider request ID when supplied.\n\n## Source\n[Refresh Dataset in Group](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset-in-group)', + }, + { + name: 'monitor-refresh-health', + description: + 'Inspect refresh history for failed requests and summarize their attempts and errors.', + content: + '# Monitor Refresh Health\n\n## Steps\n1. Use Get Refresh History for a selected semantic model with model Write permission.\n2. Inspect request IDs, statuses, timestamps, service exceptions, and refresh attempts. Unknown does not confirm completion.\n3. Report failed requests with available error details and avoid duplicate alerts by retaining previously reported request IDs.\n\n## Output\nA refresh health summary with failed requests and unresolved statuses.\n\n## Sources\n[Get Refresh History](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/get-refresh-history-in-group) · [Microsoft refresh monitoring guidance](https://learn.microsoft.com/en-us/power-bi/guidance/powerbi-implementation-planning-auditing-monitoring-data-level-auditing)', + }, + { + name: 'check-data-freshness', + description: + 'Compare completed refresh history with a freshness deadline before distributing a KPI report.', + content: + '# Check Data Freshness\n\n## Steps\n1. Retrieve enough recent refresh history for the chosen semantic model.\n2. Find the most recent completed refresh with an end time and compare it with the agreed freshness deadline.\n3. Report stale or unknown freshness before publishing a KPI briefing. Refresh history does not include OneDrive refreshes or all refresh warnings.\n\n## Output\nThe last observed completed refresh and whether it meets the deadline.\n\n## Source\n[Microsoft data refresh guidance](https://learn.microsoft.com/en-us/power-bi/connect-data/refresh-data)', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/youcom.ts b/apps/sim/blocks/blocks/youcom.ts new file mode 100644 index 00000000000..b760e162097 --- /dev/null +++ b/apps/sim/blocks/blocks/youcom.ts @@ -0,0 +1,717 @@ +import { YouComIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +const COUNTRY_OPTIONS = [ + { label: 'Any', id: '' }, + { label: 'Argentina', id: 'AR' }, + { label: 'Australia', id: 'AU' }, + { label: 'Austria', id: 'AT' }, + { label: 'Belgium', id: 'BE' }, + { label: 'Brazil', id: 'BR' }, + { label: 'Canada', id: 'CA' }, + { label: 'Chile', id: 'CL' }, + { label: 'China', id: 'CN' }, + { label: 'Denmark', id: 'DK' }, + { label: 'Finland', id: 'FI' }, + { label: 'France', id: 'FR' }, + { label: 'Germany', id: 'DE' }, + { label: 'Hong Kong', id: 'HK' }, + { label: 'India', id: 'IN' }, + { label: 'Indonesia', id: 'ID' }, + { label: 'Italy', id: 'IT' }, + { label: 'Japan', id: 'JP' }, + { label: 'Malaysia', id: 'MY' }, + { label: 'Mexico', id: 'MX' }, + { label: 'Netherlands', id: 'NL' }, + { label: 'New Zealand', id: 'NZ' }, + { label: 'Norway', id: 'NO' }, + { label: 'Philippines', id: 'PH' }, + { label: 'Poland', id: 'PL' }, + { label: 'Portugal', id: 'PT' }, + { label: 'Russia', id: 'RU' }, + { label: 'Saudi Arabia', id: 'SA' }, + { label: 'South Africa', id: 'ZA' }, + { label: 'South Korea', id: 'KR' }, + { label: 'Spain', id: 'ES' }, + { label: 'Sweden', id: 'SE' }, + { label: 'Switzerland', id: 'CH' }, + { label: 'Taiwan', id: 'TW' }, + { label: 'Turkey', id: 'TR' }, + { label: 'United Kingdom', id: 'GB' }, + { label: 'United States', id: 'US' }, +] + +/** Languages the Answer API accepts. Search additionally accepts {@link SEARCH_ONLY_LANGUAGES}. */ +const ANSWER_LANGUAGE_OPTIONS = [ + { label: 'Default', id: '' }, + { label: 'Arabic', id: 'AR' }, + { label: 'Basque', id: 'EU' }, + { label: 'Bengali', id: 'BN' }, + { label: 'Bulgarian', id: 'BG' }, + { label: 'Catalan', id: 'CA' }, + { label: 'Croatian', id: 'HR' }, + { label: 'Czech', id: 'CS' }, + { label: 'Danish', id: 'DA' }, + { label: 'Dutch', id: 'NL' }, + { label: 'English', id: 'EN' }, + { label: 'English (UK)', id: 'EN-GB' }, + { label: 'Estonian', id: 'ET' }, + { label: 'Finnish', id: 'FI' }, + { label: 'French', id: 'FR' }, + { label: 'Galician', id: 'GL' }, + { label: 'German', id: 'DE' }, + { label: 'Greek', id: 'EL' }, + { label: 'Gujarati', id: 'GU' }, + { label: 'Hebrew', id: 'HE' }, + { label: 'Hindi', id: 'HI' }, + { label: 'Hungarian', id: 'HU' }, + { label: 'Icelandic', id: 'IS' }, + { label: 'Italian', id: 'IT' }, + { label: 'Kannada', id: 'KN' }, + { label: 'Korean', id: 'KO' }, + { label: 'Latvian', id: 'LV' }, + { label: 'Lithuanian', id: 'LT' }, + { label: 'Malay', id: 'MS' }, + { label: 'Malayalam', id: 'ML' }, + { label: 'Marathi', id: 'MR' }, + { label: 'Norwegian (Bokmål)', id: 'NB' }, + { label: 'Polish', id: 'PL' }, + { label: 'Punjabi', id: 'PA' }, + { label: 'Romanian', id: 'RO' }, + { label: 'Russian', id: 'RU' }, + { label: 'Serbian', id: 'SR' }, + { label: 'Slovak', id: 'SK' }, + { label: 'Slovenian', id: 'SL' }, + { label: 'Spanish', id: 'ES' }, + { label: 'Swedish', id: 'SV' }, + { label: 'Tamil', id: 'TA' }, + { label: 'Telugu', id: 'TE' }, + { label: 'Thai', id: 'TH' }, + { label: 'Turkish', id: 'TR' }, + { label: 'Ukrainian', id: 'UK' }, + { label: 'Vietnamese', id: 'VI' }, +] + +const SEARCH_ONLY_LANGUAGES = [ + { label: 'Chinese (Simplified)', id: 'ZH-HANS' }, + { label: 'Chinese (Traditional)', id: 'ZH-HANT' }, + { label: 'Japanese', id: 'JA' }, + { label: 'Portuguese (Brazil)', id: 'PT-BR' }, + { label: 'Portuguese (Portugal)', id: 'PT-PT' }, +] + +const SEARCH_LANGUAGE_OPTIONS = [ + ANSWER_LANGUAGE_OPTIONS[0], + ...[...ANSWER_LANGUAGE_OPTIONS.slice(1), ...SEARCH_ONLY_LANGUAGES].sort((a, b) => + a.label.localeCompare(b.label) + ), +] + +const SAFESEARCH_OPTIONS = [ + { label: 'Moderate (default)', id: '' }, + { label: 'Off', id: 'off' }, + { label: 'Strict', id: 'strict' }, +] + +const DOMAIN_FILTER_OPERATIONS = ['youcom_search', 'youcom_answer', 'youcom_research'] + +/** + * You.com documents Exhaustive research as taking up to 300s, the same as the default outbound + * request deadline, so synchronous Exhaustive runs get headroom. Still capped by the plan limit. + */ +const EXHAUSTIVE_REQUEST_TIMEOUT_MS = 360_000 + +function toOptionalNumber(value: unknown): number | undefined { + if (value === undefined || value === null || String(value).trim() === '') return undefined + const number = Number(value) + return Number.isFinite(number) ? number : undefined +} + +export const YouComBlock: BlockConfig = { + type: 'youcom', + name: 'You.com', + description: 'Search, read, and research the web with You.com', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate You.com into the workflow. Search the web and news with query-relevant highlights or full page content, fetch page contents, get cited answers, run deep or financial research, search images, and check API credit balance.', + docsLink: 'https://docs.sim.ai/integrations/youcom', + category: 'tools', + integrationType: IntegrationType.Search, + bgColor: '#FFFFFF', + icon: YouComIcon, + canvasPresentation: { + defaultTitle: 'You.com', + sentences: { + byOperation: { + youcom_search: [ + { text: 'Search the web for', field: 'query', core: true }, + { text: ', returning', field: 'extractionMode' }, + { text: ', within', field: 'includeDomains' }, + ], + youcom_get_contents: [{ text: 'Read page contents from', field: 'urls', core: true }], + youcom_answer: [ + { text: 'Answer', field: 'query', after: 'with cited sources', core: true }, + ], + youcom_research: [ + { text: 'Research', field: 'input', core: true }, + { text: 'at', field: 'researchEffort', after: 'effort' }, + ], + youcom_get_research_task: [{ text: 'Check research task', field: 'taskId', core: true }], + youcom_finance_research: [ + { text: 'Research financial question', field: 'input', core: true }, + ], + youcom_search_images: [{ text: 'Find images of', field: 'query', core: true }], + youcom_get_account_balance: ['Get remaining API credit balance'], + }, + }, + }, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Search', id: 'youcom_search' }, + { label: 'Get Contents', id: 'youcom_get_contents' }, + { label: 'Answer', id: 'youcom_answer' }, + { label: 'Research', id: 'youcom_research' }, + { label: 'Get Research Task', id: 'youcom_get_research_task' }, + { label: 'Finance Research', id: 'youcom_finance_research' }, + { label: 'Search Images', id: 'youcom_search_images' }, + { label: 'Get Account Balance', id: 'youcom_get_account_balance' }, + ], + value: () => 'youcom_search', + }, + { + id: 'query', + title: 'Search Query', + type: 'long-input', + placeholder: 'Enter your search query', + condition: { field: 'operation', value: 'youcom_search' }, + required: true, + }, + { + id: 'query', + title: 'Question', + type: 'long-input', + placeholder: 'Enter your question', + condition: { field: 'operation', value: 'youcom_answer' }, + required: true, + }, + { + id: 'query', + title: 'Image Query', + type: 'long-input', + placeholder: 'Enter what to find images of (supports site: and filetype:)', + condition: { field: 'operation', value: 'youcom_search_images' }, + required: true, + }, + { + id: 'extractionMode', + title: 'Result Content', + type: 'dropdown', + options: [ + { label: 'Snippets', id: '' }, + { label: 'Highlights', id: 'highlights' }, + { label: 'Full Page', id: 'full_page' }, + ], + value: () => '', + description: 'Highlights return only the passages relevant to the query', + condition: { field: 'operation', value: 'youcom_search' }, + }, + { + id: 'extractionFormats', + title: 'Full Page Formats', + type: 'dropdown', + multiSelect: true, + options: [ + { label: 'Markdown', id: 'markdown' }, + { label: 'HTML', id: 'html' }, + ], + placeholder: 'Defaults to Markdown', + condition: { + field: 'operation', + value: 'youcom_search', + and: { field: 'extractionMode', value: 'full_page' }, + }, + mode: 'advanced', + }, + { + id: 'extractionSource', + title: 'Full Page Source', + type: 'dropdown', + options: [ + { label: 'Blend (default)', id: '' }, + { label: 'Cache Only', id: 'cache' }, + { label: 'Live Fetch', id: 'fetch' }, + ], + value: () => '', + condition: { + field: 'operation', + value: 'youcom_search', + and: { field: 'extractionMode', value: 'full_page' }, + }, + mode: 'advanced', + }, + { + id: 'urls', + title: 'URLs', + type: 'long-input', + placeholder: 'https://example.com, https://another.com', + condition: { field: 'operation', value: 'youcom_get_contents' }, + required: true, + }, + { + id: 'formats', + title: 'Formats', + type: 'dropdown', + multiSelect: true, + options: [ + { label: 'Markdown', id: 'markdown' }, + { label: 'HTML', id: 'html' }, + { label: 'Metadata', id: 'metadata' }, + ], + placeholder: 'Select formats to return', + condition: { field: 'operation', value: 'youcom_get_contents' }, + }, + { + id: 'maxAge', + title: 'Max Cache Age (Seconds)', + type: 'short-input', + placeholder: 'Defaults to no limit', + condition: { field: 'operation', value: 'youcom_get_contents' }, + mode: 'advanced', + }, + { + id: 'input', + title: 'Research Question', + type: 'long-input', + placeholder: 'Enter the question to research', + condition: { field: 'operation', value: 'youcom_research' }, + required: true, + }, + { + id: 'researchEffort', + title: 'Research Effort', + type: 'dropdown', + options: [ + { label: 'Lite', id: 'lite' }, + { label: 'Standard', id: 'standard' }, + { label: 'Deep', id: 'deep' }, + { label: 'Exhaustive', id: 'exhaustive' }, + { label: 'Frontier', id: 'frontier' }, + ], + value: () => 'standard', + description: + 'Lite under 10s, Standard 10-30s, Deep under 2 min, Exhaustive under 5 min. Frontier always runs in the background', + condition: { field: 'operation', value: 'youcom_research' }, + }, + { + id: 'outputSchema', + title: 'Output Schema', + type: 'code', + language: 'json', + placeholder: + '{\n "type": "object",\n "properties": {},\n "required": [],\n "additionalProperties": false\n}', + description: + 'JSON Schema for structured output. Every property must be required; not supported with Lite', + condition: { field: 'operation', value: 'youcom_research' }, + mode: 'advanced', + }, + { + id: 'background', + title: 'Run in Background', + type: 'switch', + description: + 'Return a task ID immediately and fetch the result with Get Research Task. Always on for Frontier', + condition: { field: 'operation', value: 'youcom_research' }, + mode: 'advanced', + }, + { + id: 'taskId', + title: 'Task ID', + type: 'short-input', + placeholder: 'Enter the research task ID', + condition: { field: 'operation', value: 'youcom_get_research_task' }, + required: true, + }, + { + id: 'input', + title: 'Financial Question', + type: 'long-input', + placeholder: 'e.g., What drove NVIDIA revenue growth in fiscal 2025?', + condition: { field: 'operation', value: 'youcom_finance_research' }, + required: true, + }, + { + id: 'researchEffort', + title: 'Research Effort', + type: 'dropdown', + options: [ + { label: 'Deep', id: 'deep' }, + { label: 'Exhaustive', id: 'exhaustive' }, + ], + value: () => 'deep', + description: 'Deep takes under 2 min. Exhaustive can take up to 5 min', + condition: { field: 'operation', value: 'youcom_finance_research' }, + }, + { + id: 'count', + title: 'Number of Results', + type: 'short-input', + placeholder: 'Defaults to 10', + condition: { field: 'operation', value: ['youcom_search', 'youcom_search_images'] }, + mode: 'advanced', + }, + { + id: 'offset', + title: 'Page Offset', + type: 'short-input', + placeholder: '0 to 9, in multiples of the result count', + condition: { field: 'operation', value: 'youcom_search' }, + mode: 'advanced', + }, + { + id: 'freshness', + title: 'Freshness', + type: 'short-input', + placeholder: 'day, week, month, year, or 2025-01-01to2025-12-31', + condition: { field: 'operation', value: DOMAIN_FILTER_OPERATIONS }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a You.com freshness value from the user description. Return one of day, week, month, year, or a date range in the exact format YYYY-MM-DDtoYYYY-MM-DD. Return ONLY the value.', + generationType: 'timestamp', + }, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => '', + condition: { field: 'operation', value: DOMAIN_FILTER_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'language', + title: 'Language', + type: 'dropdown', + options: SEARCH_LANGUAGE_OPTIONS, + value: () => '', + condition: { field: 'operation', value: 'youcom_search' }, + mode: 'advanced', + }, + { + id: 'language', + title: 'Language', + type: 'dropdown', + options: ANSWER_LANGUAGE_OPTIONS, + value: () => '', + condition: { field: 'operation', value: 'youcom_answer' }, + mode: 'advanced', + }, + { + id: 'safesearch', + title: 'Safe Search', + type: 'dropdown', + options: SAFESEARCH_OPTIONS, + value: () => '', + condition: { field: 'operation', value: ['youcom_search', 'youcom_answer'] }, + mode: 'advanced', + }, + { + id: 'includeDomains', + title: 'Include Domains', + type: 'long-input', + placeholder: 'example.com, another.com (cannot combine with exclude or boost)', + condition: { field: 'operation', value: DOMAIN_FILTER_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'excludeDomains', + title: 'Exclude Domains', + type: 'long-input', + placeholder: 'example.com, another.com', + condition: { field: 'operation', value: DOMAIN_FILTER_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'boostDomains', + title: 'Boost Domains', + type: 'long-input', + placeholder: 'example.com, another.com', + condition: { field: 'operation', value: DOMAIN_FILTER_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'knowledge', + title: 'Knowledge Results', + type: 'dropdown', + options: [ + { label: 'Off', id: '' }, + { label: 'Include Licensed Data (core)', id: 'core' }, + ], + value: () => '', + condition: { field: 'operation', value: 'youcom_search' }, + mode: 'advanced', + }, + { + id: 'crawlTimeout', + title: 'Crawl Timeout (Seconds)', + type: 'short-input', + placeholder: '1 to 60, defaults to 10', + condition: { field: 'operation', value: ['youcom_search', 'youcom_get_contents'] }, + mode: 'advanced', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your You.com API key', + password: true, + required: true, + }, + ], + tools: { + access: [ + 'youcom_search', + 'youcom_get_contents', + 'youcom_answer', + 'youcom_research', + 'youcom_get_research_task', + 'youcom_finance_research', + 'youcom_search_images', + 'youcom_get_account_balance', + ], + config: { + tool: (params) => params.operation ?? 'youcom_search', + params: (params) => { + const result: Record = {} + const count = toOptionalNumber(params.count) + if (count !== undefined) result.count = count + const offset = toOptionalNumber(params.offset) + if (offset !== undefined) result.offset = offset + const crawlTimeout = toOptionalNumber(params.crawlTimeout) + if (crawlTimeout !== undefined) result.crawlTimeout = crawlTimeout + const maxAge = toOptionalNumber(params.maxAge) + if (maxAge !== undefined) result.maxAge = maxAge + + // Keyed on effort, not operation: Agent tool rows store the operation outside these params + if (params.researchEffort === 'exhaustive' && params.background !== true) { + result.timeout = EXHAUSTIVE_REQUEST_TIMEOUT_MS + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'You.com API key' }, + query: { type: 'string', description: 'Search query, question, or image query' }, + extractionMode: { type: 'string', description: 'Per-result content: highlights or full_page' }, + extractionFormats: { type: 'json', description: 'Full page formats: markdown, html' }, + extractionSource: { type: 'string', description: 'Full page source: blend, cache, or fetch' }, + urls: { type: 'string', description: 'Comma-separated URLs to fetch' }, + formats: { type: 'json', description: 'Content formats: markdown, html, metadata' }, + maxAge: { type: 'number', description: 'Maximum cached content age in seconds' }, + input: { type: 'string', description: 'Research or financial research question' }, + researchEffort: { type: 'string', description: 'Research effort level' }, + outputSchema: { type: 'json', description: 'JSON Schema for structured research output' }, + background: { type: 'boolean', description: 'Run research as a background task' }, + taskId: { type: 'string', description: 'Background research task ID' }, + count: { type: 'number', description: 'Maximum number of results' }, + offset: { type: 'number', description: 'Search page offset' }, + freshness: { type: 'string', description: 'Result recency or date range' }, + country: { type: 'string', description: 'Two-letter country code' }, + language: { type: 'string', description: 'Result language' }, + safesearch: { type: 'string', description: 'Safe search level' }, + includeDomains: { type: 'string', description: 'Domains to restrict results to' }, + excludeDomains: { type: 'string', description: 'Domains to exclude' }, + boostDomains: { type: 'string', description: 'Domains to rank higher' }, + knowledge: { type: 'string', description: 'Set to core to include knowledge results' }, + crawlTimeout: { type: 'number', description: 'Crawl timeout in seconds' }, + }, + outputs: { + web: { + type: 'json', + description: + 'Search only. [{url, title, description, snippets, thumbnailUrl, faviconUrl, pageAge, highlights, html, markdown}]', + }, + news: { + type: 'json', + description: + 'Search only. [{url, title, description, thumbnailUrl, pageAge, html, markdown}]', + }, + knowledge: { + type: 'json', + description: + 'Search only. [{type, title, description, asOf, attribution: [{name, sourceDescription}]}]', + }, + searchUuid: { type: 'string', description: 'Unique ID of the search' }, + query: { type: 'string', description: 'Query used to retrieve the results' }, + latency: { type: 'number', description: 'Search latency in seconds' }, + pages: { + type: 'json', + description: 'Get Contents only. [{url, title, html, markdown, siteName, faviconUrl}]', + }, + answer: { type: 'string', description: 'Answer with numbered inline citations' }, + citations: { type: 'json', description: 'Answer only. [{source, excerpts}]' }, + results: { + type: 'json', + description: + 'Answer only. [{url, title, snippets, description, thumbnailUrl, pageAge}] — every web result considered', + }, + content: { + type: 'json', + description: + 'Research answer in Markdown with numbered citations, or an object matching the output schema', + }, + contentType: { type: 'string', description: 'Research content format: text or object' }, + sources: { type: 'json', description: 'Research sources [{url, title, snippets}]' }, + warnings: { type: 'json', description: 'Research warnings, such as partial results' }, + taskId: { type: 'string', description: 'Background research task ID' }, + status: { + type: 'string', + description: 'Research task status: queued, running, completed, failed, or cancelled', + }, + streamUrl: { type: 'string', description: 'URL path of the research task event stream' }, + createdAt: { type: 'string', description: 'When the research task was created' }, + taskType: { type: 'string', description: 'Research task type' }, + updatedAt: { type: 'string', description: 'When the research task was last updated' }, + completedAt: { type: 'string', description: 'When the research task finished' }, + error: { type: 'string', description: 'Research task failure message' }, + taskInput: { + type: 'json', + description: + 'Research task request {input, researchEffort, background, outputSchema, sourceControl, type}', + }, + images: { + type: 'json', + description: 'Search Images only. [{title, pageUrl, imageUrl, thumbnail, largeThumbnail}]', + }, + accountId: { type: 'string', description: 'Hashed ID of the billing account' }, + accountType: { type: 'string', description: 'Billing entity type' }, + balance: { type: 'number', description: 'Remaining API credit balance in cents' }, + }, +} + +export const YouComBlockMeta = { + tags: ['web-scraping', 'llm', 'agentic'], + url: 'https://you.com', + templates: [ + { + icon: YouComIcon, + title: 'You.com grounded answers', + prompt: + 'Build an agent that takes a question from Chat, uses You.com Answer to get a cited answer from the live web, and replies with the answer plus the source links.', + modules: ['agent', 'workflows'], + category: 'popular', + tags: ['research'], + }, + { + icon: YouComIcon, + title: 'You.com research brief', + prompt: + 'Build a workflow that takes a topic, runs You.com Research at deep effort, and saves the cited Markdown report as a file for the team.', + modules: ['agent', 'files', 'workflows'], + category: 'productivity', + tags: ['research', 'reporting'], + }, + { + icon: YouComIcon, + title: 'You.com earnings analyst', + prompt: + 'Create a scheduled workflow that runs You.com Finance Research on each ticker in a watchlist table after earnings season, summarizes revenue drivers with citations, and writes the summary back to the table.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'research'], + }, + { + icon: YouComIcon, + title: 'You.com news monitor', + prompt: + 'Build a scheduled daily workflow that runs You.com Search with day freshness for my competitors, extracts highlights from each news result, and posts a short digest to Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'monitoring'], + alsoIntegrations: ['slack'], + }, + { + icon: YouComIcon, + title: 'You.com knowledge loader', + prompt: + 'Create a workflow that takes a list of documentation URLs, fetches each page as Markdown with You.com Get Contents, and adds the pages to a knowledge base so agents can answer from them.', + modules: ['knowledge-base', 'workflows'], + category: 'engineering', + tags: ['sync', 'research'], + }, + { + icon: YouComIcon, + title: 'You.com structured lead research', + prompt: + 'Build a workflow that reads company names from a table, runs You.com Research with an output schema for headquarters, headcount, and recent funding, and writes each structured result to its row.', + modules: ['tables', 'agent', 'workflows'], + category: 'sales', + tags: ['sales', 'enrichment'], + }, + { + icon: YouComIcon, + title: 'You.com long-running research', + prompt: + 'Create a workflow that starts You.com Research in the background at frontier effort, then a scheduled workflow that checks the task with Get Research Task and emails the report when it completes.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'productivity', + tags: ['research', 'reporting'], + alsoIntegrations: ['gmail'], + }, + { + icon: YouComIcon, + title: 'You.com credit alert', + prompt: + 'Build a scheduled workflow that checks the You.com account balance every morning and sends a Slack alert when remaining credits fall below a set threshold.', + modules: ['scheduled', 'workflows'], + category: 'operations', + tags: ['monitoring'], + alsoIntegrations: ['slack'], + }, + ], + skills: [ + { + name: 'ground-answer-with-web-search', + description: + 'Search the live web with You.com and ground a response in query-relevant highlights.', + content: + '# Ground an Answer with Web Search\n\nUse You.com Search to collect current, citable evidence before answering.\n\n## Steps\n1. Use the Search operation with a focused query. Search operators such as site: and filetype: are supported.\n2. Set Result Content to Highlights to get only the passages that address the query, which keeps context small.\n3. Use freshness (day, week, month, year, or a date range) for time-sensitive questions, and include or exclude domains to control sources.\n4. Read the web and news results; cite each claim with its URL.\n\n## Output\nReturn the answer with inline source URLs. Say when results are thin or conflicting.', + }, + { + name: 'answer-question-with-citations', + description: + 'Use You.com Answer to get a synthesized answer with verbatim supporting excerpts.', + content: + '# Answer a Question with Citations\n\nGet a direct answer to a factual question, backed by sources.\n\n## Steps\n1. Use the Answer operation and pass the question in natural language.\n2. Add freshness for recent events and include domains when only specific sources are trusted.\n3. Check the citations array: each entry has a source URL and the excerpts that support the answer.\n\n## Output\nReturn the answer and a numbered list of cited sources. Flag any claim without a supporting excerpt.', + }, + { + name: 'run-deep-research-report', + description: + 'Use You.com Research to investigate a complex question and produce a cited report.', + content: + '# Run a Deep Research Report\n\nAnswer a question that needs many searches and cross-referencing.\n\n## Steps\n1. Use the Research operation and write the input as a full instruction: what to find and how to report it.\n2. Pick an effort: lite for quick answers, standard for most questions, deep or exhaustive when accuracy matters more than speed.\n3. For structured fields, supply an output schema (every property required, additionalProperties false); content is then an object.\n4. For frontier effort or very long runs, enable Run in Background and fetch the result later with Get Research Task.\n\n## Output\nReturn the Markdown report (or structured object) and its sources. Mention any warnings returned.', + }, + { + name: 'research-company-financials', + description: + 'Use You.com Finance Research for earnings analysis and competitive benchmarking with citations.', + content: + '# Research Company Financials\n\nAnswer financial questions from earnings reports, SEC filings, analyst coverage, and market news.\n\n## Steps\n1. Use the Finance Research operation. Name the companies and tickers, the metrics, and the periods explicitly.\n2. Use deep effort for most questions and exhaustive for multi-company comparisons.\n3. Check that every figure in the answer has a citation in sources.\n\n## Output\nReturn the cited answer with a short table of key figures where useful, plus the source list.', + }, + { + name: 'extract-web-page-content', + description: 'Use You.com Get Contents to pull clean Markdown or HTML from a set of URLs.', + content: + '# Extract Web Page Content\n\nRead full pages for summarizing, indexing, or analysis.\n\n## Steps\n1. Use the Get Contents operation with the target URLs, comma-separated.\n2. Select Markdown for text an agent will read, HTML when layout matters, and Metadata for site name and favicon.\n3. Set a max cache age (in seconds) when the page must be re-fetched rather than served from cache.\n\n## Output\nReturn each page URL and title with its content. Flag any URL that returned no content.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index 18d3914c14b..a8ff079286f 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -270,6 +270,7 @@ import { PlanetScaleBlock, PlanetScaleBlockMeta } from '@/blocks/blocks/planetsc import { PolymarketBlock, PolymarketBlockMeta } from '@/blocks/blocks/polymarket' import { PostgreSQLBlock, PostgreSQLBlockMeta } from '@/blocks/blocks/postgresql' import { PostHogBlock, PostHogBlockMeta } from '@/blocks/blocks/posthog' +import { PowerBIBlock, PowerBIBlockMeta } from '@/blocks/blocks/powerbi' import { ProfoundBlock, ProfoundBlockMeta } from '@/blocks/blocks/profound' import { ProspeoBlock, ProspeoBlockMeta } from '@/blocks/blocks/prospeo' import { PulseBlock, PulseBlockMeta, PulseV2Block } from '@/blocks/blocks/pulse' @@ -374,6 +375,7 @@ import { WorkdayBlock, WorkdayBlockMeta } from '@/blocks/blocks/workday' import { WorkflowBlock } from '@/blocks/blocks/workflow' import { WorkflowInputBlock } from '@/blocks/blocks/workflow_input' import { XBlock, XBlockMeta } from '@/blocks/blocks/x' +import { YouComBlock, YouComBlockMeta } from '@/blocks/blocks/youcom' import { YouTubeBlock, YouTubeBlockMeta } from '@/blocks/blocks/youtube' import { ZendeskBlock, ZendeskBlockMeta } from '@/blocks/blocks/zendesk' import { ZepBlock, ZepBlockMeta } from '@/blocks/blocks/zep' @@ -624,6 +626,7 @@ export const BLOCK_REGISTRY: Record = { polymarket: PolymarketBlock, postgresql: PostgreSQLBlock, posthog: PostHogBlock, + powerbi: PowerBIBlock, profound: ProfoundBlock, prospeo: ProspeoBlock, pulse: PulseBlock, @@ -738,6 +741,7 @@ export const BLOCK_REGISTRY: Record = { workflow: WorkflowBlock, workflow_input: WorkflowInputBlock, x: XBlock, + youcom: YouComBlock, youtube: YouTubeBlock, zendesk: ZendeskBlock, zep: ZepBlock, @@ -956,6 +960,7 @@ export const BLOCK_META_REGISTRY: Record = { polymarket: PolymarketBlockMeta, postgresql: PostgreSQLBlockMeta, posthog: PostHogBlockMeta, + powerbi: PowerBIBlockMeta, profound: ProfoundBlockMeta, prospeo: ProspeoBlockMeta, pulse: PulseBlockMeta, @@ -1039,6 +1044,7 @@ export const BLOCK_META_REGISTRY: Record = { wordpress: WordPressBlockMeta, workday: WorkdayBlockMeta, x: XBlockMeta, + youcom: YouComBlockMeta, youtube: YouTubeBlockMeta, zendesk: ZendeskBlockMeta, zep: ZepBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index ff3d66b7c84..c186ddf787a 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5217,6 +5217,61 @@ export function MicrosoftSharepointIcon(props: SVGProps) { ) } +export function PowerBIIcon(props: SVGProps) { + const id = useId() + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + export function MicrosoftPlannerIcon(props: SVGProps) { return ( @@ -10040,3 +10095,32 @@ export function PlanetScaleIcon(props: SVGProps) { ) } + +export function YouComIcon(props: SVGProps) { + const id = useId() + const gradientId = `youcom_gradient_${id}` + + return ( + + + + + + + + + + ) +} diff --git a/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx b/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx index 02207a8be24..c29ba3227ab 100644 --- a/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx +++ b/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx @@ -3,10 +3,10 @@ slug: ai-agent-examples-by-department-and-industry title: 'AI Agent Examples by Department and Industry: A Practical Roundup' description: 'A practical roundup of AI agent examples across sales, support, operations, engineering, marketing, HR, and five industries, with build patterns for Sim.' date: 2026-09-17 -updated: 2026-09-17 +updated: 2026-10-02 authors: - andrew -readingTime: 16 +readingTime: 22 tags: [AI Agents, Workflow Automation, Industry Use Cases, Sim] ogImage: /library/ai-agent-examples-by-department-and-industry/cover.jpg draft: false @@ -18,14 +18,54 @@ faq: - q: "Do these AI agent examples require coding?" a: "Many examples can use visual blocks, integrations, and prompts without custom code. Sim supports visual, conversational, and code-based building, while custom APIs or unusual business rules may require a function block or developer support." - q: "How do you enforce human oversight?" - a: "Approval steps pause an agent before refunds, candidate decisions, account changes, or other consequential actions. Guardrails can restrict which tools an agent may call and enforce approved spending limits. Run logs record the actions taken for later review." + a: "A Human in the Loop block pauses a run for submitted form fields before refunds, candidate decisions, account changes, or other consequential actions. A downstream Condition checks the approval field. Guardrails reports whether checks passed or failed, and a downstream Condition routes on that result. Run logs record the actions taken for later review." + - q: "What are examples of AI agents in business?" + a: "AI agents in business include lead-research agents, support-ticket triage agents, invoice-exception agents, campaign-production agents, vendor-onboarding agents, and incident-investigation agents." + - q: "What are examples of AI agents in sales?" + a: "Sales AI agents can research accounts, enrich leads, score qualification evidence, summarize calls, identify missing next steps, draft follow-ups, and update CRM records with human review before external commitments." + - q: "What are examples of AI agents in customer service?" + a: "Customer-service AI agents can classify tickets, retrieve account context, search approved documentation, draft grounded responses, route urgent cases, and escalate requests that require judgment." + - q: "What are examples of AI agents in operations?" + a: "Operations AI agents can process document intake, investigate invoice mismatches, coordinate vendor onboarding, monitor service-level exceptions, and prepare cases for an authorized reviewer." + - q: "What are examples of AI agents in marketing?" + a: "Marketing AI agents can research defined audiences, create campaign drafts, repurpose approved content, check required claims, coordinate reviews, and summarize performance data." + - q: "What are examples of AI agents for technical teams?" + a: "Technical AI agents can investigate incidents, correlate logs with deployments, triage engineering requests, search runbooks, draft issue details, and prepare remediation options for an engineer." + - q: "What is a real-world example of an AI agent?" + a: "A real-world AI agent can receive a support ticket, identify the customer and issue, retrieve relevant documentation, draft a grounded response, and route uncertain or sensitive cases to a support specialist." + - q: "What is the difference between an AI agent and an automation?" + a: "An AI agent uses model-based interpretation or decision-making within a bounded process, while conventional automation primarily executes predefined rules and branches." + - q: "What is the difference between an AI agent and a chatbot?" + a: "An AI agent can choose and execute actions across connected systems, while a chatbot primarily generates responses within a conversation." + - q: "Do AI agents replace employees?" + a: "AI agents are best used to handle bounded tasks and prepare decisions while employees retain accountability for judgment, exceptions, relationships, and consequential actions." + - q: "When should an AI agent require human approval?" + a: "An AI agent should require human approval before consequential, difficult-to-reverse, legally sensitive, security-sensitive, or low-confidence actions." + - q: "How do you choose the first AI agent use case?" + a: "A team should choose a first AI agent use case with frequent work, clear inputs, a measurable outcome, reversible actions, accessible data, and an accountable process owner." + - q: "How do you measure AI agent performance?" + a: "AI agent performance should be measured with task completion, accuracy, correction rate, escalation rate, latency, cost, human review time, reliability, safety events, and the workflow's business outcome." + - q: "Can an AI agent use multiple business applications?" + a: "An AI agent can use multiple business applications when it has authenticated tools, narrowly scoped permissions, reliable identifiers, and rules for handling failed or conflicting actions." + - q: "Can I build these AI agent examples without code?" + a: "Sim provides a visual workflow builder for assembling agent logic and connected actions, although production deployments still require careful configuration, testing, permissions, and monitoring." + - q: "Can I build these AI agent examples with n8n?" + a: "n8n can build many integration-heavy examples, while Sim is designed as an open-source AI workspace for teams building, deploying, and managing AI agents." + - q: "Is Sim open source?" + a: "Sim's core is Apache 2.0 open source, while code in apps/sim/ee is covered by the separate Sim Enterprise License and requires an Enterprise subscription for production use: https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE" + - q: "Can Sim use local AI models?" + a: "Self-hosted Sim can use local or privately hosted models through Ollama, vLLM, LM Studio, or LiteLLM without requiring Sim Enterprise." + - q: "Does an AI agent need a human in the loop?" + a: "An AI agent needs a human in the loop whenever business risk, uncertainty, policy, or regulation requires a person to review or authorize the next action." + - q: "How do you prevent an AI agent from taking the wrong action?" + a: "AI agent risk is reduced through narrow permissions, deterministic policy checks, human approval, grounded context, input validation, complete run records, adversarial testing, and safe failure behavior." --- ## TL;DR - A support agent can start when Zendesk receives a ticket, search the knowledge base, check Salesforce and Jira, then set the priority, route the ticket, and send a status reply. [Each tool call advances the task](https://composio.dev/content/ai-agents-customer-support-use-cases). - AI agents respond to triggers, gather context, choose actions, and use connected tools. Human approval can protect consequential actions such as refunds or candidate decisions. -- The roundup groups examples by six departments and five industries. Each example includes a version you can build in [Sim](https://www.sim.ai/) with Agent blocks, connected workflows, and review steps. +- The roundup groups examples by six departments and eight industry patterns, with detailed sections for five industries. Each example includes a version you can build in [Sim](https://www.sim.ai/) with Agent blocks, connected workflows, and review steps. ## What an AI agent example actually looks like @@ -33,6 +73,8 @@ An AI agent receives a trigger, evaluates available context, chooses a next step A chatbot usually retrieves information and generates a response. Retrieval-augmented generation, or RAG, lets the chatbot search a knowledge base before answering. An agent can continue when the knowledge base lacks a complete answer. It can call external systems, compare their results, and execute a multi-step task such as issuing a refund after approval. [Customer support agents commonly use tool calling](https://composio.dev/content/ai-agents-customer-support-use-cases) to perform these actions across ticketing, billing, and engineering systems. For a closer comparison, see [AI agent vs. chatbot](https://www.sim.ai/library/ai-agent-vs-chatbot). +The interface does not define the category. A chatbot might explain a return policy, while an agent identifies the customer, retrieves the order, checks the policy, prepares the permitted action, and routes an exception to a person. A deterministic automation follows predefined branches; an agent adds model-based interpretation where inputs vary. Production systems often combine all three: conversational intake, model-based interpretation, and deterministic rules that constrain actions. + Invoice processing provides another useful distinction. OCR software extracts vendor names, amounts, and line items from a document. An invoice agent compares those fields with purchase orders and receipts, then evaluates why records differ. For example, the agent might identify a partial delivery rather than merely flagging an amount mismatch. Invoice agents can route uncertain or exceptional cases to a reviewer with the relevant records attached. The examples in this roundup follow a recurring pattern. A trigger starts the job, the agent gathers context, and tool calls perform an action. A human-in-the-loop checkpoint controls sensitive or irreversible decisions. In [Sim](https://www.sim.ai), an Agent block handles reasoning, a knowledge base supplies trusted context, and workflows connect triggers, tools, actions, and review steps. The guide to [what an AI agent is](https://www.sim.ai/library/what-is-an-ai-agent-definition-how-it-works-and-examples) explains these components in more detail. @@ -41,6 +83,23 @@ The examples in this roundup follow a recurring pattern. A trigger starts the jo The following examples show how sales, support, operations, engineering and IT, marketing, and HR departments can use agents. Each section also explains how to build the pattern in [Sim](https://www.sim.ai) with Agent blocks, connected workflows, and human review steps. +| Department | Practical AI agent | Trigger | Actions | Typical integrations | Human review | Measurable outcome | +|---|---|---|---|---|---|---| +| Sales | Lead research and qualification agent | New lead or target account | Enriches the record, researches the company, scores fit, drafts outreach, and updates the CRM | CRM, enrichment service, web search, email | Representative approves outreach or reviews low-confidence matches | Research time, accepted leads, response rate, meetings booked | +| Sales | Pipeline follow-up agent | Opportunity stage changes or becomes inactive | Summarizes history, identifies missing next steps, drafts follow-up, and creates tasks | CRM, email, calendar, team chat | Account owner approves external messages | Opportunities without next steps, follow-up latency, stage conversion | +| Support | Ticket triage and routing agent | New support request | Classifies intent and urgency, retrieves account context, finds relevant documentation, and assigns a queue | Help desk, knowledge base, CRM, incident system | Specialist reviews sensitive or low-confidence cases | First-response time, routing accuracy, reassignment rate | +| Support | Resolution-drafting agent | Ticket is assigned | Retrieves approved sources, proposes troubleshooting steps, and drafts a cited reply | Help desk, documentation, status page, customer database | Support representative approves customer-facing answers or flagged drafts | Handle time, draft acceptance rate, reopen rate | +| Operations | Invoice exception agent | Invoice fails validation | Extracts fields, compares the invoice with purchase records, identifies discrepancies, and requests clarification | Email, document storage, ERP, procurement system | Finance or procurement approves payment changes | Exception cycle time, manual touches, duplicate payments avoided | +| Operations | Vendor onboarding agent | Vendor request is submitted | Collects documents, checks completeness, routes risk questions, and updates the vendor record | Forms, document storage, procurement, identity and risk tools | Procurement, legal, or security approves flagged cases | Onboarding time, incomplete submissions, review backlog | +| Marketing | Campaign production agent | Campaign brief is approved | Researches the audience, creates channel-specific drafts, checks required claims, and opens review tasks | Project management, CMS, CRM, analytics, ad platforms | Brand, legal, or campaign owner approves deployment | Production time, revision count, on-time launches | +| Marketing | Content repurposing agent | Webinar, interview, or report is completed | Extracts themes, creates derivative drafts, proposes distribution, and records assets | Video transcript, document storage, CMS, social tools | Editor approves facts, voice, and deployment | Assets per source, editing time, engagement by format | +| Technical | Incident investigation agent | Monitoring alert fires | Collects logs and recent changes, forms hypotheses, runs approved diagnostics, and drafts a status update | Observability, source control, incident management, team chat | On-call engineer approves remediation and external communication | Time to acknowledge, time to diagnose, time to resolve | +| Technical | Engineering intake agent | Bug report or feature request arrives | Reproduces context, searches related issues, classifies ownership, and drafts acceptance criteria | Issue tracker, source control, documentation, product analytics | Engineer or product owner approves prioritization | Triage time, duplicate rate, issues ready for development | +| HR | Resume intake agent | Application or resume is submitted | Extracts role, location, and experience, flags missing information, and creates a structured review record | Forms, email, document storage, applicant tracking system | Recruiter decides whether the candidate advances | Intake time, incomplete applications, extraction corrections | +| HR | Employee onboarding agent | Signed offer is received | Generates documents, opens IT and facilities tasks, tracks prerequisites, and notifies owners | HR system, e-signature, identity management, ticketing | HR reviews exceptions, failed checks, and role or location changes | Onboarding time, missing tasks, delayed starts | + +These patterns do not guarantee results. Establish a baseline, name an owner, and measure the selected outcome before expanding an agent's permissions. + ### Sales: outbound prospecting and inbound lead qualification agents Outbound prospecting agents turn a target account profile into a researched outreach sequence. The agent finds matching companies, enriches each contact with CRM and external data, and researches recent signals such as hiring activity or company news. It then drafts a message based on those signals and sends follow-ups through approved channels. [Outbound sales agents](https://www.11x.ai/guides/ai-sales-agents) can apply this process across channels such as email, LinkedIn, SMS, and voice. @@ -59,7 +118,7 @@ An agent-assist workflow gives support representatives similar tool access witho Place human approval immediately before an agent takes a consequential or difficult-to-reverse action. Examples include issuing a refund, cancelling an account, changing an entitlement, or sending a binding response. Classification and context gathering can run automatically when a reviewer can correct their outputs before execution. See [what human in the loop means for AI agents](https://www.sim.ai/library/what-is-human-in-the-loop-in-ai-agents) for more approval patterns. -In [Sim](https://www.sim.ai), an Agent block can classify the ticket and enrich it with CRM or bug-tracker data. Workflow branches can route routine questions to support and known incidents to engineering. A Human in the Loop block can pause refund execution and request approval through a connected channel or webhook. Sim’s run logs then record the blocks, actions, costs, and failures associated with each support request. +In [Sim](https://www.sim.ai), an Agent block can classify the ticket and enrich it with CRM or bug-tracker data. Workflow branches can route routine questions to support and known incidents to engineering. A Human in the Loop block can pause refund execution and resume the run with submitted form fields. A downstream Condition can check the approval field before allowing the refund action. Sim’s run logs then record the blocks, actions, costs, and failures associated with each support request. ### Operations: invoice processing and document triage agents @@ -77,7 +136,7 @@ A helpdesk triage agent resolves routine requests and sends complex cases to an Coding agents apply a similar model to repetitive engineering work. A coding agent can inspect a repository, generate replacement modules, and run tests, but an engineer should review proposed changes before merging or deployment. -You can build the helpdesk pattern with a [Sim workflow](https://www.sim.ai/) that sends each incoming ticket to an Agent block. The agent routes the request by type and calls approved integrations. A Guardrails or Evaluator block can check the proposed response and tool output before the workflow closes a ticket. Low-confidence answers, failed tool calls, and sensitive access requests should route to a human reviewer instead. +You can build the helpdesk pattern with a [Sim workflow](https://www.sim.ai/) that sends each incoming ticket to an Agent block. The agent routes the request by type and calls approved integrations. A Guardrails or Evaluator block can check the proposed response and tool output before the workflow closes a ticket. Because Guardrails reports whether checks passed or failed rather than stopping the run, a downstream Condition must route on that result. Low-confidence answers, failed tool calls, and sensitive access requests should route to a human reviewer instead. ### Marketing: content and research agents @@ -101,7 +160,20 @@ In [Sim](https://www.sim.ai/), an Agent block can receive documents and extract ## AI agent examples by industry -The following examples apply agent patterns to ecommerce, healthcare, finance, SaaS, and real estate. Each section explains how to build the pattern in [Sim](https://www.sim.ai) with Agent blocks, connected tools, workflows, and human review steps. +The table covers eight industry patterns. The detailed sections that follow focus on ecommerce, healthcare, finance, SaaS, and real estate, with additional table examples for software, professional services, and procurement. Each detailed section explains how to build the pattern in [Sim](https://www.sim.ai) with Agent blocks, connected tools, workflows, and human review steps. + +| Industry | Example | Trigger | Agent actions | Typical integrations | Required human control | Outcome to measure | +|---|---|---|---|---|---|---| +| Ecommerce | Return-request preparation | New return request | Retrieves order details, checks policy, identifies exceptions, and prepares the permitted action | Storefront, order management, help desk, payments | Specialist reviews fraud signals, refunds, and policy exceptions | Resolution time and exception accuracy | +| Healthcare | Non-clinical intake routing | Intake form is submitted | Checks form completeness and routes administrative requests | Forms, scheduling, approved records system | Authorized staff handles clinical or sensitive decisions | Routing accuracy and administrative wait time | +| Finance | Invoice exception handling | Invoice fails validation | Extracts data, compares records, and explains discrepancies | Document storage, ERP, procurement system | Authorized employee approves payment or account changes | Exception cycle time and rework | +| SaaS | Onboarding and usage-signal agent | Account starts or reaches a checkpoint | Reviews setup and usage, recommends a next step, and routes accounts needing attention | Product analytics, CRM, email, in-app messaging | Account owner approves consequential outreach or commitments | Setup completion, response time, and qualified handoffs | +| Real estate | Maintenance triage | Maintenance request arrives | Collects property and issue details, categorizes urgency, and prepares dispatch | Property system, ticketing, vendor system, messaging | Property manager reviews emergencies and unclear requests | Triage time and correct dispatch rate | +| Software | Incident investigation | Monitoring alert fires | Correlates alerts, logs, deployments, and runbooks | Observability, source control, incident management | Engineer approves remediation and communication | Diagnosis and resolution time | +| Professional services | Client-intake preparation | Client submission arrives | Summarizes submissions, checks completeness, and creates an initial work plan | Forms, document storage, CRM, project management | Qualified professional approves scope and advice | Intake cycle time and missing-information rate | +| Procurement | Supplier review coordination | Supplier request is submitted | Collects documents, summarizes responses, and routes risks | Procurement, document storage, identity and risk tools | Procurement, legal, security, or finance accepts risk | Onboarding time and review backlog | + +Industry labels do not remove the need for workflow-level risk analysis. Identify regulated data, access boundaries, retention requirements, required approvals, and prohibited actions before deploying an agent. ### Ecommerce: shopping support and merchandising agents @@ -111,7 +183,7 @@ Klarna shows both the value and the limit of this approach. [Klarna reported](ht Merchandising agents can adjust product placement or pricing within limits set by a person. A personalization agent can select products or homepage modules based on browsing behavior and customer segment. A pricing agent can respond to inventory levels and competitor changes, but human-set limits should prevent excessive discounts or sudden price jumps. -You can build the support pattern in [Sim](https://www.sim.ai/) with an Agent block grounded in a Knowledge Base of return policies and product information. The workflow can call the order system for live shipment or purchase data. Guardrails can restrict refunds and discounts to approved limits, while a review step sends exceptions to a support agent before any irreversible action. +You can build the support pattern in [Sim](https://www.sim.ai/) with an Agent block grounded in a Knowledge Base of return policies and product information. The workflow can call the order system for live shipment or purchase data. Guardrails can report whether a proposed refund or discount passes approved checks, while a downstream Condition routes failures to a support agent before any irreversible action. ### Healthcare: prior authorization and intake agents @@ -149,6 +221,30 @@ A maintenance triage agent follows a similarly short path. The agent collects th You can build either pattern in [Sim](https://www.sim.ai/) with an Agent block handling the conversation and a workflow action completing the handoff. The lead workflow can book a calendar slot or update a CRM. The maintenance workflow can send a ticket to a vendor system. Both workflows limit the agent to a narrow decision followed by a defined CRM, calendar, or vendor-system action. +## How to design and implement an AI agent workflow + +Begin with one bounded business outcome, one accountable owner, and an explicit definition of what the agent may not do. The guide to [building an AI agent with Sim](https://www.sim.ai/library/how-to-create-an-ai-agent) shows how to turn that boundary into a working workflow. + +1. Define the trigger and the record that starts the run. +2. List the required context and the authoritative source for each field. +3. Separate model-based classification, extraction, and summarization from deterministic business rules. +4. Grant only the permissions required for approved actions. +5. Add human review before consequential or uncertain steps. +6. Record inputs, model outputs, tool calls, decisions, approvals, errors, and final outcomes. +7. Test normal cases, missing and conflicting data, prompt injection, tool failure, and duplicate events. +8. Establish baseline metrics before deployment and compare results over time. +9. Expand permissions only after the agent performs reliably within its original boundary. + +Human review belongs before actions that are consequential, difficult to reverse, legally or security sensitive, or based on uncertain evidence. Examples include sending external communications, changing contracts or prices, issuing refunds, moving money, modifying production systems, accepting risk, changing access, and acting on sensitive personal data. Confidence can help route work, but it should not replace a risk-based approval policy. + +In Sim, the Human in the Loop block pauses a run and resumes with submitted form fields. Approval or rejection is represented as a field, so a downstream Condition must inspect it before the workflow continues. The Guardrails block reports whether checks passed or failed; a downstream Condition must route execution based on that result. + +## How to measure whether an AI agent works + +Measure business outcomes, decision quality, safety, reliability, cost, and human effort rather than output volume alone. Start with a baseline for the existing process, then track task completion, correction and escalation rates, latency, cost per completed case, review time, tool failures, policy violations, and the business metric named in the workflow's objective. + +Review results by case type because a strong average can hide failures on rare but consequential requests. Preserve enough run data to explain the context used, tools called, approvals received, errors encountered, and final outcome. That evidence supports both debugging individual runs and evaluating whether the agent improves over time. + ## Choosing a pattern versus building it yourself Evaluate a workflow builder when you can map the job as a mostly predictable sequence. Compare [n8n](https://docs.n8n.io/build/flow-logic) and Make for processes built around branches and error handling. Make documents both [branching with routers](https://help.make.com/router) and [scenario error handling](https://help.make.com/Overview-of-error-handling). Consider [Gumloop](https://docs.gumloop.com/nodes/web_scraping/website_scraper) for browser automation and data-heavy flows. In each case, confirm current product capabilities and deployment requirements before choosing a tool. @@ -157,6 +253,10 @@ Evaluate a packaged assistant when the job resembles personal or executive assis An open agent workspace fits a use case that requires custom instructions, company knowledge, model choice, or access to several tools. [Sim](https://www.sim.ai/) provides Agent blocks for individual reasoning tasks and workflows for connecting those tasks to integrations, code, data, and approval steps. Sim’s Chat can coordinate specialized agents when a workflow needs to delegate work or combine agent outputs. Read [AI agent orchestration frameworks explained](https://www.sim.ai/library/ai-agent-orchestration-frameworks-explained) for the underlying coordination patterns. +As of October 2026, [Sim's core uses the Apache 2.0 license](https://github.com/simstudioai/sim/blob/main/LICENSE), while [apps/sim/ee uses the separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Sim Enterprise subscription for production use. Self-hosted Sim deployments can connect local models through Ollama, vLLM, LM Studio, or LiteLLM without requiring Enterprise. + +For integration-heavy conventional automation, teams may also evaluate n8n, Zapier, and Make. As of October 2026, [n8n uses its Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), a source-available fair-code license that is not OSI-approved. Zapier and Make are common candidates when a cloud workflow is primarily deterministic; compare required actions, approval controls, deployment needs, and governance rather than choosing by connector count alone. + The use case should determine the category. A fixed invoice-routing sequence may need a workflow builder, while a research agent that delegates analysis and drafting may benefit from multi-agent coordination. Coding requirements also vary. Visual builders reduce setup work, while code and self-hosting options give you more control over custom behavior and deployment. ## Conclusion diff --git a/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx b/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx new file mode 100644 index 00000000000..bf62d64a28d --- /dev/null +++ b/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx @@ -0,0 +1,401 @@ +--- +slug: postgres-mcp-server-ai-agents +title: 'How to Use a Postgres MCP Server With AI Agents: Security, Deployment, and Evaluation' +description: 'Learn how to connect a Postgres MCP server to AI agents with restricted roles, bounded queries, private deployment, audit logs, and production evaluations.' +date: 2026-10-02 +updated: 2026-10-02 +authors: + - andrew +readingTime: 15 +tags: [MCP, PostgreSQL, AI Agents, Security, Sim] +ogImage: /library/postgres-mcp-server-ai-agents/cover.jpg +draft: false +faq: + - q: "Can an AI agent query PostgreSQL through MCP?" + a: "A Postgres MCP server lets an AI agent query PostgreSQL through structured tools exposed over the Model Context Protocol." + - q: "Is a Postgres MCP server safe for production?" + a: "A Postgres MCP server can be safe enough for a defined production use case when PostgreSQL privileges, tool allow lists, private networking, input validation, limits, and audit logs independently constrain it." + - q: "Should a Postgres MCP server use a read-only database user?" + a: "A Postgres MCP server should use a dedicated read-only PostgreSQL role for every workflow that does not explicitly require writes." + - q: "Should an AI agent be allowed to generate arbitrary SQL?" + a: "An AI agent should not be allowed to execute arbitrary SQL against production PostgreSQL when approved views, query templates, or stored procedures can satisfy the use case." + - q: "Can a read-only Postgres MCP server still be risky?" + a: "A read-only Postgres MCP server can still expose sensitive data, run expensive queries, hold locks, or cross tenant boundaries if its views, privileges, limits, and filters are weak." + - q: "How do I stop a Postgres MCP server from returning too much data?" + a: "A Postgres MCP server should enforce maximum rows, selected columns, response bytes, execution time, and pagination regardless of the limit requested by the agent." + - q: "How should a Postgres MCP server handle multi-tenant data?" + a: "A Postgres MCP server should derive tenant scope from trusted authenticated identity and reinforce it with PostgreSQL authorization or row-level security rather than accepting a model-supplied tenant identifier as authority." + - q: "Should a Postgres MCP server connect to a read replica?" + a: "A Postgres MCP server should use a read replica for analytical read workloads when replication delay is acceptable and the replica reduces risk to the primary database." + - q: "Where should Postgres MCP credentials be stored?" + a: "A Postgres MCP server should store credentials in the deployment's secret-management system and never place them in prompts, source code, tool descriptions, or tool results." + - q: "What should be logged for each Postgres MCP query?" + a: "A Postgres MCP server should log the authenticated actor, agent run, tool, policy decision, database role, duration, row count, outcome, and redacted parameters for each query." + - q: "How should Postgres MCP writes be approved?" + a: "A Postgres MCP server should route consequential writes through an explicit approval decision before invoking a narrow, idempotent write procedure." + - q: "How do I evaluate a Postgres MCP server?" + a: "A Postgres MCP server should be evaluated with authorization, injection, timeout, tenant-isolation, retry, secret-leakage, logging, revocation, and failure-recovery tests." + - q: "Does Sim support Postgres MCP workflows?" + a: "Sim supports MCP-connected agent workflows and can be evaluated for orchestrating restricted Postgres MCP tools with multi-step logic, tracing, and approval paths." + - q: "Is Sim open source?" + a: "Sim's core is Apache 2.0 open source, while apps/sim/ee is governed by the separate Sim Enterprise License and requires an active Enterprise subscription for production use. See https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE" + - q: "Is n8n open source?" + a: "n8n uses the Sustainable Use License, which is source-available but is not an OSI-approved open-source license." + - q: "Is Sim or n8n better for Postgres MCP?" + a: "Sim is the stronger option to evaluate when the priority is an open-source AI workspace with an Apache 2.0 core, while n8n is an incumbent worth evaluating when the priority is extending an existing n8n automation environment." + - q: "What is the best AI agent builder for Postgres MCP?" + a: "Sim is a strong AI agent platform to evaluate for Postgres MCP, while the broader Best AI Agent Platforms and Builders in 2026 guide compares the full head-term market." + - q: "What is the difference between a Postgres connector and a Postgres MCP server?" + a: "A Postgres connector gives one platform a predefined database integration, while a Postgres MCP server exposes structured tools through a protocol that compatible MCP clients can consume." + - q: "Can one Postgres MCP server be shared by multiple AI agents?" + a: "A Postgres MCP server can serve multiple AI agents only when it authenticates each client, authorizes tools per identity, isolates tenants, limits workloads, and preserves distinct audit trails." + - q: "Do I need a separate Postgres MCP server for writes?" + a: "A separate Postgres MCP server is the safest default for writes because it allows independent credentials, tools, approvals, network policy, scaling, and audit controls." +--- + +## TL;DR + +A Postgres MCP server gives AI agents a controlled interface to PostgreSQL, but production safety depends on narrow database roles, explicit tools, bounded queries, private deployment, and complete audit logs. + +The safest architecture does not let a model improvise arbitrary SQL against a production database. It exposes approved read tools, views, or stored procedures through a Model Context Protocol server, then treats every model-generated argument as untrusted input. + +## What is a Postgres MCP server? + +A Postgres MCP server is a Model Context Protocol server that exposes selected PostgreSQL operations as tools an AI agent can discover and invoke. + +The server sits between an MCP-compatible agent platform and PostgreSQL: + +1. The AI agent decides that it needs database information or an approved database action. +2. The MCP client sends a structured tool request to the Postgres MCP server. +3. The server validates the tool name and arguments. +4. The server executes an allow-listed query, view, or stored procedure using a restricted PostgreSQL role. +5. The server returns a bounded result to the agent. +6. The application records the tool call, execution metadata, and outcome. + +This separation matters because the model should not receive a database password or direct network access. The MCP server owns database connectivity and policy enforcement, while PostgreSQL remains the final authorization boundary. + +For a protocol-level introduction, read [What Is an MCP Server?](https://www.sim.ai/library/what-is-an-mcp-server). For broader threat modeling, read [MCP Security: A Practical Guide to Secure MCP Server Development](https://www.sim.ai/library/mcp-security). + +## How do you connect an AI agent to a Postgres MCP server? + +An AI agent connects to a Postgres MCP server by registering the server with an MCP-compatible client, authenticating the connection, and exposing only the database tools the agent needs. + +A production-oriented setup follows this sequence: + +1. **Define the use case.** Decide which questions or actions the agent must support before granting database access. +2. **Create the database interface.** Prefer approved views and stored procedures over unrestricted table access. +3. **Create a dedicated PostgreSQL role.** Give each environment and workload its own identity. +4. **Deploy the MCP server.** Place it near the database on a private network when possible. +5. **Store credentials outside prompts and tool definitions.** Load database and MCP credentials from the deployment's secret-management system. +6. **Register the server with the agent platform.** Use a transport and authentication method supported by both the MCP server and client. +7. **Limit the available tools.** Do not expose administrative or write tools to an agent that only needs reporting data. +8. **Test expected and adversarial requests.** Confirm authorization, timeouts, result limits, logging, and failure behavior. +9. **Deploy gradually.** Start with read-only access to non-production or replicated data before considering controlled writes. + +The official [MCP architecture documentation](https://modelcontextprotocol.org/docs/learn/architecture) explains the relationship among hosts, clients, and servers. PostgreSQL's [client authentication documentation](https://www.postgresql.org/docs/current/client-authentication.html) explains how database connections are authenticated. + +## How should a Postgres MCP server be architected for production? + +A production Postgres MCP server should be a narrow policy-enforcement service rather than a transparent SQL proxy. + +A defensible architecture has five layers: + +| Layer | Responsibility | Recommended boundary | +|---|---|---| +| Agent platform | Plans the task and invokes tools | The model never receives database credentials | +| MCP client | Connects the workflow to approved servers | Only trusted server definitions are available | +| Postgres MCP server | Validates tools, arguments, identity, and limits | Unknown tools and unexpected parameters fail closed | +| PostgreSQL authorization | Enforces roles, object privileges, and transaction rules | The MCP role has no ownership or administrative privileges | +| Observability system | Records requests, timing, rows, failures, and approvals | Sensitive values are redacted while security metadata is retained | + +PostgreSQL must remain an independent security boundary. An application-side allow list is valuable, but it should not compensate for a database role that can read or modify everything. + +For higher-risk systems, separate read and write execution paths. A read server can use a read replica and a role limited to reporting views, while a write server can expose a few reviewed procedures behind approval and policy checks. + +## Should a Postgres MCP server allow arbitrary SQL? + +A Postgres MCP server should not allow arbitrary model-generated SQL against production data unless the environment is isolated, disposable, and explicitly designed for that risk. + +Arbitrary SQL creates several failure modes: + +- The model can select sensitive columns that were irrelevant to the user's request. +- A seemingly harmless query can consume excessive CPU, memory, locks, or I/O. +- Prompt injection in retrieved content can influence subsequent tool calls. +- Dynamic identifiers can bypass assumptions made by parameterized value handling. +- Functions and stored procedures can have side effects even when the original task appears read-only. +- Large result sets can leak data into model context, logs, or downstream tools. + +The safer pattern is to expose intent-specific tools such as: + +- `get_customer_order_summary(customer_id, start_date, end_date)` +- `list_overdue_invoices(account_id, limit)` +- `search_support_cases(query, team_id, limit)` +- `create_refund_request(order_id, reason)` + +Each tool should have a strict input schema, authorization check, row limit, timeout, and documented output. Parameterize values and allow-list any selectable identifiers, sort fields, or operators; SQL parameters do not turn an untrusted table or column name into a safe identifier. + +## How do you make Postgres MCP queries read-only? + +A Postgres MCP server makes queries read-only by combining restricted PostgreSQL privileges, approved views, read-only transactions, and an MCP tool allow list. + +Do not rely on a prompt that says “only run SELECT.” Prompts influence model behavior, but PostgreSQL privileges enforce what the database identity can actually do. + +A minimal pattern is shown below. Use the schema-wide grant only when `agent_api` is a dedicated, views-only schema; otherwise, grant `SELECT` on each reviewed view explicitly. + +```sql +CREATE ROLE agent_reader LOGIN PASSWORD 'managed-outside-source-control'; + +GRANT CONNECT ON DATABASE app_db TO agent_reader; +GRANT USAGE ON SCHEMA agent_api TO agent_reader; +GRANT SELECT ON ALL TABLES IN SCHEMA agent_api TO agent_reader; + +ALTER ROLE agent_reader SET default_transaction_read_only = on; +ALTER ROLE agent_reader SET statement_timeout = '5s'; +ALTER ROLE agent_reader SET lock_timeout = '1s'; +ALTER ROLE agent_reader SET idle_in_transaction_session_timeout = '10s'; +``` + +The `agent_api` schema should contain only reviewed views intended for agent consumption. The role should not own tables, schemas, functions, or the database, and it should not have superuser, role-management, replication, or bypass-row-security privileges. + +Read-only transactions are defense in depth, not the entire policy. Review callable functions, sequence access, temporary-object behavior, row-level security, and every privilege inherited through role membership. PostgreSQL documents object grants in [GRANT](https://www.postgresql.org/docs/current/sql-grant.html) and transaction behavior in [SET TRANSACTION](https://www.postgresql.org/docs/current/sql-set-transaction.html). + +## When should a Postgres MCP server have write access? + +A Postgres MCP server should receive write access only when a specific business action requires it and that action can be constrained, validated, audited, and reversed. + +Do not convert a read role into a broad read-write role. Create a separate database identity and expose narrow operations such as `create_draft_ticket`, `record_review_decision`, or `request_account_update`. + +A safe write path should include: + +1. A dedicated writer role that cannot modify unrelated tables. +2. An intent-specific stored procedure or service method rather than raw SQL. +3. Server-side validation of identifiers, state transitions, and business rules. +4. Idempotency keys for operations that could be retried. +5. A human approval step for high-impact or irreversible actions. +6. An audit record linking the user, agent run, tool call, arguments, and database transaction. +7. A tested rollback or compensating action. + +PostgreSQL functions that use elevated privileges require special care. PostgreSQL's [function security guidance](https://www.postgresql.org/docs/current/perm-functions.html) explains why untrusted users must not be able to alter objects or schemas involved in privileged execution. + +## How should Postgres MCP credentials be secured? + +A Postgres MCP server should keep database credentials in a secret manager, use a dedicated non-owner role, encrypt network connections, and rotate credentials without changing prompts or workflows. + +The following controls form a practical baseline: + +- Use different credentials for development, staging, and production. +- Use different roles for read and write servers. +- Never place passwords, connection strings, tokens, or private keys in prompts. +- Never return credentials in MCP tool results or error messages. +- Restrict network access by source, destination, and port. +- Require encrypted PostgreSQL connections and validate server identity. +- Prefer short-lived credentials or an identity-aware database proxy when the environment supports them. +- Rotate credentials and test revocation. +- Redact secrets and sensitive query values from logs. +- Prevent the database role from creating roles, databases, schemas, extensions, or untrusted functions. + +PostgreSQL documents encrypted client connections in [SSL support](https://www.postgresql.org/docs/current/ssl-tcp.html) and network authentication rules in [The pg_hba.conf File](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). + +## How should a Postgres MCP server handle schema discovery? + +A Postgres MCP server should expose a curated schema description rather than giving the agent unrestricted access to every catalog, table, and column. + +Schema discovery improves query quality, but complete discovery can disclose internal names, sensitive relationships, deprecated objects, and tenant boundaries. A curated catalog should include only: + +- Approved schemas, views, procedures, and tools. +- Human-readable descriptions of business meaning. +- Allowed filters, sort fields, and maximum limits. +- Column classifications such as public, internal, confidential, or restricted. +- Expected join paths and tenant-scoping rules. +- Freshness and ownership metadata where relevant. + +Treat schema descriptions as untrusted context if database comments or metadata can be edited by users. Instructions embedded in comments, rows, or retrieved text must never override the MCP server's authorization and validation rules. + +## How do you prevent unsafe or expensive Postgres MCP queries? + +A Postgres MCP server prevents unsafe queries by validating inputs, limiting query shapes, enforcing timeouts and row caps, and using a database role that cannot exceed the intended operation. + +Use controls at both the server and database layers: + +| Risk | MCP-server control | PostgreSQL control | +|---|---|---| +| Unbounded results | Require and cap `limit` | Apply a hard limit in the approved query or view | +| Slow scans | Restrict filters and query templates | Set `statement_timeout` and monitor plans | +| Lock contention | Keep transactions short | Set `lock_timeout` and idle transaction timeout | +| Unauthorized rows | Derive tenant scope from trusted identity | Use privileges and row-level security where appropriate | +| SQL injection | Parameterize values and reject unknown fields | Remove unnecessary privileges | +| Duplicate writes | Require an idempotency key | Enforce a unique constraint | +| Destructive action | Do not expose the tool by default | Deny DDL and broad DML privileges | +| Data exfiltration | Cap rows, columns, and response size | Grant access only to approved views | + +Do not ask the model to choose its own safety limits. The server should impose maximum values even when the tool request omits them or requests a larger value. + +## What should a Postgres MCP server log? + +A Postgres MCP server should log enough metadata to reconstruct every tool call without copying secrets or unnecessary sensitive data into the logging system. + +Useful fields include: + +- Timestamp and environment. +- Authenticated user, service, and tenant identity. +- Agent run, workflow, session, and trace identifiers. +- MCP server and tool name. +- Policy version and approval decision. +- Parameter names plus redacted or hashed sensitive values. +- Database role and target database. +- Query template or procedure identifier, not an uncontrolled secret-bearing string. +- Duration, rows returned or affected, timeout status, and error class. +- Idempotency key and transaction identifier for writes. +- Human approver identity when approval is required. + +Database logs and agent traces answer different questions. PostgreSQL records database activity, while agent observability shows why a tool was selected and what happened before and after it. [What Is AI Agent Observability? Traces, Metrics, and Evals Explained](https://www.sim.ai/library/ai-agent-observability) covers the broader tracing and evaluation layer. + +## Where should a Postgres MCP server be deployed? + +A Postgres MCP server should be deployed where it can reach PostgreSQL privately while exposing the smallest possible authenticated surface to the agent platform. + +Three common deployment choices are: + +| Deployment choice | Best fit | Main trade-off | +|---|---|---| +| Local process | Development and single-user testing | Simple, but tied to one machine and unsuitable for shared production access | +| Private sidecar or internal service | Production systems with private database networking | Strong network isolation, but requires deployment and operational ownership | +| Shared remote MCP service | Multiple approved clients and centralized policy | Easier reuse, but authentication, tenant isolation, scaling, and patching become critical | + +A local process is useful for testing against disposable data. A private service is usually the strongest production default because the database does not need to become internet-accessible. A shared remote service can work when it has explicit client authentication, authorization, rate limits, tenant isolation, and operational monitoring. + +## What are common AI agent workflows for a Postgres MCP server? + +A Postgres MCP server is most useful when an AI agent needs current structured data or a tightly controlled database action as one step in a larger workflow. + +Common patterns include: + +- **Customer support context:** Retrieve an account's recent orders and open cases before drafting a response. +- **Revenue operations:** Summarize pipeline movement from approved reporting views. +- **Operations monitoring:** Check delayed jobs, inventory exceptions, or failed transactions and route an alert. +- **Finance review:** Retrieve overdue invoices and prepare a review queue without authorizing payment changes. +- **Data-quality triage:** Find records that violate known rules and create remediation tasks. +- **Internal analytics:** Answer bounded questions over curated reporting views. +- **Controlled updates:** Submit a draft change or approval request through a narrow write tool. + +The database tool should usually be one component in a multi-step agent workflow, not the agent's only capability. [AI Agent Workflow Builders for Multi-Step Tasks: 6-Platform Comparison](https://www.sim.ai/library/ai-agent-workflow-builders-multi-step-tasks) explains how platforms coordinate tools, conditions, approvals, and downstream actions. + +## How do you evaluate a Postgres MCP server? + +A Postgres MCP server should be evaluated on authorization, query safety, deployment fit, observability, protocol compatibility, and operational reliability rather than on whether it can execute a demo query. + +Use these buyer criteria: + +| Criterion | Evidence to request | Pass condition | +|---|---|---| +| Tool restriction | Tool manifest and server policy | Clients can access only explicitly approved operations | +| Database authorization | Role grants and membership | The role cannot access objects outside its purpose | +| Read/write separation | Distinct server and role configuration | Read workloads cannot invoke write operations | +| Input validation | Schemas and negative tests | Unknown fields, operators, and identifiers fail closed | +| Resource controls | Timeout and result-limit tests | Expensive requests terminate within defined bounds | +| Authentication | Client and database authentication design | Anonymous production access is impossible | +| Tenant isolation | Cross-tenant adversarial tests | One tenant cannot infer or retrieve another tenant's data | +| Logging | Example trace and database audit record | A tool call can be reconstructed end to end | +| Secret handling | Configuration and redaction review | Credentials never enter prompts or tool outputs | +| Failure behavior | Network, timeout, and retry tests | Failures do not cause duplicate writes or silent partial success | +| Deployment | Network diagram and patching process | PostgreSQL remains private and the server can be updated promptly | +| Protocol compatibility | Test against the intended MCP client | Discovery, invocation, errors, and authentication work together | + +A strong evaluation uses realistic data volumes and adversarial cases, not only a successful `SELECT` statement. + +## How do you test a Postgres MCP server before production? + +A Postgres MCP server should pass functional, authorization, injection, load, failure, and audit tests before it can reach production data. + +Include at least these test cases: + +1. A valid request returns the expected bounded result. +2. An unknown tool is rejected. +3. An unknown argument or operator is rejected. +4. A request for an unauthorized table or column fails. +5. A request for another tenant's data fails. +6. SQL fragments supplied as values remain data rather than executable syntax. +7. An oversized limit is reduced or rejected. +8. A slow query times out. +9. A lock wait terminates within the configured bound. +10. A database outage produces a controlled error without exposing credentials. +11. A retried write does not create duplicate effects. +12. A rejected approval cannot reach the write procedure. +13. Sensitive values are absent from agent and server logs. +14. Every successful write can be traced to a user, run, tool call, and transaction. +15. Revoking the database role or MCP credential stops new access. + +The evaluation dataset should contain synthetic sensitive fields, adversarial text, duplicate records, empty results, large result sets, and cross-tenant examples. + +## What is the best AI agent platform for Postgres MCP? + +Sim is a strong choice for Postgres MCP workflows when a team wants an open-source AI workspace for building, deploying, and managing agents that combine MCP tools with multi-step logic. + +The platform decision should consider more than MCP connectivity. Buyers should compare: + +- How MCP servers and credentials are configured. +- Whether tools can be limited per agent or workflow. +- How conditions and human approvals are modeled. +- Whether tool calls appear in end-to-end traces. +- How deployment and private-network access work. +- Whether model, tool, and environment credentials can be separated. +- How workflows are tested, versioned, and operated. +- What the platform's license permits. + +Sim's core is licensed under [Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), while code in `apps/sim/ee` is governed by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Sim Enterprise subscription for production use. This distinction matters when evaluating self-hosting and modification rights. + +As of October 2026, n8n is an incumbent that buyers should also evaluate for MCP-connected automation. n8n documents an [MCP Client Tool node](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp), and n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is source-available rather than OSI-approved open source. + +A custom-coded agent host can provide maximum control over authentication, query policy, and deployment, but the team must implement and operate orchestration, retries, approvals, tracing, and lifecycle management itself. + +## How do you connect a Postgres MCP server to Sim? + +Sim connects an agent workflow to a Postgres MCP server by registering the server as an MCP connection, making its approved tools available to the workflow, and testing each tool with restricted credentials. + +A safe implementation sequence is: + +1. Deploy the Postgres MCP server with a read-only database role. +2. Confirm that the server exposes only the intended tools. +3. Add the MCP server connection to Sim using a transport and authentication method supported by both systems. +4. Make only the required MCP tools available to the agent workflow. +5. Add explicit workflow logic around empty results, errors, and high-impact actions. +6. Test the workflow against staging data and adversarial requests. +7. Review traces to confirm that arguments, timing, outcomes, and failures are visible without exposing secrets. +8. Keep write tools out of the agent block. Route each proposed write to a separate workflow step that can run only after a Human in the Loop step collects the decision and a downstream Condition confirms approval. + +An MCP write tool attached directly to an agent block is not gated per tool call, so the model could invoke it before a person approves the action. + +Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Teams evaluating the exact connection surface should also use [Best AI Agent Builders with MCP Support](https://www.sim.ai/library/best-ai-agent-builders-with-mcp-support) to compare MCP-oriented platform requirements. + +## What is the safest default configuration for a Postgres MCP server? + +A Postgres MCP server is safest by default when it uses a private deployment, a dedicated read-only role, curated views, allow-listed tools, hard timeouts, bounded results, and end-to-end audit logs. + +Use this baseline checklist: + +- Private network path to PostgreSQL. +- Authenticated and authorized MCP clients. +- Separate credentials for every environment. +- Dedicated non-owner database role. +- Read-only access to curated views. +- No arbitrary SQL tool. +- No DDL, role management, extension management, or administrative access. +- Strict tool input schemas. +- Parameterized values and allow-listed identifiers. +- Hard row, response-size, statement, and lock limits. +- Redacted logs linked to agent traces. +- Separate write service, role, and approval policy. +- Tested credential rotation and revocation. +- Adversarial and cross-tenant evaluation before production. + +This baseline does not eliminate risk, but it gives the model less authority and gives operators multiple independent controls when the model produces a bad request. + +## Which related guides explain MCP and AI agent platform choices? + +Sim's library separates Postgres implementation guidance from broader MCP, security, and platform-selection questions. + +- [What Is an MCP Server?](https://www.sim.ai/library/what-is-an-mcp-server) explains the protocol and core architecture. +- [MCP Security: A Practical Guide to Secure MCP Server Development](https://www.sim.ai/library/mcp-security) covers broader MCP threats and controls. +- [Best AI Agent Builders with MCP Support](https://www.sim.ai/library/best-ai-agent-builders-with-mcp-support) compares MCP-oriented platform criteria. +- [How to Turn a Workflow Into a Reusable MCP Tool (Sim vs n8n)](https://www.sim.ai/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool) covers the inverse pattern of exposing a workflow as an MCP tool. +- AI Agent Observability explains production monitoring and evaluation. diff --git a/apps/sim/ee/workspace-forking/application/admit-sync.ts b/apps/sim/ee/workspace-forking/application/admit-sync.ts index 243d3332609..6d590df1378 100644 --- a/apps/sim/ee/workspace-forking/application/admit-sync.ts +++ b/apps/sim/ee/workspace-forking/application/admit-sync.ts @@ -2,8 +2,6 @@ import { generateId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' import { enqueueOutboxEvent } from '@/lib/core/outbox/service' import type { DbOrTx } from '@/lib/db/types' -import { prepareWorkflowSnapshotDeployment } from '@/lib/workflows/orchestration/deploy' -import { loadWorkflowDeploymentSnapshot } from '@/lib/workflows/persistence/utils' import { insertWorkspaceOperationReceipt, type WorkspaceOperationReport, @@ -14,6 +12,10 @@ import { type ForkContentCopyPayload, hasForkContentToCopy, } from '@/ee/workspace-forking/lib/copy/content-copy-runner' +import { + type PrepareForkDeploymentsParams, + prepareForkSyncDeployments, +} from '@/ee/workspace-forking/lib/promote/prepare-deployments' import type { PromoteForkResult } from '@/ee/workspace-forking/lib/promote/promote' /** Persists the receipt, exact deployment versions, and resumable effects with the sync writes. */ @@ -29,6 +31,10 @@ export async function admitForkSync( undeployEventIds: string[] mcpAttachmentServerIds: string[] needsConfigurationIds: Set + deploymentSources: Pick< + PrepareForkDeploymentsParams, + 'childWorkspaceId' | 'items' | 'sourceVersions' + > copy?: ForkContentCopyPayload } ): Promise { @@ -72,19 +78,16 @@ export async function admitForkSync( report.copyProgress = { status: 'pending', copied: 0, failed: 0 } report.contentOutboxEventId = await enqueueDurableForkContent(tx, report, params.copy) } - for (const workflowId of [...params.targetIds].sort()) { - if (params.needsConfigurationIds.has(workflowId)) continue - const workflowState = await loadWorkflowDeploymentSnapshot(workflowId, tx) - if (!workflowState) throw new Error('A synced workflow is missing its admitted graph') - const prepared = await prepareWorkflowSnapshotDeployment({ - params: { workflowId, userId: params.userId, requestId: admission.requestId }, - actorId: params.userId, - requestId: admission.requestId, - idempotencyKey: `${report.operationId}:${workflowId}`, - workflowState, - tx, - workspaceOperationId: report.operationId, - }) + const deployments = await prepareForkSyncDeployments(tx, { + ...params.deploymentSources, + targetWorkspaceId: params.targetWorkspaceId, + promoteRunId: result.promoteRunId, + needsConfigurationIds: params.needsConfigurationIds, + userId: params.userId, + requestId: admission.requestId, + workspaceOperationId: report.operationId, + }) + for (const [workflowId, prepared] of deployments) { if (prepared.success) { report.deploymentOperationIds!.push(prepared.operation.id) if (prepared.outboxEventId) report.effectEventIds!.push(prepared.outboxEventId) diff --git a/apps/sim/ee/workspace-forking/application/create-and-sync.ts b/apps/sim/ee/workspace-forking/application/create-and-sync.ts index 07dcdee0515..cb0b929d016 100644 --- a/apps/sim/ee/workspace-forking/application/create-and-sync.ts +++ b/apps/sim/ee/workspace-forking/application/create-and-sync.ts @@ -29,6 +29,7 @@ export interface ForkInput { previewFingerprint?: string } export interface SyncInput extends SyncChoices { + expectedSourceVersions?: PromoteForkParams['expectedSourceVersions'] dependentValues?: PromoteForkParams['dependentValues'] workspaceId: string otherWorkspaceId: string @@ -255,6 +256,7 @@ export const syncWorkspace = defineForkUseCase< ...syncContext(input, context), ...choices, dependentValues: input.dependentValues, + expectedSourceVersions: input.expectedSourceVersions, userId: context.userId, actorName: await loadActorName(context.userId), otherWorkspaceName: context.other!.name, diff --git a/apps/sim/ee/workspace-forking/application/preview-sync.test.ts b/apps/sim/ee/workspace-forking/application/preview-sync.test.ts index e8f74421fb1..26d0ab28f4a 100644 --- a/apps/sim/ee/workspace-forking/application/preview-sync.test.ts +++ b/apps/sim/ee/workspace-forking/application/preview-sync.test.ts @@ -4,6 +4,7 @@ import { workspaceForkingMappingStoreMock, workspaceForkingMappingStoreMockFns, } from '@sim/testing/mocks/workspace-forking-mapping-store.mock' +import { workspaceForkingRevisionMock } from '@sim/testing/mocks/workspace-forking-revision.mock' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import type { WorkflowState } from '@/stores/workflows/workflow/types' @@ -15,9 +16,7 @@ vi.mock('@/lib/selectors/application/get-selector-option', () => ({ vi.mock('@/lib/workflows/references/custom-block-reconfigs', () => ({ collectForkCustomBlockReconfigs: vi.fn(async () => []), })) -vi.mock('@/ee/workspace-forking/application/revision', () => ({ - loadForkPreviewRevision: vi.fn(async () => ({ fingerprint: 'reviewed' })), -})) +vi.mock('@/ee/workspace-forking/application/revision', () => workspaceForkingRevisionMock) vi.mock('@/ee/workspace-forking/application/validate-bindings', () => ({ validateForkWorkflowBindings: vi.fn(), })) @@ -189,7 +188,9 @@ function prepare(state: WorkflowState, options: { copied?: boolean; create?: boo vi.mocked(loadSourceDeployedStates).mockResolvedValue({ deployedWorkflows: [], sourceStates: new Map([['workflow', state]]), - sourceVersionIds: new Map(), + sourceVersionIds: new Map([ + ['workflow', { id: 'source-version-1', version: 1, digest: 'fixture' }], + ]), }) return plan } diff --git a/apps/sim/ee/workspace-forking/application/preview-sync.ts b/apps/sim/ee/workspace-forking/application/preview-sync.ts index 3ac80c9ab6a..67656fd0305 100644 --- a/apps/sim/ee/workspace-forking/application/preview-sync.ts +++ b/apps/sim/ee/workspace-forking/application/preview-sync.ts @@ -27,6 +27,7 @@ import { import { buildPromoteCopySelection } from '@/ee/workspace-forking/lib/promote/copy-unmapped' import type { PromoteForkParams } from '@/ee/workspace-forking/lib/promote/promote' import { computeForkPromotePlan } from '@/ee/workspace-forking/lib/promote/promote-plan' +import { loadForkWorkflowComparisons } from '@/ee/workspace-forking/lib/promote/sync-provenance' import { buildForkTriggerPlan, resolveForkTriggerPaths, @@ -60,7 +61,8 @@ export async function previewForkSync( const { edge, sourceWorkspaceId, targetWorkspaceId } = params const revision = await loadForkPreviewRevision(db, params, choices) await validateForkMappingTargets(sourceWorkspaceId, targetWorkspaceId, params.mappings ?? []) - const { deployedWorkflows, sourceStates } = await loadSourceDeployedStates(sourceWorkspaceId) + const { deployedWorkflows, sourceStates, sourceVersionIds } = + await loadSourceDeployedStates(sourceWorkspaceId) const mappingRows = overlayForkMappingEntries( await getEdgeMappingRows(db, edge.childWorkspaceId), edge, @@ -308,18 +310,29 @@ export async function previewForkSync( 'conflict', 'Workspace changed during preview; request another preview' ) + const comparisons = await loadForkWorkflowComparisons( + db, + edge.childWorkspaceId, + plan.items, + sourceVersionIds + ) const preview = { previewFingerprint: revision.fingerprint, sourceWorkspaceId, targetWorkspaceId, ready: blockers.length === 0, workflows: [ - ...plan.items.map((item) => ({ - action: item.mode, - sourceWorkflowId: item.sourceWorkflowId, - ...(item.mode === 'replace' ? { targetWorkflowId: item.targetWorkflowId } : {}), - name: item.sourceMeta.name, - })), + ...plan.items.map((item) => { + const comparison = comparisons.get(item.sourceWorkflowId) + if (!comparison) throw new Error('Missing source workflow comparison') + return { + action: item.mode, + sourceWorkflowId: item.sourceWorkflowId, + comparison, + ...(item.mode === 'replace' ? { targetWorkflowId: item.targetWorkflowId } : {}), + name: item.sourceMeta.name, + } + }), ...plan.archivedTargets.map((item) => ({ action: 'archive' as const, targetWorkflowId: item.id, diff --git a/apps/sim/ee/workspace-forking/application/revision.ts b/apps/sim/ee/workspace-forking/application/revision.ts index 1bfb81aaf92..7a2380a48f3 100644 --- a/apps/sim/ee/workspace-forking/application/revision.ts +++ b/apps/sim/ee/workspace-forking/application/revision.ts @@ -160,15 +160,15 @@ export async function lockForkRevision(tx: DbTransaction, scope: ForkRevisionSco ) await tx.execute(sql`SELECT id FROM ${workspace} WHERE id IN (${values}) ORDER BY id FOR UPDATE`) await tx.execute( - sql`SELECT id FROM ${workflow} WHERE workspace_id IN (${values}) ORDER BY id FOR UPDATE` + sql`SELECT count(*) FROM (SELECT id FROM ${workflow} WHERE workspace_id IN (${values}) ORDER BY id FOR UPDATE) locked` ) for (const table of [workflowBlocks, workflowEdges, workflowSubflows]) { await tx.execute( - sql`SELECT r.id FROM ${table} r JOIN ${workflow} w ON w.id = r.workflow_id WHERE w.workspace_id IN (${values}) ORDER BY r.id FOR UPDATE OF r` + sql`SELECT count(*) FROM (SELECT r.id FROM ${table} r JOIN ${workflow} w ON w.id = r.workflow_id WHERE w.workspace_id IN (${values}) ORDER BY r.id FOR UPDATE OF r) locked` ) } await tx.execute( - sql`SELECT d.id FROM ${workflowDeploymentVersion} d JOIN ${workflow} w ON w.id = d.workflow_id WHERE w.workspace_id IN (${values}) AND d.is_active = true ORDER BY d.id FOR SHARE OF d` + sql`SELECT count(*) FROM (SELECT d.id FROM ${workflowDeploymentVersion} d JOIN ${workflow} w ON w.id = d.workflow_id WHERE w.workspace_id IN (${values}) AND d.is_active = true ORDER BY d.id FOR SHARE OF d) locked` ) if (scope.edge) { const [edge] = await tx.execute<{ parent: string | null }>( @@ -202,15 +202,23 @@ export async function assertForkPreviewFresh( export async function assertForkSourceVersions( tx: DbOrTx, sourceWorkspaceId: string, - expected: ReadonlyMap + expected: ReadonlyMap, + reviewed?: readonly { workflowId: string; deploymentVersionId: string }[] ): Promise { const rows = await tx.execute<{ workflowId: string; id: string; digest: string }>(sql` SELECT w.id AS "workflowId", d.id, md5(d.state::text) AS digest FROM ${workflow} w JOIN ${workflowDeploymentVersion} d ON d.workflow_id = w.id AND d.is_active = true WHERE w.workspace_id = ${sourceWorkspaceId} AND w.is_deployed = true AND w.archived_at IS NULL AND w.fork_sync_excluded = false + LIMIT ${expected.size + 1} `) if ( + (reviewed !== undefined && + (reviewed.length !== expected.size || + new Set(reviewed.map((entry) => entry.workflowId)).size !== reviewed.length || + reviewed.some( + (entry) => expected.get(entry.workflowId)?.id !== entry.deploymentVersionId + ))) || rows.length !== expected.size || rows.some( (row) => @@ -219,7 +227,7 @@ export async function assertForkSourceVersions( ) ) throw new WorkspaceOperationConflict( - 'Source deployment changed during loading; request a new preview', + 'Source deployment changed; refresh the sync details and review the comparison again', { applied: false, reason: 'stale_preview', diff --git a/apps/sim/ee/workspace-forking/application/sync-details.ts b/apps/sim/ee/workspace-forking/application/sync-details.ts index fd5b8d34965..8c8694b6943 100644 --- a/apps/sim/ee/workspace-forking/application/sync-details.ts +++ b/apps/sim/ee/workspace-forking/application/sync-details.ts @@ -26,6 +26,7 @@ import { collectForkClearedRefCandidates, } from '@/ee/workspace-forking/lib/promote/cleared-refs' import { computeForkPromotePlan } from '@/ee/workspace-forking/lib/promote/promote-plan' +import { loadForkWorkflowComparisons } from '@/ee/workspace-forking/lib/promote/sync-provenance' import { buildForkTriggerPlan } from '@/ee/workspace-forking/lib/promote/trigger-urls' import { buildForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' @@ -52,7 +53,7 @@ export const getWorkspaceSyncDetails = defineForkUseCase({ sourceWorkspaceId: direction === 'push' ? id : input.otherWorkspaceId, targetWorkspaceId: direction === 'push' ? input.otherWorkspaceId : id, } - const { deployedWorkflows, sourceStates } = await loadSourceDeployedStates( + const { deployedWorkflows, sourceStates, sourceVersionIds } = await loadSourceDeployedStates( auth.sourceWorkspaceId ) const plan = await computeForkPromotePlan({ @@ -245,12 +246,22 @@ export const getWorkspaceSyncDetails = defineForkUseCase({ // Orient the mapping around the workspace the modal is open in (`id`): show the // caller's workflow name first, the sync partner's second, so renames are legible. const currentIsSource = auth.sourceWorkspaceId === id + const comparisons = await loadForkWorkflowComparisons( + db, + auth.edge.childWorkspaceId, + plan.items, + sourceVersionIds + ) const workflows = [ ...plan.items.map((item) => { + const comparison = comparisons.get(item.sourceWorkflowId) + if (!comparison) throw new Error('Missing source workflow comparison') if (item.mode === 'create') { // The target inherits the source's name, so both sides read the same. return { action: 'create' as const, + sourceWorkflowId: item.sourceWorkflowId, + comparison, currentName: item.sourceMeta.name, otherName: item.sourceMeta.name, } @@ -258,12 +269,15 @@ export const getWorkspaceSyncDetails = defineForkUseCase({ const targetName = item.targetName ?? item.sourceMeta.name return { action: 'update' as const, + sourceWorkflowId: item.sourceWorkflowId, + comparison, currentName: currentIsSource ? item.sourceMeta.name : targetName, otherName: currentIsSource ? targetName : item.sourceMeta.name, } }), ...plan.archivedTargets.map((target) => ({ action: 'archive' as const, + targetWorkflowId: target.id, currentName: target.name, otherName: target.name, })), @@ -272,6 +286,10 @@ export const getWorkspaceSyncDetails = defineForkUseCase({ return { sourceWorkspaceId: auth.sourceWorkspaceId, targetWorkspaceId: auth.targetWorkspaceId, + sourceVersions: [...sourceVersionIds].map(([workflowId, version]) => ({ + workflowId, + deploymentVersionId: version.id, + })), willUpdate: plan.willUpdate, willCreate: plan.willCreate, willArchive: plan.willArchive, diff --git a/apps/sim/ee/workspace-forking/components/fork-sync/fork-comparison-modal.tsx b/apps/sim/ee/workspace-forking/components/fork-sync/fork-comparison-modal.tsx new file mode 100644 index 00000000000..943ef0d6069 --- /dev/null +++ b/apps/sim/ee/workspace-forking/components/fork-sync/fork-comparison-modal.tsx @@ -0,0 +1,49 @@ +'use client' + +import type { ForkWorkflowComparison } from '@/lib/api/contracts/workspace-fork' +import { WorkflowComparisonModal } from '@/app/workspace/[workspaceId]/w/components/workflow-diff' +import { useDeploymentVersionState } from '@/hooks/queries/workflows' + +/** Pins the last synced and proposed deployment identities within one source workflow. */ +export interface ForkComparisonSelection { + sourceWorkflowId: string + comparison: Extract +} + +interface ForkComparisonModalProps extends ForkComparisonSelection { + onClose: () => void +} + +/** Loads the pinned source snapshots; missing or replaced versions remain explicit load errors. */ +export function ForkComparisonModal({ + sourceWorkflowId, + comparison, + onClose, +}: ForkComparisonModalProps) { + const base = useDeploymentVersionState( + sourceWorkflowId, + comparison.base.version, + comparison.base.id + ) + const target = useDeploymentVersionState( + sourceWorkflowId, + comparison.target.version, + comparison.target.id + ) + return ( + { + if (!open) onClose() + }} + header='Compare versions' + baseState={base.data ?? null} + targetState={target.data ?? null} + isLoading={base.isLoading || target.isLoading} + error={base.error ?? target.error} + comparisonKey={`${comparison.base.id}:${comparison.target.id}`} + baseLabel='Last Sync' + targetLabel='Now' + /> + ) +} diff --git a/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx b/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx index fe2d8c765cc..8c8c3bc3701 100644 --- a/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx @@ -1,6 +1,14 @@ 'use client' -import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react' +import { + type Dispatch, + Fragment, + lazy, + type SetStateAction, + Suspense, + useMemo, + useState, +} from 'react' import { Badge, ChevronDown, @@ -15,7 +23,6 @@ import { OverflowText, Tooltip, } from '@sim/emcn' -import { ArrowRight } from '@sim/emcn/icons' import type { ForkCopyableUnmapped, ForkDependentReconfig, @@ -25,7 +32,12 @@ import type { } from '@/lib/api/contracts/workspace-fork' import type { SelectorKey } from '@/lib/selectors/manifest' import { buildWebhookTriggerUrl } from '@/lib/webhooks/trigger-url' +import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { + RESOURCE_LIST_STACK, + SettingsResourceRow, +} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row' import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { FileKindRow, @@ -61,6 +73,12 @@ import type { import type { ForkDirection } from '@/ee/workspace-forking/hooks/workspace-fork' import { forkSyncBlockerReasonFor } from '@/ee/workspace-forking/lib/promote/sync-blockers' +const ForkComparisonModal = lazy(() => + import('@/ee/workspace-forking/components/fork-sync/fork-comparison-modal').then((module) => ({ + default: module.ForkComparisonModal, + })) +) + /** * Copyable kinds as expandable rows in the "Copy resources" section, ordered + labeled to match * the fork modal's resource picker exactly. Files nest in a folder ▸ file tree; every other kind @@ -831,6 +849,14 @@ export function ForkSyncView({ controller, onDirectionChange }: ForkSyncViewProp return (
+ {controller.comparisonSelection ? ( + + + + ) : null}
{controller.workflowChanges.length + excludedRows.length > 0 ? ( -
- {controller.workflowChanges.map((change, index) => { - const renamed = change.currentName !== change.otherName +
+ {controller.workflowChanges.map((change) => { + const comparison = change.action === 'archive' ? null : change.comparison + const unavailableReason = + comparison?.status === 'unavailable' ? comparison.reason : null + const tooltip = + unavailableReason === 'new_workflow' + ? 'Newly added.' + : unavailableReason === 'no_baseline' + ? 'Available after a successful sync.' + : unavailableReason === 'missing_baseline' + ? 'The last synced version is no longer available.' + : undefined return ( -
- - {change.currentName} - - {renamed ? ( - <> - - - {change.otherName} - - - ) : null} -
+ controller.openComparison(change), + disabled: + !controller.comparisonReady || comparison?.status !== 'available', + tooltip: controller.comparisonReady + ? tooltip + : 'Loading sync details…', + }, + ]} + /> + ) + } + /> ) })} {excludedRows.map(({ name, tooltip }, index) => ( diff --git a/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.test.tsx b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.test.tsx index e5781b1dade..b337a21b4b7 100644 --- a/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.test.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.test.tsx @@ -143,6 +143,7 @@ function diffData(dependentReconfigs: ForkDependentReconfig[]) { sourceWorkspaceId: WORKSPACE_ID, targetWorkspaceId: OTHER_WORKSPACE_ID, workflows: [], + sourceVersions: [], dependentReconfigs, resourceUsages: [], copyableUnmapped: [], diff --git a/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts index c4de4a5d719..52fa62adc39 100644 --- a/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts @@ -1,6 +1,13 @@ 'use client' -import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react' +import { + type Dispatch, + type SetStateAction, + useCallback, + useEffect, + useMemo, + useState, +} from 'react' import { toast } from '@sim/emcn' import { getErrorMessage } from '@sim/utils/errors' import type { @@ -38,6 +45,7 @@ import { effectiveDependentValue, isDependentInvalidated, } from '@/ee/workspace-forking/components/fork-sync/dependent-value' +import type { ForkComparisonSelection } from '@/ee/workspace-forking/components/fork-sync/fork-comparison-modal' import { forkDyingTriggerUrls, forkTriggerChoices, @@ -191,6 +199,10 @@ export interface ForkSyncController { dependentClears: ForkClearedRef[] /** Deployed-workflow change list (update → create → archive, then by name). */ workflowChanges: ForkWorkflowChange[] + comparisonReady: boolean + comparisonSelection: ForkComparisonSelection | null + openComparison: (change: Exclude) => void + closeComparison: () => void /** Names of target workflows this sync archives, for the confirm modal. */ archivedWorkflowNames: string[] /** @@ -331,23 +343,31 @@ export function useForkSync(params: { // stored in the target block's `triggerPath`, so later syncs preserve it with no input at all. // `''` is the explicit "mint a new URL" choice, distinct from an absent key (take the default). const [triggerAdoptions, setTriggerAdoptions] = useState>({}) - const [submitting, setSubmitting] = useState(false) + const [comparisonSelection, setComparisonSelection] = useState( + null + ) + const [reviewedSourceVersions, setReviewedSourceVersions] = useState>({}) + const sessionKey = JSON.stringify([workspaceId, direction, otherWorkspaceId]) + const [previousSessionKey, setPreviousSessionKey] = useState(sessionKey) - // Drop every in-session choice when the direction (or edge) changes - the mapping set, - // copy candidates, and blockers all depend on it. - useEffect(() => { + if (previousSessionKey !== sessionKey) { + setPreviousSessionKey(sessionKey) setTargets({}) setReconfig({}) setCopySelected(new Set()) setCopyDefaulted(false) setDroppedRefs(new Set()) setTriggerAdoptions({}) - }, [direction, otherWorkspaceId]) + setComparisonSelection(null) + setReviewedSourceVersions({}) + } const mapping = useForkMapping({ workspaceId, otherWorkspaceId, direction, enabled }) const diff = useForkDiff({ workspaceId, otherWorkspaceId, direction, enabled }) const updateMapping = useUpdateForkMapping() const promote = usePromoteFork() + const submitting = promote.isPending + const comparisonReady = enabled && !!diff.data && !diff.isPlaceholderData && !diff.isError const entries = useMemo(() => mapping.data?.entries ?? [], [mapping.data]) const dependentReconfigs = useMemo( @@ -879,15 +899,42 @@ export function useForkSync(params: { const triggerChoiceFor = (sourceBlockId: string): string => chosenTriggerPaths.get(sourceBlockId) ?? '' + const openComparison = useCallback( + (change) => { + if (!comparisonReady || change.comparison.status !== 'available') return + setComparisonSelection({ + sourceWorkflowId: change.sourceWorkflowId, + comparison: change.comparison, + }) + setReviewedSourceVersions((current) => ({ + ...current, + [change.sourceWorkflowId]: change.comparison.target.id, + })) + }, + [comparisonReady] + ) + const closeComparison = useCallback(() => setComparisonSelection(null), []) + const discard = () => { setTargets({}) setReconfig({}) setTriggerAdoptions({}) + setReviewedSourceVersions({}) + setComparisonSelection(null) } const sync = async () => { - if (!otherWorkspaceId) return - setSubmitting(true) + if (!otherWorkspaceId || !diff.data) return + const submittedReviewedVersions = reviewedSourceVersions + const sourceVersions = new Map( + diff.data.sourceVersions.map((source) => [source.workflowId, source.deploymentVersionId]) + ) + for (const [workflowId, deploymentVersionId] of Object.entries(reviewedSourceVersions)) + sourceVersions.set(workflowId, deploymentVersionId) + const expectedSourceVersions = Array.from( + sourceVersions, + ([workflowId, deploymentVersionId]) => ({ workflowId, deploymentVersionId }) + ) const submittedTargets = targets const submittedReconfig = reconfig const submittedDroppedRefs = droppedRefs @@ -926,10 +973,6 @@ export function useForkSync(params: { adoptPath: triggerAdoptions[mapping.sourceBlockId] || null, })) try { - await updateMapping.mutateAsync({ - workspaceId, - body: { otherWorkspaceId, direction, entries: mappingEntries }, - }) const copyResources = { knowledgeBases: selectedCopyables .filter((c) => c.kind === 'knowledge-base') @@ -949,6 +992,8 @@ export function useForkSync(params: { body: { otherWorkspaceId, direction, + mappings: mappingEntries, + expectedSourceVersions, // Once the diff has loaded, ALWAYS send the full effective set - including `[]`, // which means "every dependent went away" and must reconcile/clear the live replace // targets' stored rows. Collapsing `[]` into omission would make the backend @@ -990,6 +1035,7 @@ export function useForkSync(params: { // stored, and the accepted drops and trigger choices are applied. Drop only the exact // snapshots it submitted; edits made while the request was in flight were not committed by // this run and must remain available for the next Save/Sync. + setReviewedSourceVersions((current) => (current === submittedReviewedVersions ? {} : current)) setTargets((current) => (current === submittedTargets ? {} : current)) setReconfig((current) => (current === submittedReconfig ? {} : current)) setDroppedRefs((current) => (current === submittedDroppedRefs ? new Set() : current)) @@ -1029,8 +1075,6 @@ export function useForkSync(params: { } } catch (error) { toast.error(getErrorMessage(error, 'Sync failed')) - } finally { - setSubmitting(false) } } @@ -1075,6 +1119,10 @@ export function useForkSync(params: { blockingRefs, dependentClears, workflowChanges, + comparisonReady, + comparisonSelection, + openComparison, + closeComparison, archivedWorkflowNames, triggerUrlChanges, triggerMappings, diff --git a/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts b/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts index 9bb10647c97..aa8fbf734ff 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts @@ -179,7 +179,7 @@ export async function getActiveDeploymentVersionNumbers( export async function loadSourceDeployedStates(sourceWorkspaceId: string): Promise<{ deployedWorkflows: DeployedWorkflowSummary[] sourceStates: Map - sourceVersionIds: Map + sourceVersionIds: Map }> { const deployedWorkflows = await listDeployedWorkflows(db, sourceWorkspaceId) // Fail fast on the cheap count before loading any heavy state into memory. @@ -216,6 +216,7 @@ export async function loadSourceDeployedStates(sourceWorkspaceId: string): Promi .select({ id: workflowDeploymentVersion.id, workflowId: workflowDeploymentVersion.workflowId, + version: workflowDeploymentVersion.version, bytes: sql`octet_length(${workflowDeploymentVersion.state}::text)`, digest: sql`md5(${workflowDeploymentVersion.state}::text)`, }) @@ -233,7 +234,10 @@ export async function loadSourceDeployedStates(sourceWorkspaceId: string): Promi if (versions.reduce((total, row) => total + Number(row.bytes), 0) > MAX_FORK_STATE_BYTES) throw new ForkError('Deployed workflow states exceed the aggregate byte limit', 413) const sourceVersionIds = new Map( - versions.map((version) => [version.workflowId, { id: version.id, digest: version.digest }]) + versions.map((version) => [ + version.workflowId, + { id: version.id, version: version.version, digest: version.digest }, + ]) ) if ( sourceVersionIds.size !== deployedWorkflows.length || diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts index 648815617f7..be6e8e5738c 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts @@ -10,6 +10,10 @@ import { workspaceForkingMappingStoreMock, workspaceForkingMappingStoreMockFns, } from '@sim/testing/mocks/workspace-forking-mapping-store.mock' +import { + workspaceForkingRevisionMock, + workspaceForkingRevisionMockFns, +} from '@sim/testing/mocks/workspace-forking-revision.mock' import { workspacesPolicyMock } from '@sim/testing/mocks/workspaces-policy.mock' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -23,7 +27,6 @@ const { mockFinishBackgroundWork, mockScheduleForkContentCopy, mockCollectReferencedFileFolderPaths, - mockLockForkRevision, } = vi.hoisted(() => ({ mockSumForkCopyBytes: vi.fn(), mockAssertForkStorageHeadroom: vi.fn(), @@ -34,7 +37,6 @@ const { mockFinishBackgroundWork: vi.fn(), mockScheduleForkContentCopy: vi.fn(), mockCollectReferencedFileFolderPaths: vi.fn(() => new Set()), - mockLockForkRevision: vi.fn(async () => {}), })) vi.mock('@/lib/workflows/defaults', () => ({ @@ -78,11 +80,7 @@ vi.mock('@/ee/workspace-forking/lib/copy/deploy-bridge', () => ({ loadSourceDeployedStates: mockLoadSourceDeployedStates, })) vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => workspaceForkingLineageMock) -vi.mock('@/ee/workspace-forking/application/revision', () => ({ - lockForkRevision: mockLockForkRevision, - assertForkPreviewFresh: vi.fn(async () => {}), - assertForkSourceVersions: vi.fn(async () => {}), -})) +vi.mock('@/ee/workspace-forking/application/revision', () => workspaceForkingRevisionMock) vi.mock('@/lib/workspaces/operations/receipts', () => ({ findWorkspaceOperationReceipt: vi.fn(async () => null), insertWorkspaceOperationReceipt: vi.fn(async () => {}), @@ -106,6 +104,8 @@ vi.mock('@/lib/workspaces/policy', () => workspacesPolicyMock) import { createFork } from '@/ee/workspace-forking/lib/create-fork' +const { mockLockForkRevision } = workspaceForkingRevisionMockFns + const { mockSeedEdgeMappings } = workspaceForkingMappingStoreMockFns const SOURCE = { id: 'src-ws', name: 'Parent', allowPersonalApiKeys: false } as never diff --git a/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts index d2eb41a82c9..276899bf746 100644 --- a/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts @@ -27,7 +27,7 @@ describe('unlinkForkEdge', () => { mockResolveForkLineageRootId.mockResolvedValue('root-ws') }) - it('nulls the child pointer and purges all four edge tables under the edge lock', async () => { + it('detaches the child under its edge lock', async () => { dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'child-ws' }]) const result = await unlinkForkEdge(EDGE, 'req-1') @@ -39,7 +39,6 @@ describe('unlinkForkEdge', () => { expect(dbChainMockFns.set).toHaveBeenCalledWith( expect.objectContaining({ forkedFromWorkspaceId: null }) ) - expect(dbChainMockFns.delete).toHaveBeenCalledTimes(4) }) /** A root moved by a concurrent unlink higher up means the lock taken no longer covers it. */ diff --git a/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts index 6540a3c4a96..6f77fee413f 100644 --- a/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts @@ -5,6 +5,7 @@ import { workspaceForkDependentValue, workspaceForkPromoteRun, workspaceForkResourceMap, + workspaceForkWorkflowSync, } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' @@ -86,6 +87,9 @@ export async function unlinkForkEdge( await tx .delete(workspaceForkPromoteRun) .where(eq(workspaceForkPromoteRun.childWorkspaceId, childWorkspaceId)) + await tx + .delete(workspaceForkWorkflowSync) + .where(eq(workspaceForkWorkflowSync.childWorkspaceId, childWorkspaceId)) return true }) diff --git a/apps/sim/ee/workspace-forking/lib/promote/prepare-deployments.ts b/apps/sim/ee/workspace-forking/lib/promote/prepare-deployments.ts new file mode 100644 index 00000000000..e006a797f4f --- /dev/null +++ b/apps/sim/ee/workspace-forking/lib/promote/prepare-deployments.ts @@ -0,0 +1,90 @@ +import { workflow, workspaceForkWorkflowSync } from '@sim/db/schema' +import { compareStrings } from '@sim/utils/string' +import { inArray } from 'drizzle-orm' +import type { DbOrTx } from '@/lib/db/types' +import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants' +import { isFolderPathEffectivelyLocked } from '@/lib/folders/paths' +import { loadActiveFolderPathIndex } from '@/lib/folders/queries' +import { prepareWorkflowSnapshotDeployment } from '@/lib/workflows/orchestration/deploy' +import { loadWorkflowDeploymentSnapshot } from '@/lib/workflows/persistence/utils' + +export interface PrepareForkDeploymentsParams { + childWorkspaceId: string + targetWorkspaceId: string + promoteRunId: string + items: readonly { sourceWorkflowId: string; targetWorkflowId: string }[] + sourceVersions: ReadonlyMap + needsConfigurationIds: ReadonlySet + userId: string + requestId: string + workspaceOperationId?: string +} + +export type PreparedForkDeployment = Awaited> + +/** Admits copied graphs and their source provenance together, before releasing the sync locks. */ +export async function prepareForkSyncDeployments( + tx: DbOrTx, + params: PrepareForkDeploymentsParams +): Promise> { + const results = new Map() + const items = params.items.filter( + (item) => !params.needsConfigurationIds.has(item.targetWorkflowId) + ) + if (!items.length) return results + const folders = await loadActiveFolderPathIndex(params.targetWorkspaceId, 'workflow', tx, { + maxRows: MAX_FOLDERS_PER_WORKSPACE, + }) + const targets = await tx + .select({ id: workflow.id, locked: workflow.locked, folderId: workflow.folderId }) + .from(workflow) + .where( + inArray( + workflow.id, + items.map((item) => item.targetWorkflowId) + ) + ) + const byId = new Map(targets.map((target) => [target.id, target])) + for (const item of [...items].sort((a, b) => + compareStrings(a.targetWorkflowId, b.targetWorkflowId) + )) { + const workflowId = item.targetWorkflowId + const target = byId.get(workflowId) + if (!target) throw new Error('A synced workflow is missing its admitted target') + if ( + target.locked || + (target.folderId && isFolderPathEffectivelyLocked(folders, target.folderId)) + ) { + results.set(workflowId, { + success: false, + error: target.locked ? 'Workflow is locked' : 'Workflow is locked by its containing folder', + errorCode: 'locked', + }) + continue + } + const sourceVersion = params.sourceVersions.get(item.sourceWorkflowId) + if (!sourceVersion) throw new Error('A synced workflow is missing its admitted source version') + const workflowState = await loadWorkflowDeploymentSnapshot(workflowId, tx) + if (!workflowState) throw new Error('A synced workflow is missing its admitted graph') + const prepared = await prepareWorkflowSnapshotDeployment({ + params: { workflowId, userId: params.userId, requestId: params.requestId }, + actorId: params.userId, + requestId: params.requestId, + idempotencyKey: `${params.promoteRunId}:${workflowId}`, + workflowState, + tx, + workspaceOperationId: params.workspaceOperationId, + }) + if (prepared.success) + await tx.insert(workspaceForkWorkflowSync).values({ + deploymentOperationId: prepared.operation.id, + childWorkspaceId: params.childWorkspaceId, + sourceWorkflowId: item.sourceWorkflowId, + targetWorkflowId: workflowId, + sourceDeploymentVersionId: sourceVersion.id, + promoteRunId: params.promoteRunId, + }) + results.set(workflowId, prepared) + } + return results +} diff --git a/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts b/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts index 6ecb3c22e3a..eea03a09575 100644 --- a/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts @@ -1,3 +1,4 @@ +import { resetDbChainMock } from '@sim/testing/mocks/database.mock' import { permissionsMock, permissionsMockFns } from '@sim/testing/mocks/permissions.mock' import { workflowsPersistenceUtilsMock, @@ -5,6 +6,7 @@ import { } from '@sim/testing/mocks/workflows-persistence-utils.mock' import { workspaceForkingLineageMock } from '@sim/testing/mocks/workspace-forking-lineage.mock' import { workspaceForkingMappingStoreMock } from '@sim/testing/mocks/workspace-forking-mapping-store.mock' +import { workspaceForkingRevisionMock } from '@sim/testing/mocks/workspace-forking-revision.mock' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ForkSyncBlocker } from '@/lib/api/contracts/workspace-fork' @@ -49,7 +51,13 @@ vi.mock('@/lib/workflows/deployment-outbox', () => ({ processWorkflowDeploymentOutboxEvent: vi.fn(), })) vi.mock('@/lib/workflows/orchestration/deploy', () => ({ - performFullDeploy: vi.fn(async () => ({ success: true })), + finishPreparedWorkflowDeployment: vi.fn(async () => ({ success: true })), +})) +vi.mock('@/ee/workspace-forking/application/revision', () => workspaceForkingRevisionMock) +vi.mock('@/ee/workspace-forking/lib/promote/prepare-deployments', () => ({ + prepareForkSyncDeployments: vi.fn( + async () => new Map([['wf-tgt', { success: true, operation: { workflowId: 'wf-tgt' } }]]) + ), })) vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) vi.mock('@/ee/workspace-forking/lib/background-work/store', () => ({ @@ -150,10 +158,11 @@ vi.mock('@/ee/workspace-forking/lib/socket', () => ({ vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) import { db } from '@sim/db' -import { performFullDeploy } from '@/lib/workflows/orchestration/deploy' +import { finishPreparedWorkflowDeployment } from '@/lib/workflows/orchestration/deploy' import { getBlock } from '@/blocks/registry' import { copyWorkflowStateIntoTarget } from '@/ee/workspace-forking/lib/copy/copy-workflows' import { reconcileForkDependentValues } from '@/ee/workspace-forking/lib/mapping/dependent-value-store' +import { prepareForkSyncDeployments } from '@/ee/workspace-forking/lib/promote/prepare-deployments' import { promoteFork } from '@/ee/workspace-forking/lib/promote/promote' import type { ForkPromotePlan } from '@/ee/workspace-forking/lib/promote/promote-plan' @@ -236,13 +245,12 @@ function emptyCopyResult() { } beforeEach(() => { - vi.mocked(db.transaction).mockImplementation( - async (cb: (tx: unknown) => unknown) => cb({}) as never - ) + resetDbChainMock() mockGetUsersWithPermissions.mockResolvedValue([]) mockLoadSourceDeployedStates.mockResolvedValue({ deployedWorkflows: [], sourceStates: new Map(), + sourceVersionIds: new Map(), }) mockComputePlan.mockResolvedValue(makePlan()) mockBuildCopySelection.mockReturnValue({ @@ -558,6 +566,7 @@ describe('promoteFork trigger URLs', () => { ).rejects.toMatchObject({ code: 'validation' }) expect(copyWorkflowStateIntoTarget).not.toHaveBeenCalled() expect(mockUpsertPromoteRun).not.toHaveBeenCalled() - expect(performFullDeploy).not.toHaveBeenCalled() + expect(prepareForkSyncDeployments).not.toHaveBeenCalled() + expect(finishPreparedWorkflowDeployment).not.toHaveBeenCalled() }) }) diff --git a/apps/sim/ee/workspace-forking/lib/promote/promote.ts b/apps/sim/ee/workspace-forking/lib/promote/promote.ts index afb47d0bfe3..266a3bca4ab 100644 --- a/apps/sim/ee/workspace-forking/lib/promote/promote.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote.ts @@ -18,7 +18,7 @@ import { enqueueWorkflowUndeploySideEffects, processWorkflowDeploymentOutboxEvent, } from '@/lib/workflows/deployment-outbox' -import { performFullDeploy } from '@/lib/workflows/orchestration/deploy' +import { finishPreparedWorkflowDeployment } from '@/lib/workflows/orchestration/deploy' import { undeployWorkflow } from '@/lib/workflows/persistence/utils' import { collectForkCustomBlockReconfigs } from '@/lib/workflows/references/custom-block-reconfigs' import { collectForkDependentReconfigs } from '@/lib/workflows/references/dependent-reconfigs' @@ -113,6 +113,10 @@ import { copyPromoteUnmappedResources, hasPromoteCopySelection, } from '@/ee/workspace-forking/lib/promote/copy-unmapped' +import { + type PreparedForkDeployment, + prepareForkSyncDeployments, +} from '@/ee/workspace-forking/lib/promote/prepare-deployments' import { computeForkPromotePlan, type ForkPromotePlan, @@ -186,6 +190,7 @@ export interface PromoteForkParams { subBlockKey: string value: string }> + expectedSourceVersions?: Array<{ workflowId: string; deploymentVersionId: string }> } export interface PromoteForkResult { @@ -424,6 +429,7 @@ type PromoteTxBlocked = | { blocked: 'cleared-refs'; blockers: ForkSyncBlocker[] } interface PromoteTxApplied { + deployments: Map operation?: WorkspaceOperationReport blocked: null promoteRunId: string @@ -622,11 +628,16 @@ export async function promoteFork(params: PromoteForkParams): Promise item.targetWorkflowId), @@ -1287,6 +1299,11 @@ export async function promoteFork(params: PromoteForkParams): Promise { + const now = new Date() + await tx + .update(workspaceForkWorkflowSync) + .set({ activatedAt: now }) + .where( + and( + eq(workspaceForkWorkflowSync.deploymentOperationId, operation.id), + eq(workspaceForkWorkflowSync.targetWorkflowId, operation.workflowId), + isNull(workspaceForkWorkflowSync.activatedAt) + ) + ) + await tx + .update(workspaceForkWorkflowSync) + .set({ rolledBackAt: now }) + .where( + and( + eq(workspaceForkWorkflowSync.rollbackOperationId, operation.id), + eq(workspaceForkWorkflowSync.targetWorkflowId, operation.workflowId), + isNull(workspaceForkWorkflowSync.rolledBackAt) + ) + ) +} + +/** Binds only this undo's records; a completed partial undo remains completed on retry. */ +export async function bindForkSyncRollback( + tx: DbOrTx, + promoteRunId: string, + operation: WorkflowDeploymentOperation +): Promise { + await tx + .update(workspaceForkWorkflowSync) + .set({ + rollbackOperationId: operation.id, + ...(operation.status === 'active' ? { rolledBackAt: new Date() } : {}), + }) + .where( + and( + eq(workspaceForkWorkflowSync.promoteRunId, promoteRunId), + eq(workspaceForkWorkflowSync.targetWorkflowId, operation.workflowId), + isNull(workspaceForkWorkflowSync.rolledBackAt) + ) + ) +} + +/** An undeploy cuts over synchronously, in the caller's rollback transaction. */ +export async function undeployForkSyncProvenance( + tx: DbOrTx, + promoteRunId: string, + workflowId: string +): Promise { + await tx + .update(workspaceForkWorkflowSync) + .set({ rolledBackAt: new Date() }) + .where( + and( + eq(workspaceForkWorkflowSync.promoteRunId, promoteRunId), + eq(workspaceForkWorkflowSync.targetWorkflowId, workflowId), + isNull(workspaceForkWorkflowSync.rolledBackAt) + ) + ) +} + +/** Resolves one baseline per requested pair before looking up its possibly deleted snapshot. */ +export async function loadForkWorkflowComparisons( + executor: DbOrTx, + childWorkspaceId: string, + items: readonly { + sourceWorkflowId: string + targetWorkflowId: string + mode: 'create' | 'replace' + }[], + sourceVersions: ReadonlyMap +): Promise> { + if (!items.length) return new Map() + const pairs = sql.join( + items.map((item) => sql`(${item.sourceWorkflowId}::text, ${item.targetWorkflowId}::text)`), + sql`, ` + ) + const rows = await executor.execute<{ + sourceWorkflowId: string + sourceDeploymentVersionId: string | null + version: number | null + }>(sql` + SELECT pair.source_id AS "sourceWorkflowId", baseline.source_deployment_version_id AS "sourceDeploymentVersionId", version.version + FROM (VALUES ${pairs}) AS pair(source_id, target_id) + LEFT JOIN LATERAL ( + SELECT source_deployment_version_id FROM ${workspaceForkWorkflowSync} + WHERE child_workspace_id = ${childWorkspaceId} + AND source_workflow_id = pair.source_id AND target_workflow_id = pair.target_id + AND activated_at IS NOT NULL AND rolled_back_at IS NULL + ORDER BY sequence DESC LIMIT 1 + ) baseline ON true + LEFT JOIN ${workflowDeploymentVersion} version + ON version.id = baseline.source_deployment_version_id AND version.workflow_id = pair.source_id + `) + const baselines = new Map(rows.map((row) => [row.sourceWorkflowId, row])) + return new Map( + items.map((item) => { + const target = sourceVersions.get(item.sourceWorkflowId) + if (!target) throw new Error('Sync comparison is missing its admitted source version') + const baseline = baselines.get(item.sourceWorkflowId) + const current = { id: target.id, version: target.version } + let comparison: ForkWorkflowComparison + if (item.mode === 'create') { + comparison = { status: 'unavailable', reason: 'new_workflow', target: current } + } else if (!baseline?.sourceDeploymentVersionId) { + comparison = { status: 'unavailable', reason: 'no_baseline', target: current } + } else if (baseline.version === null) { + comparison = { status: 'unavailable', reason: 'missing_baseline', target: current } + } else { + comparison = { + status: 'available', + base: { id: baseline.sourceDeploymentVersionId, version: baseline.version }, + target: current, + } + } + return [item.sourceWorkflowId, comparison] + }) + ) +} diff --git a/apps/sim/executor/handlers/pi/cloud/cli-runtime.test.ts b/apps/sim/executor/handlers/pi/cloud/cli-runtime.test.ts new file mode 100644 index 00000000000..08fb344da10 --- /dev/null +++ b/apps/sim/executor/handlers/pi/cloud/cli-runtime.test.ts @@ -0,0 +1,84 @@ +import { execFile } from 'node:child_process' +import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { promisify } from 'node:util' +import { describe, expect, it } from 'vitest' +import { PI_EVENT_FILTER_SOURCE } from '@/executor/handlers/pi/cloud/event-filter-source' +import { buildPiScript } from '@/executor/handlers/pi/cloud/shared' +import { PI_PACKAGE_VERSION } from '@/scripts/pi-sandbox-packages' + +const exec = promisify(execFile) + +async function invokeSandbox(version: string) { + const root = await mkdtemp(join(tmpdir(), 'sim-pi-cli-')) + const workspace = join(root, 'workspace') + const repo = join(workspace, 'repo') + const bin = join(root, 'bin') + await mkdir(repo, { recursive: true }) + await mkdir(bin) + await writeFile(join(workspace, 'pi-prompt.txt'), 'fixture prompt') + await writeFile(join(workspace, 'sim-pi-event-filter.mjs'), PI_EVENT_FILTER_SOURCE) + await writeFile( + join(bin, 'pi'), + `#!/bin/bash +if [ "$1" = "--version" ]; then + printf "%s\\n" "${version}" + exit 0 +fi +printf "%s" "$PI_CODING_AGENT_DIR" > "${root}/invoked" +printf "%s\\n" '{"type":"message_update","assistantMessageEvent":{"type":"text_delta","delta":"ok"}}' +exit 0 +`, + { mode: 0o700 } + ) + const script = buildPiScript().replaceAll('/workspace', workspace) + try { + const result = await exec('/bin/bash', ['-c', script], { + env: { + ...process.env, + PATH: `${bin}:${process.env.PATH}`, + PI_PROVIDER: 'fixture', + PI_MODEL: 'fixture', + PI_THINKING: 'off', + }, + }).then( + ({ stdout, stderr }) => ({ code: 0, stdout, stderr }), + (error: { code: number; stdout: string; stderr: string }) => error + ) + const invoked = await readFile(join(root, 'invoked'), 'utf8').catch(() => null) + const settings = invoked + ? await readFile(join(invoked, 'settings.json'), 'utf8').catch(() => null) + : null + const storedFiles = invoked ? await readdir(invoked).catch(() => []) : [] + return { ...result, invoked, settings, storedFiles, repo } + } finally { + await rm(root, { recursive: true, force: true }) + } +} + +describe('sandbox Pi runtime boundary', () => { + it.each(['0.80.10', 'unexpected-version'])( + 'rejects %s before starting the agent', + async (version) => { + const result = await invokeSandbox(version) + expect(result.code).not.toBe(0) + expect(result.invoked).toBeNull() + expect(result.stderr).toMatch(/rebuild|update/i) + expect(result.stderr).toContain(PI_PACKAGE_VERSION) + } + ) + + it('uses private settings outside the repository with warming off and no saved credentials', async () => { + const result = await invokeSandbox(PI_PACKAGE_VERSION) + expect(result.code).toBe(0) + expect(result.invoked).toBeTruthy() + expect(result.invoked?.startsWith(result.repo)).toBe(false) + expect(JSON.parse(result.settings ?? '{}')).toEqual({ cacheWarming: 'off' }) + expect(result.storedFiles).toEqual(['settings.json']) + expect(JSON.parse(result.stdout.trim())).toEqual({ + type: 'message_update', + assistantMessageEvent: { type: 'text_delta', delta: 'ok' }, + }) + }) +}) diff --git a/apps/sim/executor/handlers/pi/cloud/event-filter-source.ts b/apps/sim/executor/handlers/pi/cloud/event-filter-source.ts index 5456607eb78..6e2edb602c9 100644 --- a/apps/sim/executor/handlers/pi/cloud/event-filter-source.ts +++ b/apps/sim/executor/handlers/pi/cloud/event-filter-source.ts @@ -2,10 +2,9 @@ * The stdout filter the sandbox modes pipe the Pi CLI through, written at runtime like the search * extension and the review tools script next to it. * - * Pi's `--mode json` writes `JSON.stringify(event)` for every session event with no filtering, and - * `message_update` repeats the whole assistant message alongside each delta — so raw stdout grows - * with the square of the response length, and `tool_execution_end`, `turn_end`, and `agent_end` - * each add a full tool result, turn transcript, or run transcript on top of that. + * Pi 1.0 emits delta-only `message_update` events, but `tool_execution_end`, `turn_end`, and + * `agent_end` still include tool results and transcripts. The filter preserves Sim's event + * contract without retaining those cumulative payloads. * * The reduction has to happen in the sandbox because by the time Sim could drop the bytes they are * already retained: E2B's SDK accumulates every callback-delivered chunk internally, so its adapter diff --git a/apps/sim/executor/handlers/pi/cloud/review/backend.test.ts b/apps/sim/executor/handlers/pi/cloud/review/backend.test.ts index 2e7abe911df..1a9c63757fe 100644 --- a/apps/sim/executor/handlers/pi/cloud/review/backend.test.ts +++ b/apps/sim/executor/handlers/pi/cloud/review/backend.test.ts @@ -45,7 +45,7 @@ const mockAgentSession = { const sealedResourceLoader = { kind: 'sealed' } const mockSdk = { - SettingsManager: { inMemory: vi.fn(() => ({})) }, + SettingsManager: { inMemory: () => ({}) }, SessionManager: { inMemory: vi.fn(() => ({})) }, createAgentSession: mockCreateAgentSession, defineTool: vi.fn((tool) => tool), diff --git a/apps/sim/executor/handlers/pi/cloud/review/backend.ts b/apps/sim/executor/handlers/pi/cloud/review/backend.ts index 289416ce96b..f648700b33e 100644 --- a/apps/sim/executor/handlers/pi/cloud/review/backend.ts +++ b/apps/sim/executor/handlers/pi/cloud/review/backend.ts @@ -300,7 +300,7 @@ export const runCloudReviewPi: PiBackendRun = async (par ) } - const settingsManager = sdk.SettingsManager.inMemory() + const settingsManager = sdk.SettingsManager.inMemory({ cacheWarming: 'off' }) const resourceLoader = createSealedPiResourceLoader( sdk, buildReviewSystemPrompt(Boolean(searchTool)) diff --git a/apps/sim/executor/handlers/pi/cloud/shared.ts b/apps/sim/executor/handlers/pi/cloud/shared.ts index 6dcdbaf6ea3..a1bf348d2ae 100644 --- a/apps/sim/executor/handlers/pi/cloud/shared.ts +++ b/apps/sim/executor/handlers/pi/cloud/shared.ts @@ -9,6 +9,7 @@ import { getMaxExecutionTimeout } from '@/lib/core/execution-limits' import { resolvePiSandboxLifetimeMs } from '@/lib/execution/remote-sandbox/pi-lifetime' import { PI_EVENT_FILTER_PATH } from '@/executor/handlers/pi/cloud/event-filter-source' import { scrubPiSecrets } from '@/executor/handlers/pi/core/redaction' +import { PI_PACKAGE_VERSION } from '@/scripts/pi-sandbox-packages' export const REPO_DIR = '/workspace/repo' export const PROMPT_PATH = '/workspace/pi-prompt.txt' @@ -141,6 +142,10 @@ export const PUSH_SCRIPT = `cd ${REPO_DIR} * there first — skipping that write does not fall back to the raw stream, it fails the run on the * missing module. * + * Rejects stale images before starting the agent. Each invocation resets Sim's private agent + * directory outside the clone, so neither stored credentials nor cache-warming settings can leak + * between rounds. Model credentials remain environment-only. + * * Selects `/bin/bash` explicitly because `pipefail` is not portable to `/bin/sh`, and without it * the pipeline reports the filter's exit code rather than Pi's, so an upstream crash would read as * a clean run. Both dedicated Pi images are Debian-based and provide Bash, so provider @@ -165,7 +170,17 @@ export function buildPiScript( ? ' --no-extensions' : '' const extensionArgs = extensionPath ? ` -e ${extensionPath}` : '' - return `/bin/bash -o pipefail -c 'cd ${REPO_DIR} + return `/bin/bash -o pipefail -c 'set -e +if ! PI_INSTALLED_VERSION="$(pi --version 2>/dev/null)" || [ "$PI_INSTALLED_VERSION" != "${PI_PACKAGE_VERSION}" ]; then + printf "%s\\n" "Pi sandbox runtime must be ${PI_PACKAGE_VERSION}. Rebuild or update the configured Pi sandbox image before running this workflow." >&2 + exit 1 +fi +export PI_CODING_AGENT_DIR=/workspace/sim-pi-agent +(umask 077 +rm -rf "$PI_CODING_AGENT_DIR" +mkdir -p "$PI_CODING_AGENT_DIR" +printf "%s\\n" "{\\"cacheWarming\\":\\"off\\"}" > "$PI_CODING_AGENT_DIR/settings.json") +cd ${REPO_DIR} pi -p --mode json --provider "$PI_PROVIDER" --model "$PI_MODEL" --thinking "$PI_THINKING"${repositoryArgs}${extensionArgs} < ${PROMPT_PATH} | node ${PI_EVENT_FILTER_PATH}'` } diff --git a/apps/sim/executor/handlers/pi/core/pi-sdk.ts b/apps/sim/executor/handlers/pi/core/pi-sdk.ts index 30e43c61b54..251f4212a36 100644 --- a/apps/sim/executor/handlers/pi/core/pi-sdk.ts +++ b/apps/sim/executor/handlers/pi/core/pi-sdk.ts @@ -29,10 +29,9 @@ function isToolArguments(value: unknown): value is Record { * credentials from thrown and reported tool errors. Successful tool output is ordinary model * content and stays verbatim; Sim-secret projection is owned by the tool adapter's provenance. * - * A spec's `isError` is rethrown rather than reported in the result: Pi derives a call's error state - * solely from whether `execute` threw, so a resolved failure would reach the model as a successful - * tool call whose text happens to describe a failure. Throwing also matches Pi's own `bash`, which - * throws on a non-zero exit with the output appended, so the failure text survives either way. + * A spec's `isError` is rethrown with scrubbed text, preserving Sim's existing failure semantics. + * Pi 1.0 also accepts an explicit error result, but throwing keeps the same diagnostic boundary + * for reported failures and exceptions. * * Shared by both host-side backends: Local Dev converts its SSH, Sim, and search tools here, and * Review Code converts its search tool here alongside the review tools it builds directly. @@ -90,7 +89,9 @@ export function createSealedPiResourceLoader(sdk: PiSdk, systemPrompt: string): getThemes: () => ({ themes: [], diagnostics: [] }), getAgentsFiles: () => ({ agentsFiles: [] }), getSystemPrompt: () => systemPrompt, + getSystemPromptSource: () => undefined, getAppendSystemPrompt: () => [], + getAppendSystemPromptSources: () => [], extendResources: () => {}, reload: async () => {}, } diff --git a/apps/sim/executor/handlers/pi/local/backend.test.ts b/apps/sim/executor/handlers/pi/local/backend.test.ts index bc52090b63e..3dadd0c714d 100644 --- a/apps/sim/executor/handlers/pi/local/backend.test.ts +++ b/apps/sim/executor/handlers/pi/local/backend.test.ts @@ -39,6 +39,7 @@ const mockAgentSession = { } const mockSdk = { defineTool: vi.fn((tool) => tool), + SettingsManager: { inMemory: () => ({}) }, SessionManager: { inMemory: vi.fn(() => ({})) }, createAgentSession: mockCreateAgentSession, } diff --git a/apps/sim/executor/handlers/pi/local/backend.ts b/apps/sim/executor/handlers/pi/local/backend.ts index e9fc2541c20..c27af12822b 100644 --- a/apps/sim/executor/handlers/pi/local/backend.ts +++ b/apps/sim/executor/handlers/pi/local/backend.ts @@ -93,6 +93,7 @@ async function runLocalAgent( noTools: 'builtin', customTools, modelRuntime, + settingsManager: sdk.SettingsManager.inMemory({ cacheWarming: 'off' }), sessionManager: sdk.SessionManager.inMemory(isolatedDir), }) diff --git a/apps/sim/executor/handlers/pi/pi-handler.test.ts b/apps/sim/executor/handlers/pi/pi-handler.test.ts index fd5b41fd8f2..3261a760d6c 100644 --- a/apps/sim/executor/handlers/pi/pi-handler.test.ts +++ b/apps/sim/executor/handlers/pi/pi-handler.test.ts @@ -213,13 +213,17 @@ describe('PiBlockHandler', () => { expect(mockRunLocal).not.toHaveBeenCalled() }) - it('rejects an unavailable model before resolving credentials', async () => { + it('rejects an unavailable model with replacement guidance before credentials or remote setup', async () => { mockResolvePiModelId.mockReturnValue(undefined) await expect(handler.execute(ctx(), block, localInputs())).rejects.toThrow( - /not available.*installed Pi catalog/ + /not available.*Choose a supported Pi model/ ) expect(mockResolveKey).not.toHaveBeenCalled() + expect(mockRunLocal).not.toHaveBeenCalled() + expect(mockRunCloud).not.toHaveBeenCalled() + expect(mockRunCloudPlan).not.toHaveBeenCalled() + expect(mockRunCloudReview).not.toHaveBeenCalled() }) it('adds successful Function tool cost once to a non-streaming Local Dev result', async () => { diff --git a/apps/sim/executor/handlers/pi/pi-handler.ts b/apps/sim/executor/handlers/pi/pi-handler.ts index 97f674ed1c8..84e3e7527b2 100644 --- a/apps/sim/executor/handlers/pi/pi-handler.ts +++ b/apps/sim/executor/handlers/pi/pi-handler.ts @@ -222,7 +222,7 @@ export class PiBlockHandler implements BlockHandler { const piModel = resolvePiModelId(providerId, model) if (!piModel) { throw new Error( - `Pi model "${model}" is not available for provider "${providerId}" in the installed Pi catalog` + `Pi model "${model}" is not available for provider "${providerId}" in the installed Pi catalog. Choose a supported Pi model in the block settings.` ) } diff --git a/apps/sim/hooks/mcp/use-mcp-oauth-popup.ts b/apps/sim/hooks/mcp/use-mcp-oauth-popup.ts index 5fbd589bed2..b0ee6e6f0f0 100644 --- a/apps/sim/hooks/mcp/use-mcp-oauth-popup.ts +++ b/apps/sim/hooks/mcp/use-mcp-oauth-popup.ts @@ -125,13 +125,12 @@ export function useMcpOauthPopup({ workspaceId }: UseMcpOauthPopupProps) { ) useEffect(() => { - const pending = pendingFlows return () => { - for (const { timeout, poll } of pending.values()) { + for (const { timeout, poll } of pendingFlows.values()) { window.clearTimeout(timeout) if (poll !== undefined) window.clearInterval(poll) } - pending.clear() + pendingFlows.clear() } }, []) diff --git a/apps/sim/hooks/queries/utils/fetch-deployment-version-state.ts b/apps/sim/hooks/queries/utils/fetch-deployment-version-state.ts index 19c6c42b8b9..d31485fa53e 100644 --- a/apps/sim/hooks/queries/utils/fetch-deployment-version-state.ts +++ b/apps/sim/hooks/queries/utils/fetch-deployment-version-state.ts @@ -1,5 +1,8 @@ import { requestJson } from '@/lib/api/client/request' -import { getDeploymentVersionStateContract } from '@/lib/api/contracts/deployments' +import { + type DeploymentVersionStateQuery, + getDeploymentVersionStateContract, +} from '@/lib/api/contracts/deployments' import type { WorkflowState } from '@/stores/workflows/workflow/types' /** @@ -8,10 +11,12 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types' export async function fetchDeploymentVersionState( workflowId: string, version: number, - signal?: AbortSignal + signal?: AbortSignal, + expectedDeploymentVersionId?: DeploymentVersionStateQuery['expectedDeploymentVersionId'] ): Promise { const data = await requestJson(getDeploymentVersionStateContract, { params: { id: workflowId, version }, + query: { expectedDeploymentVersionId }, signal, }) if (!data.deployedState) { diff --git a/apps/sim/hooks/queries/utils/workflow-keys.ts b/apps/sim/hooks/queries/utils/workflow-keys.ts index 5121870b3aa..f656d296141 100644 --- a/apps/sim/hooks/queries/utils/workflow-keys.ts +++ b/apps/sim/hooks/queries/utils/workflow-keys.ts @@ -6,8 +6,17 @@ export const workflowKeys = { list: (workspaceId: string | undefined, scope: WorkflowQueryScope = 'active') => [...workflowKeys.lists(), workspaceId ?? '', scope] as const, deploymentVersions: () => [...workflowKeys.all, 'deploymentVersion'] as const, - deploymentVersion: (workflowId: string | undefined, version: number | undefined) => - [...workflowKeys.deploymentVersions(), workflowId ?? '', version ?? 0] as const, + deploymentVersion: ( + workflowId: string | undefined, + version: number | undefined, + expectedDeploymentVersionId?: string + ) => + [ + ...workflowKeys.deploymentVersions(), + workflowId ?? '', + version ?? 0, + expectedDeploymentVersionId ?? '', + ] as const, states: () => [...workflowKeys.all, 'state'] as const, state: (workflowId: string | undefined) => [...workflowKeys.states(), workflowId ?? ''] as const, } diff --git a/apps/sim/hooks/queries/workflows.ts b/apps/sim/hooks/queries/workflows.ts index 3bf98b2dce6..b1c1f770164 100644 --- a/apps/sim/hooks/queries/workflows.ts +++ b/apps/sim/hooks/queries/workflows.ts @@ -565,12 +565,21 @@ export function useDeleteWorkflowMutation() { }) } -export function useDeploymentVersionState(workflowId: string | null, version: number | null) { +export function useDeploymentVersionState( + workflowId: string | null, + version: number | null, + expectedDeploymentVersionId?: string +) { return useQuery({ - queryKey: workflowKeys.deploymentVersion(workflowId ?? undefined, version ?? undefined), + queryKey: workflowKeys.deploymentVersion( + workflowId ?? undefined, + version ?? undefined, + expectedDeploymentVersionId + ), queryFn: workflowId && version !== null - ? ({ signal }) => fetchDeploymentVersionState(workflowId, version, signal) + ? ({ signal }) => + fetchDeploymentVersionState(workflowId, version, signal, expectedDeploymentVersionId) : skipToken, staleTime: WORKFLOW_DEPLOYMENT_VERSION_STATE_STALE_TIME, }) diff --git a/apps/sim/lib/api/contracts/deployments.ts b/apps/sim/lib/api/contracts/deployments.ts index 8da43bc4a79..2efce206ed1 100644 --- a/apps/sim/lib/api/contracts/deployments.ts +++ b/apps/sim/lib/api/contracts/deployments.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import { versionNumberPathSchema } from '@/lib/api/contracts/primitives' +import { nonEmptyIdSchema, versionNumberPathSchema } from '@/lib/api/contracts/primitives' import { defineRouteContract } from '@/lib/api/contracts/types' import { workflowIdParamsSchema } from '@/lib/api/contracts/workflows' import { @@ -352,10 +352,16 @@ export const listDeploymentVersionsContract = defineRouteContract({ }, }) +const deploymentVersionStateQuerySchema = z.object({ + expectedDeploymentVersionId: nonEmptyIdSchema.optional(), +}) +export type DeploymentVersionStateQuery = z.input + export const getDeploymentVersionStateContract = defineRouteContract({ method: 'GET', path: '/api/workflows/[id]/deployments/[version]', params: deploymentVersionParamsSchema, + query: deploymentVersionStateQuerySchema, response: { mode: 'json', schema: z.object({ diff --git a/apps/sim/lib/api/contracts/mothership-assistant-tools.ts b/apps/sim/lib/api/contracts/mothership-assistant-tools.ts index 1447831aa29..60a9db7177d 100644 --- a/apps/sim/lib/api/contracts/mothership-assistant-tools.ts +++ b/apps/sim/lib/api/contracts/mothership-assistant-tools.ts @@ -4,11 +4,6 @@ import { LIVE_SEARCH_PROVIDER_IDS } from '@/lib/sim-search/live/provider-catalog export const liveSearchProviderSchema = z.enum(LIVE_SEARCH_PROVIDER_IDS) export type LiveSearchProvider = z.output -export const SEARCH_TERMS_REQUIRED = { - notion: 'Notion requires search terms. Add keywords or a concise question.', - lucid: 'Lucid requires search terms. Add document-title keywords or a literal shape-text query.', -} as const - /** * Native queries one call may send to the same provider account. Alternatives run as separate * provider searches and fuse into one ranking, so the bound keeps a call within the provider's @@ -54,6 +49,12 @@ export const nativeSearchQuerySchema = z accountId: z.string().min(1).max(200).optional(), kind: nativeSearchKindSchema.optional(), project: z.string().min(1).max(300).optional(), + browse: z + .enum(['folder', 'private', 'shared', 'favorites', 'recent']) + .optional() + .describe( + 'Queryless discovery: Lucid folder lists one folder page (omit project for root; otherwise use a returned numeric folder ID). Notion private/shared list sidebar pages, favorites lists pinned pages, recent lists recently viewed pages, not recently modified pages. These lists are not an exhaustive workspace inventory. Follow the returned cursor with the same account, browse mode, project, filters and topK.' + ), cursor: z.string().max(4000).optional(), termClauses: z.array(z.string().max(500)).max(10).optional(), modifiers: z.string().max(1000).optional(), @@ -72,11 +73,32 @@ export const nativeSearchQuerySchema = z : `${input.provider} does not support kind selection.`, }) } - if ((input.provider === 'notion' || input.provider === 'lucid') && !input.query) + if ( + input.browse && + (input.query || + input.modifiers || + input.keywordOnly || + input.termClauses?.length || + (input.browse === 'folder' ? input.provider !== 'lucid' : input.provider !== 'notion') || + (input.provider === 'notion' && input.project)) + ) + context.addIssue({ + code: 'custom', + path: ['browse'], + message: + 'Browse requires an empty query and a supported provider mode; Notion lists cannot be scoped to a page.', + }) + if (input.browse === 'folder' && input.project && !/^[1-9]\d{0,14}$/.test(input.project)) + context.addIssue({ + code: 'custom', + path: ['project'], + message: 'Lucid folder browsing requires a numeric folder ID returned by the provider.', + }) + if (input.provider === 'lucid' && !input.query && !input.browse) context.addIssue({ code: 'custom', path: ['query'], - message: SEARCH_TERMS_REQUIRED[input.provider], + message: 'Lucid requires title keywords or explicit browse: folder.', }) }) export type NativeSearchQuery = z.output @@ -113,7 +135,9 @@ export const nativeSearchQueriesSchema = z addIssue('Duplicate native query.') else if ( hasSearchKinds(query.provider) && - earlier.some((previous) => !previous.kind || !query.kind) + earlier.some( + (previous) => !previous.browse && !query.browse && (!previous.kind || !query.kind) + ) ) addIssue( 'A GitHub, GitLab, HubSpot, Lucid, Google Meet, or Zoom query without a kind already searches its default kinds; give each query on this account a kind.' @@ -134,6 +158,10 @@ export const liveSearchAccountStatusSchema = z.object({ status: z.enum(['ok', 'partial', 'reconnect', 'rate_limited', 'unavailable', 'timeout']), message: z.string().optional(), nextCursor: z.string().optional(), + folders: z + .array(z.object({ id: z.string(), name: z.string() })) + .max(10) + .optional(), retryAfterSeconds: z.number().optional(), }) export type LiveSearchAccountStatus = z.output @@ -198,7 +226,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema nativeQueries: nativeSearchQueriesSchema .optional() .describe( - `Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound or sortBy newest/oldest; Notion and Lucid always require search terms. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple queries on one account, each need a kind, which may repeat. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid searches titles with no search continuation; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.` + `Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound, sortBy newest/oldest, or explicit browse mode. Lucid browse folder lists root or a numeric folder project; Notion browse private/shared/favorites/recent lists sidebar pages. Recent means viewed, not modified. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple content queries on one account, each need a kind, which may repeat; explicit browse queries may select distinct folders or sidebar sections without a kind. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid title search has no continuation; folder browsing is paginated and returns child folders in account coverage; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.` ), query: z .string() @@ -206,7 +234,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema .max(2000) .default('') .describe( - 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported; Notion requires search terms.' + 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported, or use an explicit native browse mode. Notion date-only search depends on plan capabilities.' ), topK: z .number() @@ -227,7 +255,11 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema input.sortBy === 'newest' || input.sortBy === 'oldest' ) - if (!input.query && !input.nativeQueries?.some((query) => query.query) && !bounded) + if ( + !input.query && + !input.nativeQueries?.some((query) => query.query || query.browse) && + !bounded + ) context.addIssue({ code: 'custom', path: ['query'], @@ -243,7 +275,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema path: ['endDate'], message: 'endDate must be after startDate.', }) - if (input.nativeQueries?.some((query) => !query.query) && !bounded) + if (input.nativeQueries?.some((query) => !query.query && !query.browse) && !bounded) context.addIssue({ code: 'custom', path: ['nativeQueries'], diff --git a/apps/sim/lib/api/contracts/tools/file.ts b/apps/sim/lib/api/contracts/tools/file.ts index 9829ed78d50..599f3894312 100644 --- a/apps/sim/lib/api/contracts/tools/file.ts +++ b/apps/sim/lib/api/contracts/tools/file.ts @@ -209,11 +209,12 @@ export const fileManageContentBodySchema = z fileId: fileIdSelectionSchema.optional(), fileInput: z.unknown().optional(), /** - * First line to return, 1-based. Applied to each selected file separately, - * because a selection can be several files and one running offset across - * them would depend on an ordering the caller cannot see. + * First line to return, 1-based; 0 also starts at the first line, so a + * zero-based caller's first window reads instead of failing. Applied to each + * selected file separately, because a selection can be several files and one + * running offset across them would depend on an ordering the caller cannot see. */ - offset: z.number().int().min(1, 'offset starts at line 1').optional(), + offset: z.number().int().min(0, 'offset cannot be negative').optional(), /** How many lines to return from `offset`. Absent reads to the end. */ limit: z.number().int().min(1, 'limit must be at least 1').optional(), }) diff --git a/apps/sim/lib/api/contracts/v2/credentials.ts b/apps/sim/lib/api/contracts/v2/credentials.ts index 500c8b1d8ed..cc583dc8d5a 100644 --- a/apps/sim/lib/api/contracts/v2/credentials.ts +++ b/apps/sim/lib/api/contracts/v2/credentials.ts @@ -253,6 +253,7 @@ export const V2_OAUTH_CONNECTION_PROVIDER_IDS = [ 'microsoft-dataverse', 'microsoft-excel', 'microsoft-planner', + 'microsoft-powerbi', 'microsoft-teams', 'microsoft-word', 'outlook', diff --git a/apps/sim/lib/api/contracts/v2/files.ts b/apps/sim/lib/api/contracts/v2/files.ts index 8e36c324a53..9e8a8003ca5 100644 --- a/apps/sim/lib/api/contracts/v2/files.ts +++ b/apps/sim/lib/api/contracts/v2/files.ts @@ -853,9 +853,11 @@ export const v2ReadFileTextQuerySchema = z offset: z.coerce .number() .int() - .min(1, 'offset starts at line 1') + .min(0, 'offset cannot be negative') .optional() - .describe('First line to return, 1-based. Absent starts at the first line.'), + .describe( + 'First line to return, 1-based; 0 also starts at the first line. Absent starts at the first line.' + ), limit: z.coerce .number() .int() diff --git a/apps/sim/lib/api/contracts/v2/workspace-fork.ts b/apps/sim/lib/api/contracts/v2/workspace-fork.ts index 647acc15872..6d02f4bf1be 100644 --- a/apps/sim/lib/api/contracts/v2/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/v2/workspace-fork.ts @@ -11,6 +11,7 @@ import { forkMappableResourceTypeSchema, forkRemapKindSchema, forkResourceSelectionSchema, + forkWorkflowComparisonSchema, promoteCopyResourcesSchema, } from '@/lib/api/contracts/workspace-fork' @@ -336,6 +337,11 @@ export const v2SyncPreviewDataSchema = z 'Existing target workflow identifier; absent when apply will create a new target.' ), name: z.string().max(1024).describe('Display name of the workflow or workspace.'), + comparison: forkWorkflowComparisonSchema + .optional() + .describe( + 'Last successfully synced source deployment compared with the source deployment pinned by this preview. Absent for archive actions.' + ), }) ) .max(2000) diff --git a/apps/sim/lib/api/contracts/workspace-fork.ts b/apps/sim/lib/api/contracts/workspace-fork.ts index 3f8176b97c2..14a5ad93f9c 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.ts @@ -1,5 +1,10 @@ import { z } from 'zod' -import { nonEmptyIdSchema, workspaceIdSchema } from '@/lib/api/contracts/primitives' +import { + nonEmptyIdSchema, + versionNumberSchema, + workflowIdSchema, + workspaceIdSchema, +} from '@/lib/api/contracts/primitives' import { defineRouteContract } from '@/lib/api/contracts/types' import { workspaceSchema } from '@/lib/api/contracts/workspaces' import { WORKFLOW_RESOURCE_KINDS } from '@/lib/workflows/references/types' @@ -314,13 +319,55 @@ export const forkUnmappedReferenceSchema = z.object({ blockName: z.string().optional(), }) -export const forkWorkflowChangeSchema = z.object({ - action: z.enum(['update', 'create', 'archive']), +const forkSourceVersionSchema = z.object({ + id: nonEmptyIdSchema.describe('Exact source deployment snapshot identifier.'), + version: versionNumberSchema.describe('Saved version number in the source workflow.'), +}) + +export const forkWorkflowComparisonSchema = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('available').describe('Both exact source snapshots are available.'), + base: forkSourceVersionSchema.describe( + 'Source deployment last successfully synced to this destination.' + ), + target: forkSourceVersionSchema.describe('Pinned source deployment that this sync would copy.'), + }), + z.object({ + status: z.literal('unavailable').describe('The source baseline cannot be compared.'), + reason: z + .enum(['new_workflow', 'no_baseline', 'missing_baseline']) + .describe( + 'New destination workflow, no recorded successful sync, or deleted baseline snapshot.' + ), + target: forkSourceVersionSchema.describe('Pinned source deployment that this sync would copy.'), + }), +]) +export type ForkWorkflowComparison = z.output + +const forkSourceVersionAssertionSchema = z + .object({ + workflowId: workflowIdSchema, + deploymentVersionId: nonEmptyIdSchema, + }) + .strict() + +const forkWorkflowNamesSchema = z.object({ /** Workflow name in the workspace the modal is open in. */ currentName: z.string(), /** Workflow name in the sync-partner workspace (differs from `currentName` after a rename). */ otherName: z.string(), }) +export const forkWorkflowChangeSchema = z.discriminatedUnion('action', [ + forkWorkflowNamesSchema.extend({ + action: z.enum(['update', 'create']), + sourceWorkflowId: workflowIdSchema, + comparison: forkWorkflowComparisonSchema, + }), + forkWorkflowNamesSchema.extend({ + action: z.literal('archive'), + targetWorkflowId: workflowIdSchema, + }), +]) /** * A configured selector field (Gmail label, Slack channel, KB document, ...) that @@ -588,6 +635,7 @@ export const getForkDiffContract = defineRouteContract({ schema: z.object({ sourceWorkspaceId: z.string(), targetWorkspaceId: z.string(), + sourceVersions: z.array(forkSourceVersionAssertionSchema).max(1000), willUpdate: z.number().int(), willCreate: z.number().int(), willArchive: z.number().int(), @@ -693,6 +741,8 @@ export type PromoteCopyResources = z.input export const promoteForkBodySchema = z.object({ otherWorkspaceId: workspaceIdSchema, direction: forkDirectionSchema, + mappings: updateForkMappingBodySchema.shape.entries.optional(), + expectedSourceVersions: z.array(forkSourceVersionAssertionSchema).max(1000).optional(), /** * The full stored mapping of dependent-field values; persisted to * `workspace_fork_dependent_value` and applied to the target blocks verbatim. Omitting the diff --git a/apps/sim/lib/auth/connectors/providers.ts b/apps/sim/lib/auth/connectors/providers.ts index f7c6e8baae0..f5afe09a82a 100644 --- a/apps/sim/lib/auth/connectors/providers.ts +++ b/apps/sim/lib/auth/connectors/providers.ts @@ -870,6 +870,28 @@ export function buildConnectorProviders(): GenericOAuthConfig[] { }, }, + { + providerId: 'microsoft-powerbi', + clientId: env.MICROSOFT_CLIENT_ID as string, + clientSecret: env.MICROSOFT_CLIENT_SECRET as string, + authorizationUrl: 'https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/organizations/oauth2/v2.0/token', + scopes: getCanonicalScopesForProvider('microsoft-powerbi'), + responseType: 'code', + accessType: 'offline', + authentication: 'basic', + pkce: true, + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/microsoft-powerbi`, + getUserInfo: async (tokens) => { + const canonicalScopes = getCanonicalScopesForProvider('microsoft-powerbi') + tokens.scopes = tokens.scopes?.map((scope) => { + const qualified = `https://analysis.windows.net/powerbi/api/${scope}` + return canonicalScopes.includes(qualified) ? qualified : scope + }) + return getMicrosoftUserInfoFromIdToken(tokens, 'microsoft-powerbi') + }, + }, + { providerId: 'outlook', clientId: env.MICROSOFT_CLIENT_ID as string, diff --git a/apps/sim/lib/block-metadata/names.generated.ts b/apps/sim/lib/block-metadata/names.generated.ts index 87708ddd386..3f517a01c3c 100644 --- a/apps/sim/lib/block-metadata/names.generated.ts +++ b/apps/sim/lib/block-metadata/names.generated.ts @@ -209,6 +209,7 @@ export const BLOCK_NAMES: Readonly> = { polymarket: 'Polymarket', postgresql: 'PostgreSQL', posthog: 'PostHog', + powerbi: 'Power BI', profound: 'Profound', prospeo: 'Prospeo', pulse_v2: 'Pulse', @@ -307,6 +308,7 @@ export const BLOCK_NAMES: Readonly> = { workday: 'Workday', workflow_input: 'Workflow', x: 'X', + youcom: 'You.com', youtube: 'YouTube', zendesk: 'Zendesk', zep: 'Zep', diff --git a/apps/sim/lib/cleanup/chat-cleanup.test.ts b/apps/sim/lib/cleanup/chat-cleanup.test.ts new file mode 100644 index 00000000000..ef054b4696b --- /dev/null +++ b/apps/sim/lib/cleanup/chat-cleanup.test.ts @@ -0,0 +1,101 @@ +import { copilotChats, copilotMessages, workspaceFiles } from '@sim/db/schema' +import { queueTableRows, resetDbChainMock } from '@sim/testing/mocks/database.mock' +import { storageServiceMockFns } from '@sim/testing/mocks/storage-service.mock' +import { uploadsMock, uploadsMockFns } from '@sim/testing/mocks/uploads.mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('@/lib/uploads', () => uploadsMock) + +import { prepareChatCleanup } from '@/lib/cleanup/chat-cleanup' +import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-key' + +const chatId = '3f0c2a52-8a43-4d4b-9b5f-0b3c7a1e2d10' + +function attachment(key: string) { + return { id: 'wf_x', key, filename: 'x.png', media_type: 'image/png', size: 1 } +} + +/** + * Purges one deleted chat whose message rows are `messages`, while `remaining` are the message + * rows other chats of the organization still hold; returns the deleted keys by context. + */ +async function purge( + messages: Record[], + remaining: Record[] = [] +) { + queueTableRows(workspaceFiles, []) + queueTableRows( + copilotMessages, + messages.map((content) => ({ chatId, content })) + ) + const cleanup = await prepareChatCleanup([chatId], 'test') + queueTableRows(copilotChats, []) + queueTableRows( + copilotMessages, + remaining.map((content) => ({ content })) + ) + await cleanup.execute() + return Object.fromEntries( + storageServiceMockFns.mockDeleteFiles.mock.calls.map(([keys, context]) => [context, keys]) + ) +} + +describe('chat purge storage', () => { + beforeEach(() => { + resetDbChainMock() + uploadsMockFns.mockIsUsingCloudStorage.mockReturnValue(true) + storageServiceMockFns.mockDeleteFiles.mockReset() + storageServiceMockFns.mockDeleteFiles.mockResolvedValue({ deleted: 1, failed: [] }) + }) + + it('never deletes a workspace attachment as copilot storage', async () => { + // A fork carries its source's key when the file's copy failed or the file was deleted, and + // the copilot bucket can be the workspace bucket, so a workspace key here is another chat's file. + const deleted = await purge([ + { + role: 'user', + content: 'look', + fileAttachments: [ + attachment('workspace/ws-1/1700-abc-shared.png'), + attachment('copilot/1234/legacy.png'), + ], + }, + ]) + expect(deleted).toEqual({ copilot: ['copilot/1234/legacy.png'] }) + }) + + it('deletes an organization attachment only once no remaining chat references it', async () => { + const shared = 'assistant/org-1/user-1/u1/shared.png' + const own = 'assistant/org-1/user-1/u2/own.png' + const message = { + role: 'user', + content: 'look', + fileAttachments: [attachment(shared), attachment(own)], + } + // A fork of this chat still holds the shared upload. + const deleted = await purge( + [message], + [{ role: 'user', content: 'fork', fileAttachments: [attachment(shared)] }] + ) + expect(deleted).toEqual({ mothership: [own] }) + }) + + it('deletes the inline images an assistant message published', async () => { + const deleted = await purge([ + { + role: 'assistant', + requestId: 'req-1', + content: 'Here: ![chart](files/chart.png) and `![code](files/no.png)`', + contentBlocks: [{ type: 'text', content: '![other](/tmp/out.png)' }], + }, + { role: 'user', requestId: 'req-2', content: '![u](files/u.png)' }, + { role: 'assistant', content: '![x](files/x.png)' }, + ]) + expect(deleted).toEqual({ + mothership: [ + inlineChatImageKey(chatId, 'req-1', 'files/chart.png'), + inlineChatImageKey(chatId, 'req-1', '/tmp/out.png'), + ], + }) + }) +}) diff --git a/apps/sim/lib/cleanup/chat-cleanup.ts b/apps/sim/lib/cleanup/chat-cleanup.ts index b3910219bb3..ab9745dd093 100644 --- a/apps/sim/lib/cleanup/chat-cleanup.ts +++ b/apps/sim/lib/cleanup/chat-cleanup.ts @@ -2,11 +2,17 @@ import { dbFor } from '@sim/db' import { copilotChats, copilotMessages, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { chunkArray } from '@sim/utils/helpers' -import { and, inArray, isNull } from 'drizzle-orm' +import { isRecordLike } from '@sim/utils/object' +import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm' import { env } from '@/lib/core/config/env' +import { + inlineChatImageKey, + inlineChatImageReferences, +} from '@/lib/mothership/chat/inline-image-key' import { SIM_AGENT_API_URL } from '@/lib/mothership/constants' import type { StorageContext } from '@/lib/uploads' import { isUsingCloudStorage, StorageService } from '@/lib/uploads' +import { tryInferContextFromKey } from '@/lib/uploads/utils/file-utils' const logger = createLogger('ChatCleanup') @@ -30,12 +36,63 @@ interface FileRef { key: string context: ChatScopedContext chatId: string + /** + * An organization Chat attachment (`assistant//…`): no `workspace_files` row owns it, + * and a fork carries the same key, so it is deleted only once no remaining chat of that + * organization references it. + */ + organizationId?: string +} + +/** The organization an `assistant//…` attachment key was uploaded under. */ +function organizationAttachmentOwner(key: string): string | undefined { + if (tryInferContextFromKey(key) !== 'mothership') return undefined + const [, organizationId] = key.split('/') + return organizationId || undefined +} + +/** + * The chat images an assistant message published under its request id, keyed by chat id, + * so they are purged with the chat. A row this cannot read as such a message has none. + */ +function inlineChatImageKeys(chatId: string, content: Record): string[] { + if (content.role !== 'assistant' || typeof content.requestId !== 'string') return [] + const published = inlineChatImageReferences({ + role: 'assistant', + requestId: content.requestId, + content: typeof content.content === 'string' ? content.content : '', + contentBlocks: Array.isArray(content.contentBlocks) + ? content.contentBlocks.flatMap((block) => + isRecordLike(block) && block.type === 'text' && typeof block.content === 'string' + ? [{ type: 'text' as const, content: block.content }] + : [] + ) + : undefined, + }) + if (!published) return [] + const keys: string[] = [] + for (const reference of published.references) { + try { + keys.push(inlineChatImageKey(chatId, published.requestId, reference)) + } catch { + // A chat or request id outside the key grammar never had an image stored under it. + } + } + return keys } /** - * Collect all file storage keys for the given chat IDs from two sources: + * Collect all file storage keys for the given chat IDs from three sources: * 1. workspaceFiles rows with chatId FK (chat-scoped contexts only) - * 2. fileAttachments[].key inside each copilot_messages.content + * 2. fileAttachments[].key inside each copilot_messages.content: copilot keys, and + * organization attachments under their own context (see {@link FileRef.organizationId}) + * 3. the chat-scoped inline images each assistant message published + * + * A workspace attachment is owned by its `workspace_files` row (source 1, or the workspace + * file lifecycle), never by the message: a fork carries the same attachment when its copy + * failed or its file was deleted, and the copilot bucket falls back to the workspace bucket + * on GCS (and may be configured to it on S3), so deleting such a key as copilot storage + * could delete a file another chat or the workspace still uses. */ export async function collectChatFiles(chatIds: string[]): Promise { const files: FileRef[] = [] @@ -77,6 +134,12 @@ export async function collectChatFiles(chatIds: string[]): Promise { for (const row of messageRows) { const msg = row.content if (!msg || typeof msg !== 'object') continue + for (const key of inlineChatImageKeys(row.chatId, msg as Record)) { + if (!seen.has(key)) { + seen.add(key) + files.push({ key, context: 'mothership', chatId: row.chatId }) + } + } const attachments = (msg as Record).fileAttachments if (!Array.isArray(attachments)) continue for (const attachment of attachments) { @@ -86,7 +149,12 @@ export async function collectChatFiles(chatIds: string[]): Promise { (attachment as Record).key ) { const key = (attachment as Record).key as string - if (!seen.has(key)) { + if (seen.has(key)) continue + const organizationId = organizationAttachmentOwner(key) + if (organizationId) { + seen.add(key) + files.push({ key, context: 'mothership', chatId: row.chatId, organizationId }) + } else if (tryInferContextFromKey(key) === 'copilot') { seen.add(key) files.push({ key, context: 'copilot', chatId: row.chatId }) } @@ -98,6 +166,55 @@ export async function collectChatFiles(chatIds: string[]): Promise { return files } +/** + * Organization attachment keys that a remaining chat still references, so they outlive the + * chats being purged. Runs after the caller deleted those chats' rows, so any match is + * another chat: a fork, or a chat that is only soft-deleted and may be restored. + */ +async function organizationAttachmentsStillReferenced(files: FileRef[]): Promise> { + const keysByOrganization = new Map() + for (const file of files) { + if (!file.organizationId) continue + const keys = keysByOrganization.get(file.organizationId) + if (keys) keys.push(file.key) + else keysByOrganization.set(file.organizationId, [file.key]) + } + const referenced = new Set() + for (const [organizationId, keys] of keysByOrganization) { + for (const chunk of chunkArray(keys, CHAT_FILE_COLLECT_CHUNK_SIZE)) { + const wanted = new Set(chunk) + const rows = await cleanupDb + .select({ content: copilotMessages.content }) + .from(copilotMessages) + .innerJoin(copilotChats, eq(copilotChats.id, copilotMessages.chatId)) + .where( + and( + eq(copilotChats.organizationId, organizationId), + or( + ...chunk.map( + (key) => + sql`${copilotMessages.content} @> ${JSON.stringify({ fileAttachments: [{ key }] })}::jsonb` + ) + ) + ) + ) + for (const { content } of rows) { + const attachments = isRecordLike(content) ? content.fileAttachments : undefined + if (!Array.isArray(attachments)) continue + for (const attachment of attachments) { + if ( + isRecordLike(attachment) && + typeof attachment.key === 'string' && + wanted.has(attachment.key) + ) + referenced.add(attachment.key) + } + } + } + } + return referenced +} + /** Groups files by storage context so each context can use one batch DELETE call. */ export async function deleteStorageFiles( files: FileRef[], @@ -220,7 +337,12 @@ export async function prepareChatCleanup( ) } const confirmedChatIds = chatIds.filter((id) => !survivors.has(id)) - const confirmedFiles = files.filter((file) => !survivors.has(file.chatId)) + const sharedKeys = await organizationAttachmentsStillReferenced( + files.filter((file) => !survivors.has(file.chatId)) + ) + const confirmedFiles = files.filter( + (file) => !survivors.has(file.chatId) && !sharedKeys.has(file.key) + ) // Call copilot backend if (confirmedChatIds.length > 0) { diff --git a/apps/sim/lib/core/errors/retryable-infrastructure.ts b/apps/sim/lib/core/errors/retryable-infrastructure.ts index 73051cb11c0..3890d19eb8c 100644 --- a/apps/sim/lib/core/errors/retryable-infrastructure.ts +++ b/apps/sim/lib/core/errors/retryable-infrastructure.ts @@ -23,6 +23,7 @@ const RETRYABLE_NETWORK_ERROR_CODES = new Set([ 'ECONNREFUSED', 'EPIPE', 'ERR_STREAM_PREMATURE_CLOSE', + 'EHOSTUNREACH', 'ENETDOWN', 'ENETRESET', 'ENETUNREACH', diff --git a/apps/sim/lib/integrations/credential-display.test.ts b/apps/sim/lib/integrations/credential-display.test.ts index a980857f7a0..b89865d2899 100644 --- a/apps/sim/lib/integrations/credential-display.test.ts +++ b/apps/sim/lib/integrations/credential-display.test.ts @@ -37,7 +37,7 @@ const EXPECTED_COVERAGE: Record = { 'attio-service-account': ['attio'], 'box-service-account': ['box'], 'calcom-service-account': ['cal-com'], - 'claude-platform-service-account': [], + 'claude-platform-service-account': ['claude-managed-agents'], 'clickup-service-account': ['clickup'], 'coda-service-account': ['coda'], 'github-app-installation': ['github'], diff --git a/apps/sim/lib/integrations/credential-visibility.server.test.ts b/apps/sim/lib/integrations/credential-visibility.server.test.ts index e478a4be9bd..566ce32c374 100644 --- a/apps/sim/lib/integrations/credential-visibility.server.test.ts +++ b/apps/sim/lib/integrations/credential-visibility.server.test.ts @@ -67,40 +67,56 @@ describe('integration credential visibility', () => { ]) }) - it('exposes Coda token credentials without OAuth while honoring integration policy and visibility', () => { - const catalog = resolveIntegrationAvailability({}) - expect(catalog.find((entry) => entry.type === 'coda')).toMatchObject({ - state: 'ready', - oauthAvailable: false, - serviceAccountAvailable: true, - }) - getIntegrationAvailabilityMock.mockReturnValue(catalog) - const service: OAuthServiceMetadata = { + it.each([ + { serviceId: 'coda', - providerId: 'coda', - serviceAccountProviderId: 'coda-service-account', - authType: 'service_account', + providerId: 'coda-service-account', + blockType: 'coda', name: 'Coda', - description: 'Coda token', - baseProvider: 'coda', - } - const identity = { providerId: 'coda-service-account', type: 'service_account' } as const - const visibility = (allowed: ReadonlySet | null, disabled: boolean) => - createIntegrationCredentialVisibility({ - allowedIntegrationTypes: allowed, - oauthServices: [service], - blockVisibility: { - revealed: new Set(), - previewTagged: new Set(), - disabled: new Set(disabled ? ['coda'] : []), - }, + }, + { + serviceId: 'claude-platform', + providerId: 'claude-platform-service-account', + blockType: 'managed_agent', + name: 'Claude Platform', + }, + ])( + 'exposes $name token credentials without OAuth while honoring integration policy and visibility', + ({ serviceId, providerId, blockType, name }) => { + const catalog = resolveIntegrationAvailability({}) + expect(catalog.find((entry) => entry.type === blockType)).toMatchObject({ + state: 'ready', + oauthAvailable: false, + serviceAccountAvailable: true, }) - expect(visibility(new Set(['coda']), false).isCredentialVisible(identity)).toBe(true) - expect(visibility(new Set(['slack_v2']), false).isCredentialVisible(identity)).toBe(false) - expect(visibility(null, true).isCredentialVisible(identity)).toBe(false) - getBlockMock.mockReturnValue({ type: 'coda', preview: true } as never) - expect(visibility(null, false).isCredentialVisible(identity)).toBe(false) - }) + getIntegrationAvailabilityMock.mockReturnValue(catalog) + const service: OAuthServiceMetadata = { + serviceId, + providerId: serviceId, + serviceAccountProviderId: providerId, + authType: 'service_account', + name, + description: `${name} token`, + baseProvider: serviceId, + } + const identity = { providerId, type: 'service_account' } as const + const visibility = (allowed: ReadonlySet | null, disabled: boolean) => + createIntegrationCredentialVisibility({ + allowedIntegrationTypes: allowed, + oauthServices: [service], + blockVisibility: { + revealed: new Set(), + previewTagged: new Set(), + disabled: new Set(disabled ? [blockType] : []), + }, + }) + expect(visibility(new Set([blockType]), false).isCredentialVisible(identity)).toBe(true) + expect(visibility(new Set(['slack_v2']), false).isCredentialVisible(identity)).toBe(false) + expect(visibility(null, true).isCredentialVisible(identity)).toBe(false) + getBlockMock.mockReturnValue({ type: blockType, preview: true } as never) + expect(visibility(null, false).isCredentialVisible(identity)).toBe(false) + } + ) it('applies the integration allowlist to OAuth and service-account credentials', () => { const visibility = createIntegrationCredentialVisibility({ diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 0aea283bda2..34c62ec06c2 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -191,6 +191,7 @@ import { PolymarketIcon, PostgresIcon, PosthogIcon, + PowerBIIcon, ProfoundIcon, ProspeoIcon, PulseIcon, @@ -271,6 +272,7 @@ import { WordpressIcon, WorkdayIcon, xIcon, + YouComIcon, YouTubeIcon, ZendeskIcon, ZepIcon, @@ -486,6 +488,7 @@ export const blockTypeToIconMap: Record = { polymarket: PolymarketIcon, postgresql: PostgresIcon, posthog: PosthogIcon, + powerbi: PowerBIIcon, profound: ProfoundIcon, prospeo: ProspeoIcon, pulse_v2: PulseIcon, @@ -573,6 +576,7 @@ export const blockTypeToIconMap: Record = { wordpress: WordpressIcon, workday: WorkdayIcon, x: xIcon, + youcom: YouComIcon, youtube: YouTubeIcon, zendesk: ZendeskIcon, zep: ZepIcon, diff --git a/apps/sim/lib/internal/file/operations.test.ts b/apps/sim/lib/internal/file/operations.test.ts index 5e3e0ce1195..8488db23333 100644 --- a/apps/sim/lib/internal/file/operations.test.ts +++ b/apps/sim/lib/internal/file/operations.test.ts @@ -808,6 +808,21 @@ describe('file manage operations', () => { }, }) }) + it('reads offset 0 as the first line of each selected file', async () => { + const response = await POST( + createMockRequest('POST', { + operation: 'content', + workspaceId: 'workspace-1', + fileId: 'file-1', + offset: 0, + limit: 1, + }) + ) + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ + data: { contents: ['content:file-1.txt'], lineRanges: [{ offset: 1, lineCount: 1 }] }, + }) + }) it('does not bypass complete extraction limits with a raw-text fallback', async () => { vi.mocked(isSupportedFileType).mockReturnValueOnce(true) vi.mocked(parseBuffer).mockRejectedValueOnce( diff --git a/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts b/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts new file mode 100644 index 00000000000..f2e61a91623 --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts @@ -0,0 +1,235 @@ +import { writeFileSync } from 'node:fs' +import { db } from '@sim/db' +import { document, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { eq, inArray, sql } from 'drizzle-orm' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { + createKnowledgeAclFixtureIds, + seedKnowledgeAclFixture, +} from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { type KnowledgeAccessScope, WORKSPACE_ACCESS_TOKENS } from '@/lib/knowledge/access/types' +import { getWorkspaceKnowledgeBases } from '@/lib/knowledge/service' + +/** + * A small KB page must not count the rest of its workspace or other tenants before applying + * its limit. Real query plans catch this even when warm caches hide it from a timing test. + * The same read must retain ACL/lifecycle filtering, empty bases, and keyset continuity. + */ +const ids = createKnowledgeAclFixtureIds() +const foreign = createKnowledgeAclFixtureIds() +const manyBases = createKnowledgeAclFixtureIds() +const offPageId = generateId() +const emptyId = generateId() +const archivedId = generateId() +const access: KnowledgeAccessScope = { kind: 'workspace', tokens: WORKSPACE_ACCESS_TOKENS } +const reports: Array> = [] + +interface CapturedQuery { + query: string + parameters: NonNullable[1]> +} + +interface ExplainNode { + 'Relation Name'?: string + 'Index Name'?: string + 'Actual Rows': number + 'Actual Loops': number + 'Rows Removed by Filter'?: number + 'Rows Removed by Index Recheck'?: number + Plans?: ExplainNode[] +} + +function documentVisits(node: ExplainNode): number { + const readsDocuments = + node['Relation Name'] === 'document' || node['Index Name']?.startsWith('doc_') + const own = readsDocuments + ? (node['Actual Rows'] + + (node['Rows Removed by Filter'] ?? 0) + + (node['Rows Removed by Index Recheck'] ?? 0)) * + node['Actual Loops'] + : 0 + return own + (node.Plans ?? []).reduce((total, child) => total + documentVisits(child), 0) +} + +beforeAll(async () => { + await seedKnowledgeAclFixture(ids, { connectorType: 'google_drive' }) + await seedKnowledgeAclFixture(foreign, { connectorType: 'google_drive' }) + await db + .update(knowledgeBase) + .set({ name: 'A small', createdAt: new Date('2026-01-01') }) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + await db.insert(knowledgeBase).values([ + { + id: emptyId, + workspaceId: ids.workspaceId, + userId: ids.aliceId, + name: 'B empty', + createdAt: new Date('2026-01-02'), + }, + { + id: offPageId, + workspaceId: ids.workspaceId, + userId: ids.aliceId, + name: 'C large', + createdAt: new Date('2026-01-03'), + }, + { + id: archivedId, + workspaceId: ids.workspaceId, + userId: ids.aliceId, + name: 'D archived', + deletedAt: new Date(), + }, + ]) + await db.insert(document).values( + [ + { tokenCount: 7 }, + { tokenCount: 11 }, + { tokenCount: 100, acl: ['u:hidden@fixture.test'] }, + { tokenCount: 100, archivedAt: new Date() }, + { tokenCount: 100, deletedAt: new Date() }, + { tokenCount: 100, userExcluded: true }, + ].map((row) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + filename: 'fixture.txt', + fileUrl: 'https://fixture.invalid/document', + fileSize: 1, + mimeType: 'text/plain', + acl: ['ws'], + ...row, + })) + ) + for (const baseId of [offPageId, foreign.knowledgeBaseId]) { + await db.execute(sql`INSERT INTO document + (id, knowledge_base_id, filename, file_url, file_size, mime_type, acl, token_count) + SELECT ${baseId} || '-' || n, ${baseId}, 'bulk.txt', 'https://fixture.invalid/bulk', + 1, 'text/plain', ARRAY['ws'], 1 FROM generate_series(1, 10000) AS n`) + } + await seedKnowledgeAclFixture(manyBases, { connectorType: 'google_drive' }) + await db.execute(sql`INSERT INTO knowledge_base (id, workspace_id, user_id, name) + SELECT ${manyBases.knowledgeBaseId} || '-' || n, ${manyBases.workspaceId}, + ${manyBases.aliceId}, 'Scale fixture ' || n FROM generate_series(1, 10000) AS n`) + await db.execute(sql`INSERT INTO document + (id, knowledge_base_id, filename, file_url, file_size, mime_type, acl, token_count) + VALUES (${generateId()}, ${manyBases.knowledgeBaseId}, 'first.txt', + 'https://fixture.invalid/first', 1, 'text/plain', ARRAY['ws'], 13), + (${generateId()}, ${`${manyBases.knowledgeBaseId}-10000`}, 'last.txt', + 'https://fixture.invalid/last', 1, 'text/plain', ARRAY['ws'], 17)`) + await db.execute(sql`ANALYZE knowledge_base`) + await db.execute(sql`ANALYZE document`) +}, 60_000) + +afterAll(async () => { + const reportPath = process.env.KNOWLEDGE_BASE_LIST_REPORT_PATH + if (reportPath) writeFileSync(reportPath, JSON.stringify(reports, null, 2)) + try { + for (const fixture of [ids, foreign, manyBases]) { + await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await db.delete(organization).where(eq(organization.id, fixture.organizationId)) + await db.delete(user).where(inArray(user.id, [fixture.aliceId, fixture.bobId])) + } + } finally { + await db.$client.end() + } +}) + +describe('knowledge base list counts on real Postgres', () => { + it.each(['name', 'createdAt'] as const)( + 'bounds document reads to a small page ordered by %s', + async (sortBy) => { + const captured: CapturedQuery[] = [] + const previousDebug = db.$client.options.debug + db.$client.options.debug = (_connection, query, parameters) => { + if (captured.length < 30) captured.push({ query, parameters: [...parameters] }) + } + try { + const page = await getWorkspaceKnowledgeBases(ids.workspaceId, 'active', { + countsFor: access, + limit: 1, + sortBy, + }) + expect( + page.data.map(({ id, docCount, tokenCount }) => ({ id, docCount, tokenCount })) + ).toEqual([{ id: ids.knowledgeBaseId, docCount: 2, tokenCount: 18 }]) + expect(page.nextCursorKeys).not.toBeNull() + } finally { + db.$client.options.debug = previousDebug + } + const plans = [] + for (const statement of captured.filter(({ query }) => query.includes('"document"'))) { + const [result] = await db.$client.unsafe< + Array<{ 'QUERY PLAN': Array<{ Plan: ExplainNode }> }> + >(`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${statement.query}`, statement.parameters) + plans.push(...result['QUERY PLAN']) + } + const visits = plans.reduce((total, plan) => total + documentVisits(plan.Plan), 0) + reports.push({ sortBy, visits, plans }) + expect(plans.length).toBeGreaterThan(0) + expect(visits).toBeLessThan(100) + } + ) + + it('keeps empty KBs and count visibility through pagination and unpaged reads', async () => { + const first = await getWorkspaceKnowledgeBases(ids.workspaceId, 'active', { + countsFor: access, + limit: 1, + sortBy: 'name', + }) + if (!first.nextCursorKeys) throw new Error('Expected a second knowledge-base page') + const second = await getWorkspaceKnowledgeBases(ids.workspaceId, 'active', { + countsFor: access, + limit: 1, + sortBy: 'name', + cursorKeys: first.nextCursorKeys, + }) + expect( + second.data.map(({ id, docCount, tokenCount }) => ({ id, docCount, tokenCount })) + ).toEqual([{ id: emptyId, docCount: 0, tokenCount: 0 }]) + const all = await getWorkspaceKnowledgeBases(ids.workspaceId, 'active', { + countsFor: access, + sortBy: 'name', + }) + expect(all.data.map(({ id, docCount, tokenCount }) => ({ id, docCount, tokenCount }))).toEqual([ + { id: ids.knowledgeBaseId, docCount: 2, tokenCount: 18 }, + { id: emptyId, docCount: 0, tokenCount: 0 }, + { id: offPageId, docCount: 10000, tokenCount: 10000 }, + ]) + expect(all.nextCursorKeys).toBeNull() + const archived = await getWorkspaceKnowledgeBases(ids.workspaceId, 'archived', { + countsFor: access, + }) + expect(archived.data.map(({ id }) => id)).toEqual([archivedId]) + }) + + it('counts a large unpaged workspace within a fixed database round-trip budget', async () => { + let documentQueries = 0 + const previousDebug = db.$client.options.debug + db.$client.options.debug = (_connection, query) => { + if (query.includes('"document"')) documentQueries++ + } + try { + const all = await getWorkspaceKnowledgeBases(manyBases.workspaceId, 'active', { + countsFor: access, + }) + expect(all.data).toHaveLength(10001) + expect(all.nextCursorKeys).toBeNull() + expect(all.data.find((kb) => kb.id === manyBases.knowledgeBaseId)).toMatchObject({ + docCount: 1, + tokenCount: 13, + }) + expect(all.data.find((kb) => kb.id === `${manyBases.knowledgeBaseId}-10000`)).toMatchObject({ + docCount: 1, + tokenCount: 17, + }) + expect(all.data.reduce((total, kb) => total + kb.docCount, 0)).toBe(2) + expect(all.data.reduce((total, kb) => total + kb.tokenCount, 0)).toBe(30) + reports.push({ unpagedBases: all.data.length, documentQueries }) + expect(documentQueries).toBeGreaterThan(0) + expect(documentQueries).toBeLessThan(10) + } finally { + db.$client.options.debug = previousDebug + } + }) +}) diff --git a/apps/sim/lib/knowledge/service.test.ts b/apps/sim/lib/knowledge/service.test.ts index db333fd2ba2..1204da201ef 100644 --- a/apps/sim/lib/knowledge/service.test.ts +++ b/apps/sim/lib/knowledge/service.test.ts @@ -293,18 +293,16 @@ describe('knowledge base counts with live source permissions', () => { id: 'kb-1', workspaceId: 'ws-1', chunkingConfig: {}, - docCount: 2, - tokenCount: 10, createdAt: new Date('2026-01-01'), }, ]) + queueTableRows(schemaMock.document, [{ knowledgeBaseId: 'kb-1', docCount: 2, tokenCount: 10 }]) const result = await getWorkspaceKnowledgeBases('ws-1', 'archived', { countsFor: access }) expect(result.data[0]).toMatchObject({ docCount: 2, tokenCount: 10 }) expect(getForConnectors).not.toHaveBeenCalled() expect(dbChainMockFns.select).not.toHaveBeenCalledWith({ connectorId: schemaMock.knowledgeConnector.id, }) - expect(dbChainMockFns.groupBy).toHaveBeenCalledOnce() }) it('does not retain stale totals when a live source no longer authorizes its documents', async () => { diff --git a/apps/sim/lib/knowledge/service.ts b/apps/sim/lib/knowledge/service.ts index f6e43d4bc4f..423de6a68bc 100644 --- a/apps/sim/lib/knowledge/service.ts +++ b/apps/sim/lib/knowledge/service.ts @@ -33,7 +33,7 @@ import { resourceScopeCondition } from '@/lib/core/resource-scope.server' import { generateRestoreName } from '@/lib/core/utils/restore-name' import { findActiveFolder, resolveRestoredFolderId } from '@/lib/folders/queries' import { isKnowledgeMemberAccessAvailable } from '@/lib/knowledge/access/availability' -import { knowledgeAccessCondition } from '@/lib/knowledge/access/predicate' +import { knowledgeAccessCondition, textArrayLiteral } from '@/lib/knowledge/access/predicate' import type { KnowledgeAccessProvider } from '@/lib/knowledge/access/types' import { mirrorsSourceAcls } from '@/lib/knowledge/connectors/access-modes' import { @@ -188,8 +188,8 @@ async function readKnowledgeBaseRows( } /** - * {@link readKnowledgeBaseRows} plus the live totals of the documents `access` admits. Only the - * surfaces that display totals pay for the document join, and they always count as a reader. + * Pages bases before counting the documents `access` admits. Explicit document base IDs keep + * the count selective instead of scanning a shared ACL token across tenants before the join. */ async function readCountedKnowledgeBaseRows( where: SQL | undefined, @@ -200,31 +200,18 @@ async function readCountedKnowledgeBaseRows( Array> > { const scope = 'get' in access ? await access.get() : access - const query = db - .select({ - ...ACTIVE_KNOWLEDGE_BASE_REFERENCE_FIELDS, - tokenCount: sql`COALESCE(SUM(${document.tokenCount}), 0)`.mapWith(Number), - docCount: count(document.knowledgeBaseId), - }) - .from(knowledgeBase) - .leftJoin( - document, - and( - eq(document.knowledgeBaseId, knowledgeBase.id), - eq(document.userExcluded, false), - isNull(document.archivedAt), - isNull(document.deletedAt), - knowledgeAccessCondition(scope) - ) - ) - .where(where) - .groupBy(knowledgeBase.id) - .orderBy(...orderBy) - - const rows = limit === undefined ? await query : await query.limit(limit) + const rows = await readKnowledgeBaseRows(where, orderBy, limit) + const totals = + rows.length > 0 + ? await countDocumentsByKnowledgeBase( + sql`${document.knowledgeBaseId} = ANY(${textArrayLiteral(rows.map((kb) => kb.id))})`, + knowledgeAccessCondition(scope) + ) + : [] + const counts = new Map(totals.map((total) => [total.knowledgeBaseId, total])) /** - * The join above already counted everything the reader's stored ACL admits. Only a + * The counts above already include everything the reader's stored ACL admits. Only a * provider can add documents a live source (GitHub, Confluence) authorizes beyond that, * and that supplement is resolved once for the whole list: an unpaged list is bounded by * its own filter, a page by its row IDs, so a workspace with tens of thousands of bases @@ -243,9 +230,9 @@ async function readCountedKnowledgeBaseRows( ) : undefined return rows.map((kb) => ({ - ...toActiveKnowledgeBaseReference(kb), - docCount: Number(kb.docCount) + (liveCounts?.get(kb.id)?.docCount ?? 0), - tokenCount: kb.tokenCount + (liveCounts?.get(kb.id)?.tokenCount ?? 0), + ...kb, + docCount: (counts.get(kb.id)?.docCount ?? 0) + (liveCounts?.get(kb.id)?.docCount ?? 0), + tokenCount: (counts.get(kb.id)?.tokenCount ?? 0) + (liveCounts?.get(kb.id)?.tokenCount ?? 0), })) } diff --git a/apps/sim/lib/mothership/agent-cli/engines/file-read.test.ts b/apps/sim/lib/mothership/agent-cli/engines/file-read.test.ts index 6d6b67ac323..40c5c585800 100644 --- a/apps/sim/lib/mothership/agent-cli/engines/file-read.test.ts +++ b/apps/sim/lib/mothership/agent-cli/engines/file-read.test.ts @@ -205,11 +205,28 @@ describe('content-aware files read augmentation', () => { } ) + it('forwards --offset 0 to the text API instead of refusing it', async () => { + mockResolveWorkspaceFileReference.mockResolvedValue({ + ...file, + name: 'notes.txt', + type: 'text/plain', + }) + mocks.request.mockResolvedValue(textResponse('notes.txt')) + const result = await runEngine('files read', ['uploads/notes.txt'], runtime, { + offset: '0', + limit: '2', + }) + expect(result.exitCode).toBe(0) + expect(mocks.request).toHaveBeenCalledWith(`/api/v2/files/${fileId}/text`, { + query: { workspaceId, offset: '0', limit: '2' }, + }) + }) + it('rejects incompatible flags and invalid byte/line bounds before lookup', async () => { for (const flags of [ { render: true, limit: '2' } as const, { pages: '1', offset: '1' }, - { offset: '0' }, + { offset: '-1' }, { offset: true } as const, { limit: true } as const, { 'max-bytes': true } as const, diff --git a/apps/sim/lib/mothership/chat/application/fork.ts b/apps/sim/lib/mothership/chat/application/fork.ts index d3c2ebcec3b..97b15c298ce 100644 --- a/apps/sim/lib/mothership/chat/application/fork.ts +++ b/apps/sim/lib/mothership/chat/application/fork.ts @@ -19,10 +19,14 @@ import { planChatFileCopies, } from '@/lib/mothership/chat/fork-chat-files' import { planForkInlineImages } from '@/lib/mothership/chat/fork-inline-images' -import { copyWorkerConversation } from '@/lib/mothership/chat/fork-worker' +import { + copyWorkerConversation, + discardWorkerConversation, +} from '@/lib/mothership/chat/fork-worker' import { loadCopilotChatMessages } from '@/lib/mothership/chat/lifecycle' import { appendCopilotChatMessages } from '@/lib/mothership/chat/messages-store' import { + publishedFileRefMaps, rewriteMessageFileRefs, rewriteResourceFileRefs, } from '@/lib/mothership/chat/rewrite-file-references' @@ -88,6 +92,8 @@ export const forkChat = defineAuthorizedChatUseCase({ const { chatId, upToMessageId } = input let preparedBlobs: ChatBlobCopyTask[] = [] let published = false + const newId = generateId() + let workerCopyRequested = false try { const messages = await loadCopilotChatMessages(chatId) const forkIdx = messages.findIndex((m) => m.id === upToMessageId) @@ -95,23 +101,28 @@ export const forkChat = defineAuthorizedChatUseCase({ throw new OrchestrationError('validation', 'Message not found in chat') } const forkedMessages = messages.slice(0, forkIdx + 1) + const keptMessageIds = new Set(forkedMessages.map((m) => m.id)) + const keptRequestIds = new Set( + forkedMessages.flatMap((m) => (m.requestId ? [m.requestId] : [])) + ) + /** The Sources panel reads its response by message or request id; a response past the cut is not in the fork. */ + const addressesKeptMessage = ({ sources }: MothershipResource) => + !!sources && + (keptMessageIds.has(sources.messageId) || + (!!sources.requestId && keptRequestIds.has(sources.requestId))) /** Single workspace_files read per fork: every chat-owned upload. The copied set is timeline-cut to the kept message slice in memory (files born after the fork point stay behind). */ const chatOwnedFiles = context.workspaceId ? await listForkableChatFiles(db, chatId) : [] - const sourceFiles = filterForkableChatFiles( - chatOwnedFiles, - new Set(forkedMessages.map((m) => m.id)) - ) + const sourceFiles = filterForkableChatFiles(chatOwnedFiles, keptMessageIds) /** Resources are stored as a jsonb array on the chat row. They carry no timestamps, so they can't be timeline-cut like messages — instead, file resources whose chat-owned file is NOT copied (uploads born after the cut) are dropped in the rewrite below; everything else is copied. */ const parentResources = sanitizeChatResources( Array.isArray(parent.resources) ? (parent.resources as MothershipResource[]) : [] - ) + ).filter((resource) => resource.type !== 'sources' || addressesKeptMessage(resource)) /** The source chat's chat-owned file ids (no cut) — the "is this resource a ghost?" test set for the rewrite. */ const chatOwnedFileIds = new Set(chatOwnedFiles.map((row) => row.id)) - const newId = generateId() /** Strip a leading "Fork | " so titles don't stack prefixes when forking a forked chat. */ const baseTitle = (parent.title ?? 'New chat').replace(/^Fork \| /, '') const title = `Fork | ${baseTitle}` @@ -121,14 +132,15 @@ export const forkChat = defineAuthorizedChatUseCase({ preparedBlobs = [...plan.blobTasks, ...planForkInlineImages(forkedMessages, chatId, newId)] const { failed, failedCopyIds } = await executeChatFileBlobCopies(preparedBlobs) const failedIds = new Set(failedCopyIds) - const maps = { fileIds: plan.idMap, fileKeys: plan.keyMap } - const newChatResources = rewriteResourceFileRefs( - parentResources, - maps, - chatOwnedFileIds - ).filter((resource) => resource.type !== 'file' || !failedIds.has(resource.id)) + const maps = { + ...publishedFileRefMaps(plan, failedIds), + workspaceId: context.workspaceId, + } + /** A chat-owned file whose copy failed is a ghost here too: no published copy stands in for it. */ + const newChatResources = rewriteResourceFileRefs(parentResources, maps, chatOwnedFileIds) const cutUser = [...forkedMessages].reverse().find((message) => message.role === 'user') if (!cutUser) throw new Error('The fork has no user message') + workerCopyRequested = true await copyWorkerConversation({ sourceChatId: chatId, newChatId: newId, @@ -138,12 +150,26 @@ export const forkChat = defineAuthorizedChatUseCase({ userId, upToMessageId: cutUser.id, includeResponse: forkedMessages.at(-1)?.role === 'assistant', - fileIds: Object.fromEntries(plan.idMap), - fileKeys: Object.fromEntries(plan.keyMap), + fileIds: Object.fromEntries(maps.fileIds), + fileKeys: Object.fromEntries(maps.fileKeys), }) /** Publish only after both the file bytes and the worker conversation are prepared. */ await db.transaction(async (tx) => { + /** + * The fork can share keys with its source (organization attachments, files whose copy + * failed), and chat cleanup deletes a shared key once no remaining chat references it, + * checking only after it deletes the source row. Holding the source row until commit + * orders the two: a purge that already removed it refuses this fork, and one that has + * not waits for this commit and then sees the fork's references. + */ + const [source] = await tx + .select({ id: copilotChats.id }) + .from(copilotChats) + .where(eq(copilotChats.id, chatId)) + .for('key share') + .limit(1) + if (!source) throw new OrchestrationError('not_found', 'Chat not found') const [row] = await tx .insert(copilotChats) .values({ @@ -198,6 +224,8 @@ export const forkChat = defineAuthorizedChatUseCase({ ...(failed > 0 ? { failedFileCopies: failed } : {}), } } catch (error) { + if (!published && workerCopyRequested) + await discardWorkerConversation({ newChatId: newId, userId }) if (!published) { await mapWithConcurrency(preparedBlobs, 4, async (task) => { try { diff --git a/apps/sim/lib/mothership/chat/application/inline-images.test.ts b/apps/sim/lib/mothership/chat/application/inline-images.test.ts index d34295c72d4..b004fee8a2d 100644 --- a/apps/sim/lib/mothership/chat/application/inline-images.test.ts +++ b/apps/sim/lib/mothership/chat/application/inline-images.test.ts @@ -46,8 +46,8 @@ import { materializeStreamImage, readInlineChatImage, } from '@/lib/mothership/chat/application/inline-images' +import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-key' import { inlineChatImageUrl } from '@/lib/mothership/chat/inline-image-reference' -import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-storage' import { GET } from '@/app/api/mothership/chats/[chatId]/images/[requestId]/route' const mocks = { diff --git a/apps/sim/lib/mothership/chat/fork-inline-images.test.ts b/apps/sim/lib/mothership/chat/fork-inline-images.test.ts index 63ad5be033a..9131a08f7cb 100644 --- a/apps/sim/lib/mothership/chat/fork-inline-images.test.ts +++ b/apps/sim/lib/mothership/chat/fork-inline-images.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest' vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock) import { planForkInlineImages } from '@/lib/mothership/chat/fork-inline-images' -import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-storage' +import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-key' import type { PersistedMessage } from '@/lib/mothership/chat/persisted-message' const message = (content: string): PersistedMessage => ({ diff --git a/apps/sim/lib/mothership/chat/fork-inline-images.ts b/apps/sim/lib/mothership/chat/fork-inline-images.ts index d0d495883bd..17dd744ba33 100644 --- a/apps/sim/lib/mothership/chat/fork-inline-images.ts +++ b/apps/sim/lib/mothership/chat/fork-inline-images.ts @@ -1,14 +1,10 @@ import { OrchestrationError } from '@/lib/core/orchestration/types' import type { ChatBlobCopyTask } from '@/lib/mothership/chat/fork-chat-files' import { - inlineImageRequestIdSchema, - inlineImageSourceSchema, -} from '@/lib/mothership/chat/inline-image-reference' -import { - INLINE_CHAT_IMAGE_MAX_BYTES, inlineChatImageKey, -} from '@/lib/mothership/chat/inline-image-storage' -import { collectMarkdownImageSources } from '@/lib/mothership/chat/markdown-images' + inlineChatImageReferences, +} from '@/lib/mothership/chat/inline-image-key' +import { INLINE_CHAT_IMAGE_MAX_BYTES } from '@/lib/mothership/chat/inline-image-storage' import type { PersistedMessage } from '@/lib/mothership/chat/persisted-message' /** Only retained assistant image references are copied; their Markdown and request IDs stay unchanged. */ @@ -19,38 +15,25 @@ export function planForkInlineImages( ): ChatBlobCopyTask[] { const tasks = new Map() for (const message of messages) { - if ( - message.role !== 'assistant' || - !message.requestId || - !inlineImageRequestIdSchema.safeParse(message.requestId).success - ) - continue - const contents = [ - message.content, - ...(message.contentBlocks ?? []) - .filter((block) => block.type === 'text') - .map((block) => block.content ?? ''), - ] - for (const content of contents) { - for (const reference of collectMarkdownImageSources(content)) { - if (!inlineImageSourceSchema.safeParse(reference).success) continue - const sourceKey = inlineChatImageKey(sourceChatId, message.requestId, reference) - tasks.set(sourceKey, { - copyId: sourceKey, - sourceKey, - targetKey: inlineChatImageKey(newChatId, message.requestId, reference), - context: 'mothership', - fileName: 'chat-image.webp', - contentType: 'image/webp', - maxBytes: INLINE_CHAT_IMAGE_MAX_BYTES, - persistMetadata: false, - }) - if (tasks.size > 200) - throw new OrchestrationError( - 'payload_too_large', - 'A chat fork can copy at most 200 inline images.' - ) - } + const published = inlineChatImageReferences(message) + if (!published) continue + for (const reference of published.references) { + const sourceKey = inlineChatImageKey(sourceChatId, published.requestId, reference) + tasks.set(sourceKey, { + copyId: sourceKey, + sourceKey, + targetKey: inlineChatImageKey(newChatId, published.requestId, reference), + context: 'mothership', + fileName: 'chat-image.webp', + contentType: 'image/webp', + maxBytes: INLINE_CHAT_IMAGE_MAX_BYTES, + persistMetadata: false, + }) + if (tasks.size > 200) + throw new OrchestrationError( + 'payload_too_large', + 'A chat fork can copy at most 200 inline images.' + ) } } return [...tasks.values()] diff --git a/apps/sim/lib/mothership/chat/fork-worker.test.ts b/apps/sim/lib/mothership/chat/fork-worker.test.ts index 9d159598215..63bf3de8351 100644 --- a/apps/sim/lib/mothership/chat/fork-worker.test.ts +++ b/apps/sim/lib/mothership/chat/fork-worker.test.ts @@ -8,14 +8,13 @@ import { } from '@sim/testing/mocks/mothership-go-fetch.mock' import { generateId } from '@sim/utils/id' import { beforeEach, expect, it, vi } from 'vitest' +import { asOrchestrationError } from '@/lib/core/orchestration/types' import { copyWorkerConversation } from '@/lib/mothership/chat/fork-worker' import type { ForkChatRequest } from '@/lib/mothership/generated/protocol' vi.mock('@/lib/mothership/request/go/fetch', () => mothershipGoFetchMock) vi.mock('@/lib/mothership/server/agent-url', () => mothershipAgentUrlMock) -const fetchWorker = mothershipGoFetchMockFns.mockFetchGo - const request: ForkChatRequest = { sourceChatId: generateId(), newChatId: generateId(), @@ -27,37 +26,132 @@ const request: ForkChatRequest = { fileKeys: {}, } +type Answer = () => Promise + +/** What undici throws for a refused or reset socket: a TypeError with the syscall code on `cause`. */ +function socketFailure(code: string): TypeError { + return new TypeError('fetch failed', { cause: Object.assign(new Error(code), { code }) }) +} + +/** A fake worker: it records every fork request it receives and answers from a script. */ +let received: unknown[] = [] +let script: Answer[] = [] +const receipt: Answer = async () => + Response.json({ chatId: request.newChatId, sourceThroughSeq: 7 }) + +function answer(...answers: Answer[]) { + script = answers +} + +const status = + (code: number, body: BodyInit | null = ''): Answer => + async () => + new Response(body, { status: code }) + beforeEach(() => { mothershipAgentUrlMockFns.mockGetMothershipBaseURL.mockResolvedValue('http://worker.test') - fetchWorker.mockReset() - fetchWorker.mockImplementation(async () => - Response.json({ chatId: request.newChatId, sourceThroughSeq: 7 }) + received = [] + script = [] + mothershipGoFetchMockFns.mockFetchGo.mockReset() + mothershipGoFetchMockFns.mockFetchGo.mockImplementation( + async (_url: string, init: { body: string }) => { + received.push(JSON.parse(init.body)) + return (script.shift() ?? receipt)() + } ) }) +it.each(['EHOSTUNREACH', 'ENETUNREACH'])( + 'retries a fork the worker never received (%s)', + async (code) => { + answer(async () => { + throw socketFailure(code) + }) + await copyWorkerConversation(request) + expect(received).toEqual([request, request]) + } +) + it.each(['lost-response', 'temporary-error'])( 'retries the same immutable fork after %s', async (failure) => { - if (failure === 'lost-response') - fetchWorker.mockRejectedValueOnce(new TypeError('Connection ended')) - else fetchWorker.mockResolvedValueOnce(new Response('', { status: 503 })) + answer( + failure === 'lost-response' + ? async () => { + throw socketFailure('ECONNRESET') + } + : status(503) + ) await copyWorkerConversation(request) - expect(fetchWorker).toHaveBeenCalledTimes(2) - expect(fetchWorker.mock.calls[0][1].body).toBe(fetchWorker.mock.calls[1][1].body) - expect(JSON.parse(fetchWorker.mock.calls[1][1].body)).toEqual(request) + expect(received).toEqual([request, request]) } ) it.each(['missing-receipt', 'wrong-chat', 'unavailable'])( 'refuses an unconfirmed copy: %s', async (failure) => { - fetchWorker.mockImplementation(async () => { - if (failure === 'unavailable') throw new TypeError('Worker unavailable') + const reply: Answer = async () => { + if (failure === 'unavailable') throw socketFailure('ECONNREFUSED') return Response.json( failure === 'wrong-chat' ? { chatId: generateId(), sourceThroughSeq: 7 } : { ok: true } ) - }) + } + answer(reply, reply) await expect(copyWorkerConversation(request)).rejects.toThrow() - expect(fetchWorker).toHaveBeenCalledTimes(2) + // Only the unreachable worker may never have seen the request; an answer is final. + expect(received).toHaveLength(failure === 'unavailable' ? 2 : 1) } ) + +it.each([ + [404, 'not_found'], + [409, 'conflict'], + [413, 'payload_too_large'], +] as const)('classifies a worker %i refusal as %s without retrying it', async (code, kind) => { + answer(status(code, JSON.stringify({ error: 'refused' })), receipt) + const failure = await copyWorkerConversation(request).catch((error: unknown) => error) + expect(asOrchestrationError(failure)?.code).toBe(kind) + expect(received).toHaveLength(1) +}) + +it('classifies a refusal whose body fails to cancel', async () => { + const body = new ReadableStream({ + cancel() { + throw new TypeError('Body already closed') + }, + }) + answer(status(404, body), receipt) + const failure = await copyWorkerConversation(request).catch((error: unknown) => error) + expect(asOrchestrationError(failure)?.code).toBe('not_found') + expect(received).toHaveLength(1) +}) + +it.each([500, 400])('does not repeat a fork the worker failed with %i', async (code) => { + answer(status(code), receipt) + const failure = await copyWorkerConversation(request).catch((error: unknown) => error) + expect(failure).toBeInstanceOf(Error) + expect(asOrchestrationError(failure)).toBeNull() + expect(received).toHaveLength(1) +}) + +it.each([502, 504])('retries a %i gateway failure once', async (code) => { + answer(status(code)) + await copyWorkerConversation(request) + expect(received).toEqual([request, request]) +}) + +it('does not start a second copy while a timed-out one may still be running', async () => { + answer(async () => { + throw new DOMException('The operation timed out.', 'TimeoutError') + }, receipt) + await expect(copyWorkerConversation(request)).rejects.toThrow() + expect(received).toHaveLength(1) +}) + +it('does not repeat a fork after a TypeError that is not a socket failure', async () => { + answer(async () => { + throw new TypeError('Body is unusable: Body has already been read') + }, receipt) + await expect(copyWorkerConversation(request)).rejects.toThrow() + expect(received).toHaveLength(1) +}) diff --git a/apps/sim/lib/mothership/chat/fork-worker.ts b/apps/sim/lib/mothership/chat/fork-worker.ts index bda6145b4dd..d5570db0c2b 100644 --- a/apps/sim/lib/mothership/chat/fork-worker.ts +++ b/apps/sim/lib/mothership/chat/fork-worker.ts @@ -1,35 +1,123 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { isRetryableNetworkError } from '@/lib/core/errors/retryable-infrastructure' +import { OrchestrationError } from '@/lib/core/orchestration/types' import { type ForkChatRequest, ForkChatResponse } from '@/lib/mothership/generated/protocol' import { fetchGo } from '@/lib/mothership/request/go/fetch' import { mothershipRequestHeaders } from '@/lib/mothership/request/headers' import { getMothershipBaseURL } from '@/lib/mothership/server/agent-url' -/** A lost copy acknowledgement retries the same destination and immutable request. */ +const logger = createLogger('ForkWorker') + +/** + * How long one attempt waits for the copy. The worker refuses a cut above 50,000 events + * with 413, and measured locally it copies 15,000 events in 1.5–2 s and 50,000 in 5–9 s; + * production databases are slower by an estimated factor of up to eight, which puts the + * ceiling at 40–70 s. 120 s leaves headroom above that, and the load balancers on both + * sides keep idle connections far longer. A legitimate fork therefore finishes inside one + * attempt, and one that does not is abandoned: the worker rolls a copy back once its + * caller's connection closes, so a timed-out attempt is never retried and never published. + */ +const ATTEMPT_TIMEOUT_MS = 120_000 + +/** Cleanup is a small archive-and-purge of one chat, never a copy. */ +const DISCARD_TIMEOUT_MS = 10_000 + +/** Gateway failures: the request may never have reached a worker, so one more attempt is safe. */ +const RETRYABLE_STATUSES = new Set([502, 503, 504]) + +/** The worker's typed refusals, each one the caller can act on. */ +function workerRefusal(status: number): Error { + if (status === 404) return new OrchestrationError('not_found', 'Chat not found') + if (status === 409) + return new OrchestrationError( + 'conflict', + 'The selected response has not finished. Retry the fork once it completes.' + ) + if (status === 413) + return new OrchestrationError( + 'payload_too_large', + 'This conversation is too long to fork. Fork from an earlier message.' + ) + return new Error('The conversation could not be copied. Retry the fork.') +} + +type Attempt = { kind: 'receipt'; body: unknown } | { kind: 'status'; status: number } + +/** + * A lost copy acknowledgement retries the same destination and immutable request; the + * worker answers a repeat with the first attempt's receipt. Only a refused, reset or dropped + * socket or a gateway status is retried: a timed-out attempt may still be copying. + */ export async function copyWorkerConversation(request: ForkChatRequest): Promise { const baseURL = await getMothershipBaseURL({ userId: request.userId }) const body = JSON.stringify(request) - for (let attempt = 0; attempt < 2; attempt++) { + for (let attempt = 0; ; attempt++) { + const canRetry = attempt === 0 + let outcome: Attempt try { const response = await fetchGo(`${baseURL}/api/chats/fork`, { method: 'POST', headers: mothershipRequestHeaders(), body, - signal: AbortSignal.timeout(15_000), + signal: AbortSignal.timeout(ATTEMPT_TIMEOUT_MS), spanName: 'sim → worker /api/chats/fork', operation: 'fork_chat', }) - if (!response.ok) { - if (response.status >= 500 && attempt === 0) { - await response.body?.cancel() - continue - } - throw new Error('The conversation could not be copied. Retry the fork.') + if (response.ok) outcome = { kind: 'receipt', body: await response.json() } + else { + outcome = { kind: 'status', status: response.status } + // Releasing the unread error body is best effort; the status is already the answer. + await response.body?.cancel().catch(() => undefined) } - const receipt = ForkChatResponse.parse(await response.json()) - if (receipt.chatId !== request.newChatId) - throw new Error('The fork returned a different chat') - return } catch (error) { - if (attempt === 1) throw error + if (canRetry && isRetryableNetworkError(error)) continue + throw error + } + if (outcome.kind === 'status') { + if (canRetry && RETRYABLE_STATUSES.has(outcome.status)) continue + throw workerRefusal(outcome.status) } + const receipt = ForkChatResponse.parse(outcome.body) + if (receipt.chatId !== request.newChatId) throw new Error('The fork returned a different chat') + return + } +} + +/** + * Removes the worker's copy of a fork that will not be published, so the worker keeps no + * conversation Sim has no chat for. Best effort: a failure is logged, never thrown, since + * the caller is already reporting the fork's own failure. + * + * The worker rolls back a copy whose caller disconnected, so after a timed-out attempt + * this is normally a no-op. A worker that commits in the instant between its last + * disconnect check and its commit, or one deployed before that rollback existed, can + * commit after this call runs; that conversation then stays on the worker, unreachable + * from Sim. + */ +export async function discardWorkerConversation( + request: Pick +): Promise { + try { + const baseURL = await getMothershipBaseURL({ userId: request.userId }) + const response = await fetchGo(`${baseURL}/api/tasks/cleanup`, { + method: 'POST', + headers: mothershipRequestHeaders(), + body: JSON.stringify({ chatIds: [request.newChatId] }), + signal: AbortSignal.timeout(DISCARD_TIMEOUT_MS), + spanName: 'sim → worker /api/tasks/cleanup', + operation: 'discard_fork', + }) + await response.body?.cancel().catch(() => undefined) + if (!response.ok) + logger.warn('Worker refused to discard an unpublished fork', { + chatId: request.newChatId, + status: response.status, + }) + } catch (error) { + logger.warn('Failed to discard an unpublished fork on the worker', { + chatId: request.newChatId, + error: getErrorMessage(error), + }) } } diff --git a/apps/sim/lib/mothership/chat/inline-image-key.ts b/apps/sim/lib/mothership/chat/inline-image-key.ts new file mode 100644 index 00000000000..aa35c449011 --- /dev/null +++ b/apps/sim/lib/mothership/chat/inline-image-key.ts @@ -0,0 +1,47 @@ +import { createHash } from 'node:crypto' +import { + INLINE_CHAT_IMAGE_PREFIX, + inlineImageRequestIdSchema, + inlineImageSourceSchema, + normalizeInlineFileReference, +} from '@/lib/mothership/chat/inline-image-reference' +import { collectMarkdownImageSources } from '@/lib/mothership/chat/markdown-images' +import type { PersistedMessage } from '@/lib/mothership/chat/persisted-message' + +/** Stable per-message source identity makes replay independent of sandbox lifetime. */ +export function inlineChatImageKey(chatId: string, requestId: string, reference: string): string { + inlineImageRequestIdSchema.parse(chatId) + inlineImageRequestIdSchema.parse(requestId) + const digest = createHash('sha256').update(normalizeInlineFileReference(reference)).digest('hex') + return `${INLINE_CHAT_IMAGE_PREFIX}${chatId}/${requestId}/${digest}.webp` +} + +/** + * The inline image references an assistant message published under its request id: every + * first-party Markdown image source in its text and text blocks. Anything else (user text, + * a message with no request receipt, remote URLs) never had a chat image stored. + */ +export function inlineChatImageReferences( + message: Pick +): { requestId: string; references: string[] } | null { + if ( + message.role !== 'assistant' || + !message.requestId || + !inlineImageRequestIdSchema.safeParse(message.requestId).success + ) + return null + const contents = [ + message.content, + ...(message.contentBlocks ?? []) + .filter((block) => block.type === 'text') + .map((block) => block.content ?? ''), + ] + const references = new Set() + for (const content of contents) { + // Every Markdown image, inline or reference-style, opens with `![`; most messages have none. + if (typeof content !== 'string' || !content.includes('![')) continue + for (const reference of collectMarkdownImageSources(content)) + if (inlineImageSourceSchema.safeParse(reference).success) references.add(reference) + } + return { requestId: message.requestId, references: [...references] } +} diff --git a/apps/sim/lib/mothership/chat/inline-image-storage.ts b/apps/sim/lib/mothership/chat/inline-image-storage.ts index 0747cd7c3df..40f253ceda5 100644 --- a/apps/sim/lib/mothership/chat/inline-image-storage.ts +++ b/apps/sim/lib/mothership/chat/inline-image-storage.ts @@ -1,11 +1,6 @@ -import { createHash } from 'node:crypto' import sharp from 'sharp' import { OrchestrationError } from '@/lib/core/orchestration/types' -import { - INLINE_CHAT_IMAGE_PREFIX, - inlineImageRequestIdSchema, - normalizeInlineFileReference, -} from '@/lib/mothership/chat/inline-image-reference' +import { inlineChatImageKey } from '@/lib/mothership/chat/inline-image-key' import { isObjectNotFoundError } from '@/lib/uploads/core/errors' import { downloadFile, uploadFile } from '@/lib/uploads/core/storage-service' @@ -112,11 +107,3 @@ export async function loadInlineChatImage( throw error } } - -/** Stable per-message source identity makes replay independent of sandbox lifetime. */ -export function inlineChatImageKey(chatId: string, requestId: string, reference: string): string { - inlineImageRequestIdSchema.parse(chatId) - inlineImageRequestIdSchema.parse(requestId) - const digest = createHash('sha256').update(normalizeInlineFileReference(reference)).digest('hex') - return `${INLINE_CHAT_IMAGE_PREFIX}${chatId}/${requestId}/${digest}.webp` -} diff --git a/apps/sim/lib/mothership/chat/rewrite-file-references.ts b/apps/sim/lib/mothership/chat/rewrite-file-references.ts index e7ebea3dc2f..1ecc8fa7d2e 100644 --- a/apps/sim/lib/mothership/chat/rewrite-file-references.ts +++ b/apps/sim/lib/mothership/chat/rewrite-file-references.ts @@ -1,3 +1,5 @@ +import { isPlainRecord } from '@sim/utils/object' +import type { PersistedContentBlock } from '@/lib/api/contracts/copilot-messages' import type { PersistedMessage } from '@/lib/mothership/chat/persisted-message' import type { MothershipResource } from '@/lib/mothership/resources/types' import { rewriteForkContentRefs } from '@/ee/workspace-forking/lib/remap/remap-content-refs' @@ -10,6 +12,33 @@ import { rewriteForkContentRefs } from '@/ee/workspace-forking/lib/remap/remap-c export interface ChatFileRefMaps { fileIds: ReadonlyMap fileKeys: ReadonlyMap + /** + * The workspace both chats live in. A fork stays in its source's workspace, so in-app + * `/workspace//files/` links keep their workspace and only the file id moves. + */ + workspaceId?: string | null +} + +/** + * The copy plan's id/key maps restricted to copies whose bytes were prepared. A failed copy + * is never published, so the fork's messages, resources and worker history keep naming the + * source file (alive while the source chat is) instead of an id or key that never exists. + */ +export function publishedFileRefMaps( + plan: { + idMap: ReadonlyMap + keyMap: ReadonlyMap + blobTasks: readonly { copyId: string; targetKey: string }[] + }, + failedCopyIds: ReadonlySet +): { fileIds: Map; fileKeys: Map } { + const failedKeys = new Set( + plan.blobTasks.filter((task) => failedCopyIds.has(task.copyId)).map((task) => task.targetKey) + ) + return { + fileIds: new Map([...plan.idMap].filter(([, copyId]) => !failedCopyIds.has(copyId))), + fileKeys: new Map([...plan.keyMap].filter(([, copyKey]) => !failedKeys.has(copyKey))), + } } function hasMappings(maps: ChatFileRefMaps): boolean { @@ -17,15 +46,64 @@ function hasMappings(maps: ChatFileRefMaps): boolean { } function rewriteText(text: string, maps: ChatFileRefMaps): string { - return rewriteForkContentRefs(text, { fileIds: maps.fileIds, fileKeys: maps.fileKeys }) + return rewriteForkContentRefs(text, { + fileIds: maps.fileIds, + fileKeys: maps.fileKeys, + ...(maps.workspaceId ? { workspaceId: { from: maps.workspaceId, to: maps.workspaceId } } : {}), + }) +} + +/** + * Tool arguments name a file by its bare id or storage key (`{ fileId }`, `["files", "read", id]`), + * so a string that IS a mapped id or key is replaced whole; any other string gets the URL grammar. + */ +function rewriteToolValue(value: unknown, maps: ChatFileRefMaps): unknown { + if (typeof value === 'string') + return maps.fileIds.get(value) ?? maps.fileKeys.get(value) ?? rewriteText(value, maps) + if (Array.isArray(value)) return value.map((entry) => rewriteToolValue(entry, maps)) + if (isPlainRecord(value)) + return Object.fromEntries( + Object.entries(value).map(([key, entry]) => [key, rewriteToolValue(entry, maps)]) + ) + return value +} + +function rewriteBlock(block: PersistedContentBlock, maps: ChatFileRefMaps): PersistedContentBlock { + const toolCall = block.toolCall + return { + ...block, + ...(block.content ? { content: rewriteText(block.content, maps) } : {}), + ...(toolCall + ? { + toolCall: { + ...toolCall, + ...(toolCall.params + ? { params: rewriteToolValue(toolCall.params, maps) as Record } + : {}), + ...(toolCall.activityDescription + ? { activityDescription: rewriteText(toolCall.activityDescription, maps) } + : {}), + ...(toolCall.display?.title + ? { + display: { + ...toolCall.display, + title: rewriteText(toolCall.display.title, maps), + }, + } + : {}), + }, + } + : {}), + } } /** * Re-point every file reference in a copied transcript at the copied files, so * the fork is self-contained (it survives the original chat's deletion). * Rewrites: free-text URLs in `content` and text content blocks (serve/view/ - * in-app/`sim:file` forms, via the shared fork grammar), attachment chip - * ids+keys, and `@`-mention context chip file ids. References to anything not + * in-app/`sim:file` forms, via the shared fork grammar), tool-call arguments + * and display text, attachment chip ids+keys, and `@`-mention context chip + * file ids. References to anything not * in the maps (shared workspace files, workflows, other chats) pass through * unchanged. Pure; returns the input array untouched when there is nothing to * rewrite. @@ -50,9 +128,7 @@ export function rewriteMessageFileRefs( content: rewriteText(message.content, maps), } if (message.contentBlocks?.length) { - rewritten.contentBlocks = message.contentBlocks.map((block) => - block.content ? { ...block, content: rewriteText(block.content, maps) } : block - ) + rewritten.contentBlocks = message.contentBlocks.map((block) => rewriteBlock(block, maps)) } if (message.fileAttachments?.length) { rewritten.fileAttachments = message.fileAttachments.map((att) => ({ diff --git a/apps/sim/lib/mothership/generated/docs-manifest.ts b/apps/sim/lib/mothership/generated/docs-manifest.ts index 33498c52b1c..c2a8e16efcd 100644 --- a/apps/sim/lib/mothership/generated/docs-manifest.ts +++ b/apps/sim/lib/mothership/generated/docs-manifest.ts @@ -269,6 +269,7 @@ export const DOCS_MANIFEST: readonly string[] = [ 'integrations/polymarket.mdx', 'integrations/postgresql.mdx', 'integrations/posthog.mdx', + 'integrations/powerbi.mdx', 'integrations/profound.mdx', 'integrations/prospeo.mdx', 'integrations/pulse.mdx', @@ -355,6 +356,7 @@ export const DOCS_MANIFEST: readonly string[] = [ 'integrations/wordpress.mdx', 'integrations/workday.mdx', 'integrations/x.mdx', + 'integrations/youcom.mdx', 'integrations/youtube.mdx', 'integrations/zendesk.mdx', 'integrations/zep.mdx', diff --git a/apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts b/apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts index 2c7108b798c..f09e343936b 100644 --- a/apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts +++ b/apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts @@ -24,11 +24,6 @@ export const liveSearchProviderSchema = z.enum([ ]) export type LiveSearchProvider = z.output -export const SEARCH_TERMS_REQUIRED = { - notion: 'Notion requires search terms. Add keywords or a concise question.', - lucid: 'Lucid requires search terms. Add document-title keywords or a literal shape-text query.', -} as const - /** * Native queries one call may send to the same provider account. Alternatives run as separate * provider searches and fuse into one ranking, so the bound keeps a call within the provider's @@ -74,6 +69,12 @@ export const nativeSearchQuerySchema = z accountId: z.string().min(1).max(200).optional(), kind: nativeSearchKindSchema.optional(), project: z.string().min(1).max(300).optional(), + browse: z + .enum(['folder', 'private', 'shared', 'favorites', 'recent']) + .optional() + .describe( + 'Queryless discovery: Lucid folder lists one folder page (omit project for root; otherwise use a returned numeric folder ID). Notion private/shared list sidebar pages, favorites lists pinned pages, recent lists recently viewed pages, not recently modified pages. These lists are not an exhaustive workspace inventory. Follow the returned cursor with the same account, browse mode, project, filters and topK.' + ), cursor: z.string().max(4000).optional(), termClauses: z.array(z.string().max(500)).max(10).optional(), modifiers: z.string().max(1000).optional(), @@ -92,11 +93,32 @@ export const nativeSearchQuerySchema = z : `${input.provider} does not support kind selection.`, }) } - if ((input.provider === 'notion' || input.provider === 'lucid') && !input.query) + if ( + input.browse && + (input.query || + input.modifiers || + input.keywordOnly || + input.termClauses?.length || + (input.browse === 'folder' ? input.provider !== 'lucid' : input.provider !== 'notion') || + (input.provider === 'notion' && input.project)) + ) + context.addIssue({ + code: 'custom', + path: ['browse'], + message: + 'Browse requires an empty query and a supported provider mode; Notion lists cannot be scoped to a page.', + }) + if (input.browse === 'folder' && input.project && !/^[1-9]\d{0,14}$/.test(input.project)) + context.addIssue({ + code: 'custom', + path: ['project'], + message: 'Lucid folder browsing requires a numeric folder ID returned by the provider.', + }) + if (input.provider === 'lucid' && !input.query && !input.browse) context.addIssue({ code: 'custom', path: ['query'], - message: SEARCH_TERMS_REQUIRED[input.provider], + message: 'Lucid requires title keywords or explicit browse: folder.', }) }) export type NativeSearchQuery = z.output @@ -133,7 +155,9 @@ export const nativeSearchQueriesSchema = z addIssue('Duplicate native query.') else if ( hasSearchKinds(query.provider) && - earlier.some((previous) => !previous.kind || !query.kind) + earlier.some( + (previous) => !previous.browse && !query.browse && (!previous.kind || !query.kind) + ) ) addIssue( 'A GitHub, GitLab, HubSpot, Lucid, Google Meet, or Zoom query without a kind already searches its default kinds; give each query on this account a kind.' @@ -154,6 +178,10 @@ export const liveSearchAccountStatusSchema = z.object({ status: z.enum(['ok', 'partial', 'reconnect', 'rate_limited', 'unavailable', 'timeout']), message: z.string().optional(), nextCursor: z.string().optional(), + folders: z + .array(z.object({ id: z.string(), name: z.string() })) + .max(10) + .optional(), retryAfterSeconds: z.number().optional(), }) export type LiveSearchAccountStatus = z.output @@ -218,7 +246,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema nativeQueries: nativeSearchQueriesSchema .optional() .describe( - `Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound or sortBy newest/oldest; Notion and Lucid always require search terms. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple queries on one account, each need a kind, which may repeat. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid searches titles with no search continuation; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.` + `Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound, sortBy newest/oldest, or explicit browse mode. Lucid browse folder lists root or a numeric folder project; Notion browse private/shared/favorites/recent lists sidebar pages. Recent means viewed, not modified. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple content queries on one account, each need a kind, which may repeat; explicit browse queries may select distinct folders or sidebar sections without a kind. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid title search has no continuation; folder browsing is paginated and returns child folders in account coverage; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.` ), query: z .string() @@ -226,7 +254,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema .max(2000) .default('') .describe( - 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported; Notion requires search terms.' + 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported, or use an explicit native browse mode. Notion date-only search depends on plan capabilities.' ), topK: z .number() @@ -247,7 +275,11 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema input.sortBy === 'newest' || input.sortBy === 'oldest' ) - if (!input.query && !input.nativeQueries?.some((query) => query.query) && !bounded) + if ( + !input.query && + !input.nativeQueries?.some((query) => query.query || query.browse) && + !bounded + ) context.addIssue({ code: 'custom', path: ['query'], @@ -263,7 +295,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema path: ['endDate'], message: 'endDate must be after startDate.', }) - if (input.nativeQueries?.some((query) => !query.query) && !bounded) + if (input.nativeQueries?.some((query) => !query.query && !query.browse) && !bounded) context.addIssue({ code: 'custom', path: ['nativeQueries'], diff --git a/apps/sim/lib/mothership/generated/tool-catalog-v1.ts b/apps/sim/lib/mothership/generated/tool-catalog-v1.ts index bf94e2d5695..6a0cc58e12e 100644 --- a/apps/sim/lib/mothership/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/mothership/generated/tool-catalog-v1.ts @@ -6097,7 +6097,7 @@ export const SearchWorkspace: ToolCatalogEntry = { }, nativeQueries: { description: - "Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound or sortBy newest/oldest; Notion and Lucid always require search terms. Up to 4 per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple queries on one account, each need a kind, which may repeat. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid searches titles with no search continuation; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.", + "Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound, sortBy newest/oldest, or explicit browse mode. Lucid browse folder lists root or a numeric folder project; Notion browse private/shared/favorites/recent lists sidebar pages. Recent means viewed, not modified. Up to 4 per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple content queries on one account, each need a kind, which may repeat; explicit browse queries may select distinct folders or sidebar sections without a kind. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid title search has no continuation; folder browsing is paginated and returns child folders in account coverage; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.", minItems: 1, maxItems: 9, type: 'array', @@ -6149,6 +6149,12 @@ export const SearchWorkspace: ToolCatalogEntry = { ], }, project: { type: 'string', minLength: 1, maxLength: 300 }, + browse: { + description: + 'Queryless discovery: Lucid folder lists one folder page (omit project for root; otherwise use a returned numeric folder ID). Notion private/shared list sidebar pages, favorites lists pinned pages, recent lists recently viewed pages, not recently modified pages. These lists are not an exhaustive workspace inventory. Follow the returned cursor with the same account, browse mode, project, filters and topK.', + type: 'string', + enum: ['folder', 'private', 'shared', 'favorites', 'recent'], + }, cursor: { type: 'string', maxLength: 4000 }, termClauses: { maxItems: 10, type: 'array', items: { type: 'string', maxLength: 500 } }, modifiers: { type: 'string', maxLength: 1000 }, @@ -6161,7 +6167,7 @@ export const SearchWorkspace: ToolCatalogEntry = { query: { default: '', description: - 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported; Notion requires search terms.', + 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported, or use an explicit native browse mode. Notion date-only search depends on plan capabilities.', type: 'string', maxLength: 2000, }, diff --git a/apps/sim/lib/mothership/generated/tool-schemas-v1.ts b/apps/sim/lib/mothership/generated/tool-schemas-v1.ts index 07020b2a423..369aee5b4cf 100644 --- a/apps/sim/lib/mothership/generated/tool-schemas-v1.ts +++ b/apps/sim/lib/mothership/generated/tool-schemas-v1.ts @@ -6043,7 +6043,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { }, nativeQueries: { description: - "Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound or sortBy newest/oldest; Notion and Lucid always require search terms. Up to 4 per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple queries on one account, each need a kind, which may repeat. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid searches titles with no search continuation; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.", + "Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound, sortBy newest/oldest, or explicit browse mode. Lucid browse folder lists root or a numeric folder project; Notion browse private/shared/favorites/recent lists sidebar pages. Recent means viewed, not modified. Up to 4 per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple content queries on one account, each need a kind, which may repeat; explicit browse queries may select distinct folders or sidebar sections without a kind. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid title search has no continuation; folder browsing is paginated and returns child folders in account coverage; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.", minItems: 1, maxItems: 9, type: 'array', @@ -6106,6 +6106,12 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { minLength: 1, maxLength: 300, }, + browse: { + description: + 'Queryless discovery: Lucid folder lists one folder page (omit project for root; otherwise use a returned numeric folder ID). Notion private/shared list sidebar pages, favorites lists pinned pages, recent lists recently viewed pages, not recently modified pages. These lists are not an exhaustive workspace inventory. Follow the returned cursor with the same account, browse mode, project, filters and topK.', + type: 'string', + enum: ['folder', 'private', 'shared', 'favorites', 'recent'], + }, cursor: { type: 'string', maxLength: 4000, @@ -6133,7 +6139,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { query: { default: '', description: - 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported; Notion requires search terms.', + 'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported, or use an explicit native browse mode. Notion date-only search depends on plan capabilities.', type: 'string', maxLength: 2000, }, diff --git a/apps/sim/lib/mothership/tools/client-executed-tools.ts b/apps/sim/lib/mothership/tools/client-executed-tools.ts index 63c506bdff3..167132015bf 100644 --- a/apps/sim/lib/mothership/tools/client-executed-tools.ts +++ b/apps/sim/lib/mothership/tools/client-executed-tools.ts @@ -14,19 +14,29 @@ export function isWorkflowToolName(name: string): boolean { } /** - * Tool calls the browser starts from the call frame's own arguments: workflow - * runs, local file access, browser actions, and terminal commands. The stream - * must deliver those arguments exactly as the model sent them. + * Client-executed calls only the desktop app can run: local file access, the agent browser, and + * the terminal. A web tab watching the same chat must leave them to the desktop app. */ -export function isClientExecutedToolCall( +export function isDesktopExecutedToolCall( name: string, args: Record | undefined ): boolean { return ( - isWorkflowToolName(name) || isNativeFileTool(name) || isUserLocalVfsToolCall(name, args) || isCurrentBrowserToolName(name) || isTerminalToolName(name) ) } + +/** + * Tool calls the browser starts from the call frame's own arguments: workflow + * runs, local file access, browser actions, and terminal commands. The stream + * must deliver those arguments exactly as the model sent them. + */ +export function isClientExecutedToolCall( + name: string, + args: Record | undefined +): boolean { + return isWorkflowToolName(name) || isDesktopExecutedToolCall(name, args) +} diff --git a/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts b/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts new file mode 100644 index 00000000000..813a3be4e0e --- /dev/null +++ b/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts @@ -0,0 +1,212 @@ +/** + * One Chat open in the desktop app and in a plain web tab at once. Both tail the same run, so a + * desktop-only tool call (the agent browser, the terminal) reaches both. Runs against real + * PostgreSQL and Redis: the web tab is the production stream tool-event path and client executor, + * the desktop speaks its production protocol (claim through the authorize route, then report + * through the confirm route), and the agent's answer is what the server-side waiter resolves. + */ +import { isCurrentBrowserToolName } from '@sim/browser-protocol' +import { authMock, authMockFns } from '@sim/testing/mocks/auth.mock' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' + +const { redisUrl, inheritedEnv } = await vi.hoisted(async () => { + const { readTestRedisUrl } = await import('@sim/db/testing/test-infrastructure') + const url = readTestRedisUrl() + const inheritedEnv = { REDIS_URL: process.env.REDIS_URL } + /** The real Redis module and the confirmation channel read this at import. */ + process.env.REDIS_URL = url + return { redisUrl: url, inheritedEnv } +}) + +vi.mock('@/lib/auth', () => authMock) + +import { db } from '@sim/db' +import { copilotAsyncToolCalls, copilotChats, copilotRuns, user, workspace } from '@sim/db/schema' +import { sleep } from '@sim/utils/helpers' +import { generateId } from '@sim/utils/id' +import { eq } from 'drizzle-orm' +import { NextRequest } from 'next/server' +import { closeRedisConnection } from '@/lib/core/config/redis' +import { SIM_TOOL_EXECUTION_VERSION } from '@/lib/mothership/async-runs/lifecycle' +import type { PersistedStreamEventEnvelope } from '@/lib/mothership/request/session/contract' +import { waitForClientToolCompletion } from '@/lib/mothership/request/tools/client' +import { executeBrowserToolOnClient } from '@/lib/mothership/tools/client/browser-tool-execution' +import { executeTerminalToolOnClient } from '@/lib/mothership/tools/client/terminal-tool-execution' +import { POST as confirmPOST } from '@/app/api/copilot/confirm/route' +import { POST as authorizePOST } from '@/app/api/desktop/tool/authorize/route' +import { dispatchStreamEvent } from '@/app/workspace/[workspaceId]/home/hooks/stream/dispatch-stream-event' +import { createStreamLoopContext } from '@/app/workspace/[workspaceId]/home/hooks/stream/stream-context' +import { makeStreamLoopDeps } from '@/app/workspace/[workspaceId]/home/hooks/stream/stream-test-helpers' + +const APP_ORIGIN = 'http://localhost:3000' +const ROUTES: Record Promise> = { + '/api/copilot/confirm': async (request) => confirmPOST(request, {}), + '/api/desktop/tool/authorize': async (request) => authorizePOST(request, {}), +} + +/** Every route request a client has started, so a test can wait for fire-and-forget reports. */ +const inFlight: Promise[] = [] + +function callRoute(path: string, { method, headers, body }: RequestInit): Promise { + const handler = ROUTES[path] + if (!handler) throw new Error(`No route fixture for ${path}`) + const response = handler(new NextRequest(new URL(path, APP_ORIGIN), { method, headers, body })) + inFlight.push(response) + return response +} + +/** The desktop app's server protocol: claim the pending call, run it, report its result. */ +async function desktopExecutes(toolCallId: string, result: Record) { + const authorization = await callRoute('/api/desktop/tool/authorize', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ toolCallId }), + }) + if (!authorization.ok) return { authorized: authorization.status, confirmed: null } + const confirmation = await callRoute('/api/copilot/confirm', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ toolCallId, status: 'success', message: 'Done', data: result }), + }) + return { authorized: authorization.status, confirmed: confirmation.status } +} + +/** + * A plain web tab receiving the call frame on its live tail: the production stream handler with + * the executors `useChat` wires into it. + */ +async function webTabReceives( + chatId: string, + toolCallId: string, + toolName: string, + args: Record +) { + const ctx = createStreamLoopContext( + makeStreamLoopDeps({ + startClientBrowserTool: (id, name, toolArgs, eventTs) => { + if (isCurrentBrowserToolName(name)) + executeBrowserToolOnClient(id, name, toolArgs, chatId, eventTs) + }, + startClientTerminalTool: (id, _name, toolArgs, eventTs) => + executeTerminalToolOnClient(id, toolArgs, chatId, eventTs), + }) + ) + const envelope: PersistedStreamEventEnvelope = { + type: 'tool', + v: 1, + seq: 1, + ts: new Date().toISOString(), + stream: { streamId: generateId(), cursor: '1' }, + payload: { + phase: 'call', + executor: 'client', + mode: 'async', + toolCallId, + toolName, + arguments: args, + status: 'executing', + }, + } + dispatchStreamEvent(ctx, envelope) + // The client executors are fire-and-forget: let any report they start reach the server. + await sleep(250) + await Promise.allSettled(inFlight) +} + +afterAll(async () => { + const channels = globalThis as typeof globalThis & { + _toolConfirmationChannel?: { dispose(): void } + } + channels._toolConfirmationChannel?.dispose() + channels._toolConfirmationChannel = undefined + await closeRedisConnection() + for (const [key, value] of Object.entries(inheritedEnv)) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } +}) + +describe.runIf(Boolean(redisUrl))('a desktop tool call watched by a web tab', () => { + const userId = generateId() + const workspaceId = generateId() + const chatId = generateId() + const runId = generateId() + + beforeAll(async () => { + const now = new Date() + await db.insert(user).values({ + id: userId, + name: 'Desktop executor fixture', + email: `${userId}@desktop-executor.test`, + emailVerified: true, + createdAt: now, + updatedAt: now, + }) + await db.insert(workspace).values({ + id: workspaceId, + name: 'Desktop executor fixture', + ownerId: userId, + billedAccountUserId: userId, + }) + await db.insert(copilotChats).values({ + id: chatId, + userId, + workspaceId, + type: 'mothership', + conversationId: generateId(), + }) + await db.insert(copilotRuns).values({ + id: runId, + executionId: generateId(), + chatId, + userId, + workspaceId, + streamId: generateId(), + toolExecutionVersion: SIM_TOOL_EXECUTION_VERSION, + status: 'paused_waiting_for_tool', + requestContext: { source: 'headless_lifecycle' }, + }) + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: userId, email: `${userId}@desktop-executor.test`, name: 'Desktop executor' }, + session: { id: generateId(), userId }, + }) + vi.stubGlobal('fetch', (input: RequestInfo | URL, init?: RequestInit) => + callRoute(new URL(String(input), APP_ORIGIN).pathname, init ?? {}) + ) + }) + + afterAll(async () => { + await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) + await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await db.delete(user).where(eq(user.id, userId)) + }) + + it.each([ + ['browser_find', { query: 'Sign in' }], + ['terminal', { operation: 'run', args: { command: 'ls' } }], + ])( + 'delivers the desktop result of %s when the web tab receives the call first', + async (toolName, args) => { + const toolCallId = generateId() + await db + .insert(copilotAsyncToolCalls) + .values({ runId, toolCallId, toolName, args, status: 'pending' }) + const agentAnswer = waitForClientToolCompletion({ + toolCallId, + runId, + userId, + timeoutMs: 10_000, + }) + + await webTabReceives(chatId, toolCallId, toolName, args) + const desktop = await desktopExecutes(toolCallId, { matches: 1 }) + expect(desktop).toEqual({ authorized: 200, confirmed: 200 }) + expect(await agentAnswer).toMatchObject({ status: 'success' }) + const [row] = await db + .select({ status: copilotAsyncToolCalls.status }) + .from(copilotAsyncToolCalls) + .where(eq(copilotAsyncToolCalls.toolCallId, toolCallId)) + expect(row.status).toBe('completed') + } + ) +}) diff --git a/apps/sim/lib/mothership/tools/server/knowledge/workspace-search.test.ts b/apps/sim/lib/mothership/tools/server/knowledge/workspace-search.test.ts index a926b851cdc..8ae1042bd73 100644 --- a/apps/sim/lib/mothership/tools/server/knowledge/workspace-search.test.ts +++ b/apps/sim/lib/mothership/tools/server/knowledge/workspace-search.test.ts @@ -155,24 +155,6 @@ describe('Assistant retrieval tools', () => { }) }) } - it.each([{ startDate: '2026-09-01T00:00:00Z' }, { sortBy: 'newest' }, { sortBy: 'oldest' }])( - 'returns actionable validation for empty Notion native queries with %j', - async (bound) => { - const result = await searchWorkspaceServerTool.execute( - { - ...bound, - query: 'fallback terms', - nativeQueries: [{ provider: 'notion', query: ' \t ' }], - }, - { ...context, assistantSearch: undefined } - ) - expect(result).toMatchObject({ - success: false, - message: 'Notion requires search terms. Add keywords or a concise question.', - }) - expect(result).not.toHaveProperty('data') - } - ) it.each([ { provider: 'slack', kind: 'meeting' }, { provider: 'google_drive', kind: 'transcript' }, diff --git a/apps/sim/lib/oauth/microsoft.ts b/apps/sim/lib/oauth/microsoft.ts index 13f85443be3..959df71cf73 100644 --- a/apps/sim/lib/oauth/microsoft.ts +++ b/apps/sim/lib/oauth/microsoft.ts @@ -16,6 +16,7 @@ export const MICROSOFT_PROVIDERS = new Set([ 'microsoft-dataverse', 'microsoft-excel', 'microsoft-planner', + 'microsoft-powerbi', 'microsoft-teams', 'microsoft-word', 'outlook', diff --git a/apps/sim/lib/oauth/oauth.test.ts b/apps/sim/lib/oauth/oauth.test.ts index b5e312a8bbb..1554aab2b6e 100644 --- a/apps/sim/lib/oauth/oauth.test.ts +++ b/apps/sim/lib/oauth/oauth.test.ts @@ -38,6 +38,8 @@ afterAll(resetEnvMock) import { buildConnectorProviders } from '@/lib/auth/connectors/providers' import { DEFAULT_MAX_ERROR_BODY_BYTES } from '@/lib/core/utils/stream-limits' import { getPerRequestOAuthLinkScopes, OAUTH_PROVIDERS, refreshOAuthToken } from '@/lib/oauth' +import { getMissingRequiredScopes } from '@/lib/oauth/utils' +import { PowerBIBlock } from '@/blocks/blocks/powerbi' /** * Default OAuth token response for successful requests. @@ -109,6 +111,50 @@ describe('Monday OAuth connector', () => { }) }) +describe('Power BI OAuth callback scope compatibility', () => { + it('preserves identity while making bare Power BI grants usable by the block', async () => { + const connector = buildConnectorProviders().find( + (item) => item.providerId === 'microsoft-powerbi' + ) + if (!connector?.getUserInfo) throw new Error('Power BI OAuth connector is not configured') + const tokens = { + accessToken: 'powerbi-access', + idToken: oauthTestJwt({ sub: 'fixture-subject', email: 'fixture@example.invalid' }), + scopes: ['Workspace.Read.All', 'Report.Read.All', 'Dataset.ReadWrite.All'], + } + const requiredScopes = PowerBIBlock.subBlocks.find( + (subBlock) => subBlock.id === 'credential' + )?.requiredScopes + if (!requiredScopes) throw new Error('Power BI credential requirements are missing') + const profile = await connector.getUserInfo(tokens) + expect(profile).toMatchObject({ email: 'fixture@example.invalid' }) + expect(getMissingRequiredScopes(tokens, requiredScopes)).toEqual([]) + expect( + getMissingRequiredScopes( + { scopes: tokens.scopes?.filter((scope) => !scope.endsWith('/Dataset.ReadWrite.All')) }, + requiredScopes + ) + ).toEqual(['https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All']) + }) + + it('does not promote a Graph-qualified lookalike into a Power BI grant', async () => { + const connector = buildConnectorProviders().find( + (item) => item.providerId === 'microsoft-powerbi' + ) + if (!connector?.getUserInfo) throw new Error('Power BI OAuth connector is not configured') + const tokens = { + accessToken: 'powerbi-access', + idToken: oauthTestJwt({ sub: 'fixture-subject', email: 'fixture@example.invalid' }), + scopes: ['https://graph.microsoft.com/Report.Read.All'], + } + await connector.getUserInfo(tokens) + expect( + getMissingRequiredScopes(tokens, ['https://analysis.windows.net/powerbi/api/Report.Read.All']) + ).toEqual(['https://analysis.windows.net/powerbi/api/Report.Read.All']) + expect(tokens.scopes).toEqual(['https://graph.microsoft.com/Report.Read.All']) + }) +}) + describe('Microsoft Dataverse OAuth connector', () => { it('keeps static connector scopes empty and supplies the canonical legacy grant per request', () => { const connector = buildConnectorProviders().find( @@ -575,6 +621,32 @@ describe('OAuth Token Refresh', () => { }) describe('Token Response Handling', () => { + it('refreshes Power BI with resource-qualified scopes before identity scopes', async () => { + const mockFetch = vi.fn(async (_url: string, init: RequestInit) => { + const scope = new URLSearchParams(init.body as string).get('scope')?.split(' ') + if ( + scope?.[0] !== 'https://analysis.windows.net/powerbi/api/Workspace.Read.All' || + !scope.includes('https://analysis.windows.net/powerbi/api/Report.Read.All') || + !scope.includes('https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All') || + !scope.includes('offline_access') + ) { + return Response.json({ error: 'invalid_scope' }, { status: 400 }) + } + return Response.json({ access_token: 'powerbi-access', expires_in: 3600 }) + }) + + const result = await withMockFetch(mockFetch, () => + refreshOAuthToken('microsoft-powerbi', 'powerbi-refresh') + ) + + expect(result).toEqual({ + ok: true, + accessToken: 'powerbi-access', + refreshToken: 'powerbi-refresh', + expiresIn: 3600, + }) + }) + it.concurrent('should bound successful token responses before parsing them', async () => { const mockFetch = vi .fn() diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 71c20c39ef2..00d64a6d5e9 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -56,6 +56,7 @@ import { NotionIcon, OutlookIcon, PipedriveIcon, + PowerBIIcon, QuickBooksIcon, RedditIcon, SalesforceIcon, @@ -445,6 +446,22 @@ export const OAUTH_PROVIDERS: Record = { 'offline_access', ], }, + 'microsoft-powerbi': { + name: 'Power BI', + description: 'Connect to Power BI and query semantic models, reports, and refresh history.', + providerId: 'microsoft-powerbi', + icon: PowerBIIcon, + baseProviderIcon: MicrosoftIcon, + scopes: [ + 'https://analysis.windows.net/powerbi/api/Workspace.Read.All', + 'https://analysis.windows.net/powerbi/api/Report.Read.All', + 'https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All', + 'openid', + 'profile', + 'email', + 'offline_access', + ], + }, 'microsoft-teams': { name: 'Microsoft Teams', description: 'Connect to Microsoft Teams and manage messages.', @@ -2346,6 +2363,11 @@ export async function refreshOAuthToken( const { headers, bodyParams, useJsonBody } = buildAuthRequest(config, refreshToken) + // Microsoft refresh tokens are resource-independent. Keep the Power BI audience explicit. + if (providerId === 'microsoft-powerbi') { + bodyParams.scope = OAUTH_PROVIDERS.microsoft.services['microsoft-powerbi'].scopes.join(' ') + } + const response = await fetch(config.tokenEndpoint, { method: 'POST', headers, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 393fcf13d7f..01750b0d171 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -65,6 +65,7 @@ export type OAuthProvider = | 'microsoft-dataverse' | 'microsoft-excel' | 'microsoft-planner' + | 'microsoft-powerbi' | 'microsoft-teams' | 'microsoft-word' | 'outlook' @@ -129,6 +130,7 @@ export type OAuthService = | 'microsoft-excel' | 'microsoft-teams' | 'microsoft-planner' + | 'microsoft-powerbi' | 'microsoft-word' | 'sharepoint' | 'outlook' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 7a1c22011a3..8982b33a499 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -12,6 +12,10 @@ import type { * Used by the OAuth Required Modal and available for any UI that needs to display scope info. */ export const SCOPE_DESCRIPTIONS: Record = { + 'https://analysis.windows.net/powerbi/api/Workspace.Read.All': 'View Power BI workspaces', + 'https://analysis.windows.net/powerbi/api/Report.Read.All': 'View Power BI reports', + 'https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All': + 'Read, create, update, refresh, and delete Power BI semantic models within your account’s permissions', 'users.profile:write': 'Update Slack user profiles', 'users.profile:read': 'View Slack user profiles', 'channels:join': 'Join public Slack channels', diff --git a/apps/sim/lib/selectors/manifest.ts b/apps/sim/lib/selectors/manifest.ts index 04d883a5c78..262c0dd9a40 100644 --- a/apps/sim/lib/selectors/manifest.ts +++ b/apps/sim/lib/selectors/manifest.ts @@ -380,6 +380,15 @@ export const selectorManifest = { search: true, detail: true, }), + 'powerbi.workspaces': providerSelector([], { listMode: 'paginated', detail: true }), + 'powerbi.datasets': providerSelector(['groupId'], { + readiness: { all: ['oauthCredential', 'groupId'] }, + detail: true, + }), + 'powerbi.reports': providerSelector(['groupId'], { + readiness: { all: ['oauthCredential', 'groupId'] }, + detail: true, + }), 'planetscale.branches': rawProviderSelector( ['serviceTokenId', 'serviceToken', 'organization', 'database'], { diff --git a/apps/sim/lib/selectors/server/providers/powerbi.test.ts b/apps/sim/lib/selectors/server/providers/powerbi.test.ts new file mode 100644 index 00000000000..d42c9984652 --- /dev/null +++ b/apps/sim/lib/selectors/server/providers/powerbi.test.ts @@ -0,0 +1,247 @@ +import { jsonResponse } from '@sim/testing/helpers/http' +import { + selectorCredentialsMock, + selectorCredentialsMockFns, +} from '@sim/testing/mocks/selector-credentials.mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { + buildSelectorContextFromValues, + getSelectorContextSubBlocks, +} from '@/lib/selectors/context' +import { createSelectorProtectedValues } from '@/lib/selectors/server/protected-values' +import { powerBISelectorAttachments } from '@/lib/selectors/server/providers/powerbi' +import type { ExecuteServerSelectorArgs } from '@/lib/selectors/server/types' +import { getDependsOnFields } from '@/lib/workflows/subblocks/dependencies' +import { buildCanonicalIndexForSurface } from '@/lib/workflows/subblocks/visibility' +import { PowerBIBlock } from '@/blocks/blocks/powerbi' + +vi.mock('@/lib/selectors/server/credentials', () => selectorCredentialsMock) + +function args(overrides: Partial = {}): ExecuteServerSelectorArgs { + return { + selectorKey: 'powerbi.workspaces', + context: { oauthCredential: 'powerbi-credential', groupId: 'workspace-one' }, + request: { kind: 'list' }, + scope: { kind: 'workspace', workspaceId: 'sim-workspace' }, + workspaceId: 'sim-workspace', + principal: { kind: 'session', userId: 'user-one', sessionId: 'session-one' }, + requesterUserId: 'user-one', + credential: { suppliedId: 'powerbi-credential', providerId: 'microsoft-powerbi' }, + references: new Map(), + protectedValues: createSelectorProtectedValues(), + ...overrides, + } +} + +describe('Power BI selector provider boundary', () => { + const fetchMock = vi.fn() + + beforeEach(() => { + fetchMock.mockReset() + vi.stubGlobal('fetch', fetchMock) + selectorCredentialsMockFns.mockResolveSelectorOAuthAccessToken.mockResolvedValue( + 'powerbi-test-token' + ) + }) + + it('advances numeric workspace offsets one page at a time and ends on a short page', async () => { + fetchMock.mockResolvedValueOnce( + jsonResponse({ + value: Array.from({ length: 100 }, (_, index) => ({ + id: `workspace-${index}`, + name: `Workspace ${index}`, + token: 'provider-secret', + })), + '@odata.nextLink': 'https://untrusted.example/continue', + }) + ) + const first = await powerBISelectorAttachments['powerbi.workspaces'].execute( + args({ request: { kind: 'list', cursor: '0' } }) + ) + expect(first).toEqual({ + kind: 'list', + items: Array.from({ length: 100 }, (_, index) => ({ + id: `workspace-${index}`, + label: `Workspace ${index}`, + })), + nextCursor: '100', + }) + fetchMock.mockResolvedValueOnce( + jsonResponse({ value: [{ id: 'workspace-last', name: 'Last workspace' }] }) + ) + const last = await powerBISelectorAttachments['powerbi.workspaces'].execute( + args({ request: { kind: 'list', cursor: first.kind === 'list' ? first.nextCursor : '' } }) + ) + expect(last).toEqual({ + kind: 'list', + items: [{ id: 'workspace-last', label: 'Last workspace' }], + }) + const urls = fetchMock.mock.calls.map(([input]) => new URL(String(input))) + expect(urls.map((url) => url.origin)).toEqual([ + 'https://api.powerbi.com', + 'https://api.powerbi.com', + ]) + expect(urls.map((url) => url.searchParams.get('$skip'))).toEqual(['0', '100']) + expect(urls.map((url) => url.searchParams.get('$top'))).toEqual(['100', '100']) + }) + + it.each(['-1', '1.5', '01', '2147483648', '9007199254740993', 'https://evil.example/page'])( + 'rejects a noncanonical or out-of-range offset: %s', + async (cursor) => { + fetchMock.mockResolvedValueOnce(jsonResponse({ value: [] })) + await expect( + powerBISelectorAttachments['powerbi.workspaces'].execute( + args({ request: { kind: 'list', cursor } }) + ) + ).rejects.toMatchObject({ name: 'SelectorContextUnavailableError' }) + } + ) + + it.each(['datasets', 'reports'] as const)( + 'binds the %s list to an encoded workspace and exposes only resource IDs and names', + async (kind) => { + fetchMock.mockResolvedValueOnce( + jsonResponse({ + value: [{ id: 'resource-id', name: 'Readable resource', accessToken: 'must-not-escape' }], + }) + ) + const key = `powerbi.${kind}` as const + const result = await powerBISelectorAttachments[key].execute( + args({ + selectorKey: key, + context: { oauthCredential: 'powerbi-credential', groupId: ' group#one ' }, + }) + ) + expect(result).toEqual({ + kind: 'list', + items: [{ id: 'resource-id', label: 'Readable resource' }], + }) + const url = new URL(String(fetchMock.mock.calls[0]?.[0])) + expect(url.pathname).toBe(`/v1.0/myorg/groups/group%23one/${kind}`) + expect(url.search).toBe('') + } + ) + + it.each([ + ['basic', 'workspaceSelector', 'workspace-basic'], + ['advanced', 'manualWorkspaceId', '{{POWERBI_WORKSPACE}}'], + ] as const)( + 'uses the active %s workspace value for semantic-model selector context', + async (mode, field, workspace) => { + const values = { + operation: 'powerbi_execute_query', + credential: 'powerbi-credential', + manualCredential: 'stale-credential', + workspaceSelector: 'stale-workspace', + manualWorkspaceId: 'stale-manual-workspace', + [field]: workspace, + } + const picker = PowerBIBlock.subBlocks.find((subBlock) => subBlock.id === 'datasetSelector') + if (!picker) throw new Error('Power BI block has no datasetSelector') + const context = buildSelectorContextFromValues({ + selectorKey: 'powerbi.datasets', + contextConfigs: getSelectorContextSubBlocks(PowerBIBlock.subBlocks, values), + values, + dependsOn: getDependsOnFields(picker.dependsOn), + canonicalIndex: buildCanonicalIndexForSurface(PowerBIBlock.subBlocks, false), + canonicalModes: { groupId: mode }, + }) + expect(context).toEqual({ oauthCredential: 'powerbi-credential', groupId: workspace }) + if (mode === 'basic') { + fetchMock.mockResolvedValueOnce( + jsonResponse({ value: [{ id: 'model-id', name: 'KPI model' }] }) + ) + const result = await powerBISelectorAttachments['powerbi.datasets'].execute( + args({ selectorKey: 'powerbi.datasets', context }) + ) + expect(result).toEqual({ kind: 'list', items: [{ id: 'model-id', label: 'KPI model' }] }) + expect(new URL(String(fetchMock.mock.calls[0]?.[0])).pathname).toBe( + '/v1.0/myorg/groups/workspace-basic/datasets' + ) + } + } + ) + + it.each(['workspaces', 'datasets', 'reports'] as const)( + 'resolves a saved %s selection through one direct encoded provider request', + async (kind) => { + fetchMock.mockResolvedValueOnce( + jsonResponse({ + id: 'resource#id', + name: 'Saved resource', + rawProviderSecret: 'must-not-escape', + }) + ) + const key = `powerbi.${kind}` as const + expect( + await powerBISelectorAttachments[key].execute( + args({ selectorKey: key, request: { kind: 'detail', id: ' resource#id ' } }) + ) + ).toEqual({ kind: 'detail', item: { id: 'resource#id', label: 'Saved resource' } }) + expect(fetchMock).toHaveBeenCalledTimes(1) + const url = new URL(String(fetchMock.mock.calls[0]?.[0])) + expect(url.origin).toBe('https://api.powerbi.com') + expect(url.pathname).toBe( + kind === 'workspaces' + ? '/v1.0/myorg/groups/resource%23id' + : `/v1.0/myorg/groups/workspace-one/${kind}/resource%23id` + ) + expect(url.search).toBe('') + expect(url.hash).toBe('') + } + ) + + it.each(['workspaces', 'datasets'] as const)( + 'returns no detail for a deleted %s selection', + async (kind) => { + fetchMock.mockResolvedValueOnce(new Response(null, { status: 404 })) + const key = `powerbi.${kind}` as const + expect( + await powerBISelectorAttachments[key].execute( + args({ selectorKey: key, request: { kind: 'detail', id: 'removed-resource' } }) + ) + ).toEqual({ kind: 'detail', item: null }) + } + ) + + it.each([ + {}, + { value: null }, + { value: [{ id: 'resource' }] }, + { value: [{ id: '', name: 'Resource' }] }, + { value: [{ id: 'resource', name: '' }] }, + { + value: Array.from({ length: 101 }, (_, index) => ({ id: String(index), name: 'Workspace' })), + }, + ])('rejects malformed or overfull workspace pages: %j', async (payload) => { + fetchMock.mockResolvedValueOnce(jsonResponse(payload)) + await expect( + powerBISelectorAttachments['powerbi.workspaces'].execute(args()) + ).rejects.toMatchObject({ + name: 'SelectorOptionsUnavailableError', + }) + }) + + it('does not accept a late successful page after cancellation', async () => { + const controller = new AbortController() + fetchMock.mockImplementationOnce(async () => { + controller.abort() + return jsonResponse({ value: [{ id: 'late', name: 'Late resource' }] }) + }) + await expect( + powerBISelectorAttachments['powerbi.workspaces'].execute(args({ signal: controller.signal })) + ).rejects.toMatchObject({ name: 'AbortError' }) + }) + + it('propagates cancellation to provider transport', async () => { + const controller = new AbortController() + fetchMock.mockImplementationOnce(async (_url, init) => { + controller.abort() + init?.signal?.throwIfAborted() + return jsonResponse({ value: [] }) + }) + await expect( + powerBISelectorAttachments['powerbi.workspaces'].execute(args({ signal: controller.signal })) + ).rejects.toMatchObject({ name: 'AbortError' }) + }) +}) diff --git a/apps/sim/lib/selectors/server/providers/powerbi.ts b/apps/sim/lib/selectors/server/providers/powerbi.ts new file mode 100644 index 00000000000..783d7f2eb7c --- /dev/null +++ b/apps/sim/lib/selectors/server/providers/powerbi.ts @@ -0,0 +1,44 @@ +import { resolveSelectorOAuthAccessToken } from '@/lib/selectors/server/credentials' +import { SelectorConnectionUnavailableError } from '@/lib/selectors/server/errors' +import type { + ExecuteServerSelectorArgs, + ServerSelectorAttachment, + ServerSelectorAttachmentMap, +} from '@/lib/selectors/server/types' +import { listPowerBIOptions, type PowerBIListingKind } from '@/tools/powerbi/listing' + +function attachment(kind: PowerBIListingKind): ServerSelectorAttachment { + return { + credential: { + kind: 'stored', + field: 'oauthCredential', + serviceIds: ['microsoft-powerbi'], + }, + destination: 'fixed', + execute: async (args: ExecuteServerSelectorArgs) => { + args.signal?.throwIfAborted() + if (!args.credential) throw new SelectorConnectionUnavailableError() + const accessToken = await resolveSelectorOAuthAccessToken({ + credential: args.credential, + serviceId: 'microsoft-powerbi', + protectedValues: args.protectedValues, + recordCredentialUse: args.recordCredentialUse, + }) + return listPowerBIOptions({ + kind, + accessToken, + groupId: args.context.groupId, + request: args.request, + signal: args.signal, + }) + }, + } +} + +export const powerBISelectorAttachments = { + 'powerbi.workspaces': attachment('workspaces'), + 'powerbi.datasets': attachment('datasets'), + 'powerbi.reports': attachment('reports'), +} satisfies ServerSelectorAttachmentMap< + 'powerbi.workspaces' | 'powerbi.datasets' | 'powerbi.reports' +> diff --git a/apps/sim/lib/selectors/server/registry.ts b/apps/sim/lib/selectors/server/registry.ts index 152ca4d97c2..ed373342b0a 100644 --- a/apps/sim/lib/selectors/server/registry.ts +++ b/apps/sim/lib/selectors/server/registry.ts @@ -26,6 +26,7 @@ import { netsuiteSelectorAttachments } from '@/lib/selectors/server/providers/ne import { notionSelectorAttachments } from '@/lib/selectors/server/providers/notion' import { pipedriveSelectorAttachments } from '@/lib/selectors/server/providers/pipedrive' import { planetScaleSelectorAttachments } from '@/lib/selectors/server/providers/planetscale' +import { powerBISelectorAttachments } from '@/lib/selectors/server/providers/powerbi' import { sharepointSelectorAttachments } from '@/lib/selectors/server/providers/sharepoint' import { slackSelectorAttachments } from '@/lib/selectors/server/providers/slack' import { snowflakeSelectorAttachments } from '@/lib/selectors/server/providers/snowflake' @@ -64,6 +65,7 @@ export const serverSelectorRegistry = { ...notionSelectorAttachments, ...pipedriveSelectorAttachments, ...planetScaleSelectorAttachments, + ...powerBISelectorAttachments, ...sharepointSelectorAttachments, ...slackSelectorAttachments, ...snowflakeSelectorAttachments, diff --git a/apps/sim/lib/selectors/types.ts b/apps/sim/lib/selectors/types.ts index 67e9256d5cc..de592ca66d4 100644 --- a/apps/sim/lib/selectors/types.ts +++ b/apps/sim/lib/selectors/types.ts @@ -22,6 +22,7 @@ export const selectorContextKeys = [ 'baseId', 'docId', 'datasetId', + 'groupId', 'serviceDeskId', 'impersonateUserEmail', 'boardId', diff --git a/apps/sim/lib/sim-search/live/application.test.ts b/apps/sim/lib/sim-search/live/application.test.ts index 92573d1f542..9626a70e9e9 100644 --- a/apps/sim/lib/sim-search/live/application.test.ts +++ b/apps/sim/lib/sim-search/live/application.test.ts @@ -395,34 +395,19 @@ describe('authorized live retrieval', () => { } ) }) - describe.each(['notion', 'lucid'] as const)('%s requires terms before dispatch', (provider) => { - it.each([ - { startDate: '2026-08-01T00:00:00Z' }, - { source: ` ${provider} `, startDate: '2026-08-01T00:00:00Z' }, - { sortBy: 'newest' as const }, - { sortBy: 'oldest' as const }, - ])('rejects a provider-only listing with %j', async (bound) => { - await expect( - searchLiveKnowledge.execute({ - principal, - input: { ...input, query: ' \t ', filters: { source: provider, ...bound } }, - }) - ).rejects.toMatchObject({ code: 'validation' }) - }) - it('rejects an empty native query even with a date bound', async () => { - await expect( - searchLiveKnowledge.execute({ - principal, - input: { - ...input, - query: 'topology', - filters: { startDate: '2026-08-01T00:00:00Z' }, - nativeQueries: [{ provider, query: ' \t ' }], - }, - }) - ).rejects.toMatchObject({ - issues: expect.arrayContaining([expect.objectContaining({ path: [0, 'query'] })]), + it('rejects an empty Lucid native query without an explicit browse mode', async () => { + await expect( + searchLiveKnowledge.execute({ + principal, + input: { + ...input, + query: 'topology', + filters: { startDate: '2026-08-01T00:00:00Z' }, + nativeQueries: [{ provider: 'lucid', query: ' \t ' }], + }, }) + ).rejects.toMatchObject({ + issues: expect.arrayContaining([expect.objectContaining({ path: [0, 'query'] })]), }) }) it.each([undefined, 'google_drive'])( diff --git a/apps/sim/lib/sim-search/live/application.ts b/apps/sim/lib/sim-search/live/application.ts index 86d3239bc79..3eb7f8e96b6 100644 --- a/apps/sim/lib/sim-search/live/application.ts +++ b/apps/sim/lib/sim-search/live/application.ts @@ -13,7 +13,6 @@ import { liveSearchProviderSchema, type NativeSearchQuery, nativeSearchQueriesSchema, - SEARCH_TERMS_REQUIRED, workspaceSearchFiltersSchema, } from '@/lib/api/contracts/mothership-assistant-tools' import { canonicalJson, fingerprint, instantScopePart } from '@/lib/api/cursor-binding' @@ -73,14 +72,20 @@ const signedContinuationSchema = boundContinuationSchema.extend({ signature: z.string().regex(/^[a-f0-9]{64}$/), }) -function continuationSignature(provider: 'hubspot' | 'zoom', value: BoundContinuation): string { +type BoundProvider = 'hubspot' | 'zoom' | 'lucid' | 'notion' + +function hasBoundContinuation(provider: string): provider is BoundProvider { + return ['hubspot', 'zoom', 'lucid', 'notion'].includes(provider) +} + +function continuationSignature(provider: BoundProvider, value: BoundContinuation): string { return hmacSha256Hex( `live-search-continuation:${provider}:${canonicalJson(value)}`, env.BETTER_AUTH_SECRET ) } -function readBoundContinuation(provider: 'hubspot' | 'zoom', value: string): BoundContinuation { +function readBoundContinuation(provider: BoundProvider, value: string): BoundContinuation { try { const prefix = `${provider}:` if (!value.startsWith(prefix) || value.length > (provider === 'hubspot' ? 512 : 4000)) @@ -96,12 +101,12 @@ function readBoundContinuation(provider: 'hubspot' | 'zoom', value: string): Bou } catch { throw new NativeSearchError( 'unavailable', - `Invalid ${provider === 'zoom' ? 'Zoom' : 'HubSpot'} cursor. Restart this search without a cursor.` + `Invalid ${provider} cursor. Restart this search without a cursor.` ) } } -function writeBoundContinuation(provider: 'hubspot' | 'zoom', value: BoundContinuation): string { +function writeBoundContinuation(provider: BoundProvider, value: BoundContinuation): string { const payload = boundContinuationSchema.parse(value) const signed = { ...payload, signature: continuationSignature(provider, payload) } const cursor = `${provider}:${Buffer.from(JSON.stringify(signed)).toString('base64url')}` @@ -358,7 +363,8 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ if ( (!input.query.trim() && !hasDateBounds(input.filters) && - !queries?.some((query) => query.query)) || + !queries?.some((query) => query.query || query.browse)) || + (!queries && input.filters?.source === 'lucid' && !input.query.trim()) || input.query.length > 2000 || !Number.isInteger(input.topK) || input.topK < 1 || @@ -366,19 +372,13 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ ) throw new OrchestrationError('validation', 'Invalid live search query or result limit') const filters = input.filters - if ( - !queries && - (filters?.source === 'notion' || filters?.source === 'lucid') && - !input.query.trim() - ) - throw new OrchestrationError('validation', SEARCH_TERMS_REQUIRED[filters.source]) if ( filters?.startDate && filters.endDate && Date.parse(filters.startDate) >= Date.parse(filters.endDate) ) throw new OrchestrationError('validation', 'endDate must be after startDate') - if (queries?.some((query) => !query.query) && !hasDateBounds(filters)) + if (queries?.some((query) => !query.query && !query.browse) && !hasDateBounds(filters)) throw new OrchestrationError('validation', 'Empty native queries require a date bound') const searchSignal = input.signal ? AbortSignal.any([input.signal, AbortSignal.timeout(20_000)]) @@ -421,7 +421,7 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ let queryNative = native let continuationScope: string | undefined let listingEndDate: string | undefined - if (account.provider === 'hubspot' || account.provider === 'zoom') { + if (hasBoundContinuation(account.provider)) { continuationScope = fingerprint( canonicalJson({ provider: account.provider, @@ -430,6 +430,9 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ account: account.id, query: (native?.query ?? input.query).trim(), kind: native?.kind, + ...(['lucid', 'notion'].includes(account.provider) + ? { browse: native?.browse, project: native?.project, topK: input.topK } + : {}), sort: requestedFilters?.sortBy ?? 'relevance', startDate: instantScopePart(requestedFilters?.startDate), endDate: instantScopePart(requestedFilters?.endDate), @@ -540,10 +543,16 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ ? 'No readable matches on this page. Continue with nextCursor for more.' : undefined, ]), + ...(page.folders + ? { + folders: page.folders.map((folder) => ({ + ...folder, + name: safeContent(folder.name, input.resultSecretRegistry), + })), + } + : {}), nextCursor: - page.nextCursor && - continuationScope && - (account.provider === 'hubspot' || account.provider === 'zoom') + page.nextCursor && continuationScope && hasBoundContinuation(account.provider) ? writeBoundContinuation(account.provider, { v: 2, scope: continuationScope, diff --git a/apps/sim/lib/sim-search/live/lucid-mcp.ts b/apps/sim/lib/sim-search/live/lucid-mcp.ts index 23dcdf357bd..34ed17a7324 100644 --- a/apps/sim/lib/sim-search/live/lucid-mcp.ts +++ b/apps/sim/lib/sim-search/live/lucid-mcp.ts @@ -1,4 +1,5 @@ import { isRecordLike } from '@sim/utils/object' +import { truncate } from '@sim/utils/string' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' import { dateSortDirection, @@ -80,6 +81,7 @@ export async function searchLucidMcp( input: NativeSearchInput ): Promise { const query = nativeText(input) + if (input.native?.browse) return browseFolder(client, input) if (!query.trim()) invalid('Lucid requires search terms, including for date-filtered searches.') const project = input.native?.project if ([...query].length > (project ? 200 : 400)) @@ -202,6 +204,100 @@ export async function searchLucidMcp( } } +/** A single folder page stays within the metadata budget and never recursively expands folders. */ +async function browseFolder( + client: ManagedSearchMcpClient, + input: NativeSearchInput +): Promise { + const native = input.native + if ( + native?.browse !== 'folder' || + nativeText(input) || + native.modifiers || + native.termClauses?.length || + native.keywordOnly + ) + invalid('Lucid folder browsing requires an empty query without search operators.') + if (native.project && !/^[1-9]\d{0,14}$/.test(native.project)) + invalid('Lucid folder browsing requires a numeric folder ID.') + if (client.hasTool?.('lucid_list_folder_contents') !== true) + invalid('Lucid folder browsing is unavailable on this connection.') + if (native.kind && !PRODUCTS.some((product) => product === native.kind)) + invalid('Lucid supports lucidchart and lucidspark document kinds.') + const limit = Math.max(1, Math.min(MAX_CANDIDATES, input.limit)) + const result = object( + await client.call('lucid_list_folder_contents', { + page_size: limit, + ...(native.project ? { folder_id: Number(native.project) } : {}), + ...(native.cursor ? { page_token: native.cursor } : {}), + }) + ) + if (!Array.isArray(result.items) || result.items.length > limit) + invalid('Lucid returned an invalid or oversized folder page; restart with a smaller page.') + if ( + result.nextPageToken !== undefined && + (typeof result.nextPageToken !== 'string' || + !result.nextPageToken || + result.nextPageToken.length > 2048 || + result.nextPageToken === native.cursor) + ) + invalid('Lucid returned an invalid folder continuation.') + const folders: NonNullable = [] + const candidates: { id: string; kind: string }[] = [] + const seen = new Set() + let dropped = false + for (const item of result.items) { + const row = object(item) + if (row.isShortcut !== false) { + dropped = true + continue + } + if (row.type === 'folder') { + const id = + typeof row.id === 'number' && Number.isSafeInteger(row.id) ? String(row.id) : string(row.id) + if (!/^[1-9]\d{0,14}$/.test(id) || typeof row.name !== 'string') { + dropped = true + continue + } + if (!seen.has(`folder:${id}`)) folders.push({ id, name: truncate(row.name, 200) }) + seen.add(`folder:${id}`) + continue + } + const id = string(row.id) + const kind = string(row.product) + if ( + row.type !== 'document' || + !UUID.test(id) || + !PRODUCTS.some((product) => product === kind) + ) { + dropped = true + continue + } + if (native.kind && native.kind !== kind) continue + if (!seen.has(id)) candidates.push({ id, kind }) + seen.add(id) + } + const documents = await mapWithConcurrency(candidates, 3, async (candidate) => { + const document = await metadata(client, candidate.id) + if (!document || document.kind !== candidate.kind) { + dropped = true + return undefined + } + return document + }) + const nextCursor = string(result.nextPageToken) || undefined + return { + documents: documents.filter((document) => document !== undefined), + folders, + nextCursor, + hasMore: Boolean(nextCursor), + partial: dropped || hasDateBounds(input.filters) || Boolean(dateSortDirection(input.filters)), + message: + 'Lucid lists one folder’s direct children, not the entire account. Child folders are navigation references: use browse folder with their ID as project. Continue the same folder with nextCursor, including when no documents matched this page. Dates and sorting apply only to this page’s current document metadata; folder names and document titles are not diagram evidence.' + + (dropped ? ' Shortcuts, unsupported items or unreadable metadata were excluded.' : ''), + } +} + function manifest(value: unknown, id: string, kind: string | undefined) { const row = object(value) const url = resource(string(row.edit_url)) diff --git a/apps/sim/lib/sim-search/live/managed-mcp-config.ts b/apps/sim/lib/sim-search/live/managed-mcp-config.ts index 6606a39581b..d5aa3b32d1a 100644 --- a/apps/sim/lib/sim-search/live/managed-mcp-config.ts +++ b/apps/sim/lib/sim-search/live/managed-mcp-config.ts @@ -4,8 +4,23 @@ export const MANAGED_SEARCH_MCP_READ_TOOLS = { fireflies: ['fireflies_get_transcripts', 'fireflies_get_transcript', 'fireflies_get_summary'], granola: ['query_granola_meetings', 'list_meetings', 'get_meetings', 'get_meeting_transcript'], hubspot: ['get_user_details', 'search_crm_objects', 'get_crm_objects'], - lucid: ['search', 'fetch', 'lucid_search_document', 'lucid_get_document_metadata'], - notion: ['notion-get-tool-access', 'notion-search', 'notion-ai-search', 'notion-fetch'], + lucid: [ + 'search', + 'fetch', + 'lucid_search_document', + 'lucid_get_document_metadata', + 'lucid_list_folder_contents', + ], + notion: [ + 'notion-get-tool-access', + 'notion-search', + 'notion-ai-search', + 'notion-fetch', + 'notion-list-private-pages', + 'notion-list-shared-pages', + 'notion-list-favorite-pages', + 'notion-list-recent-pages', + ], zoom: ['search_meetings', 'get_meeting_assets'], } as const diff --git a/apps/sim/lib/sim-search/live/managed-mcp.integration.ts b/apps/sim/lib/sim-search/live/managed-mcp.integration.ts index 3beb8aa1e8f..43a6df18f81 100644 --- a/apps/sim/lib/sim-search/live/managed-mcp.integration.ts +++ b/apps/sim/lib/sim-search/live/managed-mcp.integration.ts @@ -36,19 +36,28 @@ const RESOURCE = 'https://mcp.lucid.app/mcp/readonly' const DOCUMENT = '00000000-0000-4000-8000-000000000001' const SECOND_DOCUMENT = '00000000-0000-4000-8000-000000000002' const TITLE = 'Synthetic topology' -const editUrl = `https://lucid.app/lucidchart/${DOCUMENT}/edit` const actors = [0, 1].map(() => ({ userId: generateId(), organizationId: generateId(), credentialId: `mcp-cg-${generateId()}`, token: generateId(), + provider: 'lucid' as 'lucid' | 'notion', })) actors.push({ userId: generateId(), organizationId: actors[0].organizationId, credentialId: `mcp-cg-${generateId()}`, token: generateId(), + provider: 'lucid', }) +actors.push({ + userId: generateId(), + organizationId: generateId(), + credentialId: `mcp-cg-${generateId()}`, + token: generateId(), + provider: 'notion', +}) +const NOTION_RESOURCE = 'https://mcp.notion.com/mcp' const sessions = new Map() const serverIds = new Map() const protocols: Server[] = [] @@ -98,20 +107,32 @@ const providerServer = createServer(async (request, response) => { if (failDiscovery && params?.cursor) throw new Error('Synthetic discovery failure') return { ...(failDiscovery ? { nextCursor: 'second-page' } : {}), - tools: ['search', 'fetch', 'lucid_get_document_metadata', 'write_document'].map( - (name) => ({ - name, - inputSchema: { - type: 'object' as const, - properties: { - query: { type: 'string' }, - product: { type: 'array', items: { type: 'string' } }, - last_modified_after: { type: 'string' }, - }, - additionalProperties: name !== 'search', + tools: [ + 'search', + 'fetch', + 'lucid_get_document_metadata', + 'lucid_list_folder_contents', + 'notion-get-tool-access', + 'notion-search', + 'notion-list-private-pages', + 'notion-list-shared-pages', + 'notion-fetch', + 'write_document', + ].map((name) => ({ + name, + inputSchema: { + type: 'object' as const, + properties: { + query: { type: 'string' }, + cursor: { type: 'string' }, + limit: { type: 'number' }, + sort: { type: 'string' }, + product: { type: 'array', items: { type: 'string' } }, + last_modified_after: { type: 'string' }, }, - }) - ), + additionalProperties: name !== 'search', + }, + })), } }) protocol.setRequestHandler(CallToolRequestSchema, async ({ params }) => { @@ -119,11 +140,68 @@ const providerServer = createServer(async (request, response) => { await onTool?.(params.name, params.arguments ?? {}) const documentId = params.arguments?.query === 'second topology' || - params.arguments?.document_id === SECOND_DOCUMENT + params.arguments?.document_id === SECOND_DOCUMENT || + params.arguments?.id === SECOND_DOCUMENT ? SECOND_DOCUMENT : DOCUMENT const documentTitle = documentId === SECOND_DOCUMENT ? 'Second topology' : TITLE const documentUrl = `https://lucid.app/lucidchart/${documentId}/edit` + if (params.name === 'notion-get-tool-access') + return payload({ + current_tool_access: { + search: { status: 'available' }, + list_private_pages: { status: 'available' }, + list_shared_pages: { status: 'available' }, + }, + }) + if (params.name === 'notion-search' || params.name.startsWith('notion-list-')) + return payload({ + results: [ + { + type: 'page', + title: TITLE, + url: `https://www.notion.so/${params.arguments?.cursor ? SECOND_DOCUMENT : DOCUMENT}`, + }, + ], + ...(params.arguments?.cursor ? {} : { nextCursor: 'offset:1' }), + }) + if (params.name === 'notion-fetch') + return payload({ + id: params.arguments?.id, + text: 'Private page evidence', + page_last_edited_at: '2026-09-01T12:00:00Z', + }) + if (params.name === 'lucid_list_folder_contents' && params.arguments?.folder_id === 42) + return payload({ + items: [ + { + id: SECOND_DOCUMENT, + type: 'document', + product: 'lucidchart', + name: 'Second topology', + isShortcut: false, + }, + ], + }) + if (params.name === 'lucid_list_folder_contents') + return payload( + params.arguments?.page_token + ? { + items: [ + { + id: DOCUMENT, + type: 'document', + product: 'lucidchart', + name: TITLE, + isShortcut: false, + }, + ], + } + : { + items: [{ id: 42, type: 'folder', name: 'Architecture', isShortcut: false }], + nextPageToken: 'page-two', + } + ) if (params.name === 'search') return payload({ results: [{ id: documentId, title: documentTitle, url: documentUrl }] }) if (params.name === 'lucid_get_document_metadata') @@ -137,9 +215,9 @@ const providerServer = createServer(async (request, response) => { lastModified: '2026-09-01T12:00:00Z', }) const manifest = { - document_id: DOCUMENT, - title: TITLE, - edit_url: editUrl, + document_id: documentId, + title: documentTitle, + edit_url: documentUrl, metadata: { page_count: 1, page_region_counts: [1] }, } if (params.arguments?.metadata_only) return payload(manifest) @@ -158,7 +236,10 @@ const providerServer = createServer(async (request, response) => { requestedChunks: [ { chunkIndex: 0, - data: { nodes: [{ label: `Private diagram ${actor}` }], edges: [] }, + data: { + nodes: [{ label: `Private diagram ${actor}: ${documentTitle}` }], + edges: [], + }, }, ], }, @@ -192,12 +273,13 @@ beforeAll(async () => { origin = `http://127.0.0.1:${address.port}/mcp` const guarded = pinnedFetch.createGuardedMcpFetch vi.spyOn(pinnedFetch, 'createGuardedMcpFetch').mockImplementation((url) => { - if (url !== RESOURCE) throw new Error('Unexpected MCP fixture origin') + if (!url || ![RESOURCE, NOTION_RESOURCE].includes(url)) + throw new Error('Unexpected MCP fixture origin') const transport = guarded(origin) openTransports.add(transport) return { fetch: (input, init) => { - if (new URL(input instanceof Request ? input.url : input).href !== RESOURCE) + if (new URL(input instanceof Request ? input.url : input).href !== url) throw new Error('Unexpected MCP fixture destination') return transport.fetch(origin, init) }, @@ -238,7 +320,10 @@ beforeAll(async () => { organizationId: actor.organizationId, credentialGroupId: group.id, userId: actor.userId, - validated: { input: { connectorId: 'lucid' }, url: RESOURCE }, + validated: { + input: { connectorId: actor.provider }, + url: actor.provider === 'notion' ? NOTION_RESOURCE : RESOURCE, + }, }, tx ) @@ -278,7 +363,11 @@ beforeAll(async () => { }) await db .insert(organizationSearchIntegration) - .values({ organizationId: actor.organizationId, connectorType: 'lucid', approved: true }) + .values({ + organizationId: actor.organizationId, + connectorType: actor.provider, + approved: true, + }) .onConflictDoNothing() } }) @@ -412,6 +501,8 @@ describe('managed Search operation sessions', () => { { queryIndex: 1, status: 'ok' }, ]) expect(events.slice(offset).filter((event) => event.method === 'initialize')).toHaveLength(1) + for (const result of found.results) + expect(JSON.stringify(await read(result.documentId))).toContain(result.documentName) expect(openTransports.size).toBe(0) } finally { clearTimeout(timer) @@ -419,6 +510,209 @@ describe('managed Search operation sessions', () => { onTool = undefined } }) + it('binds folder continuations to account, folder, page size and filters', async () => { + const browse = ( + index = 0, + options: { + cursor?: string + project?: string + topK?: number + query?: string + startDate?: string + sortBy?: 'newest' + } = {} + ) => { + const actor = actors[index] + return searchLiveKnowledge.execute({ + principal: createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }), + input: { + organizationId: actor.organizationId, + query: '', + topK: options.topK ?? 10, + filters: { + source: 'lucid', + ...(options.startDate ? { startDate: options.startDate } : {}), + ...(options.sortBy ? { sortBy: options.sortBy } : {}), + }, + nativeQueries: [ + { + provider: 'lucid', + query: options.query ?? '', + ...(options.query ? {} : { browse: 'folder' as const }), + accountId: actor.credentialId, + ...(options.cursor ? { cursor: options.cursor } : {}), + ...(options.project ? { project: options.project } : {}), + }, + ], + }, + }) + } + const first = await browse() + expect(first.live?.accounts[0].folders).toEqual([{ id: '42', name: 'Architecture' }]) + const cursor = first.live?.accounts[0].nextCursor + expect(cursor).toBeTruthy() + expect((await browse(0, { cursor })).results).toHaveLength(1) + const actor = actors[0] + const parallel = await searchLiveKnowledge.execute({ + principal: createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }), + input: { + organizationId: actor.organizationId, + query: '', + topK: 10, + filters: { source: 'lucid' }, + nativeQueries: [ + { provider: 'lucid', query: '', browse: 'folder', accountId: actor.credentialId, cursor }, + { + provider: 'lucid', + query: '', + browse: 'folder', + accountId: actor.credentialId, + project: '42', + }, + ], + }, + }) + expect(parallel.results.map((result) => result.documentName).sort()).toEqual([ + 'Second topology', + TITLE, + ]) + + for (const options of [{ project: '42' }, { topK: 5 }, { startDate: '2026-09-01T00:00:00Z' }]) { + expect((await browse(0, options)).live?.accounts[0]?.status).not.toBe('unavailable') + const result = await browse(0, { ...options, cursor }) + expect(result.results).toHaveLength(0) + expect(result.live?.accounts[0]).toMatchObject({ status: 'unavailable' }) + } + expect((await browse(2, { cursor })).live?.accounts[0]).toMatchObject({ status: 'unavailable' }) + expect((await browse(0, { cursor: `${cursor}tampered` })).live?.accounts[0]).toMatchObject({ + status: 'unavailable', + }) + const sorted = await browse(0, { sortBy: 'newest' }) + expect( + (await browse(0, { sortBy: 'newest', cursor: sorted.live?.accounts[0].nextCursor })).results + ).toHaveLength(1) + expect(openTransports.size).toBe(0) + }) + it.each([ + { startDate: '2026-09-01T00:00:00Z' }, + { sortBy: 'newest' }, + { sortBy: 'oldest' }, + ] as const)( + 'rejects plain empty Lucid searches with %j before opening the provider', + async (filters) => { + const actor = actors[0] + const before = events.length + await expect( + searchLiveKnowledge.execute({ + principal: createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }), + input: { + organizationId: actor.organizationId, + query: '', + topK: 10, + filters: { source: 'lucid', ...filters }, + }, + }) + ).rejects.toMatchObject({ code: 'validation' }) + expect(events.length).toBe(before) + } + ) + it('combines Lucid folder browsing and typed title search while preserving each query outcome', async () => { + const actor = actors[0] + const found = await searchLiveKnowledge.execute({ + principal: createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }), + input: { + organizationId: actor.organizationId, + query: '', + topK: 10, + filters: { source: 'lucid' }, + nativeQueries: [ + { provider: 'lucid', query: '', browse: 'folder', accountId: actor.credentialId }, + { + provider: 'lucid', + query: 'topology', + kind: 'lucidchart', + accountId: actor.credentialId, + }, + ], + }, + }) + expect(found.results).toHaveLength(1) + expect(found.live?.accounts).toEqual([ + expect.objectContaining({ + queryIndex: 0, + folders: [{ id: '42', name: 'Architecture' }], + nextCursor: expect.any(String), + }), + expect.objectContaining({ queryIndex: 1, status: 'ok' }), + ]) + expect(openTransports.size).toBe(0) + }) + it.each(['newest', 'oldest'] as const)( + 'continues a plain Notion topical search sorted %s', + async (sortBy) => { + const actor = actors[3] + const principal = createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }) + const input = { + organizationId: actor.organizationId, + query: 'topology', + topK: 10, + filters: { source: 'notion' as const, sortBy }, + } + const first = await searchLiveKnowledge.execute({ principal, input }) + expect(first.results, JSON.stringify(first.live)).toHaveLength(1) + const cursor = first.live?.accounts[0].nextCursor + expect(cursor).toBeTruthy() + const next = await searchLiveKnowledge.execute({ + principal, + input: { + ...input, + nativeQueries: [ + { provider: 'notion', query: input.query, accountId: actor.credentialId, cursor }, + ], + }, + }) + expect(next.results, JSON.stringify(next.live)).toHaveLength(1) + expect(next.results[0].documentId).not.toBe(first.results[0].documentId) + expect(openTransports.size).toBe(0) + } + ) + it('binds Notion continuation to its sidebar mode and preserves independent page reads', async () => { + const actor = actors[3] + const browse = (mode: 'private' | 'shared', cursor?: string) => + searchLiveKnowledge.execute({ + principal: createSessionPrincipal({ userId: actor.userId, sessionId: generateId() }), + input: { + organizationId: actor.organizationId, + query: '', + topK: 10, + filters: { source: 'notion' }, + nativeQueries: [ + { + provider: 'notion', + query: '', + browse: mode, + accountId: actor.credentialId, + ...(cursor ? { cursor } : {}), + }, + ], + }, + }) + const first = await browse('private') + expect(first.results, JSON.stringify(first.live)).toHaveLength(1) + const cursor = first.live?.accounts[0].nextCursor + expect(cursor).toBeTruthy() + const next = await browse('private', cursor) + expect(next.results).toHaveLength(1) + expect(next.results[0].documentId).not.toBe(first.results[0].documentId) + expect(JSON.stringify(await read(next.results[0].documentId, 3))).toContain( + 'Private page evidence' + ) + expect((await browse('shared')).results).toHaveLength(1) + const changed = await browse('shared', cursor) + expect(changed.results).toHaveLength(0) + expect(changed.live?.accounts[0].status).toBe('unavailable') + expect(openTransports.size).toBe(0) + }) it('isolates simultaneous users and rejects another organization’s signed document reference', async () => { const results = await Promise.all([search(0), search(1)]) const documents = await Promise.all( diff --git a/apps/sim/lib/sim-search/live/notion-mcp.ts b/apps/sim/lib/sim-search/live/notion-mcp.ts index c9ab482b082..b9d095267bf 100644 --- a/apps/sim/lib/sim-search/live/notion-mcp.ts +++ b/apps/sim/lib/sim-search/live/notion-mcp.ts @@ -1,4 +1,9 @@ -import { dateSortDirection, hasDateBounds, nativeText } from '@/lib/sim-search/live/dates' +import { + dateSortDirection, + hasDateBounds, + nativeDateBounds, + nativeText, +} from '@/lib/sim-search/live/dates' import { array, NativeSearchError, object, string } from '@/lib/sim-search/live/http' import type { ManagedSearchMcpClient } from '@/lib/sim-search/live/managed-mcp' import type { NativeDocument, NativePage, NativeSearchInput } from '@/lib/sim-search/live/types' @@ -59,17 +64,49 @@ function searchDocument(row: Record): NativeDocument | undefine } } +const BROWSE_TOOLS = { + private: 'notion-list-private-pages', + shared: 'notion-list-shared-pages', + favorites: 'notion-list-favorite-pages', + recent: 'notion-list-recent-pages', +} as const + /** Tool visibility and workspace-plan access are separate; the current access map owns routing. */ -async function searchTool(client: ManagedSearchMcpClient): Promise { +async function searchTool( + client: ManagedSearchMcpClient, + browse?: NativeSearchInput['native'] +): Promise<{ tool: string; restrictions: Record }> { const access = object(object(await client.call('notion-get-tool-access', {})).current_tool_access) - const ai = string(object(access.ai_search).status) - const keyword = string(object(access.search).status) + if (browse?.browse) { + if (!(browse.browse in BROWSE_TOOLS)) + throw new NativeSearchError('unavailable', 'Unsupported Notion browse mode.') + const name = BROWSE_TOOLS[browse.browse as keyof typeof BROWSE_TOOLS] + const entry = object(access[name.replace('notion-', '').replaceAll('-', '_')]) + if ( + client.hasTool?.(name) !== true || + !['available', 'available_with_limit'].includes(string(entry.status)) + ) + throw new NativeSearchError( + 'unavailable', + 'Notion browsing is unavailable for this connection.' + ) + return { tool: name, restrictions: object(entry.restricted_parameters) } + } + const ai = object(access.ai_search) + const keyword = object(access.search) const exposed = (name: string) => client.hasTool?.(name) !== false - if (ai === 'available' && exposed('notion-ai-search')) return 'notion-ai-search' - if (['available', 'available_with_limit'].includes(keyword) && exposed('notion-search')) - return 'notion-search' - if (['upgrade_required', 'plan_required'].includes(ai) && exposed('notion-ai-search')) - return 'notion-ai-search' + if (ai.status === 'available' && exposed('notion-ai-search')) + return { tool: 'notion-ai-search', restrictions: object(ai.restricted_parameters) } + if ( + ['available', 'available_with_limit'].includes(string(keyword.status)) && + exposed('notion-search') + ) + return { tool: 'notion-search', restrictions: object(keyword.restricted_parameters) } + if ( + ['upgrade_required', 'plan_required'].includes(string(ai.status)) && + exposed('notion-ai-search') + ) + return { tool: 'notion-ai-search', restrictions: object(ai.restricted_parameters) } throw new NativeSearchError( 'unavailable', 'Notion search is unavailable for this connection. Check the enabled tools and workspace plan.' @@ -81,24 +118,63 @@ export async function searchNotionMcp( input: NativeSearchInput ): Promise { const query = nativeText(input) - if (!query) + const browsing = Boolean(input.native?.browse) + if ( + browsing && + (query || + input.native?.project || + input.native?.modifiers || + input.native?.termClauses?.length || + input.native?.keywordOnly) + ) throw new NativeSearchError( 'unavailable', - 'Notion requires search terms. Use short keywords or a concise question, then narrow by dates.' + 'Notion sidebar browsing requires an empty query without page scope or search operators.' ) - const tool = await searchTool(client) + const { tool, restrictions } = await searchTool(client, input.native) + const supported = (path: string) => + client.hasArgument?.(tool, path) === true && + !Object.keys(restrictions).some((key) => path === key || path.startsWith(`${key}.`)) const localFilters = hasDateBounds(input.filters) || Boolean(dateSortDirection(input.filters)) - const args: Record = { query, query_type: 'internal' } - if (input.native?.project) { - const scope = notionResource(input.native.project) - if (!scope || !client.hasArgument?.(tool, 'page_url')) + const args: Record = browsing ? {} : { query, query_type: 'internal' } + let providerDates = false + if (!browsing) { + if (input.native?.project) { + const scope = notionResource(input.native.project) + if (!scope || !supported('page_url')) + throw new NativeSearchError( + 'unavailable', + 'Notion page scope requires a Notion page URL or ID and a connection advertising page_url. Search by page title if scoped search is unavailable.' + ) + args.page_url = scope.url + } + const bounds = nativeDateBounds(input) + const start = + bounds.start && supported('filters.last_edited_date_range.start_date') + ? bounds.start + : undefined + const end = + bounds.end && supported('filters.last_edited_date_range.end_date') ? bounds.end : undefined + if (start || end) { + // Date-only provider bounds have no timezone; widen candidate days before exact local checks. + const day = (value: string, offset: number) => + new Date(Date.parse(value) + offset * 86_400_000).toISOString().slice(0, 10) + args.filters = { + last_edited_date_range: { + ...(start ? { start_date: day(start, -1) } : {}), + ...(end ? { end_date: day(end, 2) } : {}), + }, + } + providerDates = true + } + if (input.filters?.sortBy === 'newest' && supported('sort')) args.sort = 'last_edited' + if (!query && !providerDates && !args.sort) throw new NativeSearchError( 'unavailable', - 'Notion page scope requires a Notion page URL or ID and a connection advertising page_url. Search by page title if scoped search is unavailable.' + 'Notion date-only search requires supported modification filters or newest sorting on the workspace plan. Use an explicit private, shared, favorites or recent browse mode, or supply search terms.' ) - args.page_url = scope.url } - const limit = Math.min(input.limit, localFilters ? 10 : 50) + const limit = Math.max(1, Math.min(input.limit, localFilters ? 10 : 50)) if (client.hasArgument?.(tool, 'page_size')) args.page_size = limit else if (client.hasArgument?.(tool, 'limit')) args.limit = limit const cursorKey = client.hasArgument?.(tool, 'cursor') @@ -152,7 +228,17 @@ export async function searchNotionMcp( } documents = hydrated } - const next = string(result.next_cursor ?? result.nextCursor) + const continuation = result.next_cursor ?? result.nextCursor + if ( + continuation !== undefined && + continuation !== null && + (typeof continuation !== 'string' || + !continuation || + continuation.length > 2048 || + continuation === input.native?.cursor) + ) + throw new NativeSearchError('unavailable', 'Notion returned an invalid search continuation.') + const next = string(continuation) const nextCursor = cursorKey && next && !clipped ? next : undefined const notices = array(result.notices).length > 0 const aiSearch = @@ -174,7 +260,9 @@ export async function searchNotionMcp( cappedWithoutCoverage || (hasMore && !nextCursor), message: - 'Notion searches page content with the connected member’s current access. Only Notion pages and databases are returned; connected-app results are excluded. Read important matches before relying on them.' + + (browsing + ? `Notion lists ${input.native?.browse} sidebar pages and databases, not an exhaustive workspace inventory. Recent means viewed/frequency, not last modified. List entries are navigation metadata; read pages for content evidence.` + : 'Notion searches page content with the connected member’s current access. Only Notion pages and databases are returned; connected-app results are excluded. Read important matches before relying on them.') + (tool === 'notion-search' ? ' This connection uses keyword search; use short, specific title or content terms.' : '') + @@ -184,8 +272,11 @@ export async function searchNotionMcp( (notices ? ' Notion returned plan or search notices; requested coverage may be limited.' : '') + + (providerDates + ? ' Provider modification-day filters narrow candidates; exact timestamps are checked from current page metadata.' + : '') + (localFilters - ? ' Dates and sorting use freshly fetched page modification timestamps for at most 10 candidates; this does not exhaustively search a date range.' + ? ' Dates and sorting use freshly fetched page modification timestamps for at most 10 candidates; this does not exhaustively search a date range or establish global oldest/newest results.' : '') + (hasMore && !nextCursor ? ' More results may exist, but no safe continuation is available. Narrow the query.' diff --git a/apps/sim/lib/sim-search/live/provider-discovery.integration.ts b/apps/sim/lib/sim-search/live/provider-discovery.integration.ts new file mode 100644 index 00000000000..3e9fdac5e6a --- /dev/null +++ b/apps/sim/lib/sim-search/live/provider-discovery.integration.ts @@ -0,0 +1,361 @@ +import { createServer } from 'node:http' +import type { AddressInfo } from 'node:net' +import { Server } from '@modelcontextprotocol/sdk/server/index.js' +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' +import { generateId } from '@sim/utils/id' +import { toRecord } from '@sim/utils/object' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' +import { nativeSearchQuerySchema } from '@/lib/api/contracts/mothership-assistant-tools' +import { McpClient } from '@/lib/mcp/client' +import { compileMcpToolSchema } from '@/lib/mcp/tool-schema' +import type { McpToolSchemaProperty } from '@/lib/mcp/types' +import { searchLucidMcp } from '@/lib/sim-search/live/lucid-mcp' +import type { ManagedSearchMcpClient } from '@/lib/sim-search/live/managed-mcp' +import { managedMcpPayload } from '@/lib/sim-search/live/managed-mcp-payload' +import { searchNotionMcp } from '@/lib/sim-search/live/notion-mcp' + +const ID = '00000000-0000-4000-8000-000000000001' +const ID2 = '00000000-0000-4000-8000-000000000002' +const stamp = '2026-09-20T12:00:00Z' +const folder = { id: 42, type: 'folder', name: 'Architecture', isShortcut: false } +const lucidDocument = { + id: ID, + type: 'document', + name: 'Topology', + product: 'lucidchart', + isShortcut: false, +} +const notionDocument = { type: 'page', title: 'Decisions', url: `https://www.notion.so/${ID}` } +const field = { type: 'string' } +const tool = ( + name: string, + properties: Record, + required: string[] = [] +) => ({ + name, + inputSchema: { type: 'object' as const, properties, required, additionalProperties: false }, +}) +const schemas = [ + tool('lucid_list_folder_contents', { + folder_id: { type: 'integer' }, + page_size: { type: 'integer', minimum: 1, maximum: 200 }, + page_token: field, + }), + tool('lucid_get_document_metadata', { document_id: field }, ['document_id']), + tool('notion-get-tool-access', {}), + ...['private', 'shared', 'favorite', 'recent'].map((list) => + tool(`notion-list-${list}-pages`, { limit: { type: 'number' }, cursor: field }) + ), + ...['notion-search', 'notion-ai-search'].map((name) => + tool( + name, + { + query: field, + query_type: { enum: ['internal'] }, + page_size: { type: 'integer', maximum: 50 }, + page_url: field, + sort: { enum: ['relevance', 'last_edited', 'created'] }, + filters: { + type: 'object', + properties: { + last_edited_date_range: { + type: 'object', + properties: { + start_date: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' }, + end_date: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' }, + }, + }, + }, + additionalProperties: false, + }, + }, + ['query'] + ) + ), + tool('notion-fetch', { id: field }, ['id']), +] +let restricted = false +let providerOffset = 0 +let ai = false +let mode = '' +let calls = 0 +const requests: { name: string; args: Record }[] = [] +const protocol = new Server( + { name: 'discovery-fixture', version: '1' }, + { capabilities: { tools: {} } } +) +const payload = (data: unknown) => ({ + content: [{ type: 'text' as const, text: JSON.stringify(data) }], +}) +protocol.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: schemas })) +protocol.setRequestHandler(CallToolRequestSchema, async ({ params }) => { + calls++ + if (calls > 12) throw Error('Exceeded per-query provider budget') + const args = params.arguments ?? {} + requests.push({ name: params.name, args }) + const schema = schemas.find((value) => value.name === params.name) + if (!schema || !compileMcpToolSchema(schema.inputSchema)(args)) + throw Error('Provider rejected arguments') + if (params.name === 'lucid_list_folder_contents') { + if (mode === 'oversize') + return payload({ + items: Array.from({ length: 11 }, () => lucidDocument), + nextPageToken: 'after-omitted', + }) + if (mode === 'invalid-cursor') return payload({ items: [], nextPageToken: 123 }) + if (mode === 'unsafe') + return payload({ + items: [ + { ...lucidDocument, isShortcut: true }, + { id: 'https://evil.invalid', type: 'folder', name: 'Unsafe', isShortcut: false }, + ], + }) + if (args.folder_id === 42) return payload({ items: [{ ...lucidDocument, id: ID2 }] }) + return payload( + args.page_token ? { items: [lucidDocument] } : { items: [folder], nextPageToken: 'page-two' } + ) + } + if (params.name === 'lucid_get_document_metadata') + return payload({ + documentId: args.document_id, + title: 'Topology', + product: 'lucidchart', + viewUrl: `https://lucid.app/lucidchart/${args.document_id}/view`, + version: 7, + pageCount: 1, + lastModified: stamp, + }) + if (params.name === 'notion-get-tool-access') + return payload({ + current_tool_access: { + [ai ? 'ai_search' : 'search']: { + status: 'available', + restricted_parameters: restricted + ? { + [mode === 'restricted-child' + ? 'filters.last_edited_date_range.start_date' + : 'filters.last_edited_date_range']: 'Plan restriction', + sort: 'Plan restriction', + } + : {}, + }, + ...Object.fromEntries( + ['private', 'shared', 'favorite', 'recent'].map((list) => [ + `list_${list}_pages`, + { status: 'available' }, + ]) + ), + }, + }) + if (params.name.startsWith('notion-list-')) + return payload( + args.cursor + ? { results: [{ ...notionDocument, url: `https://www.notion.so/${ID2}` }] } + : { results: [notionDocument], nextCursor: 'offset:1' } + ) + if (params.name === 'notion-fetch') + return payload({ + id: args.id, + text: 'Authoritative page content', + page_last_edited_at: + mode === 'date-boundary' + ? args.id === ID + ? '2026-09-19T11:00:00Z' + : '2026-09-20T10:59:59Z' + : stamp, + }) + if (params.name === 'notion-search' || params.name === 'notion-ai-search') { + const dates = toRecord(toRecord(args.filters).last_edited_date_range) + if (mode === 'restricted-child' && dates.start_date) throw Error('Restricted field was sent') + if (!args.query && !args.sort && !Object.keys(dates).length) + throw Error('Empty search has no constraint') + if (mode === 'date-boundary') { + const candidates = [ + { ...notionDocument, edited: '2026-09-19T11:00:00Z' }, + { ...notionDocument, url: `https://www.notion.so/${ID2}`, edited: '2026-09-20T10:59:59Z' }, + ] + return payload({ + results: candidates.filter((row) => { + const date = new Date(Date.parse(row.edited) + providerOffset * 3_600_000) + .toISOString() + .slice(0, 10) + return ( + (typeof dates.start_date !== 'string' || date >= dates.start_date) && + (typeof dates.end_date !== 'string' || date < dates.end_date) + ) + }), + }) + } + return payload({ + type: ai ? 'ai_search' : 'workspace_search', + results: [notionDocument], + ...(mode === 'notice' ? { notices: [{ message: 'Filter was ignored' }] } : {}), + }) + } + throw Error('Unexpected provider operation') +}) +const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: generateId }) +const server = createServer((req, res) => { + void transport.handleRequest(req, res).catch(() => res.end()) +}) +let client: McpClient +let available = new Set(schemas.map((value) => value.name)) +const reader: ManagedSearchMcpClient = { + hasTool: (name) => available.has(name), + hasArgument(name, path) { + let schema: Record = toRecord( + schemas.find((value) => value.name === name)?.inputSchema + ) + for (const key of path.split('.')) { + schema = toRecord(toRecord(schema.properties)[key]) + if (!Object.keys(schema).length) return false + } + return true + }, + async call(name, args) { + return managedMcpPayload( + await client.callTool( + { name, arguments: args }, + { signal: AbortSignal.timeout(5000), timeoutMs: 5000 } + ), + name.startsWith('notion') ? 'Notion' : 'Lucid' + ) + }, +} +const browse = (provider: string, mode: string, extra = {}) => ({ + query: '', + limit: 10, + scopes: [], + native: nativeSearchQuerySchema.parse({ provider, query: '', browse: mode, ...extra }), +}) +beforeAll(async () => { + await protocol.connect(transport) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + client = new McpClient({ + config: { + id: 'discovery-fixture', + name: 'Discovery', + transport: 'streamable-http', + url: `http://127.0.0.1:${(server.address() as AddressInfo).port}/mcp`, + authType: 'none', + }, + resolvedIP: '127.0.0.1', + securityPolicy: { requireConsent: false, auditLevel: 'none' }, + }) + await client.connect() + await client.listTools() +}) +beforeEach(() => { + calls = 0 + requests.length = 0 + mode = '' + restricted = false + ai = false + available = new Set(schemas.map((value) => value.name)) +}) +afterAll(async () => { + await client?.disconnect() + await protocol.close() + await transport.close() + await new Promise((resolve) => { + server.close(() => resolve()) + server.closeAllConnections() + }) +}) + +describe('Provider discovery over real MCP HTTP', () => { + it('lists Lucid root folders, continues empty document pages, and reads a selected child folder', async () => { + const first = await searchLucidMcp(reader, browse('lucid', 'folder')) + expect(first).toMatchObject({ + documents: [], + folders: [{ id: '42', name: 'Architecture' }], + nextCursor: 'page-two', + }) + calls = 0 + const next = await searchLucidMcp( + reader, + browse('lucid', 'folder', { cursor: first.nextCursor }) + ) + expect(next.documents.map((item) => item.id)).toEqual([ID]) + calls = 0 + const child = await searchLucidMcp(reader, browse('lucid', 'folder', { project: '42' })) + expect(child.documents.map((item) => item.id)).toEqual([ID2]) + }) + for (const failure of ['oversize', 'invalid-cursor']) + it(`rejects ${failure} folder pages instead of issuing a lossy continuation`, async () => { + mode = failure + await expect(searchLucidMcp(reader, browse('lucid', 'folder'))).rejects.toThrow() + }) + it('excludes shortcuts and malformed folder identities with incomplete coverage', async () => { + mode = 'unsafe' + const page = await searchLucidMcp(reader, browse('lucid', 'folder')) + expect(page).toMatchObject({ documents: [], partial: true }) + expect(page.folders ?? []).toEqual([]) + }) + for (const list of ['private', 'shared', 'favorites', 'recent']) + it(`browses Notion ${list} with cursor without guessing keywords`, async () => { + const first = await searchNotionMcp(reader, browse('notion', list)) + expect(first.documents.map((item) => item.id)).toEqual([ID]) + expect(first.nextCursor).toBe('offset:1') + calls = 0 + const next = await searchNotionMcp( + reader, + browse('notion', list, { cursor: first.nextCursor }) + ) + expect(next.documents.map((item) => item.id)).toEqual([ID2]) + expect(requests.some((call) => call.name === 'notion-search')).toBe(false) + }) + it('refuses a browse tool missing from the connection', async () => { + available.delete('notion-list-private-pages') + await expect(searchNotionMcp(reader, browse('notion', 'private'))).rejects.toThrow( + /unavailable|advertise/i + ) + }) + it.each([-12, 14])( + 'retains boundary candidates with provider day offset %s and returns current timestamps', + async (offset) => { + providerOffset = offset + mode = 'date-boundary' + const page = await searchNotionMcp(reader, { + query: '', + limit: 10, + scopes: [], + filters: { + startDate: '2026-09-20T01:00:00+14:00', + endDate: '2026-09-21T01:00:00+14:00', + sortBy: 'newest', + }, + }) + expect(page.documents.map((document) => [document.id, document.modifiedAt])).toEqual([ + [ID, '2026-09-19T11:00:00Z'], + [ID2, '2026-09-20T10:59:59Z'], + ]) + expect(requests.find((call) => call.name === 'notion-search')?.args.filters).toBeTruthy() + expect(requests.find((call) => call.name === 'notion-search')?.args.sort).toBe('last_edited') + } + ) + it.each(['restricted-parent', 'restricted-child'])( + 'refuses date-only search when the plan restricts %s', + async (restriction) => { + mode = restriction + restricted = true + await expect( + searchNotionMcp(reader, { + query: '', + limit: 10, + scopes: [], + filters: { startDate: stamp, sortBy: 'newest' }, + }) + ).rejects.toThrow(/plan|supported|available/i) + } + ) + it('preserves topical AI routing and reports dropped provider constraints', async () => { + ai = true + mode = 'notice' + const page = await searchNotionMcp(reader, { query: 'launch decisions', limit: 10, scopes: [] }) + expect(page.documents[0]?.id).toBe(ID) + expect(page.partial).toBe(true) + expect(requests.some((call) => call.name === 'notion-ai-search')).toBe(true) + }) +}) diff --git a/apps/sim/lib/sim-search/live/providers.ts b/apps/sim/lib/sim-search/live/providers.ts index 177e8613ee5..1f74acef8f5 100644 --- a/apps/sim/lib/sim-search/live/providers.ts +++ b/apps/sim/lib/sim-search/live/providers.ts @@ -64,7 +64,7 @@ export const LIVE_SEARCH_PROVIDERS = { transport: 'managed_mcp', guide: { syntax: - 'Nonempty document-title keywords, at most 400 characters. Results are relevance-ranked, not guaranteed literal title matches. The provider returns at most 200 relevance-ranked candidates; Sim verifies metadata for at most 10. Search has no continuation and cannot enumerate the account. For a browse-all request without a title or topic, ask for one instead of guessing keywords.', + 'Nonempty document-title keywords, at most 400 characters. Results are relevance-ranked, not guaranteed literal title matches. The provider returns at most 200 relevance-ranked candidates; Sim verifies metadata for at most 10. Title search has no continuation. To discover available documents without guessing keywords, use native query {provider: lucid, query: empty string, browse: folder}; omit project for the root folder, or pass a returned numeric folder ID. Each page lists direct children, with child folders in account coverage; follow the cursor with the same account, mode, project, filters and topK. Do not claim recursive or whole-account completeness.', scope: 'kind lucidchart or lucidspark selects a product; omit to search both. To search shape text within a known document, set project to its UUID or Lucid URL and use one literal substring of at most 200 characters. Dates use modification time; sorting and end dates apply only to retrieved candidates, not the entire account.', example: 'deployment architecture', @@ -264,9 +264,9 @@ export const LIVE_SEARCH_PROVIDERS = { transport: 'managed_mcp', guide: { syntax: - 'Natural-language or plain keyword content search through Notion MCP. Search terms are required even with dates or sorting; this search cannot enumerate the account. For a browse-all request without a title or topic, ask for one instead of guessing keywords. Availability depends on the connected account and plan; results are restricted to Notion pages, excluding connected apps.', + 'Natural-language or plain keyword content search through Notion MCP. For navigation without a topic, use a queryless native browse mode: private or shared for sidebar pages, favorites for pinned pages, recent for recently viewed pages. These bounded, paginated lists are not an exhaustive workspace inventory; recent is not last modified. Follow the account cursor with the same browse mode, account, filters and topK. Availability depends on the connected account and plan; results are restricted to Notion pages, excluding connected apps.', scope: - 'project optionally takes a known Notion page URL when the advertised tool supports page scoping. Dates use explicit last-edited timestamps; results without those timestamps cannot satisfy date filters. Read a result for page content.', + 'project optionally takes a known Notion page URL when the advertised tool supports page scoping. Modification filters and newest sorting are pushed to the provider only if the advertised schema and plan support them. Date-only search requires those capabilities; use a sidebar browse mode or terms otherwise. Exact date checks use freshly fetched last-edited timestamps for at most 10 candidates; oldest is local ordering, not global oldest discovery. Read a result for page content.', example: 'deployment rollback checklist', avoid: 'Treating REST title search as full-content search, unsupported boolean qualifiers, claiming exhaustive results, or assuming advanced filters were applied when the provider reports they were dropped.', diff --git a/apps/sim/lib/sim-search/live/types.ts b/apps/sim/lib/sim-search/live/types.ts index 54afa4a6704..7f582aabfa5 100644 --- a/apps/sim/lib/sim-search/live/types.ts +++ b/apps/sim/lib/sim-search/live/types.ts @@ -61,6 +61,8 @@ export interface NativeDocument { export interface NativePage { documents: NativeDocument[] + /** Provider folder references are navigation hints, not readable document identities. */ + folders?: { id: string; name: string }[] /** Continues this exact query and account; implies more results exist. */ nextCursor?: string /** More matches exist beyond this page but cannot be continued through it. */ diff --git a/apps/sim/lib/workflows/application/read-workflow-version.ts b/apps/sim/lib/workflows/application/read-workflow-version.ts index defbb97b811..8d506dba323 100644 --- a/apps/sim/lib/workflows/application/read-workflow-version.ts +++ b/apps/sim/lib/workflows/application/read-workflow-version.ts @@ -36,6 +36,7 @@ export interface ReadWorkflowVersionInput { workflowId: string assertedWorkspaceId?: string version: number | 'active' + expectedDeploymentVersionId?: string /** Stored snapshots remain pinned; comparison previews apply the current runtime migrations. */ representation?: 'stored' | 'comparison' /** @@ -52,7 +53,11 @@ export const readWorkflowVersion = defineAuthorizedWorkflowUseCase({ resolveContext: resolvePrincipalWorkflowContext, async execute({ principal, input, context }) { const version = await getWorkflowDeploymentVersion(context.workflowId, input.version) - if (!version?.state) { + if ( + !version?.state || + (input.expectedDeploymentVersionId !== undefined && + version.id !== input.expectedDeploymentVersionId) + ) { throw new OrchestrationError('not_found', 'Deployment version not found') } if (!isWorkflowState(version.state)) { diff --git a/apps/sim/lib/workflows/deployment-outbox.test.ts b/apps/sim/lib/workflows/deployment-outbox.test.ts index eb9e6593736..3ec1bff2595 100644 --- a/apps/sim/lib/workflows/deployment-outbox.test.ts +++ b/apps/sim/lib/workflows/deployment-outbox.test.ts @@ -1,5 +1,6 @@ import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { auditMock, auditMockFns } from '@sim/testing/mocks/audit.mock' +import { dbChainMock } from '@sim/testing/mocks/database.mock' import { outboxServiceMock } from '@sim/testing/mocks/outbox-service.mock' import { posthogServerMock, posthogServerMockFns } from '@sim/testing/mocks/posthog-server.mock' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -26,7 +27,6 @@ const { mockDeleteInactiveDeploymentSchedules, mockGetProtectedDeploymentVersionId, mockIsDeploymentVersionActive, - mockTx, } = vi.hoisted(() => ({ mockPrepareWebhooks: vi.fn(), mockGetDeploymentOperation: vi.fn(), @@ -49,7 +49,6 @@ const { mockDeleteInactiveDeploymentSchedules: vi.fn(), mockGetProtectedDeploymentVersionId: vi.fn(), mockIsDeploymentVersionActive: vi.fn(), - mockTx: { select: vi.fn(), update: vi.fn(), execute: vi.fn() }, })) vi.mock('@sim/audit', () => auditMock) @@ -116,6 +115,8 @@ import { WORKFLOW_DEPLOYMENT_OUTBOX_EVENTS, } from '@/lib/workflows/deployment-outbox' +const mockTx = dbChainMock.db + const mockRecordAudit = auditMockFns.mockRecordAudit const mockCaptureServerEvent = posthogServerMockFns.mockCaptureServerEvent diff --git a/apps/sim/lib/workflows/deployment-outbox.ts b/apps/sim/lib/workflows/deployment-outbox.ts index 65e22702e44..2eadd2f9112 100644 --- a/apps/sim/lib/workflows/deployment-outbox.ts +++ b/apps/sim/lib/workflows/deployment-outbox.ts @@ -66,6 +66,7 @@ import { } from '@/lib/workflows/schedules' import { emitWorkflowDeployedEvent } from '@/lib/workspace-events/emitter' import type { WorkspaceOperationReport } from '@/lib/workspaces/operations/receipts' +import { activateForkSyncProvenance } from '@/ee/workspace-forking/lib/promote/sync-provenance' import type { BlockState } from '@/stores/workflows/workflow/types' const logger = createLogger('WorkflowDeploymentOutbox') @@ -515,7 +516,7 @@ async function prepareDeploymentOperation( workflowId: payload.workflowId, operationId: payload.operationId, generation: payload.generation, - onActivateTransaction: async (tx) => { + onActivateTransaction: async (tx, activatedOperation) => { context.signal.throwIfAborted() await activateWebhookRegistrations(tx, { workflowId: payload.workflowId, @@ -535,6 +536,7 @@ async function prepareDeploymentOperation( notify: false, throwOnError: true, }) + await activateForkSyncProvenance(tx, activatedOperation) context.signal.throwIfAborted() }, }) diff --git a/apps/sim/lib/workflows/orchestration/deploy.test.ts b/apps/sim/lib/workflows/orchestration/deploy.test.ts index 1f5bba01a72..1c190f5e7ff 100644 --- a/apps/sim/lib/workflows/orchestration/deploy.test.ts +++ b/apps/sim/lib/workflows/orchestration/deploy.test.ts @@ -21,6 +21,7 @@ const { mockPrepareWorkflowDeployment, mockPrepareWorkflowVersionActivation, mockGetWorkflowDeploymentStatus, + mockGetDeploymentOperation, mockEnqueueWorkflowDeploymentPreparation, mockProcessWorkflowDeploymentOutboxEvent, mockNotifySocketDeploymentChanged, @@ -32,6 +33,7 @@ const { mockPrepareWorkflowDeployment: vi.fn(), mockPrepareWorkflowVersionActivation: vi.fn(), mockGetWorkflowDeploymentStatus: vi.fn(), + mockGetDeploymentOperation: vi.fn(), mockEnqueueWorkflowDeploymentPreparation: vi.fn(), mockProcessWorkflowDeploymentOutboxEvent: vi.fn(), mockNotifySocketDeploymentChanged: vi.fn(), @@ -55,6 +57,7 @@ vi.mock('@/lib/workflows/deployment-outbox', () => ({ vi.mock('@/lib/workflows/persistence/deployment-operations', () => ({ getWorkflowDeploymentStatus: mockGetWorkflowDeploymentStatus, + getDeploymentOperation: mockGetDeploymentOperation, prepareWorkflowDeployment: mockPrepareWorkflowDeployment, prepareWorkflowVersionActivation: mockPrepareWorkflowVersionActivation, })) @@ -241,14 +244,16 @@ describe('performFullDeploy workspace event emission', () => { await input.onPrepareTransaction?.(mockTx, operation) return { success: true, operation, reused: false } }) - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: { deploymentVersionId: 'dv-1', version: 4, deployedAt: now, }, latestOperation: operation, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) }) it('marks the latest active operation historical when no matching version is live', async () => { @@ -402,10 +407,12 @@ describe('performFullDeploy workspace event emission', () => { return { success: true, operation, reused: false } }) mockProcessWorkflowDeploymentOutboxEvent.mockResolvedValue('pending') - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: null, latestOperation: operation, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) const result = await performFullDeploy({ workflowId: 'workflow-1', @@ -468,14 +475,16 @@ describe('performFullDeploy workspace event emission', () => { return { success: true, operation, reused: false } }) mockProcessWorkflowDeploymentOutboxEvent.mockResolvedValue('pending') - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: { deploymentVersionId: 'dv-live', version: 4, deployedAt: now, }, latestOperation: operation, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) const result = await performFullDeploy({ workflowId: 'workflow-1', @@ -552,7 +561,7 @@ describe('performFullDeploy workspace event emission', () => { return { success: true, operation, reused: false } }) mockProcessWorkflowDeploymentOutboxEvent.mockResolvedValue('completed') - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: null, latestOperation: { ...operation, @@ -561,7 +570,9 @@ describe('performFullDeploy workspace event emission', () => { errorMessage: 'Webhook path "/leads" is already in use. Choose a different path.', completedAt: now, }, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) const result = await performFullDeploy({ workflowId: 'workflow-1', @@ -618,14 +629,16 @@ describe('performActivateVersion workspace event emission', () => { await input.onPrepareTransaction?.(mockTx, operation) return { success: true, operation, reused: false } }) - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: { deploymentVersionId: 'dv-2', version: 2, deployedAt: now, }, latestOperation: operation, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) }) it('commits optional metadata inside activation admission before enqueueing work', async () => { @@ -679,7 +692,7 @@ describe('performActivateVersion workspace event emission', () => { name: 'Release 2', description: null, }) - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: null, latestOperation: { id: 'operation-activate-default', @@ -701,7 +714,9 @@ describe('performActivateVersion workspace event emission', () => { createdAt: failedAt, updatedAt: failedAt, }, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) const result = await performActivateVersion({ workflowId: 'workflow-1', @@ -754,14 +769,16 @@ describe('performActivateVersion workspace event emission', () => { return { success: true, operation, reused: false } }) mockProcessWorkflowDeploymentOutboxEvent.mockResolvedValue('pending') - mockGetWorkflowDeploymentStatus.mockResolvedValue({ + const deploymentStatus = { activeDeployment: { deploymentVersionId: 'dv-1', version: 1, deployedAt: now, }, latestOperation: operation, - }) + } + mockGetWorkflowDeploymentStatus.mockResolvedValue(deploymentStatus) + mockGetDeploymentOperation.mockResolvedValue(deploymentStatus.latestOperation) const result = await performActivateVersion({ workflowId: 'workflow-1', diff --git a/apps/sim/lib/workflows/orchestration/deploy.ts b/apps/sim/lib/workflows/orchestration/deploy.ts index e43a9829634..5f9fe84f0ad 100644 --- a/apps/sim/lib/workflows/orchestration/deploy.ts +++ b/apps/sim/lib/workflows/orchestration/deploy.ts @@ -32,6 +32,7 @@ import { processWorkflowDeploymentOutboxEvent, } from '@/lib/workflows/deployment-outbox' import { + getDeploymentOperation, getWorkflowDeploymentStatus, prepareWorkflowDeployment, prepareWorkflowVersionActivation, @@ -261,11 +262,22 @@ async function performStableFullDeploy(params: { const prepared = await prepareWorkflowSnapshotDeployment({ ...params, workflowState }) if (!prepared.success) return prepared - const outboxEventId = prepared.outboxEventId + return finishPreparedWorkflowDeployment(prepared, params.requestId) +} - const processResult = await processStableDeploymentPreparationNow(outboxEventId, params.requestId) - const deploymentStatus = await getWorkflowDeploymentStatus(params.params.workflowId) - const inlineFailure = buildInlinePreparationFailure(prepared.operation.id, deploymentStatus) +/** Processes the exact admitted deployment without reading a newer editor draft. */ +export async function finishPreparedWorkflowDeployment( + prepared: { operation: WorkflowDeploymentOperation; outboxEventId?: string }, + requestId: string +): Promise { + const processResult = await processStableDeploymentPreparationNow( + prepared.outboxEventId, + requestId + ) + const [deploymentStatus, inlineFailure] = await Promise.all([ + getWorkflowDeploymentStatus(prepared.operation.workflowId), + getInlinePreparationFailure(prepared.operation), + ]) if (inlineFailure) return inlineFailure const result = buildStableDeploymentResult(deploymentStatus, processResult) /** @@ -282,23 +294,35 @@ async function performStableFullDeploy(params: { } /** - * Surfaces a synchronous failure when the attempt created by this request - * already failed terminally, so callers get an error response instead of a - * success payload with a buried failed status. + * Reports terminal failure for the admitted attempt, even after a newer + * operation has become the workflow's latest attempt. */ -function buildInlinePreparationFailure( - operationId: string, - status: WorkflowDeploymentStatus -): { success: false; error: string; errorCode: OrchestrationErrorCode } | null { - const latest = status.latestOperation - if (!latest || latest.id !== operationId || latest.status !== 'failed') return null +async function getInlinePreparationFailure( + admitted: WorkflowDeploymentOperation +): Promise<{ success: false; error: string; errorCode: OrchestrationErrorCode } | null> { + const operation = await getDeploymentOperation({ + workflowId: admitted.workflowId, + operationId: admitted.id, + generation: admitted.generation, + }) + if (!operation) { + return { success: false, error: 'Deployment operation not found', errorCode: 'not_found' } + } + if (operation.status === 'superseded') { + return { + success: false, + error: 'Deployment was superseded by a newer operation', + errorCode: 'conflict', + } + } + if (operation.status !== 'failed') return null return { success: false, - error: latest.errorMessage || 'Deployment preparation failed', + error: operation.errorMessage || 'Deployment preparation failed', errorCode: - latest.errorCode === DEPLOYMENT_ERROR_CODES.webhookPathConflict + operation.errorCode === DEPLOYMENT_ERROR_CODES.webhookPathConflict ? 'conflict' - : latest.errorCode === DEPLOYMENT_ERROR_CODES.invalidTriggerConfiguration + : operation.errorCode === DEPLOYMENT_ERROR_CODES.invalidTriggerConfiguration ? 'validation' : 'internal', } @@ -866,8 +890,10 @@ async function performStableVersionActivation(params: { } const processResult = await processStableDeploymentPreparationNow(outboxEventId, params.requestId) - const status = await getWorkflowDeploymentStatus(params.workflowId) - const inlineFailure = buildInlinePreparationFailure(prepared.operation.id, status) + const [status, inlineFailure] = await Promise.all([ + getWorkflowDeploymentStatus(params.workflowId), + getInlinePreparationFailure(prepared.operation), + ]) if (inlineFailure) return { ...inlineFailure, ...metadata } const result = buildStableDeploymentResult(status, processResult) return { diff --git a/apps/sim/lib/workspace-files/text-lines.test.ts b/apps/sim/lib/workspace-files/text-lines.test.ts index 15830cee287..d4ba1276d0f 100644 --- a/apps/sim/lib/workspace-files/text-lines.test.ts +++ b/apps/sim/lib/workspace-files/text-lines.test.ts @@ -25,6 +25,12 @@ describe('file text line windows', () => { } } ) + it('reads offset 0 as the first line and reports the 1-based offset it used', () => { + expect(sliceFileTextLines('one\ntwo\nthree', 0, 2, false)).toEqual({ + text: 'one\ntwo', + lineRange: { offset: 1, lineCount: 2, totalLines: 3, totalLinesExact: true }, + }) + }) it('reads a late window in a newline-dense document', () => { const result = sliceFileTextLines( `${'abc\n'.repeat(1_000_000)}tail needle\n`, diff --git a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts index bfd53dbe599..a84026527d4 100644 --- a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert/strict' import { AuditAction } from '@sim/audit' import { db } from '@sim/db' import { @@ -16,6 +17,7 @@ import { workflowDeploymentVersion, workspace, workspaceForkResourceMap, + workspaceForkWorkflowSync, workspaceOperationReceipt, workspaceSandbox, } from '@sim/db/schema' @@ -24,8 +26,15 @@ import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { withWorkspaceInvocationScope } from '@/lib/core/application/workspace-invocation-scope' import { processOutboxEventById } from '@/lib/core/outbox/service' +import * as workflowMcpSync from '@/lib/mcp/workflow-mcp-sync' import { createScopedCliTransport } from '@/lib/mothership/agent-cli/scoped-transport' +import { readWorkflowVersion } from '@/lib/workflows/application/read-workflow-version' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' +import { + finishPreparedWorkflowDeployment, + performActivateVersion, + performFullDeploy, +} from '@/lib/workflows/orchestration/deploy' import { performCreateWorkflowTransition } from '@/lib/workflows/orchestration/workflow-lifecycle' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' import { admitWorkflowState, saveAdmittedWorkflowState } from '@/lib/workflows/persistence/utils' @@ -38,8 +47,13 @@ import { previewWorkspaceSync, syncWorkspace, } from '@/ee/workspace-forking/application/create-and-sync' +import { + rollbackWorkspaceFork, + unlinkWorkspaceFork, +} from '@/ee/workspace-forking/application/recovery-and-mappings' import { assertForkSourceVersions } from '@/ee/workspace-forking/application/revision' import { setForkSyncDefault } from '@/ee/workspace-forking/application/sync-default' +import { getWorkspaceSyncDetails } from '@/ee/workspace-forking/application/sync-details' import { copyForkResourceContainers, copyForkResourceContent, @@ -217,6 +231,9 @@ describe('authorized fork and sync against PostgreSQL', () => { } const preview = await previewWorkspaceSync.execute({ principal, input }) expect(preview.ready).toBe(true) + expect(preview.workflows[0]).toMatchObject({ + comparison: { status: 'unavailable', reason: 'no_baseline' }, + }) const apply = { ...input, requestId: generateId(), @@ -229,6 +246,10 @@ describe('authorized fork and sync against PostgreSQL', () => { const report = results[0].operation! expect(report.applied).toBe(true) expect(report.deploymentOperationIds).toHaveLength(1) + const pending = await previewWorkspaceSync.execute({ principal, input }) + expect(pending.workflows[0]).toMatchObject({ + comparison: { status: 'unavailable', reason: 'no_baseline' }, + }) const [attempt] = await db .select() .from(workflowDeploymentOperation) @@ -259,6 +280,20 @@ describe('authorized fork and sync against PostgreSQL', () => { .where(eq(workflowDeploymentVersion.id, attempt.deploymentVersionId)) expect(active.isActive).toBe(true) expect(active.state).toEqual(version.state) + const synced = await previewWorkspaceSync.execute({ principal, input }) + const [sourceVersion] = await db + .select({ id: workflowDeploymentVersion.id, version: workflowDeploymentVersion.version }) + .from(workflowDeploymentVersion) + .where( + and( + eq(workflowDeploymentVersion.workflowId, sourceWorkflowId), + eq(workflowDeploymentVersion.isActive, true) + ) + ) + expect(synced.workflows[0]).toMatchObject({ + sourceWorkflowId, + comparison: { status: 'available', base: sourceVersion, target: sourceVersion }, + }) const [identity] = await db .select() @@ -293,6 +328,230 @@ describe('authorized fork and sync against PostgreSQL', () => { .where(eq(workflowDeploymentVersion.workflowId, attempt.workflowId)) ).toEqual([{ id: attempt.deploymentVersionId }]) }) + it('keeps exact directed baselines through activation, supersession, undo, retention, and source rollback', async () => { + const childId = await createChild() + const siblingId = await createChild() + const input = { + workspaceId: sourceWorkspaceId, + otherWorkspaceId: childId, + direction: 'push' as const, + } + const preview = () => previewWorkspaceSync.execute({ principal, input }) + const comparison = async () => { + const [item] = (await preview()).workflows + if (item.action === 'archive') throw new Error('Fixture source must remain deployed') + return item.comparison + } + const admit = async () => { + const current = await preview() + const result = await syncWorkspace.execute({ + principal, + input: { + ...input, + requestId: generateId(), + previewFingerprint: current.previewFingerprint, + }, + }) + assert(result.operation) + return result.operation + } + const [initial] = await db + .select() + .from(workflowDeploymentVersion) + .where( + and( + eq(workflowDeploymentVersion.workflowId, sourceWorkflowId), + eq(workflowDeploymentVersion.isActive, true) + ) + ) + const first = { id: initial.id, version: initial.version } + const seeded = await admit() + await finishDeployments(seeded) + const targetId = seeded.resourceIds[0] + const sibling = await previewWorkspaceSync.execute({ + principal, + input: { ...input, otherWorkspaceId: siblingId }, + }) + expect(sibling.workflows[0]).toMatchObject({ + comparison: { status: 'unavailable', reason: 'no_baseline' }, + }) + const reverse = await previewWorkspaceSync.execute({ + principal, + input: { workspaceId: childId, otherWorkspaceId: sourceWorkspaceId, direction: 'push' }, + }) + expect(reverse.workflows[0]).toMatchObject({ + comparison: { status: 'unavailable', reason: 'no_baseline' }, + }) + + const sourceDeploy = await performFullDeploy({ workflowId: sourceWorkflowId, userId }) + expect(sourceDeploy.success).toBe(true) + assert(sourceDeploy.deploymentVersionId && typeof sourceDeploy.version === 'number') + const second = { id: sourceDeploy.deploymentVersionId, version: sourceDeploy.version } + expect(second.id).not.toBe(first.id) + expect(await comparison()).toEqual({ + status: 'available', + base: first, + target: second, + }) + const pending = await admit() + expect(await comparison()).toEqual({ + status: 'available', + base: first, + target: second, + }) + expect((await performFullDeploy({ workflowId: targetId, userId })).success).toBe(true) + await finishDeployments(pending) + const pendingOperationId = pending.deploymentOperationIds?.[0] + assert(pendingOperationId) + const [superseded] = await db + .select() + .from(workflowDeploymentOperation) + .where(eq(workflowDeploymentOperation.id, pendingOperationId)) + expect(superseded.status).toBe('superseded') + expect( + await finishPreparedWorkflowDeployment({ operation: superseded }, generateId()) + ).toMatchObject({ success: false, errorCode: 'conflict' }) + expect(await comparison()).toEqual({ + status: 'available', + base: first, + target: second, + }) + + const details = await getWorkspaceSyncDetails.execute({ principal, input }) + const synced = await syncWorkspace.execute({ + principal, + input: { ...input, expectedSourceVersions: details.sourceVersions }, + }) + expect(synced.redeployed).toBe(1) + expect(await comparison()).toEqual({ + status: 'available', + base: second, + target: second, + }) + const blockedCutover = vi + .spyOn(workflowMcpSync, 'syncMcpToolsForWorkflow') + .mockRejectedValue(new Error('Rollback cutover temporarily unavailable')) + try { + const pendingUndo = await rollbackWorkspaceFork.execute({ + principal, + input: { workspaceId: childId, otherWorkspaceId: sourceWorkspaceId }, + }) + expect(pendingUndo.pendingActivations).toEqual([targetId]) + expect(await comparison()).toEqual({ + status: 'available', + base: second, + target: second, + }) + } finally { + blockedCutover.mockRestore() + } + const undone = await rollbackWorkspaceFork.execute({ + principal, + input: { workspaceId: childId, otherWorkspaceId: sourceWorkspaceId }, + }) + expect(undone.pendingActivations).toEqual([]) + expect(await comparison()).toEqual({ + status: 'available', + base: first, + target: second, + }) + expect((await syncWorkspace.execute({ principal, input })).redeployed).toBe(1) + expect(await comparison()).toEqual({ + status: 'available', + base: second, + target: second, + }) + + expect((await performFullDeploy({ workflowId: targetId, userId })).success).toBe(true) + await db + .delete(workflowDeploymentOperation) + .where( + and( + eq(workflowDeploymentOperation.workflowId, targetId), + sql`${workflowDeploymentOperation.generation} < (SELECT max(generation) FROM workflow_deployment_operation WHERE workflow_id = ${targetId})` + ) + ) + expect(await comparison()).toEqual({ + status: 'available', + base: second, + target: second, + }) + expect( + ( + await performActivateVersion({ + workflowId: sourceWorkflowId, + version: first.version, + userId, + }) + ).success + ).toBe(true) + expect(await comparison()).toEqual({ + status: 'available', + base: second, + target: first, + }) + + await db.delete(workflowDeploymentVersion).where(eq(workflowDeploymentVersion.id, second.id)) + expect(await comparison()).toEqual({ + status: 'unavailable', + reason: 'missing_baseline', + target: first, + }) + const replacement = await performFullDeploy({ workflowId: sourceWorkflowId, userId }) + expect(replacement.success).toBe(true) + expect(replacement.version).toBe(second.version) + expect(replacement.deploymentVersionId).not.toBe(second.id) + await expect( + readWorkflowVersion.execute({ + principal, + input: { + workflowId: sourceWorkflowId, + version: second.version, + expectedDeploymentVersionId: second.id, + }, + }) + ).rejects.toMatchObject({ code: 'not_found' }) + expect(await comparison()).toMatchObject({ + status: 'unavailable', + reason: 'missing_baseline', + }) + expect((await syncWorkspace.execute({ principal, input })).redeployed).toBe(1) + await db + .delete(workflowDeploymentOperation) + .where(eq(workflowDeploymentOperation.workflowId, targetId)) + const latest = await performFullDeploy({ workflowId: sourceWorkflowId, userId }) + expect(latest.success).toBe(true) + expect((await syncWorkspace.execute({ principal, input })).redeployed).toBe(1) + assert(latest.deploymentVersionId && typeof latest.version === 'number') + const newest = { id: latest.deploymentVersionId, version: latest.version } + expect(await comparison()).toEqual({ + status: 'available', + base: newest, + target: newest, + }) + + const syncHistory = () => + db + .select({ id: workspaceForkWorkflowSync.deploymentOperationId }) + .from(workspaceForkWorkflowSync) + .where(eq(workspaceForkWorkflowSync.childWorkspaceId, childId)) + expect((await syncHistory()).length).toBeGreaterThan(0) + await unlinkWorkspaceFork.execute({ + principal, + input: { workspaceId: childId, otherWorkspaceId: sourceWorkspaceId }, + }) + expect(await syncHistory()).toEqual([]) + expect( + await db + .select({ parent: workspace.forkedFromWorkspaceId }) + .from(workspace) + .where(eq(workspace.id, childId)) + ).toEqual([{ parent: null }]) + expect( + await db.select({ id: workflow.id }).from(workflow).where(eq(workflow.id, targetId)) + ).toEqual([{ id: targetId }]) + }) + it.each([ { acting: 'child', direction: 'pull' as const }, { acting: 'child', direction: 'push' as const }, @@ -488,6 +747,25 @@ describe('authorized fork and sync against PostgreSQL', () => { expect(fresh.ready).toBe(true) expect(fresh.previewFingerprint).not.toBe(preview.previewFingerprint) expect(await readSandboxMappings()).toHaveLength(0) + await expect( + syncWorkspace.execute({ + principal, + input: { + ...input, + expectedSourceVersions: [ + { workflowId: sourceWorkflowId, deploymentVersionId: generateId() }, + ], + }, + }) + ).rejects.toMatchObject({ + details: expect.objectContaining({ applied: false, reason: 'stale_preview' }), + }) + expect(await readSandboxMappings()).toHaveLength(0) + const [stillUnchanged] = await db + .select() + .from(workflowBlocks) + .where(and(eq(workflowBlocks.workflowId, target.id), eq(workflowBlocks.type, 'function'))) + expect(stillUnchanged.name).toBe('Concurrent sandbox draft edit') const result = await syncWorkspace.execute({ principal, input: { ...input, requestId, previewFingerprint: fresh.previewFingerprint }, diff --git a/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts b/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts index 0a76531d69a..ce4f836e5be 100644 --- a/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts @@ -19,10 +19,16 @@ import { eq } from 'drizzle-orm' import { NextRequest } from 'next/server' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { hashApiKey } from '@/lib/api-key/crypto' +import { processOutboxEventById } from '@/lib/core/outbox/service' +import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' +import { performFullDeploy } from '@/lib/workflows/orchestration/deploy' +import { workspaceOperationOutboxHandlers } from '@/lib/workspaces/operations/outbox' +import type { WorkspaceOperationReport } from '@/lib/workspaces/operations/receipts' import { GET as logGet } from '@/app/api/v2/logs/[runId]/route' import { POST as importPreview } from '@/app/api/v2/workflows/import/preview/route' import { POST as importApply } from '@/app/api/v2/workflows/import/route' import { POST as forkPreview } from '@/app/api/v2/workspaces/[workspaceId]/fork/preview/route' +import { POST as pushPreview } from '@/app/api/v2/workspaces/[workspaceId]/fork/push/preview/route' import { POST as pushApply } from '@/app/api/v2/workspaces/[workspaceId]/fork/push/route' import { POST as forkApply } from '@/app/api/v2/workspaces/[workspaceId]/fork/route' import { GET as operationGet } from '@/app/api/v2/workspaces/[workspaceId]/operations/[operationId]/route' @@ -201,18 +207,20 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { ? await forkPreview(request, context) : match?.[2] === 'fork' ? await forkApply(request, context) - : match?.[2] === 'fork/push' - ? await pushApply(request, context) - : match?.[2] === 'operations' - ? await operationsList(request, context) - : match?.[2].startsWith('operations/') - ? await operationGet(request, { - params: Promise.resolve({ - workspaceId: match[1], - operationId: match[2].slice('operations/'.length), - }), - }) - : new Response('Unknown fixture route', { status: 404 }) + : match?.[2] === 'fork/push/preview' + ? await pushPreview(request, context) + : match?.[2] === 'fork/push' + ? await pushApply(request, context) + : match?.[2] === 'operations' + ? await operationsList(request, context) + : match?.[2].startsWith('operations/') + ? await operationGet(request, { + params: Promise.resolve({ + workspaceId: match[1], + operationId: match[2].slice('operations/'.length), + }), + }) + : new Response('Unknown fixture route', { status: 404 }) outgoing.statusCode = response.status response.headers.forEach((value, name) => outgoing.setHeader(name, value)) const body = await response.text() @@ -394,7 +402,14 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { expect(requestCount).toBe(before) }) - it('creates a draft fork through the CLI and follows its operation receipt', async () => { + it('creates a draft fork and exposes the activated same-source comparison through the CLI', async () => { + const [sourceWorkflow] = await db + .select() + .from(workflow) + .where(eq(workflow.workspaceId, workspaceId)) + .limit(1) + const deployed = await performFullDeploy({ workflowId: sourceWorkflow.id, userId }) + expect(deployed.success).toBe(true) const preview = await cli(['workspaces', 'fork-preview', '--name', 'CLI fork fixture']) expect(preview.code, preview.stderr).toBe(0) const created = await cli([ @@ -415,5 +430,40 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { const drafts = await db.select().from(workflow).where(eq(workflow.workspaceId, childId)) expect(drafts.length).toBeGreaterThan(0) expect(drafts.every((draft) => !draft.isDeployed)).toBe(true) + const previewArgs = ['workspaces', 'push-preview', '--other-workspace-id', childId] + const before = await cli(previewArgs) + expect(before.code, before.stderr).toBe(0) + const previewData = JSON.parse(before.stdout) + expect(previewData.workflows[0]).toMatchObject({ + sourceWorkflowId: sourceWorkflow.id, + comparison: { status: 'unavailable', reason: 'no_baseline' }, + }) + const pushed = await cli([ + 'workspaces', + 'push', + '--other-workspace-id', + childId, + '--preview-fingerprint', + previewData.previewFingerprint, + '--request-id', + generateId(), + '--yes', + ]) + expect(pushed.code, pushed.stderr).toBe(0) + const [receipt] = await db + .select() + .from(workspaceOperationReceipt) + .where(eq(workspaceOperationReceipt.id, JSON.parse(pushed.stdout).operationId)) + const admitted = receipt.report as WorkspaceOperationReport + const registry = { ...workflowDeploymentOutboxHandlers, ...workspaceOperationOutboxHandlers } + for (const eventId of admitted.effectEventIds ?? []) + expect(await processOutboxEventById(eventId, registry)).toBe('completed') + const after = await cli(previewArgs) + expect(after.code, after.stderr).toBe(0) + const version = { id: deployed.deploymentVersionId, version: deployed.version } + expect(JSON.parse(after.stdout).workflows[0]).toMatchObject({ + sourceWorkflowId: sourceWorkflow.id, + comparison: { status: 'available', base: version, target: version }, + }) }) }) diff --git a/apps/sim/package.json b/apps/sim/package.json index 3bd7ed252be..f537910f724 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -77,8 +77,8 @@ "@cerebras/cerebras_cloud_sdk": "^1.23.0", "@daytona/sdk": "0.207.0", "@e2b/code-interpreter": "2.7.1", - "@earendil-works/pi-ai": "0.80.10", - "@earendil-works/pi-coding-agent": "0.80.10", + "@earendil-works/pi-ai": "1.0.0", + "@earendil-works/pi-coding-agent": "1.0.0", "@floating-ui/dom": "1.7.6", "@google-cloud/storage": "7.21.0", "@google/genai": "2.13.0", diff --git a/apps/sim/providers/pi-model-catalog.generated.ts b/apps/sim/providers/pi-model-catalog.generated.ts index e08acce2e65..3468c93e70c 100644 --- a/apps/sim/providers/pi-model-catalog.generated.ts +++ b/apps/sim/providers/pi-model-catalog.generated.ts @@ -5,19 +5,21 @@ export const PI_MODEL_IDS_BY_PROVIDER = { anthropic: [ 'claude-fable-5', + 'claude-fable-5-1', 'claude-haiku-4-5', 'claude-haiku-4-5-20251001', - 'claude-opus-4-1', - 'claude-opus-4-1-20250805', 'claude-opus-4-5', 'claude-opus-4-5-20251101', 'claude-opus-4-6', 'claude-opus-4-7', 'claude-opus-4-8', + 'claude-opus-5', + 'claude-opus-5-5', 'claude-sonnet-4-5', 'claude-sonnet-4-5-20250929', 'claude-sonnet-4-6', 'claude-sonnet-5', + 'claude-sonnet-5-5', ], openai: [ 'gpt-4', @@ -32,18 +34,12 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'gpt-4o-mini', 'gpt-5', 'gpt-5-chat-latest', - 'gpt-5-codex', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5-pro', 'gpt-5.1', - 'gpt-5.1-chat-latest', - 'gpt-5.1-codex', - 'gpt-5.1-codex-max', - 'gpt-5.1-codex-mini', 'gpt-5.2', 'gpt-5.2-chat-latest', - 'gpt-5.2-codex', 'gpt-5.2-pro', 'gpt-5.3-chat-latest', 'gpt-5.3-codex', @@ -57,36 +53,46 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'gpt-5.6-luna', 'gpt-5.6-sol', 'gpt-5.6-terra', + 'gpt-6-astra', + 'gpt-6-luna', + 'gpt-6-sol', + 'gpt-6.1-sol', + 'gpt-daybreak-blue-latest', + 'gpt-daybreak-red-latest', 'gpt-realtime-2.1', 'o1', 'o1-pro', 'o3', - 'o3-deep-research', 'o3-mini', 'o3-pro', 'o4-mini', - 'o4-mini-deep-research', ], google: [ - 'gemini-2.0-flash', - 'gemini-2.0-flash-lite', + 'deep-research-max-preview-04-2026', + 'deep-research-preview-04-2026', + 'gemini-2.5-computer-use-preview-10-2025', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-pro', 'gemini-3-flash-preview', - 'gemini-3-pro-preview', 'gemini-3.1-flash-lite', + 'gemini-3.1-flash-lite-image', 'gemini-3.1-flash-lite-preview', + 'gemini-3.1-flash-live-preview', 'gemini-3.1-pro-preview', 'gemini-3.1-pro-preview-customtools', 'gemini-3.5-flash', + 'gemini-3.5-flash-lite', + 'gemini-3.6-flash', + 'gemini-3.7-flash', + 'gemini-3.8-flash', 'gemini-flash-latest', 'gemini-flash-lite-latest', 'gemma-4-26b-a4b-it', 'gemma-4-31b-it', ], - xai: ['grok-4.3', 'grok-4.5', 'grok-build-0.1'], - deepseek: ['deepseek-v4-flash', 'deepseek-v4-pro'], + xai: ['grok-4.3', 'grok-4.5', 'grok-4.6', 'grok-4.7'], + deepseek: ['deepseek-flash', 'deepseek-v4-pro'], mistral: [ 'codestral-latest', 'devstral-2512', @@ -97,7 +103,6 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'devstral-small-2507', 'labs-devstral-small-2512', 'magistral-medium-latest', - 'magistral-small', 'ministral-3b-latest', 'ministral-8b-latest', 'mistral-large-2411', @@ -118,49 +123,70 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'open-mixtral-8x7b', 'pixtral-12b', 'pixtral-large-latest', + 'voxtral-small-latest', + 'zai-glm-5-2', + 'zai-glm-5-3', ], groq: [ 'llama-3.1-8b-instant', 'llama-3.3-70b-versatile', - 'meta-llama/llama-4-scout-17b-16e-instruct', 'openai/gpt-oss-120b', 'openai/gpt-oss-20b', 'openai/gpt-oss-safeguard-20b', - 'qwen/qwen3-32b', + 'qwen/qwen3.6-27b', + 'qwen/qwen3.8-27b', ], - cerebras: ['gemma-4-31b', 'gpt-oss-120b', 'zai-glm-4.7'], + cerebras: ['gpt-oss-120b', 'qwen-3.8-27b'], openrouter: [ - 'ai21/jamba-large-1.7', 'aion-labs/aion-2.0', 'aion-labs/aion-3.0', 'aion-labs/aion-3.0-mini', + 'aion-labs/aion-3.5', + 'aion-labs/aion-3.5-mini', 'amazon/nova-2-lite-v1', 'amazon/nova-lite-v1', 'amazon/nova-micro-v1', 'amazon/nova-premier-v1', 'amazon/nova-pro-v1', - 'anthropic/claude-3-haiku', 'anthropic/claude-fable-5', + 'anthropic/claude-fable-5.1', + 'anthropic/claude-fable-5.1:batch', + 'anthropic/claude-fable-5:batch', 'anthropic/claude-haiku-4.5', - 'anthropic/claude-opus-4', + 'anthropic/claude-haiku-4.5:batch', 'anthropic/claude-opus-4.1', + 'anthropic/claude-opus-4.1:batch', 'anthropic/claude-opus-4.5', + 'anthropic/claude-opus-4.5:batch', 'anthropic/claude-opus-4.6', + 'anthropic/claude-opus-4.6:batch', 'anthropic/claude-opus-4.7', - 'anthropic/claude-opus-4.7-fast', + 'anthropic/claude-opus-4.7:batch', 'anthropic/claude-opus-4.8', - 'anthropic/claude-opus-4.8-fast', + 'anthropic/claude-opus-4.8:batch', + 'anthropic/claude-opus-5', + 'anthropic/claude-opus-5.5', + 'anthropic/claude-opus-5.5:batch', + 'anthropic/claude-opus-5:batch', 'anthropic/claude-sonnet-4', 'anthropic/claude-sonnet-4.5', + 'anthropic/claude-sonnet-4.5:batch', 'anthropic/claude-sonnet-4.6', + 'anthropic/claude-sonnet-4.6:batch', 'anthropic/claude-sonnet-5', + 'anthropic/claude-sonnet-5.5', + 'anthropic/claude-sonnet-5.5:batch', + 'anthropic/claude-sonnet-5:batch', + 'apodex/apodex-1.1-mini:free', 'arcee-ai/trinity-large-thinking', - 'arcee-ai/virtuoso-large', 'auto', 'bytedance-seed/seed-1.6', 'bytedance-seed/seed-1.6-flash', + 'bytedance-seed/seed-2-1-turbo', + 'bytedance-seed/seed-2.0-code', 'bytedance-seed/seed-2.0-lite', 'bytedance-seed/seed-2.0-mini', + 'cohere/command-a-plus', 'cohere/command-r-08-2024', 'cohere/command-r-plus-08-2024', 'cohere/north-mini-code:free', @@ -173,40 +199,67 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'deepseek/deepseek-v3.2', 'deepseek/deepseek-v3.2-exp', 'deepseek/deepseek-v4-flash', + 'deepseek/deepseek-v4-flash-0731', + 'deepseek/deepseek-v4-flash-vision-exp', 'deepseek/deepseek-v4-pro', + 'deepseek/deepseek-v4-pro-0813', + 'deepseek/deepseek-v4.1-flash', + 'deepseek/deepseek-v4.1-flash:batch', + 'dots-studio/dots-3-note-preview:free', + 'fireworks/ember-1', 'google/gemini-2.5-flash', 'google/gemini-2.5-flash-lite', + 'google/gemini-2.5-flash-lite:batch', + 'google/gemini-2.5-flash:batch', 'google/gemini-2.5-pro', 'google/gemini-2.5-pro-preview', - 'google/gemini-2.5-pro-preview-05-06', + 'google/gemini-2.5-pro:batch', 'google/gemini-3-flash-preview', + 'google/gemini-3-flash-preview:batch', 'google/gemini-3-pro-image', 'google/gemini-3.1-flash-lite', 'google/gemini-3.1-flash-lite-preview', + 'google/gemini-3.1-flash-lite:batch', 'google/gemini-3.1-pro-preview', 'google/gemini-3.1-pro-preview-customtools', + 'google/gemini-3.1-pro-preview:batch', 'google/gemini-3.5-flash', + 'google/gemini-3.5-flash-lite', + 'google/gemini-3.5-flash-lite:batch', + 'google/gemini-3.5-flash:batch', + 'google/gemini-3.6-flash', + 'google/gemini-3.6-flash:batch', + 'google/gemini-3.7-flash', + 'google/gemini-3.7-flash:batch', + 'google/gemini-3.8-flash', + 'google/gemini-3.8-flash:batch', 'google/gemma-3-12b-it', 'google/gemma-3-27b-it', 'google/gemma-4-26b-a4b-it', 'google/gemma-4-26b-a4b-it:free', 'google/gemma-4-31b-it', 'google/gemma-4-31b-it:free', - 'ibm-granite/granite-4.1-8b', + 'ibm-granite/granite-4.2-8b', 'inception/mercury-2', - 'inclusionai/ling-2.6-1t', - 'inclusionai/ling-2.6-flash', - 'inclusionai/ring-2.6-1t', - 'kwaipilot/kat-coder-air-v2.5', - 'kwaipilot/kat-coder-pro-v2', + 'inception/mercury-2.5', + 'inclusionai/ling-3.0-flash', + 'inclusionai/ling-3.0-flash-fin', + 'inclusionai/ling-3.0-flash-sante:free', + 'inclusionai/ling-3.0-flash-vl', 'kwaipilot/kat-coder-pro-v2.5', + 'liquid/lfm-2.5-2.6b:free', + 'meituan/longcat-2.0', 'meta-llama/llama-3.1-70b-instruct', 'meta-llama/llama-3.1-8b-instruct', 'meta-llama/llama-3.3-70b-instruct', - 'meta-llama/llama-3.3-70b-instruct:free', 'meta-llama/llama-4-maverick', 'meta-llama/llama-4-scout', + 'meta/muse-glimmer-30b', 'meta/muse-spark-1.1', + 'meta/muse-spark-1.2', + 'meta/muse-spark-1.2-contributor', + 'meta/muse-spark-1.3', + 'meta/muse-spark-1.3-contributor', 'minimax/minimax-m1', 'minimax/minimax-m2', 'minimax/minimax-m2.1', @@ -214,19 +267,26 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'minimax/minimax-m2.7', 'minimax/minimax-m3', 'mistralai/codestral-2508', + 'mistralai/codestral-2508:batch', 'mistralai/devstral-2512', 'mistralai/ministral-14b-2512', 'mistralai/ministral-3b-2512', 'mistralai/ministral-8b-2512', + 'mistralai/ministral-8b-2512:batch', 'mistralai/mistral-large', 'mistralai/mistral-large-2407', 'mistralai/mistral-large-2512', + 'mistralai/mistral-large-2512:batch', 'mistralai/mistral-medium-3', 'mistralai/mistral-medium-3-5', + 'mistralai/mistral-medium-3-5:batch', 'mistralai/mistral-medium-3.1', + 'mistralai/mistral-medium-3.1:batch', 'mistralai/mistral-nemo', 'mistralai/mistral-saba', 'mistralai/mistral-small-2603', + 'mistralai/mistral-small-2603:batch', + 'mistralai/mistral-small-3.1-24b-instruct', 'mistralai/mistral-small-3.2-24b-instruct', 'mistralai/mixtral-8x22b-instruct', 'mistralai/voxtral-small-24b-2507', @@ -237,89 +297,127 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'moonshotai/kimi-k2.6', 'moonshotai/kimi-k2.7-code', 'moonshotai/kimi-k3', - 'nex-agi/nex-n2-mini', - 'nex-agi/nex-n2-pro', - 'nvidia/llama-3.3-nemotron-super-49b-v1.5', + 'moonshotai/kimi-k3:batch', + 'nex-agi/nex-n2.5-pro', 'nvidia/nemotron-3-nano-30b-a3b', - 'nvidia/nemotron-3-nano-30b-a3b:free', 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free', 'nvidia/nemotron-3-super-120b-a12b', 'nvidia/nemotron-3-super-120b-a12b:free', 'nvidia/nemotron-3-ultra-550b-a55b', 'nvidia/nemotron-3-ultra-550b-a55b:free', - 'nvidia/nemotron-nano-12b-v2-vl:free', - 'nvidia/nemotron-nano-9b-v2:free', + 'nvidia/nemotron-3.5-lightning', + 'nvidia/nemotron-3.5-lightning:free', 'openai/gpt-3.5-turbo', 'openai/gpt-3.5-turbo-0613', 'openai/gpt-3.5-turbo-16k', + 'openai/gpt-3.5-turbo:batch', 'openai/gpt-4', 'openai/gpt-4-turbo', - 'openai/gpt-4-turbo-preview', + 'openai/gpt-4-turbo:batch', 'openai/gpt-4.1', 'openai/gpt-4.1-mini', + 'openai/gpt-4.1-mini:batch', 'openai/gpt-4.1-nano', + 'openai/gpt-4.1-nano:batch', + 'openai/gpt-4.1:batch', 'openai/gpt-4o', 'openai/gpt-4o-2024-05-13', 'openai/gpt-4o-2024-08-06', 'openai/gpt-4o-2024-11-20', 'openai/gpt-4o-mini', 'openai/gpt-4o-mini-2024-07-18', + 'openai/gpt-4o-mini:batch', + 'openai/gpt-4o:batch', 'openai/gpt-5', - 'openai/gpt-5-codex', 'openai/gpt-5-mini', + 'openai/gpt-5-mini:batch', 'openai/gpt-5-nano', + 'openai/gpt-5-nano:batch', 'openai/gpt-5-pro', + 'openai/gpt-5-pro:batch', 'openai/gpt-5.1', - 'openai/gpt-5.1-chat', 'openai/gpt-5.1-codex', 'openai/gpt-5.1-codex-max', 'openai/gpt-5.1-codex-mini', + 'openai/gpt-5.1:batch', 'openai/gpt-5.2', 'openai/gpt-5.2-chat', 'openai/gpt-5.2-codex', 'openai/gpt-5.2-pro', - 'openai/gpt-5.3-chat', + 'openai/gpt-5.2-pro:batch', + 'openai/gpt-5.2:batch', 'openai/gpt-5.3-codex', 'openai/gpt-5.4', 'openai/gpt-5.4-mini', + 'openai/gpt-5.4-mini:batch', 'openai/gpt-5.4-nano', + 'openai/gpt-5.4-nano:batch', 'openai/gpt-5.4-pro', + 'openai/gpt-5.4-pro:batch', + 'openai/gpt-5.4:batch', 'openai/gpt-5.5', 'openai/gpt-5.5-pro', + 'openai/gpt-5.5-pro:batch', + 'openai/gpt-5.5:batch', 'openai/gpt-5.6-luna', 'openai/gpt-5.6-luna-pro', + 'openai/gpt-5.6-luna-pro:batch', + 'openai/gpt-5.6-luna:batch', 'openai/gpt-5.6-sol', 'openai/gpt-5.6-sol-pro', + 'openai/gpt-5.6-sol-pro:batch', + 'openai/gpt-5.6-sol:batch', 'openai/gpt-5.6-terra', 'openai/gpt-5.6-terra-pro', + 'openai/gpt-5.6-terra-pro:batch', + 'openai/gpt-5.6-terra:batch', + 'openai/gpt-5:batch', + 'openai/gpt-6-astra', + 'openai/gpt-6-astra-pro', + 'openai/gpt-6-astra-pro:batch', + 'openai/gpt-6-astra:batch', + 'openai/gpt-6-luna', + 'openai/gpt-6-luna-pro', + 'openai/gpt-6-luna-pro:batch', + 'openai/gpt-6-luna:batch', + 'openai/gpt-6-sol', + 'openai/gpt-6-sol-pro', + 'openai/gpt-6-sol-pro:batch', + 'openai/gpt-6-sol:batch', + 'openai/gpt-6.1-sol', + 'openai/gpt-6.1-sol-pro', 'openai/gpt-audio', 'openai/gpt-audio-mini', 'openai/gpt-chat-latest', 'openai/gpt-oss-120b', + 'openai/gpt-oss-120b:batch', 'openai/gpt-oss-20b', - 'openai/gpt-oss-20b:free', + 'openai/gpt-oss-20b:batch', 'openai/gpt-oss-safeguard-20b', 'openai/o1', 'openai/o3', - 'openai/o3-deep-research', 'openai/o3-mini', 'openai/o3-mini-high', + 'openai/o3-mini:batch', 'openai/o3-pro', + 'openai/o3:batch', 'openai/o4-mini', - 'openai/o4-mini-deep-research', 'openai/o4-mini-high', + 'openai/o4-mini:batch', 'openrouter/auto', + 'openrouter/auto-beta', 'openrouter/free', 'openrouter/fusion', - 'poolside/laguna-m.1', - 'poolside/laguna-m.1:free', + 'perceptron/perceptron-mk1.5', + 'poolside/laguna-s-2.1', + 'poolside/laguna-s-2.1:free', 'poolside/laguna-xs-2.1', 'poolside/laguna-xs-2.1:free', + 'prism-ml/ternary-bonsai-2-27b', 'qwen/qwen-2.5-72b-instruct', 'qwen/qwen-2.5-7b-instruct', 'qwen/qwen-plus', 'qwen/qwen-plus-2025-07-28', - 'qwen/qwen-plus-2025-07-28:thinking', 'qwen/qwen3-14b', 'qwen/qwen3-235b-a22b', 'qwen/qwen3-235b-a22b-2507', @@ -334,11 +432,9 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'qwen/qwen3-coder-flash', 'qwen/qwen3-coder-next', 'qwen/qwen3-coder-plus', - 'qwen/qwen3-coder:free', 'qwen/qwen3-max', 'qwen/qwen3-max-thinking', 'qwen/qwen3-next-80b-a3b-instruct', - 'qwen/qwen3-next-80b-a3b-instruct:free', 'qwen/qwen3-next-80b-a3b-thinking', 'qwen/qwen3-vl-235b-a22b-instruct', 'qwen/qwen3-vl-235b-a22b-thinking', @@ -360,25 +456,51 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'qwen/qwen3.6-flash', 'qwen/qwen3.6-max-preview', 'qwen/qwen3.6-plus', + 'qwen/qwen3.7-flash', 'qwen/qwen3.7-max', 'qwen/qwen3.7-plus', + 'qwen/qwen3.8-2.4t-a95b', + 'qwen/qwen3.8-27b', + 'qwen/qwen3.8-27b:free', + 'qwen/qwen3.8-flash', + 'qwen/qwen3.8-max-0902', + 'qwen/qwen3.8-max-prime', + 'qwen/qwen3.8-omni-flash', 'rekaai/reka-edge', 'relace/relace-search', + 'sakana/fugu-max', 'sakana/fugu-ultra', + 'sakana/fugu-ultra-v2', + 'sakana/sakana-namazu', 'sao10k/l3.1-euryale-70b', + 'stealth/space-bunny-alpha', 'stepfun/step-3.5-flash', 'stepfun/step-3.7-flash', 'tencent/hy3', 'tencent/hy3-preview', - 'tencent/hy3:free', - 'thedrummer/unslopnemo-12b', + 'tencent/hy4-preview', + 'thinkingmachines/inkling', + 'thinkingmachines/inkling-small', + 'thinkingmachines/inkling-small:free', + 'thinkingmachines/inkling:free', + 'typesafe/jev-router', + 'unbiased/pareto', + 'unbiased/pareto-26.10-preview', + 'upstage/solar-mini4', 'upstage/solar-pro-3', + 'upstage/solar-pro4', 'x-ai/grok-4.20', 'x-ai/grok-4.3', + 'x-ai/grok-4.3:batch', 'x-ai/grok-4.5', + 'x-ai/grok-4.6', + 'x-ai/grok-4.7', 'x-ai/grok-build-0.1', 'xiaomi/mimo-v2.5', 'xiaomi/mimo-v2.5-pro', + 'xiaomi/mimo-v2.6-flash', + 'xiaomi/mimo-v2.6-pro', + 'xiaomi/mimo-v2.6-pro-ultraspeed', 'z-ai/glm-4.5', 'z-ai/glm-4.5-air', 'z-ai/glm-4.5v', @@ -390,91 +512,107 @@ export const PI_MODEL_IDS_BY_PROVIDER = { 'z-ai/glm-5-turbo', 'z-ai/glm-5.1', 'z-ai/glm-5.2', + 'z-ai/glm-5.3', + 'z-ai/glm-5.3-flash', + 'z-ai/glm-5.3-flash:batch', + 'z-ai/glm-5.3-flashx', + 'z-ai/glm-5.3-prime', + 'z-ai/glm-5.3:batch', 'z-ai/glm-5v-turbo', '~anthropic/claude-fable-latest', '~anthropic/claude-haiku-latest', '~anthropic/claude-opus-latest', '~anthropic/claude-sonnet-latest', + '~deepseek/deepseek-flash-latest', + '~deepseek/deepseek-pro-latest', + '~deepseek/deepseek-v4-flash-latest', '~google/gemini-flash-latest', '~google/gemini-pro-latest', '~moonshotai/kimi-latest', - '~openai/gpt-latest', + '~openai/gpt-astra-latest', + '~openai/gpt-luna-latest', '~openai/gpt-mini-latest', + '~openai/gpt-sol-latest', + '~openai/gpt-terra-latest', '~x-ai/grok-latest', + '~z-ai/glm-flash-latest', + '~z-ai/glm-latest', ], fireworks: [ - 'accounts/fireworks/models/deepseek-v4-flash', - 'accounts/fireworks/models/deepseek-v4-pro', - 'accounts/fireworks/models/glm-5p1', - 'accounts/fireworks/models/glm-5p2', + 'accounts/fireworks/models/deepseek-v4p1-flash', + 'accounts/fireworks/models/ember-1', + 'accounts/fireworks/models/glm-5p3', + 'accounts/fireworks/models/glm-5p3-flash', 'accounts/fireworks/models/gpt-oss-120b', - 'accounts/fireworks/models/gpt-oss-20b', - 'accounts/fireworks/models/kimi-k2p6', - 'accounts/fireworks/models/kimi-k2p7-code', - 'accounts/fireworks/models/minimax-m2p7', + 'accounts/fireworks/models/inkling', + 'accounts/fireworks/models/kimi-k3', 'accounts/fireworks/models/minimax-m3', - 'accounts/fireworks/models/qwen3p7-plus', - 'accounts/fireworks/routers/glm-5p1-fast', - 'accounts/fireworks/routers/glm-5p2-fast', - 'accounts/fireworks/routers/kimi-k2p6-fast', - 'accounts/fireworks/routers/kimi-k2p6-turbo', - 'accounts/fireworks/routers/kimi-k2p7-code-fast', + 'accounts/fireworks/models/nemotron-3-ultra-nvfp4', + 'accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b', + 'accounts/fireworks/models/qwen3p8-2p4t-a95b', + 'accounts/fireworks/models/qwen3p8-max', + 'accounts/fireworks/routers/deepseek-flash-latest', + 'accounts/fireworks/routers/glm-5p3-fast', + 'accounts/fireworks/routers/glm-fast-latest', + 'accounts/fireworks/routers/glm-flash-latest', + 'accounts/fireworks/routers/glm-latest', + 'accounts/fireworks/routers/kimi-fast-latest', + 'accounts/fireworks/routers/kimi-k3-fast', + 'accounts/fireworks/routers/kimi-latest', + 'accounts/fireworks/routers/minimax-latest', + 'accounts/fireworks/routers/qwen-max-latest', ], together: [ 'MiniMaxAI/MiniMax-M2.7', 'MiniMaxAI/MiniMax-M3', 'Qwen/Qwen2.5-7B-Instruct-Turbo', - 'Qwen/Qwen3-235B-A22B-Instruct-2507-tput', - 'Qwen/Qwen3.5-397B-A17B', 'Qwen/Qwen3.5-9B', 'Qwen/Qwen3.6-Plus', 'Qwen/Qwen3.7-Max', + 'deepseek-ai/DeepSeek-V4-Flash-0731', 'deepseek-ai/DeepSeek-V4-Pro', - 'essentialai/Rnj-1-Instruct', + 'deepseek-ai/DeepSeek-V4-Pro-0813', + 'deepseek-ai/DeepSeek-V4.1-Flash', 'google/gemma-4-31B-it', 'meta-llama/Llama-3.3-70B-Instruct-Turbo', - 'moonshotai/Kimi-K2.6', - 'moonshotai/Kimi-K2.7-Code', + 'moonshotai/Kimi-K3', 'nvidia/nemotron-3-ultra-550b-a55b', 'openai/gpt-oss-120b', 'openai/gpt-oss-20b', - 'zai-org/GLM-5', - 'zai-org/GLM-5.1', + 'thinkingmachines/Inkling', 'zai-org/GLM-5.2', + 'zai-org/GLM-5.3', + 'zai-org/GLM-5.3-Flash', ], nvidia: [ - 'meta/llama-3.1-70b-instruct', - 'meta/llama-3.1-8b-instruct', + 'google/gemma-3-12b-it', + 'google/gemma-3-4b-it', 'meta/llama-3.2-11b-vision-instruct', 'meta/llama-3.2-90b-vision-instruct', - 'meta/llama-3.3-70b-instruct', - 'minimaxai/minimax-m3', - 'mistralai/mistral-large-3-675b-instruct-2512', - 'mistralai/mistral-small-4-119b-2603', + 'meta/muse-glimmer-30b', + 'mistralai/mistral-7b-instruct-v0.3', 'moonshotai/kimi-k2.6', - 'nvidia/nemotron-3-nano-30b-a3b', + 'moonshotai/kimi-k3', + 'nvidia/cosmos-reason2-8b', + 'nvidia/llama-3.1-nemotron-70b-instruct', + 'nvidia/llama-3.1-nemotron-ultra-253b-v1', 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', 'nvidia/nemotron-3-super-120b-a12b', 'nvidia/nemotron-3-ultra-550b-a55b', - 'nvidia/nvidia-nemotron-nano-9b-v2', - 'openai/gpt-oss-120b', + 'nvidia/nemotron-3.5-lightning-30b-a3b', 'openai/gpt-oss-20b', - 'qwen/qwen3.5-122b-a10b', - 'stepfun-ai/step-3.5-flash', - 'stepfun-ai/step-3.7-flash', - 'z-ai/glm-5.2', + 'poolside/laguna-xs-2.1', + 'z-ai/glm-5.3', + 'z-ai/glm-5.3-flash', ], - zai: ['glm-4.5-air', 'glm-4.7', 'glm-5-turbo', 'glm-5.1', 'glm-5.2', 'glm-5v-turbo'], - kimi: [ - 'kimi-k2-0711-preview', - 'kimi-k2-0905-preview', - 'kimi-k2-thinking', - 'kimi-k2-thinking-turbo', - 'kimi-k2-turbo-preview', - 'kimi-k2.5', - 'kimi-k2.6', - 'kimi-k2.7-code', - 'kimi-k2.7-code-highspeed', - 'kimi-k3', + zai: [ + 'glm-4.7', + 'glm-5-turbo', + 'glm-5.2', + 'glm-5.2-highspeed', + 'glm-5.3', + 'glm-5.3-flash', + 'glm-5.3-highspeed', ], + kimi: ['kimi-k2.6', 'kimi-k2.7-code', 'kimi-k2.7-code-highspeed', 'kimi-k3'], } as const diff --git a/apps/sim/providers/pi-providers.test.ts b/apps/sim/providers/pi-providers.test.ts index c2bf73bbf80..3d12c9ceb42 100644 --- a/apps/sim/providers/pi-providers.test.ts +++ b/apps/sim/providers/pi-providers.test.ts @@ -32,11 +32,11 @@ describe('Pi provider catalog', () => { ) }) - it('keeps persisted and selectable Sim models in the pinned Pi catalog', () => { - expect(resolvePiModelId('anthropic', 'claude-opus-4-1')).toBe('claude-opus-4-1') - expect(resolvePiModelId('cerebras', 'cerebras/zai-glm-4.7')).toBe('zai-glm-4.7') - expect(resolvePiModelId('zai', 'glm-5.1')).toBe('glm-5.1') - expect(resolvePiModelId('zai', 'glm-4.5-air')).toBe('glm-4.5-air') + it('rejects saved model IDs removed by Pi 1.0 without substituting another model', () => { + expect(resolvePiModelId('anthropic', 'claude-opus-4-1')).toBeUndefined() + expect(resolvePiModelId('cerebras', 'cerebras/zai-glm-4.7')).toBeUndefined() + expect(resolvePiModelId('zai', 'glm-5.1')).toBeUndefined() + expect(resolvePiModelId('zai', 'glm-4.5-air')).toBeUndefined() }) it('rejects provider/model pairs absent from the installed Pi catalog', () => { diff --git a/apps/sim/public/library/postgres-mcp-server-ai-agents/cover.jpg b/apps/sim/public/library/postgres-mcp-server-ai-agents/cover.jpg new file mode 100644 index 00000000000..92215727840 Binary files /dev/null and b/apps/sim/public/library/postgres-mcp-server-ai-agents/cover.jpg differ diff --git a/apps/sim/scripts/build-pi-e2b-template.ts b/apps/sim/scripts/build-pi-e2b-template.ts index c31a14e0925..2f88a7ae287 100644 --- a/apps/sim/scripts/build-pi-e2b-template.ts +++ b/apps/sim/scripts/build-pi-e2b-template.ts @@ -29,7 +29,7 @@ import { const DEFAULT_TEMPLATE_NAME = 'sim-pi' -/** Pi 0.84 requires Node >=22.19; E2B's code-interpreter base currently ships Node 20. */ +/** Pi 1.0 requires Node >=22.19; E2B's code-interpreter base currently ships Node 20. */ const INSTALL_NODE_COMMAND = `curl -fsSL https://deb.nodesource.com/setup_${PI_NODE_MAJOR}.x | bash - && apt-get install -y nodejs && ${PI_NODE_VERSION_ASSERT}` /** Pi uses the command and filesystem APIs, so the inherited Jupyter service is unnecessary. */ diff --git a/apps/sim/scripts/pi-sandbox-packages.ts b/apps/sim/scripts/pi-sandbox-packages.ts index e74b2b464a9..1a99a4920c7 100644 --- a/apps/sim/scripts/pi-sandbox-packages.ts +++ b/apps/sim/scripts/pi-sandbox-packages.ts @@ -17,7 +17,7 @@ export const PI_BUN_VERSION = '1.4.2' /** Exact Pi version mirrored from the app dependencies and lockfile. */ -export const PI_PACKAGE_VERSION = '0.80.10' +export const PI_PACKAGE_VERSION = '1.0.0' /** Exact global package versions mirrored from package.json and bun.lock. */ export const PI_GLOBAL_NPM_PACKAGES = [ @@ -49,7 +49,7 @@ export const PI_APT = [ ] as const /** - * Pi 0.80 requires Node >= 22.19 — higher than the Node 20 both the E2B base and + * Pi 1.0 requires Node >= 22.19 — higher than the Node 20 both the E2B base and * the other two sandbox images carry, so this image installs its own. */ export const PI_NODE_MAJOR = 22 diff --git a/apps/sim/tools/generated/tool-ids.ts b/apps/sim/tools/generated/tool-ids.ts index 059714052df..c9672981478 100644 --- a/apps/sim/tools/generated/tool-ids.ts +++ b/apps/sim/tools/generated/tool-ids.ts @@ -3,7 +3,7 @@ /** Every registered tool id, including versioned variants. */ const toolIds: string[] = JSON.parse( - '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_access_application","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_ruleset_rule","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_r2_bucket","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_revoke_access_service_token","cloudflare_update_access_application","cloudflare_update_access_policy","cloudflare_update_dns_record","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset_rule","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","coda_add_custom_domain","coda_add_permission","coda_change_user_role","coda_create_doc","coda_create_folder","coda_create_page","coda_delete_custom_domain","coda_delete_doc","coda_delete_folder","coda_delete_page","coda_delete_page_content","coda_delete_permission","coda_delete_row","coda_delete_rows","coda_export_page","coda_get_acl_settings","coda_get_analytics_last_updated","coda_get_column","coda_get_control","coda_get_custom_domain_provider","coda_get_doc","coda_get_doc_analytics_summary","coda_get_folder","coda_get_formula","coda_get_mutation_status","coda_get_page","coda_get_page_content","coda_get_page_export_status","coda_get_row","coda_get_sharing_metadata","coda_get_table","coda_list_categories","coda_list_columns","coda_list_controls","coda_list_custom_domains","coda_list_doc_analytics","coda_list_docs","coda_list_folder_children","coda_list_folders","coda_list_formulas","coda_list_page_analytics","coda_list_pages","coda_list_permissions","coda_list_rows","coda_list_tables","coda_list_workspace_members","coda_list_workspace_roles","coda_publish_doc","coda_push_button","coda_resolve_browser_link","coda_search_principals","coda_trigger_automation","coda_unpublish_doc","coda_update_acl_settings","coda_update_doc","coda_update_folder","coda_update_page","coda_update_row","coda_upsert_rows","coda_whoami","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_genie_agent_ask","databricks_genie_agent_list_items","databricks_genie_ask","databricks_genie_delete_conversation","databricks_genie_delete_message","databricks_genie_download_visualization","databricks_genie_execute_query","databricks_genie_get_message","databricks_genie_get_query_result","databricks_genie_get_space","databricks_genie_list_conversations","databricks_genie_list_messages","databricks_genie_list_spaces","databricks_genie_send_feedback","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_download_v2","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_get_qr_code_v2","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_respond","google_calendar_respond_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_policy","iam_get_role","iam_get_user","iam_list_access_keys","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","iam_update_access_key","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_get_content_v2","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_download_file_v2","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","otter_create_conversation","otter_get_conversation","otter_get_conversation_audio","otter_get_workspace","otter_list_channel_members","otter_list_channels","otter_list_conversations","otter_list_workspace_conversations","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","planetscale_close_deploy_request","planetscale_create_backup","planetscale_create_branch","planetscale_create_deploy_request","planetscale_delete_branch","planetscale_get_backup","planetscale_get_branch","planetscale_get_database","planetscale_get_deploy_request","planetscale_list_backups","planetscale_list_branches","planetscale_list_databases","planetscale_list_deploy_requests","planetscale_queue_deploy_request","planetscale_review_deploy_request","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_download_v2","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_bookmark","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_close_conversation","slack_create_channel_canvas","slack_create_conversation","slack_create_user_group","slack_delete_canvas","slack_delete_file","slack_delete_message","slack_delete_scheduled_message","slack_disable_user_group","slack_download","slack_edit_bookmark","slack_edit_canvas","slack_enable_user_group","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_dnd_info","slack_get_file_info","slack_get_message","slack_get_permalink","slack_get_reactions","slack_get_team_dnd_info","slack_get_team_info","slack_get_team_profile","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_get_user_profile","slack_invite_to_conversation","slack_join_conversation","slack_kick_conversation","slack_leave_conversation","slack_list_bookmarks","slack_list_canvases","slack_list_channels","slack_list_emoji","slack_list_files","slack_list_members","slack_list_pins","slack_list_reactions","slack_list_scheduled_messages","slack_list_user_conversations","slack_list_user_group_members","slack_list_user_groups","slack_list_users","slack_lists_access_delete","slack_lists_access_set","slack_lists_create","slack_lists_download_get","slack_lists_download_start","slack_lists_items_create","slack_lists_items_delete","slack_lists_items_delete_multiple","slack_lists_items_info","slack_lists_items_list","slack_lists_items_update","slack_lists_update","slack_lookup_canvas_sections","slack_lookup_user_by_email","slack_mark_conversation_read","slack_message","slack_message_reader","slack_open_conversation","slack_open_view","slack_pin_message","slack_publish_view","slack_push_view","slack_remove_bookmark","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_revoke_canvas_access","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_set_user_presence","slack_share_canvas","slack_unarchive_conversation","slack_unfurl_links","slack_unpin_message","slack_update_message","slack_update_user_group","slack_update_user_group_members","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_cortex_analyst_ask","snowflake_cortex_analyst_feedback","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_download_file_v2","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoho_desk_add_comment","zoho_desk_get_attachment","zoho_desk_get_contact","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_comments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_update_ticket","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' + '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_access_application","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_ruleset_rule","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_r2_bucket","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_revoke_access_service_token","cloudflare_update_access_application","cloudflare_update_access_policy","cloudflare_update_dns_record","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset_rule","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","coda_add_custom_domain","coda_add_permission","coda_change_user_role","coda_create_doc","coda_create_folder","coda_create_page","coda_delete_custom_domain","coda_delete_doc","coda_delete_folder","coda_delete_page","coda_delete_page_content","coda_delete_permission","coda_delete_row","coda_delete_rows","coda_export_page","coda_get_acl_settings","coda_get_analytics_last_updated","coda_get_column","coda_get_control","coda_get_custom_domain_provider","coda_get_doc","coda_get_doc_analytics_summary","coda_get_folder","coda_get_formula","coda_get_mutation_status","coda_get_page","coda_get_page_content","coda_get_page_export_status","coda_get_row","coda_get_sharing_metadata","coda_get_table","coda_list_categories","coda_list_columns","coda_list_controls","coda_list_custom_domains","coda_list_doc_analytics","coda_list_docs","coda_list_folder_children","coda_list_folders","coda_list_formulas","coda_list_page_analytics","coda_list_pages","coda_list_permissions","coda_list_rows","coda_list_tables","coda_list_workspace_members","coda_list_workspace_roles","coda_publish_doc","coda_push_button","coda_resolve_browser_link","coda_search_principals","coda_trigger_automation","coda_unpublish_doc","coda_update_acl_settings","coda_update_doc","coda_update_folder","coda_update_page","coda_update_row","coda_upsert_rows","coda_whoami","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_genie_agent_ask","databricks_genie_agent_list_items","databricks_genie_ask","databricks_genie_delete_conversation","databricks_genie_delete_message","databricks_genie_download_visualization","databricks_genie_execute_query","databricks_genie_get_message","databricks_genie_get_query_result","databricks_genie_get_space","databricks_genie_list_conversations","databricks_genie_list_messages","databricks_genie_list_spaces","databricks_genie_send_feedback","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_download_v2","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_get_qr_code_v2","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_respond","google_calendar_respond_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_policy","iam_get_role","iam_get_user","iam_list_access_keys","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","iam_update_access_key","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_get_content_v2","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_download_file_v2","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","otter_create_conversation","otter_get_conversation","otter_get_conversation_audio","otter_get_workspace","otter_list_channel_members","otter_list_channels","otter_list_conversations","otter_list_workspace_conversations","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","planetscale_close_deploy_request","planetscale_create_backup","planetscale_create_branch","planetscale_create_deploy_request","planetscale_delete_branch","planetscale_get_backup","planetscale_get_branch","planetscale_get_database","planetscale_get_deploy_request","planetscale_list_backups","planetscale_list_branches","planetscale_list_databases","planetscale_list_deploy_requests","planetscale_queue_deploy_request","planetscale_review_deploy_request","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","powerbi_execute_query","powerbi_get_dataset","powerbi_get_refresh_history","powerbi_get_report","powerbi_list_datasets","powerbi_list_reports","powerbi_list_workspaces","powerbi_refresh_dataset","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_download_v2","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_bookmark","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_close_conversation","slack_create_channel_canvas","slack_create_conversation","slack_create_user_group","slack_delete_canvas","slack_delete_file","slack_delete_message","slack_delete_scheduled_message","slack_disable_user_group","slack_download","slack_edit_bookmark","slack_edit_canvas","slack_enable_user_group","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_dnd_info","slack_get_file_info","slack_get_message","slack_get_permalink","slack_get_reactions","slack_get_team_dnd_info","slack_get_team_info","slack_get_team_profile","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_get_user_profile","slack_invite_to_conversation","slack_join_conversation","slack_kick_conversation","slack_leave_conversation","slack_list_bookmarks","slack_list_canvases","slack_list_channels","slack_list_emoji","slack_list_files","slack_list_members","slack_list_pins","slack_list_reactions","slack_list_scheduled_messages","slack_list_user_conversations","slack_list_user_group_members","slack_list_user_groups","slack_list_users","slack_lists_access_delete","slack_lists_access_set","slack_lists_create","slack_lists_download_get","slack_lists_download_start","slack_lists_items_create","slack_lists_items_delete","slack_lists_items_delete_multiple","slack_lists_items_info","slack_lists_items_list","slack_lists_items_update","slack_lists_update","slack_lookup_canvas_sections","slack_lookup_user_by_email","slack_mark_conversation_read","slack_message","slack_message_reader","slack_open_conversation","slack_open_view","slack_pin_message","slack_publish_view","slack_push_view","slack_remove_bookmark","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_revoke_canvas_access","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_set_user_presence","slack_share_canvas","slack_unarchive_conversation","slack_unfurl_links","slack_unpin_message","slack_update_message","slack_update_user_group","slack_update_user_group_members","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_cortex_analyst_ask","snowflake_cortex_analyst_feedback","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_download_file_v2","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youcom_answer","youcom_finance_research","youcom_get_account_balance","youcom_get_contents","youcom_get_research_task","youcom_research","youcom_search","youcom_search_images","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoho_desk_add_comment","zoho_desk_get_attachment","zoho_desk_get_contact","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_comments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_update_ticket","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' ) export default toolIds diff --git a/apps/sim/tools/generated/tool-metadata.ts b/apps/sim/tools/generated/tool-metadata.ts index 08efbd654dc..242c1f2b60a 100644 --- a/apps/sim/tools/generated/tool-metadata.ts +++ b/apps/sim/tools/generated/tool-metadata.ts @@ -3,7 +3,7 @@ /** Serializable metadata for every built-in tool, keyed by tool id. */ const toolMetadata: Record = JSON.parse( - '{"a2a_cancel_task":{"id":"a2a_cancel_task","name":"A2A Cancel Task","description":"Request cancellation of an in-progress A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to cancel"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_get_agent_card":{"id":"a2a_get_agent_card","name":"A2A Get Agent Card","description":"Fetch the Agent Card (discovery document) for an external A2A agent.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_get_task":{"id":"a2a_get_task","name":"A2A Get Task","description":"Retrieve the current state and result of an A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to retrieve"},"historyLength":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of history messages to include"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_send_message":{"id":"a2a_send_message","name":"A2A Send Message","description":"Send a message to an external A2A agent and return its response.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"message":{"type":"string","required":true,"visibility":"user-or-llm","description":"The message text to send"},"data":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional structured JSON data to attach"},"files":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional files to attach"},"taskId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Existing task ID to continue"},"contextId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversation context ID to continue"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"affinity_batch_update_entity_fields":{"id":"affinity_batch_update_entity_fields","name":"Affinity Batch Update Entity Fields","description":"Write up to 100 non-list field values on one company or person in a single request.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the fields on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"hostedApiKey":"none"},"affinity_batch_update_list_entry_fields":{"id":"affinity_batch_update_list_entry_fields","name":"Affinity Batch Update List Entry Fields","description":"Write up to 100 field values on one list row in a single request. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"hostedApiKey":"none"},"affinity_create_list":{"id":"affinity_create_list","name":"Affinity Create List","description":"Create a list. Its type fixes which entities it can hold, and the API key holder becomes its creator and owner.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the new list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Entity kind the list holds: company, opportunity, or person"},"isPublic":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether everyone in the organization can see the list"}},"hostedApiKey":"none"},"affinity_create_list_field_dropdown_option":{"id":"affinity_create_list_field_dropdown_option","name":"Affinity Create List Field Dropdown Option","description":"Add a selectable option to a dropdown field on a list. A ranked or status option also needs a rank and a color.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Kind of option to create, matching the field. dropdown takes only a label; ranked-dropdown also requires rank and color; status-dropdown additionally requires a status category. Sending a field the kind does not accept is rejected"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The option label"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"hostedApiKey":"none"},"affinity_create_merge":{"id":"affinity_create_merge","name":"Affinity Create Merge","description":"Fold a duplicate company or person into the record you are keeping. The merge runs asynchronously — poll the returned task to see it finish. Requires the \\"Manage duplicates\\" permission and an admin role.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to merge: companies or persons"},"primaryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to keep"},"duplicateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the duplicate record to fold in"}},"hostedApiKey":"none"},"affinity_create_note":{"id":"affinity_create_note","name":"Affinity Create Note","description":"Write a note — attached to companies, persons, and opportunities, anchored to a meeting, call, or chat message, or posted as a reply to an existing note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Note shape: entities to attach it to records, interaction to anchor it to a meeting, call, or chat message, or user-reply to reply to a note"},"html":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Companies to attach the note to, e.g. [1, 2]. Not used on a reply"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Persons to attach the note to, e.g. [1, 2]. Not used on a reply"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Opportunities to attach the note to, e.g. [1, 2]. Not used on a reply"},"interactionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The interaction to anchor the note to. Required for an interaction note"},"interactionType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Kind of the anchoring interaction: meeting, call, or chat-message. Required for an interaction note"},"parentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The note being replied to. Required for a user-reply note"},"creatorId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Attribute the note to another internal person. Defaults to the API key holder"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdate the note to this ISO 8601 timestamp"}},"hostedApiKey":"none"},"affinity_create_reminder":{"id":"affinity_create_reminder","name":"Affinity Create Reminder","description":"Create a reminder on one company, person, or opportunity. A recurring reminder resets whenever the chosen signal happens instead of firing once.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"one-time to fire once, or recurring to reset on a signal"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What the reminder is about: company, person, or opportunity"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"dueDate":{"type":"string","required":false,"visibility":"user-or-llm","description":"When the reminder is due, as an ISO 8601 timestamp. Required for a one-time reminder; on a recurring one Affinity computes it from the period when omitted"},"content":{"type":"string","required":false,"visibility":"user-or-llm","description":"What the reminder says"},"ownerId":{"type":"string","required":true,"visibility":"user-or-llm","description":"User the reminder is assigned to. Must be an internal user. The API key holder is recorded as the creator, which is a separate field"},"resetTrigger":{"type":"string","required":false,"visibility":"user-or-llm","description":"What restarts a recurring reminder: interaction, email, or event. Required when the type is recurring"},"periodDays":{"type":"number","required":false,"visibility":"user-or-llm","description":"Days between firings of a recurring reminder. Required when the type is recurring"}},"hostedApiKey":"none"},"affinity_delete_list_field_dropdown_option":{"id":"affinity_delete_list_field_dropdown_option","name":"Affinity Delete List Field Dropdown Option","description":"Permanently delete a dropdown option on a list field. Every list entry currently set to it is cleared, and those values cannot be recovered.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to delete"}},"hostedApiKey":"none"},"affinity_delete_note":{"id":"affinity_delete_note","name":"Affinity Delete Note","description":"Delete a note you created. Deleting a root note also deletes its replies; deleting a reply removes only that reply.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to delete"}},"hostedApiKey":"none"},"affinity_get_company":{"id":"affinity_get_company","name":"Affinity Get Company","description":"Look up one company by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"companyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The company ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_current_user":{"id":"affinity_get_current_user","name":"Affinity Get Current User","description":"Verify an Affinity API key and return the tenant, the user behind the key, and the scopes the grant carries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"}},"hostedApiKey":"none"},"affinity_get_entity_field_value":{"id":"affinity_get_entity_field_value","name":"Affinity Get Entity Field Value","description":"Read one non-list field value from a company or person.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read the field from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"hostedApiKey":"none"},"affinity_get_list":{"id":"affinity_get_list","name":"Affinity Get List","description":"Read one list — its name, type, owner, and privacy setting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"}},"hostedApiKey":"none"},"affinity_get_list_entry":{"id":"affinity_get_list_entry","name":"Affinity Get List Entry","description":"Read one row of a list with its entity. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_list_entry_field":{"id":"affinity_get_list_entry_field","name":"Affinity Get List Entry Field","description":"Read one field value on a list row.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"hostedApiKey":"none"},"affinity_get_list_field_dropdown_option":{"id":"affinity_get_list_field_dropdown_option","name":"Affinity Get List Field Dropdown Option","description":"Read one dropdown option on a list field.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID"}},"hostedApiKey":"none"},"affinity_get_merge":{"id":"affinity_get_merge","name":"Affinity Get Merge","description":"Read the status of one company or person merge, including why it failed if it did.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge to read: companies or persons"},"mergeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge ID"}},"hostedApiKey":"none"},"affinity_get_merge_task":{"id":"affinity_get_merge_task","name":"Affinity Get Merge Task","description":"Read one merge task and how its merges are progressing. Poll this after starting a merge.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge task to read: companies or persons"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge task ID"}},"hostedApiKey":"none"},"affinity_get_note":{"id":"affinity_get_note","name":"Affinity Get Note","description":"Read one note with its body, author, mentions, and attached records.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"}},"hostedApiKey":"none"},"affinity_get_opportunity":{"id":"affinity_get_opportunity","name":"Affinity Get Opportunity","description":"Read one opportunity and the list it belongs to. Its field data lives on the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"opportunityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The opportunity ID"}},"hostedApiKey":"none"},"affinity_get_person":{"id":"affinity_get_person","name":"Affinity Get Person","description":"Look up one person by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"personId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The person ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_saved_view":{"id":"affinity_get_saved_view","name":"Affinity Get Saved View","description":"Read one saved view — its name, kind, and creation date.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"}},"hostedApiKey":"none"},"affinity_get_transcript":{"id":"affinity_get_transcript","name":"Affinity Get Transcript","description":"Read one transcript with its first 100 fragments. Page the fragments endpoint for a longer meeting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"}},"hostedApiKey":"none"},"affinity_get_user":{"id":"affinity_get_user","name":"Affinity Get User","description":"Read one internal user. A user and their person record share the same numeric ID, so a person ID works here.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"userId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The user ID, which is also their person ID"}},"hostedApiKey":"none"},"affinity_list_calls":{"id":"affinity_list_calls","name":"Affinity List Calls","description":"Page through logged calls and their participants. Only calls the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_chat_messages":{"id":"affinity_list_chat_messages","name":"Affinity List Chat Messages","description":"Page through logged chat messages and their participants. Only messages the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_companies":{"id":"affinity_list_companies","name":"Affinity List Companies","description":"Page through companies. Companies come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these company IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_coworker_connections":{"id":"affinity_list_coworker_connections","name":"Affinity List Coworker Connections","description":"Find warm paths into a company through shared work history: who in your Affinity data once worked alongside the people you want to reach. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_emails":{"id":"affinity_list_emails","name":"Affinity List Emails","description":"Page through email metadata — subject, participants, and timestamps. Affinity never exposes email bodies through the API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_field_values":{"id":"affinity_list_entity_field_values","name":"Affinity List Entity Field Values","description":"Page through a company\'s or person\'s non-list field values. List fields are not returned here — read those through the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read field values from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_list_entity_list_entries":{"id":"affinity_list_entity_list_entries","name":"Affinity List Entity List Entries","description":"Page through a company\'s or person\'s rows across every list, each carrying that list\'s field values and when the entity was added.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the rows of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_lists":{"id":"affinity_list_entity_lists","name":"Affinity List Entity Lists","description":"List every list a company or person appears on that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the lists of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_notes":{"id":"affinity_list_entity_notes","name":"Affinity List Entity Notes","description":"List the notes relevant to one company, person, or opportunity — directly attached notes plus notes reaching it through its people and meetings.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity the notes hang off: companies, persons, or opportunities"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_entity_relationships":{"id":"affinity_list_entity_relationships","name":"Affinity List Entity Relationships","description":"List who knows a company or person, scored 0.0 to 1.0 by how much the two actually interact. Strongest first by default.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up relationships for: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on interactionScore only, e.g. \\"interactionScore>=0.5\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"interactionScore\\"] for weakest first, [\\"-interactionScore\\"] for strongest first (the default)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_field_dropdown_options":{"id":"affinity_list_field_dropdown_options","name":"Affinity List Field Dropdown Options","description":"List the selectable options on a dropdown or ranked-dropdown company or person field. Writing such a field needs the option ID, not its text.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which field family the field belongs to: companies or persons"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown or ranked-dropdown field ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_field_metadata":{"id":"affinity_list_field_metadata","name":"Affinity List Field Metadata","description":"List the non-list company or person fields, with the value type, filter operators, and sort support of each. Start here to find the Field IDs the read and write tools take.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which fields to describe: companies or persons"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Status\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_field_value_changes":{"id":"affinity_list_field_value_changes","name":"Affinity List Field Value Changes","description":"Page through field value changes across the whole workspace. Built for delta sync: follow nextCursor to the end of a run, then resume from the last cursor next time.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, listEntry.id, changer.id, changedAt, or actionType. Resume a sync with e.g. \\"changedAt>2026-06-01T12:00:00Z\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"changedAt\\"] for oldest first (the default), [\\"-changedAt\\"] for newest first"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_investor_executive_connections":{"id":"affinity_list_investor_executive_connections","name":"Affinity List Investor Executive Connections","description":"Find warm paths into a company through investment history: which investors in your Affinity data backed a company the people you want to reach once led. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_list_entries":{"id":"affinity_list_list_entries","name":"Affinity List List Entries","description":"Page through the rows of a list. Rows come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_entry_field_value_changes":{"id":"affinity_list_list_entry_field_value_changes","name":"Affinity List List Entry Field Value Changes","description":"Page through the history of one list row — who changed which field, when, and to what.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, changer.id, changedAt, or actionType, e.g. \\"field.id=field-1234\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_entry_fields":{"id":"affinity_list_list_entry_fields","name":"Affinity List List Entry Fields","description":"Page through every field value on one list row, including the list-specific columns. All fields are returned unless narrowed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, list, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_list_list_field_dropdown_options":{"id":"affinity_list_list_field_dropdown_options","name":"Affinity List List Field Dropdown Options","description":"List the selectable options on a dropdown, ranked-dropdown, or status-dropdown field of a list.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_fields":{"id":"affinity_list_list_fields","name":"Affinity List List Fields","description":"List the fields available on one list, including its list-specific columns. Use these Field IDs when reading or writing list entries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Stage\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_lists":{"id":"affinity_list_lists","name":"Affinity List Lists","description":"Page through the lists in the organization that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive substring match on the list name"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_meetings":{"id":"affinity_list_meetings","name":"Affinity List Meetings","description":"Page through past and upcoming meetings with their organizer and attendees.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_merge_tasks":{"id":"affinity_list_merge_tasks","name":"Affinity List Merge Tasks","description":"Page through merge tasks, each summarizing how many of its merges are in progress, succeeded, or failed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge tasks to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on status only, e.g. \\"status=in-progress\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_merges":{"id":"affinity_list_merges","name":"Affinity List Merges","description":"Page through the company or person merges the organization has run, with the status and the records involved in each.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merges to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over status or taskId, e.g. \\"status=failed\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_note_attached_companies":{"id":"affinity_list_note_attached_companies","name":"Affinity List Note Attached Companies","description":"List the companies directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_attached_opportunities":{"id":"affinity_list_note_attached_opportunities","name":"Affinity List Note Attached Opportunities","description":"List the opportunities directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_attached_persons":{"id":"affinity_list_note_attached_persons","name":"Affinity List Note Attached Persons","description":"List the persons directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_replies":{"id":"affinity_list_note_replies","name":"Affinity List Note Replies","description":"Page through the replies on one note, including AI Notetaker replies.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID whose replies to read"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_notes":{"id":"affinity_list_notes","name":"Affinity List Notes","description":"Page through every note the caller can see. Replies are excluded.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_opportunities":{"id":"affinity_list_opportunities","name":"Affinity List Opportunities","description":"Page through opportunities. Field data lives on the list entry, not here — read it through the list or saved view the opportunity belongs to.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these opportunity IDs, e.g. [1, 2, 3]"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_persons":{"id":"affinity_list_persons","name":"Affinity List Persons","description":"Page through persons. Persons come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these person IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_reminders":{"id":"affinity_list_reminders","name":"Affinity List Reminders","description":"Page through the reminders the caller can see. Filter by status to surface what is overdue.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_saved_view_entries":{"id":"affinity_list_saved_view_entries","name":"Affinity List Saved View Entries","description":"Page through the rows of a saved view. The view\'s own filters and columns decide which rows and which field data come back.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_saved_views":{"id":"affinity_list_saved_views","name":"Affinity List Saved Views","description":"List the saved views on a list that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_transcript_fragments":{"id":"affinity_list_transcript_fragments","name":"Affinity List Transcript Fragments","description":"Page through everything said in a meeting, segment by segment with the speaker.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_transcripts":{"id":"affinity_list_transcripts","name":"Affinity List Transcripts","description":"Page through meeting transcript metadata. Read one transcript to get what was actually said.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_users":{"id":"affinity_list_users","name":"Affinity List Users","description":"Page through the internal users in the organization. Email addresses and roles are returned only to callers with the \\"Manage Users\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive match across first name, last name, and primary email"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over id or status, e.g. \\"status=active\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_search_companies":{"id":"affinity_search_companies","name":"Affinity Search Companies","description":"Search companies by filters, sorts, and a free-text term. Requires the \\"Export All Organizations directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_search_files":{"id":"affinity_search_files","name":"Affinity Search Files","description":"Search files by keyword, ordered by relevance. Narrow to specific files or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these file IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s files. Cannot be combined with file IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of files to return, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_search_list_entries":{"id":"affinity_search_list_entries","name":"Affinity Search List Entries","description":"Search the rows of one list by filters, sorts, and a free-text term. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID to search"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_search_notes":{"id":"affinity_search_notes","name":"Affinity Search Notes","description":"Search notes by keyword, ordered by relevance. Narrow to specific notes or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these note IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s notes. Cannot be combined with note IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of notes to return, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_search_persons":{"id":"affinity_search_persons","name":"Affinity Search Persons","description":"Search persons by filters, sorts, and a free-text term. Requires the \\"Export All People directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_semantic_search":{"id":"affinity_semantic_search","name":"Affinity Semantic Search","description":"Find companies from a description in plain language — industry, technology, stage, or business model. Currently searches companies only.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to look for, in plain language, e.g. \\"climate tech companies in our pipeline\\". Up to 500 characters"},"listIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to companies on these lists, e.g. [1, 2]"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of companies to return, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_update_entity_field_value":{"id":"affinity_update_entity_field_value","name":"Affinity Update Entity Field Value","description":"Write one non-list field value on a company or person. The value type must match how the field is defined.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the field on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"hostedApiKey":"none"},"affinity_update_list_entry_field":{"id":"affinity_update_list_entry_field","name":"Affinity Update List Entry Field","description":"Write one field value on a list row. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"hostedApiKey":"none"},"affinity_update_list_field_dropdown_option":{"id":"affinity_update_list_field_dropdown_option","name":"Affinity Update List Field Dropdown Option","description":"Change a dropdown option on a list field. Every field is optional — supply only what should change, and only fields the option\'s kind actually has.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to update"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement option label. Supply at least one field to change"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"hostedApiKey":"none"},"affinity_update_note":{"id":"affinity_update_note","name":"Affinity Update Note","description":"Rewrite a note\'s body or replace which records it is attached to. Each list of IDs replaces that association wholesale, an empty list clears it, and omitting one leaves it untouched. A note\'s type cannot be changed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to update"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached companies, e.g. [1, 2]. Send [] to detach every company; omit to leave them unchanged"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached persons, e.g. [1, 2]. Send [] to detach every person; omit to leave them unchanged"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached opportunities, e.g. [1, 2]. Send [] to detach every opportunity; omit to leave them unchanged"}},"hostedApiKey":"none"},"agentmail_create_draft":{"id":"agentmail_create_draft","name":"Create Draft","description":"Create a new email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to create the draft in"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"inReplyTo":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of message being replied to"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}},"hostedApiKey":"none"},"agentmail_create_inbox":{"id":"agentmail_create_inbox","name":"Create Inbox","description":"Create a new email inbox with AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"username":{"type":"string","required":false,"visibility":"user-or-llm","description":"Username for the inbox email address"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Domain for the inbox email address"},"displayName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Display name for the inbox"}},"hostedApiKey":"none"},"agentmail_delete_draft":{"id":"agentmail_delete_draft","name":"Delete Draft","description":"Delete an email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to delete"}},"hostedApiKey":"none"},"agentmail_delete_inbox":{"id":"agentmail_delete_inbox","name":"Delete Inbox","description":"Delete an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to delete"}},"hostedApiKey":"none"},"agentmail_delete_thread":{"id":"agentmail_delete_thread","name":"Delete Thread","description":"Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to delete"},"permanent":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Force permanent deletion instead of moving to trash"}},"hostedApiKey":"none"},"agentmail_forward_message":{"id":"agentmail_forward_message","name":"Forward Message","description":"Forward an email message to new recipients in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to forward"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional plain text to prepend"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional HTML to prepend"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}},"hostedApiKey":"none"},"agentmail_get_draft":{"id":"agentmail_get_draft","name":"Get Draft","description":"Get details of a specific email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox the draft belongs to"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to retrieve"}},"hostedApiKey":"none"},"agentmail_get_inbox":{"id":"agentmail_get_inbox","name":"Get Inbox","description":"Get details of a specific email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to retrieve"}},"hostedApiKey":"none"},"agentmail_get_message":{"id":"agentmail_get_message","name":"Get Message","description":"Get details of a specific email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to retrieve"}},"hostedApiKey":"none"},"agentmail_get_thread":{"id":"agentmail_get_thread","name":"Get Thread","description":"Get details of a specific email thread including messages in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to retrieve"}},"hostedApiKey":"none"},"agentmail_list_drafts":{"id":"agentmail_list_drafts","name":"List Drafts","description":"List email drafts in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list drafts from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of drafts to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_inboxes":{"id":"agentmail_list_inboxes","name":"List Inboxes","description":"List all email inboxes in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of inboxes to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_messages":{"id":"agentmail_list_messages","name":"List Messages","description":"List messages in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list messages from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of messages to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_threads":{"id":"agentmail_list_threads","name":"List Threads","description":"List email threads in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list threads from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of threads to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"},"labels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to filter threads by"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentmail_reply_message":{"id":"agentmail_reply_message","name":"Reply to Message","description":"Reply to an existing email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to reply from"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to reply to"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text reply body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML reply body"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override recipient email addresses (comma-separated)"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC email addresses (comma-separated)"},"replyAll":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reply to all recipients of the original message"}},"hostedApiKey":"none"},"agentmail_send_draft":{"id":"agentmail_send_draft","name":"Send Draft","description":"Send an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to send"}},"hostedApiKey":"none"},"agentmail_send_message":{"id":"agentmail_send_message","name":"Send Message","description":"Send an email message from an AgentMail inbox","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to send from"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email address (comma-separated for multiple)"},"subject":{"type":"string","required":true,"visibility":"user-or-llm","description":"Email subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text email body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML email body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}},"hostedApiKey":"none"},"agentmail_update_draft":{"id":"agentmail_update_draft","name":"Update Draft","description":"Update an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to update"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}},"hostedApiKey":"none"},"agentmail_update_inbox":{"id":"agentmail_update_inbox","name":"Update Inbox","description":"Update the display name of an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to update"},"displayName":{"type":"string","required":true,"visibility":"user-or-llm","description":"New display name for the inbox"}},"hostedApiKey":"none"},"agentmail_update_message":{"id":"agentmail_update_message","name":"Update Message","description":"Add or remove labels on an email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the message"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the message"}},"hostedApiKey":"none"},"agentmail_update_thread":{"id":"agentmail_update_thread","name":"Update Thread Labels","description":"Add or remove labels on an email thread in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the thread"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the thread"}},"hostedApiKey":"none"},"agentphone_create_call":{"id":"agentphone_create_call","name":"Create Outbound Call","description":"Initiate an outbound voice call from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent that will handle the call"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number to call in E.164 format (e.g. +14155551234)"},"fromNumberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent\'s first assigned number is used."},"initialGreeting":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional greeting spoken when the recipient answers"},"voice":{"type":"string","required":false,"visibility":"user-or-llm","description":"Voice ID override for this call (defaults to the agent\'s configured voice)"},"systemPrompt":{"type":"string","required":false,"visibility":"user-or-llm","description":"When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook"}},"hostedApiKey":"none"},"agentphone_create_contact":{"id":"agentphone_create_contact","name":"Create Contact","description":"Create a new contact in AgentPhone","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"phoneNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number in E.164 format (e.g. +14155551234)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact\'s full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Contact\'s email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Freeform notes stored on the contact"}},"hostedApiKey":"none"},"agentphone_create_number":{"id":"agentphone_create_number","name":"Create Phone Number","description":"Provision a new SMS- and voice-enabled phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code (e.g. US, CA). Defaults to US."},"areaCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Preferred area code (US/CA only, e.g. \\"415\\"). Best-effort — may be ignored if unavailable."},"agentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optionally attach the number to an agent immediately"}},"hostedApiKey":"none"},"agentphone_delete_contact":{"id":"agentphone_delete_contact","name":"Delete Contact","description":"Delete a contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}},"hostedApiKey":"none"},"agentphone_get_call":{"id":"agentphone_get_call","name":"Get Call","description":"Fetch a call and its full transcript","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve"}},"hostedApiKey":"none"},"agentphone_get_call_transcript":{"id":"agentphone_get_call_transcript","name":"Get Call Transcript","description":"Get the full ordered transcript for a call","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve the transcript for"}},"hostedApiKey":"none"},"agentphone_get_contact":{"id":"agentphone_get_contact","name":"Get Contact","description":"Fetch a single contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}},"hostedApiKey":"none"},"agentphone_get_conversation":{"id":"agentphone_get_conversation","name":"Get Conversation","description":"Get a conversation along with its recent messages","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"messageLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of recent messages to include (default 50, max 100)"}},"hostedApiKey":"none"},"agentphone_get_conversation_messages":{"id":"agentphone_get_conversation_messages","name":"Get Conversation Messages","description":"Get paginated messages for a conversation","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentphone_get_number_messages":{"id":"agentphone_get_number_messages","name":"Get Phone Number Messages","description":"Fetch messages received on a specific phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentphone_get_usage":{"id":"agentphone_get_usage","name":"Get Usage","description":"Retrieve current usage statistics for the AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"}},"hostedApiKey":"none"},"agentphone_get_usage_daily":{"id":"agentphone_get_usage_daily","name":"Get Daily Usage","description":"Get a daily breakdown of usage (messages, calls, webhooks) for the last N days","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"days":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of days to return (1-365, default 30)"}},"hostedApiKey":"none"},"agentphone_get_usage_monthly":{"id":"agentphone_get_usage_monthly","name":"Get Monthly Usage","description":"Get monthly usage aggregation (messages, calls, webhooks) for the last N months","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"months":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of months to return (1-24, default 6)"}},"hostedApiKey":"none"},"agentphone_list_calls":{"id":"agentphone_list_calls","name":"List Calls","description":"List voice calls for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"},"status":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by status (completed, in-progress, failed)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by direction (inbound, outbound)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by call type (pstn, web)"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search by phone number (matches fromNumber or toNumber)"}},"hostedApiKey":"none"},"agentphone_list_contacts":{"id":"agentphone_list_contacts","name":"List Contacts","description":"List contacts for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by name or phone number (case-insensitive contains)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 50, max 200)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_list_conversations":{"id":"agentphone_list_conversations","name":"List Conversations","description":"List conversations (message threads) for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_list_numbers":{"id":"agentphone_list_numbers","name":"List Phone Numbers","description":"List all phone numbers provisioned for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_react_to_message":{"id":"agentphone_react_to_message","name":"React to Message","description":"Send an iMessage tapback reaction to a message (iMessage only)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to react to"},"reaction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Reaction type: love, like, dislike, laugh, emphasize, or question"}},"hostedApiKey":"none"},"agentphone_release_number":{"id":"agentphone_release_number","name":"Release Phone Number","description":"Release (delete) a phone number. This action is irreversible.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number to release"}},"hostedApiKey":"none"},"agentphone_send_message":{"id":"agentphone_send_message","name":"Send Message","description":"Send an outbound SMS or iMessage from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent sending the message"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient phone number in E.164 format (e.g. +14155551234)"},"body":{"type":"string","required":true,"visibility":"user-or-llm","description":"Message text to send"},"mediaUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional URL of an image, video, or file to attach"},"numberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to send from. If omitted, the agent\'s first assigned number is used."}},"hostedApiKey":"none"},"agentphone_update_contact":{"id":"agentphone_update_contact","name":"Update Contact","description":"Update a contact\'s fields","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"New phone number in E.164 format"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New contact name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"New email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"New freeform notes"}},"hostedApiKey":"none"},"agentphone_update_conversation":{"id":"agentphone_update_conversation","name":"Update Conversation","description":"Update conversation metadata (stored state). Pass null to clear existing metadata.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"metadata":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom key-value metadata to store on the conversation. Pass null to clear existing metadata."}},"hostedApiKey":"none"},"agiloft_async_status":{"id":"agiloft_async_status","name":"Agiloft Async Status","description":"Check whether an asynchronous Agiloft call, such as a run action button, has completed.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table the asynchronous call was made against"},"callbackId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Callback ID returned by the asynchronous call, e.g. from Run Action Button"}},"hostedApiKey":"none"},"agiloft_attach_file":{"id":"agiloft_attach_file","name":"Agiloft Attach File","description":"Attach a file to a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to attach the file to"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"file":{"type":"file","required":true,"visibility":"user-or-llm","description":"File to attach"},"fileName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name to assign to the file (defaults to original file name)"},"overwrite":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Replace the contents of the field instead of adding another file to it"}},"hostedApiKey":"none"},"agiloft_attachment_info":{"id":"agiloft_attachment_info","name":"Agiloft Attachment Info","description":"Get information about file attachments on a record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to check attachments on"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field to inspect"}},"hostedApiKey":"none"},"agiloft_create_record":{"id":"agiloft_create_record","name":"Agiloft Create Record","description":"Create a new record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record field values as a JSON object (e.g., {\\"first_name\\": \\"John\\", \\"status\\": \\"Active\\"})"}},"hostedApiKey":"none"},"agiloft_delete_record":{"id":"agiloft_delete_record","name":"Agiloft Delete Record","description":"Delete a record from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to delete"},"substituteIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated IDs of records that adopt the dependants of the deleted record. Read only when the delete rule is REPLACE_WITH_ANOTHER."},"deleteRule":{"type":"string","required":false,"visibility":"user-or-llm","description":"How to treat records that depend on this one: ERROR_IF_DEPENDANTS (default — fails rather than cascading), APPLY_DELETE_WHERE_POSSIBLE, DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK, APPLY_UNLINK, UNLINK_WHERE_POSSIBLE_OTHERWISE_DELETE, or REPLACE_WITH_ANOTHER"}},"hostedApiKey":"none"},"agiloft_get_choice_line_id":{"id":"agiloft_get_choice_line_id","name":"Agiloft Get Choice Line ID","description":"Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"case\\", \\"contracts\\")"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice field name (e.g., \\"priority\\", \\"status\\")"},"value":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice display value to resolve (e.g., \\"High\\", \\"Active\\")"}},"hostedApiKey":"none"},"agiloft_list_tables":{"id":"agiloft_list_tables","name":"Agiloft List Tables","description":"List the tables and fields in an Agiloft knowledge base, to discover the logical names other operations need.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":false,"visibility":"user-or-llm","description":"Logical name of a single table to describe (e.g., \\"contacts\\"). Leave empty to list every table in the knowledge base."},"includeLinkedInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the source table and column behind each linked field"},"skipColumnsInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Return table names only, omitting field details, for a much smaller response"}},"hostedApiKey":"none"},"agiloft_lock_record":{"id":"agiloft_lock_record","name":"Agiloft Lock Record","description":"Lock, unlock, or check the lock status of an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to lock, unlock, or check"},"lockAction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Action to perform: \\"lock\\", \\"unlock\\", or \\"check\\""},"force":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Unlock only: release a lock held by another user."}},"hostedApiKey":"none"},"agiloft_nlp_search":{"id":"agiloft_nlp_search","name":"Agiloft Natural Language Search","description":"Search Agiloft records by describing what you want in plain language, such as \\"active NDAs submitted last month\\".","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"nlpQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The request in plain language, e.g. \\"Show me open, high-priority contracts\\". Structured field filters are not accepted — use Search Records for those."},"fields":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated field names to return, e.g. \\"id, contract_title1, company_name\\""},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number, starting from 0"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Records per page"}},"hostedApiKey":"none"},"agiloft_read_record":{"id":"agiloft_read_record","name":"Agiloft Read Record","description":"Read a record by ID from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to read"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the response"}},"hostedApiKey":"none"},"agiloft_remove_attachment":{"id":"agiloft_remove_attachment","name":"Agiloft Remove Attachment","description":"Remove an attached file from a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file to remove (starting from 0)"}},"hostedApiKey":"none"},"agiloft_retrieve_attachment":{"id":"agiloft_retrieve_attachment","name":"Agiloft Retrieve Attachment","description":"Download an attached file from an Agiloft record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file in the field (starting from 0)"}},"hostedApiKey":"none"},"agiloft_run_action_button":{"id":"agiloft_run_action_button","name":"Agiloft Run Action Button","description":"Run an action button on an Agiloft record, such as an approval or send-for-signature step.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"case\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to run the action button on"},"actionButtonField":{"type":"string","required":true,"visibility":"user-or-llm","description":"Logical name of the field holding the action button (e.g., \\"ab_field\\")"}},"hostedApiKey":"none"},"agiloft_saved_search":{"id":"agiloft_saved_search","name":"Agiloft Saved Search","description":"List the saved searches defined for an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Logical table name to list saved searches for (e.g., \\"contract\\")"}},"hostedApiKey":"none"},"agiloft_search_records":{"id":"agiloft_search_records","name":"Agiloft Search Records","description":"Search for records in an Agiloft table using a query.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name to search in (e.g., \\"contracts\\", \\"contacts.employees\\")"},"query":{"type":"string","required":false,"visibility":"user-or-llm","description":"Ad hoc EWSearch query. Combine conditions with && (and) or || (or) and quote every value — e.g. \\"summary~=\'test\'&&priority=\'High\'\\". Required unless a saved search is given."},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Label of a saved search defined on the table (e.g., \\"C: Status is Closed\\"). Can be combined with a query to narrow it further."},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the results"},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number for paginated results (starting from 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return per page. Agiloft treats 0 as \\"all records\\", so leave it unset or use a positive value to keep result sizes bounded."}},"hostedApiKey":"none"},"agiloft_select_records":{"id":"agiloft_select_records","name":"Agiloft Select Records","description":"Select record IDs matching a SQL WHERE clause from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"where":{"type":"string","required":true,"visibility":"user-or-llm","description":"SQL WHERE clause using database column names (e.g., \\"summary like \'%new%\'\\" or \\"assigned_person=\'John Doe\'\\"). EWSelect has no page size and returns every matching ID, so append a database limit such as \\"limit 0,200\\" to bound the result."}},"hostedApiKey":"none"},"agiloft_update_record":{"id":"agiloft_update_record","name":"Agiloft Update Record","description":"Update an existing record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to update"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Updated field values as a JSON object (e.g., {\\"status\\": \\"Active\\", \\"priority\\": \\"High\\"})"}},"hostedApiKey":"none"},"agiloft_upsert_record":{"id":"agiloft_upsert_record","name":"Agiloft Upsert Record","description":"Create an Agiloft record, or update it when a record already matches the given fields.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"match":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field used to find an existing record (e.g., \\"ext_id\\"). Pick something that identifies a record uniquely — if more than one record matches, Agiloft writes nothing and returns a conflict."},"async":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Queue the write instead of waiting for it. Returns a callback ID instead of a record ID; pass that to Async Status to poll the result."},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field values as a JSON object. On create these populate the new record; on update only the supplied fields change."}},"hostedApiKey":"none"},"ahrefs_anchors":{"id":"ahrefs_anchors","name":"Ahrefs Anchors","description":"Get the anchor text distribution for a target domain or URL\'s backlinks, showing how many links and referring domains use each anchor text.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_backlinks":{"id":"ahrefs_backlinks","name":"Ahrefs Backlinks","description":"Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live backlinks), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_backlinks_stats":{"id":"ahrefs_backlinks_stats","name":"Ahrefs Backlinks Stats","description":"Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_batch_analysis":{"id":"ahrefs_batch_analysis","name":"Ahrefs Batch Analysis","description":"Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traffic, and more) for multiple domains or URLs in a single request. Useful for comparing many competitors at once.","version":"1.0.0","params":{"targets":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated list of domains or URLs to analyze. Example: \\"example.com,competitor.com\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode applied to every target: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"protocol":{"type":"string","required":false,"visibility":"user-or-llm","description":"Protocol applied to every target: \\"both\\" (default), \\"http\\", or \\"https\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_broken_backlinks":{"id":"ahrefs_broken_backlinks","name":"Ahrefs Broken Backlinks","description":"Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_domain_rating":{"id":"ahrefs_domain_rating","name":"Ahrefs Domain Rating","description":"Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website\'s backlink profile on a scale from 0 to 100.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze (e.g., example.com)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date for historical data in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_domain_rating_history":{"id":"ahrefs_domain_rating_history","name":"Ahrefs Domain Rating History","description":"Get the historical Domain Rating (DR) trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_keyword_overview":{"id":"ahrefs_keyword_overview","name":"Ahrefs Keyword Overview","description":"Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The keyword to analyze"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_keywords_history":{"id":"ahrefs_keywords_history","name":"Ahrefs Keywords History","description":"Get the historical organic keyword ranking distribution for a target domain or URL over a date range: how many keywords rank in each position bucket at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_metrics":{"id":"ahrefs_metrics","name":"Ahrefs Metrics","description":"Get a one-call organic and paid search overview for a target domain or URL: organic traffic, organic keywords, paid traffic, paid keywords, and estimated traffic cost.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_metrics_history":{"id":"ahrefs_metrics_history","name":"Ahrefs Metrics History","description":"Get the historical organic and paid traffic trend for a target domain or URL over a date range: organic traffic/cost and paid traffic/cost at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_organic_competitors":{"id":"ahrefs_organic_competitors","name":"Ahrefs Organic Competitors","description":"Get domains that compete with a target domain or URL for the same organic keywords, ranked by keyword overlap.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_organic_keywords":{"id":"ahrefs_organic_keywords","name":"Ahrefs Organic Keywords","description":"Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_paid_pages":{"id":"ahrefs_paid_pages","name":"Ahrefs Paid Pages","description":"Get a target domain\'s pages that receive paid search traffic, sorted by estimated paid traffic. Returns page URLs with their paid traffic, keyword counts, and estimated spend.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_competitors_overview":{"id":"ahrefs_rank_tracker_competitors_overview","name":"Ahrefs Rank Tracker Competitors Overview","description":"Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker project: each tracked keyword\'s volume and difficulty alongside every competitor\'s position, traffic, and traffic value. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_competitors_stats":{"id":"ahrefs_rank_tracker_competitors_stats","name":"Ahrefs Rank Tracker Competitors Stats","description":"Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competitor\'s traffic, traffic value, average position, and share of voice across all tracked keywords. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report metrics for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_overview":{"id":"ahrefs_rank_tracker_overview","name":"Ahrefs Rank Tracker Overview","description":"Get ranking overview metrics for the keywords tracked in an Ahrefs Rank Tracker project: position, search volume, keyword difficulty, and estimated traffic. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_serp_overview":{"id":"ahrefs_rank_tracker_serp_overview","name":"Ahrefs Rank Tracker SERP Overview","description":"Get the full SERP (search engine results page) for a keyword tracked in an Ahrefs Rank Tracker project, including every ranking URL with its position, title, and authority metrics. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The tracked keyword to retrieve SERP data for"},"country":{"type":"string","required":true,"visibility":"user-or-llm","description":"Country code for the tracked keyword. Example: \\"us\\", \\"gb\\", \\"de\\""},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"topPositions":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of top organic positions to return (defaults to all available)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Timestamp to return the last available SERP Overview at, in YYYY-MM-DDThh:mm:ss format"},"locationId":{"type":"number","required":false,"visibility":"user-or-llm","description":"Location ID of the tracked keyword, if tracked at a specific location"},"languageCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code of the tracked keyword"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_refdomains_history":{"id":"ahrefs_refdomains_history","name":"Ahrefs Referring Domains History","description":"Get the historical referring domains trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_referring_domains":{"id":"ahrefs_referring_domains","name":"Ahrefs Referring Domains","description":"Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost domains), or \\"since:YYYY-MM-DD\\" (domains found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_related_terms":{"id":"ahrefs_related_terms","name":"Ahrefs Related Terms","description":"Get keyword ideas related to a seed keyword: terms the same top-ranking pages also rank for (\\"also rank for\\") or also discuss (\\"also talk about\\"), with volume, difficulty, and CPC.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The seed keyword to find related terms for"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"terms":{"type":"string","required":false,"visibility":"user-or-llm","description":"Type of related keywords to return: \\"also_rank_for\\", \\"also_talk_about\\", or \\"all\\" (default: \\"all\\")"},"viewFor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Whether to derive related terms from the top 10 or top 100 ranking pages (default: \\"top_10\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_site_audit_page_explorer":{"id":"ahrefs_site_audit_page_explorer","name":"Ahrefs Site Audit Page Explorer","description":"Get crawled pages from an Ahrefs Site Audit project with health and SEO metrics: HTTP status, title, link counts, backlinks, indexability, and traffic. Optionally filter to pages affected by a specific issue.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Site Audit project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Crawl date in YYYY-MM-DDThh:mm:ss format (defaults to the most recent crawl)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip, for pagination"},"issueId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Only return pages affected by this issue ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_top_pages":{"id":"ahrefs_top_pages","name":"Ahrefs Top Pages","description":"Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"airtable_create_records":{"id":"airtable_create_records","name":"Airtable Create Records","description":"Write new records to an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to create, each with a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_delete_records":{"id":"airtable_delete_records","name":"Airtable Delete Records","description":"Delete one or more records from an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordIds":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of record IDs to delete (each starts with \\"rec\\", e.g., [\\"recXXXXXXXXXXXXXX\\"]). Pass a single-element array to delete one record."}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_get_base_schema":{"id":"airtable_get_base_schema","name":"Airtable Get Base Schema","description":"Get the schema of all tables, fields, and views in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_get_record":{"id":"airtable_get_record","name":"Airtable Get Record","description":"Retrieve a single record from an Airtable table by its ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to retrieve (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_bases":{"id":"airtable_list_bases","name":"Airtable List Bases","description":"List all bases the authenticated user has access to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination offset for retrieving additional bases"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_records":{"id":"airtable_list_records","name":"Airtable List Records","description":"Read records from an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"maxRecords":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return (default: all records)"},"filterFormula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Formula to filter records (e.g., \\"({Field Name} = \'Value\')\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_tables":{"id":"airtable_list_tables","name":"Airtable List Tables","description":"List all tables and their schema in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_update_multiple_records":{"id":"airtable_update_multiple_records","name":"Airtable Update Multiple Records","description":"Update multiple existing records in an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to update, each with an `id` and a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_update_record":{"id":"airtable_update_record","name":"Airtable Update Record","description":"Update an existing record in an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to update (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"},"fields":{"type":"json","required":true,"visibility":"user-or-llm","description":"An object containing the field names and their new values"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_upsert_records":{"id":"airtable_upsert_records","name":"Airtable Upsert Records","description":"Update existing records or create new ones in an Airtable table, matching on the specified merge fields","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to upsert, each with a `fields` object"},"fieldsToMergeOn":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of field names used to match existing records (max 3). A record is updated when all merge fields match, otherwise it is created. Example: [\\"Name\\"]"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airweave_search":{"id":"airweave_search","name":"Airweave Search","description":"Search your synced data collections using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Airweave API Key for authentication"},"collectionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The readable ID of the collection to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query text"},"limit":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 100)"},"retrievalStrategy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retrieval strategy: hybrid (default), neural, or keyword"},"expandQuery":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate query variations to improve recall"},"rerank":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reorder results for improved relevance using LLM"},"generateAnswer":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate a natural-language answer to the query"}},"hostedApiKey":"none"},"algolia_add_record":{"id":"algolia_add_record","name":"Algolia Add Record","description":"Add or replace a record in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":false,"visibility":"user-or-llm","description":"Object ID for the record (auto-generated if not provided)"},"record":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object representing the record to add"}},"hostedApiKey":"none"},"algolia_batch_operations":{"id":"algolia_batch_operations","name":"Algolia Batch Operations","description":"Perform batch add, update, partial update, or delete operations on records in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of batch operations. Each item has \\"action\\" (addObject, updateObject, partialUpdateObject, partialUpdateObjectNoCreate, deleteObject, delete, clear) and \\"body\\" (the record data; must include objectID for update/delete; use an empty object {} for the index-level delete/clear actions)"}},"hostedApiKey":"none"},"algolia_browse_records":{"id":"algolia_browse_records","name":"Algolia Browse Records","description":"Browse and iterate over all records in an Algolia index using cursor pagination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key (must have browse ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to browse"},"query":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search query to filter browsed records"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string to narrow down results"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 1000, max: 1000)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous browse response for pagination"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}},"hostedApiKey":"none"},"algolia_clear_records":{"id":"algolia_clear_records","name":"Algolia Clear Records","description":"Clear all records from an Algolia index while keeping settings, synonyms, and rules","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to clear"}},"hostedApiKey":"none"},"algolia_copy_move_index":{"id":"algolia_copy_move_index","name":"Algolia Copy/Move Index","description":"Copy or move an Algolia index to a new destination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the source index"},"operation":{"type":"string","required":true,"visibility":"user-or-llm","description":"Operation to perform: \\"copy\\" or \\"move\\""},"destination":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the destination index"},"scope":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of scopes to copy (only for \\"copy\\" operation): [\\"settings\\", \\"synonyms\\", \\"rules\\"]. Omit to copy everything including records."}},"hostedApiKey":"none"},"algolia_delete_by_filter":{"id":"algolia_delete_by_filter","name":"Algolia Delete By Filter","description":"Delete all records matching a filter from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter expression to match records for deletion (e.g., \\"category:outdated\\")"},"facetFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of facet filters (e.g., [\\"brand:Acme\\"])"},"numericFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of numeric filters (e.g., [\\"price > 100\\"])"},"tagFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of tag filters using the _tags attribute (e.g., [\\"published\\"])"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search filter (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search filter"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search filter"}},"hostedApiKey":"none"},"algolia_delete_index":{"id":"algolia_delete_index","name":"Algolia Delete Index","description":"Delete an entire Algolia index and all its records","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to delete"}},"hostedApiKey":"none"},"algolia_delete_record":{"id":"algolia_delete_record","name":"Algolia Delete Record","description":"Delete a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to delete"}},"hostedApiKey":"none"},"algolia_get_record":{"id":"algolia_get_record","name":"Algolia Get Record","description":"Get a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to retrieve"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"}},"hostedApiKey":"none"},"algolia_get_records":{"id":"algolia_get_records","name":"Algolia Get Records","description":"Retrieve multiple records by objectID from one or more Algolia indices","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Default index name for all requests"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of objects specifying records to retrieve. Each must have \\"objectID\\" and optionally \\"indexName\\" and \\"attributesToRetrieve\\"."}},"hostedApiKey":"none"},"algolia_get_settings":{"id":"algolia_get_settings","name":"Algolia Get Settings","description":"Retrieve the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"}},"hostedApiKey":"none"},"algolia_get_task_status":{"id":"algolia_get_task_status","name":"Algolia Get Task Status","description":"Check whether an Algolia indexing task has finished publishing","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index the task ran against"},"taskID":{"type":"number","required":true,"visibility":"user-or-llm","description":"The taskID returned by a previous write operation"}},"hostedApiKey":"none"},"algolia_list_indices":{"id":"algolia_list_indices","name":"Algolia List Indices","description":"List all indices in an Algolia application","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for paginating indices (default: not paginated)"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of indices per page (default: 100)"}},"hostedApiKey":"none"},"algolia_partial_update_record":{"id":"algolia_partial_update_record","name":"Algolia Partial Update Record","description":"Partially update a record in an Algolia index without replacing it entirely","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to update"},"attributes":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with attributes to update. Supports built-in operations like {\\"stock\\": {\\"_operation\\": \\"Decrement\\", \\"value\\": 1}}"},"createIfNotExists":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to create the record if it does not exist (default: true)"}},"hostedApiKey":"none"},"algolia_search":{"id":"algolia_search","name":"Algolia Search","description":"Search an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"Search query text"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 20)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number to retrieve (default: 0)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string (e.g., \\"category:electronics AND price < 100\\")"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"facets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of facet attribute names to retrieve counts for (use \\"*\\" for all)"},"getRankingInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to include detailed ranking information in each hit"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}},"hostedApiKey":"none"},"algolia_update_settings":{"id":"algolia_update_settings","name":"Algolia Update Settings","description":"Update the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have editSettings ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"settings":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with settings to update (e.g., {\\"searchableAttributes\\": [\\"name\\", \\"description\\"], \\"customRanking\\": [\\"desc(popularity)\\"]})"},"forwardToReplicas":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to apply changes to replica indices (default: false)"}},"hostedApiKey":"none"},"amplitude_event_segmentation":{"id":"amplitude_event_segmentation","name":"Amplitude Event Segmentation","description":"Query event analytics data with segmentation. Get event counts, uniques, averages, and more.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Event type name to analyze"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: uniques, totals, pct_dau, average, histogram, sums, value_avg, or formula (default: uniques)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (prefix custom user properties with \\"gp:\\")"},"groupBy2":{"type":"string","required":false,"visibility":"user-or-llm","description":"Second property name to group by (prefix custom user properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (max 1000)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON array of filter objects applied to the event, e.g. [{\\"subprop_type\\":\\"event\\",\\"subprop_key\\":\\"city\\",\\"subprop_op\\":\\"is\\",\\"subprop_value\\":[\\"San Francisco\\"]}]"},"formula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when metric is \\"formula\\", e.g. \\"UNIQUES(A)/UNIQUES(B)\\""},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_funnels":{"id":"amplitude_funnels","name":"Amplitude Funnels","description":"Analyze conversion rates and drop-off between a sequence of events.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"events":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON array of event objects, one per funnel step in order, e.g. [{\\"event_type\\":\\"signup\\"},{\\"event_type\\":\\"purchase\\"}]"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Funnel ordering: \\"ordered\\", \\"unordered\\", or \\"sequential\\" (default: ordered)"},"userType":{"type":"string","required":false,"visibility":"user-or-llm","description":"User type: \\"new\\" or \\"active\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: -300000 (real-time), -3600000 (hourly), 1 (daily), 7 (weekly), or 30 (monthly)"},"conversionWindowSeconds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversion window in seconds (default: 2592000, i.e. 30 days)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (default: 100, max: 1000)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_get_active_users":{"id":"amplitude_get_active_users","name":"Amplitude Get Active Users","description":"Get active or new user counts over a date range from the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: \\"active\\" or \\"new\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_get_revenue":{"id":"amplitude_get_revenue","name":"Amplitude Get Revenue","description":"Get revenue LTV data including ARPU, ARPPU, total revenue, and paying user counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric: 0 (ARPU), 1 (ARPPU), 2 (Total Revenue), 3 (Paying Users)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (limit: one)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_group_identify":{"id":"amplitude_group_identify","name":"Amplitude Group Identify","description":"Set group-level properties in Amplitude. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"groupType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Group classification (e.g., \\"company\\", \\"org_id\\")"},"groupValue":{"type":"string","required":true,"visibility":"user-or-llm","description":"Specific group identifier (e.g., \\"Acme Corp\\")"},"groupProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of group properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_identify_user":{"id":"amplitude_identify_user","name":"Amplitude Identify User","description":"Set user properties in Amplitude using the Identify API. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"userProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of user properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_list_events":{"id":"amplitude_list_events","name":"Amplitude List Events","description":"List all event types in the Amplitude project with their weekly totals and unique counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_realtime_active_users":{"id":"amplitude_realtime_active_users","name":"Amplitude Real-time Active Users","description":"Get real-time active user counts at 5-minute granularity for the last 2 days.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_retention":{"id":"amplitude_retention","name":"Amplitude Retention","description":"Measure how many users return to perform an action after a starting action.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"startEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON starting event object, e.g. {\\"event_type\\":\\"_new\\"} or {\\"event_type\\":\\"_active\\"}"},"returnEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON returning event object, e.g. {\\"event_type\\":\\"_all\\"} or {\\"event_type\\":\\"_active\\"}"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"retentionMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retention type: \\"bracket\\", \\"rolling\\", or \\"n-day\\" (default: n-day)"},"retentionBrackets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when Retention Mode is \\"bracket\\". Day ranges, e.g. [[0,4]]"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_send_event":{"id":"amplitude_send_event","name":"Amplitude Send Event","description":"Track an event in Amplitude using the HTTP V2 API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the event (e.g., \\"page_view\\", \\"purchase\\")"},"eventProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of custom event properties"},"userProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of user properties to set (supports $set, $setOnce, $add, $append, $unset)"},"time":{"type":"string","required":false,"visibility":"user-or-llm","description":"Event timestamp in milliseconds since epoch"},"sessionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Session start time in milliseconds since epoch"},"insertId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Unique ID for deduplication (within 7-day window)"},"appVersion":{"type":"string","required":false,"visibility":"user-or-llm","description":"Application version string"},"platform":{"type":"string","required":false,"visibility":"user-or-llm","description":"Platform (e.g., \\"Web\\", \\"iOS\\", \\"Android\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code"},"language":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code (e.g., \\"en\\")"},"ip":{"type":"string","required":false,"visibility":"user-or-llm","description":"IP address for geo-location"},"price":{"type":"string","required":false,"visibility":"user-or-llm","description":"Price of the item purchased"},"quantity":{"type":"string","required":false,"visibility":"user-or-llm","description":"Quantity of items purchased"},"revenue":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue amount"},"productId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Product identifier"},"revenueType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue type (e.g., \\"purchase\\", \\"refund\\")"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_user_activity":{"id":"amplitude_user_activity","name":"Amplitude User Activity","description":"Get the event stream for a specific user by their Amplitude ID.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"amplitudeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Amplitude internal user ID"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Offset for pagination (default 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of events to return (default 1000, max 1000)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort direction: \\"latest\\" or \\"earliest\\" (default: latest)"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_user_profile":{"id":"amplitude_user_profile","name":"Amplitude User Profile","description":"Get a user profile including properties, cohort memberships, and computed properties. Not available for EU data-residency projects.","version":"1.0.0","params":{"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"External user ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"getAmpProps":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include Amplitude user properties (true/false, default: false)"},"getCohortIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include cohort IDs the user belongs to (true/false, default: false)"},"getComputations":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include computed user properties (true/false, default: false)"}},"hostedApiKey":"none"},"amplitude_user_search":{"id":"amplitude_user_search","name":"Amplitude User Search","description":"Search for a user by User ID, Device ID, or Amplitude ID using the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"user":{"type":"string","required":true,"visibility":"user-or-llm","description":"User ID, Device ID, or Amplitude ID to search for"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"apify_get_dataset_items":{"id":"apify_get_dataset_items","name":"APIFY Get Dataset Items","description":"Retrieve items stored in an APIFY dataset","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"datasetId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Dataset ID to read items from. Example: \\"9RnD3Pql2vGZkc5H5\\""},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max items to return (1-250000). Default: all items. Example: 500"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to skip at the start. Default: 0"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of fields to include. Example: \\"title,url,price\\""}},"hostedApiKey":"none"},"apify_get_run":{"id":"apify_get_run","name":"APIFY Get Run","description":"Get the status and details of an APIFY actor run","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"runId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor run ID to fetch. Example: \\"HG7ML7M8z78YcAPEB\\""}},"hostedApiKey":"none"},"apify_run_actor_async":{"id":"apify_run_actor_async","name":"APIFY Run Actor (Async)","description":"Run an APIFY actor asynchronously with polling for long-running tasks","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ], \\"maxPages\\": 10 }"},"waitForFinish":{"type":"number","required":false,"visibility":"user-or-llm","description":"Initial wait time in seconds (0-60) before polling starts. Example: 30"},"itemLimit":{"type":"number","required":false,"default":100,"visibility":"user-or-llm","description":"Max dataset items to fetch (1-250000). Default: 100. Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}},"hostedApiKey":"none"},"apify_run_actor_sync":{"id":"apify_run_actor_sync","name":"APIFY Run Actor (Sync)","description":"Run an APIFY actor synchronously and get results (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ], \\"maxPages\\": 10 }"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}},"hostedApiKey":"none"},"apify_run_task":{"id":"apify_run_task","name":"APIFY Run Task","description":"Run a saved APIFY actor task synchronously and get dataset items (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task ID or username/task-name. Examples: \\"janedoe/my-task\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON string that overrides the task\'s saved input. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ] }"},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max dataset items to return (1-250000). Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the run (128-32768). Example: 1024 for 1GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the run. Example: 300 for 5 minutes"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\""}},"hostedApiKey":"none"},"apollo_account_bulk_create":{"id":"apollo_account_bulk_create","name":"Apollo Bulk Create Accounts","description":"Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"accounts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of accounts to create (max 100). Each account should include a name, and may optionally include domain, phone, phone_status_cd, raw_address, owner_id, linkedin_url, facebook_url, twitter_url, salesforce_id, and hubspot_id."},"append_label_names":{"type":"array","required":false,"visibility":"user-only","description":"Array of label names to add to ALL accounts in this request"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, performs aggressive deduplication by domain, organization_id, and name (defaults to false)"}},"hostedApiKey":"none"},"apollo_account_bulk_update":{"id":"apollo_account_bulk_update","name":"Apollo Bulk Update Accounts","description":"Update up to 1000 existing accounts at once in your Apollo database (higher limit than contacts!). Each account must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"account_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of account IDs to update with the same values (max 1000). Use with name/owner_id for uniform updates. Use either this OR account_attributes."},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this name to all accounts"},"owner_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this owner to all accounts"},"account_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this account stage to all accounts"},"account_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of account objects with individual updates (each must include id). Example: [{\\"id\\": \\"acc1\\", \\"name\\": \\"Acme\\", \\"owner_id\\": \\"u1\\", \\"account_stage_id\\": \\"s1\\", \\"typed_custom_fields\\": {\\"field_id\\": \\"value\\"}}]"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes."}},"hostedApiKey":"none"},"apollo_account_create":{"id":"apollo_account_create","name":"Apollo Create Account","description":"Create a new account (company) in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain without www. prefix (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the account"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_account_search":{"id":"apollo_account_search","name":"Apollo Search Accounts","description":"Search your team\'s accounts in Apollo. Display limit: 50,000 records (100 records per page, 500 pages max). Use filters to narrow results. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter accounts by organization name (partial-match search)"},"account_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account stage IDs"},"account_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account label IDs"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"account_last_activity_date\\", \\"account_created_at\\", or \\"account_updated_at\\""},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Sort ascending when true. Defaults to descending."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_account_update":{"id":"apollo_account_update","name":"Apollo Update Account","description":"Update an existing account in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the account to update (e.g., \\"acc_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company phone number"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_contact_bulk_create":{"id":"apollo_contact_bulk_create","name":"Apollo Bulk Create Contacts","description":"Create up to 100 contacts at once in your Apollo database. Supports deduplication to prevent creating duplicate contacts. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contacts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contacts to create (max 100). Each contact may include first_name, last_name, email, title, organization_name, account_id, owner_id, contact_stage_id, linkedin_url, phone (single string) or phone_numbers (array of {raw_number, position}), contact_emails, typed_custom_fields, and CRM IDs (salesforce_contact_id, hubspot_id, team_id) for cross-system matching"},"append_label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Label names to add to all contacts in this request (e.g., [\\"Hot Lead\\"])"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"Enable deduplication to prevent creating duplicate contacts. When true, existing contacts are returned without modification"}},"hostedApiKey":"none"},"apollo_contact_bulk_update":{"id":"apollo_contact_bulk_update","name":"Apollo Bulk Update Contacts","description":"Update up to 100 existing contacts at once in your Apollo database. Each contact must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to update. Must be paired with an object-form contact_attributes specifying the fields to apply uniformly to all listed contacts."},"contact_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Required. Either an array of per-contact updates (each with id) — used standalone — or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"Force asynchronous processing. Automatically enabled for >100 contacts"}},"hostedApiKey":"none"},"apollo_contact_create":{"id":"apollo_contact_create","name":"Apollo Create Contact","description":"Create a new contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the contact"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID to associate with (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for POST /contacts)"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, Apollo deduplicates against existing contacts"}},"hostedApiKey":"none"},"apollo_contact_search":{"id":"apollo_contact_search","name":"Apollo Search Contacts","description":"Search your team\'s contacts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"contact_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by contact stage IDs"},"contact_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by Apollo label IDs (lists)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-only","description":"Sort field: contact_last_activity_date, contact_email_last_opened_at, contact_email_last_clicked_at, contact_created_at, or contact_updated_at"},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, sort ascending. Must be used together with sort_by_field"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_contact_update":{"id":"apollo_contact_update","name":"Apollo Update Contact","description":"Update an existing contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"contact_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the contact to update (e.g., \\"con_abc123\\")"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for PATCH /contacts/{id})"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"}},"hostedApiKey":"none"},"apollo_email_accounts":{"id":"apollo_email_accounts","name":"Apollo Get Email Accounts","description":"Get list of team\'s linked email accounts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"}},"hostedApiKey":"none"},"apollo_opportunity_create":{"id":"apollo_opportunity_create","name":"Apollo Create Opportunity","description":"Create a new deal for an account in your Apollo database (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of the account this opportunity belongs to (e.g., \\"acc_abc123\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_opportunity_get":{"id":"apollo_opportunity_get","name":"Apollo Get Opportunity","description":"Retrieve complete details of a specific deal/opportunity by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to retrieve (e.g., \\"opp_abc123\\")"}},"hostedApiKey":"none"},"apollo_opportunity_search":{"id":"apollo_opportunity_search","name":"Apollo Search Opportunities","description":"Search and list all deals/opportunities in your team\'s Apollo account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"amount\\", \\"is_closed\\", or \\"is_won\\""},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_opportunity_update":{"id":"apollo_opportunity_update","name":"Apollo Update Opportunity","description":"Update an existing deal/opportunity in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to update (e.g., \\"opp_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_organization_bulk_enrich":{"id":"apollo_organization_bulk_enrich","name":"Apollo Bulk Organization Enrichment","description":"Enrich data for up to 10 organizations at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domains":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of company domains to enrich (max 10, no www. or @, e.g., [\\"apollo.io\\", \\"stripe.com\\"])"}},"hostedApiKey":"none"},"apollo_organization_enrich":{"id":"apollo_organization_enrich","name":"Apollo Organization Enrichment","description":"Enrich data for a single organization using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domain":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"}},"hostedApiKey":"none"},"apollo_organization_search":{"id":"apollo_organization_search","name":"Apollo Organization Search","description":"Search Apollo\'s database for companies using filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company HQ locations (cities, US states, or countries)"},"organization_not_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Exclude companies whose HQ is in these locations"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges as \\"min,max\\" strings (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"q_organization_keyword_tags":{"type":"array","required":false,"visibility":"user-or-llm","description":"Industry or keyword tags"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Organization name to search for (e.g., \\"Acme\\", \\"TechCorp\\")"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to include (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Domain names to filter by (no www. or @, up to 1,000)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_people_bulk_enrich":{"id":"apollo_people_bulk_enrich","name":"Apollo Bulk People Enrichment","description":"Enrich data for up to 10 people at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"people":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of people to enrich (max 10)"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}},"hostedApiKey":"none"},"apollo_people_enrich":{"id":"apollo_people_enrich","name":"Apollo People Enrichment","description":"Enrich data for a single person using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the person"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the person"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Full name of the person (alternative to first_name/last_name)"},"id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the person"},"hashed_email":{"type":"string","required":false,"visibility":"user-or-llm","description":"MD5 or SHA-256 hashed email"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the person"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name where the person works"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"},"linkedin_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}},"hostedApiKey":"none"},"apollo_people_search":{"id":"apollo_people_search","name":"Apollo People Search","description":"Search Apollo\'s database for people using demographic filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"person_titles":{"type":"array","required":false,"visibility":"user-or-llm","description":"Job titles to search for (e.g., [\\"CEO\\", \\"VP of Sales\\"])"},"include_similar_titles":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to return people with job titles similar to person_titles"},"person_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Locations to search in (e.g., [\\"San Francisco, CA\\", \\"New York, NY\\"])"},"person_seniorities":{"type":"array","required":false,"visibility":"user-or-llm","description":"Seniority levels (one of: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern)"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to filter by (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"organization_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company names to search within (legacy filter)"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Headquarters locations of the people\'s current employer (e.g., [\'texas\', \'tokyo\', \'spain\'])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employer domain names (e.g., [\\"apollo.io\\", \\"microsoft.com\\"]) — up to 1,000, no www. or @"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges for the person\'s current employer. Each entry is \\"min,max\\" (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"contact_email_status":{"type":"array","required":false,"visibility":"user-or-llm","description":"Email statuses to filter by: \\"verified\\", \\"unverified\\", \\"likely to engage\\", \\"unavailable\\""},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination, default 1 (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, default 25, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_sequence_add_contacts":{"id":"apollo_sequence_add_contacts","name":"Apollo Add Contacts to Sequence","description":"Add contacts to an Apollo sequence","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sequence_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the sequence to add contacts to (e.g., \\"seq_abc123\\")"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to add to the sequence (e.g., [\\"con_abc123\\", \\"con_def456\\"]). Either contact_ids or label_names must be provided."},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of label names to identify contacts to add to the sequence. Either contact_ids or label_names must be provided."},"send_email_from_email_account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the email account to send from. Use the Get Email Accounts operation to look this up."},"send_email_from_email_address":{"type":"string","required":false,"visibility":"user-only","description":"Specific email address to send from within the email account."},"sequence_no_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they have no email address"},"sequence_unverified_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts with unverified email addresses"},"sequence_job_change":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who recently changed jobs"},"sequence_active_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts active in other campaigns"},"sequence_finished_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who finished other campaigns"},"sequence_same_company_in_same_campaign":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if others from the same company are in the sequence"},"contacts_without_ownership_permission":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts without ownership permission"},"add_if_in_queue":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they are in the queue"},"contact_verification_skipped":{"type":"boolean","required":false,"visibility":"user-only","description":"Skip contact verification when adding"},"user_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the user performing the action"},"status":{"type":"string","required":false,"visibility":"user-only","description":"Initial status for added contacts: \\"active\\" or \\"paused\\""},"auto_unpause_at":{"type":"string","required":false,"visibility":"user-only","description":"ISO 8601 datetime to automatically unpause contacts"}},"hostedApiKey":"none"},"apollo_sequence_search":{"id":"apollo_sequence_search","name":"Apollo Search Sequences","description":"Search for sequences/campaigns in your team\'s Apollo account (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search sequences by name (e.g., \\"Outbound Q1\\", \\"Follow-up\\")"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_task_create":{"id":"apollo_task_create","name":"Apollo Create Task","description":"Create one or more tasks in Apollo (one task per contact_id, master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"user_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the Apollo user the task is assigned to"},"contact_ids":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contact IDs. One task is created per contact."},"priority":{"type":"string","required":false,"visibility":"user-or-llm","description":"Task priority: \\"high\\", \\"medium\\", or \\"low\\" (defaults to \\"medium\\")"},"due_at":{"type":"string","required":true,"visibility":"user-or-llm","description":"Due date/time in ISO 8601 format (e.g., \\"2024-12-31T23:59:59Z\\")"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task type: \\"call\\", \\"outreach_manual_email\\", \\"linkedin_step_connect\\", \\"linkedin_step_message\\", \\"linkedin_step_view_profile\\", \\"linkedin_step_interact_post\\", or \\"action_item\\""},"status":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task status: \\"scheduled\\", \\"completed\\", or \\"skipped\\""},"note":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-form note providing context for the task"}},"hostedApiKey":"none"},"apollo_task_search":{"id":"apollo_task_search","name":"Apollo Search Tasks","description":"Search for tasks in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"task_due_at\\" or \\"task_priority\\""},"open_factor_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Filter by status. Common values: [\\"task_types\\"] for open tasks, [\\"task_completed_at\\"] for completed tasks."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"appconfig_create_application":{"id":"appconfig_create_application","name":"AppConfig Create Application","description":"Create an application in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the application to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the application"}},"hostedApiKey":"none"},"appconfig_create_configuration_profile":{"id":"appconfig_create_configuration_profile","name":"AppConfig Create Configuration Profile","description":"Create a configuration profile in an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the configuration profile in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the configuration profile"},"locationUri":{"type":"string","required":true,"visibility":"user-or-llm","description":"Where the configuration is stored. Use \\"hosted\\" for AppConfig-hosted configurations, or an SSM/S3 URI"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"ARN of an IAM role to retrieve the configuration (required for non-hosted URIs)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Profile type: AWS.Freeform (default) or AWS.AppConfig.FeatureFlags"}},"hostedApiKey":"none"},"appconfig_create_environment":{"id":"appconfig_create_environment","name":"AppConfig Create Environment","description":"Create an environment for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the environment in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the environment to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the environment"}},"hostedApiKey":"none"},"appconfig_create_hosted_configuration_version":{"id":"appconfig_create_hosted_configuration_version","name":"AppConfig Create Hosted Configuration Version","description":"Create a new hosted configuration version for an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to add the version to"},"content":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration content (e.g., a JSON or YAML document)"},"contentType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Content type of the configuration (e.g., application/json, text/plain)"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration version"},"latestVersionNumber":{"type":"number","required":false,"visibility":"user-or-llm","description":"The version number of the latest version, used for optimistic concurrency"},"versionLabel":{"type":"string","required":false,"visibility":"user-or-llm","description":"A user-defined label for the configuration version"}},"hostedApiKey":"none"},"appconfig_delete_application":{"id":"appconfig_delete_application","name":"AppConfig Delete Application","description":"Delete an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_configuration_profile":{"id":"appconfig_delete_configuration_profile","name":"AppConfig Delete Configuration Profile","description":"Delete an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_environment":{"id":"appconfig_delete_environment","name":"AppConfig Delete Environment","description":"Delete an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_hosted_configuration_version":{"id":"appconfig_delete_hosted_configuration_version","name":"AppConfig Delete Hosted Configuration Version","description":"Delete a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID that owns the version"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to delete"}},"hostedApiKey":"none"},"appconfig_get_application":{"id":"appconfig_get_application","name":"AppConfig Get Application","description":"Get details about a single AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_configuration":{"id":"appconfig_get_configuration","name":"AppConfig Get Configuration","description":"Retrieve the latest deployed configuration for an AppConfig application, environment, and profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID or name to retrieve configuration for"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID or name to retrieve configuration for"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID or name to retrieve"}},"hostedApiKey":"none"},"appconfig_get_configuration_profile":{"id":"appconfig_get_configuration_profile","name":"AppConfig Get Configuration Profile","description":"Get details about a single AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_deployment":{"id":"appconfig_get_deployment","name":"AppConfig Get Deployment","description":"Get details about a specific AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment"}},"hostedApiKey":"none"},"appconfig_get_environment":{"id":"appconfig_get_environment","name":"AppConfig Get Environment","description":"Get details about a single AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_hosted_configuration_version":{"id":"appconfig_get_hosted_configuration_version","name":"AppConfig Get Hosted Configuration Version","description":"Retrieve a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to read the version from"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to retrieve"}},"hostedApiKey":"none"},"appconfig_list_applications":{"id":"appconfig_list_applications","name":"AppConfig List Applications","description":"List applications in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of applications to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_configuration_profiles":{"id":"appconfig_list_configuration_profiles","name":"AppConfig List Configuration Profiles","description":"List configuration profiles for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profiles"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of configuration profiles to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_deployment_strategies":{"id":"appconfig_list_deployment_strategies","name":"AppConfig List Deployment Strategies","description":"List deployment strategies available in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployment strategies to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_deployments":{"id":"appconfig_list_deployments","name":"AppConfig List Deployments","description":"List deployments for an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployments"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_environments":{"id":"appconfig_list_environments","name":"AppConfig List Environments","description":"List environments for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of environments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_hosted_configuration_versions":{"id":"appconfig_list_hosted_configuration_versions","name":"AppConfig List Hosted Configuration Versions","description":"List hosted configuration versions for an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to list versions for"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of versions to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_start_deployment":{"id":"appconfig_start_deployment","name":"AppConfig Start Deployment","description":"Start deploying a configuration version to an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to deploy in"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to deploy to"},"deploymentStrategyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The deployment strategy ID to use"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to deploy"},"configurationVersion":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration version to deploy"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the deployment"}},"hostedApiKey":"none"},"appconfig_stop_deployment":{"id":"appconfig_stop_deployment","name":"AppConfig Stop Deployment","description":"Stop an in-progress AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment to stop"}},"hostedApiKey":"none"},"appconfig_update_application":{"id":"appconfig_update_application","name":"AppConfig Update Application","description":"Update the name or description of an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the application"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the application"}},"hostedApiKey":"none"},"appconfig_update_configuration_profile":{"id":"appconfig_update_configuration_profile","name":"AppConfig Update Configuration Profile","description":"Update the name, description, or retrieval role of an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the configuration profile"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"New ARN of the IAM role used to retrieve the configuration"}},"hostedApiKey":"none"},"appconfig_update_environment":{"id":"appconfig_update_environment","name":"AppConfig Update Environment","description":"Update the name or description of an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the environment"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the environment"}},"hostedApiKey":"none"},"arxiv_get_author_papers":{"id":"arxiv_get_author_papers","name":"ArXiv Get Author Papers","description":"Search for papers by a specific author on ArXiv.","version":"1.0.0","params":{"authorName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Author name to search for"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"}},"hostedApiKey":"none"},"arxiv_get_paper":{"id":"arxiv_get_paper","name":"ArXiv Get Paper","description":"Get detailed information about a specific ArXiv paper by its ID.","version":"1.0.0","params":{"paperId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ArXiv paper ID (e.g., \\"1706.03762\\")"}},"hostedApiKey":"none"},"arxiv_search":{"id":"arxiv_search","name":"ArXiv Search","description":"Search for academic papers on ArXiv by keywords, authors, titles, or other fields.","version":"1.0.0","params":{"searchQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query to execute"},"searchField":{"type":"string","required":false,"visibility":"user-only","description":"Field to search in: all, ti (title), au (author), abs (abstract), co (comment), jr (journal), cat (category), rn (report number)"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"},"sortBy":{"type":"string","required":false,"visibility":"user-only","description":"Sort by: relevance, lastUpdatedDate, submittedDate (default: relevance)"},"sortOrder":{"type":"string","required":false,"visibility":"user-only","description":"Sort order: ascending, descending (default: descending)"}},"hostedApiKey":"none"},"asana_add_comment":{"id":"asana_add_comment","name":"Asana Add Comment","description":"Add a comment (story) to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string)"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The text content of the comment"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_add_followers":{"id":"asana_add_followers","name":"Asana Add Followers","description":"Add one or more followers to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task (numeric string)"},"followers":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of user GIDs to add as followers to the task"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_project":{"id":"asana_create_project","name":"Asana Create Project","description":"Create a new project in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the project will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the project"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the project"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_section":{"id":"asana_create_section","name":"Asana Create Section","description":"Create a new section in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to add the section to"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the section"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_subtask":{"id":"asana_create_subtask","name":"Asana Create Subtask","description":"Create a subtask under an existing Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the parent Asana task (numeric string)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the subtask"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the subtask"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the subtask to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_task":{"id":"asana_create_task","name":"Asana Create Task","description":"Create a new task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the task will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the task to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_delete_task":{"id":"asana_delete_task","name":"Asana Delete Task","description":"Delete an Asana task by its GID (moves it to the trash)","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task to delete (numeric string)"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_project":{"id":"asana_get_project","name":"Asana Get Project","description":"Retrieve a single Asana project by its GID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to retrieve"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_projects":{"id":"asana_get_projects","name":"Asana Get Projects","description":"Retrieve all projects from an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to retrieve projects from"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_task":{"id":"asana_get_task","name":"Asana Get Task","description":"Retrieve a single task by GID or get multiple tasks with filters","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":false,"visibility":"user-or-llm","description":"The globally unique identifier (GID) of the task. If not provided, will get multiple tasks."},"workspace":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to filter tasks (required when not using taskGid)"},"project":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to filter tasks"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of tasks to return (default: 50)"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_list_sections":{"id":"asana_list_sections","name":"Asana List Sections","description":"List all sections in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to list sections from"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_list_workspaces":{"id":"asana_list_workspaces","name":"Asana List Workspaces","description":"List all Asana workspaces and organizations the authenticated user belongs to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_search_tasks":{"id":"asana_search_tasks","name":"Asana Search Tasks","description":"Search for tasks in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to search tasks in"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Text to search for in task names"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter tasks by assignee user GID"},"projects":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of Asana project GIDs (numeric strings) to filter tasks by"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Filter by completion status"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_update_task":{"id":"asana_update_task","name":"Asana Update Task","description":"Update an existing task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string) of the task to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated name for the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated assignee user GID"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Mark task as completed or not completed"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"ashby_add_candidate_tag":{"id":"ashby_add_candidate_tag","name":"Ashby Add Candidate Tag","description":"Adds a tag to a candidate in Ashby and returns the updated candidate.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the tag to"},"tagId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the tag to add"}},"hostedApiKey":"none"},"ashby_anonymize_candidate":{"id":"ashby_anonymize_candidate","name":"Ashby Anonymize Candidate","description":"Strips personally identifiable information from a candidate in Ashby. This does not delete the candidate - the record and its applications remain, with the PII removed. Ashby exposes no candidate deletion endpoint; true deletion is UI-only, restricted by role, and limited to a 10-day window. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the candidate to anonymize"}},"hostedApiKey":"none"},"ashby_change_application_source":{"id":"ashby_change_application_source","name":"Ashby Change Application Source","description":"Changes the source attributed to an existing application, so programmatically created applications report correctly on the recruiting side. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the application whose source should change"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the application to, as returned by List Sources. Omit only when unsetSource is true."},"unsetSource":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Set true to deliberately clear the application source. Required to unset, so that a missing or empty sourceId cannot wipe attribution by accident."}},"hostedApiKey":"none"},"ashby_change_application_stage":{"id":"ashby_change_application_stage","name":"Ashby Change Application Stage","description":"Moves an application to a different interview stage. Requires an archive reason when moving to an Archived stage.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the application to update the stage of"},"interviewStageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the interview stage to move the application to"},"archiveReasonId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Archive reason UUID. Required when moving to an Archived stage, ignored otherwise"},"archiveEmail":{"type":"json","required":false,"visibility":"user-or-llm","description":"Archive email configuration with communicationTemplateId and optional sendAt ISO 8601 timestamp. Pass null or omit to send no archive email."}},"hostedApiKey":"none"},"ashby_create_application":{"id":"ashby_create_application","name":"Ashby Create Application","description":"Creates a new application for a candidate on a job. Optionally specify interview plan, stage, source, and credited user.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to consider for the job"},"jobId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the job to consider the candidate for"},"interviewPlanId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview plan to use (defaults to the job default plan)"},"interviewStageId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview stage to place the application in, or FirstPreInterviewScreen (defaults to the first Lead stage)"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to set on the application"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the user the application is credited to"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to set as the application creation date (defaults to now)"},"applicationHistory":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional documented application history entries to create with the application"}},"hostedApiKey":"none"},"ashby_create_candidate":{"id":"ashby_create_candidate","name":"Ashby Create Candidate","description":"Creates a new candidate record in Ashby.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"The candidate full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary email address for the candidate"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the candidate"},"linkedInUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"githubUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"GitHub profile URL"},"website":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal website URL"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the candidate to"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the Ashby user to credit with sourcing this candidate"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdated creation timestamp in ISO 8601 (e.g. 2024-01-01T00:00:00Z). Defaults to now."},"alternateEmailAddresses":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of additional email address strings to add to the candidate, e.g. [\\"a@x.com\\",\\"b@y.com\\"]"},"location":{"type":"json","required":false,"visibility":"user-or-llm","description":"Candidate location object with optional city, region, and country"}},"hostedApiKey":"none"},"ashby_create_note":{"id":"ashby_create_note","name":"Ashby Create Note","description":"Creates a note on a candidate in Ashby. Supports plain text and HTML content (bold, italic, underline, links, lists, code).","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the note to"},"note":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note content. If noteType is text/html, supports: , , , ,