@kognitivedev/backend-cloud 0.2.29

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.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-test.log +14 -0
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +88 -0
  5. package/dist/cloud-voice-parameters.d.ts +11 -0
  6. package/dist/cloud-voice-parameters.js +219 -0
  7. package/dist/cloud-voice-prompt-service.d.ts +24 -0
  8. package/dist/cloud-voice-prompt-service.js +382 -0
  9. package/dist/cloud-voice-runtime-service.d.ts +73 -0
  10. package/dist/cloud-voice-runtime-service.js +443 -0
  11. package/dist/cloud-voice.d.ts +36 -0
  12. package/dist/cloud-voice.js +683 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.js +26 -0
  15. package/dist/phone-control.d.ts +50 -0
  16. package/dist/phone-control.js +97 -0
  17. package/dist/phone-runtime/audio-playout-tracker.d.ts +51 -0
  18. package/dist/phone-runtime/audio-playout-tracker.js +93 -0
  19. package/dist/phone-runtime/openai-twilio-realtime.d.ts +95 -0
  20. package/dist/phone-runtime/openai-twilio-realtime.js +1074 -0
  21. package/dist/tools.d.ts +2 -0
  22. package/dist/tools.js +216 -0
  23. package/dist/types.d.ts +468 -0
  24. package/dist/types.js +2 -0
  25. package/dist/utils.d.ts +3 -0
  26. package/dist/utils.js +14 -0
  27. package/package.json +47 -0
  28. package/src/__tests__/audio-playout-tracker.test.ts +46 -0
  29. package/src/__tests__/cloud-voice.test.ts +1006 -0
  30. package/src/__tests__/openai-twilio-realtime.test.ts +1193 -0
  31. package/src/__tests__/phone-control.test.ts +105 -0
  32. package/src/cloud-voice-parameters.ts +236 -0
  33. package/src/cloud-voice-prompt-service.ts +493 -0
  34. package/src/cloud-voice-runtime-service.ts +465 -0
  35. package/src/cloud-voice.ts +831 -0
  36. package/src/index.ts +10 -0
  37. package/src/phone-control.ts +156 -0
  38. package/src/phone-runtime/audio-playout-tracker.ts +132 -0
  39. package/src/phone-runtime/openai-twilio-realtime.ts +1250 -0
  40. package/src/tools.ts +227 -0
  41. package/src/types.ts +529 -0
  42. package/src/utils.ts +11 -0
  43. package/tsconfig.json +13 -0
@@ -0,0 +1,2 @@
1
+
2
+ $ tsc
@@ -0,0 +1,14 @@
1
+ $ vitest run
2
+
3
+ RUN v3.2.4 /Users/vserifsaglam/work/memory-experiment/packages/backend-cloud
4
+
5
+ ✓ src/__tests__/audio-playout-tracker.test.ts (1 test) 3ms
6
+ ✓ src/__tests__/phone-control.test.ts (4 tests) 2ms
7
+ ✓ src/__tests__/cloud-voice.test.ts (21 tests) 20ms
8
+ ✓ src/__tests__/openai-twilio-realtime.test.ts (11 tests) 142ms
9
+
10
+ Test Files 4 passed (4)
11
+ Tests 37 passed (37)
12
+ Start at 17:30:13
13
+ Duration 477ms (transform 288ms, setup 0ms, collect 515ms, tests 168ms, environment 0ms, prepare 330ms)
14
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @kognitivedev/backend-cloud
2
+
3
+ ## 0.2.29
4
+
5
+ ### Patch Changes
6
+
7
+ - release
8
+
9
+ - Updated dependencies []:
10
+ - @kognitivedev/telephony@0.2.29
11
+ - @kognitivedev/voice-media-bridge@0.2.29
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @kognitivedev/backend-cloud
2
+
3
+ Reusable backend orchestration helpers for Kognitive Cloud products.
4
+
5
+ This package contains provider-neutral Cloud Voice backend mechanics: session
6
+ preparation, tool manifest generation, managed tool orchestration,
7
+ phone session snapshots, and managed phone-control tools such as
8
+ `hang_up_call`.
9
+
10
+ It intentionally does not import Twilio, Next.js, Drizzle, Clerk, or any
11
+ `apps/backend` aliases. Applications compose those concrete dependencies.
12
+
13
+ ## Boundaries
14
+
15
+ - `@kognitivedev/backend-cloud` owns Cloud Voice orchestration and interfaces.
16
+ - `@kognitivedev/telephony` owns provider mechanics such as Twilio REST,
17
+ TwiML, signatures, and media stream helpers.
18
+ - `apps/backend` owns routes, auth, DB stores, trace/session persistence,
19
+ environment loading, and concrete adapter registration.
20
+
21
+ ## Prepare a Session
22
+
23
+ ```ts
24
+ import {
25
+ createPhonePrepareSnapshot,
26
+ prepareCloudVoiceSessionConfig,
27
+ } from "@kognitivedev/backend-cloud";
28
+
29
+ const prepare = prepareCloudVoiceSessionConfig(agentConfig, {
30
+ agentName: agent.name,
31
+ sessionId,
32
+ resourceId: { userId },
33
+ channel: "phone",
34
+ });
35
+
36
+ const snapshot = createPhonePrepareSnapshot({
37
+ agent,
38
+ channel: "phone",
39
+ config: agentConfig,
40
+ prepare,
41
+ });
42
+ ```
43
+
44
+ Phone channels automatically receive the built-in `hang_up_call` manifest.
45
+ Web, iframe, and script channels do not.
46
+
47
+ ## Execute Tools
48
+
49
+ `executeCloudVoiceToolBinding` is dependency-injected. It does not persist
50
+ events or read databases.
51
+
52
+ ```ts
53
+ const { result, metadata } = await executeCloudVoiceToolBinding({
54
+ projectId,
55
+ sessionId,
56
+ tool,
57
+ args,
58
+ resourceId,
59
+ executeWebSearch,
60
+ executeCloudFlow,
61
+ executeKnowledgeBase,
62
+ executePlatformAction,
63
+ });
64
+ ```
65
+
66
+ The app should persist `voice.tool.started`, `voice.tool.completed`, and
67
+ `voice.tool.failed` events around this call.
68
+
69
+ ## Phone Control
70
+
71
+ Phone control is provider-neutral. The package defines the tool flow; provider
72
+ packages implement the mechanics.
73
+
74
+ ```ts
75
+ const result = await executeCloudVoicePhoneControlTool({
76
+ toolId: "hang_up_call",
77
+ provider: callLeg.provider,
78
+ providerCallId: callLeg.providerCallId,
79
+ args,
80
+ adapters: [twilioPhoneControlAdapter],
81
+ });
82
+ ```
83
+
84
+ To add another phone provider:
85
+
86
+ 1. Implement a provider adapter in `@kognitivedev/telephony`.
87
+ 2. Register that adapter from `apps/backend`.
88
+ 3. Keep Cloud Voice tool orchestration unchanged.
@@ -0,0 +1,11 @@
1
+ import type { CloudVoiceAgentConfig, CloudVoiceParameterDefinition, CloudVoiceParameterResolutionMode, CloudVoiceParameterResolutionResult, CloudVoiceParameterValueMap } from "./types";
2
+ export declare const CLOUD_VOICE_PREDEFINED_PARAMETERS: CloudVoiceParameterDefinition[];
3
+ export declare function normalizeCloudVoiceParameterDefinitions(value: unknown): CloudVoiceParameterDefinition[];
4
+ export declare function resolveCloudVoiceParameters(input: {
5
+ config: Pick<CloudVoiceAgentConfig, "parameters">;
6
+ supplied?: CloudVoiceParameterValueMap | null;
7
+ system?: Record<string, unknown> | null;
8
+ mode?: CloudVoiceParameterResolutionMode;
9
+ }): CloudVoiceParameterResolutionResult;
10
+ export declare function renderCloudVoiceParameterTemplate(text: string, parameters: CloudVoiceParameterValueMap): string;
11
+ export declare function renderCloudVoiceParameterObject<T>(value: T, parameters: CloudVoiceParameterValueMap): T;
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLOUD_VOICE_PREDEFINED_PARAMETERS = void 0;
4
+ exports.normalizeCloudVoiceParameterDefinitions = normalizeCloudVoiceParameterDefinitions;
5
+ exports.resolveCloudVoiceParameters = resolveCloudVoiceParameters;
6
+ exports.renderCloudVoiceParameterTemplate = renderCloudVoiceParameterTemplate;
7
+ exports.renderCloudVoiceParameterObject = renderCloudVoiceParameterObject;
8
+ const utils_1 = require("./utils");
9
+ exports.CLOUD_VOICE_PREDEFINED_PARAMETERS = [
10
+ { key: "user_id", label: "User ID", type: "string", source: "system", preset: "user_id", description: "Resolved user identifier for the voice session." },
11
+ { key: "session_id", label: "Session ID", type: "string", source: "system", preset: "session_id", description: "Public Cloud Voice session id." },
12
+ { key: "agent_slug", label: "Agent slug", type: "string", source: "system", preset: "agent_slug", description: "Slug of the selected voice agent." },
13
+ { key: "channel", label: "Channel", type: "string", source: "system", preset: "channel", description: "Run channel such as web, script, phone, or outbound." },
14
+ { key: "phone", label: "Phone", type: "phone", source: "call", preset: "phone", description: "Primary phone number for the caller when available." },
15
+ { key: "caller_phone", label: "Caller phone", type: "phone", source: "call", preset: "caller_phone", description: "Caller phone number when available." },
16
+ { key: "from_phone", label: "From phone", type: "phone", source: "call", preset: "from_phone", description: "Originating phone number when available." },
17
+ { key: "to_phone", label: "To phone", type: "phone", source: "call", preset: "to_phone", description: "Destination phone number when available." },
18
+ { key: "provider", label: "Telephony provider", type: "string", source: "call", preset: "provider", description: "Phone provider when available." },
19
+ { key: "provider_call_id", label: "Provider call ID", type: "string", source: "call", preset: "provider_call_id", description: "Provider call id when available." },
20
+ { key: "call_direction", label: "Call direction", type: "string", source: "call", preset: "call_direction", description: "Inbound or outbound direction when available." },
21
+ { key: "phone_number_id", label: "Phone number ID", type: "string", source: "call", preset: "phone_number_id", description: "Configured Cloud Voice phone number id when available." },
22
+ { key: "embed_origin", label: "Embed origin", type: "string", source: "session", preset: "embed_origin", description: "Embed origin for iframe/script starts when available." },
23
+ ];
24
+ const RESERVED_TEMPLATE_KEYS = [
25
+ "session_messages",
26
+ "tools",
27
+ "voice_context",
28
+ "project_id",
29
+ "workflow_run_id",
30
+ "workflow_name",
31
+ "workflow_input",
32
+ "workflow_output",
33
+ ];
34
+ const RESERVED_PARAMETER_KEYS = new Set([
35
+ ...exports.CLOUD_VOICE_PREDEFINED_PARAMETERS.map((parameter) => parameter.key),
36
+ ...RESERVED_TEMPLATE_KEYS,
37
+ ]);
38
+ const VALID_PARAMETER_TYPES = new Set(["string", "number", "boolean", "json", "phone", "email"]);
39
+ function normalizeParameterKey(value) {
40
+ return (0, utils_1.getString)(value, "")
41
+ .trim()
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9_]+/g, "_")
44
+ .replace(/^_+|_+$/g, "");
45
+ }
46
+ function normalizeParameterType(value) {
47
+ return VALID_PARAMETER_TYPES.has(String(value)) ? value : "string";
48
+ }
49
+ function isCustomParameter(definition) {
50
+ return !definition.preset && !RESERVED_PARAMETER_KEYS.has(definition.key);
51
+ }
52
+ function normalizeEnum(value) {
53
+ return Array.isArray(value) ? value.filter((item) => item !== undefined) : undefined;
54
+ }
55
+ function normalizeCloudVoiceParameterDefinitions(value) {
56
+ var _a, _b, _c;
57
+ const rawDefinitions = Array.isArray(value) ? value : [];
58
+ const definitions = new Map();
59
+ for (const predefined of exports.CLOUD_VOICE_PREDEFINED_PARAMETERS) {
60
+ definitions.set(predefined.key, predefined);
61
+ }
62
+ for (const raw of rawDefinitions) {
63
+ const record = (0, utils_1.getRecord)(raw);
64
+ const key = normalizeParameterKey((_a = record.key) !== null && _a !== void 0 ? _a : record.name);
65
+ if (!key || RESERVED_PARAMETER_KEYS.has(key))
66
+ continue;
67
+ definitions.set(key, {
68
+ key,
69
+ label: (0, utils_1.getString)(record.label, (0, utils_1.getString)(record.name, key)),
70
+ type: normalizeParameterType(record.type),
71
+ source: "custom",
72
+ required: record.required === true,
73
+ description: (0, utils_1.getString)(record.description, "") || undefined,
74
+ defaultValue: record.defaultValue,
75
+ enum: normalizeEnum((_b = record.enum) !== null && _b !== void 0 ? _b : record.options),
76
+ sensitive: record.sensitive === true,
77
+ jsonSchema: (0, utils_1.getRecord)((_c = record.jsonSchema) !== null && _c !== void 0 ? _c : record.schema),
78
+ });
79
+ }
80
+ return Array.from(definitions.values());
81
+ }
82
+ function hasOwn(record, key) {
83
+ return Object.prototype.hasOwnProperty.call(record, key);
84
+ }
85
+ function readSystemValue(key, system) {
86
+ var _a, _b, _c, _d, _e, _f, _g;
87
+ switch (key) {
88
+ case "user_id":
89
+ return system.userId;
90
+ case "session_id":
91
+ return system.sessionId;
92
+ case "agent_slug":
93
+ return system.agentSlug;
94
+ case "channel":
95
+ return system.channel;
96
+ case "phone":
97
+ return (_b = (_a = system.phone) !== null && _a !== void 0 ? _a : system.callerPhone) !== null && _b !== void 0 ? _b : system.fromPhone;
98
+ case "caller_phone":
99
+ return (_d = (_c = system.callerPhone) !== null && _c !== void 0 ? _c : system.phone) !== null && _d !== void 0 ? _d : system.fromPhone;
100
+ case "from_phone":
101
+ return system.fromPhone;
102
+ case "to_phone":
103
+ return (_e = system.toPhone) !== null && _e !== void 0 ? _e : system.phoneNumber;
104
+ case "provider":
105
+ return system.provider;
106
+ case "provider_call_id":
107
+ return system.providerCallId;
108
+ case "call_direction":
109
+ return (_f = system.callDirection) !== null && _f !== void 0 ? _f : system.direction;
110
+ case "phone_number_id":
111
+ return system.phoneNumberId;
112
+ case "embed_origin":
113
+ return (_g = system.embedOrigin) !== null && _g !== void 0 ? _g : system.origin;
114
+ default:
115
+ return undefined;
116
+ }
117
+ }
118
+ function coerceValue(value, definition) {
119
+ if (value === undefined || value === null || value === "")
120
+ return { ok: true, value: undefined };
121
+ if (definition.enum && definition.enum.length > 0 && !definition.enum.includes(value)) {
122
+ return { ok: false, error: `${definition.key} must be one of ${definition.enum.map(String).join(", ")}` };
123
+ }
124
+ if (definition.type === "number") {
125
+ const numberValue = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
126
+ return Number.isFinite(numberValue) ? { ok: true, value: numberValue } : { ok: false, error: `${definition.key} must be a number` };
127
+ }
128
+ if (definition.type === "boolean") {
129
+ if (typeof value === "boolean")
130
+ return { ok: true, value };
131
+ if (value === "true")
132
+ return { ok: true, value: true };
133
+ if (value === "false")
134
+ return { ok: true, value: false };
135
+ return { ok: false, error: `${definition.key} must be a boolean` };
136
+ }
137
+ if (definition.type === "json") {
138
+ return { ok: true, value };
139
+ }
140
+ return { ok: true, value: String(value) };
141
+ }
142
+ function resolveCloudVoiceParameters(input) {
143
+ var _a, _b;
144
+ const mode = (_a = input.mode) !== null && _a !== void 0 ? _a : "strict";
145
+ const supplied = (0, utils_1.getRecord)(input.supplied);
146
+ const system = (0, utils_1.getRecord)(input.system);
147
+ const definitions = normalizeCloudVoiceParameterDefinitions(input.config.parameters);
148
+ const values = {};
149
+ const missingRequired = [];
150
+ const errors = [];
151
+ const sensitiveKeys = [];
152
+ for (const definition of definitions) {
153
+ const custom = isCustomParameter(definition);
154
+ const rawValue = custom && hasOwn(supplied, definition.key)
155
+ ? supplied[definition.key]
156
+ : custom && definition.defaultValue !== undefined
157
+ ? definition.defaultValue
158
+ : !custom
159
+ ? readSystemValue(definition.key, system)
160
+ : undefined;
161
+ const coerced = coerceValue(rawValue, definition);
162
+ if (!coerced.ok) {
163
+ errors.push((_b = coerced.error) !== null && _b !== void 0 ? _b : `${definition.key} is invalid`);
164
+ continue;
165
+ }
166
+ if (coerced.value !== undefined) {
167
+ values[definition.key] = coerced.value;
168
+ if (definition.sensitive)
169
+ sensitiveKeys.push(definition.key);
170
+ continue;
171
+ }
172
+ if (definition.required && custom) {
173
+ missingRequired.push(definition.key);
174
+ }
175
+ }
176
+ if (mode === "strict" && (missingRequired.length > 0 || errors.length > 0)) {
177
+ const parts = [
178
+ missingRequired.length ? `missing required parameters: ${missingRequired.join(", ")}` : "",
179
+ ...errors,
180
+ ].filter(Boolean);
181
+ throw new Error(`Cloud voice parameters are invalid: ${parts.join("; ")}`);
182
+ }
183
+ return {
184
+ definitions,
185
+ values,
186
+ missingRequired,
187
+ errors,
188
+ sensitiveKeys,
189
+ };
190
+ }
191
+ function stringifyTemplateValue(value) {
192
+ if (value === undefined || value === null)
193
+ return "";
194
+ if (typeof value === "string")
195
+ return value;
196
+ if (typeof value === "number" || typeof value === "boolean")
197
+ return String(value);
198
+ return JSON.stringify(value);
199
+ }
200
+ function renderCloudVoiceParameterTemplate(text, parameters) {
201
+ if (!text || !text.includes("{{"))
202
+ return text;
203
+ return text
204
+ .replace(/\{\{\s*(?:params|parameters)\.([a-zA-Z0-9_]+)\s*\}\}/g, (_match, key) => stringifyTemplateValue(parameters[key]))
205
+ .replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (match, key) => Object.prototype.hasOwnProperty.call(parameters, key) ? stringifyTemplateValue(parameters[key]) : match);
206
+ }
207
+ function renderCloudVoiceParameterObject(value, parameters) {
208
+ if (typeof value === "string")
209
+ return renderCloudVoiceParameterTemplate(value, parameters);
210
+ if (Array.isArray(value))
211
+ return value.map((item) => renderCloudVoiceParameterObject(item, parameters));
212
+ if (!value || typeof value !== "object")
213
+ return value;
214
+ const next = {};
215
+ for (const [key, child] of Object.entries(value)) {
216
+ next[key] = renderCloudVoiceParameterObject(child, parameters);
217
+ }
218
+ return next;
219
+ }
@@ -0,0 +1,24 @@
1
+ import type { CloudVoiceAgentConfig, CloudVoiceChannel } from "./types";
2
+ export declare function compileCloudVoiceInstructions(input: {
3
+ config: Pick<CloudVoiceAgentConfig, "humanization" | "metadata" | "instructions">;
4
+ agentName?: string;
5
+ channel?: CloudVoiceChannel;
6
+ toolCount?: number;
7
+ phoneControlRule?: string;
8
+ compiledFlowInstructions?: string;
9
+ }): string;
10
+ export declare function buildPhoneControlRule(input: {
11
+ config?: Pick<CloudVoiceAgentConfig, "metadata"> | Record<string, unknown>;
12
+ channel?: CloudVoiceChannel;
13
+ }): string;
14
+ export declare function buildPhoneOpeningPrompt(input: {
15
+ config?: Pick<CloudVoiceAgentConfig, "humanization" | "metadata"> | Record<string, unknown>;
16
+ agentName?: string;
17
+ channel?: CloudVoiceChannel;
18
+ }): string | null;
19
+ export declare function resolveCloudVoiceProviderSystemPrompt(input: {
20
+ voiceConfig?: {
21
+ system?: unknown;
22
+ } | null;
23
+ config?: Pick<CloudVoiceAgentConfig, "metadata" | "instructions"> | Record<string, unknown> | null;
24
+ }): string;