@fonoster/autopilot 0.5.1 → 0.5.3

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.
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { EffectsManager } from "./effects";
4
- import { IntentsEngine } from "../intents/types";
4
+ import { Intent, IntentsEngine } from "../intents/types";
5
5
  import { SGatherStream, VoiceRequest, VoiceResponse } from "@fonoster/voice";
6
6
  import { CerebroConfig, CerebroStatus } from "./types";
7
7
  import Events from "events";
@@ -18,10 +18,11 @@ export declare class Cerebro {
18
18
  intentsEngine: IntentsEngine;
19
19
  stream: SGatherStream;
20
20
  config: CerebroConfig;
21
- lastIntent: any;
21
+ lastIntent: Intent;
22
22
  effects: EffectsManager;
23
23
  interactionsTimer: NodeJS.Timeout;
24
24
  isCallHandover: boolean;
25
+ isDead: boolean;
25
26
  constructor(config: CerebroConfig);
26
27
  wake(): Promise<void>;
27
28
  sleep(): Promise<void>;
@@ -37,4 +38,5 @@ export declare class Cerebro {
37
38
  startInteractionTimer(): void;
38
39
  resetInteractionTimer(): void;
39
40
  stopPlayback(): Promise<void>;
41
+ cleanup(): Promise<void>;
40
42
  }
@@ -47,6 +47,7 @@ class Cerebro {
47
47
  effects;
48
48
  interactionsTimer;
49
49
  isCallHandover = false;
50
+ isDead = false;
50
51
  constructor(config) {
51
52
  this.voiceResponse = config.voiceResponse;
52
53
  this.voiceRequest = config.voiceRequest;
@@ -128,12 +129,17 @@ class Cerebro {
128
129
  this.startActiveTimer();
129
130
  }
130
131
  }
132
+ }, () => {
133
+ logger.verbose("invokeEffects cleanup callback", {
134
+ sessionId: this.voiceRequest.sessionId
135
+ });
136
+ this.cleanup();
131
137
  });
132
- logger.verbose("cerebro finished processing intent effects", {
133
- sessionId: this.voiceRequest.sessionId
134
- });
135
- if (this.isCallHandover) {
138
+ if (this.isDead || this.isCallHandover) {
136
139
  try {
140
+ logger.verbose("the call was handover or cerebro was cleaned up", {
141
+ sessionId: this.voiceRequest.sessionId
142
+ });
137
143
  this.voiceResponse.hangup();
138
144
  }
139
145
  catch (e) {
@@ -141,6 +147,9 @@ class Cerebro {
141
147
  }
142
148
  return;
143
149
  }
150
+ logger.verbose("cerebro finished processing intent effects", {
151
+ sessionId: this.voiceRequest.sessionId
152
+ });
144
153
  // Reset the interactions timer
145
154
  if (!this.config.activationIntentId) {
146
155
  this.resetInteractionTimer();
@@ -190,11 +199,13 @@ class Cerebro {
190
199
  sessionId: this.voiceRequest.sessionId,
191
200
  failedInteractions: this.failedInteractions
192
201
  });
202
+ // Fix hard coded intent
193
203
  let intentId = "welcome";
194
204
  if (this.failedInteractions >= this.maxIteractionsBeforeHangup) {
195
205
  logger.verbose("there was no interaction so for a long time so we hangup", {
196
206
  sessionId: this.voiceRequest.sessionId
197
207
  });
208
+ // Fix hard coded intent
198
209
  intentId = "goodbye";
199
210
  clearTimeout(this.interactionsTimer);
200
211
  }
@@ -207,6 +218,11 @@ class Cerebro {
207
218
  logger.verbose("invokeEffects callback", {
208
219
  sessionId: this.voiceRequest.sessionId
209
220
  });
221
+ }, () => {
222
+ logger.verbose("invokeEffects cleanup callback", {
223
+ sessionId: this.voiceRequest.sessionId
224
+ });
225
+ this.cleanup();
210
226
  });
211
227
  }, this.activationTimeout);
212
228
  }
@@ -237,5 +253,14 @@ class Cerebro {
237
253
  }
238
254
  }
239
255
  }
256
+ // Cleanup all timers and events
257
+ async cleanup() {
258
+ this.isDead = true;
259
+ await this.voiceResponse.closeMediaPipe();
260
+ this.stream.close();
261
+ this.cerebroEvents.removeAllListeners();
262
+ clearTimeout(this.activeTimer);
263
+ clearTimeout(this.interactionsTimer);
264
+ }
240
265
  }
241
266
  exports.Cerebro = Cerebro;
@@ -5,7 +5,7 @@ export declare class EffectsManager {
5
5
  voice: VoiceResponse;
6
6
  config: EffectsManagerConfig;
7
7
  constructor(config: EffectsManagerConfig);
8
- invokeEffects(intent: Intent, status: CerebroStatus, activateCallback: () => void): Promise<void>;
8
+ invokeEffects(intent: Intent, status: CerebroStatus, activateCallback: () => void, cleanupCallback: () => void): Promise<void>;
9
9
  run(effect: Effect): Promise<void>;
10
10
  transferEffect(effect: Effect): Promise<void>;
11
11
  }
@@ -15,7 +15,7 @@ class EffectsManager {
15
15
  this.voice = config.voice;
16
16
  this.config = config;
17
17
  }
18
- async invokeEffects(intent, status, activateCallback) {
18
+ async invokeEffects(intent, status, activateCallback, cleanupCallback) {
19
19
  activateCallback();
20
20
  logger.verbose("intent received", { intentRef: intent.ref });
21
21
  if (this.config.activationIntentId === intent.ref) {
@@ -31,8 +31,18 @@ class EffectsManager {
31
31
  }
32
32
  // eslint-disable-next-line no-loops/no-loops
33
33
  for (const e of intent.effects) {
34
- logger.verbose("effects running", { type: e.type });
35
- await this.run(e);
34
+ try {
35
+ logger.verbose("effects running", { type: e.type });
36
+ await this.run(e);
37
+ }
38
+ catch (e) {
39
+ const axiosError = e;
40
+ if (axiosError.response?.status !== 404) {
41
+ logger.error("error running effect", { error: e });
42
+ return;
43
+ }
44
+ cleanupCallback();
45
+ }
36
46
  }
37
47
  }
38
48
  async run(effect) {
@@ -13,7 +13,7 @@ export interface CerebroConfig {
13
13
  activationTimeout?: number;
14
14
  activationIntentId?: string;
15
15
  intentsEngine: IntentsEngine;
16
- voiceConfig: Record<string, string>;
16
+ voiceConfig: Record<string, string | string[]>;
17
17
  eventsClient: EventsClient | null;
18
18
  transfer?: Transfer;
19
19
  alternativeLanguageCode?: string;
@@ -1,8 +1,24 @@
1
1
  import { DialogFlowCXConfig, IntentsEngine, Intent } from "./types";
2
+ import { Effect } from "../cerebro/types";
2
3
  import { SessionsClient } from "@google-cloud/dialogflow-cx";
4
+ type DetectItemRequest = {
5
+ session: string;
6
+ queryParams: Record<string, unknown>;
7
+ queryInput: {
8
+ text: {
9
+ text: string;
10
+ };
11
+ } | ({
12
+ event: {
13
+ event: string;
14
+ };
15
+ } & {
16
+ languageCode: string;
17
+ });
18
+ };
3
19
  export default class DialogFlowCX implements IntentsEngine {
4
20
  sessionClient: SessionsClient;
5
- sessionPath: any;
21
+ sessionPath: string;
6
22
  config: DialogFlowCXConfig;
7
23
  projectId: string;
8
24
  location: string;
@@ -10,6 +26,18 @@ export default class DialogFlowCX implements IntentsEngine {
10
26
  sessionId: string;
11
27
  constructor(config: DialogFlowCXConfig);
12
28
  setProjectId(id: string): void;
13
- findIntent(txt: string): Promise<Intent>;
29
+ findIntent(text: string, payload?: Record<string, unknown>): Promise<Intent>;
30
+ findIntentWithEvent(name: string, payload?: Record<string, unknown>): Promise<{
31
+ ref: string;
32
+ effects: Effect[];
33
+ confidence: number;
34
+ }>;
35
+ detectIntent(request: DetectItemRequest, payload?: Record<string, unknown>): Promise<{
36
+ ref: string;
37
+ effects: Effect[];
38
+ confidence: number;
39
+ }>;
40
+ private getSessionPath;
14
41
  private getEffects;
15
42
  }
43
+ export {};
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const df_utils_1 = require("./df_utils");
7
+ const pb_util_1 = require("pb-util");
7
8
  const logger_1 = require("@fonoster/logger");
8
9
  const dialogflow_cx_1 = __importDefault(require("@google-cloud/dialogflow-cx"));
9
10
  const uuid = require("uuid");
@@ -36,46 +37,60 @@ class DialogFlowCX {
36
37
  setProjectId(id) {
37
38
  this.projectId = id;
38
39
  }
39
- async findIntent(txt) {
40
- const sessionPath = this.sessionClient.projectLocationAgentSessionPath(this.projectId, this.location, this.agent, this.sessionId);
40
+ async findIntent(text, payload) {
41
41
  const request = {
42
- session: sessionPath,
42
+ session: this.getSessionPath(),
43
+ queryParams: {},
43
44
  queryInput: {
44
45
  text: {
45
- text: txt
46
+ text
46
47
  },
47
48
  languageCode: this.config.languageCode
48
49
  }
49
50
  };
50
- const responses = await this.sessionClient.detectIntent(request);
51
- logger.silly("got speech from api", { text: JSON.stringify(responses[0]) });
52
- if (!responses ||
53
- !responses[0].queryResult ||
54
- !responses[0].queryResult.responseMessages) {
55
- throw new Error("got unexpect null intent");
51
+ return this.detectIntent(request, payload);
52
+ }
53
+ async findIntentWithEvent(name, payload) {
54
+ const request = {
55
+ session: this.getSessionPath(),
56
+ queryParams: {},
57
+ queryInput: {
58
+ languageCode: this.config.languageCode,
59
+ event: {
60
+ event: name
61
+ }
62
+ }
63
+ };
64
+ return this.detectIntent(request, payload);
65
+ }
66
+ async detectIntent(request, payload) {
67
+ if (payload) {
68
+ request.queryParams = { payload: pb_util_1.struct.encode(payload) };
56
69
  }
57
- const effects = this.getEffects(responses[0].queryResult.responseMessages);
58
- const ref = responses[0].queryResult.intent
59
- ? responses[0].queryResult.intent.displayName || "unknown"
60
- : "unknown";
70
+ const [response] = await this.sessionClient.detectIntent(request);
71
+ const effects = this.getEffects(response.queryResult.responseMessages);
72
+ const params = pb_util_1.struct.decode(response.queryResult.parameters);
73
+ const ref = params.skillCode?.toString() || "unknown";
61
74
  return {
62
75
  ref,
63
76
  effects,
64
- confidence: responses[0].queryResult.intentDetectionConfidence || 0,
65
- allRequiredParamsPresent: responses[0].queryResult.text ? true : false
77
+ confidence: response.queryResult.match?.confidence || 0
66
78
  };
67
79
  }
80
+ getSessionPath() {
81
+ return this.sessionClient.projectLocationAgentSessionPath(this.projectId, this.location, this.agent, this.sessionId);
82
+ }
68
83
  getEffects(responseMessages) {
69
84
  const effects = [];
70
85
  for (const r of responseMessages) {
71
86
  if (r.message === "text") {
87
+ const res = r.text;
72
88
  effects.push({
73
89
  type: "say",
74
90
  parameters: {
75
- response: r.text.text[0]
91
+ response: res.text[0]
76
92
  }
77
93
  });
78
- continue;
79
94
  }
80
95
  else if (r.payload) {
81
96
  effects.push((0, df_utils_1.transformPayloadToEffect)(r.payload));
@@ -2,7 +2,7 @@ import * as dialogflow from "@google-cloud/dialogflow";
2
2
  import { IntentsEngine, Intent, DialogFlowESConfig } from "./types";
3
3
  export default class DialogFlow implements IntentsEngine {
4
4
  sessionClient: dialogflow.v2beta1.SessionsClient;
5
- sessionPath: any;
5
+ sessionPath: string;
6
6
  config: DialogFlowESConfig;
7
7
  sessionId: string;
8
8
  projectId: string;
@@ -10,6 +10,6 @@ export default class DialogFlow implements IntentsEngine {
10
10
  setProjectId(projectId: string): void;
11
11
  findIntentWithEvent(name: string, payload?: Record<string, unknown>): Promise<Intent>;
12
12
  findIntent(txt: string, payload?: Record<string, unknown>): Promise<Intent>;
13
- private detect;
13
+ private detectItent;
14
14
  private getEffects;
15
15
  }
@@ -80,7 +80,7 @@ class DialogFlow {
80
80
  }
81
81
  }
82
82
  };
83
- return this.detect(request, payload);
83
+ return this.detectItent(request, payload);
84
84
  }
85
85
  async findIntent(txt, payload) {
86
86
  const request = {
@@ -92,9 +92,9 @@ class DialogFlow {
92
92
  }
93
93
  }
94
94
  };
95
- return this.detect(request, payload);
95
+ return this.detectItent(request, payload);
96
96
  }
97
- async detect(request, payload) {
97
+ async detectItent(request, payload) {
98
98
  const sessionPath = this.sessionClient.projectAgentSessionPath(this.projectId, this.sessionId);
99
99
  request.session = sessionPath;
100
100
  if (payload) {
@@ -102,33 +102,30 @@ class DialogFlow {
102
102
  payload: pb_util_1.struct.encode(payload)
103
103
  };
104
104
  }
105
- const responses = await this.sessionClient.detectIntent(request);
106
- logger.silly("got speech from api", { text: JSON.stringify(responses[0]) });
107
- if (!responses ||
108
- !responses[0].queryResult ||
109
- !responses[0].queryResult.intent) {
105
+ const [response] = await this.sessionClient.detectIntent(request);
106
+ logger.silly("got speech from api", { text: JSON.stringify(response) });
107
+ if (!response.queryResult?.intent) {
110
108
  throw new Error("got unexpect null intent");
111
109
  }
112
110
  let effects = [];
113
- if (responses[0].queryResult.fulfillmentMessages) {
114
- const messages = responses[0].queryResult.fulfillmentMessages.filter((f) => f.platform === this.config.platform);
111
+ if (response.queryResult.fulfillmentMessages) {
112
+ const messages = response.queryResult.fulfillmentMessages.filter((f) => f.platform === this.config.platform);
115
113
  effects = this.getEffects(messages);
116
114
  }
117
- else if (responses[0].queryResult.fulfillmentText) {
115
+ else if (response.queryResult.fulfillmentText) {
118
116
  effects = [
119
117
  {
120
118
  type: "say",
121
119
  parameters: {
122
- response: responses[0].queryResult.fulfillmentText
120
+ response: response.queryResult.fulfillmentText
123
121
  }
124
122
  }
125
123
  ];
126
124
  }
127
125
  return {
128
- ref: responses[0].queryResult.intent.displayName || "unknown",
126
+ ref: response.queryResult.intent.displayName || "unknown",
129
127
  effects,
130
- confidence: responses[0].queryResult.intentDetectionConfidence || 0,
131
- allRequiredParamsPresent: responses[0].queryResult.allRequiredParamsPresent
128
+ confidence: response.queryResult.intentDetectionConfidence || 0
132
129
  };
133
130
  }
134
131
  getEffects(fulfillmentMessages) {
@@ -3,7 +3,6 @@ export interface Intent {
3
3
  ref: string;
4
4
  effects: Effect[];
5
5
  confidence: number;
6
- allRequiredParamsPresent: boolean;
7
6
  }
8
7
  export interface IntentsEngine {
9
8
  setProjectId: (id: string) => void;
package/dist/pilot.js CHANGED
@@ -66,7 +66,8 @@ function pilot(config) {
66
66
  intentsEngine?.setProjectId(app.intentsEngineConfig.projectId);
67
67
  const voiceConfig = {
68
68
  name: app.speechConfig.voice,
69
- playbackId: (0, nanoid_1.nanoid)()
69
+ playbackId: (0, nanoid_1.nanoid)(),
70
+ cachingFields: ["name"]
70
71
  };
71
72
  const speechSecret = await secrets.getSecret(app.speechConfig.secretName);
72
73
  const speechCredentials = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/autopilot",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "license": "MIT",
5
5
  "main": "dist/index",
6
6
  "types": "dist/index",
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@fonoster/apps": "^0.5.1",
16
16
  "@fonoster/googleasr": "^0.5.1",
17
- "@fonoster/googletts": "^0.5.1",
17
+ "@fonoster/googletts": "^0.5.3",
18
18
  "@fonoster/logger": "^0.5.1",
19
19
  "@fonoster/secrets": "^0.5.1",
20
20
  "@fonoster/voice": "^0.5.1",
@@ -37,5 +37,5 @@
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
- "gitHead": "0f7b0ed84ded17ea6b3aa593fafc97914478f3a6"
40
+ "gitHead": "a75f426e647949f536417951e0956439856d5ae9"
41
41
  }