wingbot 3.74.0 → 3.74.1

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/src/LLMTool.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /** @typedef {import('./LLMSession').Tool} Tool */
7
+ /** @typedef {import('./LLMSession').ToolInput} ToolInput */
8
+ /** @typedef {import('./LLMSession').ObjectTool} ObjectTool */
9
+ /** @typedef {import('./LLMSession').ToolFunctionInput} ToolFunctionInput */
10
+ /** @typedef {import('./LLMSession').ToolFnCallback} ToolFnCallback */
11
+ /** @typedef {import('./LLMSession').ToolFunction} ToolFunction */
12
+ /** @typedef {import('./LLMSession').FnParamsObject} FnParamsObject */
13
+ /** @typedef {import('./LLMSession').SimpleJsonSchema} SimpleJsonSchema */
14
+
15
+ /** @typedef {string[]} Enum */
16
+
17
+ /**
18
+ * @typedef {{}} SchemaDefinition
19
+ */
20
+
21
+ /**
22
+ * @class LLMTool
23
+ */
24
+ class LLMTool {
25
+
26
+ /**
27
+ *
28
+ * @param {ToolFnCallback} fn
29
+ * @param {ToolFunctionInput} options
30
+ * @returns {ObjectTool}
31
+ */
32
+ static create (fn, {
33
+ name = fn.name,
34
+ ...rest
35
+ }) {
36
+ let {
37
+ parameters = {
38
+ properties: {}
39
+ }
40
+ } = rest;
41
+
42
+ if ('toJSON' in parameters && typeof parameters.toJSON === 'function') {
43
+ parameters = parameters.toJSON();
44
+ }
45
+
46
+ return {
47
+ fn,
48
+ name,
49
+ ...rest,
50
+ parameters
51
+ };
52
+ }
53
+
54
+ }
55
+
56
+ module.exports = LLMTool;
package/src/LLMType.js ADDED
@@ -0,0 +1,331 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Lightweight fluent builder for JSON Schema definitions used by LLM tools.
5
+ *
6
+ * @class LLMType
7
+ */
8
+ class LLMType {
9
+
10
+ /**
11
+ * @param {'string'|'number'|'enum'|'boolean'|'array'|'object'} type
12
+ * @param {object} [config]
13
+ * @param {string[]} [config.values]
14
+ * @param {LLMType} [config.items]
15
+ * @param {{ [key: string]: LLMType }} [config.properties]
16
+ * @param {string} [config.name]
17
+ */
18
+ constructor (type, {
19
+ values = null,
20
+ items = null,
21
+ properties = null,
22
+ name = null
23
+ } = {}) {
24
+ this._type = type;
25
+ this._name = name;
26
+
27
+ this._description = null;
28
+ this._min = null;
29
+ this._max = null;
30
+
31
+ this._required = true;
32
+
33
+ this._values = values;
34
+ this._items = items;
35
+ this._properties = properties;
36
+ }
37
+
38
+ /**
39
+ * Creates a string schema.
40
+ *
41
+ * @example
42
+ * const title = LLMType.string()
43
+ * .description('Short title')
44
+ * .min(1)
45
+ * .max(64);
46
+ *
47
+ * @returns {LLMType}
48
+ */
49
+ static string () {
50
+ return new LLMType('string');
51
+ }
52
+
53
+ /**
54
+ * Creates a number schema.
55
+ *
56
+ * @example
57
+ * const temperature = LLMType.number()
58
+ * .description('Temperature in Celsius')
59
+ * .min(-40)
60
+ * .max(100);
61
+ *
62
+ * @returns {LLMType}
63
+ */
64
+ static number () {
65
+ return new LLMType('number');
66
+ }
67
+
68
+ /**
69
+ * Creates an enum schema.
70
+ *
71
+ * @example
72
+ * const tone = LLMType.enum(['formal', 'casual'])
73
+ * .description('Response tone');
74
+ *
75
+ * @param {string[]} values
76
+ * @returns {LLMType}
77
+ */
78
+ static enum (values) {
79
+ if (!Array.isArray(values) || values.length === 0 || !values.every((v) => typeof v === 'string')) {
80
+ throw new Error('LLMType.enum(values) expects a non-empty array of strings');
81
+ }
82
+ return new LLMType('enum', { values: [...values] });
83
+ }
84
+
85
+ /**
86
+ * Creates a boolean schema.
87
+ *
88
+ * @example
89
+ * const includeSummary = LLMType.boolean()
90
+ * .description('Whether summary should be included');
91
+ *
92
+ * @returns {LLMType}
93
+ */
94
+ static boolean () {
95
+ return new LLMType('boolean');
96
+ }
97
+
98
+ /**
99
+ * Creates an array schema with one item type.
100
+ *
101
+ * @example
102
+ * const tags = LLMType.array(LLMType.string().min(1))
103
+ * .description('List of tags')
104
+ * .min(1)
105
+ * .max(20);
106
+ *
107
+ * @param {LLMType} itemType
108
+ * @returns {LLMType}
109
+ */
110
+ static array (itemType) {
111
+ if (!(itemType instanceof LLMType)) {
112
+ throw new Error('LLMType.array(itemType) expects itemType to be LLMType');
113
+ }
114
+ return new LLMType('array', { items: itemType });
115
+ }
116
+
117
+ /**
118
+ * Creates an object schema from named `LLMType` properties.
119
+ * Properties are required by default unless `optional()` is called.
120
+ *
121
+ * @example
122
+ * const user = LLMType.object({
123
+ * name: LLMType.string().description('User name'),
124
+ * age: LLMType.number().optional()
125
+ * });
126
+ *
127
+ * @param {{ [key: string]: LLMType }} properties
128
+ * @param {string} [name=null]
129
+ * @returns {LLMType}
130
+ */
131
+ static object (properties = {}, name = null) {
132
+ if (!properties || typeof properties !== 'object' || Array.isArray(properties)) {
133
+ throw new Error('LLMType.object(properties) expects an object');
134
+ }
135
+ const entries = Object.entries(properties);
136
+ for (const [key, value] of entries) {
137
+ if (!(value instanceof LLMType)) {
138
+ throw new Error(`LLMType.object(properties) expects property "${key}" to be LLMType`);
139
+ }
140
+ }
141
+ return new LLMType('object', { properties: { ...properties }, name });
142
+ }
143
+
144
+ /**
145
+ * Sets human-readable field description.
146
+ *
147
+ * @example
148
+ * const schema = LLMType.string().description('Display name');
149
+ *
150
+ * @param {string} text
151
+ * @returns {this}
152
+ */
153
+ description (text) {
154
+ this._description = text;
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Sets minimum boundary and maps it to a type-specific JSON Schema keyword.
160
+ *
161
+ * Mapping:
162
+ * - string/enum -> `minLength`
163
+ * - number -> `minimum`
164
+ * - array -> `minItems`
165
+ * - object -> `minProperties`
166
+ *
167
+ * @example
168
+ * const schema = LLMType.array(LLMType.string()).min(2);
169
+ *
170
+ * @param {number} value
171
+ * @returns {this}
172
+ */
173
+ min (value) {
174
+ this._min = value;
175
+ return this;
176
+ }
177
+
178
+ /**
179
+ * Sets maximum boundary and maps it to a type-specific JSON Schema keyword.
180
+ *
181
+ * Mapping:
182
+ * - string/enum -> `maxLength`
183
+ * - number -> `maximum`
184
+ * - array -> `maxItems`
185
+ * - object -> `maxProperties`
186
+ *
187
+ * @example
188
+ * const schema = LLMType.number().max(100);
189
+ *
190
+ * @param {number} value
191
+ * @returns {this}
192
+ */
193
+ max (value) {
194
+ this._max = value;
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * Marks field as optional when used as an object property.
200
+ *
201
+ * When used outside object-property context, it has no practical effect,
202
+ * because requiredness is evaluated by parent object schemas.
203
+ *
204
+ * @example
205
+ * const input = LLMType.object({
206
+ * query: LLMType.string(),
207
+ * locale: LLMType.string().optional()
208
+ * });
209
+ *
210
+ * @returns {this}
211
+ */
212
+ optional () {
213
+ this._required = false;
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Returns JSON Schema representation.
219
+ *
220
+ * For object schemas, nested properties are resolved recursively by calling
221
+ * `toJSON()` on each nested `LLMType` instance.
222
+ *
223
+ * @example
224
+ * const schema = LLMType.object({
225
+ * query: LLMType.string().min(1),
226
+ * limit: LLMType.number().optional()
227
+ * }).toJSON();
228
+ *
229
+ * @returns {object}
230
+ */
231
+ toJSON () {
232
+ const schema = this._baseSchema();
233
+
234
+ switch (this._type) {
235
+ case 'enum':
236
+ schema.type = 'string';
237
+ schema.enum = this._values;
238
+ break;
239
+
240
+ case 'array':
241
+ schema.type = 'array';
242
+ schema.items = this._items.toJSON();
243
+ break;
244
+
245
+ case 'object': {
246
+ const properties = {};
247
+ const required = [];
248
+ Object.entries(this._properties).forEach(([key, child]) => {
249
+ properties[key] = child.toJSON();
250
+ if (child._required) {
251
+ required.push(key);
252
+ }
253
+ });
254
+
255
+ schema.type = 'object';
256
+ schema.properties = properties;
257
+ schema.additionalProperties = false;
258
+ if (required.length > 0) {
259
+ schema.required = required;
260
+ }
261
+ break;
262
+ }
263
+
264
+ default:
265
+ schema.type = this._type;
266
+ }
267
+
268
+ this._applyMinMax(schema);
269
+ return schema;
270
+ }
271
+
272
+ /**
273
+ * @returns {object}
274
+ */
275
+ _baseSchema () {
276
+ return /** @type {object} */ ({
277
+ ...(this._description !== null && { description: this._description }),
278
+ ...(this._name !== null && { name: this._name })
279
+ });
280
+ }
281
+
282
+ /**
283
+ * @param {object} schema
284
+ */
285
+ _applyMinMax (schema) {
286
+ if (this._min === null && this._max === null) {
287
+ return;
288
+ }
289
+
290
+ const target = schema;
291
+
292
+ let minKey = null;
293
+ let maxKey = null;
294
+
295
+ switch (this._type) {
296
+ case 'string':
297
+ case 'enum':
298
+ minKey = 'minLength';
299
+ maxKey = 'maxLength';
300
+ break;
301
+
302
+ case 'number':
303
+ minKey = 'minimum';
304
+ maxKey = 'maximum';
305
+ break;
306
+
307
+ case 'array':
308
+ minKey = 'minItems';
309
+ maxKey = 'maxItems';
310
+ break;
311
+
312
+ case 'object':
313
+ minKey = 'minProperties';
314
+ maxKey = 'maxProperties';
315
+ break;
316
+
317
+ default:
318
+ return;
319
+ }
320
+
321
+ if (this._min !== null) {
322
+ target[minKey] = this._min;
323
+ }
324
+ if (this._max !== null) {
325
+ target[maxKey] = this._max;
326
+ }
327
+ }
328
+
329
+ }
330
+
331
+ module.exports = LLMType;
package/src/Processor.js CHANGED
@@ -21,7 +21,7 @@ const LLMMockProvider = require('./LLMMockProvider');
21
21
  /** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
22
22
  /** @typedef {import('./analytics/consts').TrackingType} TrackingType */
23
23
  /** @typedef {import('./analytics/consts').ResponseFlag} ResponseFlag */
24
- /** @typedef {import('./LLM').LLMConfiguration} LLMConfiguration */
24
+ /** @typedef {import('./LLM').LLMGlobalConfig} LLMGlobalConfig */
25
25
 
26
26
  /**
27
27
  * @typedef {object} AutoTypingConfig
@@ -113,7 +113,7 @@ const LLMMockProvider = require('./LLMMockProvider');
113
113
  * @prop {string} [apiUrl] - Url for calling orchestrator API
114
114
  * @prop {Function} [fetch] - Fetch function for calling orchestrator API
115
115
  * @prop {number} [sessionDuration] - Session duration for analytic purposes
116
- * @prop {LLMConfiguration} [llm] - LLM model configuration
116
+ * @prop {LLMGlobalConfig} [llm] - LLM model configuration
117
117
  * @prop {Preloader<R>} [preloader]
118
118
  */
119
119
 
@@ -405,19 +405,19 @@ class Processor extends EventEmitter {
405
405
  return { status: 304 };
406
406
  }
407
407
 
408
- /** @type {LLMConfiguration} */
409
- const llmOptions = {
408
+ /** @type {LLMGlobalConfig} */
409
+ const llmConfiguration = {
410
410
  provider: new LLMMockProvider(),
411
411
  ...this.options.llm
412
412
  };
413
413
 
414
414
  if (typeof messageSender.logPrompt === 'function') {
415
- Object.assign(llmOptions, {
415
+ Object.assign(llmConfiguration, {
416
416
  logger: messageSender
417
417
  });
418
418
  }
419
419
 
420
- const llm = new LLM(llmOptions, Ai.ai);
420
+ const llm = new LLM(llmConfiguration, Ai.ai, this.options.log);
421
421
 
422
422
  const result = await this
423
423
  ._dispatch(message, pageId, messageSender, responderData, preloadPromise, llm);
package/src/Responder.js CHANGED
@@ -34,7 +34,7 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
34
34
  /** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
35
35
  /** @typedef {import('./analytics/consts').TrackingType} TrackingType */
36
36
 
37
- /** @typedef {import('./LLM').LLMConfiguration} LLMConfiguration */
37
+ /** @typedef {import('./LLM').LLMCallPreset} LLMCallPreset */
38
38
  /** @typedef {import('./LLM').PreprocessedRule} PreprocessedRule */
39
39
  /** @typedef {import('./LLM').EvaluationRuleAction} EvaluationRuleAction */
40
40
  /** @typedef {import('./LLM').EvaluationResult} EvaluationResult */
@@ -42,6 +42,8 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
42
42
  /** @typedef {import('./LLMSession').LLMFilterFn} LLMFilterFn */
43
43
  /** @typedef {import('./LLMSession').LLMFilter} LLMFilter */
44
44
  /** @typedef {import('./LLMSession').FilterScope} FilterScope */
45
+ /** @typedef {import('./LLMSession').LLMMessageSrc} LLMMessageSrc */
46
+ /** @typedef {import('./LLMSession').AsyncLLMMessage} AsyncLLMMessage */
45
47
  /** @typedef {import('./utils/stateData').IStateRequest} IStateRequest */
46
48
 
47
49
  /**
@@ -63,6 +65,7 @@ Object.freeze(ExpectedInput);
63
65
  * @prop {string} [webview_height_ratio]
64
66
  * @prop {string} [onCloseAction]
65
67
  * @prop {string} [onCloseActionData]
68
+ * @prop {number} [max_length]
66
69
  */
67
70
 
68
71
  /**
@@ -355,13 +358,34 @@ class Responder {
355
358
  return this;
356
359
  }
357
360
 
358
- async llmSession (contextType = this.LLM_CTX_DEFAULT) {
361
+ /**
362
+ *
363
+ * @param {string} contextType
364
+ * @returns {AsyncLLMMessage}
365
+ */
366
+ async _getSystemMessagesForType (contextType) {
359
367
  const system = await this._getSystemContentForType(contextType);
368
+ return system.map((content) => ({ role: LLM.ROLE_SYSTEM, content }));
369
+ }
360
370
 
361
- const chat = system.map((content) => ({ role: LLM.ROLE_SYSTEM, content }));
371
+ /**
372
+ *
373
+ * @param {string} contextType
374
+ * @returns {Promise<LLMSession>}
375
+ */
376
+ async llmSession (contextType = this.LLM_CTX_DEFAULT) {
377
+ const system = this._getSystemMessagesForType(contextType);
362
378
 
363
379
  const filters = this._filtersForContext(contextType);
364
- return new LLMSession(this.llm, chat, this._llmSend.bind(this), filters);
380
+ return new LLMSession(this.llm, [system], this._llmSend.bind(this), filters);
381
+ }
382
+
383
+ /**
384
+ *
385
+ * @returns {LLMSession}
386
+ */
387
+ llmSessionEmpty () {
388
+ return new LLMSession(this.llm, [], this._llmSend.bind(this));
365
389
  }
366
390
 
367
391
  /**
@@ -372,7 +396,7 @@ class Responder {
372
396
  */
373
397
  async llmEvaluate (session, contextType = this.LLM_CTX_DEFAULT) {
374
398
  const rules = this._llmResultRules.get(contextType) || [];
375
- const text = session.lastResponse();
399
+ const text = session.lastResponseSync();
376
400
 
377
401
  if (rules.length === 0 || !text) {
378
402
  return {
@@ -449,38 +473,35 @@ class Responder {
449
473
  return resolved;
450
474
  }
451
475
 
452
- async llmSessionWithHistory (
476
+ /**
477
+ *
478
+ * @param {'default'|string} contextType
479
+ * @param {LLMCallPreset} callPreset
480
+ * @returns {LLMSession}
481
+ */
482
+ llmSessionWithHistory (
453
483
  contextType = this.LLM_CTX_DEFAULT,
454
- transcriptLength = undefined,
455
- transcriptFlag = undefined
484
+ callPreset = undefined
456
485
  ) {
457
486
  const {
458
487
  transcriptAnonymize,
459
- transcriptFlag: transcriptFlagCfg,
460
- transcriptLength: transcriptLengthCfg
461
- } = this.llm.configuration;
462
-
463
- const computedTranscriptLength = transcriptLength === undefined
464
- ? transcriptLengthCfg
465
- : transcriptLength;
466
- const computedTranscriptFlag = transcriptFlag === undefined
467
- ? transcriptFlagCfg : transcriptFlag;
468
-
469
- const [systems, transcript] = await Promise.all([
470
- this._getSystemContentForType(contextType),
471
- this.getTranscript(
472
- computedTranscriptLength,
473
- computedTranscriptFlag
474
- )
475
- ]);
476
-
477
- const chat = [
478
- ...systems.map((content) => ({ role: LLM.ROLE_SYSTEM, content })),
479
- ...LLM.anonymizeTranscript(transcript, transcriptAnonymize)
480
- ];
488
+ transcriptFlag,
489
+ transcriptLength
490
+ } = this.llm.llmOptions(callPreset);
481
491
 
482
492
  const filters = this._filtersForContext(contextType);
483
- return new LLMSession(this.llm, chat, this._llmSend.bind(this), filters);
493
+ return new LLMSession(this.llm, [
494
+ this._getSystemMessagesForType(contextType),
495
+ this._getTranscriptMessages(transcriptLength, transcriptFlag, transcriptAnonymize)
496
+ ], this._llmSend.bind(this), filters);
497
+ }
498
+
499
+ async _getTranscriptMessages (transcriptLength, transcriptFlag, transcriptAnonymize) {
500
+ const transcript = await this.getTranscript(
501
+ transcriptLength,
502
+ transcriptFlag
503
+ );
504
+ return LLM.anonymizeTranscript(transcript, transcriptAnonymize);
484
505
  }
485
506
 
486
507
  /**
@@ -1188,6 +1209,7 @@ class Responder {
1188
1209
  * After processing the user input, next requests will be processed as usual,
1189
1210
  *
1190
1211
  * @param {ExpectedInput} [expectedInput]
1212
+ * @param {ExpectedInputOptions} [options]
1191
1213
  * @returns {this}
1192
1214
  * @example
1193
1215
  *
@@ -1213,9 +1235,9 @@ class Responder {
1213
1235
  * .setState({ cardNumber });
1214
1236
  * });
1215
1237
  */
1216
- expectedConfidentInput (expectedInput = null) {
1217
- if (expectedInput) {
1218
- this.expectedInput(expectedInput);
1238
+ expectedConfidentInput (expectedInput = null, options = undefined) {
1239
+ if (expectedInput || options) {
1240
+ this.expectedInput(expectedInput, options);
1219
1241
  }
1220
1242
  return this.setState({
1221
1243
  _expectedConfidentInput: true
package/src/Router.js CHANGED
@@ -7,8 +7,13 @@ const { pathToRegexp } = require('path-to-regexp');
7
7
  const ReducerWrapper = require('./ReducerWrapper');
8
8
  const { makeAbsolute } = require('./utils');
9
9
 
10
- const Responder = require('./Responder'); // eslint-disable-line no-unused-vars
11
- const Request = require('./Request'); // eslint-disable-line no-unused-vars
10
+ /** @typedef {import('./Responder')} Responder */
11
+
12
+ /**
13
+ * @template {object} [S=object]
14
+ * @template {BaseConfiguration} [C=object]
15
+ * @typedef {import('./Request')<S, C>} Request
16
+ */
12
17
 
13
18
  function defaultPathContext () {
14
19
  return { globalIntentsMeta: {}, path: '/*' };
@@ -5,13 +5,22 @@
5
5
 
6
6
  const Router = require('../Router');
7
7
 
8
- function expectedInput ({ type = null, confident = false }, { isLastIndex }) {
8
+ function expectedInput ({ type = null, confident = false, maxLength = null }, { isLastIndex }) {
9
9
 
10
10
  return (req, res) => {
11
+ const opts = {};
12
+
13
+ const useMaxLen = maxLength && parseInt(`${maxLength}`, 10);
14
+ if ((!type || type === 'password') && useMaxLen) {
15
+ Object.assign(opts, {
16
+ max_length: useMaxLen
17
+ });
18
+ }
19
+
11
20
  if (confident) {
12
21
  res.expectedConfidentInput(type);
13
22
  } else if (type) {
14
- res.expectedInput(type);
23
+ res.expectedInput(type, opts);
15
24
  }
16
25
 
17
26
  return isLastIndex ? Router.END : Router.CONTINUE;
@@ -455,8 +455,7 @@ function message (params, context = {}) {
455
455
  let discard;
456
456
 
457
457
  try {
458
- session = await res.llmSessionWithHistory(params.llmContextType);
459
-
458
+ session = res.llmSessionWithHistory(params.llmContextType);
460
459
  await session.systemPrompt(text)
461
460
  .generate();
462
461
 
@@ -483,7 +482,7 @@ function message (params, context = {}) {
483
482
  // // no response?
484
483
  // }
485
484
 
486
- const messages = session.messagesToSend();
485
+ const messages = session.messagesToSendSync();
487
486
 
488
487
  res.setFlag(LLM.GPT_FLAG);
489
488
 
@@ -1,12 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm run:*)",
5
- "Bash(npx mocha:*)",
6
- "mcp__ide__getDiagnostics",
7
- "Bash(grep -r \"nonce\\\\|senderId.*FormData\\\\|multipart\\\\|busboy\" /Users/ondrejveres/Wingbot/wingbot --include=*.js --exclude-dir=node_modules --exclude-dir=coverage --exclude-dir=docs --exclude-dir=documentation)",
8
- "Bash(grep:*)",
9
- "Bash(npm test:*)"
10
- ]
11
- }
12
- }