@owlmeans/llm-common 0.1.18-rc.38 → 0.1.18-rc.39
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/llm-common/SKILL.md +1 -1
- package/build/inquiry/helper/types.d.ts +47 -0
- package/build/inquiry/helper/types.d.ts.map +1 -0
- package/build/inquiry/helper/types.js +2 -0
- package/build/inquiry/helper/types.js.map +1 -0
- package/build/inquiry/helper.d.ts +4 -0
- package/build/inquiry/helper.d.ts.map +1 -0
- package/build/inquiry/helper.js +39 -0
- package/build/inquiry/helper.js.map +1 -0
- package/build/inquiry/index.d.ts +2 -0
- package/build/inquiry/index.d.ts.map +1 -1
- package/build/inquiry/index.js +1 -0
- package/build/inquiry/index.js.map +1 -1
- package/build/inquiry/utils.d.ts +6 -37
- package/build/inquiry/utils.d.ts.map +1 -1
- package/build/inquiry/utils.js +13 -71
- package/build/inquiry/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/inquiry/helper/types.ts +47 -0
- package/src/inquiry/helper.ts +53 -0
- package/src/inquiry/index.ts +2 -0
- package/src/inquiry/utils.ts +13 -77
- package/tests/inquiry.spec.ts +23 -24
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ extends these; `@owlmeans/llm` implements against them.
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
bun add @owlmeans/llm-common@^0.1.18-rc.
|
|
23
|
+
bun add @owlmeans/llm-common@^0.1.18-rc.39
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Usage
|
|
@@ -96,7 +96,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
96
96
|
your project's skill store (`.agents/skills/`):
|
|
97
97
|
|
|
98
98
|
```sh
|
|
99
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
99
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.49
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/llm-common",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.39",
|
|
5
|
+
"generatedAt": "2026-10-05T21:03:06.400Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/llm-common
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/llm-common": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/llm-common": "^0.1.18-rc.39"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The contracts half of the LLM stack. **No `@langchain/*` runtime dependency** — importable
|
|
14
14
|
from a browser bundle, a queue worker, or any package that must not pull an inference SDK.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Inquiry, InquiryAnswer } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers over an inquiry and its answer — no IO, no state, no clock.
|
|
4
|
+
*
|
|
5
|
+
* They are here rather than in the runtime because every layer needs the same reading of an
|
|
6
|
+
* answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
|
|
7
|
+
* screen that finally shows it. A second reading of "was this answered" is a second contract.
|
|
8
|
+
*/
|
|
9
|
+
export interface InquiryHelper {
|
|
10
|
+
/**
|
|
11
|
+
* What to assume when nobody answers: the question's own default, or a decline.
|
|
12
|
+
*
|
|
13
|
+
* A decline is an ANSWER — the run carries on and records what it assumed — which is why this
|
|
14
|
+
* never throws and never returns null.
|
|
15
|
+
*/
|
|
16
|
+
defaultAnswerFor: (inquiry: Inquiry) => InquiryAnswer;
|
|
17
|
+
/**
|
|
18
|
+
* The one thing an answer says, as a string — the first chosen value, else the free text, else
|
|
19
|
+
* `null` when it says nothing at all (a decline, or an empty answer).
|
|
20
|
+
*/
|
|
21
|
+
answeredWith: (answer: InquiryAnswer) => string | null;
|
|
22
|
+
/** Nobody could decide. Distinct from an empty answer — see {@link InquiryHelper.answeredWith}. */
|
|
23
|
+
isDeclined: (answer: InquiryAnswer) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Cut an answer's free TEXT to the ceiling and SAY SO.
|
|
26
|
+
*
|
|
27
|
+
* Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
|
|
28
|
+
* question's own option values — so slicing one does not degrade an answer, it silently replaces
|
|
29
|
+
* it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
|
|
30
|
+
* what the person chose. An over-long value is a defect upstream rather than a long answer (the
|
|
31
|
+
* connector's schema refuses one outright instead of shortening it), so it travels on whole and is
|
|
32
|
+
* REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
|
|
33
|
+
* what the person gave".
|
|
34
|
+
*/
|
|
35
|
+
capAnswer: (answer: InquiryAnswer, max?: number) => InquiryAnswer;
|
|
36
|
+
/**
|
|
37
|
+
* The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
|
|
38
|
+
* {@link INQUIRY_STATE_TEXT_CHARS}.
|
|
39
|
+
*
|
|
40
|
+
* The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
|
|
41
|
+
* asks several questions still holds a state made of keys rather than of paragraphs.
|
|
42
|
+
*/
|
|
43
|
+
stateAnswerOf: (answer: InquiryAnswer) => InquiryAnswer;
|
|
44
|
+
/** One-line label of a question — for a note, a trace line or a run row. */
|
|
45
|
+
renderInquiry: (inquiry: Inquiry) => string;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/inquiry/helper/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,aAAa,CAAA;IACrD;;;OAGG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAA;IACtD,mGAAmG;IACnG,UAAU,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAA;IAC9C;;;;;;;;;;OAUG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,aAAa,CAAA;IACjE;;;;;;OAMG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAA;IACvD,4EAA4E;IAC5E,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;CAC5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/inquiry/helper/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/inquiry/helper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,eAAO,MAAM,mBAAmB,QAAO,aA8CtC,CAAA;AAED,eAAO,MAAM,aAAa,eAAwB,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DEFAULT_INQUIRY_ANSWER_CHARS, INQUIRY_STATE_TEXT_CHARS } from './consts.js';
|
|
2
|
+
export const createInquiryHelper = () => {
|
|
3
|
+
const defaultAnswerFor = (inquiry) => inquiry.default != null
|
|
4
|
+
? { inquiryId: inquiry.id, value: inquiry.default }
|
|
5
|
+
: { inquiryId: inquiry.id, declined: true };
|
|
6
|
+
const answeredWith = (answer) => {
|
|
7
|
+
const value = Array.isArray(answer.value) ? answer.value[0] : answer.value;
|
|
8
|
+
if (value != null && value !== '')
|
|
9
|
+
return value;
|
|
10
|
+
if (answer.text != null && answer.text !== '')
|
|
11
|
+
return answer.text;
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
const isDeclined = (answer) => answer.declined === true;
|
|
15
|
+
const capAnswer = (answer, max = DEFAULT_INQUIRY_ANSWER_CHARS) => {
|
|
16
|
+
const text = answer.text != null && answer.text.length > max
|
|
17
|
+
? answer.text.slice(0, max)
|
|
18
|
+
: undefined;
|
|
19
|
+
const values = Array.isArray(answer.value)
|
|
20
|
+
? answer.value
|
|
21
|
+
: answer.value != null ? [answer.value] : [];
|
|
22
|
+
const oversized = values.some(value => value.length > max);
|
|
23
|
+
if (text == null && !oversized)
|
|
24
|
+
return answer;
|
|
25
|
+
return { ...answer, ...(text != null ? { text } : {}), truncated: true };
|
|
26
|
+
};
|
|
27
|
+
const stateAnswerOf = (answer) => answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
|
|
28
|
+
? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
|
|
29
|
+
: answer;
|
|
30
|
+
const renderInquiry = (inquiry) => {
|
|
31
|
+
const options = inquiry.options != null && inquiry.options.length > 0
|
|
32
|
+
? ` (${inquiry.options.map(option => option.value).join(' | ')})`
|
|
33
|
+
: '';
|
|
34
|
+
return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim();
|
|
35
|
+
};
|
|
36
|
+
return { defaultAnswerFor, answeredWith, isDeclined, capAnswer, stateAnswerOf, renderInquiry };
|
|
37
|
+
};
|
|
38
|
+
export const inquiryHelper = createInquiryHelper();
|
|
39
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/inquiry/helper.ts"],"names":[],"mappings":"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"}
|
package/build/inquiry/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inquiry/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inquiry/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,mBAAmB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
|
package/build/inquiry/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/inquiry/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/inquiry/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAG3B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
|
package/build/inquiry/utils.d.ts
CHANGED
|
@@ -1,45 +1,14 @@
|
|
|
1
1
|
import type { Inquiry, InquiryAnswer } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Pure helpers over an inquiry and its answer — no IO, no state, no clock.
|
|
4
|
-
*
|
|
5
|
-
* They are here rather than in the runtime because every layer needs the same reading of an
|
|
6
|
-
* answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
|
|
7
|
-
* screen that finally shows it. A second reading of "was this answered" is a second contract.
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* What to assume when nobody answers: the question's own default, or a decline.
|
|
11
|
-
*
|
|
12
|
-
* A decline is an ANSWER — the run carries on and records what it assumed — which is why this
|
|
13
|
-
* never throws and never returns null.
|
|
14
|
-
*/
|
|
2
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.defaultAnswerFor(…)` */
|
|
15
3
|
export declare const defaultAnswerFor: (inquiry: Inquiry) => InquiryAnswer;
|
|
16
|
-
/**
|
|
17
|
-
* The one thing an answer says, as a string — the first chosen value, else the free text, else
|
|
18
|
-
* `null` when it says nothing at all (a decline, or an empty answer).
|
|
19
|
-
*/
|
|
4
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.answeredWith(…)` */
|
|
20
5
|
export declare const answeredWith: (answer: InquiryAnswer) => string | null;
|
|
21
|
-
/**
|
|
6
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.isDeclined(…)` */
|
|
22
7
|
export declare const isDeclined: (answer: InquiryAnswer) => boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Cut an answer's free TEXT to the ceiling and SAY SO.
|
|
25
|
-
*
|
|
26
|
-
* Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
|
|
27
|
-
* question's own option values — so slicing one does not degrade an answer, it silently replaces
|
|
28
|
-
* it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
|
|
29
|
-
* what the person chose. An over-long value is a defect upstream rather than a long answer (the
|
|
30
|
-
* connector's schema refuses one outright instead of shortening it), so it travels on whole and is
|
|
31
|
-
* REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
|
|
32
|
-
* what the person gave".
|
|
33
|
-
*/
|
|
8
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.capAnswer(…)` */
|
|
34
9
|
export declare const capAnswer: (answer: InquiryAnswer, max?: number) => InquiryAnswer;
|
|
35
|
-
/**
|
|
36
|
-
* The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
|
|
37
|
-
* {@link INQUIRY_STATE_TEXT_CHARS}.
|
|
38
|
-
*
|
|
39
|
-
* The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
|
|
40
|
-
* asks several questions still holds a state made of keys rather than of paragraphs.
|
|
41
|
-
*/
|
|
10
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.stateAnswerOf(…)` */
|
|
42
11
|
export declare const stateAnswerOf: (answer: InquiryAnswer) => InquiryAnswer;
|
|
43
|
-
/**
|
|
12
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.renderInquiry(…)` */
|
|
44
13
|
export declare const renderInquiry: (inquiry: Inquiry) => string;
|
|
45
14
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAExD
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAExD,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,YAAa,OAAO,KAAG,aAAwD,CAAA;AAE5G,gFAAgF;AAChF,eAAO,MAAM,YAAY,WAAY,aAAa,KAAG,MAAM,GAAG,IAA0C,CAAA;AAExG,8EAA8E;AAC9E,eAAO,MAAM,UAAU,WAAY,aAAa,KAAG,OAA2C,CAAA;AAE9F,6EAA6E;AAC7E,eAAO,MAAM,SAAS,WAAY,aAAa,QAAQ,MAAM,KAAG,aAAqD,CAAA;AAErH,iFAAiF;AACjF,eAAO,MAAM,aAAa,WAAY,aAAa,KAAG,aAAoD,CAAA;AAE1G,iFAAiF;AACjF,eAAO,MAAM,aAAa,YAAa,OAAO,KAAG,MAA8C,CAAA"}
|
package/build/inquiry/utils.js
CHANGED
|
@@ -1,72 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export const defaultAnswerFor = (inquiry) => inquiry.default != null
|
|
16
|
-
? { inquiryId: inquiry.id, value: inquiry.default }
|
|
17
|
-
: { inquiryId: inquiry.id, declined: true };
|
|
18
|
-
/**
|
|
19
|
-
* The one thing an answer says, as a string — the first chosen value, else the free text, else
|
|
20
|
-
* `null` when it says nothing at all (a decline, or an empty answer).
|
|
21
|
-
*/
|
|
22
|
-
export const answeredWith = (answer) => {
|
|
23
|
-
const value = Array.isArray(answer.value) ? answer.value[0] : answer.value;
|
|
24
|
-
if (value != null && value !== '')
|
|
25
|
-
return value;
|
|
26
|
-
if (answer.text != null && answer.text !== '')
|
|
27
|
-
return answer.text;
|
|
28
|
-
return null;
|
|
29
|
-
};
|
|
30
|
-
/** Nobody could decide. Distinct from an empty answer — see {@link answeredWith}. */
|
|
31
|
-
export const isDeclined = (answer) => answer.declined === true;
|
|
32
|
-
/**
|
|
33
|
-
* Cut an answer's free TEXT to the ceiling and SAY SO.
|
|
34
|
-
*
|
|
35
|
-
* Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
|
|
36
|
-
* question's own option values — so slicing one does not degrade an answer, it silently replaces
|
|
37
|
-
* it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
|
|
38
|
-
* what the person chose. An over-long value is a defect upstream rather than a long answer (the
|
|
39
|
-
* connector's schema refuses one outright instead of shortening it), so it travels on whole and is
|
|
40
|
-
* REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
|
|
41
|
-
* what the person gave".
|
|
42
|
-
*/
|
|
43
|
-
export const capAnswer = (answer, max = DEFAULT_INQUIRY_ANSWER_CHARS) => {
|
|
44
|
-
const text = answer.text != null && answer.text.length > max
|
|
45
|
-
? answer.text.slice(0, max)
|
|
46
|
-
: undefined;
|
|
47
|
-
const values = Array.isArray(answer.value)
|
|
48
|
-
? answer.value
|
|
49
|
-
: answer.value != null ? [answer.value] : [];
|
|
50
|
-
const oversized = values.some(value => value.length > max);
|
|
51
|
-
if (text == null && !oversized)
|
|
52
|
-
return answer;
|
|
53
|
-
return { ...answer, ...(text != null ? { text } : {}), truncated: true };
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
|
|
57
|
-
* {@link INQUIRY_STATE_TEXT_CHARS}.
|
|
58
|
-
*
|
|
59
|
-
* The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
|
|
60
|
-
* asks several questions still holds a state made of keys rather than of paragraphs.
|
|
61
|
-
*/
|
|
62
|
-
export const stateAnswerOf = (answer) => answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
|
|
63
|
-
? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
|
|
64
|
-
: answer;
|
|
65
|
-
/** One-line label of a question — for a note, a trace line or a run row. */
|
|
66
|
-
export const renderInquiry = (inquiry) => {
|
|
67
|
-
const options = inquiry.options != null && inquiry.options.length > 0
|
|
68
|
-
? ` (${inquiry.options.map(option => option.value).join(' | ')})`
|
|
69
|
-
: '';
|
|
70
|
-
return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim();
|
|
71
|
-
};
|
|
1
|
+
import { inquiryHelper } from './helper.js';
|
|
2
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.defaultAnswerFor(…)` */
|
|
3
|
+
export const defaultAnswerFor = (inquiry) => inquiryHelper.defaultAnswerFor(inquiry);
|
|
4
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.answeredWith(…)` */
|
|
5
|
+
export const answeredWith = (answer) => inquiryHelper.answeredWith(answer);
|
|
6
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.isDeclined(…)` */
|
|
7
|
+
export const isDeclined = (answer) => inquiryHelper.isDeclined(answer);
|
|
8
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.capAnswer(…)` */
|
|
9
|
+
export const capAnswer = (answer, max) => inquiryHelper.capAnswer(answer, max);
|
|
10
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.stateAnswerOf(…)` */
|
|
11
|
+
export const stateAnswerOf = (answer) => inquiryHelper.stateAnswerOf(answer);
|
|
12
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.renderInquiry(…)` */
|
|
13
|
+
export const renderInquiry = (inquiry) => inquiryHelper.renderInquiry(inquiry);
|
|
72
14
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAgB,EAAiB,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;AAE5G,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAqB,EAAiB,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;AAExG,8EAA8E;AAC9E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAqB,EAAW,EAAE,CAAC,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;AAE9F,6EAA6E;AAC7E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAqB,EAAE,GAAY,EAAiB,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAErH,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAqB,EAAiB,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;AAE1G,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Inquiry, InquiryAnswer } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure helpers over an inquiry and its answer — no IO, no state, no clock.
|
|
5
|
+
*
|
|
6
|
+
* They are here rather than in the runtime because every layer needs the same reading of an
|
|
7
|
+
* answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
|
|
8
|
+
* screen that finally shows it. A second reading of "was this answered" is a second contract.
|
|
9
|
+
*/
|
|
10
|
+
export interface InquiryHelper {
|
|
11
|
+
/**
|
|
12
|
+
* What to assume when nobody answers: the question's own default, or a decline.
|
|
13
|
+
*
|
|
14
|
+
* A decline is an ANSWER — the run carries on and records what it assumed — which is why this
|
|
15
|
+
* never throws and never returns null.
|
|
16
|
+
*/
|
|
17
|
+
defaultAnswerFor: (inquiry: Inquiry) => InquiryAnswer
|
|
18
|
+
/**
|
|
19
|
+
* The one thing an answer says, as a string — the first chosen value, else the free text, else
|
|
20
|
+
* `null` when it says nothing at all (a decline, or an empty answer).
|
|
21
|
+
*/
|
|
22
|
+
answeredWith: (answer: InquiryAnswer) => string | null
|
|
23
|
+
/** Nobody could decide. Distinct from an empty answer — see {@link InquiryHelper.answeredWith}. */
|
|
24
|
+
isDeclined: (answer: InquiryAnswer) => boolean
|
|
25
|
+
/**
|
|
26
|
+
* Cut an answer's free TEXT to the ceiling and SAY SO.
|
|
27
|
+
*
|
|
28
|
+
* Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
|
|
29
|
+
* question's own option values — so slicing one does not degrade an answer, it silently replaces
|
|
30
|
+
* it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
|
|
31
|
+
* what the person chose. An over-long value is a defect upstream rather than a long answer (the
|
|
32
|
+
* connector's schema refuses one outright instead of shortening it), so it travels on whole and is
|
|
33
|
+
* REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
|
|
34
|
+
* what the person gave".
|
|
35
|
+
*/
|
|
36
|
+
capAnswer: (answer: InquiryAnswer, max?: number) => InquiryAnswer
|
|
37
|
+
/**
|
|
38
|
+
* The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
|
|
39
|
+
* {@link INQUIRY_STATE_TEXT_CHARS}.
|
|
40
|
+
*
|
|
41
|
+
* The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
|
|
42
|
+
* asks several questions still holds a state made of keys rather than of paragraphs.
|
|
43
|
+
*/
|
|
44
|
+
stateAnswerOf: (answer: InquiryAnswer) => InquiryAnswer
|
|
45
|
+
/** One-line label of a question — for a note, a trace line or a run row. */
|
|
46
|
+
renderInquiry: (inquiry: Inquiry) => string
|
|
47
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DEFAULT_INQUIRY_ANSWER_CHARS, INQUIRY_STATE_TEXT_CHARS } from './consts.js'
|
|
2
|
+
import type { Inquiry, InquiryAnswer } from './types.js'
|
|
3
|
+
import type { InquiryHelper } from './helper/types.js'
|
|
4
|
+
|
|
5
|
+
export const createInquiryHelper = (): InquiryHelper => {
|
|
6
|
+
const defaultAnswerFor = (inquiry: Inquiry): InquiryAnswer =>
|
|
7
|
+
inquiry.default != null
|
|
8
|
+
? { inquiryId: inquiry.id, value: inquiry.default }
|
|
9
|
+
: { inquiryId: inquiry.id, declined: true }
|
|
10
|
+
|
|
11
|
+
const answeredWith = (answer: InquiryAnswer): string | null => {
|
|
12
|
+
const value = Array.isArray(answer.value) ? answer.value[0] : answer.value
|
|
13
|
+
if (value != null && value !== '') return value
|
|
14
|
+
if (answer.text != null && answer.text !== '') return answer.text
|
|
15
|
+
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isDeclined = (answer: InquiryAnswer): boolean => answer.declined === true
|
|
20
|
+
|
|
21
|
+
const capAnswer = (
|
|
22
|
+
answer: InquiryAnswer, max: number = DEFAULT_INQUIRY_ANSWER_CHARS
|
|
23
|
+
): InquiryAnswer => {
|
|
24
|
+
const text = answer.text != null && answer.text.length > max
|
|
25
|
+
? answer.text.slice(0, max)
|
|
26
|
+
: undefined
|
|
27
|
+
const values = Array.isArray(answer.value)
|
|
28
|
+
? answer.value
|
|
29
|
+
: answer.value != null ? [answer.value] : []
|
|
30
|
+
const oversized = values.some(value => value.length > max)
|
|
31
|
+
|
|
32
|
+
if (text == null && !oversized) return answer
|
|
33
|
+
|
|
34
|
+
return { ...answer, ...(text != null ? { text } : {}), truncated: true }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stateAnswerOf = (answer: InquiryAnswer): InquiryAnswer =>
|
|
38
|
+
answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
|
|
39
|
+
? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
|
|
40
|
+
: answer
|
|
41
|
+
|
|
42
|
+
const renderInquiry = (inquiry: Inquiry): string => {
|
|
43
|
+
const options = inquiry.options != null && inquiry.options.length > 0
|
|
44
|
+
? ` (${inquiry.options.map(option => option.value).join(' | ')})`
|
|
45
|
+
: ''
|
|
46
|
+
|
|
47
|
+
return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { defaultAnswerFor, answeredWith, isDeclined, capAnswer, stateAnswerOf, renderInquiry }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const inquiryHelper = createInquiryHelper()
|
package/src/inquiry/index.ts
CHANGED
package/src/inquiry/utils.ts
CHANGED
|
@@ -1,84 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inquiryHelper } from './helper.js'
|
|
2
2
|
import type { Inquiry, InquiryAnswer } from './types.js'
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* They are here rather than in the runtime because every layer needs the same reading of an
|
|
8
|
-
* answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
|
|
9
|
-
* screen that finally shows it. A second reading of "was this answered" is a second contract.
|
|
10
|
-
*/
|
|
4
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.defaultAnswerFor(…)` */
|
|
5
|
+
export const defaultAnswerFor = (inquiry: Inquiry): InquiryAnswer => inquiryHelper.defaultAnswerFor(inquiry)
|
|
11
6
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* A decline is an ANSWER — the run carries on and records what it assumed — which is why this
|
|
16
|
-
* never throws and never returns null.
|
|
17
|
-
*/
|
|
18
|
-
export const defaultAnswerFor = (inquiry: Inquiry): InquiryAnswer =>
|
|
19
|
-
inquiry.default != null
|
|
20
|
-
? { inquiryId: inquiry.id, value: inquiry.default }
|
|
21
|
-
: { inquiryId: inquiry.id, declined: true }
|
|
7
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.answeredWith(…)` */
|
|
8
|
+
export const answeredWith = (answer: InquiryAnswer): string | null => inquiryHelper.answeredWith(answer)
|
|
22
9
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
* `null` when it says nothing at all (a decline, or an empty answer).
|
|
26
|
-
*/
|
|
27
|
-
export const answeredWith = (answer: InquiryAnswer): string | null => {
|
|
28
|
-
const value = Array.isArray(answer.value) ? answer.value[0] : answer.value
|
|
29
|
-
if (value != null && value !== '') return value
|
|
30
|
-
if (answer.text != null && answer.text !== '') return answer.text
|
|
10
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.isDeclined(…)` */
|
|
11
|
+
export const isDeclined = (answer: InquiryAnswer): boolean => inquiryHelper.isDeclined(answer)
|
|
31
12
|
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.capAnswer(…)` */
|
|
14
|
+
export const capAnswer = (answer: InquiryAnswer, max?: number): InquiryAnswer => inquiryHelper.capAnswer(answer, max)
|
|
34
15
|
|
|
35
|
-
/**
|
|
36
|
-
export const
|
|
16
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.stateAnswerOf(…)` */
|
|
17
|
+
export const stateAnswerOf = (answer: InquiryAnswer): InquiryAnswer => inquiryHelper.stateAnswerOf(answer)
|
|
37
18
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
* Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
|
|
42
|
-
* question's own option values — so slicing one does not degrade an answer, it silently replaces
|
|
43
|
-
* it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
|
|
44
|
-
* what the person chose. An over-long value is a defect upstream rather than a long answer (the
|
|
45
|
-
* connector's schema refuses one outright instead of shortening it), so it travels on whole and is
|
|
46
|
-
* REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
|
|
47
|
-
* what the person gave".
|
|
48
|
-
*/
|
|
49
|
-
export const capAnswer = (
|
|
50
|
-
answer: InquiryAnswer, max: number = DEFAULT_INQUIRY_ANSWER_CHARS
|
|
51
|
-
): InquiryAnswer => {
|
|
52
|
-
const text = answer.text != null && answer.text.length > max
|
|
53
|
-
? answer.text.slice(0, max)
|
|
54
|
-
: undefined
|
|
55
|
-
const values = Array.isArray(answer.value)
|
|
56
|
-
? answer.value
|
|
57
|
-
: answer.value != null ? [answer.value] : []
|
|
58
|
-
const oversized = values.some(value => value.length > max)
|
|
59
|
-
|
|
60
|
-
if (text == null && !oversized) return answer
|
|
61
|
-
|
|
62
|
-
return { ...answer, ...(text != null ? { text } : {}), truncated: true }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
|
|
67
|
-
* {@link INQUIRY_STATE_TEXT_CHARS}.
|
|
68
|
-
*
|
|
69
|
-
* The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
|
|
70
|
-
* asks several questions still holds a state made of keys rather than of paragraphs.
|
|
71
|
-
*/
|
|
72
|
-
export const stateAnswerOf = (answer: InquiryAnswer): InquiryAnswer =>
|
|
73
|
-
answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
|
|
74
|
-
? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
|
|
75
|
-
: answer
|
|
76
|
-
|
|
77
|
-
/** One-line label of a question — for a note, a trace line or a run row. */
|
|
78
|
-
export const renderInquiry = (inquiry: Inquiry): string => {
|
|
79
|
-
const options = inquiry.options != null && inquiry.options.length > 0
|
|
80
|
-
? ` (${inquiry.options.map(option => option.value).join(' | ')})`
|
|
81
|
-
: ''
|
|
82
|
-
|
|
83
|
-
return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim()
|
|
84
|
-
}
|
|
19
|
+
/** @deprecated compat:factory-refactor — use `inquiryHelper.renderInquiry(…)` */
|
|
20
|
+
export const renderInquiry = (inquiry: Inquiry): string => inquiryHelper.renderInquiry(inquiry)
|
package/tests/inquiry.spec.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
INQUIRY_STATE_TEXT_CHARS, InquiryKind, isDeclined, renderInquiry, stateAnswerOf,
|
|
3
|
+
CONFIRM_NO, CONFIRM_YES, DEFAULT_INQUIRY_ANSWER_CHARS, INQUIRY_STATE_TEXT_CHARS, InquiryKind, inquiryHelper,
|
|
5
4
|
} from '../src/index.js'
|
|
6
5
|
import type { Inquiry, InquiryAnswer } from '../src/index.js'
|
|
7
6
|
|
|
@@ -24,61 +23,61 @@ const inquiry = (patch: Partial<Inquiry> = {}): Inquiry => ({
|
|
|
24
23
|
|
|
25
24
|
describe('@owlmeans/llm-common — what an unanswered question assumes', () => {
|
|
26
25
|
test('a question with a default answers itself with it', () => {
|
|
27
|
-
expect(defaultAnswerFor(inquiry({ default: 'postgres' })))
|
|
26
|
+
expect(inquiryHelper.defaultAnswerFor(inquiry({ default: 'postgres' })))
|
|
28
27
|
.toEqual({ inquiryId: 'q1', value: 'postgres' })
|
|
29
28
|
})
|
|
30
29
|
|
|
31
30
|
test('a question without one declines — a decision, never a failure', () => {
|
|
32
|
-
const answer = defaultAnswerFor(inquiry())
|
|
33
|
-
expect(isDeclined(answer)).toBe(true)
|
|
34
|
-
expect(answeredWith(answer)).toBeNull()
|
|
31
|
+
const answer = inquiryHelper.defaultAnswerFor(inquiry())
|
|
32
|
+
expect(inquiryHelper.isDeclined(answer)).toBe(true)
|
|
33
|
+
expect(inquiryHelper.answeredWith(answer)).toBeNull()
|
|
35
34
|
})
|
|
36
35
|
|
|
37
36
|
test('a multiple-choice default travels whole', () => {
|
|
38
|
-
expect(defaultAnswerFor(inquiry({ multiple: true, default: ['postgres', 'mongo'] })).value)
|
|
37
|
+
expect(inquiryHelper.defaultAnswerFor(inquiry({ multiple: true, default: ['postgres', 'mongo'] })).value)
|
|
39
38
|
.toEqual(['postgres', 'mongo'])
|
|
40
39
|
})
|
|
41
40
|
})
|
|
42
41
|
|
|
43
42
|
describe('@owlmeans/llm-common — reading one answer', () => {
|
|
44
43
|
test('the chosen value wins over free text, and the first of a list is the answer', () => {
|
|
45
|
-
expect(answeredWith({ inquiryId: 'q1', value: 'mongo', text: 'or postgres' })).toBe('mongo')
|
|
46
|
-
expect(answeredWith({ inquiryId: 'q1', value: ['mongo', 'redis'] })).toBe('mongo')
|
|
44
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', value: 'mongo', text: 'or postgres' })).toBe('mongo')
|
|
45
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', value: ['mongo', 'redis'] })).toBe('mongo')
|
|
47
46
|
})
|
|
48
47
|
|
|
49
48
|
test('free text answers a question that offered no options', () => {
|
|
50
|
-
expect(answeredWith({ inquiryId: 'q1', text: 'the vendor one' })).toBe('the vendor one')
|
|
49
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', text: 'the vendor one' })).toBe('the vendor one')
|
|
51
50
|
})
|
|
52
51
|
|
|
53
52
|
test('an answer that says nothing reads as nothing, declined or not', () => {
|
|
54
|
-
expect(answeredWith({ inquiryId: 'q1' })).toBeNull()
|
|
55
|
-
expect(answeredWith({ inquiryId: 'q1', value: '', text: '' })).toBeNull()
|
|
56
|
-
expect(isDeclined({ inquiryId: 'q1' })).toBe(false)
|
|
57
|
-
expect(isDeclined({ inquiryId: 'q1', declined: true })).toBe(true)
|
|
53
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1' })).toBeNull()
|
|
54
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', value: '', text: '' })).toBeNull()
|
|
55
|
+
expect(inquiryHelper.isDeclined({ inquiryId: 'q1' })).toBe(false)
|
|
56
|
+
expect(inquiryHelper.isDeclined({ inquiryId: 'q1', declined: true })).toBe(true)
|
|
58
57
|
})
|
|
59
58
|
|
|
60
59
|
test('a confirmation is just its two values', () => {
|
|
61
|
-
expect(answeredWith({ inquiryId: 'q1', value: CONFIRM_YES })).toBe(CONFIRM_YES)
|
|
62
|
-
expect(answeredWith({ inquiryId: 'q1', value: CONFIRM_NO })).toBe(CONFIRM_NO)
|
|
60
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', value: CONFIRM_YES })).toBe(CONFIRM_YES)
|
|
61
|
+
expect(inquiryHelper.answeredWith({ inquiryId: 'q1', value: CONFIRM_NO })).toBe(CONFIRM_NO)
|
|
63
62
|
})
|
|
64
63
|
})
|
|
65
64
|
|
|
66
65
|
describe('@owlmeans/llm-common — the one ceiling', () => {
|
|
67
66
|
test('an answer inside the ceiling is left exactly as it came', () => {
|
|
68
67
|
const answer: InquiryAnswer = { inquiryId: 'q1', value: 'mongo', text: 'because of the driver' }
|
|
69
|
-
expect(capAnswer(answer)).toEqual(answer)
|
|
70
|
-
expect(capAnswer(answer).truncated).toBeUndefined()
|
|
68
|
+
expect(inquiryHelper.capAnswer(answer)).toEqual(answer)
|
|
69
|
+
expect(inquiryHelper.capAnswer(answer).truncated).toBeUndefined()
|
|
71
70
|
})
|
|
72
71
|
|
|
73
72
|
test('a cut is never silent', () => {
|
|
74
|
-
const capped = capAnswer({ inquiryId: 'q1', text: 'x'.repeat(DEFAULT_INQUIRY_ANSWER_CHARS + 5) })
|
|
73
|
+
const capped = inquiryHelper.capAnswer({ inquiryId: 'q1', text: 'x'.repeat(DEFAULT_INQUIRY_ANSWER_CHARS + 5) })
|
|
75
74
|
expect(capped.text).toHaveLength(DEFAULT_INQUIRY_ANSWER_CHARS)
|
|
76
75
|
expect(capped.truncated).toBe(true)
|
|
77
76
|
})
|
|
78
77
|
|
|
79
78
|
test('an over-long value is reported, never shortened into an option nobody offered', () => {
|
|
80
79
|
const value = ['ok', 'y'.repeat(40)]
|
|
81
|
-
const capped = capAnswer({ inquiryId: 'q1', value }, 10)
|
|
80
|
+
const capped = inquiryHelper.capAnswer({ inquiryId: 'q1', value }, 10)
|
|
82
81
|
// Cutting prose degrades an answer; cutting an identifier CHANGES it — `answeredWith` would
|
|
83
82
|
// then hand the caller a choice the question never carried.
|
|
84
83
|
expect(capped.value).toEqual(value)
|
|
@@ -89,24 +88,24 @@ describe('@owlmeans/llm-common — the one ceiling', () => {
|
|
|
89
88
|
const answer: InquiryAnswer = {
|
|
90
89
|
inquiryId: 'q1', value: 'postgres', text: 'z'.repeat(INQUIRY_STATE_TEXT_CHARS + 100),
|
|
91
90
|
}
|
|
92
|
-
const stored = stateAnswerOf(answer)
|
|
91
|
+
const stored = inquiryHelper.stateAnswerOf(answer)
|
|
93
92
|
expect(stored.value).toBe('postgres')
|
|
94
93
|
expect(stored.text).toHaveLength(INQUIRY_STATE_TEXT_CHARS)
|
|
95
94
|
expect(stored.truncated).toBe(true)
|
|
96
95
|
// The answer handed back to whoever asked is untouched — only the persisted copy is reduced.
|
|
97
96
|
expect(answer.text).toHaveLength(INQUIRY_STATE_TEXT_CHARS + 100)
|
|
98
|
-
expect(stateAnswerOf({ inquiryId: 'q1', value: 'postgres' }).truncated).toBeUndefined()
|
|
97
|
+
expect(inquiryHelper.stateAnswerOf({ inquiryId: 'q1', value: 'postgres' }).truncated).toBeUndefined()
|
|
99
98
|
})
|
|
100
99
|
})
|
|
101
100
|
|
|
102
101
|
describe('@owlmeans/llm-common — one line for a trace', () => {
|
|
103
102
|
test('the label names the kind, the question and the choices, on one line', () => {
|
|
104
|
-
expect(renderInquiry(inquiry({ question: 'Which\n database?' })))
|
|
103
|
+
expect(inquiryHelper.renderInquiry(inquiry({ question: 'Which\n database?' })))
|
|
105
104
|
.toBe('[choice] Which database? (postgres | mongo)')
|
|
106
105
|
})
|
|
107
106
|
|
|
108
107
|
test('a question with no options renders without an empty bracket', () => {
|
|
109
|
-
expect(renderInquiry({ id: 'q2', kind: InquiryKind.Confirm, question: 'Relocate the origin?' }))
|
|
108
|
+
expect(inquiryHelper.renderInquiry({ id: 'q2', kind: InquiryKind.Confirm, question: 'Relocate the origin?' }))
|
|
110
109
|
.toBe('[confirm] Relocate the origin?')
|
|
111
110
|
})
|
|
112
111
|
})
|