@twentyhq/call-recorder 1.0.1 → 1.0.2
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/manifest.json +297 -0
- package/package.json +2 -2
- package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
- package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
- package/src/logic-functions/process-recall-webhook.mjs +1744 -0
- package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
- package/src/logic-functions/recall-webhook.mjs +391 -0
- package/src/logic-functions/recall-webhook.mjs.map +7 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
- package/.env.example +0 -5
- package/.nvmrc +0 -1
- package/.oxlintrc.json +0 -20
- package/AGENTS.md +0 -67
- package/CLAUDE.md +0 -67
- package/README.md +0 -24
- package/SETUP.md +0 -95
- package/src/__tests__/global-setup.ts +0 -100
- package/src/__tests__/schema.integration-test.ts +0 -104
- package/src/application-config.ts +0 -96
- package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
- package/src/constants/app-description.ts +0 -2
- package/src/constants/app-display-name.ts +0 -1
- package/src/constants/application-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
- package/src/constants/call-recorder-preference.ts +0 -4
- package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
- package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
- package/src/constants/default-role-universal-identifier.ts +0 -2
- package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
- package/src/default-role.ts +0 -69
- package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
- package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
- package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
- package/src/front-components/components/CalendarEventRecording.tsx +0 -39
- package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
- package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
- package/src/front-components/components/RecordingTranscript.tsx +0 -92
- package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
- package/src/front-components/components/TranscriptEntryList.tsx +0 -61
- package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
- package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
- package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
- package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
- package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
- package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
- package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
- package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
- package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
- package/src/front-components/types/transcript-entry.type.ts +0 -13
- package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
- package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
- package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
- package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
- package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
- package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
- package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
- package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
- package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
- package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
- package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
- package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
- package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
- package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
- package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
- package/src/logic-functions/constants/call-recording-status.ts +0 -9
- package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
- package/src/logic-functions/constants/default-recall-region.ts +0 -1
- package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
- package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
- package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
- package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
- package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
- package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
- package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
- package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
- package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
- package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
- package/src/logic-functions/constants/twenty-page-size.ts +0 -1
- package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
- package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
- package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
- package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
- package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
- package/src/logic-functions/data/create-call-recording.util.ts +0 -41
- package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
- package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
- package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
- package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
- package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
- package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
- package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
- package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
- package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
- package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
- package/src/logic-functions/data/update-call-recording.util.ts +0 -24
- package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
- package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
- package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
- package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
- package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
- package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
- package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
- package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
- package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
- package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
- package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
- package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
- package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
- package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
- package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
- package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
- package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
- package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
- package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
- package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
- package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
- package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
- package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
- package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
- package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
- package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
- package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
- package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
- package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
- package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
- package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
- package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
- package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
- package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
- package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
- package/src/logic-functions/flows/download-transcript.util.ts +0 -67
- package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
- package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
- package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
- package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
- package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
- package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
- package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
- package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
- package/src/logic-functions/process-recall-webhook.ts +0 -23
- package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
- package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
- package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
- package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
- package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
- package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
- package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
- package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
- package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
- package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
- package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
- package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
- package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
- package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
- package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
- package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
- package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
- package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
- package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
- package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
- package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
- package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
- package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
- package/src/logic-functions/recall-webhook.ts +0 -90
- package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
- package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
- package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
- package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
- package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
- package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
- package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
- package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
- package/src/logic-functions/types/call-recording-record.type.ts +0 -15
- package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
- package/src/logic-functions/types/files-field-value.type.ts +0 -1
- package/src/logic-functions/types/meeting-recording.type.ts +0 -7
- package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
- package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
- package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
- package/src/logic-functions/types/transcript-marker.type.ts +0 -6
- package/src/logic-functions/utils/as-record.util.ts +0 -6
- package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
- package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
- package/src/logic-functions/utils/get-string.util.ts +0 -4
- package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
- package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
- package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
- package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
- package/tsconfig.json +0 -42
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -31
- package/vitest.unit.config.ts +0 -14
|
@@ -0,0 +1,1744 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'module';
|
|
2
|
+
const require = __createRequire(import.meta.url);
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
try {
|
|
11
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
+
} catch (e2) {
|
|
13
|
+
throw mod = 0, e2;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// node_modules/@sniptt/guards/build/guards/primitives.js
|
|
34
|
+
var require_primitives = __commonJS({
|
|
35
|
+
"node_modules/@sniptt/guards/build/guards/primitives.js"(exports) {
|
|
36
|
+
"use strict";
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.isSymbol = exports.isBigInt = exports.isString = exports.isNumber = exports.isBoolean = exports.isUndefined = void 0;
|
|
39
|
+
var isUndefined17 = (term) => {
|
|
40
|
+
return typeof term === "undefined";
|
|
41
|
+
};
|
|
42
|
+
exports.isUndefined = isUndefined17;
|
|
43
|
+
var isBoolean = (term) => {
|
|
44
|
+
return typeof term === "boolean";
|
|
45
|
+
};
|
|
46
|
+
exports.isBoolean = isBoolean;
|
|
47
|
+
var isNumber = (term) => {
|
|
48
|
+
return typeof term === "number" && !Number.isNaN(term);
|
|
49
|
+
};
|
|
50
|
+
exports.isNumber = isNumber;
|
|
51
|
+
var isString4 = (term) => {
|
|
52
|
+
return typeof term === "string";
|
|
53
|
+
};
|
|
54
|
+
exports.isString = isString4;
|
|
55
|
+
var isBigInt = (term) => {
|
|
56
|
+
return typeof term === "bigint";
|
|
57
|
+
};
|
|
58
|
+
exports.isBigInt = isBigInt;
|
|
59
|
+
var isSymbol = (term) => {
|
|
60
|
+
return typeof term === "symbol";
|
|
61
|
+
};
|
|
62
|
+
exports.isSymbol = isSymbol;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// node_modules/@sniptt/guards/build/guards/structural.js
|
|
67
|
+
var require_structural = __commonJS({
|
|
68
|
+
"node_modules/@sniptt/guards/build/guards/structural.js"(exports) {
|
|
69
|
+
"use strict";
|
|
70
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
71
|
+
exports.isDate = exports.isWeakSet = exports.isWeakMap = exports.isSet = exports.isMap = exports.isArray = exports.isObject = exports.isFunction = exports.isNull = void 0;
|
|
72
|
+
var isNull5 = (term) => {
|
|
73
|
+
return term === null;
|
|
74
|
+
};
|
|
75
|
+
exports.isNull = isNull5;
|
|
76
|
+
var isFunction = (term) => {
|
|
77
|
+
return typeof term === "function";
|
|
78
|
+
};
|
|
79
|
+
exports.isFunction = isFunction;
|
|
80
|
+
var isObject2 = (term) => {
|
|
81
|
+
return !exports.isNull(term) && typeof term === "object";
|
|
82
|
+
};
|
|
83
|
+
exports.isObject = isObject2;
|
|
84
|
+
var isArray4 = (term) => {
|
|
85
|
+
return Array.isArray(term);
|
|
86
|
+
};
|
|
87
|
+
exports.isArray = isArray4;
|
|
88
|
+
var isMap = (term) => {
|
|
89
|
+
return term instanceof Map;
|
|
90
|
+
};
|
|
91
|
+
exports.isMap = isMap;
|
|
92
|
+
var isSet = (term) => {
|
|
93
|
+
return term instanceof Set;
|
|
94
|
+
};
|
|
95
|
+
exports.isSet = isSet;
|
|
96
|
+
var isWeakMap = (term) => {
|
|
97
|
+
return term instanceof WeakMap;
|
|
98
|
+
};
|
|
99
|
+
exports.isWeakMap = isWeakMap;
|
|
100
|
+
var isWeakSet = (term) => {
|
|
101
|
+
return term instanceof WeakSet;
|
|
102
|
+
};
|
|
103
|
+
exports.isWeakSet = isWeakSet;
|
|
104
|
+
var isDate = (term) => {
|
|
105
|
+
return term instanceof Date;
|
|
106
|
+
};
|
|
107
|
+
exports.isDate = isDate;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// node_modules/@sniptt/guards/build/guards/convenience.js
|
|
112
|
+
var require_convenience = __commonJS({
|
|
113
|
+
"node_modules/@sniptt/guards/build/guards/convenience.js"(exports) {
|
|
114
|
+
"use strict";
|
|
115
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
116
|
+
exports.isNegativeInteger = exports.isNonNegativeInteger = exports.isPositiveInteger = exports.isInteger = exports.isNumberOrNaN = exports.isNonEmptyString = exports.isNonEmptyArray = exports.isObjectOrNull = void 0;
|
|
117
|
+
var primitives_1 = require_primitives();
|
|
118
|
+
var structural_1 = require_structural();
|
|
119
|
+
var isObjectOrNull = (term) => {
|
|
120
|
+
return typeof term === "object";
|
|
121
|
+
};
|
|
122
|
+
exports.isObjectOrNull = isObjectOrNull;
|
|
123
|
+
var isNonEmptyArray3 = (term) => {
|
|
124
|
+
return structural_1.isArray(term) && term.length > 0;
|
|
125
|
+
};
|
|
126
|
+
exports.isNonEmptyArray = isNonEmptyArray3;
|
|
127
|
+
var isNonEmptyString2 = (term) => {
|
|
128
|
+
return primitives_1.isString(term) && term.length > 0;
|
|
129
|
+
};
|
|
130
|
+
exports.isNonEmptyString = isNonEmptyString2;
|
|
131
|
+
var isNumberOrNaN = (term) => {
|
|
132
|
+
return typeof term === "number";
|
|
133
|
+
};
|
|
134
|
+
exports.isNumberOrNaN = isNumberOrNaN;
|
|
135
|
+
var isInteger = (term) => {
|
|
136
|
+
return primitives_1.isNumber(term) && Number.isInteger(term);
|
|
137
|
+
};
|
|
138
|
+
exports.isInteger = isInteger;
|
|
139
|
+
var isPositiveInteger = (term) => {
|
|
140
|
+
return exports.isInteger(term) && term > 0;
|
|
141
|
+
};
|
|
142
|
+
exports.isPositiveInteger = isPositiveInteger;
|
|
143
|
+
var isNonNegativeInteger = (term) => {
|
|
144
|
+
return exports.isInteger(term) && term >= 0;
|
|
145
|
+
};
|
|
146
|
+
exports.isNonNegativeInteger = isNonNegativeInteger;
|
|
147
|
+
var isNegativeInteger = (term) => {
|
|
148
|
+
return exports.isInteger(term) && term < 0;
|
|
149
|
+
};
|
|
150
|
+
exports.isNegativeInteger = isNegativeInteger;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// node_modules/@sniptt/guards/build/index.js
|
|
155
|
+
var require_build = __commonJS({
|
|
156
|
+
"node_modules/@sniptt/guards/build/index.js"(exports) {
|
|
157
|
+
"use strict";
|
|
158
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
159
|
+
if (k2 === void 0) k2 = k;
|
|
160
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() {
|
|
161
|
+
return m[k];
|
|
162
|
+
} });
|
|
163
|
+
}) : (function(o, m, k, k2) {
|
|
164
|
+
if (k2 === void 0) k2 = k;
|
|
165
|
+
o[k2] = m[k];
|
|
166
|
+
}));
|
|
167
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
168
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
169
|
+
}) : function(o, v) {
|
|
170
|
+
o["default"] = v;
|
|
171
|
+
});
|
|
172
|
+
var __importStar = exports && exports.__importStar || function(mod) {
|
|
173
|
+
if (mod && mod.__esModule) return mod;
|
|
174
|
+
var result = {};
|
|
175
|
+
if (mod != null) {
|
|
176
|
+
for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
177
|
+
}
|
|
178
|
+
__setModuleDefault(result, mod);
|
|
179
|
+
return result;
|
|
180
|
+
};
|
|
181
|
+
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
182
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
183
|
+
};
|
|
184
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
185
|
+
exports.structural = exports.primitives = exports.convenience = void 0;
|
|
186
|
+
exports.convenience = __importStar(require_convenience());
|
|
187
|
+
__exportStar(require_convenience(), exports);
|
|
188
|
+
exports.primitives = __importStar(require_primitives());
|
|
189
|
+
__exportStar(require_primitives(), exports);
|
|
190
|
+
exports.structural = __importStar(require_structural());
|
|
191
|
+
__exportStar(require_structural(), exports);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// src/logic-functions/process-recall-webhook.ts
|
|
196
|
+
import { CoreApiClient } from "twenty-client-sdk/core";
|
|
197
|
+
|
|
198
|
+
// twenty-sdk-define-stub:__twenty-sdk-define-stub__
|
|
199
|
+
var __defineFactoryStub = (config) => ({
|
|
200
|
+
success: true,
|
|
201
|
+
config,
|
|
202
|
+
errors: []
|
|
203
|
+
});
|
|
204
|
+
var __anyHandler = {
|
|
205
|
+
get(_target, prop) {
|
|
206
|
+
if (prop === "__esModule") return true;
|
|
207
|
+
if (prop === Symbol.toPrimitive) return () => "";
|
|
208
|
+
if (typeof prop === "symbol") return void 0;
|
|
209
|
+
return new Proxy(() => void 0, __anyHandler);
|
|
210
|
+
},
|
|
211
|
+
apply() {
|
|
212
|
+
return new Proxy(() => void 0, __anyHandler);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var __anyStub = new Proxy(() => void 0, __anyHandler);
|
|
216
|
+
var defineLogicFunction = __defineFactoryStub;
|
|
217
|
+
|
|
218
|
+
// src/constants/process-recall-webhook-logic-function-universal-identifier.ts
|
|
219
|
+
var PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "13d9c427-447e-494a-8d3c-1af5d0bacb82";
|
|
220
|
+
|
|
221
|
+
// src/logic-functions/flows/handle-recall-webhook.util.ts
|
|
222
|
+
var import_guards20 = __toESM(require_build());
|
|
223
|
+
|
|
224
|
+
// src/logic-functions/domain/build-failed-transcript-marker.util.ts
|
|
225
|
+
var buildFailedTranscriptMarker = ({
|
|
226
|
+
recallTranscriptId,
|
|
227
|
+
subCode
|
|
228
|
+
}) => ({
|
|
229
|
+
recallTranscriptId,
|
|
230
|
+
status: "FAILED",
|
|
231
|
+
subCode
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// src/logic-functions/domain/build-transcript-failure-reason.util.ts
|
|
235
|
+
var import_guards = __toESM(require_build());
|
|
236
|
+
var buildTranscriptFailureReason = (subCode) => {
|
|
237
|
+
return (0, import_guards.isNull)(subCode) ? "transcript_failed" : `transcript_failed:${subCode}`;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/logic-functions/flows/download-transcript.util.ts
|
|
241
|
+
var import_guards7 = __toESM(require_build());
|
|
242
|
+
|
|
243
|
+
// src/logic-functions/recall-api/retrieve-recall-transcript.util.ts
|
|
244
|
+
var import_guards6 = __toESM(require_build());
|
|
245
|
+
|
|
246
|
+
// src/logic-functions/utils/as-record.util.ts
|
|
247
|
+
var import_guards2 = __toESM(require_build());
|
|
248
|
+
var asRecord = (value) => (0, import_guards2.isObject)(value) && !(0, import_guards2.isArray)(value) ? value : void 0;
|
|
249
|
+
|
|
250
|
+
// src/logic-functions/recall-api/get-recall-api-config.util.ts
|
|
251
|
+
var import_guards4 = __toESM(require_build());
|
|
252
|
+
|
|
253
|
+
// src/logic-functions/constants/call-recorder-name-env-var-name.ts
|
|
254
|
+
var CALL_RECORDER_NAME_ENV_VAR_NAME = "CALL_RECORDER_NAME";
|
|
255
|
+
|
|
256
|
+
// src/logic-functions/constants/default-call-recorder-name.ts
|
|
257
|
+
var DEFAULT_CALL_RECORDER_NAME = "Twenty.com";
|
|
258
|
+
|
|
259
|
+
// src/logic-functions/constants/default-recall-region.ts
|
|
260
|
+
var DEFAULT_RECALL_REGION = "eu-central-1";
|
|
261
|
+
|
|
262
|
+
// src/logic-functions/constants/recall-api-key-env-var-name.ts
|
|
263
|
+
var RECALL_API_KEY_ENV_VAR_NAME = "RECALL_API_KEY";
|
|
264
|
+
|
|
265
|
+
// src/logic-functions/constants/recall-region-env-var-name.ts
|
|
266
|
+
var RECALL_REGION_ENV_VAR_NAME = "RECALL_REGION";
|
|
267
|
+
|
|
268
|
+
// src/logic-functions/utils/get-application-variable-value.util.ts
|
|
269
|
+
var getApplicationVariableValue = (key) => process.env[key];
|
|
270
|
+
|
|
271
|
+
// src/logic-functions/utils/is-non-empty-string.util.ts
|
|
272
|
+
var import_guards3 = __toESM(require_build());
|
|
273
|
+
var isNonEmptyString = (value) => (0, import_guards3.isString)(value) && value.trim() !== "";
|
|
274
|
+
|
|
275
|
+
// src/logic-functions/recall-api/get-recall-api-config.util.ts
|
|
276
|
+
var getRecallApiConfig = () => {
|
|
277
|
+
const apiKey = normalizeOptionalString(
|
|
278
|
+
getApplicationVariableValue(RECALL_API_KEY_ENV_VAR_NAME)
|
|
279
|
+
);
|
|
280
|
+
if ((0, import_guards4.isUndefined)(apiKey)) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
error: "RECALL_API_KEY server variable is not set. A server admin must set it on the Call Recorder application registration before scheduling bots."
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const region = normalizeOptionalString(
|
|
287
|
+
getApplicationVariableValue(RECALL_REGION_ENV_VAR_NAME)
|
|
288
|
+
) ?? DEFAULT_RECALL_REGION;
|
|
289
|
+
const botName = normalizeOptionalString(
|
|
290
|
+
getApplicationVariableValue(CALL_RECORDER_NAME_ENV_VAR_NAME)
|
|
291
|
+
) ?? DEFAULT_CALL_RECORDER_NAME;
|
|
292
|
+
return {
|
|
293
|
+
success: true,
|
|
294
|
+
config: {
|
|
295
|
+
apiKey,
|
|
296
|
+
baseUrl: `https://${region}.recall.ai/api/v1`,
|
|
297
|
+
botName
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
var normalizeOptionalString = (value) => isNonEmptyString(value) ? value.trim() : void 0;
|
|
302
|
+
|
|
303
|
+
// src/logic-functions/utils/get-string.util.ts
|
|
304
|
+
var getString = (value) => isNonEmptyString(value) ? value : void 0;
|
|
305
|
+
|
|
306
|
+
// src/logic-functions/recall-api/recall-bot-api-request.util.ts
|
|
307
|
+
var import_guards5 = __toESM(require_build());
|
|
308
|
+
|
|
309
|
+
// src/logic-functions/constants/recall-api-max-attempts.ts
|
|
310
|
+
var RECALL_API_MAX_ATTEMPTS = 3;
|
|
311
|
+
|
|
312
|
+
// src/logic-functions/constants/recall-api-retry-delay-ms.ts
|
|
313
|
+
var RECALL_API_RETRY_DELAY_MS = 500;
|
|
314
|
+
|
|
315
|
+
// src/logic-functions/recall-api/recall-bot-api-request.util.ts
|
|
316
|
+
var recallBotApiRequest = async (requestArgs) => {
|
|
317
|
+
const maxAttempts = requestArgs.maxAttempts ?? RECALL_API_MAX_ATTEMPTS;
|
|
318
|
+
for (let attemptNumber = 1; ; attemptNumber++) {
|
|
319
|
+
const { result, isRetryable } = await performRecallBotApiRequestAttempt(requestArgs);
|
|
320
|
+
if (!isRetryable || attemptNumber >= maxAttempts) {
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
await sleep(RECALL_API_RETRY_DELAY_MS * attemptNumber);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var performRecallBotApiRequestAttempt = async ({
|
|
327
|
+
config,
|
|
328
|
+
path,
|
|
329
|
+
method,
|
|
330
|
+
body,
|
|
331
|
+
allowNotFound = false
|
|
332
|
+
}) => {
|
|
333
|
+
let response;
|
|
334
|
+
try {
|
|
335
|
+
response = await fetch(`${config.baseUrl}${path}`, {
|
|
336
|
+
method,
|
|
337
|
+
headers: {
|
|
338
|
+
Authorization: buildRecallApiAuthorizationHeader(config.apiKey),
|
|
339
|
+
...(0, import_guards5.isUndefined)(body) ? {} : { "Content-Type": "application/json" }
|
|
340
|
+
},
|
|
341
|
+
...(0, import_guards5.isUndefined)(body) ? {} : { body: JSON.stringify(body) }
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
return {
|
|
345
|
+
isRetryable: true,
|
|
346
|
+
result: {
|
|
347
|
+
ok: false,
|
|
348
|
+
status: null,
|
|
349
|
+
errorMessage: `Recall API request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (allowNotFound && response.status === 404) {
|
|
354
|
+
return {
|
|
355
|
+
isRetryable: false,
|
|
356
|
+
result: {
|
|
357
|
+
ok: true,
|
|
358
|
+
status: response.status,
|
|
359
|
+
data: void 0
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (response.status === 204) {
|
|
364
|
+
return {
|
|
365
|
+
isRetryable: false,
|
|
366
|
+
result: {
|
|
367
|
+
ok: true,
|
|
368
|
+
status: response.status,
|
|
369
|
+
data: void 0
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
return {
|
|
375
|
+
isRetryable: isRetryableRecallApiStatus(response.status),
|
|
376
|
+
result: {
|
|
377
|
+
ok: false,
|
|
378
|
+
status: response.status,
|
|
379
|
+
errorMessage: await extractRecallApiErrorMessage(response)
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
return {
|
|
385
|
+
isRetryable: false,
|
|
386
|
+
result: {
|
|
387
|
+
ok: true,
|
|
388
|
+
status: response.status,
|
|
389
|
+
data: await response.json()
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
} catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
isRetryable: false,
|
|
395
|
+
result: {
|
|
396
|
+
ok: false,
|
|
397
|
+
status: response.status,
|
|
398
|
+
errorMessage: `Recall API returned a non-JSON response: ${error instanceof Error ? error.message : String(error)}`
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var isRetryableRecallApiStatus = (status) => status === 429 || status >= 500;
|
|
404
|
+
var sleep = (delayMs) => new Promise((resolve) => {
|
|
405
|
+
setTimeout(resolve, delayMs);
|
|
406
|
+
});
|
|
407
|
+
var buildRecallApiAuthorizationHeader = (apiKey) => {
|
|
408
|
+
const trimmedApiKey = apiKey.trim();
|
|
409
|
+
return trimmedApiKey.toLowerCase().startsWith("token ") ? trimmedApiKey : `Token ${trimmedApiKey}`;
|
|
410
|
+
};
|
|
411
|
+
var extractRecallApiErrorMessage = async (response) => {
|
|
412
|
+
const fallback = `Recall API responded with HTTP ${response.status}`;
|
|
413
|
+
try {
|
|
414
|
+
const body = await response.json();
|
|
415
|
+
return `${fallback}: ${JSON.stringify(body)}`;
|
|
416
|
+
} catch {
|
|
417
|
+
return fallback;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/logic-functions/recall-api/retrieve-recall-transcript.util.ts
|
|
422
|
+
var retrieveRecallTranscript = async ({
|
|
423
|
+
transcriptId
|
|
424
|
+
}) => {
|
|
425
|
+
const configResult = getRecallApiConfig();
|
|
426
|
+
if (!configResult.success) {
|
|
427
|
+
return { ok: false, status: null, errorMessage: configResult.error };
|
|
428
|
+
}
|
|
429
|
+
const result = await recallBotApiRequest({
|
|
430
|
+
config: configResult.config,
|
|
431
|
+
path: `/transcript/${transcriptId}/`,
|
|
432
|
+
method: "GET"
|
|
433
|
+
});
|
|
434
|
+
if (!result.ok) {
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
const transcript = extractRecallTranscriptDetails(result.data);
|
|
438
|
+
if (isMalformedRecallTranscriptDetails(transcript)) {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
status: result.status,
|
|
442
|
+
errorMessage: "Recall API returned malformed transcript details"
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return { ok: true, transcript };
|
|
446
|
+
};
|
|
447
|
+
var extractRecallTranscriptDetails = (response) => {
|
|
448
|
+
const data = asRecord(response?.data);
|
|
449
|
+
const status = asRecord(response?.status);
|
|
450
|
+
return {
|
|
451
|
+
downloadUrl: getString(data?.download_url),
|
|
452
|
+
statusCode: getString(status?.code),
|
|
453
|
+
statusSubCode: getString(status?.sub_code)
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
var isMalformedRecallTranscriptDetails = ({
|
|
457
|
+
downloadUrl,
|
|
458
|
+
statusCode
|
|
459
|
+
}) => (0, import_guards6.isUndefined)(downloadUrl) && (0, import_guards6.isUndefined)(statusCode) || (0, import_guards6.isUndefined)(downloadUrl) && statusCode === "done";
|
|
460
|
+
|
|
461
|
+
// src/logic-functions/flows/download-transcript.util.ts
|
|
462
|
+
var TRANSCRIPT_DOWNLOAD_TIMEOUT_MS = 2e4;
|
|
463
|
+
var downloadTranscript = async ({
|
|
464
|
+
transcriptId
|
|
465
|
+
}) => {
|
|
466
|
+
const retrieveResult = await retrieveRecallTranscript({ transcriptId });
|
|
467
|
+
if (!retrieveResult.ok) {
|
|
468
|
+
return { outcome: "error", errorMessage: retrieveResult.errorMessage };
|
|
469
|
+
}
|
|
470
|
+
const { downloadUrl, statusCode, statusSubCode } = retrieveResult.transcript;
|
|
471
|
+
if (!(0, import_guards7.isUndefined)(downloadUrl)) {
|
|
472
|
+
return downloadTranscriptContent(downloadUrl);
|
|
473
|
+
}
|
|
474
|
+
if (statusCode === "error" || statusCode === "failed") {
|
|
475
|
+
return { outcome: "failed", subCode: statusSubCode ?? null };
|
|
476
|
+
}
|
|
477
|
+
return { outcome: "pending" };
|
|
478
|
+
};
|
|
479
|
+
var downloadTranscriptContent = async (downloadUrl) => {
|
|
480
|
+
try {
|
|
481
|
+
const response = await fetch(downloadUrl, {
|
|
482
|
+
signal: AbortSignal.timeout(TRANSCRIPT_DOWNLOAD_TIMEOUT_MS)
|
|
483
|
+
});
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
console.warn(
|
|
486
|
+
`[call-recorder] transcript download responded with HTTP ${response.status}`
|
|
487
|
+
);
|
|
488
|
+
return {
|
|
489
|
+
outcome: "error",
|
|
490
|
+
errorMessage: "transcript download failed"
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return { outcome: "filled", content: await response.json() };
|
|
494
|
+
} catch (error) {
|
|
495
|
+
console.warn(
|
|
496
|
+
`[call-recorder] transcript download failed: ${error instanceof Error ? error.message : String(error)}`
|
|
497
|
+
);
|
|
498
|
+
return {
|
|
499
|
+
outcome: "error",
|
|
500
|
+
errorMessage: "transcript download failed"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts
|
|
506
|
+
var import_guards9 = __toESM(require_build());
|
|
507
|
+
|
|
508
|
+
// src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts
|
|
509
|
+
var mapRecallStatusCodeToCallRecordingStatus = (statusCode) => {
|
|
510
|
+
switch (statusCode) {
|
|
511
|
+
case "joining_call":
|
|
512
|
+
case "in_waiting_room":
|
|
513
|
+
return "JOINING" /* JOINING */;
|
|
514
|
+
case "in_call_not_recording":
|
|
515
|
+
case "recording_permission_allowed":
|
|
516
|
+
case "in_call_recording":
|
|
517
|
+
return "RECORDING" /* RECORDING */;
|
|
518
|
+
// 'done' stays PROCESSING: COMPLETED is set only after all artifacts are ingested.
|
|
519
|
+
case "call_ended":
|
|
520
|
+
case "analysis_done":
|
|
521
|
+
case "done":
|
|
522
|
+
return "PROCESSING" /* PROCESSING */;
|
|
523
|
+
case "fatal":
|
|
524
|
+
case "analysis_failed":
|
|
525
|
+
case "recording_permission_denied":
|
|
526
|
+
return "FAILED" /* FAILED */;
|
|
527
|
+
default:
|
|
528
|
+
return void 0;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/logic-functions/recall-api/normalize-recall-timestamp.util.ts
|
|
533
|
+
var import_guards8 = __toESM(require_build());
|
|
534
|
+
var normalizeRecallTimestamp = (value) => {
|
|
535
|
+
if ((0, import_guards8.isUndefined)(value)) {
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
const parsed = new Date(value);
|
|
539
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts
|
|
543
|
+
var extractRecallBotConvergence = (bot) => {
|
|
544
|
+
const statusChanges = extractStatusChanges(bot);
|
|
545
|
+
const latestStatusChange = getLatestStatusChange(statusChanges);
|
|
546
|
+
const status = mapRecallStatusCodeToCallRecordingStatus(
|
|
547
|
+
latestStatusChange?.code
|
|
548
|
+
);
|
|
549
|
+
const recording = extractFirstRecording(bot);
|
|
550
|
+
return {
|
|
551
|
+
status,
|
|
552
|
+
failureReason: status === "FAILED" /* FAILED */ ? latestStatusChange?.code : void 0,
|
|
553
|
+
startedAt: normalizeRecallTimestamp(
|
|
554
|
+
recording?.startedAt ?? findStatusChangeTimestamp(statusChanges, "in_call_recording")
|
|
555
|
+
),
|
|
556
|
+
endedAt: normalizeRecallTimestamp(
|
|
557
|
+
recording?.completedAt ?? findStatusChangeTimestamp(statusChanges, "call_ended")
|
|
558
|
+
),
|
|
559
|
+
externalRecordingId: recording?.id,
|
|
560
|
+
isRecallRecordingDone: !(0, import_guards9.isUndefined)(recording?.completedAt) || statusChanges.some((statusChange) => statusChange.code === "done")
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
var extractStatusChanges = (bot) => {
|
|
564
|
+
if (!(0, import_guards9.isArray)(bot.status_changes)) {
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
return bot.status_changes.flatMap((statusChange) => {
|
|
568
|
+
const code = getString(asRecord(statusChange)?.code);
|
|
569
|
+
if ((0, import_guards9.isUndefined)(code)) {
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
return [{ code, createdAt: getString(asRecord(statusChange)?.created_at) }];
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
var getLatestStatusChange = (statusChanges) => statusChanges.reduce(
|
|
576
|
+
(latestStatusChange, statusChange) => {
|
|
577
|
+
if ((0, import_guards9.isUndefined)(latestStatusChange)) {
|
|
578
|
+
return statusChange;
|
|
579
|
+
}
|
|
580
|
+
const statusChangeTime = getStatusChangeTime(statusChange);
|
|
581
|
+
const latestStatusChangeTime = getStatusChangeTime(latestStatusChange);
|
|
582
|
+
if ((0, import_guards9.isUndefined)(statusChangeTime) && (0, import_guards9.isUndefined)(latestStatusChangeTime)) {
|
|
583
|
+
return statusChange;
|
|
584
|
+
}
|
|
585
|
+
if ((0, import_guards9.isUndefined)(statusChangeTime)) {
|
|
586
|
+
return latestStatusChange;
|
|
587
|
+
}
|
|
588
|
+
if ((0, import_guards9.isUndefined)(latestStatusChangeTime)) {
|
|
589
|
+
return statusChange;
|
|
590
|
+
}
|
|
591
|
+
return statusChangeTime >= latestStatusChangeTime ? statusChange : latestStatusChange;
|
|
592
|
+
},
|
|
593
|
+
void 0
|
|
594
|
+
);
|
|
595
|
+
var getStatusChangeTime = (statusChange) => {
|
|
596
|
+
const normalizedTimestamp = normalizeRecallTimestamp(statusChange.createdAt);
|
|
597
|
+
if ((0, import_guards9.isUndefined)(normalizedTimestamp)) {
|
|
598
|
+
return void 0;
|
|
599
|
+
}
|
|
600
|
+
return new Date(normalizedTimestamp).getTime();
|
|
601
|
+
};
|
|
602
|
+
var extractFirstRecording = (bot) => {
|
|
603
|
+
if (!(0, import_guards9.isArray)(bot.recordings)) {
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
const recording = asRecord(bot.recordings[0]);
|
|
607
|
+
if ((0, import_guards9.isUndefined)(recording)) {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
id: getString(recording.id),
|
|
612
|
+
startedAt: getString(recording.started_at),
|
|
613
|
+
completedAt: getString(recording.completed_at)
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
var findStatusChangeTimestamp = (statusChanges, code) => statusChanges.find((statusChange) => statusChange.code === code)?.createdAt;
|
|
617
|
+
|
|
618
|
+
// src/logic-functions/recall-api/get-recall-bot.util.ts
|
|
619
|
+
var getRecallBot = async ({
|
|
620
|
+
externalBotId
|
|
621
|
+
}) => {
|
|
622
|
+
const configResult = getRecallApiConfig();
|
|
623
|
+
if (!configResult.success) {
|
|
624
|
+
return { ok: false, status: null, errorMessage: configResult.error };
|
|
625
|
+
}
|
|
626
|
+
const result = await recallBotApiRequest({
|
|
627
|
+
config: configResult.config,
|
|
628
|
+
path: `/bot/${externalBotId}/`,
|
|
629
|
+
method: "GET"
|
|
630
|
+
});
|
|
631
|
+
if (!result.ok) {
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
const bot = asRecord(result.data);
|
|
635
|
+
if (bot === void 0) {
|
|
636
|
+
return {
|
|
637
|
+
ok: false,
|
|
638
|
+
status: result.status,
|
|
639
|
+
errorMessage: "Recall API returned an empty bot response"
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return { ok: true, bot };
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/logic-functions/flows/ingest-call-recording-media.util.ts
|
|
646
|
+
var import_guards10 = __toESM(require_build());
|
|
647
|
+
import { MetadataApiClient } from "twenty-client-sdk/metadata";
|
|
648
|
+
|
|
649
|
+
// src/constants/call-recording-audio-field-universal-identifier.ts
|
|
650
|
+
var CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER = "2eafc2d0-8fec-430c-a939-65ca5fbc0f08";
|
|
651
|
+
|
|
652
|
+
// src/constants/call-recording-video-field-universal-identifier.ts
|
|
653
|
+
var CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER = "bb9523d3-457e-4f4b-8c79-27a77afb87da";
|
|
654
|
+
|
|
655
|
+
// src/logic-functions/utils/get-record-at-path.util.ts
|
|
656
|
+
var getRecordAtPath = (record, path) => path.reduce(
|
|
657
|
+
(currentValue, pathPart) => asRecord(currentValue)?.[pathPart],
|
|
658
|
+
record
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
// src/logic-functions/recall-api/extract-recall-media-urls.util.ts
|
|
662
|
+
var extractRecallMediaUrls = (recording) => {
|
|
663
|
+
const mediaShortcuts = asRecord(recording.media_shortcuts);
|
|
664
|
+
return {
|
|
665
|
+
videoUrl: extractArtifactDownloadUrl(mediaShortcuts, "video_mixed"),
|
|
666
|
+
audioUrl: extractArtifactDownloadUrl(mediaShortcuts, "audio_mixed")
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
var extractArtifactDownloadUrl = (mediaShortcuts, artifactKey) => getString(getRecordAtPath(mediaShortcuts, [artifactKey, "download_url"])) ?? getString(
|
|
670
|
+
getRecordAtPath(mediaShortcuts, [artifactKey, "data", "download_url"])
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
// src/logic-functions/recall-api/get-recall-recording.util.ts
|
|
674
|
+
var getRecallRecording = async ({
|
|
675
|
+
externalRecordingId
|
|
676
|
+
}) => {
|
|
677
|
+
const configResult = getRecallApiConfig();
|
|
678
|
+
if (!configResult.success) {
|
|
679
|
+
return { ok: false, status: null, errorMessage: configResult.error };
|
|
680
|
+
}
|
|
681
|
+
const result = await recallBotApiRequest({
|
|
682
|
+
config: configResult.config,
|
|
683
|
+
path: `/recording/${externalRecordingId}/`,
|
|
684
|
+
method: "GET"
|
|
685
|
+
});
|
|
686
|
+
if (!result.ok) {
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
return { ok: true, recording: result.data ?? {} };
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// src/logic-functions/flows/ingest-call-recording-media.util.ts
|
|
693
|
+
var MEDIA_DOWNLOAD_TIMEOUT_MS = 12e4;
|
|
694
|
+
var ingestCallRecordingMedia = async ({
|
|
695
|
+
callRecordingId,
|
|
696
|
+
externalRecordingId,
|
|
697
|
+
hasAudio,
|
|
698
|
+
hasVideo
|
|
699
|
+
}) => {
|
|
700
|
+
if (hasAudio && hasVideo) {
|
|
701
|
+
return {};
|
|
702
|
+
}
|
|
703
|
+
const recordingResult = await getRecallRecording({ externalRecordingId });
|
|
704
|
+
if (!recordingResult.ok) {
|
|
705
|
+
console.warn(
|
|
706
|
+
`[call-recorder] failed to fetch Recall recording ${externalRecordingId} while ingesting media for call recording ${callRecordingId}: ${recordingResult.errorMessage}`
|
|
707
|
+
);
|
|
708
|
+
return {};
|
|
709
|
+
}
|
|
710
|
+
const mediaUrls = extractRecallMediaUrls(recordingResult.recording);
|
|
711
|
+
const metadataClient = new MetadataApiClient();
|
|
712
|
+
const updateFields = {};
|
|
713
|
+
if (!hasVideo && !(0, import_guards10.isUndefined)(mediaUrls.videoUrl)) {
|
|
714
|
+
const video = await ingestMediaArtifact({
|
|
715
|
+
callRecordingId,
|
|
716
|
+
metadataClient,
|
|
717
|
+
url: mediaUrls.videoUrl,
|
|
718
|
+
fileName: "video.mp4",
|
|
719
|
+
fieldMetadataUniversalIdentifier: CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER
|
|
720
|
+
});
|
|
721
|
+
if (!(0, import_guards10.isUndefined)(video)) {
|
|
722
|
+
updateFields.video = video;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (!hasAudio && !(0, import_guards10.isUndefined)(mediaUrls.audioUrl)) {
|
|
726
|
+
const audio = await ingestMediaArtifact({
|
|
727
|
+
callRecordingId,
|
|
728
|
+
metadataClient,
|
|
729
|
+
url: mediaUrls.audioUrl,
|
|
730
|
+
fileName: "audio.mp3",
|
|
731
|
+
fieldMetadataUniversalIdentifier: CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER
|
|
732
|
+
});
|
|
733
|
+
if (!(0, import_guards10.isUndefined)(audio)) {
|
|
734
|
+
updateFields.audio = audio;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return updateFields;
|
|
738
|
+
};
|
|
739
|
+
var ingestMediaArtifact = async ({
|
|
740
|
+
callRecordingId,
|
|
741
|
+
metadataClient,
|
|
742
|
+
url,
|
|
743
|
+
fileName,
|
|
744
|
+
fieldMetadataUniversalIdentifier
|
|
745
|
+
}) => {
|
|
746
|
+
try {
|
|
747
|
+
const { buffer, contentType } = await downloadMediaFile(url);
|
|
748
|
+
const uploadedFile = await metadataClient.uploadFile(
|
|
749
|
+
buffer,
|
|
750
|
+
fileName,
|
|
751
|
+
contentType,
|
|
752
|
+
fieldMetadataUniversalIdentifier
|
|
753
|
+
);
|
|
754
|
+
return [{ fileId: uploadedFile.id, label: fileName }];
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.warn(
|
|
757
|
+
`[call-recorder] failed to ingest ${fileName} for call recording ${callRecordingId}: ${error instanceof Error ? error.message : String(error)}`
|
|
758
|
+
);
|
|
759
|
+
return void 0;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
var downloadMediaFile = async (url) => {
|
|
763
|
+
const response = await fetch(url, {
|
|
764
|
+
signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS)
|
|
765
|
+
});
|
|
766
|
+
if (!response.ok) {
|
|
767
|
+
throw new Error(`download failed with status ${response.status}`);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
buffer: Buffer.from(await response.arrayBuffer()),
|
|
771
|
+
contentType: response.headers.get("content-type") ?? "application/octet-stream"
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// src/logic-functions/domain/is-call-recording-status-downgrade.util.ts
|
|
776
|
+
var import_guards11 = __toESM(require_build());
|
|
777
|
+
var CALL_RECORDING_STATUS_PROGRESSION = {
|
|
778
|
+
["SCHEDULED" /* SCHEDULED */]: 0,
|
|
779
|
+
["JOINING" /* JOINING */]: 1,
|
|
780
|
+
["RECORDING" /* RECORDING */]: 2,
|
|
781
|
+
["PROCESSING" /* PROCESSING */]: 3,
|
|
782
|
+
["FAILED" /* FAILED */]: 4,
|
|
783
|
+
["COMPLETED" /* COMPLETED */]: 5
|
|
784
|
+
};
|
|
785
|
+
var getCallRecordingStatusRank = (status) => status in CALL_RECORDING_STATUS_PROGRESSION ? CALL_RECORDING_STATUS_PROGRESSION[status] : void 0;
|
|
786
|
+
var isCallRecordingStatusDowngrade = ({
|
|
787
|
+
fromStatus,
|
|
788
|
+
toStatus
|
|
789
|
+
}) => {
|
|
790
|
+
const fromRank = (0, import_guards11.isUndefined)(fromStatus) ? void 0 : getCallRecordingStatusRank(fromStatus);
|
|
791
|
+
const toRank = getCallRecordingStatusRank(toStatus);
|
|
792
|
+
if ((0, import_guards11.isUndefined)(fromRank) || (0, import_guards11.isUndefined)(toRank)) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
return toRank < fromRank;
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// src/logic-functions/domain/is-recall-recording-done-signal.util.ts
|
|
799
|
+
var isRecallRecordingDoneSignal = ({
|
|
800
|
+
event,
|
|
801
|
+
statusCode
|
|
802
|
+
}) => {
|
|
803
|
+
return event === "recording.done" || event === "recording.failed" || statusCode === "done";
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/logic-functions/recall-api/parse-recall-webhook-event.util.ts
|
|
807
|
+
var import_guards12 = __toESM(require_build());
|
|
808
|
+
|
|
809
|
+
// src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts
|
|
810
|
+
var getRecallWebhookBotMetadata = (body) => {
|
|
811
|
+
const data = asRecord(body.data);
|
|
812
|
+
const bot = asRecord(body.bot);
|
|
813
|
+
return asRecord(bot?.metadata) ?? asRecord(getRecordAtPath(data, ["bot", "metadata"])) ?? asRecord(getRecordAtPath(data, ["recording", "metadata"])) ?? asRecord(data?.metadata);
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// src/logic-functions/recall-api/parse-recall-webhook-event.util.ts
|
|
817
|
+
var parseRecallWebhookEvent = (body) => {
|
|
818
|
+
const event = getString(body.event) ?? getString(body.type);
|
|
819
|
+
if ((0, import_guards12.isUndefined)(event)) {
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
const data = asRecord(body.data);
|
|
823
|
+
const bot = asRecord(body.bot);
|
|
824
|
+
return {
|
|
825
|
+
event,
|
|
826
|
+
statusCode: getString(getRecordAtPath(data, ["status", "code"])) ?? getString(getRecordAtPath(data, ["data", "code"])) ?? getString(getRecordAtPath(bot, ["status", "code"])) ?? getStatusCodeFromEventName(event),
|
|
827
|
+
statusTimestamp: normalizeRecallTimestamp(
|
|
828
|
+
getString(getRecordAtPath(data, ["status", "created_at"])) ?? getString(getRecordAtPath(data, ["data", "updated_at"])) ?? getString(getRecordAtPath(bot, ["status", "created_at"]))
|
|
829
|
+
),
|
|
830
|
+
externalBotId: getString(data?.bot_id) ?? getString(getRecordAtPath(data, ["bot", "id"])) ?? getString(getRecordAtPath(data, ["recording", "bot_id"])) ?? getString(getRecordAtPath(data, ["recording", "bot", "id"])) ?? getString(bot?.id),
|
|
831
|
+
externalRecordingId: getString(getRecordAtPath(data, ["status", "recording_id"])) ?? getString(getRecordAtPath(data, ["recording", "id"])) ?? getString(data?.recording_id),
|
|
832
|
+
callRecordingIdFromMetadata: getString(
|
|
833
|
+
getRecallWebhookBotMetadata(body)?.twentyCallRecordingId
|
|
834
|
+
),
|
|
835
|
+
recordingStartedAt: normalizeRecallTimestamp(
|
|
836
|
+
getString(getRecordAtPath(data, ["recording", "started_at"]))
|
|
837
|
+
),
|
|
838
|
+
recordingEndedAt: normalizeRecallTimestamp(
|
|
839
|
+
getString(getRecordAtPath(data, ["recording", "completed_at"]))
|
|
840
|
+
),
|
|
841
|
+
transcriptId: getString(getRecordAtPath(data, ["transcript", "id"])),
|
|
842
|
+
transcriptFailureSubCode: getString(
|
|
843
|
+
getRecordAtPath(data, ["status", "sub_code"])
|
|
844
|
+
)
|
|
845
|
+
};
|
|
846
|
+
};
|
|
847
|
+
var getStatusCodeFromEventName = (event) => {
|
|
848
|
+
if (!event.startsWith("bot.")) {
|
|
849
|
+
return void 0;
|
|
850
|
+
}
|
|
851
|
+
const statusCode = event.slice("bot.".length);
|
|
852
|
+
return statusCode === "status_change" ? void 0 : statusCode;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/logic-functions/domain/parse-transcript-marker.util.ts
|
|
856
|
+
var import_guards13 = __toESM(require_build());
|
|
857
|
+
var parseTranscriptMarker = (transcript) => {
|
|
858
|
+
const candidate = asRecord(transcript);
|
|
859
|
+
if ((0, import_guards13.isUndefined)(candidate)) {
|
|
860
|
+
return void 0;
|
|
861
|
+
}
|
|
862
|
+
if (candidate.status !== "PENDING" && candidate.status !== "FAILED") {
|
|
863
|
+
return void 0;
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
recallTranscriptId: (0, import_guards13.isString)(candidate.recallTranscriptId) ? candidate.recallTranscriptId : null,
|
|
867
|
+
status: candidate.status,
|
|
868
|
+
...(0, import_guards13.isString)(candidate.requestedAt) ? { requestedAt: candidate.requestedAt } : {},
|
|
869
|
+
...(0, import_guards13.isString)(candidate.subCode) ? { subCode: candidate.subCode } : {}
|
|
870
|
+
};
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// src/logic-functions/constants/non-terminal-call-recording-statuses.ts
|
|
874
|
+
var NON_TERMINAL_CALL_RECORDING_STATUSES = [
|
|
875
|
+
"SCHEDULED" /* SCHEDULED */,
|
|
876
|
+
"JOINING" /* JOINING */,
|
|
877
|
+
"RECORDING" /* RECORDING */,
|
|
878
|
+
"PROCESSING" /* PROCESSING */
|
|
879
|
+
];
|
|
880
|
+
|
|
881
|
+
// src/logic-functions/data/complete-call-recording-ingestion.util.ts
|
|
882
|
+
var completeCallRecordingIngestion = async (client, { id }) => {
|
|
883
|
+
const result = await client.mutation({
|
|
884
|
+
updateCallRecordings: {
|
|
885
|
+
__args: {
|
|
886
|
+
filter: {
|
|
887
|
+
id: { eq: id },
|
|
888
|
+
status: { in: NON_TERMINAL_CALL_RECORDING_STATUSES }
|
|
889
|
+
},
|
|
890
|
+
data: { status: "COMPLETED" /* COMPLETED */ }
|
|
891
|
+
},
|
|
892
|
+
id: true
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
return (result.updateCallRecordings ?? []).length > 0;
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
// src/logic-functions/flows/charge-completed-call-recording.util.ts
|
|
899
|
+
var import_guards15 = __toESM(require_build());
|
|
900
|
+
|
|
901
|
+
// node_modules/twenty-sdk/dist/billing/index.mjs
|
|
902
|
+
var e = "TWENTY_API_URL";
|
|
903
|
+
var t = "TWENTY_APP_ACCESS_TOKEN";
|
|
904
|
+
var n = 5e3;
|
|
905
|
+
var r = async ({ creditsUsedMicro: r2, operationType: i, quantity: a = 1, resourceContext: o }) => {
|
|
906
|
+
let s = process.env[e], c = process.env[t];
|
|
907
|
+
if (!(!s || !c)) try {
|
|
908
|
+
let e2 = await fetch(`${s.replace(/\/$/, "")}/app/billing/charge`, {
|
|
909
|
+
method: "POST",
|
|
910
|
+
headers: {
|
|
911
|
+
Authorization: `Bearer ${c}`,
|
|
912
|
+
"Content-Type": "application/json"
|
|
913
|
+
},
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
creditsUsedMicro: r2,
|
|
916
|
+
quantity: a,
|
|
917
|
+
operationType: i,
|
|
918
|
+
resourceContext: o
|
|
919
|
+
}),
|
|
920
|
+
signal: AbortSignal.timeout(n)
|
|
921
|
+
});
|
|
922
|
+
if (!e2.ok) {
|
|
923
|
+
let t2 = await e2.text().catch(() => "");
|
|
924
|
+
console.error(`chargeCredits: ${e2.status} ${e2.statusText}: ${t2}`);
|
|
925
|
+
}
|
|
926
|
+
} catch (e2) {
|
|
927
|
+
console.error(`chargeCredits: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// src/logic-functions/domain/compute-call-recording-charge.util.ts
|
|
932
|
+
var import_guards14 = __toESM(require_build());
|
|
933
|
+
|
|
934
|
+
// src/logic-functions/constants/call-recording-micro-credits-per-hour.ts
|
|
935
|
+
var CALL_RECORDING_MICRO_CREDITS_PER_HOUR = 1e6;
|
|
936
|
+
|
|
937
|
+
// src/logic-functions/constants/milliseconds-per-minute.ts
|
|
938
|
+
var MILLISECONDS_PER_MINUTE = 6e4;
|
|
939
|
+
|
|
940
|
+
// src/logic-functions/domain/compute-call-recording-charge.util.ts
|
|
941
|
+
var MILLISECONDS_PER_HOUR = 36e5;
|
|
942
|
+
var computeCallRecordingCharge = ({
|
|
943
|
+
startedAt,
|
|
944
|
+
endedAt
|
|
945
|
+
}) => {
|
|
946
|
+
if ((0, import_guards14.isUndefined)(startedAt) || (0, import_guards14.isUndefined)(endedAt)) {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
const durationMilliseconds = new Date(endedAt).getTime() - new Date(startedAt).getTime();
|
|
950
|
+
if (!Number.isFinite(durationMilliseconds) || durationMilliseconds <= 0) {
|
|
951
|
+
return void 0;
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
creditsUsedMicro: Math.round(
|
|
955
|
+
durationMilliseconds / MILLISECONDS_PER_HOUR * CALL_RECORDING_MICRO_CREDITS_PER_HOUR
|
|
956
|
+
),
|
|
957
|
+
quantityMinutes: Math.max(
|
|
958
|
+
1,
|
|
959
|
+
Math.round(durationMilliseconds / MILLISECONDS_PER_MINUTE)
|
|
960
|
+
)
|
|
961
|
+
};
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
// src/logic-functions/flows/charge-completed-call-recording.util.ts
|
|
965
|
+
var chargeCompletedCallRecording = async ({
|
|
966
|
+
callRecordingId,
|
|
967
|
+
startedAt,
|
|
968
|
+
endedAt
|
|
969
|
+
}) => {
|
|
970
|
+
const charge = computeCallRecordingCharge({ startedAt, endedAt });
|
|
971
|
+
if ((0, import_guards15.isUndefined)(charge)) {
|
|
972
|
+
console.warn(
|
|
973
|
+
`[call-recorder] call recording ${callRecordingId} completed without usable recording timestamps; it will not be billed`
|
|
974
|
+
);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
await r({
|
|
978
|
+
creditsUsedMicro: charge.creditsUsedMicro,
|
|
979
|
+
quantity: charge.quantityMinutes,
|
|
980
|
+
operationType: "CALL_RECORDING",
|
|
981
|
+
resourceContext: "recall"
|
|
982
|
+
});
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// src/logic-functions/flows/complete-and-charge-call-recording.util.ts
|
|
986
|
+
var completeAndChargeCallRecording = async (client, {
|
|
987
|
+
id,
|
|
988
|
+
startedAt,
|
|
989
|
+
endedAt
|
|
990
|
+
}) => {
|
|
991
|
+
const claimed = await completeCallRecordingIngestion(client, { id });
|
|
992
|
+
if (claimed) {
|
|
993
|
+
await chargeCompletedCallRecording({
|
|
994
|
+
callRecordingId: id,
|
|
995
|
+
startedAt,
|
|
996
|
+
endedAt
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
return claimed;
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
// src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts
|
|
1003
|
+
var import_guards16 = __toESM(require_build());
|
|
1004
|
+
var isCallRecordingIngestionComplete = ({
|
|
1005
|
+
transcript,
|
|
1006
|
+
audio,
|
|
1007
|
+
video
|
|
1008
|
+
}) => !(0, import_guards16.isNull)(transcript) && !(0, import_guards16.isUndefined)(transcript) && (0, import_guards16.isUndefined)(parseTranscriptMarker(transcript)) && (0, import_guards16.isNonEmptyArray)(audio) && (0, import_guards16.isNonEmptyArray)(video);
|
|
1009
|
+
|
|
1010
|
+
// src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts
|
|
1011
|
+
var shouldCompleteCallRecordingIngestion = ({
|
|
1012
|
+
current,
|
|
1013
|
+
updateData
|
|
1014
|
+
}) => current.status !== "COMPLETED" /* COMPLETED */ && current.status !== "FAILED" /* FAILED */ && updateData.status !== "FAILED" /* FAILED */ && computeCallRecordingCharge({
|
|
1015
|
+
startedAt: updateData.startedAt ?? current.startedAt,
|
|
1016
|
+
endedAt: updateData.endedAt ?? current.endedAt
|
|
1017
|
+
}) !== void 0 && isCallRecordingIngestionComplete({
|
|
1018
|
+
transcript: updateData.transcript ?? current.transcript,
|
|
1019
|
+
audio: updateData.audio ?? current.audio,
|
|
1020
|
+
video: updateData.video ?? current.video
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// src/logic-functions/data/update-call-recording.util.ts
|
|
1024
|
+
var updateCallRecording = async (client, {
|
|
1025
|
+
id,
|
|
1026
|
+
data
|
|
1027
|
+
}) => {
|
|
1028
|
+
await client.mutation({
|
|
1029
|
+
updateCallRecording: {
|
|
1030
|
+
__args: {
|
|
1031
|
+
id,
|
|
1032
|
+
data
|
|
1033
|
+
},
|
|
1034
|
+
id: true
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
// src/logic-functions/flows/persist-call-recording-progress.util.ts
|
|
1040
|
+
var persistCallRecordingProgress = async (client, {
|
|
1041
|
+
id,
|
|
1042
|
+
current,
|
|
1043
|
+
updateData
|
|
1044
|
+
}) => {
|
|
1045
|
+
const completesIngestion = shouldCompleteCallRecordingIngestion({
|
|
1046
|
+
current,
|
|
1047
|
+
updateData
|
|
1048
|
+
});
|
|
1049
|
+
if (!completesIngestion) {
|
|
1050
|
+
await updateCallRecording(client, { id, data: updateData });
|
|
1051
|
+
return { completesIngestion: false };
|
|
1052
|
+
}
|
|
1053
|
+
const nonStatusUpdate = { ...updateData };
|
|
1054
|
+
delete nonStatusUpdate.status;
|
|
1055
|
+
delete nonStatusUpdate.callRecorderFailureReason;
|
|
1056
|
+
if (Object.keys(nonStatusUpdate).length > 0) {
|
|
1057
|
+
await updateCallRecording(client, { id, data: nonStatusUpdate });
|
|
1058
|
+
}
|
|
1059
|
+
await completeAndChargeCallRecording(client, {
|
|
1060
|
+
id,
|
|
1061
|
+
startedAt: updateData.startedAt ?? current.startedAt,
|
|
1062
|
+
endedAt: updateData.endedAt ?? current.endedAt
|
|
1063
|
+
});
|
|
1064
|
+
return { completesIngestion: true };
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts
|
|
1068
|
+
var import_guards19 = __toESM(require_build());
|
|
1069
|
+
|
|
1070
|
+
// src/logic-functions/domain/build-pending-transcript-marker.util.ts
|
|
1071
|
+
var buildPendingTranscriptMarker = ({
|
|
1072
|
+
recallTranscriptId,
|
|
1073
|
+
requestedAt
|
|
1074
|
+
}) => ({
|
|
1075
|
+
recallTranscriptId,
|
|
1076
|
+
status: "PENDING",
|
|
1077
|
+
requestedAt
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// src/logic-functions/recall-api/create-async-recall-transcript.util.ts
|
|
1081
|
+
var import_guards17 = __toESM(require_build());
|
|
1082
|
+
var createAsyncRecallTranscript = async ({
|
|
1083
|
+
externalRecordingId
|
|
1084
|
+
}) => {
|
|
1085
|
+
const configResult = getRecallApiConfig();
|
|
1086
|
+
if (!configResult.success) {
|
|
1087
|
+
return { ok: false, status: null, errorMessage: configResult.error };
|
|
1088
|
+
}
|
|
1089
|
+
const result = await recallBotApiRequest({
|
|
1090
|
+
config: configResult.config,
|
|
1091
|
+
path: `/recording/${externalRecordingId}/create_transcript/`,
|
|
1092
|
+
method: "POST",
|
|
1093
|
+
body: {
|
|
1094
|
+
provider: { recallai_async: { language_code: "auto" } },
|
|
1095
|
+
diarization: { use_separate_streams_when_available: true }
|
|
1096
|
+
},
|
|
1097
|
+
maxAttempts: 1
|
|
1098
|
+
});
|
|
1099
|
+
if (!result.ok) {
|
|
1100
|
+
return result;
|
|
1101
|
+
}
|
|
1102
|
+
if (!(0, import_guards17.isString)(result.data?.id)) {
|
|
1103
|
+
return {
|
|
1104
|
+
ok: false,
|
|
1105
|
+
status: null,
|
|
1106
|
+
errorMessage: "Recall API created a transcript but the response did not include a transcript id"
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
return { ok: true, transcriptId: result.data.id };
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
// src/logic-functions/recall-api/list-recall-transcripts.util.ts
|
|
1113
|
+
var import_guards18 = __toESM(require_build());
|
|
1114
|
+
var RECALL_TRANSCRIPT_LIST_MAX_PAGES = 10;
|
|
1115
|
+
var listRecallTranscripts = async ({
|
|
1116
|
+
externalRecordingId
|
|
1117
|
+
}) => {
|
|
1118
|
+
const configResult = getRecallApiConfig();
|
|
1119
|
+
if (!configResult.success) {
|
|
1120
|
+
return { ok: false, status: null, errorMessage: configResult.error };
|
|
1121
|
+
}
|
|
1122
|
+
const transcripts = [];
|
|
1123
|
+
let path = buildListRecallTranscriptsPath({
|
|
1124
|
+
externalRecordingId
|
|
1125
|
+
});
|
|
1126
|
+
for (let pageIndex = 0; !(0, import_guards18.isUndefined)(path) && pageIndex < RECALL_TRANSCRIPT_LIST_MAX_PAGES; pageIndex++) {
|
|
1127
|
+
const result = await recallBotApiRequest({
|
|
1128
|
+
config: configResult.config,
|
|
1129
|
+
path,
|
|
1130
|
+
method: "GET"
|
|
1131
|
+
});
|
|
1132
|
+
if (!result.ok) {
|
|
1133
|
+
return result;
|
|
1134
|
+
}
|
|
1135
|
+
const pageTranscripts = extractRecallTranscriptSummaries(result.data);
|
|
1136
|
+
if ((0, import_guards18.isUndefined)(pageTranscripts)) {
|
|
1137
|
+
return {
|
|
1138
|
+
ok: false,
|
|
1139
|
+
status: result.status,
|
|
1140
|
+
errorMessage: "Recall API returned malformed transcript list"
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
transcripts.push(...pageTranscripts);
|
|
1144
|
+
path = extractNextPath(result.data, configResult.config.baseUrl);
|
|
1145
|
+
}
|
|
1146
|
+
if (!(0, import_guards18.isUndefined)(path)) {
|
|
1147
|
+
return {
|
|
1148
|
+
ok: false,
|
|
1149
|
+
status: null,
|
|
1150
|
+
errorMessage: `Recall transcript list exceeded ${RECALL_TRANSCRIPT_LIST_MAX_PAGES} pages`
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
return { ok: true, transcripts };
|
|
1154
|
+
};
|
|
1155
|
+
var buildListRecallTranscriptsPath = ({
|
|
1156
|
+
externalRecordingId
|
|
1157
|
+
}) => {
|
|
1158
|
+
const searchParams = new URLSearchParams({
|
|
1159
|
+
recording_id: externalRecordingId
|
|
1160
|
+
});
|
|
1161
|
+
return `/transcript/?${searchParams.toString()}`;
|
|
1162
|
+
};
|
|
1163
|
+
var extractRecallTranscriptSummaries = (response) => {
|
|
1164
|
+
if (!(0, import_guards18.isArray)(response?.results)) {
|
|
1165
|
+
return void 0;
|
|
1166
|
+
}
|
|
1167
|
+
const transcripts = [];
|
|
1168
|
+
for (const result of response.results) {
|
|
1169
|
+
const transcript = extractRecallTranscriptSummary(result);
|
|
1170
|
+
if ((0, import_guards18.isUndefined)(transcript)) {
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
transcripts.push(transcript);
|
|
1174
|
+
}
|
|
1175
|
+
return transcripts;
|
|
1176
|
+
};
|
|
1177
|
+
var extractRecallTranscriptSummary = (transcript) => {
|
|
1178
|
+
const transcriptRecord = asRecord(transcript);
|
|
1179
|
+
const transcriptId = getString(transcriptRecord?.id);
|
|
1180
|
+
if ((0, import_guards18.isUndefined)(transcriptRecord) || (0, import_guards18.isUndefined)(transcriptId)) {
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
const status = asRecord(transcriptRecord.status);
|
|
1184
|
+
return {
|
|
1185
|
+
id: transcriptId,
|
|
1186
|
+
statusCode: getString(status?.code),
|
|
1187
|
+
statusSubCode: getString(status?.sub_code)
|
|
1188
|
+
};
|
|
1189
|
+
};
|
|
1190
|
+
var extractNextPath = (response, baseUrl) => {
|
|
1191
|
+
const nextPage = getString(response?.next);
|
|
1192
|
+
if ((0, import_guards18.isUndefined)(nextPage) || !nextPage.startsWith(baseUrl)) {
|
|
1193
|
+
return void 0;
|
|
1194
|
+
}
|
|
1195
|
+
return nextPage.slice(baseUrl.length);
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
// src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts
|
|
1199
|
+
var reconcileCallRecordingTranscriptArtifact = async ({
|
|
1200
|
+
callRecordingId,
|
|
1201
|
+
currentStatus,
|
|
1202
|
+
externalRecordingId,
|
|
1203
|
+
requestedAt,
|
|
1204
|
+
transcript
|
|
1205
|
+
}) => {
|
|
1206
|
+
const existingTranscriptMarker = parseTranscriptMarker(transcript);
|
|
1207
|
+
if (!(0, import_guards19.isNull)(transcript) && !(0, import_guards19.isUndefined)(transcript) && (0, import_guards19.isUndefined)(existingTranscriptMarker)) {
|
|
1208
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1209
|
+
}
|
|
1210
|
+
if (existingTranscriptMarker?.status === "FAILED") {
|
|
1211
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1212
|
+
}
|
|
1213
|
+
const listResult = await listRecallTranscripts({ externalRecordingId });
|
|
1214
|
+
if (!listResult.ok) {
|
|
1215
|
+
console.warn(
|
|
1216
|
+
`[call-recorder] failed to list Recall transcripts for recording ${externalRecordingId}: ${listResult.errorMessage}`
|
|
1217
|
+
);
|
|
1218
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1219
|
+
}
|
|
1220
|
+
const transcriptArtifact = selectRecallTranscriptArtifact(
|
|
1221
|
+
listResult.transcripts
|
|
1222
|
+
);
|
|
1223
|
+
const pendingTranscriptMarkerRecallTranscriptId = existingTranscriptMarker?.status === "PENDING" ? existingTranscriptMarker.recallTranscriptId ?? void 0 : void 0;
|
|
1224
|
+
const transcriptIdToDownload = transcriptArtifact?.id ?? pendingTranscriptMarkerRecallTranscriptId;
|
|
1225
|
+
if ((0, import_guards19.isUndefined)(transcriptArtifact) && (0, import_guards19.isUndefined)(pendingTranscriptMarkerRecallTranscriptId)) {
|
|
1226
|
+
const createResult = await createAsyncRecallTranscript({
|
|
1227
|
+
externalRecordingId
|
|
1228
|
+
});
|
|
1229
|
+
if (!createResult.ok) {
|
|
1230
|
+
console.warn(
|
|
1231
|
+
`[call-recorder] failed to request transcript for Recall recording ${externalRecordingId}: ${createResult.errorMessage}`
|
|
1232
|
+
);
|
|
1233
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
updateData: {
|
|
1237
|
+
transcript: buildPendingTranscriptMarker({
|
|
1238
|
+
recallTranscriptId: createResult.transcriptId,
|
|
1239
|
+
requestedAt
|
|
1240
|
+
})
|
|
1241
|
+
},
|
|
1242
|
+
requestedTranscript: true
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
if (!(0, import_guards19.isUndefined)(transcriptArtifact) && (transcriptArtifact.statusCode === "failed" || transcriptArtifact.statusCode === "error")) {
|
|
1246
|
+
return {
|
|
1247
|
+
updateData: buildTranscriptFailureUpdate({
|
|
1248
|
+
currentStatus,
|
|
1249
|
+
transcriptId: transcriptArtifact.id,
|
|
1250
|
+
subCode: transcriptArtifact.statusSubCode ?? null
|
|
1251
|
+
}),
|
|
1252
|
+
requestedTranscript: false
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
if (!(0, import_guards19.isUndefined)(transcriptArtifact) && transcriptArtifact.statusCode !== "done") {
|
|
1256
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1257
|
+
}
|
|
1258
|
+
if ((0, import_guards19.isUndefined)(transcriptIdToDownload)) {
|
|
1259
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1260
|
+
}
|
|
1261
|
+
const downloadResult = await downloadTranscript({
|
|
1262
|
+
transcriptId: transcriptIdToDownload
|
|
1263
|
+
});
|
|
1264
|
+
if (downloadResult.outcome === "filled") {
|
|
1265
|
+
return {
|
|
1266
|
+
updateData: {
|
|
1267
|
+
transcript: downloadResult.content
|
|
1268
|
+
},
|
|
1269
|
+
requestedTranscript: false
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
if (downloadResult.outcome === "failed") {
|
|
1273
|
+
return {
|
|
1274
|
+
updateData: buildTranscriptFailureUpdate({
|
|
1275
|
+
currentStatus,
|
|
1276
|
+
transcriptId: transcriptIdToDownload,
|
|
1277
|
+
subCode: downloadResult.subCode
|
|
1278
|
+
}),
|
|
1279
|
+
requestedTranscript: false
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
if (downloadResult.outcome === "error") {
|
|
1283
|
+
console.warn(
|
|
1284
|
+
`[call-recorder] could not fill transcript for call recording ${callRecordingId}: ${downloadResult.errorMessage}`
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
return buildEmptyTranscriptArtifactResult();
|
|
1288
|
+
};
|
|
1289
|
+
var buildEmptyTranscriptArtifactResult = () => ({
|
|
1290
|
+
updateData: {},
|
|
1291
|
+
requestedTranscript: false
|
|
1292
|
+
});
|
|
1293
|
+
var selectRecallTranscriptArtifact = (transcripts) => transcripts.find((transcript) => transcript.statusCode !== "deleted");
|
|
1294
|
+
var buildTranscriptFailureUpdate = ({
|
|
1295
|
+
currentStatus,
|
|
1296
|
+
transcriptId,
|
|
1297
|
+
subCode
|
|
1298
|
+
}) => ({
|
|
1299
|
+
transcript: buildFailedTranscriptMarker({
|
|
1300
|
+
recallTranscriptId: transcriptId,
|
|
1301
|
+
subCode
|
|
1302
|
+
}),
|
|
1303
|
+
callRecorderFailureReason: buildTranscriptFailureReason(subCode),
|
|
1304
|
+
...isCallRecordingStatusDowngrade({
|
|
1305
|
+
fromStatus: currentStatus,
|
|
1306
|
+
toStatus: "FAILED" /* FAILED */
|
|
1307
|
+
}) ? {} : { status: "FAILED" /* FAILED */ }
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// src/logic-functions/flows/handle-recall-webhook.util.ts
|
|
1311
|
+
var handleRecallWebhook = async ({
|
|
1312
|
+
client,
|
|
1313
|
+
body
|
|
1314
|
+
}) => {
|
|
1315
|
+
const webhookEvent = parseRecallWebhookEvent(body);
|
|
1316
|
+
if ((0, import_guards20.isUndefined)(webhookEvent)) {
|
|
1317
|
+
return {
|
|
1318
|
+
status: "skipped",
|
|
1319
|
+
event: null,
|
|
1320
|
+
reason: "missing event type"
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
const { event } = webhookEvent;
|
|
1324
|
+
if (event === "transcript.done" || event === "transcript.failed") {
|
|
1325
|
+
return handleRecallTranscriptEvent({ client, webhookEvent, event });
|
|
1326
|
+
}
|
|
1327
|
+
return handleRecallStatusEvent({ client, webhookEvent });
|
|
1328
|
+
};
|
|
1329
|
+
var handleRecallStatusEvent = async ({
|
|
1330
|
+
client,
|
|
1331
|
+
webhookEvent
|
|
1332
|
+
}) => {
|
|
1333
|
+
const { event, statusCode } = webhookEvent;
|
|
1334
|
+
const callRecordingStatus = mapRecallEventToCallRecordingStatus({
|
|
1335
|
+
event,
|
|
1336
|
+
statusCode
|
|
1337
|
+
});
|
|
1338
|
+
if ((0, import_guards20.isUndefined)(callRecordingStatus)) {
|
|
1339
|
+
return {
|
|
1340
|
+
status: "skipped",
|
|
1341
|
+
event,
|
|
1342
|
+
reason: `unsupported Recall event status ${statusCode ?? event}`
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
const callRecording = await findMatchingCallRecording({
|
|
1346
|
+
client,
|
|
1347
|
+
webhookEvent
|
|
1348
|
+
});
|
|
1349
|
+
if ((0, import_guards20.isUndefined)(callRecording)) {
|
|
1350
|
+
return {
|
|
1351
|
+
status: "skipped",
|
|
1352
|
+
event,
|
|
1353
|
+
reason: "no matching call recording"
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
if (isCallRecordingStatusDowngrade({
|
|
1357
|
+
fromStatus: callRecording.status,
|
|
1358
|
+
toStatus: callRecordingStatus
|
|
1359
|
+
})) {
|
|
1360
|
+
return {
|
|
1361
|
+
status: "skipped",
|
|
1362
|
+
event,
|
|
1363
|
+
reason: `stale status event (${callRecording.status} -> ${callRecordingStatus})`
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
const updateData = {
|
|
1367
|
+
...(0, import_guards20.isUndefined)(webhookEvent.externalBotId) ? {} : { externalBotId: webhookEvent.externalBotId },
|
|
1368
|
+
...buildExternalRecordingIdUpdate(webhookEvent),
|
|
1369
|
+
...buildCallRecordingStatusUpdate({
|
|
1370
|
+
reason: getRecallWebhookFailureReason(webhookEvent),
|
|
1371
|
+
status: callRecordingStatus
|
|
1372
|
+
}),
|
|
1373
|
+
...buildRecordingTimestampsUpdate({ webhookEvent, callRecording })
|
|
1374
|
+
};
|
|
1375
|
+
if (isRecallRecordingDoneSignal({ event, statusCode })) {
|
|
1376
|
+
const externalRecordingIdResolution = await resolveExternalRecordingId({
|
|
1377
|
+
callRecording,
|
|
1378
|
+
webhookEvent
|
|
1379
|
+
});
|
|
1380
|
+
Object.assign(
|
|
1381
|
+
updateData,
|
|
1382
|
+
await buildTranscriptArtifactUpdate({
|
|
1383
|
+
callRecording,
|
|
1384
|
+
externalRecordingId: externalRecordingIdResolution.externalRecordingId
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
Object.assign(
|
|
1388
|
+
updateData,
|
|
1389
|
+
await buildMediaIngestionUpdate({
|
|
1390
|
+
callRecording,
|
|
1391
|
+
externalRecordingId: externalRecordingIdResolution.externalRecordingId
|
|
1392
|
+
})
|
|
1393
|
+
);
|
|
1394
|
+
const terminalArtifactGateFailureUpdate = buildTerminalArtifactGateFailureUpdate({
|
|
1395
|
+
callRecording,
|
|
1396
|
+
providerLookupFailed: externalRecordingIdResolution.providerLookupFailed,
|
|
1397
|
+
updateData,
|
|
1398
|
+
webhookEvent
|
|
1399
|
+
});
|
|
1400
|
+
if (!(0, import_guards20.isUndefined)(terminalArtifactGateFailureUpdate)) {
|
|
1401
|
+
Object.assign(updateData, terminalArtifactGateFailureUpdate);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
const { completesIngestion } = await persistCallRecordingProgress(client, {
|
|
1405
|
+
id: callRecording.id,
|
|
1406
|
+
current: callRecording,
|
|
1407
|
+
updateData
|
|
1408
|
+
});
|
|
1409
|
+
return {
|
|
1410
|
+
status: "updated",
|
|
1411
|
+
event,
|
|
1412
|
+
callRecordingId: callRecording.id,
|
|
1413
|
+
callRecordingStatus: completesIngestion ? "COMPLETED" /* COMPLETED */ : updateData.status ?? callRecordingStatus
|
|
1414
|
+
};
|
|
1415
|
+
};
|
|
1416
|
+
var findMatchingCallRecording = async ({
|
|
1417
|
+
client,
|
|
1418
|
+
webhookEvent
|
|
1419
|
+
}) => {
|
|
1420
|
+
if (!(0, import_guards20.isUndefined)(webhookEvent.callRecordingIdFromMetadata)) {
|
|
1421
|
+
return findCallRecordingByFilter(client, {
|
|
1422
|
+
id: { eq: webhookEvent.callRecordingIdFromMetadata }
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
if ((0, import_guards20.isUndefined)(webhookEvent.externalBotId)) {
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
return findCallRecordingByFilter(client, {
|
|
1429
|
+
externalBotId: { eq: webhookEvent.externalBotId }
|
|
1430
|
+
});
|
|
1431
|
+
};
|
|
1432
|
+
var findCallRecordingByFilter = async (client, filter) => {
|
|
1433
|
+
const queryResult = await client.query({
|
|
1434
|
+
callRecordings: {
|
|
1435
|
+
__args: {
|
|
1436
|
+
filter,
|
|
1437
|
+
first: 1
|
|
1438
|
+
},
|
|
1439
|
+
edges: {
|
|
1440
|
+
node: {
|
|
1441
|
+
id: true,
|
|
1442
|
+
status: true,
|
|
1443
|
+
startedAt: true,
|
|
1444
|
+
endedAt: true,
|
|
1445
|
+
externalRecordingId: true,
|
|
1446
|
+
transcript: true,
|
|
1447
|
+
audio: { fileId: true },
|
|
1448
|
+
video: { fileId: true }
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
const node = queryResult.callRecordings?.edges?.[0]?.node;
|
|
1454
|
+
if ((0, import_guards20.isUndefined)(node) || (0, import_guards20.isNull)(node)) {
|
|
1455
|
+
return void 0;
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
id: node.id,
|
|
1459
|
+
status: getString(node.status),
|
|
1460
|
+
startedAt: getString(node.startedAt),
|
|
1461
|
+
endedAt: getString(node.endedAt),
|
|
1462
|
+
externalRecordingId: getString(node.externalRecordingId),
|
|
1463
|
+
transcript: node.transcript ?? void 0,
|
|
1464
|
+
audio: node.audio ?? void 0,
|
|
1465
|
+
video: node.video ?? void 0
|
|
1466
|
+
};
|
|
1467
|
+
};
|
|
1468
|
+
var mapRecallEventToCallRecordingStatus = ({
|
|
1469
|
+
event,
|
|
1470
|
+
statusCode
|
|
1471
|
+
}) => {
|
|
1472
|
+
if (event === "recording.done") {
|
|
1473
|
+
return "PROCESSING" /* PROCESSING */;
|
|
1474
|
+
}
|
|
1475
|
+
if (event === "recording.failed") {
|
|
1476
|
+
return "FAILED" /* FAILED */;
|
|
1477
|
+
}
|
|
1478
|
+
return mapRecallStatusCodeToCallRecordingStatus(statusCode);
|
|
1479
|
+
};
|
|
1480
|
+
var buildRecordingTimestampsUpdate = ({
|
|
1481
|
+
webhookEvent,
|
|
1482
|
+
callRecording
|
|
1483
|
+
}) => {
|
|
1484
|
+
const { event, statusCode, statusTimestamp } = webhookEvent;
|
|
1485
|
+
const impliesRecordingStarted = statusCode === "in_call_recording";
|
|
1486
|
+
const impliesRecordingEnded = event === "recording.done" || statusCode === "call_ended" || statusCode === "done";
|
|
1487
|
+
const startedAt = webhookEvent.recordingStartedAt ?? (impliesRecordingStarted ? statusTimestamp : void 0);
|
|
1488
|
+
const endedAt = webhookEvent.recordingEndedAt ?? (impliesRecordingEnded ? statusTimestamp : void 0);
|
|
1489
|
+
return {
|
|
1490
|
+
...!(0, import_guards20.isUndefined)(startedAt) && (0, import_guards20.isUndefined)(callRecording.startedAt) ? { startedAt } : {},
|
|
1491
|
+
...!(0, import_guards20.isUndefined)(endedAt) && (0, import_guards20.isUndefined)(callRecording.endedAt) ? { endedAt } : {}
|
|
1492
|
+
};
|
|
1493
|
+
};
|
|
1494
|
+
var buildExternalRecordingIdUpdate = (webhookEvent) => (0, import_guards20.isUndefined)(webhookEvent.externalRecordingId) ? {} : { externalRecordingId: webhookEvent.externalRecordingId };
|
|
1495
|
+
var buildCallRecordingStatusUpdate = ({
|
|
1496
|
+
reason,
|
|
1497
|
+
status
|
|
1498
|
+
}) => {
|
|
1499
|
+
if (status === "FAILED" /* FAILED */) {
|
|
1500
|
+
return { status, callRecorderFailureReason: reason };
|
|
1501
|
+
}
|
|
1502
|
+
return { status };
|
|
1503
|
+
};
|
|
1504
|
+
var buildTerminalArtifactGateFailureUpdate = ({
|
|
1505
|
+
callRecording,
|
|
1506
|
+
providerLookupFailed,
|
|
1507
|
+
updateData,
|
|
1508
|
+
webhookEvent
|
|
1509
|
+
}) => {
|
|
1510
|
+
if (updateData.status === "FAILED" /* FAILED */) {
|
|
1511
|
+
return (0, import_guards20.isUndefined)(updateData.callRecorderFailureReason) ? {
|
|
1512
|
+
status: "FAILED" /* FAILED */,
|
|
1513
|
+
callRecorderFailureReason: getRecallWebhookFailureReason(webhookEvent)
|
|
1514
|
+
} : void 0;
|
|
1515
|
+
}
|
|
1516
|
+
if (providerLookupFailed || hasRecordingArtifactPath({ callRecording, updateData })) {
|
|
1517
|
+
return void 0;
|
|
1518
|
+
}
|
|
1519
|
+
return {
|
|
1520
|
+
status: "FAILED" /* FAILED */,
|
|
1521
|
+
callRecorderFailureReason: "recording_artifacts_unavailable"
|
|
1522
|
+
};
|
|
1523
|
+
};
|
|
1524
|
+
var getRecallWebhookFailureReason = ({
|
|
1525
|
+
event,
|
|
1526
|
+
statusCode
|
|
1527
|
+
}) => statusCode ?? event;
|
|
1528
|
+
var hasRecordingArtifactPath = ({
|
|
1529
|
+
callRecording,
|
|
1530
|
+
updateData
|
|
1531
|
+
}) => {
|
|
1532
|
+
return !(0, import_guards20.isUndefined)(
|
|
1533
|
+
updateData.externalRecordingId ?? callRecording.externalRecordingId
|
|
1534
|
+
) || (0, import_guards20.isNonEmptyArray)(updateData.audio ?? callRecording.audio) || (0, import_guards20.isNonEmptyArray)(updateData.video ?? callRecording.video) || hasReachableTranscript(updateData.transcript ?? callRecording.transcript);
|
|
1535
|
+
};
|
|
1536
|
+
var hasReachableTranscript = (transcript) => {
|
|
1537
|
+
if ((0, import_guards20.isNull)(transcript) || (0, import_guards20.isUndefined)(transcript)) {
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
const marker = parseTranscriptMarker(transcript);
|
|
1541
|
+
return (0, import_guards20.isUndefined)(marker) || marker.status === "PENDING";
|
|
1542
|
+
};
|
|
1543
|
+
var isTranscriptUnset = (callRecording) => (0, import_guards20.isUndefined)(callRecording.transcript);
|
|
1544
|
+
var buildMediaIngestionUpdate = async ({
|
|
1545
|
+
callRecording,
|
|
1546
|
+
externalRecordingId
|
|
1547
|
+
}) => {
|
|
1548
|
+
const hasAudio = (0, import_guards20.isNonEmptyArray)(callRecording.audio);
|
|
1549
|
+
const hasVideo = (0, import_guards20.isNonEmptyArray)(callRecording.video);
|
|
1550
|
+
if (hasAudio && hasVideo) {
|
|
1551
|
+
return {};
|
|
1552
|
+
}
|
|
1553
|
+
if ((0, import_guards20.isUndefined)(externalRecordingId)) {
|
|
1554
|
+
console.warn(
|
|
1555
|
+
`[call-recorder] cannot ingest media for call recording ${callRecording.id}: no Recall recording id available`
|
|
1556
|
+
);
|
|
1557
|
+
return {};
|
|
1558
|
+
}
|
|
1559
|
+
return ingestCallRecordingMedia({
|
|
1560
|
+
callRecordingId: callRecording.id,
|
|
1561
|
+
externalRecordingId,
|
|
1562
|
+
hasAudio,
|
|
1563
|
+
hasVideo
|
|
1564
|
+
});
|
|
1565
|
+
};
|
|
1566
|
+
var buildTranscriptArtifactUpdate = async ({
|
|
1567
|
+
callRecording,
|
|
1568
|
+
externalRecordingId
|
|
1569
|
+
}) => {
|
|
1570
|
+
if ((0, import_guards20.isUndefined)(externalRecordingId)) {
|
|
1571
|
+
console.warn(
|
|
1572
|
+
`[call-recorder] cannot reconcile transcript for call recording ${callRecording.id}: no Recall recording id available`
|
|
1573
|
+
);
|
|
1574
|
+
return {};
|
|
1575
|
+
}
|
|
1576
|
+
const transcriptArtifactResult = await reconcileCallRecordingTranscriptArtifact({
|
|
1577
|
+
callRecordingId: callRecording.id,
|
|
1578
|
+
currentStatus: callRecording.status,
|
|
1579
|
+
externalRecordingId,
|
|
1580
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1581
|
+
transcript: callRecording.transcript
|
|
1582
|
+
});
|
|
1583
|
+
return {
|
|
1584
|
+
...(0, import_guards20.isUndefined)(callRecording.externalRecordingId) ? { externalRecordingId } : {},
|
|
1585
|
+
...transcriptArtifactResult.updateData
|
|
1586
|
+
};
|
|
1587
|
+
};
|
|
1588
|
+
var resolveExternalRecordingId = async ({
|
|
1589
|
+
callRecording,
|
|
1590
|
+
webhookEvent
|
|
1591
|
+
}) => {
|
|
1592
|
+
const externalRecordingId = webhookEvent.externalRecordingId ?? callRecording.externalRecordingId;
|
|
1593
|
+
if (!(0, import_guards20.isUndefined)(externalRecordingId)) {
|
|
1594
|
+
return { externalRecordingId, providerLookupFailed: false };
|
|
1595
|
+
}
|
|
1596
|
+
if ((0, import_guards20.isUndefined)(webhookEvent.externalBotId)) {
|
|
1597
|
+
return { externalRecordingId: void 0, providerLookupFailed: false };
|
|
1598
|
+
}
|
|
1599
|
+
return fetchExternalRecordingIdFromRecallBot(webhookEvent.externalBotId);
|
|
1600
|
+
};
|
|
1601
|
+
var fetchExternalRecordingIdFromRecallBot = async (externalBotId) => {
|
|
1602
|
+
const botResult = await getRecallBot({ externalBotId });
|
|
1603
|
+
if (!botResult.ok) {
|
|
1604
|
+
console.warn(
|
|
1605
|
+
`[call-recorder] failed to fetch Recall bot ${externalBotId} while resolving a recording id: ${botResult.errorMessage}`
|
|
1606
|
+
);
|
|
1607
|
+
return { externalRecordingId: void 0, providerLookupFailed: true };
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
externalRecordingId: extractRecallBotConvergence(botResult.bot).externalRecordingId,
|
|
1611
|
+
providerLookupFailed: false
|
|
1612
|
+
};
|
|
1613
|
+
};
|
|
1614
|
+
var handleRecallTranscriptEvent = async ({
|
|
1615
|
+
client,
|
|
1616
|
+
webhookEvent,
|
|
1617
|
+
event
|
|
1618
|
+
}) => {
|
|
1619
|
+
const callRecording = await findMatchingCallRecording({
|
|
1620
|
+
client,
|
|
1621
|
+
webhookEvent
|
|
1622
|
+
});
|
|
1623
|
+
if ((0, import_guards20.isUndefined)(callRecording)) {
|
|
1624
|
+
return {
|
|
1625
|
+
status: "skipped",
|
|
1626
|
+
event,
|
|
1627
|
+
reason: "no matching call recording"
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const { transcriptId } = webhookEvent;
|
|
1631
|
+
if (event === "transcript.failed") {
|
|
1632
|
+
return applyTranscriptFailure({
|
|
1633
|
+
client,
|
|
1634
|
+
callRecording,
|
|
1635
|
+
event,
|
|
1636
|
+
transcriptId,
|
|
1637
|
+
subCode: webhookEvent.transcriptFailureSubCode ?? null
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
if ((0, import_guards20.isUndefined)(transcriptId)) {
|
|
1641
|
+
return {
|
|
1642
|
+
status: "skipped",
|
|
1643
|
+
event,
|
|
1644
|
+
reason: "missing transcript id"
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
const downloadResult = await downloadTranscript({ transcriptId });
|
|
1648
|
+
switch (downloadResult.outcome) {
|
|
1649
|
+
case "filled": {
|
|
1650
|
+
const updateData = {
|
|
1651
|
+
transcript: downloadResult.content,
|
|
1652
|
+
...(0, import_guards20.isUndefined)(callRecording.externalRecordingId) ? buildExternalRecordingIdUpdate(webhookEvent) : {}
|
|
1653
|
+
};
|
|
1654
|
+
await persistCallRecordingProgress(client, {
|
|
1655
|
+
id: callRecording.id,
|
|
1656
|
+
current: callRecording,
|
|
1657
|
+
updateData
|
|
1658
|
+
});
|
|
1659
|
+
return {
|
|
1660
|
+
status: "updated",
|
|
1661
|
+
event,
|
|
1662
|
+
callRecordingId: callRecording.id,
|
|
1663
|
+
transcriptOutcome: "FILLED"
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
case "failed":
|
|
1667
|
+
return applyTranscriptFailure({
|
|
1668
|
+
client,
|
|
1669
|
+
callRecording,
|
|
1670
|
+
event,
|
|
1671
|
+
transcriptId,
|
|
1672
|
+
subCode: downloadResult.subCode
|
|
1673
|
+
});
|
|
1674
|
+
case "pending":
|
|
1675
|
+
case "error": {
|
|
1676
|
+
const reason = downloadResult.outcome === "pending" ? "transcript not downloadable yet" : downloadResult.errorMessage;
|
|
1677
|
+
console.warn(
|
|
1678
|
+
`[call-recorder] could not fill transcript for call recording ${callRecording.id}: ${reason}`
|
|
1679
|
+
);
|
|
1680
|
+
return {
|
|
1681
|
+
status: "skipped",
|
|
1682
|
+
event,
|
|
1683
|
+
reason
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
var applyTranscriptFailure = async ({
|
|
1689
|
+
client,
|
|
1690
|
+
callRecording,
|
|
1691
|
+
event,
|
|
1692
|
+
transcriptId,
|
|
1693
|
+
subCode
|
|
1694
|
+
}) => {
|
|
1695
|
+
const existingMarker = parseTranscriptMarker(callRecording.transcript);
|
|
1696
|
+
if (!isTranscriptUnset(callRecording) && (0, import_guards20.isUndefined)(existingMarker)) {
|
|
1697
|
+
return {
|
|
1698
|
+
status: "skipped",
|
|
1699
|
+
event,
|
|
1700
|
+
reason: "transcript already filled"
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
console.warn(
|
|
1704
|
+
`[call-recorder] transcript failed for call recording ${callRecording.id}${(0, import_guards20.isNull)(subCode) ? "" : ` (${subCode})`}`
|
|
1705
|
+
);
|
|
1706
|
+
await updateCallRecording(client, {
|
|
1707
|
+
id: callRecording.id,
|
|
1708
|
+
data: {
|
|
1709
|
+
transcript: buildFailedTranscriptMarker({
|
|
1710
|
+
recallTranscriptId: transcriptId ?? existingMarker?.recallTranscriptId ?? null,
|
|
1711
|
+
subCode
|
|
1712
|
+
}),
|
|
1713
|
+
callRecorderFailureReason: buildTranscriptFailureReason(subCode),
|
|
1714
|
+
...isCallRecordingStatusDowngrade({
|
|
1715
|
+
fromStatus: callRecording.status,
|
|
1716
|
+
toStatus: "FAILED" /* FAILED */
|
|
1717
|
+
}) ? {} : { status: "FAILED" /* FAILED */ }
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
return {
|
|
1721
|
+
status: "updated",
|
|
1722
|
+
event,
|
|
1723
|
+
callRecordingId: callRecording.id,
|
|
1724
|
+
transcriptOutcome: "FAILED"
|
|
1725
|
+
};
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/logic-functions/process-recall-webhook.ts
|
|
1729
|
+
var processRecallWebhookHandler = (body) => handleRecallWebhook({
|
|
1730
|
+
client: new CoreApiClient(),
|
|
1731
|
+
body
|
|
1732
|
+
});
|
|
1733
|
+
var process_recall_webhook_default = defineLogicFunction({
|
|
1734
|
+
universalIdentifier: PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
1735
|
+
name: "process-recall-webhook",
|
|
1736
|
+
description: "Updates the matching CallRecording lifecycle status from a verified Recall.ai webhook event.",
|
|
1737
|
+
timeoutSeconds: 30,
|
|
1738
|
+
handler: processRecallWebhookHandler
|
|
1739
|
+
});
|
|
1740
|
+
export {
|
|
1741
|
+
process_recall_webhook_default as default,
|
|
1742
|
+
processRecallWebhookHandler
|
|
1743
|
+
};
|
|
1744
|
+
//# sourceMappingURL=process-recall-webhook.mjs.map
|