1 |
|
2 |
|
3 |
|
4 |
|
5 | import * as CLM from '@conversationlearner/models'
|
6 | import { CLDebug } from './CLDebug'
|
7 | import * as Request from 'request'
|
8 | import * as constants from './constants'
|
9 | import { IActionResult } from './CLRunner'
|
10 |
|
11 | type HTTP_METHOD = 'GET' | 'PUT' | 'POST' | 'DELETE'
|
12 | const requestMethodMap = new Map<HTTP_METHOD, typeof Request.get | typeof Request.post>([
|
13 | ['GET', Request.get],
|
14 | ['PUT', Request.put],
|
15 | ['POST', Request.post],
|
16 | ['DELETE', Request.delete]
|
17 | ])
|
18 |
|
19 | export interface ICLClientOptions {
|
20 | CONVERSATION_LEARNER_SERVICE_URI: string
|
21 |
|
22 | APIM_SUBSCRIPTION_KEY: string | undefined
|
23 | LUIS_AUTHORING_KEY: string | undefined
|
24 | LUIS_SUBSCRIPTION_KEY?: string
|
25 | }
|
26 |
|
27 | export class CLClient {
|
28 | private options: ICLClientOptions
|
29 |
|
30 | constructor(options: ICLClientOptions) {
|
31 | this.options = options;
|
32 |
|
33 | if (options.APIM_SUBSCRIPTION_KEY === undefined) {
|
34 | options.APIM_SUBSCRIPTION_KEY = options.LUIS_AUTHORING_KEY;
|
35 | }
|
36 | }
|
37 |
|
38 | public ValidationError(): string | null {
|
39 | if (typeof this.options.CONVERSATION_LEARNER_SERVICE_URI !== 'string' || this.options.CONVERSATION_LEARNER_SERVICE_URI.length === 0) {
|
40 | return `CONVERSATION_LEARNER_SERVICE_URI must be a non-empty string. You passed: ${this.options.CONVERSATION_LEARNER_SERVICE_URI}`
|
41 | }
|
42 |
|
43 | if (typeof this.options.LUIS_AUTHORING_KEY !== 'string' || this.options.LUIS_AUTHORING_KEY.length === 0) {
|
44 | return `LUIS_AUTHORING_KEY must be a non-empty string. You passed: ${this.options.LUIS_AUTHORING_KEY}`
|
45 | }
|
46 | return null;
|
47 | }
|
48 |
|
49 | public LuisAuthoringKey(): string | undefined {
|
50 | return this.options.LUIS_AUTHORING_KEY;
|
51 | }
|
52 |
|
53 | private BuildURL(baseUri: string, apiPath: string, query?: string) {
|
54 | let uri = baseUri + (!baseUri.endsWith('/') ? '/' : '') + apiPath
|
55 | if (query) {
|
56 | uri += `?${query}`
|
57 | }
|
58 | return uri
|
59 | }
|
60 |
|
61 | private MakeURL(apiPath: string, query?: string) {
|
62 | return this.BuildURL(this.options.CONVERSATION_LEARNER_SERVICE_URI, apiPath, query)
|
63 | }
|
64 |
|
65 | private MakeSessionURL(apiPath: string, query?: string) {
|
66 |
|
67 | if (!this.options.CONVERSATION_LEARNER_SERVICE_URI.includes('api.cognitive.microsoft.com')) {
|
68 |
|
69 |
|
70 | return this.MakeURL(apiPath, query)
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | let baseUri = this.options.CONVERSATION_LEARNER_SERVICE_URI.endsWith('/') ?
|
80 | this.options.CONVERSATION_LEARNER_SERVICE_URI :
|
81 | `${this.options.CONVERSATION_LEARNER_SERVICE_URI}/`
|
82 | const apimVersionSuffix = '/v1.0/'
|
83 | if (baseUri.endsWith(apimVersionSuffix)) {
|
84 |
|
85 |
|
86 |
|
87 | baseUri = `${baseUri.substring(0, baseUri.lastIndexOf(apimVersionSuffix))}/session${apimVersionSuffix}`
|
88 | }
|
89 | else {
|
90 |
|
91 |
|
92 | baseUri += 'session/'
|
93 | }
|
94 | return this.BuildURL(baseUri, apiPath, query)
|
95 | }
|
96 |
|
97 | private send<T>(method: HTTP_METHOD, url: string, body?: any): Promise<T> {
|
98 | return new Promise((resolve, reject) => {
|
99 | const requestData = {
|
100 | url,
|
101 | headers: {
|
102 | [constants.luisAuthoringKeyHeader]: this.options.LUIS_AUTHORING_KEY,
|
103 | [constants.luisSubscriptionKeyHeader]: this.options.LUIS_SUBSCRIPTION_KEY,
|
104 |
|
105 | [constants.apimSubscriptionIdHeader]: this.options.LUIS_AUTHORING_KEY,
|
106 | [constants.apimSubscriptionKeyHeader]: this.options.APIM_SUBSCRIPTION_KEY
|
107 | },
|
108 | json: true,
|
109 | body
|
110 | }
|
111 |
|
112 | CLDebug.LogRequest(method, url, requestData)
|
113 | const requestMethod = requestMethodMap.get(method)
|
114 | if (!requestMethod) {
|
115 | throw new Error(`Request method not found for http verb: ${method}`)
|
116 | }
|
117 |
|
118 | requestMethod(requestData, (error, response, responseBody) => {
|
119 | if (error) {
|
120 | reject(error)
|
121 | } else if (response.statusCode && response.statusCode >= 300) {
|
122 | reject(response)
|
123 | } else {
|
124 | resolve(responseBody)
|
125 | }
|
126 | })
|
127 | })
|
128 | }
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | |
134 |
|
135 |
|
136 |
|
137 |
|
138 | public GetApp(appId: string): Promise<CLM.AppBase> {
|
139 | let apiPath = `app/${appId}`
|
140 | return this.send('GET', this.MakeURL(apiPath))
|
141 | }
|
142 |
|
143 | public GetAppSource(appId: string, packageId: string): Promise<CLM.AppDefinition> {
|
144 | let apiPath = `app/${appId}/source?package=${packageId}`
|
145 | return this.send('GET', this.MakeURL(apiPath))
|
146 | }
|
147 |
|
148 | public async PostAppSource(appId: string, appDefinition: CLM.AppDefinition): Promise<void> {
|
149 | let apiPath = `app/${appId}/source`
|
150 | await this.send('POST', this.MakeURL(apiPath), appDefinition);
|
151 | }
|
152 |
|
153 |
|
154 | public GetApps(query: string): Promise<CLM.AppList> {
|
155 | let apiPath = `apps`
|
156 | return this.send('GET', this.MakeURL(apiPath, query))
|
157 | }
|
158 |
|
159 |
|
160 | public CopyApps(srcUserId: string, destUserId: string, appId: string, luisSubscriptionKey: string): Promise<string> {
|
161 | const apiPath = `apps/copy?srcUserId=${srcUserId}&destUserId=${destUserId}&appId=${appId}&luisSubscriptionKey=${luisSubscriptionKey}`
|
162 | return this.send('POST', this.MakeURL(apiPath))
|
163 | }
|
164 |
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | public ArchiveApp(appId: string): Promise<string> {
|
173 | let apiPath = `app/${appId}`
|
174 | return this.send('DELETE', this.MakeURL(apiPath))
|
175 | }
|
176 |
|
177 | |
178 |
|
179 |
|
180 |
|
181 | public async AddApp(app: CLM.AppBase, query: string): Promise<string> {
|
182 | const apiPath = `app`
|
183 |
|
184 | const appResponse = await this.send<CLM.AppBase>('POST', this.MakeURL(apiPath, query), app)
|
185 | return appResponse.appId
|
186 | }
|
187 |
|
188 |
|
189 | public PublishApp(appId: string, tagName: string): Promise<CLM.PackageReference> {
|
190 | let apiPath = `app/${appId}/publish?version=${tagName}`
|
191 | return this.send('PUT', this.MakeURL(apiPath))
|
192 | }
|
193 |
|
194 |
|
195 | public PublishProdPackage(appId: string, packageId: string): Promise<string> {
|
196 | let apiPath = `app/${appId}/publish/${packageId}`
|
197 | return this.send('POST', this.MakeURL(apiPath))
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | |
204 |
|
205 |
|
206 |
|
207 |
|
208 | public GetEntities(appId: string, query?: string): Promise<CLM.EntityList> {
|
209 | let apiPath = `app/${appId}/entities`
|
210 | return this.send('GET', this.MakeURL(apiPath, query))
|
211 | }
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | |
217 |
|
218 |
|
219 |
|
220 |
|
221 | public GetLogDialogs(appId: string, packageIds: string[]): Promise<CLM.LogDialogList> {
|
222 | const packages = packageIds.map(p => `package=${p}`).join("&")
|
223 | const apiPath = `app/${appId}/logdialogs?includeDefinitions=false&${packages}`
|
224 | return this.send('GET', this.MakeURL(apiPath))
|
225 | }
|
226 |
|
227 |
|
228 | public LogDialogExtract(
|
229 | appId: string,
|
230 | logDialogId: string,
|
231 | turnIndex: string,
|
232 | userInput: CLM.UserInput
|
233 | ): Promise<CLM.ExtractResponse> {
|
234 | let apiPath = `app/${appId}/logdialog/${logDialogId}/extractor/${turnIndex}`
|
235 |
|
236 | let query = 'includeDefinitions=true'
|
237 | return this.send('PUT', this.MakeURL(apiPath, query), userInput)
|
238 | }
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | |
245 |
|
246 |
|
247 |
|
248 | public GetTrainDialog(appId: string, trainDialogId: string, includeDefinitions: boolean = false): Promise<CLM.TrainDialog> {
|
249 | let query = `includeDefinitions=${includeDefinitions}`
|
250 | let apiPath = `app/${appId}/traindialog/${trainDialogId}`
|
251 | return this.send('GET', this.MakeURL(apiPath, query))
|
252 | }
|
253 |
|
254 |
|
255 | public TrainDialogExtract(
|
256 | appId: string,
|
257 | trainDialogId: string,
|
258 | turnIndex: string,
|
259 | userInput: CLM.UserInput
|
260 | ): Promise<CLM.ExtractResponse> {
|
261 | let apiPath = `app/${appId}/traindialog/${trainDialogId}/extractor/${turnIndex}`
|
262 |
|
263 | let query = 'includeDefinitions=true'
|
264 | return this.send('PUT', this.MakeURL(apiPath, query), userInput)
|
265 | }
|
266 |
|
267 | |
268 |
|
269 |
|
270 |
|
271 | public TrainDialogValidateTextVariation(appId: string, trainDialogId: string, textVariation: CLM.TextVariation, filteredDialog: string): Promise<null> {
|
272 | let apiPath = `app/${appId}/traindialog/${trainDialogId}/extractor/textvariation`
|
273 |
|
274 | let query = filteredDialog ? `filteredDialogs=${filteredDialog}` : undefined
|
275 | return this.send('POST', this.MakeURL(apiPath, query), textVariation)
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 | public StartSession(appId: string, sessionCreateParams: CLM.SessionCreateParams): Promise<CLM.Session> {
|
284 | let apiPath = `app/${appId}/session`
|
285 | return this.send('POST', this.MakeSessionURL(apiPath), sessionCreateParams)
|
286 | }
|
287 |
|
288 |
|
289 |
|
290 | public GetSession(appId: string, sessionId: string): Promise<CLM.Session> {
|
291 | let apiPath = `app/${appId}/session/${sessionId}`
|
292 | return this.send('GET', this.MakeURL(apiPath))
|
293 | }
|
294 |
|
295 |
|
296 | public SessionExtract(appId: string, sessionId: string, userInput: CLM.UserInput): Promise<CLM.ExtractResponse> {
|
297 | let apiPath = `app/${appId}/session/${sessionId}/extractor`
|
298 |
|
299 |
|
300 | let query = 'includeDefinitions=true'
|
301 |
|
302 | return this.send('PUT', this.MakeSessionURL(apiPath, query), userInput)
|
303 | }
|
304 |
|
305 |
|
306 | public SessionScore(appId: string, sessionId: string, scorerInput: CLM.ScoreInput): Promise<CLM.ScoreResponse> {
|
307 | let apiPath = `app/${appId}/session/${sessionId}/scorer`
|
308 | return this.send('PUT', this.MakeSessionURL(apiPath), scorerInput)
|
309 | }
|
310 |
|
311 | public SessionLogicResult(appId: string, sessionId: string, actionId: string, actionResult: IActionResult) {
|
312 | let apiPath = `app/${appId}/session/${sessionId}/scorerSteps/action/${actionId}/logicResult`
|
313 | return this.send('PUT', this.MakeSessionURL(apiPath), { logicResult: actionResult.logicResult })
|
314 | }
|
315 |
|
316 |
|
317 | public EndSession(appId: string, sessionId: string): Promise<string> {
|
318 | let apiPath = `app/${appId}/session/${sessionId}`
|
319 |
|
320 | let query = 'saveDialog=false'
|
321 | return this.send('DELETE', this.MakeSessionURL(apiPath, query))
|
322 | }
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | public StartTeach(appId: string, createTeachParams: CLM.CreateTeachParams): Promise<CLM.TeachResponse> {
|
330 | let apiPath = `app/${appId}/teach`
|
331 | return this.send('POST', this.MakeURL(apiPath), createTeachParams)
|
332 | }
|
333 |
|
334 | |
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 | public TeachExtract(appId: string, teachId: string, userInput: CLM.UserInput, filteredDialog: string | null): Promise<CLM.ExtractResponse> {
|
341 | let apiPath = `app/${appId}/teach/${teachId}/extractor`
|
342 |
|
343 | let query = `includeDefinitions=true`
|
344 | if (filteredDialog) {
|
345 | query += `&filteredDialogs[]=${filteredDialog}`
|
346 | }
|
347 | return this.send('PUT', this.MakeURL(apiPath, query), { text: userInput.text })
|
348 | }
|
349 |
|
350 | |
351 |
|
352 |
|
353 |
|
354 |
|
355 | public TeachExtractFeedback(appId: string, teachId: string, extractorStep: CLM.TrainExtractorStep): Promise<CLM.TeachResponse> {
|
356 | let apiPath = `app/${appId}/teach/${teachId}/extractor`
|
357 | return this.send('POST', this.MakeURL(apiPath), extractorStep)
|
358 | }
|
359 |
|
360 | |
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 | public TeachScore(appId: string, teachId: string, scorerInput: CLM.ScoreInput): Promise<CLM.ScoreResponse> {
|
367 | let apiPath = `app/${appId}/teach/${teachId}/scorer`
|
368 | return this.send('PUT', this.MakeURL(apiPath), scorerInput)
|
369 | }
|
370 |
|
371 | |
372 |
|
373 |
|
374 |
|
375 |
|
376 | public TeachScoreFeedback(appId: string, teachId: string, scorerResponse: CLM.TrainScorerStep): Promise<CLM.TeachResponse> {
|
377 | let apiPath = `app/${appId}/teach/${teachId}/scorer`
|
378 | return this.send('POST', this.MakeURL(apiPath), scorerResponse)
|
379 | }
|
380 |
|
381 | |
382 |
|
383 |
|
384 |
|
385 |
|
386 | public EndTeach(appId: string, teachId: string, save: boolean): Promise<CLM.TrainResponse> {
|
387 | const query = save ? `saveDialog=${save}` : ''
|
388 | let apiPath = `app/${appId}/teach/${teachId}`
|
389 | return this.send('DELETE', this.MakeURL(apiPath, query))
|
390 | }
|
391 | }
|