twenty-sdk 2.56.0 → 2.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,8 +6,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Changed
10
+
11
+ - **`INBOX_ASSISTANT_MENTION` now writes a canonical assistant reference instead of plain `@Ada` text.** The inbox keeps the saved label and assistant identity when rendering the message. Existing plain text is unchanged. This does not change assistant participation or reply selection for application messages.
12
+
9
13
  ### Breaking Changes
10
14
 
15
+ - **`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` no longer includes `workflow` or `workflowAutomatedTrigger`.** Workflows live in Twenty's core workflow store, and Twenty 2.58 drops these legacy workspace objects from every workspace, along with any relation fields that point at them. Remove references to both identifiers, including relation fields that target them. To ship a workflow with your app, declare it with `defineWorkflow()`.
16
+
11
17
  - **`MetadataApiClient.uploadFile` in `twenty-client-sdk` takes one object and no longer accepts a content type.** The file now goes straight to Twenty's file storage (the API issues an upload target, the client sends the bytes there, then the API confirms the upload) instead of being streamed through the GraphQL API, and the MIME type is detected from the bytes on completion. Update every call:
12
18
 
13
19
  ```diff
@@ -32,6 +38,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
32
38
 
33
39
  ### Added
34
40
 
41
+ - **`archiveInboxThread({ threadId, workspaceMemberIds })` archives a conversation your application wrote for the members you name**, as if each had archived it from their inbox. Use it when the work behind a message got done elsewhere, such as an email sent from Gmail. A new message in the conversation brings it back. Leave out `workspaceMemberIds` to mark the conversation done in its channel instead.
42
+ - **Message attachments in `twenty-sdk/logic-function`.** `uploadMessageAttachment({ filename, content })` uploads a file for a message and returns `{ fileId, label }`, which `ingestMessages` takes in a message's `attachments`. Twenty stores each file as an attachment record on the message. Attachments are added only while the message has none, so a later delivery can add them but never replaces them, and each `IngestedMessage` now reports `hasAttachments` so a redelivery can skip downloading them again. An uploaded file that no message ends up holding is kept, so upload only for messages without attachments. Uploading needs the `UPLOAD_FILE` permission flag on the app's role. `ingestMessages` itself now reads `hasAttachments`, so every call needs a Twenty server with message attachments, not only uploads.
35
43
  - **`sendInboxMessage` in `twenty-sdk/front-component`.** A front component can post in members' inbox without a logic function in between. The message is still from your app; the call carries the component's token, so the member looking at the page needs the AI permission too, and an expired token is refreshed once before the call is retried. Open the conversation with `navigate`:
36
44
 
37
45
  ```tsx
@@ -93,16 +101,32 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
93
101
  const objectMetadata = useSelectedObjectMetadata();
94
102
  ```
95
103
 
104
+ - **`eventTriggerSettings` for logic functions and `EVENT` triggers for agents.** With `on: 'recordEvent'`, a trigger names an object, an action and `watchedFieldUniversalIdentifiers` instead of an event name and field names, plus an optional `condition`: a rule expression the record must meet, where `previous.<field>` reads it before an update. The build fails on an unknown object or field, or a condition reading a field the object does not have. These settings need a Twenty server that knows them; to deploy to an older server, keep `databaseEventTriggerSettings`.
105
+
106
+ ```ts
107
+ eventTriggerSettings: {
108
+ on: 'recordEvent',
109
+ objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
110
+ action: 'updated',
111
+ watchedFieldUniversalIdentifiers: [OPPORTUNITY.fields.stage.universalIdentifier],
112
+ condition: "stage == 'CUSTOMER' and previous.stage != 'CUSTOMER'",
113
+ },
114
+ ```
115
+
96
116
  - **`SystemPermissionFlag.USER_APPS` and `SystemPermissionFlag.WORKSPACE_APPS` in `twenty-sdk/define`.** The SDK names of the User Apps and Workspace Apps permission flags, with the same universal identifiers as `CONNECTED_ACCOUNTS` and `APPLICATIONS`. They are left out of `Object.keys`, `Object.values` and `Object.entries`, which list the same 26 names as before, so code that enumerates or filters the flags behaves as before. Only the SDK names change: the API keeps reporting these flags as `CONNECTED_ACCOUNTS` and `APPLICATIONS`, for example in `currentUserWorkspace.permissionFlags` and in command menu expressions.
97
117
 
98
118
  ### Deprecated
99
119
 
120
+ - **`databaseEventTriggerSettings` on logic functions and `DATABASE_EVENT` triggers on agents.** Use `eventTriggerSettings` and `EVENT` triggers with `on: 'recordEvent'` instead. The old forms keep working for one more release.
121
+
100
122
  - **`enqueueJob` in `twenty-sdk/logic-function`.** Use `enqueueJobs` with a one-element `payloads` list instead. `enqueueJob` keeps working for now and will be removed in a future major version.
101
123
 
102
124
  - **`SystemPermissionFlag.CONNECTED_ACCOUNTS` and `SystemPermissionFlag.APPLICATIONS` in `twenty-sdk/define`.** Use `USER_APPS` and `WORKSPACE_APPS` instead. The old names keep working and stay the ones `Object.keys` and `Object.entries` return. In logic functions and front components the new names resolve only when bundled with the `twenty` CLI 2.54 or later.
103
125
 
104
126
  ### Changed
105
127
 
128
+ - **A member mentioned in an inbox message is now notified.** `sendInboxMessage` and the Send to Inbox workflow step read the mentions `inboxMention` writes in `text`: each mentioned member gets the conversation in their inbox, unread, with a mention to resolve, without being listed in `workspaceMemberIds`, and the member who owns the conversation can be mentioned too. A mentioned member who cannot have the conversation is skipped, and the message is still written. Mentions are read when the message is written, so once it is, sending it again with the same keys notifies no one; mentions in code or with escaped brackets never notify.
129
+ - **`uploadMessageAttachment` sends `content` without copying it.** The bytes go to storage as they are, so uploading a file no longer holds a second copy of it in memory. `MetadataApiClient.uploadFile` in `twenty-client-sdk` now takes any `Uint8Array` as `fileBuffer`; a `Buffer` still works.
106
130
  - **`sendInboxMessage` runs like the default API clients.** It now sends through `MetadataApiClient`, so in a logic function it acts as the person who triggered the run, who then needs the AI permission, and as your application only when nobody did; it used to always use the application's access. A GraphQL error now rejects with the server's message instead of `sendInboxMessage() failed: ...`. Its input and result are unchanged.
107
131
  - **`twenty app deploy` sends the tarball straight to file storage.** The CLI reserves an upload with `createFileUpload`, PUTs the archive to storage and registers it with `completeAppTarballUpload`, so the tarball no longer transits through the API process. Deploying to a server that predates these mutations fails instead of falling back to the multipart `uploadAppTarball` mutation.
108
132
  - **`twenty-client-sdk` should now be a dev dependency too.** Although app code imports it (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), Twenty provides it at runtime — logic functions get it from a generated SDK layer and front components resolve it from server-served modules — so the installed copy is only needed for typechecking and the deploy-time build. Newly scaffolded apps now place it under `devDependencies`. Moving it is recommended (not required: the server already strips it from the deployed runtime), and keeps the installed app leaner:
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`TWENTY_API_URL`,t=[`workspace-suspended`,`no-subscription`,`no-credits`],n=()=>process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN??process.env.TWENTY_APP_ACCESS_TOKEN,r=5e3,i=async({creditsUsedMicro:t,quantity:i=1,operation:a,operationType:o,resourceContext:s,userWorkspaceId:c})=>{let l=process.env[e],u=n();if(!(!l||!u))try{let e=await fetch(`${l.replace(/\/$/,``)}/app/billing/charge`,{method:`POST`,headers:{Authorization:`Bearer ${u}`,"Content-Type":`application/json`},body:JSON.stringify({creditsUsedMicro:t,quantity:i,operation:a,operationType:o,resourceContext:s,userWorkspaceId:c}),signal:AbortSignal.timeout(r)});if(!e.ok){let t=await e.text().catch(()=>``);console.error(`chargeCredits: ${e.status} ${e.statusText}: ${t}`)}}catch(e){console.error(`chargeCredits: ${e instanceof Error?e.message:String(e)}`)}},a=5e3,o=()=>({hasAvailableCredits:!0}),s=e=>{if(typeof e!=`object`||!e)return null;let{hasAvailableCredits:n,reason:r}=e;return n===!0?{hasAvailableCredits:!0}:n===!1&&t.includes(r)?{hasAvailableCredits:!1,reason:r}:null},c=async()=>{let t=process.env[e],r=n();if(!t||!r)return o();try{let e=await fetch(`${t.replace(/\/$/,``)}/app/billing/credits`,{method:`GET`,headers:{Authorization:`Bearer ${r}`,Accept:`application/json`},signal:AbortSignal.timeout(a)});if(!e.ok)return console.error(`getCreditAvailability: ${e.status} ${e.statusText}`),o();let n=s(await e.json());return n===null?(console.error(`getCreditAvailability: unexpected response shape`),o()):n}catch(e){return console.error(`getCreditAvailability: ${e instanceof Error?e.message:String(e)}`),o()}};exports.chargeCredits=i,exports.getCreditAvailability=c;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@sniptt/guards");var t=`TWENTY_API_URL`,n=`TWENTY_INTERNAL_API_URL`,r=[`workspace-suspended`,`no-subscription`,`no-credits`],i=r=>{let i=r[n];return(0,e.isNonEmptyString)(i)?i:r[t]},a=()=>process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN??process.env.TWENTY_APP_ACCESS_TOKEN,o=5e3,s=async({creditsUsedMicro:e,quantity:t=1,operation:n,operationType:r,resourceContext:s,userWorkspaceId:c})=>{let l=i(process.env),u=a();if(!(!l||!u))try{let i=await fetch(`${l.replace(/\/$/,``)}/app/billing/charge`,{method:`POST`,headers:{Authorization:`Bearer ${u}`,"Content-Type":`application/json`},body:JSON.stringify({creditsUsedMicro:e,quantity:t,operation:n,operationType:r,resourceContext:s,userWorkspaceId:c}),signal:AbortSignal.timeout(o)});if(!i.ok){let e=await i.text().catch(()=>``);console.error(`chargeCredits: ${i.status} ${i.statusText}: ${e}`)}}catch(e){console.error(`chargeCredits: ${e instanceof Error?e.message:String(e)}`)}},c=5e3,l=()=>({hasAvailableCredits:!0}),u=e=>{if(typeof e!=`object`||!e)return null;let{hasAvailableCredits:t,reason:n}=e;return t===!0?{hasAvailableCredits:!0}:t===!1&&r.includes(n)?{hasAvailableCredits:!1,reason:n}:null},d=async()=>{let e=i(process.env),t=a();if(!e||!t)return l();try{let n=await fetch(`${e.replace(/\/$/,``)}/app/billing/credits`,{method:`GET`,headers:{Authorization:`Bearer ${t}`,Accept:`application/json`},signal:AbortSignal.timeout(c)});if(!n.ok)return console.error(`getCreditAvailability: ${n.status} ${n.statusText}`),l();let r=u(await n.json());return r===null?(console.error(`getCreditAvailability: unexpected response shape`),l()):r}catch(e){return console.error(`getCreditAvailability: ${e instanceof Error?e.message:String(e)}`),l()}};exports.chargeCredits=s,exports.getCreditAvailability=d;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../twenty-shared/dist/application-runtime.mjs","../../src/sdk/utils/get-application-access-token.ts","../../src/sdk/billing/charge-credits.ts","../../src/sdk/billing/get-credit-availability.ts"],"sourcesContent":["//#region src/application-runtime/constants/ConnectionScopeByLegacyVisibility.ts\nvar e = {\n\tuser: \"USER\",\n\tworkspace: \"WORKSPACE\"\n}, t = \"TWENTY_API_KEY\", n = \"TWENTY_API_URL\", r = \"TWENTY_APP_ACCESS_TOKEN\", i = \"TWENTY_APP_APPLICATION_ACCESS_TOKEN\", a = \"TWENTY_FUNCTIONS_URL\", o = \"TWENTY_SERVER_ROUTES_URL\", s = {\n\tUSER: \"user\",\n\tWORKSPACE: \"workspace\"\n}, c = [\n\t\"workspace-suspended\",\n\t\"no-subscription\",\n\t\"no-credits\"\n];\n//#endregion\nexport { e as CONNECTION_SCOPE_BY_LEGACY_VISIBILITY, c as CREDIT_UNAVAILABLE_REASONS, t as DEFAULT_API_KEY_NAME, n as DEFAULT_API_URL_NAME, r as DEFAULT_APP_ACCESS_TOKEN_NAME, i as DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME, a as DEFAULT_FUNCTIONS_URL_NAME, o as DEFAULT_SERVER_ROUTES_URL_NAME, s as LEGACY_CONNECTION_VISIBILITY_BY_SCOPE };\n","import {\n DEFAULT_APP_ACCESS_TOKEN_NAME,\n DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME,\n} from 'twenty-shared/application-runtime';\n\n// These reach the application's own resources, so prefer the application token when the runtime injects one\nexport const getApplicationAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];\n\n// acts as whoever triggered the run, like the client SDK's default runAs\nexport const getCallerAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME];\n","import { type UsageOperationTypeValue } from 'twenty-shared/application';\nimport { DEFAULT_API_URL_NAME } from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst BILLING_CHARGE_TIMEOUT_MS = 5_000;\n\nexport type ChargeCreditsParams = {\n creditsUsedMicro: number;\n quantity?: number;\n // Who the spend belongs to when no user triggered the run (webhook, cron); ignored otherwise\n userWorkspaceId?: string;\n} & (\n | {\n // An operation declared in the manifest's `billing.operations`; the platform resolves its category and label\n operation: string;\n operationType?: never;\n resourceContext?: never;\n }\n | {\n // Platform billing category; omitted, the charge lands in the dedicated APP_OPERATION category\n operationType?: UsageOperationTypeValue;\n operation?: never;\n resourceContext?: string;\n }\n);\n\n// No-ops without an API URL or access token, and never throws: a billing error must not fail a tool\nexport const chargeCredits = async ({\n creditsUsedMicro,\n quantity = 1,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n}: ChargeCreditsParams): Promise<void> => {\n const apiUrl = process.env[DEFAULT_API_URL_NAME];\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return;\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/charge`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n creditsUsedMicro,\n quantity,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n }),\n signal: AbortSignal.timeout(BILLING_CHARGE_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n\n console.error(\n `chargeCredits: ${response.status} ${response.statusText}: ${body}`,\n );\n }\n } catch (error) {\n console.error(\n `chargeCredits: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n};\n","import {\n CREDIT_UNAVAILABLE_REASONS,\n type CreditAvailability,\n type CreditUnavailableReason,\n DEFAULT_API_URL_NAME,\n} from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst CREDIT_AVAILABILITY_TIMEOUT_MS = 5_000;\n\n// A fresh object per call, since app code could mutate a shared constant\nconst available = (): CreditAvailability => ({ hasAvailableCredits: true });\n\nconst parseCreditAvailability = (body: unknown): CreditAvailability | null => {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n\n const { hasAvailableCredits, reason } = body as {\n hasAvailableCredits?: unknown;\n reason?: unknown;\n };\n\n if (hasAvailableCredits === true) {\n return { hasAvailableCredits: true };\n }\n\n if (\n hasAvailableCredits === false &&\n CREDIT_UNAVAILABLE_REASONS.includes(reason as CreditUnavailableReason)\n ) {\n return {\n hasAvailableCredits: false,\n reason: reason as CreditUnavailableReason,\n };\n }\n\n return null;\n};\n\n// Fails open on timeouts or bad responses: a hint before expensive work, never an authorization decision\nexport const getCreditAvailability = async (): Promise<CreditAvailability> => {\n const apiUrl = process.env[DEFAULT_API_URL_NAME];\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return available();\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/credits`,\n {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n signal: AbortSignal.timeout(CREDIT_AVAILABILITY_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n console.error(\n `getCreditAvailability: ${response.status} ${response.statusText}`,\n );\n\n return available();\n }\n\n const creditAvailability = parseCreditAvailability(await response.json());\n\n if (creditAvailability === null) {\n console.error('getCreditAvailability: unexpected response shape');\n\n return available();\n }\n\n return creditAvailability;\n } catch (error) {\n console.error(\n `getCreditAvailability: ${error instanceof Error ? error.message : String(error)}`,\n );\n\n return available();\n }\n};\n"],"mappings":"uEAIyB,EAAI,iBAG1B,EAAI,CACN,sBACA,kBACA,YACD,ECLa,MACX,QAAQ,IAAA,qCACR,QAAQ,IAAA,wBCHJ,EAA4B,IAuBrB,EAAgB,MAAO,CAClC,mBACA,WAAW,EACX,YACA,gBACA,kBACA,qBACwC,CACxC,IAAM,EAAS,QAAQ,IAAI,GACrB,EAAQ,EAA0B,EAEpC,MAAC,GAAU,CAAC,GAIhB,GAAI,CACF,IAAM,EAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,MAAO,EAAE,EAAE,qBAC7B,CACE,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,IACzB,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CACnB,mBACA,WACA,YACA,gBACA,kBACA,iBACF,CAAC,EACD,OAAQ,YAAY,QAAQ,CAAyB,CACvD,CACF,EAEA,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAO,MAAM,EAAS,KAAK,CAAC,CAAC,UAAY,EAAE,EAEjD,QAAQ,MACN,kBAAkB,EAAS,OAAO,GAAG,EAAS,WAAW,IAAI,GAC/D,CACF,CACF,OAAS,EAAO,CACd,QAAQ,MACN,kBAAkB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GACzE,CACF,CACF,ECnEM,EAAiC,IAGjC,OAAuC,CAAE,oBAAqB,EAAK,GAEnE,EAA2B,GAA6C,CAC5E,GAAI,OAAO,GAAS,WAAY,EAC9B,OAAO,KAGT,GAAM,CAAE,sBAAqB,UAAW,EAmBxC,OAdI,IAAwB,GACnB,CAAE,oBAAqB,EAAK,EAInC,IAAwB,IACxB,EAA2B,SAAS,CAAiC,EAE9D,CACL,oBAAqB,GACb,QACV,EAGK,IACT,EAGa,EAAwB,SAAyC,CAC5E,IAAM,EAAS,QAAQ,IAAI,GACrB,EAAQ,EAA0B,EAExC,GAAI,CAAC,GAAU,CAAC,EACd,OAAO,EAAU,EAGnB,GAAI,CACF,IAAM,EAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,MAAO,EAAE,EAAE,sBAC7B,CACE,OAAQ,MACR,QAAS,CACP,cAAe,UAAU,IACzB,OAAQ,kBACV,EACA,OAAQ,YAAY,QAAQ,CAA8B,CAC5D,CACF,EAEA,GAAI,CAAC,EAAS,GAKZ,OAJA,QAAQ,MACN,0BAA0B,EAAS,OAAO,GAAG,EAAS,YACxD,EAEO,EAAU,EAGnB,IAAM,EAAqB,EAAwB,MAAM,EAAS,KAAK,CAAC,EAQxE,OANI,IAAuB,MACzB,QAAQ,MAAM,kDAAkD,EAEzD,EAAU,GAGZ,CACT,OAAS,EAAO,CAKd,OAJA,QAAQ,MACN,0BAA0B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GACjF,EAEO,EAAU,CACnB,CACF"}
1
+ {"version":3,"file":"index.cjs","names":["e","e","e","e","t","e"],"sources":["../../../twenty-shared/dist/application-runtime/constants/DefaultApiUrlName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultAppAccessTokenName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultAppApplicationAccessTokenName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultInternalApiUrlName.mjs","../../../twenty-shared/dist/application-runtime/creditAvailabilityType.mjs","../../../twenty-shared/dist/application-runtime/utils/resolveApiUrlFromEnvironment.mjs","../../src/sdk/utils/get-application-access-token.ts","../../src/sdk/billing/charge-credits.ts","../../src/sdk/billing/get-credit-availability.ts"],"sourcesContent":["//#region src/application-runtime/constants/DefaultApiUrlName.ts\nvar e = \"TWENTY_API_URL\";\n//#endregion\nexport { e as DEFAULT_API_URL_NAME };\n","//#region src/application-runtime/constants/DefaultAppAccessTokenName.ts\nvar e = \"TWENTY_APP_ACCESS_TOKEN\";\n//#endregion\nexport { e as DEFAULT_APP_ACCESS_TOKEN_NAME };\n","//#region src/application-runtime/constants/DefaultAppApplicationAccessTokenName.ts\nvar e = \"TWENTY_APP_APPLICATION_ACCESS_TOKEN\";\n//#endregion\nexport { e as DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME };\n","//#region src/application-runtime/constants/DefaultInternalApiUrlName.ts\nvar e = \"TWENTY_INTERNAL_API_URL\";\n//#endregion\nexport { e as DEFAULT_INTERNAL_API_URL_NAME };\n","//#region src/application-runtime/creditAvailabilityType.ts\nvar e = [\n\t\"workspace-suspended\",\n\t\"no-subscription\",\n\t\"no-credits\"\n];\n//#endregion\nexport { e as CREDIT_UNAVAILABLE_REASONS };\n","import { DEFAULT_API_URL_NAME as e } from \"../constants/DefaultApiUrlName.mjs\";\nimport { DEFAULT_INTERNAL_API_URL_NAME as t } from \"../constants/DefaultInternalApiUrlName.mjs\";\nimport { isNonEmptyString as n } from \"@sniptt/guards\";\n//#region src/application-runtime/utils/resolveApiUrlFromEnvironment.ts\nvar r = (r) => {\n\tlet i = r[t];\n\treturn n(i) ? i : r[e];\n};\n//#endregion\nexport { r as resolveApiUrlFromEnvironment };\n","import {\n DEFAULT_APP_ACCESS_TOKEN_NAME,\n DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME,\n} from 'twenty-shared/application-runtime';\n\n// These reach the application's own resources, so prefer the application token when the runtime injects one\nexport const getApplicationAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];\n\n// acts as whoever triggered the run, like the client SDK's default runAs\nexport const getCallerAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME];\n","import { type UsageOperationTypeValue } from 'twenty-shared/application';\nimport { resolveApiUrlFromEnvironment } from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst BILLING_CHARGE_TIMEOUT_MS = 5_000;\n\nexport type ChargeCreditsParams = {\n creditsUsedMicro: number;\n quantity?: number;\n // Who the spend belongs to when no user triggered the run (webhook, cron); ignored otherwise\n userWorkspaceId?: string;\n} & (\n | {\n // An operation declared in the manifest's `billing.operations`; the platform resolves its category and label\n operation: string;\n operationType?: never;\n resourceContext?: never;\n }\n | {\n // Platform billing category; omitted, the charge lands in the dedicated APP_OPERATION category\n operationType?: UsageOperationTypeValue;\n operation?: never;\n resourceContext?: string;\n }\n);\n\n// No-ops without an API URL or access token, and never throws: a billing error must not fail a tool\nexport const chargeCredits = async ({\n creditsUsedMicro,\n quantity = 1,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n}: ChargeCreditsParams): Promise<void> => {\n const apiUrl = resolveApiUrlFromEnvironment(process.env);\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return;\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/charge`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n creditsUsedMicro,\n quantity,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n }),\n signal: AbortSignal.timeout(BILLING_CHARGE_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n\n console.error(\n `chargeCredits: ${response.status} ${response.statusText}: ${body}`,\n );\n }\n } catch (error) {\n console.error(\n `chargeCredits: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n};\n","import {\n CREDIT_UNAVAILABLE_REASONS,\n type CreditAvailability,\n type CreditUnavailableReason,\n resolveApiUrlFromEnvironment,\n} from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst CREDIT_AVAILABILITY_TIMEOUT_MS = 5_000;\n\n// A fresh object per call, since app code could mutate a shared constant\nconst available = (): CreditAvailability => ({ hasAvailableCredits: true });\n\nconst parseCreditAvailability = (body: unknown): CreditAvailability | null => {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n\n const { hasAvailableCredits, reason } = body as {\n hasAvailableCredits?: unknown;\n reason?: unknown;\n };\n\n if (hasAvailableCredits === true) {\n return { hasAvailableCredits: true };\n }\n\n if (\n hasAvailableCredits === false &&\n CREDIT_UNAVAILABLE_REASONS.includes(reason as CreditUnavailableReason)\n ) {\n return {\n hasAvailableCredits: false,\n reason: reason as CreditUnavailableReason,\n };\n }\n\n return null;\n};\n\n// Fails open on timeouts or bad responses: a hint before expensive work, never an authorization decision\nexport const getCreditAvailability = async (): Promise<CreditAvailability> => {\n const apiUrl = resolveApiUrlFromEnvironment(process.env);\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return available();\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/credits`,\n {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n signal: AbortSignal.timeout(CREDIT_AVAILABILITY_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n console.error(\n `getCreditAvailability: ${response.status} ${response.statusText}`,\n );\n\n return available();\n }\n\n const creditAvailability = parseCreditAvailability(await response.json());\n\n if (creditAvailability === null) {\n console.error('getCreditAvailability: unexpected response shape');\n\n return available();\n }\n\n return creditAvailability;\n } catch (error) {\n console.error(\n `getCreditAvailability: ${error instanceof Error ? error.message : String(error)}`,\n );\n\n return available();\n }\n};\n"],"mappings":"mGACA,IAAIA,EAAI,iBGAJG,EAAI,0BCAJ,EAAI,CACP,sBACA,kBACA,YACD,ECDI,EAAK,GAAM,CACd,IAAI,EAAI,EAAEC,GACV,OAAA,EAAA,EAAA,iBAAA,CAAS,CAAC,EAAI,EAAI,EAAEC,EACrB,ECDa,MACX,QAAQ,IAAA,qCACR,QAAQ,IAAA,wBCHJ,EAA4B,IAuBrB,EAAgB,MAAO,CAClC,mBACA,WAAW,EACX,YACA,gBACA,kBACA,qBACwC,CACxC,IAAM,EAAS,EAA6B,QAAQ,GAAG,EACjD,EAAQ,EAA0B,EAEpC,MAAC,GAAU,CAAC,GAIhB,GAAI,CACF,IAAM,EAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,MAAO,EAAE,EAAE,qBAC7B,CACE,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,IACzB,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CACnB,mBACA,WACA,YACA,gBACA,kBACA,iBACF,CAAC,EACD,OAAQ,YAAY,QAAQ,CAAyB,CACvD,CACF,EAEA,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAO,MAAM,EAAS,KAAK,CAAC,CAAC,UAAY,EAAE,EAEjD,QAAQ,MACN,kBAAkB,EAAS,OAAO,GAAG,EAAS,WAAW,IAAI,GAC/D,CACF,CACF,OAAS,EAAO,CACd,QAAQ,MACN,kBAAkB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GACzE,CACF,CACF,ECnEM,EAAiC,IAGjC,OAAuC,CAAE,oBAAqB,EAAK,GAEnE,EAA2B,GAA6C,CAC5E,GAAI,OAAO,GAAS,WAAY,EAC9B,OAAO,KAGT,GAAM,CAAE,sBAAqB,UAAW,EAmBxC,OAdI,IAAwB,GACnB,CAAE,oBAAqB,EAAK,EAInC,IAAwB,IACxB,EAA2B,SAAS,CAAiC,EAE9D,CACL,oBAAqB,GACb,QACV,EAGK,IACT,EAGa,EAAwB,SAAyC,CAC5E,IAAM,EAAS,EAA6B,QAAQ,GAAG,EACjD,EAAQ,EAA0B,EAExC,GAAI,CAAC,GAAU,CAAC,EACd,OAAO,EAAU,EAGnB,GAAI,CACF,IAAM,EAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,MAAO,EAAE,EAAE,sBAC7B,CACE,OAAQ,MACR,QAAS,CACP,cAAe,UAAU,IACzB,OAAQ,kBACV,EACA,OAAQ,YAAY,QAAQ,CAA8B,CAC5D,CACF,EAEA,GAAI,CAAC,EAAS,GAKZ,OAJA,QAAQ,MACN,0BAA0B,EAAS,OAAO,GAAG,EAAS,YACxD,EAEO,EAAU,EAGnB,IAAM,EAAqB,EAAwB,MAAM,EAAS,KAAK,CAAC,EAQxE,OANI,IAAuB,MACzB,QAAQ,MAAM,kDAAkD,EAEzD,EAAU,GAGZ,CACT,OAAS,EAAO,CAKd,OAJA,QAAQ,MACN,0BAA0B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GACjF,EAEO,EAAU,CACnB,CACF"}
@@ -1,61 +1,65 @@
1
- //#region ../twenty-shared/dist/application-runtime.mjs
2
- var e = "TWENTY_API_URL", t = [
1
+ import { isNonEmptyString as e } from "@sniptt/guards";
2
+ //#region ../twenty-shared/dist/application-runtime/constants/DefaultApiUrlName.mjs
3
+ var t = "TWENTY_API_URL", n = "TWENTY_INTERNAL_API_URL", r = [
3
4
  "workspace-suspended",
4
5
  "no-subscription",
5
6
  "no-credits"
6
- ], n = () => process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN ?? process.env.TWENTY_APP_ACCESS_TOKEN, r = 5e3, i = async ({ creditsUsedMicro: t, quantity: i = 1, operation: a, operationType: o, resourceContext: s, userWorkspaceId: c }) => {
7
- let l = process.env[e], u = n();
7
+ ], i = (r) => {
8
+ let i = r[n];
9
+ return e(i) ? i : r[t];
10
+ }, a = () => process.env.TWENTY_APP_APPLICATION_ACCESS_TOKEN ?? process.env.TWENTY_APP_ACCESS_TOKEN, o = 5e3, s = async ({ creditsUsedMicro: e, quantity: t = 1, operation: n, operationType: r, resourceContext: s, userWorkspaceId: c }) => {
11
+ let l = i(process.env), u = a();
8
12
  if (!(!l || !u)) try {
9
- let e = await fetch(`${l.replace(/\/$/, "")}/app/billing/charge`, {
13
+ let i = await fetch(`${l.replace(/\/$/, "")}/app/billing/charge`, {
10
14
  method: "POST",
11
15
  headers: {
12
16
  Authorization: `Bearer ${u}`,
13
17
  "Content-Type": "application/json"
14
18
  },
15
19
  body: JSON.stringify({
16
- creditsUsedMicro: t,
17
- quantity: i,
18
- operation: a,
19
- operationType: o,
20
+ creditsUsedMicro: e,
21
+ quantity: t,
22
+ operation: n,
23
+ operationType: r,
20
24
  resourceContext: s,
21
25
  userWorkspaceId: c
22
26
  }),
23
- signal: AbortSignal.timeout(r)
27
+ signal: AbortSignal.timeout(o)
24
28
  });
25
- if (!e.ok) {
26
- let t = await e.text().catch(() => "");
27
- console.error(`chargeCredits: ${e.status} ${e.statusText}: ${t}`);
29
+ if (!i.ok) {
30
+ let e = await i.text().catch(() => "");
31
+ console.error(`chargeCredits: ${i.status} ${i.statusText}: ${e}`);
28
32
  }
29
33
  } catch (e) {
30
34
  console.error(`chargeCredits: ${e instanceof Error ? e.message : String(e)}`);
31
35
  }
32
- }, a = 5e3, o = () => ({ hasAvailableCredits: !0 }), s = (e) => {
36
+ }, c = 5e3, l = () => ({ hasAvailableCredits: !0 }), u = (e) => {
33
37
  if (typeof e != "object" || !e) return null;
34
- let { hasAvailableCredits: n, reason: r } = e;
35
- return n === !0 ? { hasAvailableCredits: !0 } : n === !1 && t.includes(r) ? {
38
+ let { hasAvailableCredits: t, reason: n } = e;
39
+ return t === !0 ? { hasAvailableCredits: !0 } : t === !1 && r.includes(n) ? {
36
40
  hasAvailableCredits: !1,
37
- reason: r
41
+ reason: n
38
42
  } : null;
39
- }, c = async () => {
40
- let t = process.env[e], r = n();
41
- if (!t || !r) return o();
43
+ }, d = async () => {
44
+ let e = i(process.env), t = a();
45
+ if (!e || !t) return l();
42
46
  try {
43
- let e = await fetch(`${t.replace(/\/$/, "")}/app/billing/credits`, {
47
+ let n = await fetch(`${e.replace(/\/$/, "")}/app/billing/credits`, {
44
48
  method: "GET",
45
49
  headers: {
46
- Authorization: `Bearer ${r}`,
50
+ Authorization: `Bearer ${t}`,
47
51
  Accept: "application/json"
48
52
  },
49
- signal: AbortSignal.timeout(a)
53
+ signal: AbortSignal.timeout(c)
50
54
  });
51
- if (!e.ok) return console.error(`getCreditAvailability: ${e.status} ${e.statusText}`), o();
52
- let n = s(await e.json());
53
- return n === null ? (console.error("getCreditAvailability: unexpected response shape"), o()) : n;
55
+ if (!n.ok) return console.error(`getCreditAvailability: ${n.status} ${n.statusText}`), l();
56
+ let r = u(await n.json());
57
+ return r === null ? (console.error("getCreditAvailability: unexpected response shape"), l()) : r;
54
58
  } catch (e) {
55
- return console.error(`getCreditAvailability: ${e instanceof Error ? e.message : String(e)}`), o();
59
+ return console.error(`getCreditAvailability: ${e instanceof Error ? e.message : String(e)}`), l();
56
60
  }
57
61
  };
58
62
  //#endregion
59
- export { i as chargeCredits, c as getCreditAvailability };
63
+ export { s as chargeCredits, d as getCreditAvailability };
60
64
 
61
65
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../twenty-shared/dist/application-runtime.mjs","../../src/sdk/utils/get-application-access-token.ts","../../src/sdk/billing/charge-credits.ts","../../src/sdk/billing/get-credit-availability.ts"],"sourcesContent":["//#region src/application-runtime/constants/ConnectionScopeByLegacyVisibility.ts\nvar e = {\n\tuser: \"USER\",\n\tworkspace: \"WORKSPACE\"\n}, t = \"TWENTY_API_KEY\", n = \"TWENTY_API_URL\", r = \"TWENTY_APP_ACCESS_TOKEN\", i = \"TWENTY_APP_APPLICATION_ACCESS_TOKEN\", a = \"TWENTY_FUNCTIONS_URL\", o = \"TWENTY_SERVER_ROUTES_URL\", s = {\n\tUSER: \"user\",\n\tWORKSPACE: \"workspace\"\n}, c = [\n\t\"workspace-suspended\",\n\t\"no-subscription\",\n\t\"no-credits\"\n];\n//#endregion\nexport { e as CONNECTION_SCOPE_BY_LEGACY_VISIBILITY, c as CREDIT_UNAVAILABLE_REASONS, t as DEFAULT_API_KEY_NAME, n as DEFAULT_API_URL_NAME, r as DEFAULT_APP_ACCESS_TOKEN_NAME, i as DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME, a as DEFAULT_FUNCTIONS_URL_NAME, o as DEFAULT_SERVER_ROUTES_URL_NAME, s as LEGACY_CONNECTION_VISIBILITY_BY_SCOPE };\n","import {\n DEFAULT_APP_ACCESS_TOKEN_NAME,\n DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME,\n} from 'twenty-shared/application-runtime';\n\n// These reach the application's own resources, so prefer the application token when the runtime injects one\nexport const getApplicationAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];\n\n// acts as whoever triggered the run, like the client SDK's default runAs\nexport const getCallerAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME];\n","import { type UsageOperationTypeValue } from 'twenty-shared/application';\nimport { DEFAULT_API_URL_NAME } from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst BILLING_CHARGE_TIMEOUT_MS = 5_000;\n\nexport type ChargeCreditsParams = {\n creditsUsedMicro: number;\n quantity?: number;\n // Who the spend belongs to when no user triggered the run (webhook, cron); ignored otherwise\n userWorkspaceId?: string;\n} & (\n | {\n // An operation declared in the manifest's `billing.operations`; the platform resolves its category and label\n operation: string;\n operationType?: never;\n resourceContext?: never;\n }\n | {\n // Platform billing category; omitted, the charge lands in the dedicated APP_OPERATION category\n operationType?: UsageOperationTypeValue;\n operation?: never;\n resourceContext?: string;\n }\n);\n\n// No-ops without an API URL or access token, and never throws: a billing error must not fail a tool\nexport const chargeCredits = async ({\n creditsUsedMicro,\n quantity = 1,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n}: ChargeCreditsParams): Promise<void> => {\n const apiUrl = process.env[DEFAULT_API_URL_NAME];\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return;\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/charge`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n creditsUsedMicro,\n quantity,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n }),\n signal: AbortSignal.timeout(BILLING_CHARGE_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n\n console.error(\n `chargeCredits: ${response.status} ${response.statusText}: ${body}`,\n );\n }\n } catch (error) {\n console.error(\n `chargeCredits: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n};\n","import {\n CREDIT_UNAVAILABLE_REASONS,\n type CreditAvailability,\n type CreditUnavailableReason,\n DEFAULT_API_URL_NAME,\n} from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst CREDIT_AVAILABILITY_TIMEOUT_MS = 5_000;\n\n// A fresh object per call, since app code could mutate a shared constant\nconst available = (): CreditAvailability => ({ hasAvailableCredits: true });\n\nconst parseCreditAvailability = (body: unknown): CreditAvailability | null => {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n\n const { hasAvailableCredits, reason } = body as {\n hasAvailableCredits?: unknown;\n reason?: unknown;\n };\n\n if (hasAvailableCredits === true) {\n return { hasAvailableCredits: true };\n }\n\n if (\n hasAvailableCredits === false &&\n CREDIT_UNAVAILABLE_REASONS.includes(reason as CreditUnavailableReason)\n ) {\n return {\n hasAvailableCredits: false,\n reason: reason as CreditUnavailableReason,\n };\n }\n\n return null;\n};\n\n// Fails open on timeouts or bad responses: a hint before expensive work, never an authorization decision\nexport const getCreditAvailability = async (): Promise<CreditAvailability> => {\n const apiUrl = process.env[DEFAULT_API_URL_NAME];\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return available();\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/credits`,\n {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n signal: AbortSignal.timeout(CREDIT_AVAILABILITY_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n console.error(\n `getCreditAvailability: ${response.status} ${response.statusText}`,\n );\n\n return available();\n }\n\n const creditAvailability = parseCreditAvailability(await response.json());\n\n if (creditAvailability === null) {\n console.error('getCreditAvailability: unexpected response shape');\n\n return available();\n }\n\n return creditAvailability;\n } catch (error) {\n console.error(\n `getCreditAvailability: ${error instanceof Error ? error.message : String(error)}`,\n );\n\n return available();\n }\n};\n"],"mappings":";IAIyB,IAAI,kBAG1B,IAAI;CACN;CACA;CACA;AACD,GCLa,UACX,QAAQ,IAAA,uCACR,QAAQ,IAAA,yBCHJ,IAA4B,KAuBrB,IAAgB,OAAO,EAClC,qBACA,cAAW,GACX,cACA,kBACA,oBACA,yBACwC;CACxC,IAAM,IAAS,QAAQ,IAAI,IACrB,IAAQ,EAA0B;CAEpC,OAAC,KAAU,CAAC,IAIhB,IAAI;EACF,IAAM,IAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,OAAO,EAAE,EAAE,sBAC7B;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU;IACnB;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,QAAQ,YAAY,QAAQ,CAAyB;EACvD,CACF;EAEA,IAAI,CAAC,EAAS,IAAI;GAChB,IAAM,IAAO,MAAM,EAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GAEjD,QAAQ,MACN,kBAAkB,EAAS,OAAO,GAAG,EAAS,WAAW,IAAI,GAC/D;EACF;CACF,SAAS,GAAO;EACd,QAAQ,MACN,kBAAkB,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK,GACzE;CACF;AACF,GCnEM,IAAiC,KAGjC,WAAuC,EAAE,qBAAqB,GAAK,IAEnE,KAA2B,MAA6C;CAC5E,IAAI,OAAO,KAAS,aAAY,GAC9B,OAAO;CAGT,IAAM,EAAE,wBAAqB,cAAW;CAmBxC,OAdI,MAAwB,KACnB,EAAE,qBAAqB,GAAK,IAInC,MAAwB,MACxB,EAA2B,SAAS,CAAiC,IAE9D;EACL,qBAAqB;EACb;CACV,IAGK;AACT,GAGa,IAAwB,YAAyC;CAC5E,IAAM,IAAS,QAAQ,IAAI,IACrB,IAAQ,EAA0B;CAExC,IAAI,CAAC,KAAU,CAAC,GACd,OAAO,EAAU;CAGnB,IAAI;EACF,IAAM,IAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,OAAO,EAAE,EAAE,uBAC7B;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,QAAQ;GACV;GACA,QAAQ,YAAY,QAAQ,CAA8B;EAC5D,CACF;EAEA,IAAI,CAAC,EAAS,IAKZ,OAJA,QAAQ,MACN,0BAA0B,EAAS,OAAO,GAAG,EAAS,YACxD,GAEO,EAAU;EAGnB,IAAM,IAAqB,EAAwB,MAAM,EAAS,KAAK,CAAC;EAQxE,OANI,MAAuB,QACzB,QAAQ,MAAM,kDAAkD,GAEzD,EAAU,KAGZ;CACT,SAAS,GAAO;EAKd,OAJA,QAAQ,MACN,0BAA0B,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK,GACjF,GAEO,EAAU;CACnB;AACF"}
1
+ {"version":3,"file":"index.mjs","names":["e","e","e","e","t","n","e"],"sources":["../../../twenty-shared/dist/application-runtime/constants/DefaultApiUrlName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultAppAccessTokenName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultAppApplicationAccessTokenName.mjs","../../../twenty-shared/dist/application-runtime/constants/DefaultInternalApiUrlName.mjs","../../../twenty-shared/dist/application-runtime/creditAvailabilityType.mjs","../../../twenty-shared/dist/application-runtime/utils/resolveApiUrlFromEnvironment.mjs","../../src/sdk/utils/get-application-access-token.ts","../../src/sdk/billing/charge-credits.ts","../../src/sdk/billing/get-credit-availability.ts"],"sourcesContent":["//#region src/application-runtime/constants/DefaultApiUrlName.ts\nvar e = \"TWENTY_API_URL\";\n//#endregion\nexport { e as DEFAULT_API_URL_NAME };\n","//#region src/application-runtime/constants/DefaultAppAccessTokenName.ts\nvar e = \"TWENTY_APP_ACCESS_TOKEN\";\n//#endregion\nexport { e as DEFAULT_APP_ACCESS_TOKEN_NAME };\n","//#region src/application-runtime/constants/DefaultAppApplicationAccessTokenName.ts\nvar e = \"TWENTY_APP_APPLICATION_ACCESS_TOKEN\";\n//#endregion\nexport { e as DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME };\n","//#region src/application-runtime/constants/DefaultInternalApiUrlName.ts\nvar e = \"TWENTY_INTERNAL_API_URL\";\n//#endregion\nexport { e as DEFAULT_INTERNAL_API_URL_NAME };\n","//#region src/application-runtime/creditAvailabilityType.ts\nvar e = [\n\t\"workspace-suspended\",\n\t\"no-subscription\",\n\t\"no-credits\"\n];\n//#endregion\nexport { e as CREDIT_UNAVAILABLE_REASONS };\n","import { DEFAULT_API_URL_NAME as e } from \"../constants/DefaultApiUrlName.mjs\";\nimport { DEFAULT_INTERNAL_API_URL_NAME as t } from \"../constants/DefaultInternalApiUrlName.mjs\";\nimport { isNonEmptyString as n } from \"@sniptt/guards\";\n//#region src/application-runtime/utils/resolveApiUrlFromEnvironment.ts\nvar r = (r) => {\n\tlet i = r[t];\n\treturn n(i) ? i : r[e];\n};\n//#endregion\nexport { r as resolveApiUrlFromEnvironment };\n","import {\n DEFAULT_APP_ACCESS_TOKEN_NAME,\n DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME,\n} from 'twenty-shared/application-runtime';\n\n// These reach the application's own resources, so prefer the application token when the runtime injects one\nexport const getApplicationAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME];\n\n// acts as whoever triggered the run, like the client SDK's default runAs\nexport const getCallerAccessToken = (): string | undefined =>\n process.env[DEFAULT_APP_ACCESS_TOKEN_NAME] ??\n process.env[DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME];\n","import { type UsageOperationTypeValue } from 'twenty-shared/application';\nimport { resolveApiUrlFromEnvironment } from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst BILLING_CHARGE_TIMEOUT_MS = 5_000;\n\nexport type ChargeCreditsParams = {\n creditsUsedMicro: number;\n quantity?: number;\n // Who the spend belongs to when no user triggered the run (webhook, cron); ignored otherwise\n userWorkspaceId?: string;\n} & (\n | {\n // An operation declared in the manifest's `billing.operations`; the platform resolves its category and label\n operation: string;\n operationType?: never;\n resourceContext?: never;\n }\n | {\n // Platform billing category; omitted, the charge lands in the dedicated APP_OPERATION category\n operationType?: UsageOperationTypeValue;\n operation?: never;\n resourceContext?: string;\n }\n);\n\n// No-ops without an API URL or access token, and never throws: a billing error must not fail a tool\nexport const chargeCredits = async ({\n creditsUsedMicro,\n quantity = 1,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n}: ChargeCreditsParams): Promise<void> => {\n const apiUrl = resolveApiUrlFromEnvironment(process.env);\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return;\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/charge`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n creditsUsedMicro,\n quantity,\n operation,\n operationType,\n resourceContext,\n userWorkspaceId,\n }),\n signal: AbortSignal.timeout(BILLING_CHARGE_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n\n console.error(\n `chargeCredits: ${response.status} ${response.statusText}: ${body}`,\n );\n }\n } catch (error) {\n console.error(\n `chargeCredits: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n};\n","import {\n CREDIT_UNAVAILABLE_REASONS,\n type CreditAvailability,\n type CreditUnavailableReason,\n resolveApiUrlFromEnvironment,\n} from 'twenty-shared/application-runtime';\n\nimport { getApplicationAccessToken } from '@/sdk/utils/get-application-access-token';\n\nconst CREDIT_AVAILABILITY_TIMEOUT_MS = 5_000;\n\n// A fresh object per call, since app code could mutate a shared constant\nconst available = (): CreditAvailability => ({ hasAvailableCredits: true });\n\nconst parseCreditAvailability = (body: unknown): CreditAvailability | null => {\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n\n const { hasAvailableCredits, reason } = body as {\n hasAvailableCredits?: unknown;\n reason?: unknown;\n };\n\n if (hasAvailableCredits === true) {\n return { hasAvailableCredits: true };\n }\n\n if (\n hasAvailableCredits === false &&\n CREDIT_UNAVAILABLE_REASONS.includes(reason as CreditUnavailableReason)\n ) {\n return {\n hasAvailableCredits: false,\n reason: reason as CreditUnavailableReason,\n };\n }\n\n return null;\n};\n\n// Fails open on timeouts or bad responses: a hint before expensive work, never an authorization decision\nexport const getCreditAvailability = async (): Promise<CreditAvailability> => {\n const apiUrl = resolveApiUrlFromEnvironment(process.env);\n const token = getApplicationAccessToken();\n\n if (!apiUrl || !token) {\n return available();\n }\n\n try {\n const response = await fetch(\n `${apiUrl.replace(/\\/$/, '')}/app/billing/credits`,\n {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/json',\n },\n signal: AbortSignal.timeout(CREDIT_AVAILABILITY_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n console.error(\n `getCreditAvailability: ${response.status} ${response.statusText}`,\n );\n\n return available();\n }\n\n const creditAvailability = parseCreditAvailability(await response.json());\n\n if (creditAvailability === null) {\n console.error('getCreditAvailability: unexpected response shape');\n\n return available();\n }\n\n return creditAvailability;\n } catch (error) {\n console.error(\n `getCreditAvailability: ${error instanceof Error ? error.message : String(error)}`,\n );\n\n return available();\n }\n};\n"],"mappings":";;AACA,IAAIA,IAAI,kBGAJG,IAAI,2BCAJ,IAAI;CACP;CACA;CACA;AACD,GCDI,KAAK,MAAM;CACd,IAAI,IAAI,EAAEC;CACV,OAAOC,EAAE,CAAC,IAAI,IAAI,EAAEC;AACrB,GCDa,UACX,QAAQ,IAAA,uCACR,QAAQ,IAAA,yBCHJ,IAA4B,KAuBrB,IAAgB,OAAO,EAClC,qBACA,cAAW,GACX,cACA,kBACA,oBACA,yBACwC;CACxC,IAAM,IAAS,EAA6B,QAAQ,GAAG,GACjD,IAAQ,EAA0B;CAEpC,OAAC,KAAU,CAAC,IAIhB,IAAI;EACF,IAAM,IAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,OAAO,EAAE,EAAE,sBAC7B;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU;IACnB;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,QAAQ,YAAY,QAAQ,CAAyB;EACvD,CACF;EAEA,IAAI,CAAC,EAAS,IAAI;GAChB,IAAM,IAAO,MAAM,EAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GAEjD,QAAQ,MACN,kBAAkB,EAAS,OAAO,GAAG,EAAS,WAAW,IAAI,GAC/D;EACF;CACF,SAAS,GAAO;EACd,QAAQ,MACN,kBAAkB,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK,GACzE;CACF;AACF,GCnEM,IAAiC,KAGjC,WAAuC,EAAE,qBAAqB,GAAK,IAEnE,KAA2B,MAA6C;CAC5E,IAAI,OAAO,KAAS,aAAY,GAC9B,OAAO;CAGT,IAAM,EAAE,wBAAqB,cAAW;CAmBxC,OAdI,MAAwB,KACnB,EAAE,qBAAqB,GAAK,IAInC,MAAwB,MACxB,EAA2B,SAAS,CAAiC,IAE9D;EACL,qBAAqB;EACb;CACV,IAGK;AACT,GAGa,IAAwB,YAAyC;CAC5E,IAAM,IAAS,EAA6B,QAAQ,GAAG,GACjD,IAAQ,EAA0B;CAExC,IAAI,CAAC,KAAU,CAAC,GACd,OAAO,EAAU;CAGnB,IAAI;EACF,IAAM,IAAW,MAAM,MACrB,GAAG,EAAO,QAAQ,OAAO,EAAE,EAAE,uBAC7B;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,QAAQ;GACV;GACA,QAAQ,YAAY,QAAQ,CAA8B;EAC5D,CACF;EAEA,IAAI,CAAC,EAAS,IAKZ,OAJA,QAAQ,MACN,0BAA0B,EAAS,OAAO,GAAG,EAAS,YACxD,GAEO,EAAU;EAGnB,IAAM,IAAqB,EAAwB,MAAM,EAAS,KAAK,CAAC;EAQxE,OANI,MAAuB,QACzB,QAAQ,MAAM,kDAAkD,GAEzD,EAAU,KAGZ;CACT,SAAS,GAAO;EAKd,OAJA,QAAQ,MACN,0BAA0B,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK,GACjF,GAEO,EAAU;CACnB;AACF"}