Compare commits

...
Author SHA1 Message Date
Félix MalfaitandClaude Opus 4.7 2e64a73844 fix(ci): regenerate SDK metadata client
Adding imports to `InstanceCommandProviderModule` shifted GraphQL
code-first emission order — pure reorder, no semantic schema change
(267 insertions, 267 deletions, all reshuffling).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:00:22 +02:00
Félix MalfaitandClaude Opus 4.7 f342d85317 feat(migration): seed Exa pre-install from EXA_API_KEY env var
One-shot bridge for instances that set `EXA_API_KEY` as an env var
under the old native `web-search` driver (removed in the Exa migration
PR). Runs at deploy time:

1. Skips if `EXA_API_KEY` is unset.
2. Fetches the `twenty-exa` manifest from the app registry CDN and
   upserts the `ApplicationRegistration` (via the existing catalog-sync
   code path — no new registration plumbing).
3. Seeds `EXA_API_KEY` onto the registration's server variable,
   encrypted via `SecretEncryptionService`. Never overwrites a value
   already edited through the admin UI.
4. Flips `isPreInstalled=true` so new workspaces auto-install. Existing
   workspaces are backfilled with `install-pre-installed-apps`.

Idempotent — each step no-ops when its target already exists. Safe to
rerun. After a successful deploy, the env var can be dropped from infra
since the key now lives on the registration row.

The slow instance command hook gives us full DI, so this reuses
`MarketplaceService`, `ApplicationRegistrationService`, and
`SecretEncryptionService` rather than hand-rolling SQL + encryption.
`InstanceCommandProviderModule` now imports the modules those services
live in.

Prerequisite: `twenty-sdk@2.1.0` and `twenty-exa@0.1.0` must be
published on npm before this migration runs — otherwise the CDN fetch
returns no manifest and the command logs a warning and exits clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:49:17 +02:00
Félix MalfaitandClaude Opus 4.7 c643f3869f feat(sdk): promote chargeCredits to twenty-sdk/billing
Exposes the generic app-billing helper as a first-class SDK subpath so
any Twenty app can charge credits without copy-pasting the fetch/retry
boilerplate.

twenty-sdk changes:
- New subpath: `twenty-sdk/billing` exporting `chargeCredits` and
  `ChargeCreditsParams`. Source: `src/sdk/billing/`.
- New `vite.config.billing.ts` (mirrors `vite.config.define.ts`).
- Added billing entry to `rollup.config.sdk-dts.mjs` + build/dev targets
  in `project.json` + `"./billing"` export in `package.json`.
- Bumped `twenty-sdk` version to `2.1.0`.

Exa app changes:
- Depend on `twenty-sdk@2.1.0` and import `chargeCredits` from
  `twenty-sdk/billing`.
- Deleted `src/utils/charge-credits.ts` (no longer needed).
- Bumped `exa-js` to `^2.12.1` — the 2.x release ships accurate types
  for `category` (includes `people`, drops the stale `github`/`tweet`/
  `linkedin profile`), so the earlier type cast goes away and our
  `EXA_CATEGORIES` list aligns with the published Exa API.
- Switched `tsconfig.moduleResolution` from `node` to `bundler` so
  TypeScript honors `twenty-sdk`'s exports map (otherwise
  `twenty-sdk/billing` can't resolve against the published package).

Publish workflow: publish `twenty-sdk@2.1.0` first, then republish
`twenty-exa` (its lockfile will then resolve the new subpath).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:31:29 +02:00
Félix MalfaitandClaude Opus 4.7 a8aa48dce0 feat(exa-app): rename to twenty-exa, add logo, make buildable
- **Package name**: `exa` is taken on npm by an unrelated package.
  Renamed to `twenty-exa` — matches the `twenty-sdk` / `twenty-shared`
  / `twenty-client-sdk` publish convention. Discovery still works via
  the `twenty-app` keyword (marketplace cron queries
  `npm search text=keywords:twenty-app`, not by name).
- **Bump SDK deps** to `2.0.0` — the `0.9.0` release on npm doesn't
  export `twenty-sdk/define`, so the build couldn't resolve the
  imports.
- **Align with exa-js 1.10 API**: `exa.search(...)` moved content
  options to a separate `exa.searchAndContents(...)` method. Updated
  the handler. Also aligned `EXA_CATEGORIES` with what exa-js types
  accept (dropped `people`, added `github` / `tweet` /
  `linkedin profile`).
- **Add brand assets**: Exa logomark + full logo SVGs from Exa's
  official brand kit, wired via `logoUrl: 'public/exa-logomark.svg'`.
- **Add scaffolding**: `.yarnrc.yml`, `.nvmrc`, `.gitignore`, empty
  `yarn.lock` — makes the package installable/buildable as a
  standalone project (matching `examples/postcard`).

Local `yarn twenty build` + `npm publish --dry-run` both succeed.
Ready to publish with `npm publish` from `.twenty/output/` once the
maintainer runs `npm login`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:06:29 +02:00
Félix MalfaitandClaude Opus 4.7 4d7e874a70 refactor(exa-app): address PR review feedback
- **Move to `internal/`**: The Exa app is Twenty-built, not a community
  contribution, so it belongs next to `call-recording`, `self-hosting`,
  `twenty-for-twenty` under `packages/twenty-apps/internal/`.
- **Drop `@twenty-apps/` scope**: Every other internal + example app
  uses an unscoped package name (`self-hosting`, `hello-world`,
  `postcard`). Renamed to `exa`.
- **Add `defaultRoleUniversalIdentifier`**: `defineApplication` validates
  it, so the previous config would have failed at manifest parse time.
  Introduces a minimal no-op role (Exa only reads the `EXA_API_KEY`
  server variable — no workspace data).
- **Extract `chargeCredits` helper** (`src/utils/charge-credits.ts`):
  generic billing call, no more per-app fetch boilerplate.
- **Split schema file** (one export per file, per Twenty convention):
  - `logic-functions/constants/default-num-results.constant.ts`
  - `logic-functions/constants/exa-categories.constant.ts`
  - `logic-functions/schemas/exa-web-search-input.schema.ts`
  - `logic-functions/types/exa-web-search-input.type.ts`
- **Extend `InputJsonSchema`** (twenty-shared): add `minimum` / `maximum`
  so the tool schema can type-check `numResults` bounds properly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 28b33d897a fix(exa-app): drop unit from billing charge payload
`ChargeDto` no longer accepts `unit`; the server derives it from
`operationType`. With `forbidNonWhitelisted: true` on the validation
pipe, sending it now returns 400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 71dc6f2e67 fix(exa-app): bound Exa search + billing-charge fetch
Security-review follow-up: add inner timeouts so a slow upstream Exa
API or app-billing endpoint can't consume the full 30s runtime budget.

- `AbortSignal.timeout(5_000)` on the fire-and-forget billing charge
  fetch.
- Promise.race with a 25s inner deadline on `exa.search` (exa-js has
  no built-in abort/timeout). Leaves 5s for the billing charge + a
  graceful return before the outer runtime kill switch fires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 651166be36 refactor(exa-app): drop EXA_API_KEY env var after flag-based install
Removes the last coupling of Exa to deployment-time config after the
infrastructure PR moved pre-installation to a registration flag:

- Drops the `EXA_API_KEY` env var. Exa is now registered as an
  `ApplicationRegistration` and its API key lives on the registration's
  `ApplicationRegistrationVariable` row, same as any other app.
- Updates the Exa app manifest + README to describe the admin flow
  (register app, flip `autoInstallOnNewWorkspaces`, set EXA_API_KEY
  server variable) instead of env-var seeding.
- Updates the AI chat preload TODO comment to reflect the flag-based
  install model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 c4c2898db9 refactor(exa-app): use shared constants + parser from twenty-shared
Review cleanup:

- Exa handler now imports DEFAULT_API_URL_NAME and DEFAULT_APP_ACCESS_TOKEN_NAME
  from twenty-shared/application instead of hardcoding the env var names
  as string literals. Keeps the app in sync with whatever the execution
  env provides.

- EXA_API_KEY's @ValidateIf callback now uses parsePreInstalledApps
  (extracted in the prior commit on this stack). One parser, one
  invariant, no drift between the startup validator and the runtime
  PreInstalledAppsService.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 f5e0bd8eac fix(config): enforce EXA_API_KEY presence via @IsString
@ValidateIf alone gates other validators but isn't itself a validator,
so without @IsString() the conditional was a no-op — admins listing
@twenty-apps/exa in PRE_INSTALLED_APPS without setting EXA_API_KEY
would still pass startup validation.

Add @IsString() alongside the existing @ValidateIf so the conditional
branch actually enforces presence (and type). Import IsString from
class-validator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 6794cf34aa fix(app): address remote review on Exa migration PR
- Add `.oxlintrc.json` to the Exa app package; its `package.json`
  lint scripts reference the file, matching the convention in
  packages/twenty-apps/examples/postcard.

- Reinstate conditional validation for EXA_API_KEY. Before the
  migration it was required only when WEB_SEARCH_DRIVER=EXA; the
  migration made it unconditionally optional, so an admin listing
  @twenty-apps/exa in PRE_INSTALLED_APPS without setting
  EXA_API_KEY would only fail at tool-invocation time. Now validated
  when PRE_INSTALLED_APPS contains @twenty-apps/exa.

- Check response.ok after the billing fetch in the Exa handler.
  fetch() only rejects on network errors; a 4xx/5xx from
  /app/billing/charge would otherwise be silently swallowed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 f76cd04f7f feat(app): migrate Exa web search to a pre-installed app
Exa moves from a hardcoded action tool to a standalone app at
`packages/twenty-apps/community/exa`, installable via the
PRE_INSTALLED_APPS infrastructure landed in the prior PR. The tool
surfaces to the model as `app_exa_web_search` (logic-function-sourced,
hence the `app_` prefix).

New app package:

- `application.config.ts` declares the Exa app with one server variable
  (EXA_API_KEY, required, secret). Server admins set EXA_API_KEY as an
  env var; PreInstalledAppsService seeds it into the app registration
  at bootstrap.
- `logic-functions/exa-web-search.ts` is the runtime handler. Reads
  EXA_API_KEY from its injected execution env, calls Exa via the
  official SDK, records usage by POSTing to the generic
  /app/billing/charge endpoint (with the injected
  TWENTY_APP_ACCESS_TOKEN), returns the structured results.
- Tool input schema mirrors the previous WebSearchTool: query,
  optional category, optional numResults (1-30).

Removed (now provided by the app):

- packages/twenty-server/src/engine/core-modules/web-search/ —
  the entire module, drivers, types, and interface
- packages/twenty-server/src/engine/core-modules/tool/tools/
  web-search-tool/ — WebSearchTool, its schema, and input type
- WebSearchTool injection + toolMap entry + descriptor in
  ActionToolProvider
- WebSearchService injection from ActionToolProvider
- WebSearchModule from CoreEngineModule imports
- WEB_SEARCH_DRIVER config variable (no longer needed — Exa is an app)
- Custom driver-toggle wiring in chat preload: replaced with plain
  `app_exa_web_search` preload

EXA_API_KEY config variable stays but its description now says it
seeds the Exa app's server variables. Chat and the frontend display
already use the new `app_exa_web_search` name.

Deployment coordination: server admins must publish @twenty-apps/exa
to the app registry and set PRE_INSTALLED_APPS=@twenty-apps/exa (plus
EXA_API_KEY) post-merge. Existing workspaces backfill via the
`install-pre-installed-apps` CLI command added in the prior PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:40:24 +02:00
Félix MalfaitandClaude Opus 4.7 ea2cfe83c5 refactor: address PR review feedback
- Drop `unit` from `ChargeDto`: every `UsageOperationType` has one
  canonical unit (mirrors how `ai-billing.service.ts` emits native
  events). The service now derives `unit` from `operationType` via a
  typed map; apps no longer have to repeat the same pairing.
- Replace `void this.preInstalledAppsService.installOnWorkspace(...)` in
  workspace activation with sequential `try { await ... } catch`,
  matching the `prefillWorkflowCommandMenuItems` block right above.
  `void this.*` was unique to this spot in the whole server codebase.
- Trim block comments on the billing controller, service, DTO, entity,
  pre-installed-apps service + backfill command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:39:48 +02:00
Félix MalfaitandClaude Opus 4.7 5f1beeb0c4 feat(admin-apps): filter registrations by isPreInstalled
Adds a `Pre-installed only` toggle on the right of the Admin Panel
→ Apps search bar via the existing `SearchInput.filterDropdown` slot
(same pattern as the marketplace `Available` tab). Reuses
`Dropdown` + `DropdownContent` + `MenuItemToggle` — no new UI
components.

The fragment picks up `isPreInstalled` so both admin and non-admin
application-registration queries surface the flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:39:48 +02:00
Félix MalfaitandClaude Opus 4.7 84954b70b0 fix(app-billing): security hardening on /app/billing/charge
Addresses findings from the security review of the app-billing surface
added in the pre-installed-apps refactor:

- **Per-charge amount caps**: `ChargeDto` now enforces
  `creditsUsedMicro <= 1_000_000_000` (= \$1000) and `quantity <= 10_000`.
  An exfiltrated application-access token or a buggy app could
  otherwise submit `Number.MAX_SAFE_INTEGER` and corrupt accounting /
  drain the workspace's credits before downstream caps fire.

- **Per-(workspace, application) rate limit**: 1000 charges / 60s via
  the existing `ThrottlerService`. The logic-function executor already
  throttles *executions*, but application-access tokens are plain JWTs
  usable outside the runtime — this is the belt-and-suspenders layer.

- **IS_BILLING_ENABLED gate**: return 404 when billing is disabled so
  Community-instance apps fail fast instead of silently discarding
  charges that no listener consumes.

- **Enterprise license headers**: added `/* @license Enterprise */` to
  app-billing controller / service / module / DTO. The billing pipeline
  it feeds is Enterprise-licensed; keep the upstream aligned.

- Switched the charge response to 204 No Content — the POST has no
  meaningful body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:39:48 +02:00
Félix MalfaitandClaude Opus 4.7 cb838ea687 refactor(pre-installed-apps): drive auto-install via registration flag
Moves "pre-installed apps" from an env-var + npm-resolve model to a flag
on ApplicationRegistration. Admin registers an app once (via admin UI /
API), flips `autoInstallOnNewWorkspaces`, and the new-workspace hook +
backfill command install it everywhere. This mirrors how AppExchange,
GitHub Apps, and similar ecosystems distribute apps — registration is
the install intent, not a deployment-time config.

Removed:
- `PRE_INSTALLED_APPS` env var + `parsePreInstalledApps` util
- `PreInstalledAppsService.ensureRegistrationsExist` (CDN fetch,
  manifest resolution, upsert-from-catalog at boot)
- `seedServerVariablesFromEnv` (server variables are managed per-
  registration, same as any other app)
- `MarketplaceService.fetchLatestVersionFromRegistry` (dead code with
  env-var path gone; addresses review feedback to fix the root cause
  rather than branching the registry-fetch flow)

Added:
- `autoInstallOnNewWorkspaces` column on ApplicationRegistration
  (fast instance migration, default false)

Also:
- Moves `app-billing/` from `application/` to `billing/` (per review
  feedback — it's a billing submodule, not an application submodule)
- Drops obvious comments on the billing controller
- Documents why `unit` and `operationType` on ChargeDto are not
  redundant (per review question)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:39:48 +02:00
Félix MalfaitandClaude Opus 4.7 633fdfa475 fix(ci): regenerate SDK metadata client
Module-ordering tweaks in core-engine.module.ts shifted the GraphQL
code-first emission order, producing a pure-reordering diff in the
SDK's generated schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:11:03 +02:00
Félix MalfaitandClaude Opus 4.7 e57634f26c fix(ci): address server-test + server-validation failures
- workspace.service.spec.ts now mocks PreInstalledAppsService. The
  service was added to WorkspaceService as a dependency in this PR but
  the unit-test module wasn't updated, causing NestJS DI resolution to
  fail ("argument PreInstalledAppsService at index [15]").

- Move PreInstalledAppsModule and AppBillingModule to the end of
  CoreEngineModule's imports list. NestJS walks modules in
  declaration order for GraphQL code-first schema emission; inserting
  these two mid-list shifted MarketplaceModule's resolvers forward,
  producing a spurious reorder in the generated client-sdk schema.
  Appending the new modules keeps the emission order of existing
  modules stable, so no regeneration is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:37:54 +02:00
Félix MalfaitandClaude Opus 4.7 adb68668fb refactor(pre-installed-apps): extract parsePreInstalledApps util
The `@ValidateIf` callback on EXA_API_KEY in config-variables.ts (added
in the stacked PR) reimplements the same comma-split-trim-filter logic
as PreInstalledAppsService.getPreInstalledPackageNames. Extract the
pure parser once so the startup-validator and the runtime service can
share the invariant (whitespace, empty handling) — if the parsing rule
ever changes, both sites update together.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:32:49 +02:00
Félix MalfaitandClaude Opus 4.7 3931281b6f fix(app): use Twenty's guard stack for AppBillingController
The repo's oxlint rule `rest-api-methods-should-be-guarded` only
recognizes a specific set of auth-guard class names (UserAuthGuard,
WorkspaceAuthGuard, PublicEndpointGuard, FilePathGuard, FileByIdGuard) —
passport's `AuthGuard('jwt')` is a CallExpression, not an Identifier,
and the rule matches by identifier name.

Switch to the idiomatic Twenty REST-controller stack:

  @UseGuards(JwtAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)

JwtAuthGuard validates the bearer token (including APPLICATION_ACCESS
tokens — see jwt-auth.guard.ts line 38) and binds `application`,
`workspace`, and optional user fields onto the request object.
WorkspaceAuthGuard asserts the workspace was populated; NoPermissionGuard
marks this as not gated by role permissions (the app token itself is
the authorization).

AppBillingModule now imports AuthModule and WorkspaceCacheStorageModule
so JwtAuthGuard's dependencies are resolvable. Reading `application`/
`workspace` from `request` directly (not `request.user`) since
JwtAuthGuard's bindDataToRequestObject populates the request root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:09:34 +02:00
Félix MalfaitandClaude Opus 4.7 e49e827bf9 fix(app): remote-review follow-ups on pre-installed apps infra
Two findings from remote code review:

- AppBillingController was missing a ValidationPipe, so the ChargeDto
  class-validator decorators (@IsInt/@IsEnum/@IsString/@Min) were
  silently no-ops at runtime. Any caller with a valid APPLICATION_ACCESS
  token could submit malformed payloads (negative credits, wrong-type
  fields, unknown enum values) that flowed unchanged into USAGE_RECORDED
  events and polluted downstream billing/cap-enforcement/analytics.
  Added `@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))`
  on the charge method, mirroring the OAuth REST controllers in this
  feature area.

- PreInstalledAppsService resolved package versions via
  MarketplaceService.fetchAppsFromRegistry(), which hits the npm search
  endpoint with a hard-coded size=250 and no pagination. A pre-installed
  package ranked outside that window would be silently dropped — fine
  today while the twenty-app ecosystem is small, but wrong for exact-name
  resolution. Added `fetchLatestVersionFromRegistry(packageName)` that
  fetches the package document directly (`GET {registry}/{name}`) and
  reads `dist-tags.latest`. PreInstalledAppsService now uses it instead
  of the bounded search.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:56:16 +02:00
Félix MalfaitandClaude Opus 4.7 659137fe3e fix(app): move NoPermissionGuard to method level on app billing endpoint
The controller-guard lint rule checks guards per-method, not per-class.
Moving `@UseGuards(NoPermissionGuard)` onto the `charge` method matches
the pattern used by other REST controllers (e.g. PageLayoutWidgetController)
and unblocks server-lint-typecheck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:43:36 +02:00
Félix MalfaitandClaude Opus 4.7 aa0ec4a636 fix(app): add NoPermissionGuard to AppBillingController for lint rule
The repo's controller-guard lint rule requires every REST controller to
declare both an auth guard and a permission guard. AppBillingController
had AuthGuard('jwt') but no permission guard — the endpoint doesn't map
to workspace permissions (the app token is the authorization), so
NoPermissionGuard is the correct pairing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:38:51 +02:00
Félix MalfaitandClaude Opus 4.7 3131ddf439 refactor(app): address PR review findings
Five findings from code review:

- UsageResourceType.APP (not AI) for app billing events. App-emitted
  charges are misclassified as AI usage otherwise.

- Always decrypt ApplicationRegistrationVariable.encryptedValue in the
  logic function executor. The storage contract (set by
  ApplicationRegistrationVariableService.createVariable and .updateVariable)
  always encrypts regardless of isSecret — isSecret is display metadata
  only. The previous conditional decryption would inject ciphertext as
  env vars for non-secret values.

- Symmetric fix in PreInstalledAppsService.seedServerVariablesFromEnv:
  always encrypt on write, matching the storage contract.

- Retry registration resolution in installOnWorkspace when some
  pre-installed packages have no registration row. If bootstrap failed
  transiently (CDN outage, cold-start race), the first new workspace
  used to silently skip those apps until an admin ran the backfill
  command.

- Return 403 ForbiddenException from /app/billing/charge when the JWT
  is valid but not an APPLICATION_ACCESS context. 400 misrepresents an
  auth problem as a malformed body.

- Add @IsString() to ChargeDto.resourceContext so class-validator
  enforces the declared type on provided values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:34:57 +02:00
Félix MalfaitandClaude Opus 4.7 4a598bce7c refactor(app): address simplify review findings
- Parallelize per-app installs in PreInstalledAppsService.installOnWorkspace
  via Promise.allSettled. ApplicationInstallService acquires a per-app
  cache lock internally, so installs are safe to run concurrently. 3x
  speedup for multi-app pre-install lists.

- Make the auto-install hook in WorkspaceService.prefillCreatedWorkspaceRecords
  fire-and-forget. Npm downloads + migrations could add several seconds
  of latency per app to workspace activation; users shouldn't wait for
  non-critical installs to complete before seeing a ready workspace.

- Optimize the server-variable query in LogicFunctionExecutorService:
  filter unfilled variables server-side with `encryptedValue: Not('')`
  so the hot path ships only populated rows. Added a comment pointing
  future reviewers at WorkspaceApplicationVariableMapCacheService as the
  pattern to adopt if this becomes a measurable bottleneck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:28:37 +02:00
Félix MalfaitandClaude Opus 4.7 fcd68aa6dd feat(app): infrastructure for pre-installed apps
Adds the plumbing for server admins to declare a list of npm app packages
that are auto-installed on every new workspace and backfillable onto
existing workspaces. The follow-up PR migrates Exa onto this path.

What this PR introduces:

- **Server-level variable injection into logic function execution.**
  LogicFunctionExecutorService now resolves env vars as:
  (registration-level ApplicationRegistrationVariable) overridden by
  (workspace-level ApplicationVariable). The manifest already declares
  serverVariables; this closes the loop so apps can read those values
  at runtime without storing them per-workspace.

- **PRE_INSTALLED_APPS config variable.** Comma-separated list of npm
  package names. Default empty — opt-in by server admin.

- **PreInstalledAppsService.** On application bootstrap, fetches each
  package's manifest from the app registry CDN, upserts an
  ApplicationRegistration, and seeds declared server variables from
  matching env vars (EXA_API_KEY env -> encrypted registration variable).
  Exposes installOnWorkspace(workspaceId) for hooks and backfill.

- **Auto-install on new workspace activation.** WorkspaceService
  invokes PreInstalledAppsService.installOnWorkspace in
  prefillCreatedWorkspaceRecords. Failures are non-blocking — the
  admin can backfill later.

- **install-pre-installed-apps CLI command.** Idempotent backfill that
  iterates active and suspended workspaces. Run after changing
  PRE_INSTALLED_APPS to roll the change out to existing tenants.

- **POST /app/billing/charge endpoint.** Authenticated via
  APPLICATION_ACCESS token (DEFAULT_APP_ACCESS_TOKEN already injected
  into logic function execution env). Emits USAGE_RECORDED workspace
  events with applicationId as resourceId. Generic — future apps
  (call recorder, etc.) use the same endpoint.

- **Tool name prefix change `logic_function_` -> `app_`.** Shorter,
  signals that these tools come from installed apps. Only affects
  tools sourced from LogicFunctionToolProvider; other providers
  unchanged.

No user-visible change yet — PRE_INSTALLED_APPS defaults to empty.
The follow-up PR ships the Exa app package, sets the default, and
removes the current WebSearchTool / WebSearchService / ExaDriver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:21:04 +02:00
73 changed files with 5961 additions and 915 deletions
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn
# codegen
generated
# testing
/coverage
# dev
/dist/
.twenty
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# typescript
*.tsbuildinfo
*.d.ts
+1
View File
@@ -0,0 +1 @@
24.5.0
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -0,0 +1 @@
nodeLinker: node-modules
@@ -0,0 +1,22 @@
# twenty-exa
Exposes [Exa](https://exa.ai) structured web search to Twenty AI agents
(chat + workflow agents + MCP) as the `app_exa_web_search` tool.
## Installation
1. Register the app on the Twenty server once (admin API / UI):
`twenty-exa` from npm.
2. Set `isPreInstalled=true` on the registration so it's installed on
every new workspace. Existing workspaces can be backfilled via the
`install-pre-installed-apps` CLI command.
3. Set the `EXA_API_KEY` server variable on the registration to your Exa
API key. The value is injected into every logic function execution —
no per-workspace configuration needed.
## Billing
The handler calls Twenty's generic app billing endpoint
(`POST /app/billing/charge`) using the application access token injected
into the execution env. Pricing mirrors Exa's auto-search tier: $0.007
base (10 results) + $0.001 per additional result.
@@ -0,0 +1,33 @@
{
"name": "twenty-exa",
"version": "0.1.0",
"description": "Structured web search powered by Exa, exposed to Twenty AI agents as a tool.",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"keywords": [
"twenty-app"
],
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json .",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"exa-js": "^2.12.1",
"twenty-client-sdk": "2.0.0",
"twenty-sdk": "2.1.0"
},
"devDependencies": {
"@types/node": "^24.7.2",
"oxlint": "^0.16.0",
"typescript": "^5.9.3",
"vitest": "^3.1.1"
}
}
@@ -0,0 +1,6 @@
<svg width="1386" height="480" viewBox="0 0 1386 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1247.78 71.5781C1289.16 71.5781 1321.17 82.4448 1343.82 104.178C1366.9 125.477 1378.44 157.425 1378.44 200.023V327.163C1378.44 358.182 1380.69 384.72 1385.18 406.779C1385.71 409.423 1383.71 411.924 1381.01 411.924H1337.41C1335.43 411.924 1333.71 410.55 1333.33 408.61C1330.74 395.478 1329.44 380.499 1329.44 363.675H1328.14C1304.18 400.622 1265.86 419.096 1213.16 419.096C1179.19 419.096 1151.96 410.185 1131.49 392.364C1111.02 374.108 1100.79 350.635 1100.79 321.947C1100.79 293.259 1110.15 270.656 1128.88 254.139C1148.04 237.187 1180.71 225.016 1226.88 217.627C1258.22 212.554 1289.36 208.92 1320.29 206.725C1321.77 206.621 1322.91 205.398 1322.91 203.924V193.503C1322.91 166.988 1316.16 147.428 1302.66 134.822C1289.59 122.217 1271.3 115.914 1247.78 115.914C1224.26 115.914 1205.97 122 1192.9 134.17C1180.73 145.109 1173.85 159.634 1172.26 177.746C1172.06 179.99 1170.22 181.767 1167.96 181.767H1118.95C1116.54 181.767 1114.61 179.739 1114.81 177.337C1117.41 146.757 1129.72 121.718 1151.75 102.222C1174.83 81.7928 1206.84 71.5781 1247.78 71.5781ZM1322.91 270.439V254.774C1322.91 252.322 1320.82 250.389 1318.37 250.588C1286.75 253.153 1259.3 256.51 1236.02 260.659C1208.15 265.006 1188.33 271.526 1176.57 280.219C1165.25 288.912 1159.59 301.518 1159.59 318.035C1159.59 335.422 1165.47 349.331 1177.23 359.763C1189.42 369.761 1206.19 374.76 1227.53 374.76C1256.71 374.76 1279.58 366.066 1296.13 348.679C1306.14 338.247 1313.11 327.598 1317.03 316.731C1320.95 305.43 1322.91 289.999 1322.91 270.439Z" fill="black"/>
<path d="M1069.28 80.7062C1072.7 80.7062 1074.69 84.56 1072.72 87.3456L964.411 239.795L1083.11 405.269C1085.11 408.052 1083.11 411.924 1079.68 411.924H1020.88C1019.48 411.924 1018.17 411.224 1017.38 410.059L929.786 279.567H928.479L840.882 410.059C840.1 411.224 838.786 411.924 837.38 411.924H779.234C775.804 411.924 773.81 408.052 775.807 405.269L894.507 239.795L786.203 87.3456C784.224 84.5601 786.22 80.7062 789.641 80.7062H847.17C848.581 80.7062 849.899 81.411 850.68 82.5838L928.479 199.371H929.786L1008.24 82.5705C1009.02 81.4053 1010.33 80.7062 1011.74 80.7062H1069.28Z" fill="black"/>
<path d="M602.456 376.716C625.975 376.716 645.356 370.848 660.6 359.112C675.353 348.069 684.901 333.754 689.242 316.169C689.721 314.229 691.436 312.819 693.438 312.819H743.669C746.324 312.819 748.321 315.243 747.711 317.822C740.936 346.486 725.4 370.681 701.105 390.408C675.844 410.837 642.961 421.052 602.456 421.052C554.111 421.052 516.002 404.969 488.128 372.804C460.254 340.203 446.316 296.519 446.316 241.751C446.316 191.764 460.036 150.905 487.474 119.174C515.349 87.4435 553.023 71.5781 600.496 71.5781C637.516 71.5781 668.657 81.7928 693.918 102.222C719.615 122.652 736.818 150.905 745.529 186.983C749.676 203.95 751.848 226.236 752.047 253.84C752.064 256.163 750.175 258.051 747.847 258.051H505.986C504.794 258.051 503.839 259.042 503.889 260.231C505.474 298.271 515.029 327.102 532.552 346.723C550.845 366.718 574.146 376.716 602.456 376.716ZM600.496 115.914C573.493 115.914 551.498 124.608 534.512 141.994C518.087 158.387 508.178 181.487 504.785 211.293C504.598 212.938 505.897 214.367 507.557 214.367H692.133C693.79 214.367 695.09 212.941 694.906 211.297C691.518 181.062 681.607 157.744 665.173 141.342C648.187 124.39 626.628 115.914 600.496 115.914Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 475.765C0 478.104 1.88926 480 4.21978 480H389.626C391.957 480 393.846 478.104 393.846 475.765V455.637C393.846 453.672 393.166 451.767 391.922 450.248L221.889 242.69C220.609 241.127 220.609 238.873 221.889 237.31L391.922 29.7517C393.166 28.2333 393.846 26.3285 393.846 24.3627V4.23529C393.846 1.89621 391.957 0 389.626 0H4.21978C1.88926 0 0 1.8962 0 4.23529V475.765ZM199.219 270.589C198.088 269.208 195.98 269.216 194.859 270.605L58.1475 440.106C56.6585 441.952 57.9676 444.706 60.334 444.706H335.902C338.278 444.706 339.584 441.932 338.075 440.089L199.219 270.589ZM38.9146 407.858C37.6636 409.409 35.1648 408.521 35.1648 406.525V260.471C35.1648 258.911 36.4244 257.647 37.978 257.647H154.171C156.538 257.647 157.847 260.401 156.358 262.247L38.9146 407.858ZM154.171 222.353C156.538 222.353 157.847 219.599 156.358 217.753L38.9146 72.1424C37.6636 70.5913 35.1648 71.4792 35.1648 73.4749V219.529C35.1648 221.089 36.4243 222.353 37.978 222.353H154.171ZM58.1475 39.8942C56.6585 38.0482 57.9676 35.2941 60.334 35.2941H335.902C338.278 35.2941 339.584 38.0685 338.075 39.9109L199.219 209.411C198.088 210.792 195.98 210.784 194.859 209.395L58.1475 39.8942Z" fill="#2043EC"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,5 @@
<svg width="395" height="480" viewBox="0 0 395 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 475.765C0 478.104 1.8948 480 4.23215 480H390.768C393.105 480 395 478.104 395 475.765V455.637C395 453.672 394.318 451.767 393.07 450.248L222.539 242.69C221.255 241.127 221.255 238.873 222.539 237.31L393.07 29.7517C394.318 28.2333 395 26.3285 395 24.3627V4.23529C395 1.89621 393.105 0 390.768 0H4.23214C1.89479 0 0 1.8962 0 4.23529V475.765ZM199.802 270.589C198.668 269.208 196.554 269.216 195.43 270.605L58.3178 440.106C56.8245 441.952 58.1374 444.706 60.5108 444.706H336.886C339.269 444.706 340.579 441.932 339.065 440.089L199.802 270.589ZM39.0286 407.858C37.7739 409.409 35.2679 408.521 35.2679 406.525V260.471C35.2679 258.911 36.5311 257.647 38.0893 257.647H154.623C156.996 257.647 158.309 260.401 156.816 262.247L39.0286 407.858ZM154.623 222.353C156.996 222.353 158.309 219.599 156.816 217.753L39.0286 72.1424C37.7739 70.5913 35.2679 71.4792 35.2679 73.4749V219.529C35.2679 221.089 36.5311 222.353 38.0893 222.353H154.623ZM58.3178 39.8942C56.8245 38.0482 58.1374 35.2941 60.5108 35.2941H336.886C339.269 35.2941 340.579 38.0685 339.065 39.9109L199.802 209.411C198.668 210.792 196.554 210.784 195.43 209.395L58.3178 39.8942Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 475.765C0 478.104 1.8948 480 4.23215 480H390.768C393.105 480 395 478.104 395 475.765V455.637C395 453.672 394.318 451.767 393.07 450.248L222.539 242.69C221.255 241.127 221.255 238.873 222.539 237.31L393.07 29.7517C394.318 28.2333 395 26.3285 395 24.3627V4.23529C395 1.89621 393.105 0 390.768 0H4.23214C1.89479 0 0 1.8962 0 4.23529V475.765ZM199.802 270.589C198.668 269.208 196.554 269.216 195.43 270.605L58.3178 440.106C56.8245 441.952 58.1374 444.706 60.5108 444.706H336.886C339.269 444.706 340.579 441.932 339.065 440.089L199.802 270.589ZM39.0286 407.858C37.7739 409.409 35.2679 408.521 35.2679 406.525V260.471C35.2679 258.911 36.5311 257.647 38.0893 257.647H154.623C156.996 257.647 158.309 260.401 156.816 262.247L39.0286 407.858ZM154.623 222.353C156.996 222.353 158.309 219.599 156.816 217.753L39.0286 72.1424C37.7739 70.5913 35.2679 71.4792 35.2679 73.4749V219.529C35.2679 221.089 36.5311 222.353 38.0893 222.353H154.623ZM58.3178 39.8942C56.8245 38.0482 58.1374 35.2941 60.5108 35.2941H336.886C339.269 35.2941 340.579 38.0685 339.065 39.9109L199.802 209.411C198.668 210.792 196.554 210.784 195.43 209.395L58.3178 39.8942Z" fill="#111827"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 475.765C0 478.104 1.8948 480 4.23215 480H390.768C393.105 480 395 478.104 395 475.765V455.637C395 453.672 394.318 451.767 393.07 450.248L222.539 242.69C221.255 241.127 221.255 238.873 222.539 237.31L393.07 29.7517C394.318 28.2333 395 26.3285 395 24.3627V4.23529C395 1.89621 393.105 0 390.768 0H4.23214C1.89479 0 0 1.8962 0 4.23529V475.765ZM199.802 270.589C198.668 269.208 196.554 269.216 195.43 270.605L58.3178 440.106C56.8245 441.952 58.1374 444.706 60.5108 444.706H336.886C339.269 444.706 340.579 441.932 339.065 440.089L199.802 270.589ZM39.0286 407.858C37.7739 409.409 35.2679 408.521 35.2679 406.525V260.471C35.2679 258.911 36.5311 257.647 38.0893 257.647H154.623C156.996 257.647 158.309 260.401 156.816 262.247L39.0286 407.858ZM154.623 222.353C156.996 222.353 158.309 219.599 156.816 217.753L39.0286 72.1424C37.7739 70.5913 35.2679 71.4792 35.2679 73.4749V219.529C35.2679 221.089 36.5311 222.353 38.0893 222.353H154.623ZM58.3178 39.8942C56.8245 38.0482 58.1374 35.2941 60.5108 35.2941H336.886C339.269 35.2941 340.579 38.0685 339.065 39.9109L199.802 209.411C198.668 210.792 196.554 210.784 195.43 209.395L58.3178 39.8942Z" fill="#1E40ED"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,24 @@
import { defineApplication } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './roles/default-function.role';
export const APPLICATION_UNIVERSAL_IDENTIFIER =
'2b7f4a2e-9c4b-4a11-b63c-2e5e7d3f5a9a';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Exa',
description:
'Structured web search powered by Exa. Surfaces entity-aware results (companies, people, research, news) to Twenty AI agents.',
icon: 'IconSearch',
logoUrl: 'public/exa-logomark.svg',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
serverVariables: {
EXA_API_KEY: {
description:
'Exa API key. Set by the server admin on this registration after installation; the value is injected into every logic function execution.',
isSecret: true,
isRequired: true,
},
},
});
@@ -0,0 +1 @@
export const DEFAULT_NUM_RESULTS = 10;
@@ -0,0 +1,11 @@
// Mirrors exa-js's `BaseSearchOptions['category']` union. Kept as a
// runtime list so the tool's JSON-Schema `enum` can reference it.
export const EXA_CATEGORIES = [
'company',
'research paper',
'news',
'pdf',
'personal site',
'financial report',
'people',
] as const;
@@ -0,0 +1,117 @@
import Exa from 'exa-js';
import { chargeCredits } from 'twenty-sdk/billing';
import { defineLogicFunction } from 'twenty-sdk/define';
import { DEFAULT_NUM_RESULTS } from './constants/default-num-results.constant';
import { exaWebSearchInputSchema } from './schemas/exa-web-search-input.schema';
import { type ExaWebSearchInput } from './types/exa-web-search-input.type';
// Number of sentences surfaced per result — keeps the snippet compact
// enough for an LLM to read many results without blowing the context.
const HIGHLIGHT_NUM_SENTENCES = 5;
// Inner bound — the runtime's `timeoutSeconds: 30` is the outer kill
// switch; this one ensures we return a clean error on a slow Exa response.
const EXA_SEARCH_TIMEOUT_MS = 25_000;
// Exa auto-search pricing (2025): $0.007 covers the first 10 results,
// $0.001 per additional result. Twenty charges in micro-credits where
// 1 USD = 1_000_000 micro-credits (DOLLAR_TO_CREDIT_MULTIPLIER).
const MICRO_CREDITS_PER_DOLLAR = 1_000_000;
const EXA_BASE_COST_DOLLARS = 0.007;
const EXA_COST_PER_ADDITIONAL_RESULT_DOLLARS = 0.001;
type ExaSearchResult = {
title: string;
url: string;
snippet: string;
};
type HandlerResult = {
success: boolean;
message: string;
result?: ExaSearchResult[];
error?: string;
};
const computeMicroCredits = (numResults: number): number => {
const additional = Math.max(0, numResults - DEFAULT_NUM_RESULTS);
const dollars =
EXA_BASE_COST_DOLLARS + additional * EXA_COST_PER_ADDITIONAL_RESULT_DOLLARS;
return Math.round(dollars * MICRO_CREDITS_PER_DOLLAR);
};
const handler = async (
parameters: ExaWebSearchInput,
): Promise<HandlerResult> => {
const apiKey = process.env.EXA_API_KEY;
if (!apiKey) {
return {
success: false,
message: 'Exa is not configured',
error:
'EXA_API_KEY is not set. The server admin must provide an Exa API key for this tool to work.',
};
}
const query = parameters.query;
const numResults = parameters.numResults ?? DEFAULT_NUM_RESULTS;
const category = parameters.category;
try {
const exa = new Exa(apiKey);
// exa-js has no built-in abort — race it manually.
const response = await Promise.race([
exa.searchAndContents(query, {
type: 'auto',
numResults,
category,
highlights: { numSentences: HIGHLIGHT_NUM_SENTENCES },
}),
new Promise<never>((_, reject) =>
setTimeout(
() => reject(new Error('Exa search timed out')),
EXA_SEARCH_TIMEOUT_MS,
),
),
]);
const results: ExaSearchResult[] = response.results.map((result) => ({
title: result.title ?? '',
url: result.url,
snippet: result.highlights?.join('\n') ?? '',
}));
await chargeCredits({
creditsUsedMicro: computeMicroCredits(results.length),
operationType: 'WEB_SEARCH',
resourceContext: 'exa',
});
return {
success: true,
message: `Found ${results.length} results for "${query}"${category ? ` (category: ${category})` : ''}`,
result: results,
};
} catch (error) {
return {
success: false,
message: `Web search failed for "${query}"`,
error: error instanceof Error ? error.message : 'Web search failed',
};
}
};
export default defineLogicFunction({
universalIdentifier: '4c6f9b2a-5d8e-4c2a-af18-3e0b9c6a7e4f',
name: 'exa_web_search',
description:
'Structured web search powered by Exa. Returns entity-aware results with category filtering (companies, people, research papers, news, and other content types). Prefer this when the query benefits from structured data or a specific category. For general real-time web browsing, prefer the native `web_search` tool when it is available.',
timeoutSeconds: 30,
isTool: true,
toolInputSchema: exaWebSearchInputSchema,
handler,
});
@@ -0,0 +1,31 @@
import { type InputJsonSchema } from 'twenty-shared/logic-function';
import { DEFAULT_NUM_RESULTS } from '../constants/default-num-results.constant';
import { EXA_CATEGORIES } from '../constants/exa-categories.constant';
const MAX_NUM_RESULTS = 30;
export const exaWebSearchInputSchema: InputJsonSchema = {
type: 'object',
properties: {
query: {
type: 'string',
description:
'The search query to look up on the web. Be specific and include relevant keywords for better results.',
},
category: {
type: 'string',
enum: [...EXA_CATEGORIES],
description:
'Optional content category to focus the search. Use "company" for business/organization info, "people" for person profiles, "news" for recent articles, "research paper" for academic content.',
},
numResults: {
type: 'integer',
minimum: 1,
maximum: MAX_NUM_RESULTS,
description: `Number of search results to return. Defaults to ${DEFAULT_NUM_RESULTS}, max ${MAX_NUM_RESULTS}. Use more results when you need comprehensive coverage.`,
},
},
required: ['query'],
additionalProperties: false,
};
@@ -0,0 +1,7 @@
import { type EXA_CATEGORIES } from '../constants/exa-categories.constant';
export type ExaWebSearchInput = {
query: string;
category?: (typeof EXA_CATEGORIES)[number];
numResults?: number;
};
@@ -0,0 +1,25 @@
import { defineRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'9a55f3d2-f87c-4f1b-a0f3-5d1c6b8a2e4c';
// Exa's logic function never reads workspace data — it only reads
// EXA_API_KEY and calls Exa's external API — so the role needs no object
// permissions. Kept explicit so the manifest records the "zero data
// access" posture.
export default defineRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Exa function role',
description: 'No-op role for the exa_web_search logic function',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlags: [],
});
@@ -0,0 +1,30 @@
{
"compileOnSave": false,
"compilerOptions": {
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"allowUnreachableCode": false,
"strict": true,
"alwaysStrict": true,
"noImplicitAny": true,
"strictBindCallApply": false,
"target": "es2020",
"module": "esnext",
"lib": ["es2020"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
"paths": {
"src/*": ["./src/*"],
"~/*": ["./*"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
File diff suppressed because it is too large Load Diff
@@ -47,6 +47,7 @@ type ApplicationRegistration {
latestAvailableVersion: String
isListed: Boolean!
isFeatured: Boolean!
isPreInstalled: Boolean!
logoUrl: String
createdAt: DateTime!
updatedAt: DateTime!
@@ -1908,6 +1909,30 @@ type DeletedWorkspaceMember {
userWorkspaceId: UUID
}
type MarketplaceApp {
id: String!
name: String!
description: String!
icon: String!
author: String!
category: String!
logo: String
sourcePackage: String
isFeatured: Boolean!
}
type MarketplaceAppDetail {
universalIdentifier: String!
id: String!
name: String!
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
isListed: Boolean!
isFeatured: Boolean!
manifest: JSON
}
type Relation {
type: RelationType!
sourceObjectMetadata: Object!
@@ -2197,30 +2222,6 @@ type File {
createdAt: DateTime!
}
type MarketplaceApp {
id: String!
name: String!
description: String!
icon: String!
author: String!
category: String!
logo: String
sourcePackage: String
isFeatured: Boolean!
}
type MarketplaceAppDetail {
universalIdentifier: String!
id: String!
name: String!
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
isListed: Boolean!
isFeatured: Boolean!
manifest: JSON
}
type PublicDomain {
id: UUID!
domain: String!
@@ -3008,10 +3009,6 @@ type Query {
sorting: [AgentChatThreadSort!]! = [{field: updatedAt, direction: DESC}]
): AgentChatThreadConnection!
agentTurns(agentId: UUID!): [AgentTurn!]!
eventLogs(input: EventLogQueryInput!): EventLogQueryResult!
pieChartData(input: PieChartDataInput!): PieChartData!
lineChartData(input: LineChartDataInput!): LineChartData!
barChartData(input: BarChartDataInput!): BarChartData!
checkUserExists(email: String!, captchaToken: String): CheckUserExist!
checkWorkspaceInviteHashIsValid(inviteHash: String!): WorkspaceInviteHashValid!
findWorkspaceFromInviteHash(inviteHash: String!): Workspace!
@@ -3027,7 +3024,15 @@ type Query {
currentWorkspace: Workspace!
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
getPublicWorkspaceDataById(id: UUID!): PublicWorkspaceDataSummary!
findManyMarketplaceApps: [MarketplaceApp!]!
findMarketplaceAppDetail(universalIdentifier: String!): MarketplaceAppDetail!
findManyApplications: [Application!]!
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
getSSOIdentityProviders: [FindAvailableSSOIDP!]!
eventLogs(input: EventLogQueryInput!): EventLogQueryResult!
pieChartData(input: PieChartDataInput!): PieChartData!
lineChartData(input: LineChartDataInput!): LineChartData!
barChartData(input: BarChartDataInput!): BarChartData!
getConnectedImapSmtpCaldavAccount(id: UUID!): ConnectedImapSmtpCaldavAccount!
getAutoCompleteAddress(address: String!, token: String!, country: String, isFieldCity: Boolean): [AutocompleteResult!]!
getAddressDetails(placeId: String!, token: String!): PlaceDetailsResult!
@@ -3035,10 +3040,6 @@ type Query {
getPostgresCredentials: PostgresCredentials
findManyPublicDomains: [PublicDomain!]!
getEmailingDomains: [EmailingDomain!]!
findManyMarketplaceApps: [MarketplaceApp!]!
findMarketplaceAppDetail(universalIdentifier: String!): MarketplaceAppDetail!
findManyApplications: [Application!]!
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
}
input GetApiKeyInput {
@@ -3297,7 +3298,6 @@ type Mutation {
deactivateSkill(id: UUID!): Skill!
evaluateAgentTurn(turnId: UUID!): AgentTurnEvaluation!
runEvaluationInput(agentId: UUID!, input: String!): AgentTurn!
duplicateDashboard(id: UUID!): DuplicatedDashboard!
getAuthorizationUrlForSSO(input: GetAuthorizationUrlForSSOInput!): GetAuthorizationUrlForSSO!
getLoginTokenFromCredentials(email: String!, password: String!, captchaToken: String, locale: String, verifyEmailRedirectPath: String, origin: String!): LoginToken!
signIn(email: String!, password: String!, captchaToken: String, locale: String, verifyEmailRedirectPath: String): AvailableWorkspacesAndAccessTokens!
@@ -3336,10 +3336,17 @@ type Mutation {
updateWorkspace(data: UpdateWorkspaceInput!): Workspace!
deleteCurrentWorkspace: Workspace!
checkCustomDomainValidRecords: DomainValidRecords
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
syncMarketplaceCatalog: Boolean!
installApplication(appRegistrationId: String!, version: String): Boolean!
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
uninstallApplication(universalIdentifier: String!): Boolean!
updateOneApplicationVariable(key: String!, value: String!, applicationId: UUID!): Boolean!
createOIDCIdentityProvider(input: SetupOIDCSsoInput!): SetupSso!
createSAMLIdentityProvider(input: SetupSAMLSsoInput!): SetupSso!
deleteSSOIdentityProvider(input: DeleteSsoInput!): DeleteSso!
editSSOIdentityProvider(input: EditSsoInput!): EditSso!
duplicateDashboard(id: UUID!): DuplicatedDashboard!
impersonate(userId: UUID!, workspaceId: UUID!): Impersonate!
sendEmail(input: SendEmailInput!): SendEmailOutput!
startChannelSync(connectedAccountId: UUID!): ChannelSyncSuccess!
@@ -3354,12 +3361,6 @@ type Mutation {
deleteEmailingDomain(id: String!): Boolean!
verifyEmailingDomain(id: String!): EmailingDomain!
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
syncMarketplaceCatalog: Boolean!
installApplication(appRegistrationId: String!, version: String): Boolean!
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
uninstallApplication(universalIdentifier: String!): Boolean!
updateOneApplicationVariable(key: String!, value: String!, applicationId: UUID!): Boolean!
createDevelopmentApplication(universalIdentifier: String!, name: String!): DevelopmentApplication!
generateApplicationToken(applicationId: UUID!): ApplicationTokenPair!
syncApplication(manifest: JSON!): WorkspaceMigration!
@@ -4285,6 +4286,22 @@ input UpdateWorkspaceInput {
useRecommendedModels: Boolean
}
input WorkspaceMigrationInput {
actions: [WorkspaceMigrationDeleteActionInput!]!
}
input WorkspaceMigrationDeleteActionInput {
type: WorkspaceMigrationActionType!
metadataName: AllMetadataName!
universalIdentifier: String!
}
enum WorkspaceMigrationActionType {
delete
create
update
}
input SetupOIDCSsoInput {
name: String!
issuer: String!
@@ -4354,22 +4371,6 @@ input CreateAppTokenInput {
expiresAt: DateTime!
}
input WorkspaceMigrationInput {
actions: [WorkspaceMigrationDeleteActionInput!]!
}
input WorkspaceMigrationDeleteActionInput {
type: WorkspaceMigrationActionType!
metadataName: AllMetadataName!
universalIdentifier: String!
}
enum WorkspaceMigrationActionType {
delete
create
update
}
enum FileFolder {
ProfilePicture
WorkspaceLogo
@@ -51,6 +51,7 @@ export interface ApplicationRegistration {
latestAvailableVersion?: Scalars['String']
isListed: Scalars['Boolean']
isFeatured: Scalars['Boolean']
isPreInstalled: Scalars['Boolean']
logoUrl?: Scalars['String']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
@@ -1621,6 +1622,32 @@ export interface DeletedWorkspaceMember {
__typename: 'DeletedWorkspaceMember'
}
export interface MarketplaceApp {
id: Scalars['String']
name: Scalars['String']
description: Scalars['String']
icon: Scalars['String']
author: Scalars['String']
category: Scalars['String']
logo?: Scalars['String']
sourcePackage?: Scalars['String']
isFeatured: Scalars['Boolean']
__typename: 'MarketplaceApp'
}
export interface MarketplaceAppDetail {
universalIdentifier: Scalars['String']
id: Scalars['String']
name: Scalars['String']
sourceType: ApplicationRegistrationSourceType
sourcePackage?: Scalars['String']
latestAvailableVersion?: Scalars['String']
isListed: Scalars['Boolean']
isFeatured: Scalars['Boolean']
manifest?: Scalars['JSON']
__typename: 'MarketplaceAppDetail'
}
export interface Relation {
type: RelationType
sourceObjectMetadata: Object
@@ -1934,32 +1961,6 @@ export interface File {
__typename: 'File'
}
export interface MarketplaceApp {
id: Scalars['String']
name: Scalars['String']
description: Scalars['String']
icon: Scalars['String']
author: Scalars['String']
category: Scalars['String']
logo?: Scalars['String']
sourcePackage?: Scalars['String']
isFeatured: Scalars['Boolean']
__typename: 'MarketplaceApp'
}
export interface MarketplaceAppDetail {
universalIdentifier: Scalars['String']
id: Scalars['String']
name: Scalars['String']
sourceType: ApplicationRegistrationSourceType
sourcePackage?: Scalars['String']
latestAvailableVersion?: Scalars['String']
isListed: Scalars['Boolean']
isFeatured: Scalars['Boolean']
manifest?: Scalars['JSON']
__typename: 'MarketplaceAppDetail'
}
export interface PublicDomain {
id: Scalars['UUID']
domain: Scalars['String']
@@ -2596,10 +2597,6 @@ export interface Query {
skill?: Skill
chatThreads: AgentChatThreadConnection
agentTurns: AgentTurn[]
eventLogs: EventLogQueryResult
pieChartData: PieChartData
lineChartData: LineChartData
barChartData: BarChartData
checkUserExists: CheckUserExist
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid
findWorkspaceFromInviteHash: Workspace
@@ -2615,7 +2612,15 @@ export interface Query {
currentWorkspace: Workspace
getPublicWorkspaceDataByDomain: PublicWorkspaceData
getPublicWorkspaceDataById: PublicWorkspaceDataSummary
findManyMarketplaceApps: MarketplaceApp[]
findMarketplaceAppDetail: MarketplaceAppDetail
findManyApplications: Application[]
findOneApplication: Application
getSSOIdentityProviders: FindAvailableSSOIDP[]
eventLogs: EventLogQueryResult
pieChartData: PieChartData
lineChartData: LineChartData
barChartData: BarChartData
getConnectedImapSmtpCaldavAccount: ConnectedImapSmtpCaldavAccount
getAutoCompleteAddress: AutocompleteResult[]
getAddressDetails: PlaceDetailsResult
@@ -2623,10 +2628,6 @@ export interface Query {
getPostgresCredentials?: PostgresCredentials
findManyPublicDomains: PublicDomain[]
getEmailingDomains: EmailingDomain[]
findManyMarketplaceApps: MarketplaceApp[]
findMarketplaceAppDetail: MarketplaceAppDetail
findManyApplications: Application[]
findOneApplication: Application
__typename: 'Query'
}
@@ -2778,7 +2779,6 @@ export interface Mutation {
deactivateSkill: Skill
evaluateAgentTurn: AgentTurnEvaluation
runEvaluationInput: AgentTurn
duplicateDashboard: DuplicatedDashboard
getAuthorizationUrlForSSO: GetAuthorizationUrlForSSO
getLoginTokenFromCredentials: LoginToken
signIn: AvailableWorkspacesAndAccessTokens
@@ -2817,10 +2817,17 @@ export interface Mutation {
updateWorkspace: Workspace
deleteCurrentWorkspace: Workspace
checkCustomDomainValidRecords?: DomainValidRecords
installMarketplaceApp: Scalars['Boolean']
syncMarketplaceCatalog: Scalars['Boolean']
installApplication: Scalars['Boolean']
runWorkspaceMigration: Scalars['Boolean']
uninstallApplication: Scalars['Boolean']
updateOneApplicationVariable: Scalars['Boolean']
createOIDCIdentityProvider: SetupSso
createSAMLIdentityProvider: SetupSso
deleteSSOIdentityProvider: DeleteSso
editSSOIdentityProvider: EditSso
duplicateDashboard: DuplicatedDashboard
impersonate: Impersonate
sendEmail: SendEmailOutput
startChannelSync: ChannelSyncSuccess
@@ -2835,12 +2842,6 @@ export interface Mutation {
deleteEmailingDomain: Scalars['Boolean']
verifyEmailingDomain: EmailingDomain
createOneAppToken: AppToken
installMarketplaceApp: Scalars['Boolean']
syncMarketplaceCatalog: Scalars['Boolean']
installApplication: Scalars['Boolean']
runWorkspaceMigration: Scalars['Boolean']
uninstallApplication: Scalars['Boolean']
updateOneApplicationVariable: Scalars['Boolean']
createDevelopmentApplication: DevelopmentApplication
generateApplicationToken: ApplicationTokenPair
syncApplication: WorkspaceMigration
@@ -2912,6 +2913,7 @@ export interface ApplicationRegistrationGenqlSelection{
latestAvailableVersion?: boolean | number
isListed?: boolean | number
isFeatured?: boolean | number
isPreInstalled?: boolean | number
logoUrl?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
@@ -4561,6 +4563,34 @@ export interface DeletedWorkspaceMemberGenqlSelection{
__scalar?: boolean | number
}
export interface MarketplaceAppGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
icon?: boolean | number
author?: boolean | number
category?: boolean | number
logo?: boolean | number
sourcePackage?: boolean | number
isFeatured?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface MarketplaceAppDetailGenqlSelection{
universalIdentifier?: boolean | number
id?: boolean | number
name?: boolean | number
sourceType?: boolean | number
sourcePackage?: boolean | number
latestAvailableVersion?: boolean | number
isListed?: boolean | number
isFeatured?: boolean | number
manifest?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface RelationGenqlSelection{
type?: boolean | number
sourceObjectMetadata?: ObjectGenqlSelection
@@ -4913,34 +4943,6 @@ export interface FileGenqlSelection{
__scalar?: boolean | number
}
export interface MarketplaceAppGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
icon?: boolean | number
author?: boolean | number
category?: boolean | number
logo?: boolean | number
sourcePackage?: boolean | number
isFeatured?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface MarketplaceAppDetailGenqlSelection{
universalIdentifier?: boolean | number
id?: boolean | number
name?: boolean | number
sourceType?: boolean | number
sourcePackage?: boolean | number
latestAvailableVersion?: boolean | number
isListed?: boolean | number
isFeatured?: boolean | number
manifest?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface PublicDomainGenqlSelection{
id?: boolean | number
domain?: boolean | number
@@ -5624,10 +5626,6 @@ export interface QueryGenqlSelection{
/** Specify to sort results. */
sorting: AgentChatThreadSort[]} })
agentTurns?: (AgentTurnGenqlSelection & { __args: {agentId: Scalars['UUID']} })
eventLogs?: (EventLogQueryResultGenqlSelection & { __args: {input: EventLogQueryInput} })
pieChartData?: (PieChartDataGenqlSelection & { __args: {input: PieChartDataInput} })
lineChartData?: (LineChartDataGenqlSelection & { __args: {input: LineChartDataInput} })
barChartData?: (BarChartDataGenqlSelection & { __args: {input: BarChartDataInput} })
checkUserExists?: (CheckUserExistGenqlSelection & { __args: {email: Scalars['String'], captchaToken?: (Scalars['String'] | null)} })
checkWorkspaceInviteHashIsValid?: (WorkspaceInviteHashValidGenqlSelection & { __args: {inviteHash: Scalars['String']} })
findWorkspaceFromInviteHash?: (WorkspaceGenqlSelection & { __args: {inviteHash: Scalars['String']} })
@@ -5643,7 +5641,15 @@ export interface QueryGenqlSelection{
currentWorkspace?: WorkspaceGenqlSelection
getPublicWorkspaceDataByDomain?: (PublicWorkspaceDataGenqlSelection & { __args?: {origin?: (Scalars['String'] | null)} })
getPublicWorkspaceDataById?: (PublicWorkspaceDataSummaryGenqlSelection & { __args: {id: Scalars['UUID']} })
findManyMarketplaceApps?: MarketplaceAppGenqlSelection
findMarketplaceAppDetail?: (MarketplaceAppDetailGenqlSelection & { __args: {universalIdentifier: Scalars['String']} })
findManyApplications?: ApplicationGenqlSelection
findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} })
getSSOIdentityProviders?: FindAvailableSSOIDPGenqlSelection
eventLogs?: (EventLogQueryResultGenqlSelection & { __args: {input: EventLogQueryInput} })
pieChartData?: (PieChartDataGenqlSelection & { __args: {input: PieChartDataInput} })
lineChartData?: (LineChartDataGenqlSelection & { __args: {input: LineChartDataInput} })
barChartData?: (BarChartDataGenqlSelection & { __args: {input: BarChartDataInput} })
getConnectedImapSmtpCaldavAccount?: (ConnectedImapSmtpCaldavAccountGenqlSelection & { __args: {id: Scalars['UUID']} })
getAutoCompleteAddress?: (AutocompleteResultGenqlSelection & { __args: {address: Scalars['String'], token: Scalars['String'], country?: (Scalars['String'] | null), isFieldCity?: (Scalars['Boolean'] | null)} })
getAddressDetails?: (PlaceDetailsResultGenqlSelection & { __args: {placeId: Scalars['String'], token: Scalars['String']} })
@@ -5651,10 +5657,6 @@ export interface QueryGenqlSelection{
getPostgresCredentials?: PostgresCredentialsGenqlSelection
findManyPublicDomains?: PublicDomainGenqlSelection
getEmailingDomains?: EmailingDomainGenqlSelection
findManyMarketplaceApps?: MarketplaceAppGenqlSelection
findMarketplaceAppDetail?: (MarketplaceAppDetailGenqlSelection & { __args: {universalIdentifier: Scalars['String']} })
findManyApplications?: ApplicationGenqlSelection
findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} })
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -5825,7 +5827,6 @@ export interface MutationGenqlSelection{
deactivateSkill?: (SkillGenqlSelection & { __args: {id: Scalars['UUID']} })
evaluateAgentTurn?: (AgentTurnEvaluationGenqlSelection & { __args: {turnId: Scalars['UUID']} })
runEvaluationInput?: (AgentTurnGenqlSelection & { __args: {agentId: Scalars['UUID'], input: Scalars['String']} })
duplicateDashboard?: (DuplicatedDashboardGenqlSelection & { __args: {id: Scalars['UUID']} })
getAuthorizationUrlForSSO?: (GetAuthorizationUrlForSSOGenqlSelection & { __args: {input: GetAuthorizationUrlForSSOInput} })
getLoginTokenFromCredentials?: (LoginTokenGenqlSelection & { __args: {email: Scalars['String'], password: Scalars['String'], captchaToken?: (Scalars['String'] | null), locale?: (Scalars['String'] | null), verifyEmailRedirectPath?: (Scalars['String'] | null), origin: Scalars['String']} })
signIn?: (AvailableWorkspacesAndAccessTokensGenqlSelection & { __args: {email: Scalars['String'], password: Scalars['String'], captchaToken?: (Scalars['String'] | null), locale?: (Scalars['String'] | null), verifyEmailRedirectPath?: (Scalars['String'] | null)} })
@@ -5864,10 +5865,17 @@ export interface MutationGenqlSelection{
updateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: UpdateWorkspaceInput} })
deleteCurrentWorkspace?: WorkspaceGenqlSelection
checkCustomDomainValidRecords?: DomainValidRecordsGenqlSelection
installMarketplaceApp?: { __args: {universalIdentifier: Scalars['String'], version?: (Scalars['String'] | null)} }
syncMarketplaceCatalog?: boolean | number
installApplication?: { __args: {appRegistrationId: Scalars['String'], version?: (Scalars['String'] | null)} }
runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} }
uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} }
updateOneApplicationVariable?: { __args: {key: Scalars['String'], value: Scalars['String'], applicationId: Scalars['UUID']} }
createOIDCIdentityProvider?: (SetupSsoGenqlSelection & { __args: {input: SetupOIDCSsoInput} })
createSAMLIdentityProvider?: (SetupSsoGenqlSelection & { __args: {input: SetupSAMLSsoInput} })
deleteSSOIdentityProvider?: (DeleteSsoGenqlSelection & { __args: {input: DeleteSsoInput} })
editSSOIdentityProvider?: (EditSsoGenqlSelection & { __args: {input: EditSsoInput} })
duplicateDashboard?: (DuplicatedDashboardGenqlSelection & { __args: {id: Scalars['UUID']} })
impersonate?: (ImpersonateGenqlSelection & { __args: {userId: Scalars['UUID'], workspaceId: Scalars['UUID']} })
sendEmail?: (SendEmailOutputGenqlSelection & { __args: {input: SendEmailInput} })
startChannelSync?: (ChannelSyncSuccessGenqlSelection & { __args: {connectedAccountId: Scalars['UUID']} })
@@ -5882,12 +5890,6 @@ export interface MutationGenqlSelection{
deleteEmailingDomain?: { __args: {id: Scalars['String']} }
verifyEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {id: Scalars['String']} })
createOneAppToken?: (AppTokenGenqlSelection & { __args: {input: CreateOneAppTokenInput} })
installMarketplaceApp?: { __args: {universalIdentifier: Scalars['String'], version?: (Scalars['String'] | null)} }
syncMarketplaceCatalog?: boolean | number
installApplication?: { __args: {appRegistrationId: Scalars['String'], version?: (Scalars['String'] | null)} }
runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} }
uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} }
updateOneApplicationVariable?: { __args: {key: Scalars['String'], value: Scalars['String'], applicationId: Scalars['UUID']} }
createDevelopmentApplication?: (DevelopmentApplicationGenqlSelection & { __args: {universalIdentifier: Scalars['String'], name: Scalars['String']} })
generateApplicationToken?: (ApplicationTokenPairGenqlSelection & { __args: {applicationId: Scalars['UUID']} })
syncApplication?: (WorkspaceMigrationGenqlSelection & { __args: {manifest: Scalars['JSON']} })
@@ -6198,6 +6200,10 @@ export interface ActivateWorkspaceInput {displayName?: (Scalars['String'] | null
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null)}
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']}
export interface SetupOIDCSsoInput {name: Scalars['String'],issuer: Scalars['String'],clientID: Scalars['String'],clientSecret: Scalars['String']}
export interface SetupSAMLSsoInput {name: Scalars['String'],issuer: Scalars['String'],id: Scalars['UUID'],ssoURL: Scalars['String'],certificate: Scalars['String'],fingerprint?: (Scalars['String'] | null)}
@@ -6222,10 +6228,6 @@ appToken: CreateAppTokenInput}
export interface CreateAppTokenInput {expiresAt: Scalars['DateTime']}
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']}
export interface SubscriptionGenqlSelection{
onEventSubscription?: (EventSubscriptionGenqlSelection & { __args: {eventStreamId: Scalars['String']} })
logicFunctionLogs?: (LogicFunctionLogsGenqlSelection & { __args: {input: LogicFunctionLogsInput} })
@@ -7357,6 +7359,22 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const MarketplaceApp_possibleTypes: string[] = ['MarketplaceApp']
export const isMarketplaceApp = (obj?: { __typename?: any } | null): obj is MarketplaceApp => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMarketplaceApp"')
return MarketplaceApp_possibleTypes.includes(obj.__typename)
}
const MarketplaceAppDetail_possibleTypes: string[] = ['MarketplaceAppDetail']
export const isMarketplaceAppDetail = (obj?: { __typename?: any } | null): obj is MarketplaceAppDetail => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMarketplaceAppDetail"')
return MarketplaceAppDetail_possibleTypes.includes(obj.__typename)
}
const Relation_possibleTypes: string[] = ['Relation']
export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => {
if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"')
@@ -7717,22 +7735,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const MarketplaceApp_possibleTypes: string[] = ['MarketplaceApp']
export const isMarketplaceApp = (obj?: { __typename?: any } | null): obj is MarketplaceApp => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMarketplaceApp"')
return MarketplaceApp_possibleTypes.includes(obj.__typename)
}
const MarketplaceAppDetail_possibleTypes: string[] = ['MarketplaceAppDetail']
export const isMarketplaceAppDetail = (obj?: { __typename?: any } | null): obj is MarketplaceAppDetail => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMarketplaceAppDetail"')
return MarketplaceAppDetail_possibleTypes.includes(obj.__typename)
}
const PublicDomain_possibleTypes: string[] = ['PublicDomain']
export const isPublicDomain = (obj?: { __typename?: any } | null): obj is PublicDomain => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPublicDomain"')
File diff suppressed because it is too large Load Diff
@@ -136,6 +136,7 @@ export type ApplicationRegistration = {
id: Scalars['UUID'];
isFeatured: Scalars['Boolean'];
isListed: Scalars['Boolean'];
isPreInstalled: Scalars['Boolean'];
latestAvailableVersion?: Maybe<Scalars['String']>;
logoUrl?: Maybe<Scalars['String']>;
name: Scalars['String'];
@@ -771,7 +772,7 @@ export type GetModelsDevSuggestionsQuery = { __typename?: 'Query', getModelsDevS
export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
key: Scalars['String'];
@@ -838,7 +839,7 @@ export type FindOneAdminApplicationRegistrationQueryVariables = Exact<{
}>;
export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type GetAdminChatThreadMessagesQueryVariables = Exact<{
threadId: Scalars['UUID'];
@@ -938,10 +939,10 @@ export type GetMaintenanceModeQueryVariables = Exact<{ [key: string]: never; }>;
export type GetMaintenanceModeQuery = { __typename?: 'Query', getMaintenanceMode?: { __typename?: 'MaintenanceMode', startAt: string, endAt: string, link?: string | null } | null };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export const UserInfoFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<UserInfoFragmentFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const AddAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"providerConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}}}]}]}}]} as unknown as DocumentNode<AddAiProviderMutation, AddAiProviderMutationVariables>;
export const AddModelToProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddModelToProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addModelToProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}}}]}]}}]} as unknown as DocumentNode<AddModelToProviderMutation, AddModelToProviderMutationVariables>;
export const RemoveAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}}]}]}}]} as unknown as DocumentNode<RemoveAiProviderMutation, RemoveAiProviderMutationVariables>;
@@ -956,7 +957,7 @@ export const GetAdminAiUsageByWorkspaceDocument = {"kind":"Document","definition
export const GetAiProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAiProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAiProviders"}}]}}]} as unknown as DocumentNode<GetAiProvidersQuery, GetAiProvidersQueryVariables>;
export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"modelCount"}},{"kind":"Field","name":{"kind":"Name","value":"npm"}}]}}]}}]} as unknown as DocumentNode<GetModelsDevProvidersQuery, GetModelsDevProvidersQueryVariables>;
export const GetModelsDevSuggestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevSuggestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevSuggestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"outputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cachedInputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cacheCreationCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"contextWindowTokens"}},{"kind":"Field","name":{"kind":"Name","value":"maxOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"modalities"}},{"kind":"Field","name":{"kind":"Name","value":"supportsReasoning"}}]}}]}}]} as unknown as DocumentNode<GetModelsDevSuggestionsQuery, GetModelsDevSuggestionsQueryVariables>;
export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
export const CreateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<CreateDatabaseConfigVariableMutation, CreateDatabaseConfigVariableMutationVariables>;
export const DeleteDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}]}]}}]} as unknown as DocumentNode<DeleteDatabaseConfigVariableMutation, DeleteDatabaseConfigVariableMutationVariables>;
export const UpdateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<UpdateDatabaseConfigVariableMutation, UpdateDatabaseConfigVariableMutationVariables>;
@@ -965,7 +966,7 @@ export const GetDatabaseConfigVariableDocument = {"kind":"Document","definitions
export const UpdateWorkspaceFeatureFlagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceFeatureFlag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceFeatureFlag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"featureFlag"},"value":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;
export const AdminPanelRecentUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelRecentUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelRecentUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}}]}}]}}]} as unknown as DocumentNode<AdminPanelRecentUsersQuery, AdminPanelRecentUsersQueryVariables>;
export const AdminPanelTopWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelTopWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelTopWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"subdomain"}}]}}]}}]} as unknown as DocumentNode<AdminPanelTopWorkspacesQuery, AdminPanelTopWorkspacesQueryVariables>;
export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
export const GetAdminChatThreadMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminChatThreadMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminChatThreadMessages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"parts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"toolName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<GetAdminChatThreadMessagesQuery, GetAdminChatThreadMessagesQueryVariables>;
export const GetAdminWorkspaceChatThreadsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminWorkspaceChatThreads"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminWorkspaceChatThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<GetAdminWorkspaceChatThreadsQuery, GetAdminWorkspaceChatThreadsQueryVariables>;
export const GetVersionInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetVersionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}}]}}]}}]} as unknown as DocumentNode<GetVersionInfoQuery, GetVersionInfoQueryVariables>;
@@ -324,6 +324,7 @@ export type ApplicationRegistration = {
id: Scalars['UUID'];
isFeatured: Scalars['Boolean'];
isListed: Scalars['Boolean'];
isPreInstalled: Scalars['Boolean'];
latestAvailableVersion?: Maybe<Scalars['String']>;
logoUrl?: Maybe<Scalars['String']>;
name: Scalars['String'];
@@ -6822,7 +6823,7 @@ export type MyMessageFoldersQueryVariables = Exact<{
export type MyMessageFoldersQuery = { __typename?: 'Query', myMessageFolders: Array<{ __typename?: 'MessageFolder', id: string, name?: string | null, isSynced: boolean, isSentFolder: boolean, parentFolderId?: string | null, externalId?: string | null, messageChannelId: string, createdAt: string, updatedAt: string }> };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type DeleteApplicationRegistrationMutationVariables = Exact<{
id: Scalars['String'];
@@ -6851,7 +6852,7 @@ export type UpdateApplicationRegistrationMutationVariables = Exact<{
}>;
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{
input: UpdateApplicationRegistrationVariableInput;
@@ -6884,14 +6885,14 @@ export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query',
export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindOneApplicationRegistrationQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type UninstallApplicationMutationVariables = Exact<{
universalIdentifier: Scalars['String'];
@@ -7785,7 +7786,7 @@ export const MarketplaceAppFieldsFragmentDoc = {"kind":"Document","definitions":
export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<NavigationMenuItemFieldsFragment, unknown>;
export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<NavigationMenuItemQueryFieldsFragment, unknown>;
export const PublicConnectionParamsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode<PublicConnectionParamsFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const BillingPriceLicensedFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}}]}}]} as unknown as DocumentNode<BillingPriceLicensedFragmentFragment, unknown>;
export const BillingPriceMeteredFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode<BillingPriceMeteredFragmentFragment, unknown>;
export const ApiKeyFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode<ApiKeyFragmentFragment, unknown>;
@@ -7937,13 +7938,13 @@ export const MyMessageFoldersDocument = {"kind":"Document","definitions":[{"kind
export const DeleteApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<DeleteApplicationRegistrationMutation, DeleteApplicationRegistrationMutationVariables>;
export const RotateApplicationRegistrationClientSecretDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RotateApplicationRegistrationClientSecret"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rotateApplicationRegistrationClientSecret"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clientSecret"}}]}}]}}]} as unknown as DocumentNode<RotateApplicationRegistrationClientSecretMutation, RotateApplicationRegistrationClientSecretMutationVariables>;
export const TransferApplicationRegistrationOwnershipDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TransferApplicationRegistrationOwnership"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"targetWorkspaceSubdomain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transferApplicationRegistrationOwnership"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"targetWorkspaceSubdomain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"targetWorkspaceSubdomain"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>;
export const UpdateApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateApplicationRegistrationMutation, UpdateApplicationRegistrationMutationVariables>;
export const UpdateApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateApplicationRegistrationMutation, UpdateApplicationRegistrationMutationVariables>;
export const UpdateApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistrationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<UpdateApplicationRegistrationVariableMutation, UpdateApplicationRegistrationVariableMutationVariables>;
export const ApplicationRegistrationTarballUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ApplicationRegistrationTarballUrl"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationRegistrationTarballUrl"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>;
export const FindApplicationRegistrationStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeInstalls"}},{"kind":"Field","name":{"kind":"Name","value":"mostInstalledVersion"}},{"kind":"Field","name":{"kind":"Name","value":"versionDistribution"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<FindApplicationRegistrationStatsQuery, FindApplicationRegistrationStatsQueryVariables>;
export const FindApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<FindApplicationRegistrationVariablesQuery, FindApplicationRegistrationVariablesQueryVariables>;
export const FindManyApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindManyApplicationRegistrationsQuery, FindManyApplicationRegistrationsQueryVariables>;
export const FindOneApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneApplicationRegistrationQuery, FindOneApplicationRegistrationQueryVariables>;
export const FindManyApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindManyApplicationRegistrationsQuery, FindManyApplicationRegistrationsQueryVariables>;
export const FindOneApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneApplicationRegistrationQuery, FindOneApplicationRegistrationQueryVariables>;
export const UninstallApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UninstallApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uninstallApplication"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"universalIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}}}]}]}}]} as unknown as DocumentNode<UninstallApplicationMutation, UninstallApplicationMutationVariables>;
export const UpdateOneApplicationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneApplicationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneApplicationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}},{"kind":"Argument","name":{"kind":"Name","value":"applicationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}}}]}]}}]} as unknown as DocumentNode<UpdateOneApplicationVariableMutation, UpdateOneApplicationVariableMutationVariables>;
export const CancelSwitchBillingIntervalDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelSwitchBillingInterval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cancelSwitchBillingInterval"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentBillingSubscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CurrentBillingSubscriptionFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"billingSubscriptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItemFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"price"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhase"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start_date"}},{"kind":"Field","name":{"kind":"Name","value":"end_date"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseItemFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CurrentBillingSubscriptionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscription"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"phases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"billingSubscriptionItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"hasReachedCurrentPeriodCap"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"billingProduct"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageBased"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingSubscriptionFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingSubscription"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"metadata"}},{"kind":"Field","name":{"kind":"Name","value":"phases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingSubscriptionSchedulePhaseFragment"}}]}}]}}]} as unknown as DocumentNode<CancelSwitchBillingIntervalMutation, CancelSwitchBillingIntervalMutationVariables>;
@@ -86,11 +86,11 @@ describe('getToolDisplayMessage', () => {
});
});
describe('exa_web_search', () => {
describe('app_exa_web_search', () => {
it('should show the same searching-the-web message as native web_search', () => {
const message = getToolDisplayMessage(
{ query: 'CRM tools' },
'exa_web_search',
'app_exa_web_search',
false,
);
@@ -99,7 +99,7 @@ describe('getToolDisplayMessage', () => {
});
it('should handle missing query', () => {
const message = getToolDisplayMessage({}, 'exa_web_search', true);
const message = getToolDisplayMessage({}, 'app_exa_web_search', true);
expect(message).toContain('Searched the web');
});
@@ -88,7 +88,7 @@ export const getToolDisplayMessage = (
if (
resolvedToolName === 'web_search' ||
resolvedToolName === 'exa_web_search'
resolvedToolName === 'app_exa_web_search'
) {
const query = extractSearchQuery(resolvedInput);
@@ -1,4 +1,7 @@
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
@@ -7,16 +10,18 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useQuery } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext, useState } from 'react';
import { type ReactNode, useContext, useState } from 'react';
import { assertUnreachable, getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import {
H2Title,
IconChevronRight,
IconPinned,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import {
type ApplicationRegistrationFragmentFragment,
@@ -35,6 +40,7 @@ const TABLE_GRID_MOBILE = '3fr 3fr 1fr 40px';
export const SettingsAdminApps = () => {
const apolloAdminClient = useApolloAdminClient();
const [searchQuery, setSearchQuery] = useState('');
const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false);
const { theme } = useContext(ThemeContext);
const { data } = useQuery(FindAllApplicationRegistrationsDocument, {
@@ -44,18 +50,23 @@ export const SettingsAdminApps = () => {
const registrations: ApplicationRegistrationFragmentFragment[] =
data?.findAllApplicationRegistrations ?? [];
const filtered =
searchQuery.trim().length === 0
? registrations
: registrations.filter((registration) => {
const query = searchQuery.toLowerCase();
const query = searchQuery.trim().toLowerCase();
return (
registration.name.toLowerCase().includes(query) ||
(registration.sourcePackage ?? '').toLowerCase().includes(query) ||
registration.universalIdentifier.toLowerCase().includes(query)
);
});
const filtered = registrations.filter((registration) => {
if (showPreInstalledOnly && !registration.isPreInstalled) {
return false;
}
if (query.length === 0) {
return true;
}
return (
registration.name.toLowerCase().includes(query) ||
(registration.sourcePackage ?? '').toLowerCase().includes(query) ||
registration.universalIdentifier.toLowerCase().includes(query)
);
});
const getFormattedSource = (
registration: ApplicationRegistrationFragmentFragment,
@@ -88,6 +99,29 @@ export const SettingsAdminApps = () => {
placeholder={t`Search registrations...`}
value={searchQuery}
onChange={setSearchQuery}
filterDropdown={(filterButton: ReactNode) => (
<Dropdown
dropdownId="settings-admin-apps-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconPinned}
onToggleChange={() =>
setShowPreInstalledOnly(!showPreInstalledOnly)
}
toggled={showPreInstalledOnly}
text={t`Pre-installed only`}
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
/>
<StyledTableContainer>
<Table>
@@ -13,6 +13,7 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
latestAvailableVersion
isListed
isFeatured
isPreInstalled
ownerWorkspaceId
createdAt
updatedAt
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "2.0.0",
"version": "2.1.0",
"sideEffects": false,
"bin": {
"twenty": "dist/cli.cjs"
@@ -22,6 +22,11 @@
"development"
],
"exports": {
"./billing": {
"types": "./dist/billing/index.d.ts",
"import": "./dist/billing/index.mjs",
"require": "./dist/billing/index.cjs"
},
"./define": {
"types": "./dist/define/index.d.ts",
"import": "./dist/define/index.mjs",
+3 -3
View File
@@ -14,9 +14,9 @@
"options": {
"cwd": "{projectRoot}",
"commands": [
"npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts",
"npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts",
"tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist",
"npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs"
"npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs"
],
"parallel": false
}
@@ -26,7 +26,7 @@
"dependsOn": ["^build"],
"options": {
"cwd": "packages/twenty-sdk",
"command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.browser.ts --watch"
"command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.billing.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.browser.ts --watch"
}
},
"start": {
@@ -30,4 +30,10 @@ export default [
external,
plugins,
},
{
input: 'src/sdk/billing/index.ts',
output: { file: 'dist/billing/index.d.ts', format: 'es' },
external,
plugins,
},
];
@@ -0,0 +1,65 @@
import {
DEFAULT_API_URL_NAME,
DEFAULT_APP_ACCESS_TOKEN_NAME,
} from 'twenty-shared/application';
const BILLING_CHARGE_TIMEOUT_MS = 5_000;
export type ChargeCreditsParams = {
creditsUsedMicro: number;
operationType: string;
quantity?: number;
resourceContext?: string;
};
// Records credit usage against the running application via the Twenty
// server's `/app/billing/charge` endpoint. Reads `TWENTY_API_URL` and
// `TWENTY_APP_ACCESS_TOKEN` from the execution env (injected by the
// logic-function runtime). No-ops silently when either is missing so
// local/test runs don't crash. Failures are non-fatal — a billing error
// never surfaces as a tool failure.
export const chargeCredits = async ({
creditsUsedMicro,
operationType,
quantity = 1,
resourceContext,
}: ChargeCreditsParams): Promise<void> => {
const apiUrl = process.env[DEFAULT_API_URL_NAME];
const token = process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];
if (!apiUrl || !token) {
return;
}
try {
const response = await fetch(
`${apiUrl.replace(/\/$/, '')}/app/billing/charge`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
creditsUsedMicro,
quantity,
operationType,
resourceContext,
}),
signal: AbortSignal.timeout(BILLING_CHARGE_TIMEOUT_MS),
},
);
if (!response.ok) {
const body = await response.text().catch(() => '');
console.error(
`chargeCredits: ${response.status} ${response.statusText}: ${body}`,
);
}
} catch (error) {
console.error(
`chargeCredits: ${error instanceof Error ? error.message : String(error)}`,
);
}
};
@@ -0,0 +1 @@
export { chargeCredits, type ChargeCreditsParams } from './charge-credits';
@@ -0,0 +1,65 @@
import path from 'path';
import { type PackageJson } from 'type-fest';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import packageJson from './package.json';
export default defineConfig(() => {
return {
root: __dirname,
cacheDir: '../../node_modules/.vite/packages/twenty-sdk-billing',
resolve: {
alias: {
'@/': path.resolve(__dirname, 'src') + '/',
},
},
plugins: [
tsconfigPaths({
root: __dirname,
}),
],
build: {
emptyOutDir: false,
outDir: 'dist/billing',
sourcemap: true,
lib: {
entry: 'src/sdk/billing/index.ts',
name: 'twenty-sdk-billing',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
},
rollupOptions: {
external: (id: string) => {
if (/^node:/.test(id)) {
return true;
}
const builtins = [
'child_process',
'crypto',
'fs',
'fs/promises',
'module',
'os',
'path',
'stream',
'url',
'util',
];
if (builtins.includes(id)) {
return true;
}
const deps = Object.keys(
(packageJson as PackageJson).dependencies || {},
);
return deps.some((dep) => id === dep || id.startsWith(dep + '/'));
},
},
},
logLevel: 'warn' as const,
};
});
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { CronRegisterAllCommand } from 'src/database/commands/cron-register-all.command';
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
import { GenerateInstanceCommandCommand } from 'src/database/commands/generate-instance-command.command';
import { InstallPreInstalledAppsCommand } from 'src/database/commands/install-pre-installed-apps.command';
import { InstanceCommandGenerationService } from 'src/database/commands/instance-command-generation.service';
import { ListOrphanedWorkspaceEntitiesCommand } from 'src/database/commands/list-and-delete-orphaned-workspace-entities.command';
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
@@ -15,6 +16,7 @@ import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
import { StaleRegistrationCleanupModule } from 'src/engine/core-modules/application/application-oauth/stale-registration-cleanup/stale-registration-cleanup.module';
import { PreInstalledAppsModule } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.module';
import { ApplicationUpgradeModule } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.module';
import { RebuildApplicationDefaultDepsCommand } from 'src/database/commands/rebuild-application-default-deps.command';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
@@ -77,6 +79,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
MarketplaceModule,
ApplicationUpgradeModule,
StaleRegistrationCleanupModule,
PreInstalledAppsModule,
WorkspaceIteratorModule,
ApplicationModule,
WorkspaceCacheModule,
@@ -96,6 +99,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
EnforceUsageCapCronCommand,
UpgradeStatusCommand,
RebuildApplicationDefaultDepsCommand,
InstallPreInstalledAppsCommand,
],
})
export class DatabaseCommandModule {}
@@ -0,0 +1,41 @@
import { Command } from 'nest-commander';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { PreInstalledAppsService } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.service';
// Backfill: rolls `isPreInstalled=true` registrations out to workspaces
// that existed before the flag was flipped. Idempotent.
@Command({
name: 'install-pre-installed-apps',
description:
'Install every application registration flagged `isPreInstalled` on every active and suspended workspace. Idempotent.',
})
export class InstallPreInstalledAppsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly preInstalledAppsService: PreInstalledAppsService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
index,
total,
}: RunOnWorkspaceArgs): Promise<void> {
const dryRun = options.dryRun ?? false;
this.logger.log(
`${dryRun ? '[DRY RUN] ' : ''}Installing pre-installed apps on workspace ${workspaceId} (${index + 1}/${total})`,
);
if (dryRun) {
return;
}
await this.preInstalledAppsService.installOnWorkspace(workspaceId);
}
}
@@ -0,0 +1,21 @@
import { QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
@RegisteredInstanceCommand('2.0.0', 1776886452831)
export class AddIsPreInstalledToApplicationRegistrationFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" ADD "isPreInstalled" boolean NOT NULL DEFAULT false',
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "isPreInstalled"',
);
}
}
@@ -0,0 +1,131 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { type DataSource, type QueryRunner, Repository } from 'typeorm';
import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/marketplace.service';
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { type SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
const EXA_PACKAGE_NAME = 'twenty-exa';
// One-shot bridge for instances that set `EXA_API_KEY` as an env var under
// the old web-search driver. Registers `twenty-exa` as an application,
// seeds the key onto its `ApplicationRegistrationVariable`, and flips
// `isPreInstalled=true` so the next workspace install backfills it. The
// env var can be removed from infra after deploy. Idempotent — each step
// no-ops when its target already exists.
@RegisteredInstanceCommand('2.0.0', 1776894434000, { type: 'slow' })
@Injectable()
export class SeedExaPreInstallFromEnvSlowInstanceCommand
implements SlowInstanceCommand
{
private readonly logger = new Logger(
SeedExaPreInstallFromEnvSlowInstanceCommand.name,
);
constructor(
private readonly marketplaceService: MarketplaceService,
private readonly applicationRegistrationService: ApplicationRegistrationService,
private readonly secretEncryptionService: SecretEncryptionService,
@InjectRepository(ApplicationRegistrationEntity)
private readonly applicationRegistrationRepository: Repository<ApplicationRegistrationEntity>,
@InjectRepository(ApplicationRegistrationVariableEntity)
private readonly applicationRegistrationVariableRepository: Repository<ApplicationRegistrationVariableEntity>,
) {}
public async up(_queryRunner: QueryRunner): Promise<void> {}
public async down(_queryRunner: QueryRunner): Promise<void> {}
async runDataMigration(_dataSource: DataSource): Promise<void> {
const apiKey = process.env.EXA_API_KEY;
if (!apiKey || apiKey.length === 0) {
this.logger.log('EXA_API_KEY not set — skipping Exa pre-install seed.');
return;
}
const packages = await this.marketplaceService.fetchAppsFromRegistry();
const exaPackage = packages.find((pkg) => pkg.name === EXA_PACKAGE_NAME);
if (!exaPackage) {
this.logger.warn(
`"${EXA_PACKAGE_NAME}" not found in the app registry — skipping seed. ` +
`Publish the package first, then run \`install-pre-installed-apps\` to backfill.`,
);
return;
}
const manifest = await this.marketplaceService.fetchManifestFromRegistryCdn(
exaPackage.name,
exaPackage.version,
);
if (!manifest) {
this.logger.warn(
`Manifest not found for "${EXA_PACKAGE_NAME}@${exaPackage.version}" — skipping seed.`,
);
return;
}
await this.applicationRegistrationService.upsertFromCatalog({
universalIdentifier: manifest.application.universalIdentifier,
name: manifest.application.displayName ?? exaPackage.name,
sourceType: ApplicationRegistrationSourceType.NPM,
sourcePackage: exaPackage.name,
latestAvailableVersion: exaPackage.version,
isListed: true,
isFeatured: false,
manifest,
ownerWorkspaceId: null,
});
const registration = await this.applicationRegistrationRepository.findOne({
where: { universalIdentifier: manifest.application.universalIdentifier },
});
if (!registration) {
this.logger.error(
`upsertFromCatalog did not produce a registration for "${EXA_PACKAGE_NAME}".`,
);
return;
}
// Fill EXA_API_KEY only when unset — never overwrite a value already
// edited via the admin UI.
const variable =
await this.applicationRegistrationVariableRepository.findOne({
where: {
applicationRegistrationId: registration.id,
key: 'EXA_API_KEY',
},
});
if (variable && variable.encryptedValue === '') {
await this.applicationRegistrationVariableRepository.update(variable.id, {
encryptedValue: this.secretEncryptionService.encrypt(apiKey),
});
}
if (!registration.isPreInstalled) {
await this.applicationRegistrationRepository.update(registration.id, {
isPreInstalled: true,
});
}
this.logger.log(
`Seeded "${EXA_PACKAGE_NAME}" registration + EXA_API_KEY + isPreInstalled=true. ` +
`Run \`install-pre-installed-apps\` to backfill existing workspaces.`,
);
}
}
@@ -1,8 +1,23 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { INSTANCE_COMMANDS } from 'src/database/commands/upgrade-version-command/instance-commands.constant';
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
@Module({
imports: [
TypeOrmModule.forFeature([
ApplicationRegistrationEntity,
ApplicationRegistrationVariableEntity,
]),
ApplicationRegistrationModule,
MarketplaceModule,
SecretEncryptionModule,
],
providers: [...INSTANCE_COMMANDS],
})
export class InstanceCommandProviderModule {}
@@ -15,6 +15,8 @@ import { DropWorkspaceVersionColumnFastInstanceCommand } from 'src/database/comm
import { AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776090711153-add-global-object-context-to-command-menu-item-availability-type';
import { AddPageLayoutIdToCommandMenuItemFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1776168404836-add-page-layout-id-to-command-menu-item';
import { AddConditionalAvailabilityExpressionToPageLayoutWidgetFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-23/1-23-instance-command-fast-1775654781000-add-conditional-availability-expression-to-page-layout-widget';
import { AddIsPreInstalledToApplicationRegistrationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-0/2-0-instance-command-fast-1776886452831-add-is-pre-installed-to-application-registration';
import { SeedExaPreInstallFromEnvSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-0/2-0-instance-command-slow-1776894434000-seed-exa-pre-install-from-env';
export const INSTANCE_COMMANDS = [
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
@@ -32,4 +34,6 @@ export const INSTANCE_COMMANDS = [
AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand,
AddPageLayoutIdToCommandMenuItemFastInstanceCommand,
AddConditionalAvailabilityExpressionToPageLayoutWidgetFastInstanceCommand,
AddIsPreInstalledToApplicationRegistrationFastInstanceCommand,
SeedExaPreInstallFromEnvSlowInstanceCommand,
];
@@ -34,6 +34,7 @@ import { MarketplaceCatalogSyncCommand } from 'src/engine/core-modules/applicati
MarketplaceCatalogSyncService,
MarketplaceQueryService,
MarketplaceCatalogSyncCronCommand,
MarketplaceService,
],
})
export class MarketplaceModule {}
@@ -124,6 +124,12 @@ export class ApplicationRegistrationEntity {
@Column({ name: 'isFeatured', type: 'boolean', default: false })
isFeatured: boolean;
// Auto-installed on every new workspace; existing workspaces are
// backfilled by the `install-pre-installed-apps` CLI command.
@Field(() => Boolean)
@Column({ type: 'boolean', default: false })
isPreInstalled: boolean;
@Column({ type: 'jsonb', nullable: true })
manifest: Manifest | null;
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { PreInstalledAppsService } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.service';
@Module({
imports: [
TypeOrmModule.forFeature([ApplicationRegistrationEntity]),
ApplicationInstallModule,
],
providers: [PreInstalledAppsService],
exports: [PreInstalledAppsService],
})
export class PreInstalledAppsModule {}
@@ -0,0 +1,48 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
@Injectable()
export class PreInstalledAppsService {
private readonly logger = new Logger(PreInstalledAppsService.name);
constructor(
private readonly applicationInstallService: ApplicationInstallService,
@InjectRepository(ApplicationRegistrationEntity)
private readonly applicationRegistrationRepository: Repository<ApplicationRegistrationEntity>,
) {}
// Per-app failures are logged but never block the other installs —
// `ApplicationInstallService` holds a per-app cache lock so parallel
// installs are safe.
async installOnWorkspace(workspaceId: string): Promise<void> {
const registrations = await this.applicationRegistrationRepository.find({
where: { isPreInstalled: true },
});
if (registrations.length === 0) {
return;
}
await Promise.allSettled(
registrations.map(async (registration) => {
try {
await this.applicationInstallService.installApplication({
appRegistrationId: registration.id,
workspaceId,
});
} catch (error) {
this.logger.error(
`Failed to install pre-installed app "${registration.name}" (${registration.id}) on workspace ${workspaceId}: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
}),
);
}
}
@@ -0,0 +1,77 @@
/* @license Enterprise */
import {
Body,
Controller,
ForbiddenException,
HttpCode,
HttpStatus,
NotFoundException,
Post,
Req,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { Request } from 'express';
import { isDefined } from 'twenty-shared/utils';
import { AppBillingService } from 'src/engine/core-modules/billing/app-billing/app-billing.service';
import { ChargeDto } from 'src/engine/core-modules/billing/app-billing/dtos/charge.dto';
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
// Belt-and-suspenders on top of LogicFunctionExecutorService's execution
// throttle: application-access tokens are JWTs usable outside the runtime.
const APP_BILLING_CHARGE_THROTTLE_LIMIT = 1000;
const APP_BILLING_CHARGE_THROTTLE_TTL_MS = 60_000;
@Controller('app/billing')
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)
export class AppBillingController {
constructor(
private readonly appBillingService: AppBillingService,
private readonly throttlerService: ThrottlerService,
private readonly twentyConfigService: TwentyConfigService,
) {}
@Post('charge')
@HttpCode(HttpStatus.NO_CONTENT)
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
async charge(
@Req() request: Request,
@Body() charge: ChargeDto,
): Promise<void> {
// Billing disabled: no listener consumes the event — fail fast so apps
// don't silently discard charges on Community instances.
if (!this.twentyConfigService.get('IS_BILLING_ENABLED')) {
throw new NotFoundException();
}
// Reject user-access / api-key tokens — only application-access tokens
// populate `request.application`.
if (!isDefined(request.application) || !isDefined(request.workspace)) {
throw new ForbiddenException(
'App billing endpoint requires an APPLICATION_ACCESS token.',
);
}
await this.throttlerService.tokenBucketThrottleOrThrow(
`${request.workspace.id}-${request.application.id}-app-billing-charge`,
1,
APP_BILLING_CHARGE_THROTTLE_LIMIT,
APP_BILLING_CHARGE_THROTTLE_TTL_MS,
);
this.appBillingService.emitChargeEvent({
workspaceId: request.workspace.id,
applicationId: request.application.id,
userWorkspaceId: request.userWorkspaceId,
charge,
});
}
}
@@ -0,0 +1,25 @@
/* @license Enterprise */
import { Module } from '@nestjs/common';
import { AppBillingController } from 'src/engine/core-modules/billing/app-billing/app-billing.controller';
import { AppBillingService } from 'src/engine/core-modules/billing/app-billing/app-billing.service';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
@Module({
imports: [
AuthModule,
ThrottlerModule,
TwentyConfigModule,
WorkspaceCacheStorageModule,
WorkspaceEventEmitterModule,
],
controllers: [AppBillingController],
providers: [AppBillingService],
exports: [AppBillingService],
})
export class AppBillingModule {}
@@ -0,0 +1,63 @@
/* @license Enterprise */
import { Injectable, Logger } from '@nestjs/common';
import { type ChargeDto } from 'src/engine/core-modules/billing/app-billing/dtos/charge.dto';
import { USAGE_RECORDED } from 'src/engine/core-modules/usage/constants/usage-recorded.constant';
import { UsageOperationType } from 'src/engine/core-modules/usage/enums/usage-operation-type.enum';
import { UsageResourceType } from 'src/engine/core-modules/usage/enums/usage-resource-type.enum';
import { UsageUnit } from 'src/engine/core-modules/usage/enums/usage-unit.enum';
import { type UsageEvent } from 'src/engine/core-modules/usage/types/usage-event.type';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
// Each operation type has one canonical counting unit — matches how
// `ai-billing.service.ts` emits native usage events.
const USAGE_UNIT_BY_OPERATION_TYPE: Record<UsageOperationType, UsageUnit> = {
[UsageOperationType.AI_CHAT_TOKEN]: UsageUnit.TOKEN,
[UsageOperationType.AI_WORKFLOW_TOKEN]: UsageUnit.TOKEN,
[UsageOperationType.WORKFLOW_EXECUTION]: UsageUnit.INVOCATION,
[UsageOperationType.CODE_EXECUTION]: UsageUnit.INVOCATION,
[UsageOperationType.WEB_SEARCH]: UsageUnit.INVOCATION,
};
// `workspaceId` + `applicationId` come from the application-access token,
// never from the body — an app can't charge a different workspace or
// masquerade as a different app.
@Injectable()
export class AppBillingService {
private readonly logger = new Logger(AppBillingService.name);
constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {}
emitChargeEvent(params: {
workspaceId: string;
applicationId: string;
userWorkspaceId?: string | null;
charge: ChargeDto;
}): void {
const { workspaceId, applicationId, userWorkspaceId, charge } = params;
const unit = USAGE_UNIT_BY_OPERATION_TYPE[charge.operationType];
this.logger.log(
`App charge from applicationId=${applicationId} workspaceId=${workspaceId}: ` +
`${charge.creditsUsedMicro} micro-credits (${charge.quantity} ${unit}, ${charge.operationType})`,
);
this.workspaceEventEmitter.emitCustomBatchEvent<UsageEvent>(
USAGE_RECORDED,
[
{
resourceType: UsageResourceType.APP,
operationType: charge.operationType,
creditsUsedMicro: charge.creditsUsedMicro,
quantity: charge.quantity,
unit,
resourceId: applicationId,
resourceContext: charge.resourceContext ?? null,
userWorkspaceId: userWorkspaceId ?? null,
},
],
workspaceId,
);
}
}
@@ -0,0 +1,29 @@
/* @license Enterprise */
import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
import { UsageOperationType } from 'src/engine/core-modules/usage/enums/usage-operation-type.enum';
// $1000 in micro-credits (1 USD = 1_000_000 micro-credits). Bounds a single
// charge so a compromised or buggy app can't drain credits in one request.
const MAX_CREDITS_USED_MICRO_PER_CHARGE = 1_000_000_000;
const MAX_QUANTITY_PER_CHARGE = 10_000;
export class ChargeDto {
@IsInt()
@Min(0)
@Max(MAX_CREDITS_USED_MICRO_PER_CHARGE)
creditsUsedMicro!: number;
@IsInt()
@Min(1)
@Max(MAX_QUANTITY_PER_CHARGE)
quantity!: number;
@IsEnum(UsageOperationType)
operationType!: UsageOperationType;
@IsOptional()
@IsString()
resourceContext?: string;
}
@@ -16,9 +16,11 @@ import { ApplicationOAuthModule } from 'src/engine/core-modules/application/appl
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
import { ApplicationUpgradeModule } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { PreInstalledAppsModule } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.module';
import { ApprovedAccessDomainModule } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.module';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { BillingWebhookModule } from 'src/engine/core-modules/billing-webhook/billing-webhook.module';
import { AppBillingModule } from 'src/engine/core-modules/billing/app-billing/app-billing.module';
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
import { BillingGraphqlApiExceptionFilter } from 'src/engine/core-modules/billing/filters/billing-graphql-api-exception.filter';
import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module';
@@ -26,7 +28,6 @@ import { TimelineCalendarEventModule } from 'src/engine/core-modules/calendar/ti
import { CaptchaModule } from 'src/engine/core-modules/captcha/captcha.module';
import { CloudflareModule } from 'src/engine/core-modules/cloudflare/cloudflare.module';
import { CodeInterpreterModule } from 'src/engine/core-modules/code-interpreter/code-interpreter.module';
import { WebSearchModule } from 'src/engine/core-modules/web-search/web-search.module';
import { DnsManagerModule } from 'src/engine/core-modules/dns-manager/dns-manager.module';
import { EmailModule } from 'src/engine/core-modules/email/email.module';
import { EmailingDomainModule } from 'src/engine/core-modules/emailing-domain/emailing-domain.module';
@@ -156,7 +157,6 @@ import { FileModule } from './file/file.module';
AiBillingModule,
LogicFunctionModule.forRoot(),
CodeInterpreterModule.forRoot(),
WebSearchModule.forRoot(),
SearchModule,
ApiKeyModule,
PageLayoutModule,
@@ -164,6 +164,8 @@ import { FileModule } from './file/file.module';
TrashCleanupModule,
DashboardModule,
EventLogsModule,
PreInstalledAppsModule,
AppBillingModule,
],
providers: [
{
@@ -1,5 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service';
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
@@ -16,6 +18,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
SecretEncryptionModule,
SubscriptionsModule,
WorkspaceCacheModule,
TypeOrmModule.forFeature([ApplicationRegistrationVariableEntity]),
],
providers: [LogicFunctionExecutorService],
exports: [LogicFunctionExecutorService],
@@ -1,4 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
DEFAULT_API_KEY_NAME,
@@ -6,6 +7,7 @@ import {
DEFAULT_APP_ACCESS_TOKEN_NAME,
} from 'twenty-shared/application';
import { isDefined } from 'twenty-shared/utils';
import { Not, Repository } from 'typeorm';
import { v4 } from 'uuid';
import {
@@ -16,6 +18,7 @@ import {
import { ApplicationLogsService } from 'src/engine/core-modules/application-logs/application-logs.service';
import { parseApplicationLogLines } from 'src/engine/core-modules/application-logs/utils/parse-application-log-lines';
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
import type { FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
@@ -69,6 +72,8 @@ export class LogicFunctionExecutorService {
private readonly auditService: AuditService,
private readonly applicationLogsService: ApplicationLogsService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ApplicationRegistrationVariableEntity)
private readonly applicationRegistrationVariableRepository: Repository<ApplicationRegistrationVariableEntity>,
) {}
async execute({
@@ -219,15 +224,66 @@ export class LogicFunctionExecutorService {
const baseUrl = cleanServerUrl(this.twentyConfigService.get('SERVER_URL'));
const serverVariables = await this.buildServerVariableEnvMap(
flatApplication.applicationRegistrationId,
);
const workspaceVariables = buildEnvVar(
flatApplicationVariables,
this.secretEncryptionService,
);
return {
[DEFAULT_API_URL_NAME]: baseUrl ?? '',
[DEFAULT_APP_ACCESS_TOKEN_NAME]: applicationAccessToken.token,
[DEFAULT_API_KEY_NAME]: applicationAccessToken.token,
APPLICATION_ID: flatApplication.id,
...buildEnvVar(flatApplicationVariables, this.secretEncryptionService),
// Server variables first, workspace variables override. Workspace-level
// values let a specific tenant customize a server default.
...serverVariables,
...workspaceVariables,
};
}
// Resolves encrypted server-level variables (ApplicationRegistrationVariable)
// for the application's registration. Returns an empty object when the
// application isn't linked to a registration (legacy LOCAL apps).
//
// Runs on every logic function execution — the query is indexed on
// applicationRegistrationId and filters unfilled rows server-side. Most
// apps have 0-3 server variables so the round-trip is cheap, but if this
// becomes a hot path, move to a WorkspaceCacheProvider mirroring
// WorkspaceApplicationVariableMapCacheService.
private async buildServerVariableEnvMap(
applicationRegistrationId: string | null,
): Promise<Record<string, string>> {
if (!isDefined(applicationRegistrationId)) {
return {};
}
const serverVariables =
await this.applicationRegistrationVariableRepository.find({
where: {
applicationRegistrationId,
encryptedValue: Not(''),
},
});
const envMap: Record<string, string> = {};
// ApplicationRegistrationVariable.encryptedValue is always written
// encrypted (ApplicationRegistrationVariableService.createVariable and
// .updateVariable call encrypt unconditionally), independent of
// `isSecret`. `isSecret` is display metadata — the storage contract is
// not conditional, so decryption isn't either.
for (const variable of serverVariables) {
envMap[variable.key] = this.secretEncryptionService.decrypt(
variable.encryptedValue,
);
}
return envMap;
}
private async handleExecutionResult({
result,
flatApplication,
@@ -17,10 +17,8 @@ import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/sen
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
import { NavigateAppTool } from 'src/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool';
import { SearchHelpCenterTool } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool';
import { WebSearchTool } from 'src/engine/core-modules/tool/tools/web-search-tool/web-search-tool';
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import { WebSearchService } from 'src/engine/core-modules/web-search/web-search.service';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
@Injectable()
@@ -36,9 +34,7 @@ export class ActionToolProvider implements ToolProvider {
private readonly searchHelpCenterTool: SearchHelpCenterTool,
private readonly codeInterpreterTool: CodeInterpreterTool,
private readonly navigateAppTool: NavigateAppTool,
private readonly webSearchTool: WebSearchTool,
private readonly codeInterpreterService: CodeInterpreterService,
private readonly webSearchService: WebSearchService,
private readonly permissionsService: PermissionsService,
) {
this.toolMap = new Map<string, Tool>([
@@ -48,7 +44,6 @@ export class ActionToolProvider implements ToolProvider {
['search_help_center', this.searchHelpCenterTool],
['code_interpreter', this.codeInterpreterTool],
['navigate_app', this.navigateAppTool],
['exa_web_search', this.webSearchTool],
]);
}
@@ -128,16 +123,6 @@ export class ActionToolProvider implements ToolProvider {
);
}
if (this.webSearchService.isEnabled()) {
descriptors.push(
this.buildDescriptor(
'exa_web_search',
this.webSearchTool,
includeSchemas,
),
);
}
return descriptors;
}
@@ -94,7 +94,7 @@ export class LogicFunctionToolProvider implements ToolProvider {
}
private buildLogicFunctionToolName(functionName: string): string {
return `logic_function_${functionName
return `app_${functionName
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '')}`;
@@ -14,7 +14,6 @@ import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/sen
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
import { NavigateAppTool } from 'src/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool';
import { SearchHelpCenterTool } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool';
import { WebSearchTool } from 'src/engine/core-modules/tool/tools/web-search-tool/web-search-tool';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module';
@@ -46,7 +45,6 @@ import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbou
SearchHelpCenterTool,
CodeInterpreterTool,
NavigateAppTool,
WebSearchTool,
],
exports: [
HttpTool,
@@ -56,7 +54,6 @@ import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbou
SearchHelpCenterTool,
CodeInterpreterTool,
NavigateAppTool,
WebSearchTool,
],
})
export class ToolModule {}
@@ -1,5 +0,0 @@
import { type z } from 'zod';
import { type WebSearchInputZodSchema } from 'src/engine/core-modules/tool/tools/web-search-tool/web-search-tool.schema';
export type WebSearchInput = z.infer<typeof WebSearchInputZodSchema>;
@@ -1,29 +0,0 @@
import { z } from 'zod';
import { WEB_SEARCH_CATEGORIES } from 'src/engine/core-modules/web-search/constants/web-search-categories.const';
export const WEB_SEARCH_DEFAULT_NUM_RESULTS = 10;
export const WEB_SEARCH_MAX_NUM_RESULTS = 30;
export const WebSearchInputZodSchema = z.object({
query: z
.string()
.describe(
'The search query to look up on the web. Be specific and include relevant keywords for better results.',
),
category: z
.enum(WEB_SEARCH_CATEGORIES)
.optional()
.describe(
'Optional content category to focus the search. Use "company" for business/organization info, "people" for person profiles, "news" for recent articles, "research paper" for academic content.',
),
numResults: z
.number()
.int()
.min(1)
.max(WEB_SEARCH_MAX_NUM_RESULTS)
.optional()
.describe(
`Number of search results to return. Defaults to ${WEB_SEARCH_DEFAULT_NUM_RESULTS}, max ${WEB_SEARCH_MAX_NUM_RESULTS}. Use more results when you need comprehensive coverage.`,
),
});
@@ -1,48 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type WebSearchInput } from 'src/engine/core-modules/tool/tools/web-search-tool/web-search-input.type';
import { WebSearchInputZodSchema } from 'src/engine/core-modules/tool/tools/web-search-tool/web-search-tool.schema';
import { type ToolExecutionContext } from 'src/engine/core-modules/tool/types/tool-execution-context.type';
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import { WebSearchService } from 'src/engine/core-modules/web-search/web-search.service';
@Injectable()
export class WebSearchTool implements Tool {
description =
'Structured web search powered by Exa. Returns entity-aware results with category filtering (companies, people, research papers, news, and other content types). Prefer this when the query benefits from structured data or a specific category. For general real-time web browsing, prefer the native `web_search` tool when it is available.';
inputSchema = WebSearchInputZodSchema;
constructor(private readonly webSearchService: WebSearchService) {}
async execute(
parameters: ToolInput,
context: ToolExecutionContext,
): Promise<ToolOutput> {
const { query, category, numResults } = parameters as WebSearchInput;
try {
const results = await this.webSearchService.search(
query,
{ category, numResults },
{
workspaceId: context.workspaceId,
userWorkspaceId: context.userWorkspaceId,
},
);
return {
success: true,
message: `Found ${results.length} results for "${query}"${category ? ` (category: ${category})` : ''}`,
result: results,
};
} catch (error) {
return {
success: false,
message: `Web search failed for "${query}"`,
error: error instanceof Error ? error.message : 'Web search failed',
};
}
}
}
@@ -42,7 +42,6 @@ import {
ConfigVariableException,
ConfigVariableExceptionCode,
} from 'src/engine/core-modules/twenty-config/twenty-config.exception';
import { WebSearchDriverType } from 'src/engine/core-modules/web-search/web-search.interface';
import { type AiModelPreferences } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-preferences.type';
import { type AiProvidersConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-providers-config.type';
import { loadDefaultModelPreferences } from 'src/engine/metadata-modules/ai/ai-models/utils/load-default-model-preferences.util';
@@ -675,26 +674,6 @@ export class ConfigVariables {
@CastToPositiveNumber()
CODE_INTERPRETER_TIMEOUT_MS = 300_000;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.LLM,
description:
'Web search driver type - EXA for Exa search, DISABLED to turn off',
type: ConfigVariableType.STRING,
options: Object.values(WebSearchDriverType),
})
@IsOptional()
@CastToUpperSnakeCase()
WEB_SEARCH_DRIVER: WebSearchDriverType = WebSearchDriverType.DISABLED;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.LLM,
description: 'Exa API key for web search',
type: ConfigVariableType.STRING,
isSensitive: true,
})
@ValidateIf((env) => env.WEB_SEARCH_DRIVER === WebSearchDriverType.EXA)
EXA_API_KEY?: string;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.ANALYTICS_CONFIG,
description: 'Enable or disable analytics for telemetry',
@@ -1,9 +0,0 @@
export const WEB_SEARCH_CATEGORIES = [
'company',
'research paper',
'news',
'pdf',
'personal site',
'financial report',
'people',
] as const;
@@ -1,19 +0,0 @@
import {
type WebSearchCostModel,
type WebSearchDriver,
} from 'src/engine/core-modules/web-search/drivers/interfaces/web-search-driver.interface';
import { type WebSearchResult } from 'src/engine/core-modules/web-search/types/web-search-result.type';
export class DisabledWebSearchDriver implements WebSearchDriver {
readonly costModel: WebSearchCostModel = {
baseResultCount: 0,
baseCostDollars: 0,
costPerAdditionalResultDollars: 0,
};
constructor(private readonly reason: string) {}
async search(): Promise<WebSearchResult[]> {
throw new Error(this.reason);
}
}
@@ -1,52 +0,0 @@
import Exa from 'exa-js';
import {
type WebSearchCostModel,
type WebSearchDriver,
} from 'src/engine/core-modules/web-search/drivers/interfaces/web-search-driver.interface';
import { type WebSearchOptions } from 'src/engine/core-modules/web-search/types/web-search-options.type';
import { type WebSearchResult } from 'src/engine/core-modules/web-search/types/web-search-result.type';
const DEFAULT_NUM_RESULTS = 10;
const MAX_HIGHLIGHT_CHARACTERS = 4000;
// Exa charges $7/1k requests for auto search type (up to 10 results)
// Additional results above 10 cost $1/1k = $0.001 each
const EXA_BASE_COST_DOLLARS = 0.007;
const EXA_COST_PER_ADDITIONAL_RESULT_DOLLARS = 0.001;
export class ExaDriver implements WebSearchDriver {
readonly costModel: WebSearchCostModel = {
baseResultCount: DEFAULT_NUM_RESULTS,
baseCostDollars: EXA_BASE_COST_DOLLARS,
costPerAdditionalResultDollars: EXA_COST_PER_ADDITIONAL_RESULT_DOLLARS,
};
private readonly client: Exa;
constructor(apiKey: string) {
this.client = new Exa(apiKey);
}
async search(
query: string,
options?: WebSearchOptions,
): Promise<WebSearchResult[]> {
const numResults = options?.numResults ?? DEFAULT_NUM_RESULTS;
const response = await this.client.search(query, {
type: 'auto',
numResults,
category: options?.category,
contents: {
highlights: { maxCharacters: MAX_HIGHLIGHT_CHARACTERS },
},
});
return response.results.map((result) => ({
title: result.title ?? '',
url: result.url,
snippet: result.highlights?.join('\n') ?? '',
}));
}
}
@@ -1,14 +0,0 @@
import { type WebSearchOptions } from 'src/engine/core-modules/web-search/types/web-search-options.type';
import { type WebSearchResult } from 'src/engine/core-modules/web-search/types/web-search-result.type';
export type WebSearchCostModel = {
baseResultCount: number;
baseCostDollars: number;
costPerAdditionalResultDollars: number;
};
export interface WebSearchDriver {
readonly costModel: WebSearchCostModel;
search(query: string, options?: WebSearchOptions): Promise<WebSearchResult[]>;
}
@@ -1,4 +0,0 @@
export type WebSearchBillingContext = {
workspaceId: string;
userWorkspaceId?: string;
};
@@ -1,3 +0,0 @@
import { type WEB_SEARCH_CATEGORIES } from 'src/engine/core-modules/web-search/constants/web-search-categories.const';
export type WebSearchCategory = (typeof WEB_SEARCH_CATEGORIES)[number];
@@ -1,6 +0,0 @@
import { type WebSearchCategory } from 'src/engine/core-modules/web-search/types/web-search-category.type';
export type WebSearchOptions = {
category?: WebSearchCategory;
numResults?: number;
};
@@ -1,5 +0,0 @@
export type WebSearchResult = {
title: string;
url: string;
snippet: string;
};
@@ -1,59 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type WebSearchDriver } from 'src/engine/core-modules/web-search/drivers/interfaces/web-search-driver.interface';
import { DisabledWebSearchDriver } from 'src/engine/core-modules/web-search/drivers/disabled.driver';
import { ExaDriver } from 'src/engine/core-modules/web-search/drivers/exa.driver';
import { WebSearchDriverType } from 'src/engine/core-modules/web-search/web-search.interface';
import { DriverFactoryBase } from 'src/engine/core-modules/twenty-config/dynamic-factory.base';
import { ConfigVariablesGroup } from 'src/engine/core-modules/twenty-config/enums/config-variables-group.enum';
import { ConfigGroupHashService } from 'src/engine/core-modules/twenty-config/services/config-group-hash.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class WebSearchDriverFactory extends DriverFactoryBase<WebSearchDriver> {
constructor(
twentyConfigService: TwentyConfigService,
configGroupHashService: ConfigGroupHashService,
) {
super(twentyConfigService, configGroupHashService);
}
protected buildConfigKey(): string {
const driverType = this.twentyConfigService.get('WEB_SEARCH_DRIVER');
if (driverType !== WebSearchDriverType.DISABLED) {
return `${driverType}|${this.configGroupHashService.computeHash(ConfigVariablesGroup.LLM)}`;
}
return driverType;
}
protected createDriver(): WebSearchDriver {
const driverType = this.twentyConfigService.get('WEB_SEARCH_DRIVER');
switch (driverType) {
case WebSearchDriverType.DISABLED:
return new DisabledWebSearchDriver(
'Web search is disabled. Set WEB_SEARCH_DRIVER to EXA and provide EXA_API_KEY to enable it.',
);
case WebSearchDriverType.EXA: {
const apiKey = this.twentyConfigService.get('EXA_API_KEY');
if (!apiKey) {
throw new Error(
'EXA_API_KEY is required when WEB_SEARCH_DRIVER is EXA',
);
}
return new ExaDriver(apiKey);
}
default:
throw new Error(
`Invalid web search driver type (${driverType}), check your .env file`,
);
}
}
}
@@ -1,4 +0,0 @@
export enum WebSearchDriverType {
EXA = 'EXA',
DISABLED = 'DISABLED',
}
@@ -1,17 +0,0 @@
import { type DynamicModule, Global } from '@nestjs/common';
import { WebSearchDriverFactory } from 'src/engine/core-modules/web-search/web-search-driver.factory';
import { WebSearchService } from 'src/engine/core-modules/web-search/web-search.service';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
@Global()
export class WebSearchModule {
static forRoot(): DynamicModule {
return {
module: WebSearchModule,
imports: [TwentyConfigModule],
providers: [WebSearchDriverFactory, WebSearchService],
exports: [WebSearchService],
};
}
}
@@ -1,95 +0,0 @@
import { Injectable } from '@nestjs/common';
import {
type WebSearchCostModel,
type WebSearchDriver,
} from 'src/engine/core-modules/web-search/drivers/interfaces/web-search-driver.interface';
import { type WebSearchBillingContext } from 'src/engine/core-modules/web-search/types/web-search-billing-context.type';
import { type WebSearchOptions } from 'src/engine/core-modules/web-search/types/web-search-options.type';
import { type WebSearchResult } from 'src/engine/core-modules/web-search/types/web-search-result.type';
import { WebSearchDriverFactory } from 'src/engine/core-modules/web-search/web-search-driver.factory';
import { WebSearchDriverType } from 'src/engine/core-modules/web-search/web-search.interface';
import { USAGE_RECORDED } from 'src/engine/core-modules/usage/constants/usage-recorded.constant';
import { UsageOperationType } from 'src/engine/core-modules/usage/enums/usage-operation-type.enum';
import { UsageResourceType } from 'src/engine/core-modules/usage/enums/usage-resource-type.enum';
import { UsageUnit } from 'src/engine/core-modules/usage/enums/usage-unit.enum';
import { type UsageEvent } from 'src/engine/core-modules/usage/types/usage-event.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { DOLLAR_TO_CREDIT_MULTIPLIER } from 'src/engine/metadata-modules/ai/ai-billing/constants/dollar-to-credit-multiplier';
@Injectable()
export class WebSearchService {
constructor(
private readonly webSearchDriverFactory: WebSearchDriverFactory,
private readonly twentyConfigService: TwentyConfigService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
) {}
isEnabled(): boolean {
return (
this.twentyConfigService.get('WEB_SEARCH_DRIVER') !==
WebSearchDriverType.DISABLED
);
}
async search(
query: string,
options?: WebSearchOptions,
billingContext?: WebSearchBillingContext,
): Promise<WebSearchResult[]> {
const driver = this.webSearchDriverFactory.getCurrentDriver();
const results = await driver.search(query, options);
if (billingContext) {
this.emitUsageEvent(driver, results.length, billingContext);
}
return results;
}
static computeQueryCostDollars(
costModel: WebSearchCostModel,
numResults: number,
): number {
const additionalResults = Math.max(
0,
numResults - costModel.baseResultCount,
);
return (
costModel.baseCostDollars +
additionalResults * costModel.costPerAdditionalResultDollars
);
}
private emitUsageEvent(
driver: WebSearchDriver,
numResults: number,
billingContext: WebSearchBillingContext,
): void {
const costDollars = WebSearchService.computeQueryCostDollars(
driver.costModel,
numResults,
);
const creditsUsedMicro = Math.round(
costDollars * DOLLAR_TO_CREDIT_MULTIPLIER,
);
this.workspaceEventEmitter.emitCustomBatchEvent<UsageEvent>(
USAGE_RECORDED,
[
{
resourceType: UsageResourceType.AI,
operationType: UsageOperationType.WEB_SEARCH,
creditsUsedMicro,
quantity: 1,
unit: UsageUnit.INVOCATION,
resourceContext: this.twentyConfigService.get('WEB_SEARCH_DRIVER'),
userWorkspaceId: billingContext.userWorkspaceId ?? null,
},
],
billingContext.workspaceId,
);
}
}
@@ -38,6 +38,7 @@ import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { PreInstalledAppsService } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
@@ -126,6 +127,7 @@ describe('WorkspaceService', () => {
FileCorePictureService,
AiModelRegistryService,
ApplicationService,
PreInstalledAppsService,
PrefillLogicFunctionService,
WorkspaceMigrationValidateBuildAndRunService,
UpgradeMigrationService,
@@ -13,6 +13,7 @@ import { DataSource, QueryRunner, Repository } from 'typeorm';
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { PreInstalledAppsService } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.service';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
@@ -119,6 +120,7 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly prefillLogicFunctionService: PrefillLogicFunctionService,
private readonly applicationService: ApplicationService,
private readonly preInstalledAppsService: PreInstalledAppsService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly subdomainManagerService: SubdomainManagerService,
@@ -819,6 +821,16 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
);
this.exceptionHandlerService.captureExceptions([error as Error]);
}
try {
await this.preInstalledAppsService.installOnWorkspace(workspaceId);
} catch (error) {
this.logger.error(
`Non-critical: failed to install pre-installed apps for workspace ${workspaceId}`,
error,
);
this.exceptionHandlerService.captureExceptions([error as Error]);
}
}
async findOneWorkspaceById(id: string) {
@@ -6,6 +6,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { PreInstalledAppsModule } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
@@ -82,6 +83,7 @@ import { StandardObjectsPrefillModule } from 'src/engine/workspace-manager/stand
ViewModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
ApplicationModule,
PreInstalledAppsModule,
EnterpriseModule,
StandardObjectsPrefillModule,
WorkspaceMigrationModule,
@@ -139,10 +139,13 @@ export class ChatExecutionService {
`Built tool catalog with ${toolCatalog.length} tools, ${skillCatalog.length} skills available`,
);
// Preload Exa when the workspace has it enabled; ActionToolProvider
// only emits the exa_web_search descriptor when isEnabled() is true,
// so getToolsByName silently skips it otherwise.
const toolNamesToPreload = [...COMMON_PRELOAD_TOOLS, 'exa_web_search'];
// Preload the Exa app tool (shipped as the `twenty-exa` npm package) so chat
// has structured web search ready without discovery. getToolsByName
// silently skips the entry when the workspace doesn't have the Exa app
// installed (admin hasn't registered it + flipped `isPreInstalled`).
// TODO(app-preloading): move this list into the app manifest so any
// app can declare `preloadedInChat: true` instead of hardcoding here.
const toolNamesToPreload = [...COMMON_PRELOAD_TOOLS, 'app_exa_web_search'];
const preloadedTools = await this.toolRegistry.getToolsByName(
toolNamesToPreload,
@@ -167,8 +170,8 @@ export class ChatExecutionService {
);
// Native web_search is returned when the resolved model's SDK provider
// exposes it (Anthropic, OpenAI). Coexists with exa_web_search when both
// are available — the model picks based on tool descriptions.
// exposes it (Anthropic, OpenAI). Coexists with app_exa_web_search when
// both are available — the model picks based on tool descriptions.
const { tools: nativeSearchTools, callableToolNames: searchToolNames } =
this.getNativeWebSearchTools(registeredModel);
@@ -13,4 +13,6 @@ export type InputJsonSchema = {
properties?: Record<string, InputJsonSchema>;
required?: string[];
additionalProperties?: boolean | InputJsonSchema;
minimum?: number;
maximum?: number;
};