@avakado.ai/schemas 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -13
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/models/Action.js +19 -0
- package/src/models/Agent.js +219 -0
- package/src/models/BillingLogs.js +131 -0
- package/src/models/CallSessions.js +34 -0
- package/src/models/Channels.js +268 -0
- package/src/models/Collection.js +189 -0
- package/src/models/Data.js +35 -0
- package/src/models/Demonstarations.js +23 -0
- package/src/models/Document.js +15 -0
- package/src/models/ExternalServiceProviders.js +104 -0
- package/src/models/InbuiltNodes.js +19 -0
- package/src/models/Integrations.js +36 -0
- package/src/models/Job.js +119 -0
- package/src/models/Leads.js +84 -0
- package/src/models/Log.js +32 -0
- package/src/models/Messages.js +46 -0
- package/src/models/Plans.js +37 -0
- package/src/models/Tickets.js +50 -0
- package/src/models/User.js +473 -0
- package/src/models/Workflow.js +44 -0
- package/src/models/WorkflowLogs.js +22 -0
- package/src/models/apiAuthenticator.js +197 -0
- package/src/models/index.js +24 -0
- package/src/models/market.js +66 -0
- package/src/models/notifications.js +15 -0
- package/src/runtime.js +4 -0
package/README.md
CHANGED
|
@@ -22,8 +22,18 @@ configureSchemas({ sendKafkaMessage });
|
|
|
22
22
|
await mongoose.connect(process.env.MONGO_URI);
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
SaaS and Chat also pass the OAuth host map used by `ApiAuthenticators.refreshToken`:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
configureSchemas({
|
|
29
|
+
sendKafkaMessage,
|
|
30
|
+
getAuthProvider: (name) => PROVIDER_MAP[name],
|
|
31
|
+
providerSupportsRefresh,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
25
35
|
```js
|
|
26
|
-
import { Business,
|
|
36
|
+
import { Business, User, AgentModel, Channel, Workflow } from "@avakado.ai/schemas";
|
|
27
37
|
import { SUBSCRIPTION_STATUS, CURRENT_SUBSCRIPTION_STATUSES } from "@avakado.ai/schemas/enums";
|
|
28
38
|
import { Payment } from "@avakado.ai/schemas/models/Payments.js"; // narrower import, same registration
|
|
29
39
|
```
|
|
@@ -32,7 +42,10 @@ Every entry point returns the **same** registered model. Importing a model twice
|
|
|
32
42
|
via a subpath, or from two places in a dependency tree — reuses the existing registration instead of
|
|
33
43
|
throwing `OverwriteModelError`.
|
|
34
44
|
|
|
35
|
-
## What is in v1
|
|
45
|
+
## What is in v1.1
|
|
46
|
+
|
|
47
|
+
Every mongoose model the four services used. Where copies differed, the package keeps the **union**
|
|
48
|
+
of fields (and a superset of enums) so a save in any service still has a path to write.
|
|
36
49
|
|
|
37
50
|
| Model | Collection | Notes |
|
|
38
51
|
| --- | --- | --- |
|
|
@@ -43,15 +56,30 @@ throwing `OverwriteModelError`.
|
|
|
43
56
|
| `Conversation` | `Conversations` | includes `updateStatus`, which needs `sendKafkaMessage` |
|
|
44
57
|
| `Campaign` / `Task` | `Campaign` / `Tasks` | includes `updateTimeLine`; the task executor stays in socketio-server |
|
|
45
58
|
| `Trigger` | `triggers` | read by `Conversation.updateStatus` |
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
59
|
+
| `User` | `Users` | exports `ScopesEnum` / `RoleScopes` |
|
|
60
|
+
| `AgentModel` | `Agent` | runtime discriminators `TURN_BASED` / `REALTIME` / `BACKGROUND` |
|
|
61
|
+
| `Channel` | `Channel` | type enum includes both `website` and `web` |
|
|
62
|
+
| `CallSession` | `CallSession` | includes AvaPhone's `usage` |
|
|
63
|
+
| `Plan` | `Plans` | type enum includes `TEST` |
|
|
64
|
+
| `Api` / `Providers` | `Api` / `Providers` | |
|
|
65
|
+
| `Template` | `StandardProduct` | marketplace discriminator `agent` / `action` |
|
|
66
|
+
| `Message` / `MessageSession` | `Messages` / `MessageSessions` | |
|
|
67
|
+
| `Lead` / `LeadTemplate` | `Leads` / `LeadTemplates` | |
|
|
68
|
+
| `Log` | `Logs` | includes Lambda `error` / `response` |
|
|
69
|
+
| `BillingLog` | `BillingLogs` | |
|
|
70
|
+
| `ApiAuthenticators` | `ApiAuthenticators` | token refresh needs `getAuthProvider` |
|
|
71
|
+
| `DemonstrationModel` | `Demonstrations` | |
|
|
72
|
+
| `Action` | `Action` | |
|
|
73
|
+
| `Collection` | `Collection` | Lambda stage helpers are statics |
|
|
74
|
+
| `Node` / `NodeModel` | `Nodes` | same registration, two export names |
|
|
75
|
+
| `Integration` | `Integration` | |
|
|
76
|
+
| `Job` | `Job` | discriminator `outboundCall` |
|
|
77
|
+
| `Notification` | `Notification` | |
|
|
78
|
+
| `Ticket` | `Ticket` | |
|
|
79
|
+
| `Document` | `documents` | mongoose model name is `document` |
|
|
80
|
+
| `Data` | `Data` | |
|
|
81
|
+
| `Workflow` | `Workflow` | `updateStatus` / `getNextNode` live here; `execute` stays in socketio-server |
|
|
82
|
+
| `WorkflowLogs` | `WorkflowLogs` | |
|
|
55
83
|
|
|
56
84
|
## Behaviour that needs the host service
|
|
57
85
|
|
|
@@ -61,9 +89,11 @@ A package cannot import a service's `utils/`, so anything a shared model needs i
|
|
|
61
89
|
| Option | Used by | If missing |
|
|
62
90
|
| --- | --- | --- |
|
|
63
91
|
| `sendKafkaMessage` | `Conversation.updateStatus` | throws a named error telling you to call `configureSchemas` |
|
|
92
|
+
| `getAuthProvider` | `ApiAuthenticators` token refresh | throws if `refreshToken` / `ensureValidToken` / `_resolveProvider` runs |
|
|
93
|
+
| `providerSupportsRefresh` | `ApiAuthenticators` token refresh | same |
|
|
64
94
|
|
|
65
|
-
Methods that need more than a function — socketio-server's `Task.trigger` needs its `RealtimeServer
|
|
66
|
-
|
|
95
|
+
Methods that need more than a function — socketio-server's `Task.trigger` needs its `RealtimeServer`,
|
|
96
|
+
and `Workflow.execute` writes `WorkflowLogs` — stay in the service and attach to the shared model:
|
|
67
97
|
|
|
68
98
|
```js
|
|
69
99
|
import { Task } from "@avakado.ai/schemas";
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Schema } from "mongoose";
|
|
2
|
+
import { defineModel } from "../runtime.js";
|
|
3
|
+
const ActionSchema = new Schema({
|
|
4
|
+
name: String,
|
|
5
|
+
business: { type: Schema.Types.ObjectId, ref: 'Businesses' },
|
|
6
|
+
async: { type: Boolean, default: true },
|
|
7
|
+
description: String,
|
|
8
|
+
needsApproval: Boolean, // Knowledge fetching doesn't need approval
|
|
9
|
+
parameters: Schema.Types.Mixed,
|
|
10
|
+
config: Schema.Types.Mixed,
|
|
11
|
+
functionString: String,
|
|
12
|
+
errorFunction: String,
|
|
13
|
+
UI: Schema.Types.Mixed,
|
|
14
|
+
isPublic: { type: Boolean, default: false },
|
|
15
|
+
}, {
|
|
16
|
+
timestamps: true
|
|
17
|
+
});
|
|
18
|
+
export const Action = defineModel('Action', ActionSchema, "Action");
|
|
19
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Schema } from "mongoose";
|
|
2
|
+
import { defineModel } from "../runtime.js";
|
|
3
|
+
|
|
4
|
+
const ProviderConfig = {
|
|
5
|
+
openai: {
|
|
6
|
+
TURN_BASED: {
|
|
7
|
+
models: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'],
|
|
8
|
+
},
|
|
9
|
+
REALTIME: {
|
|
10
|
+
models: ['gpt-realtime', 'gpt-realtime-mini', 'gpt-realtime-2.1', 'gpt-realtime-2.1-mini', 'gpt-realtime-2.0', 'gpt-realtime-1.5'],
|
|
11
|
+
voices: ['alloy', 'ash', 'ballad', 'coral', 'echo', 'sage', 'shimmer', 'verse', 'marin', 'cedar'],
|
|
12
|
+
modalities: ['audio', 'text'],
|
|
13
|
+
wssUrl: 'wss://api.openai.com/v1/realtime',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
gemini: {
|
|
17
|
+
TURN_BASED: {
|
|
18
|
+
models: ['gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-3.5-flash', 'gemini-3.1-flash-lite', 'gemini-2.5-pro'],
|
|
19
|
+
},
|
|
20
|
+
REALTIME: {
|
|
21
|
+
models: ['gemini-2.5-flash-native-audio-latest'],
|
|
22
|
+
voices: ['Zephyr', 'Puck', 'Charon', 'Kore', 'Fenrir', 'Leda', 'Orus', 'Aoede', 'Callirrhoe', 'Autonoe', 'Enceladus', 'Iapetus', 'Umbriel', 'Algieba', 'Despina', 'Erinome', 'Algenib', 'Rasalgethi', 'Laomedeia', 'Achernar', 'Alnilam', 'Schedar', 'Gacrux', 'Pulcherrima', 'Achird', 'Zubenelgenubi', 'Vindemiatrix', 'Sadachbia', 'Sadaltager', 'Sulafat'],
|
|
23
|
+
modalities: ['AUDIO', 'TEXT'],
|
|
24
|
+
wssUrl: 'wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
anthropic: {
|
|
28
|
+
TURN_BASED: { models: ['claude-opus-4-8', 'claude-sonnet-5', 'claude-haiku-4-5-20251001'] },
|
|
29
|
+
REALTIME: null
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// --- AGENT SCHEMA ---
|
|
35
|
+
const ModelConfigSchema = new Schema({
|
|
36
|
+
provider: { type: String, enum: [...Object.keys(ProviderConfig), 'custom'], required: true, default: 'openai' },
|
|
37
|
+
customProviderRef: { type: String, required: function () { return this.provider === 'custom' } },
|
|
38
|
+
providerData: Schema.Types.Mixed,
|
|
39
|
+
model: {
|
|
40
|
+
type: String, required: true, validate: {
|
|
41
|
+
validator: function (value) {
|
|
42
|
+
if (this.provider === 'custom') return true;
|
|
43
|
+
return ProviderConfig[this.provider]?.[this._doc?.runtime]?.models.includes(value);
|
|
44
|
+
}, message: props => `Model "${props.value}" is not valid for provider "${this.provider}"`
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
modelSettings: {
|
|
48
|
+
temperature: { type: Number, default: 0.3 },
|
|
49
|
+
toolChoice: { type: String, default: 'auto' },
|
|
50
|
+
topP: Number,
|
|
51
|
+
frequencyPenalty: Number,
|
|
52
|
+
presencePenalty: Number,
|
|
53
|
+
parallelToolCalls: Boolean,
|
|
54
|
+
truncation: { type: String, enum: ['auto', 'disabled'] },
|
|
55
|
+
maxTokens: Number,
|
|
56
|
+
store: Boolean,
|
|
57
|
+
reasoning: {
|
|
58
|
+
effort: { type: String, enum: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] },
|
|
59
|
+
mode: String,
|
|
60
|
+
context: { type: String, enum: ['auto', 'current_turn', 'all_turns', null] },
|
|
61
|
+
summary: { type: String, enum: ['auto', 'concise', 'detailed'] },
|
|
62
|
+
},
|
|
63
|
+
text: { verbosity: { type: String, enum: ['low', 'medium', 'high'] } },
|
|
64
|
+
promptCacheOptions: {
|
|
65
|
+
mode: { type: String, enum: ['implicit', 'explicit'] },
|
|
66
|
+
ttl: { type: String, enum: ['30m'] },
|
|
67
|
+
},
|
|
68
|
+
promptCacheRetention: { type: String, enum: ['in-memory', '24h', null] },
|
|
69
|
+
// retry: {
|
|
70
|
+
// maxRetries: Number,
|
|
71
|
+
// backoff: {
|
|
72
|
+
// initialDelayMs: Number,
|
|
73
|
+
// maxDelayMs: Number,
|
|
74
|
+
// multiplier: Number,
|
|
75
|
+
// jitter: Boolean,
|
|
76
|
+
// },
|
|
77
|
+
// policy: { type: String, enum: ['providerSuggested', 'networkError', 'retryAfter', 'never'] },
|
|
78
|
+
// },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{ _id: false }
|
|
82
|
+
);
|
|
83
|
+
const ResponseConfigSchema = new Schema({
|
|
84
|
+
provider: { type: String, enum: [...Object.keys(ProviderConfig), 'elevenlabs', 'custom'], required: true },
|
|
85
|
+
}, { _id: false, discriminatorKey: 'provider' });
|
|
86
|
+
const RealtimeConfigSchema = new Schema({
|
|
87
|
+
modality: {
|
|
88
|
+
type: String, required: true,
|
|
89
|
+
validate: {
|
|
90
|
+
validator(value) {
|
|
91
|
+
const provider = this._doc?.modelConfig?.provider;
|
|
92
|
+
const runtime = this._doc?.runtime;
|
|
93
|
+
return ProviderConfig[provider]?.[runtime]?.modalities?.includes(value);
|
|
94
|
+
},
|
|
95
|
+
message: props => `Invalid modality "${props.value}" for this provider`,
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
responseConfig: ResponseConfigSchema
|
|
99
|
+
}, { _id: false });
|
|
100
|
+
const responseConfigPath = RealtimeConfigSchema.path('responseConfig');
|
|
101
|
+
responseConfigPath.discriminator('openai', new Schema({
|
|
102
|
+
wssUrl: { type: String, default: ProviderConfig.openai?.REALTIME?.wssUrl },
|
|
103
|
+
audio: {
|
|
104
|
+
input: {
|
|
105
|
+
noise_reduction: { type: String, enum: ['near_field', 'far_field'], default: 'near_field' },
|
|
106
|
+
transcription: { model: { type: String, default: "whisper-1" }, prompt: String, language: String },
|
|
107
|
+
turn_detection: {
|
|
108
|
+
type: { type: String, enum: ['server_vad', 'semantic_vad'], default: 'server_vad' },
|
|
109
|
+
// shared
|
|
110
|
+
create_response: { type: Boolean, default: true },
|
|
111
|
+
interrupt_response: { type: Boolean, default: true },
|
|
112
|
+
// server_vad only
|
|
113
|
+
prefix_padding_ms: {
|
|
114
|
+
type: Number,
|
|
115
|
+
default: function () { return this._doc.audio.input.turn_detection.type === 'server_vad' ? 300 : undefined; },
|
|
116
|
+
validate: {
|
|
117
|
+
validator(value) {
|
|
118
|
+
if (value == null) return true;
|
|
119
|
+
return this._doc.audio.input.turn_detection.type === 'server_vad';
|
|
120
|
+
},
|
|
121
|
+
message: 'prefix_padding_ms is only valid for server_vad',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
silence_duration_ms: {
|
|
125
|
+
type: Number,
|
|
126
|
+
default: function () { return this._doc.audio.input.turn_detection.type === 'server_vad' ? 500 : undefined; },
|
|
127
|
+
validate: {
|
|
128
|
+
validator(value) {
|
|
129
|
+
if (value == null) return true;
|
|
130
|
+
return this._doc.audio.input.turn_detection.type === 'server_vad';
|
|
131
|
+
},
|
|
132
|
+
message: 'silence_duration_ms is only valid for server_vad',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
threshold: {
|
|
136
|
+
type: Number,
|
|
137
|
+
min: 0,
|
|
138
|
+
max: 1,
|
|
139
|
+
default: function () { return this._doc.audio.input.turn_detection.type === 'server_vad' ? 0.5 : undefined; },
|
|
140
|
+
validate: {
|
|
141
|
+
validator(value) {
|
|
142
|
+
if (value == null) return true;
|
|
143
|
+
return this._doc.audio.input.turn_detection.type === 'server_vad';
|
|
144
|
+
},
|
|
145
|
+
message: 'threshold is only valid for server_vad',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
// semantic_vad only
|
|
149
|
+
eagerness: {
|
|
150
|
+
type: String,
|
|
151
|
+
enum: ['low', 'medium', 'high', 'auto'],
|
|
152
|
+
default: function () { return this._doc.audio.input.turn_detection.type === 'semantic_vad' ? 'auto' : undefined; },
|
|
153
|
+
validate: {
|
|
154
|
+
validator(value) {
|
|
155
|
+
if (value == null) return true;
|
|
156
|
+
return this._doc.audio.input.turn_detection.type === 'semantic_vad';
|
|
157
|
+
},
|
|
158
|
+
message: 'eagerness is only valid for semantic_vad',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
output: {
|
|
164
|
+
speed: { type: Number, min: 0.25, max: 2.0, default: 1.0 },
|
|
165
|
+
voice: { type: String, validate: { validator: function (value) { return ProviderConfig.openai?.REALTIME?.voices.includes(value); }, message: props => `Invalid voice for openai` } },
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}, { _id: false }));
|
|
169
|
+
responseConfigPath.discriminator('gemini', new Schema({
|
|
170
|
+
wssUrl: { type: String, default: ProviderConfig.gemini?.REALTIME?.wssUrl },
|
|
171
|
+
proactivity: { proactiveAudio: { type: Boolean, default: true } },
|
|
172
|
+
enableAffectiveDialog: { type: Boolean, default: true },
|
|
173
|
+
realtimeInputConfig: {
|
|
174
|
+
automaticActivityDetection: {
|
|
175
|
+
disabled: { type: Boolean, default: false },
|
|
176
|
+
startOfSpeechSensitivity: { type: String, enum: ['START_SENSITIVITY_LOW', 'START_SENSITIVITY_HIGH'], default: 'START_SENSITIVITY_HIGH' }, // gemini
|
|
177
|
+
endOfSpeechSensitivity: { type: String, enum: ['END_SENSITIVITY_LOW', 'END_SENSITIVITY_HIGH'], default: 'END_SENSITIVITY_LOW' }, // gemini
|
|
178
|
+
prefixPaddingMs: { type: Number, default: 300 },
|
|
179
|
+
silenceDurationMs: { type: Number, default: 1000 },
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
realtimeOutputConfig: {
|
|
183
|
+
speed: { type: Number, min: 0.25, max: 2.0, default: 1.0 },
|
|
184
|
+
voice: { type: String, validate: { validator: function (value) { return ProviderConfig.gemini?.REALTIME?.voices.includes(value); }, message: props => `Invalid voice for openai` } },
|
|
185
|
+
},
|
|
186
|
+
inputAudioTranscription: Schema.Types.Mixed,
|
|
187
|
+
outputAudioTranscription: Schema.Types.Mixed,
|
|
188
|
+
}, { _id: false }));
|
|
189
|
+
const AgentSchema = new Schema({
|
|
190
|
+
personalInfo: {
|
|
191
|
+
name: String,
|
|
192
|
+
description: String,
|
|
193
|
+
avatar: String,
|
|
194
|
+
systemPrompt: {
|
|
195
|
+
role: String,
|
|
196
|
+
objective: String,
|
|
197
|
+
instructionsAndWorkflow: String,
|
|
198
|
+
constraintsAndRules: String
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
runtime: { type: String, enum: ['TURN_BASED', 'REALTIME', 'BACKGROUND'], default: 'TURN_BASED' },
|
|
202
|
+
modelConfig: ModelConfigSchema,
|
|
203
|
+
workflow: { type: Schema.Types.ObjectId, ref: 'Workflow' },
|
|
204
|
+
collections: [{ type: Schema.Types.ObjectId, ref: 'Collection' }],
|
|
205
|
+
channels: [{ type: Schema.Types.ObjectId, ref: 'Channel' }],
|
|
206
|
+
actions: [{ type: Schema.Types.ObjectId, ref: 'Action' }],
|
|
207
|
+
tool_choice: { type: String, enum: ['auto', 'none', 'required'], default: "auto" },
|
|
208
|
+
business: { type: Schema.Types.ObjectId, ref: 'Businesses' },
|
|
209
|
+
createdBy: { type: Schema.Types.ObjectId, ref: 'Users' },
|
|
210
|
+
isPublic: { type: Boolean, default: false },
|
|
211
|
+
isFeatured: { type: Boolean, default: false },
|
|
212
|
+
}, {
|
|
213
|
+
discriminatorKey: 'runtime',
|
|
214
|
+
timestamps: true
|
|
215
|
+
});
|
|
216
|
+
AgentSchema.discriminator('TURN_BASED', new Schema({}, { _id: false }))
|
|
217
|
+
AgentSchema.discriminator('REALTIME', RealtimeConfigSchema)
|
|
218
|
+
AgentSchema.discriminator('BACKGROUND', new Schema({}, { _id: false }))
|
|
219
|
+
export const AgentModel = defineModel('Agent', AgentSchema, "Agent");
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Schema } from "mongoose";
|
|
2
|
+
import { defineModel } from "../runtime.js";
|
|
3
|
+
|
|
4
|
+
// ─── Enums ────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const BILLING_CATEGORY = [
|
|
7
|
+
// AI
|
|
8
|
+
"ai.conversation", // agent replying to a lead
|
|
9
|
+
"ai.analysis", // post-conversation analysis
|
|
10
|
+
"ai.summarization", // conversation summarization
|
|
11
|
+
"ai.transcription", // voice/audio → text (Whisper)
|
|
12
|
+
"ai.embedding", // vector embeddings
|
|
13
|
+
// Messaging
|
|
14
|
+
"messaging.outbound", // outbound message sent via WhatsApp/Telegram/SMS
|
|
15
|
+
"messaging.inbound", // inbound (if your provider charges per inbound)
|
|
16
|
+
"messaging.template", // WhatsApp template message (meta charges separately)
|
|
17
|
+
// Workflow
|
|
18
|
+
"workflow.execution", // workflow triggered and ran
|
|
19
|
+
"workflow.step", // individual step within a workflow
|
|
20
|
+
// Platform
|
|
21
|
+
"platform.api_call", // external API call made on behalf of business
|
|
22
|
+
"platform.storage", // file/media storage
|
|
23
|
+
"platform.export", // data export
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const BILLING_STATUS = ["success", "failed", "refunded", "pending"];
|
|
27
|
+
|
|
28
|
+
const CHANNEL_TYPE = ["Whatsapp", "Telegram", "SMS", "Email", "Web", "Phone"];
|
|
29
|
+
|
|
30
|
+
// ─── Sub-schemas ──────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
// AI-specific usage — only populated for ai.* categories
|
|
33
|
+
const aiUsageSchema = new Schema({
|
|
34
|
+
model: { type: String }, // "gpt-4o-mini", "whisper-1"
|
|
35
|
+
inputTokens: { type: Number, default: 0 },
|
|
36
|
+
cachedTokens: { type: Number, default: 0 }, // prompt cache hits (cheaper)
|
|
37
|
+
outputTokens: { type: Number, default: 0 },
|
|
38
|
+
totalTokens: { type: Number, default: 0 },
|
|
39
|
+
turns: { type: Number, default: 1 }, // how many agent turns ran
|
|
40
|
+
toolCallCount: { type: Number, default: 0 }, // number of tool calls made
|
|
41
|
+
}, { _id: false });
|
|
42
|
+
|
|
43
|
+
// Messaging-specific — only for messaging.* categories
|
|
44
|
+
const messagingUsageSchema = new Schema({
|
|
45
|
+
channel: { type: String, enum: CHANNEL_TYPE },
|
|
46
|
+
provider: { type: String }, // "meta", "twilio", "vonage"
|
|
47
|
+
messageType: { type: String }, // "text", "template", "media"
|
|
48
|
+
segmentCount: { type: Number, default: 1 }, // SMS segments
|
|
49
|
+
templateName: { type: String }, // WhatsApp template name
|
|
50
|
+
direction: { type: String, enum: ["inbound", "outbound"] },
|
|
51
|
+
}, { _id: false });
|
|
52
|
+
|
|
53
|
+
// Workflow-specific
|
|
54
|
+
const workflowUsageSchema = new Schema({
|
|
55
|
+
stepsExecuted: { type: Number, default: 0 },
|
|
56
|
+
stepsFailed: { type: Number, default: 0 },
|
|
57
|
+
durationMs: { type: Number }, // total execution time
|
|
58
|
+
}, { _id: false });
|
|
59
|
+
|
|
60
|
+
// Cost breakdown — always populated
|
|
61
|
+
const costSchema = new Schema({
|
|
62
|
+
// Raw provider cost
|
|
63
|
+
providerCostUsd: { type: Number, default: 0 }, // what YOU pay provider
|
|
64
|
+
// What you charge the business
|
|
65
|
+
creditRate: { type: Number }, // your spendRatio at time of billing
|
|
66
|
+
creditsCharged: { type: Number, default: 0 }, // credits deducted from business
|
|
67
|
+
// For reconciliation
|
|
68
|
+
currency: { type: String, default: "USD" },
|
|
69
|
+
}, { _id: false });
|
|
70
|
+
|
|
71
|
+
// References — what triggered this log entry
|
|
72
|
+
const sourceSchema = new Schema({
|
|
73
|
+
conversation: { type: Schema.Types.ObjectId, ref: "Conversation" },
|
|
74
|
+
message: { type: Schema.Types.ObjectId, ref: "Message" },
|
|
75
|
+
campaign: { type: Schema.Types.ObjectId, ref: "Campaign" },
|
|
76
|
+
workflow: { type: Schema.Types.ObjectId, ref: "Workflow" },
|
|
77
|
+
channel: { type: Schema.Types.ObjectId, ref: "Channel" },
|
|
78
|
+
agent: { type: Schema.Types.ObjectId, ref: "Agent" },
|
|
79
|
+
lead: { type: Schema.Types.ObjectId, ref: "Lead" },
|
|
80
|
+
// The external ID from the provider (wamid, etc.)
|
|
81
|
+
externalId: { type: String },
|
|
82
|
+
}, { _id: false });
|
|
83
|
+
|
|
84
|
+
// ─── Main Schema ──────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
const BillingLogSchema = new Schema({
|
|
87
|
+
// ── Who ───────────────────────────────────────────────────────────
|
|
88
|
+
business: { type: Schema.Types.ObjectId, ref: 'Businesses', required: true },
|
|
89
|
+
|
|
90
|
+
// ── What ──────────────────────────────────────────────────────────
|
|
91
|
+
category: { type: String, enum: BILLING_CATEGORY, required: true },
|
|
92
|
+
status: { type: String, enum: BILLING_STATUS, default: "success" },
|
|
93
|
+
|
|
94
|
+
// ── Why / Where ───────────────────────────────────────────────────
|
|
95
|
+
source: { type: sourceSchema, default: () => ({}) },
|
|
96
|
+
|
|
97
|
+
// ── Usage detail (only relevant sub-schema will be populated) ─────
|
|
98
|
+
ai: { type: aiUsageSchema },
|
|
99
|
+
messaging: { type: messagingUsageSchema },
|
|
100
|
+
workflow: { type: workflowUsageSchema },
|
|
101
|
+
|
|
102
|
+
// ── Cost ──────────────────────────────────────────────────────────
|
|
103
|
+
cost: { type: costSchema, required: true },
|
|
104
|
+
|
|
105
|
+
// ── Failure detail (if status = failed) ───────────────────────────
|
|
106
|
+
error: { type: String },
|
|
107
|
+
|
|
108
|
+
// ── Catch-all for provider-specific raw data ───────────────────────
|
|
109
|
+
// e.g. WhatsApp pricing object, raw OpenAI usage response
|
|
110
|
+
raw: { type: Schema.Types.Mixed },
|
|
111
|
+
|
|
112
|
+
}, { timestamps: true });
|
|
113
|
+
|
|
114
|
+
// ─── Indexes ──────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
// Dashboard: "show me all logs for business X in date range"
|
|
117
|
+
BillingLogSchema.index({ business: 1, createdAt: -1 });
|
|
118
|
+
|
|
119
|
+
// Category breakdown: "show me all AI usage for business X"
|
|
120
|
+
BillingLogSchema.index({ business: 1, category: 1, createdAt: -1 });
|
|
121
|
+
|
|
122
|
+
// Conversation drill-down: "how much did this conversation cost?"
|
|
123
|
+
BillingLogSchema.index({ "source.conversation": 1 });
|
|
124
|
+
|
|
125
|
+
// Campaign cost totals
|
|
126
|
+
BillingLogSchema.index({ "source.campaign": 1 });
|
|
127
|
+
|
|
128
|
+
// Failed billing reconciliation
|
|
129
|
+
BillingLogSchema.index({ business: 1, status: 1 });
|
|
130
|
+
|
|
131
|
+
export const BillingLog = defineModel('BillingLog', BillingLogSchema, 'BillingLogs');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Schema } from "mongoose";
|
|
2
|
+
import { defineModel } from "../runtime.js";
|
|
3
|
+
|
|
4
|
+
const TranscriptSchema = new Schema({
|
|
5
|
+
speaker: { type: String, enum: ["Lead", "agent", "user", "system"] },
|
|
6
|
+
transcript: String,
|
|
7
|
+
startMs: Number, // offset from call start
|
|
8
|
+
endMs: Number,
|
|
9
|
+
confidence: Number,
|
|
10
|
+
source: String, // provider | whisper | deepgram | …
|
|
11
|
+
usage: Schema.Types.Mixed,
|
|
12
|
+
}, { _id: false });
|
|
13
|
+
const CallSessionSchema = new Schema({
|
|
14
|
+
lead: { type: Schema.Types.ObjectId, ref: 'Lead' },
|
|
15
|
+
conversation: { type: Schema.Types.ObjectId, ref: 'Conversation' },
|
|
16
|
+
campaign: Schema.Types.ObjectId, // participant of the campaign(bulk messaging)
|
|
17
|
+
business: { type: Schema.Types.ObjectId, ref: 'Businesses' },
|
|
18
|
+
channel: { type: Schema.Types.ObjectId, ref: "Channel" },
|
|
19
|
+
agent: { type: Schema.Types.ObjectId, ref: 'Agent' },
|
|
20
|
+
externalCallSessionId: String,
|
|
21
|
+
direction: String,
|
|
22
|
+
statusTimeline: {
|
|
23
|
+
scheduledAt: Date, initiatedAt: Date, ringingAt: Date, in_progressAt: Date, completedAt: Date, failedAt: Date, busyAt: Date, no_answerAt: Date, canceledAt: Date, rejectedAt: Date, durationSec: { type: Number, default: 0 },
|
|
24
|
+
},
|
|
25
|
+
recording: { status: { type: String, enum: ["none", "pending", "stored", "failed"], default: "none" }, key: String, url: String },
|
|
26
|
+
transcripts: { type: [TranscriptSchema], default: [] }, // see note in chat re: very long calls
|
|
27
|
+
callDetails: Schema.Types.Mixed,
|
|
28
|
+
usage: { type: Schema.Types.Mixed, default: {} },
|
|
29
|
+
sequenceOfEvents: [Schema.Types.Mixed],
|
|
30
|
+
isSummarized: { type: Boolean, default: false }
|
|
31
|
+
}, {
|
|
32
|
+
timestamps: true
|
|
33
|
+
});
|
|
34
|
+
export const CallSession = defineModel('CallSession', CallSessionSchema, 'CallSession');
|