@far-world-labs/verblets 0.2.0 → 0.3.2
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 +86 -213
- package/dist/index.browser.js +74 -0
- package/dist/index.js +548 -0
- package/dist/shared-C6kPWghF.js +7806 -0
- package/package.json +32 -11
- package/.cursor/launch.json +0 -30
- package/.cursor/settings.json +0 -20
- package/.github/workflows/branch-protection.yml +0 -22
- package/.github/workflows/ci.yml +0 -165
- package/.husky/pre-commit +0 -4
- package/.prettierrc +0 -6
- package/.release-it.json +0 -12
- package/.vitest.config.examples.js +0 -12
- package/.vitest.config.js +0 -8
- package/.vscode/launch.json +0 -31
- package/AGENTS.md +0 -220
- package/DEVELOPING.md +0 -105
- package/docker-compose.yml +0 -7
- package/eslint.config.js +0 -80
- package/scripts/clear-redis.js +0 -74
- package/scripts/generate-chain/index.js +0 -111
- package/scripts/generate-lib/index.js +0 -68
- package/scripts/generate-test/index.js +0 -137
- package/scripts/generate-verblet/README.md +0 -17
- package/scripts/generate-verblet/index.js +0 -110
- package/scripts/run.sh +0 -15
- package/scripts/runner/index.js +0 -56
- package/scripts/simple-editor/README.md +0 -34
- package/scripts/simple-editor/index.js +0 -79
- package/scripts/summarize-files/index.js +0 -70
- package/src/chains/README.md +0 -30
- package/src/chains/anonymize/README.md +0 -21
- package/src/chains/anonymize/index.examples.js +0 -75
- package/src/chains/anonymize/index.js +0 -121
- package/src/chains/anonymize/index.spec.js +0 -78
- package/src/chains/bulk-central-tendency/index.examples.js +0 -138
- package/src/chains/bulk-central-tendency/index.js +0 -91
- package/src/chains/bulk-filter/README.md +0 -21
- package/src/chains/bulk-filter/index.examples.js +0 -22
- package/src/chains/bulk-filter/index.js +0 -58
- package/src/chains/bulk-filter/index.spec.js +0 -38
- package/src/chains/bulk-find/README.md +0 -16
- package/src/chains/bulk-find/index.examples.js +0 -20
- package/src/chains/bulk-find/index.js +0 -30
- package/src/chains/bulk-find/index.spec.js +0 -26
- package/src/chains/bulk-group/README.md +0 -23
- package/src/chains/bulk-group/index.examples.js +0 -18
- package/src/chains/bulk-group/index.js +0 -34
- package/src/chains/bulk-group/index.spec.js +0 -41
- package/src/chains/bulk-map/README.md +0 -43
- package/src/chains/bulk-map/index.examples.js +0 -17
- package/src/chains/bulk-map/index.js +0 -86
- package/src/chains/bulk-map/index.spec.js +0 -44
- package/src/chains/bulk-reduce/README.md +0 -12
- package/src/chains/bulk-reduce/index.examples.js +0 -15
- package/src/chains/bulk-reduce/index.js +0 -13
- package/src/chains/bulk-reduce/index.spec.js +0 -25
- package/src/chains/bulk-score/README.md +0 -16
- package/src/chains/bulk-score/bulk-score-result.json +0 -18
- package/src/chains/bulk-score/index.examples.js +0 -22
- package/src/chains/bulk-score/index.js +0 -133
- package/src/chains/bulk-score/index.spec.js +0 -30
- package/src/chains/category-samples/README.md +0 -61
- package/src/chains/category-samples/index.examples.js +0 -103
- package/src/chains/category-samples/index.js +0 -134
- package/src/chains/collect-terms/README.md +0 -12
- package/src/chains/collect-terms/index.examples.js +0 -16
- package/src/chains/collect-terms/index.js +0 -44
- package/src/chains/collect-terms/index.spec.js +0 -25
- package/src/chains/conversation/README.md +0 -26
- package/src/chains/conversation/index.examples.js +0 -398
- package/src/chains/conversation/index.js +0 -126
- package/src/chains/conversation/index.spec.js +0 -148
- package/src/chains/conversation/turn-policies.js +0 -93
- package/src/chains/conversation/turn-policies.md +0 -123
- package/src/chains/conversation/turn-policies.spec.js +0 -135
- package/src/chains/date/README.md +0 -12
- package/src/chains/date/index.examples.js +0 -47
- package/src/chains/date/index.js +0 -74
- package/src/chains/date/index.spec.js +0 -62
- package/src/chains/disambiguate/README.md +0 -22
- package/src/chains/disambiguate/disambiguate-meanings-result.json +0 -16
- package/src/chains/disambiguate/index.examples.js +0 -18
- package/src/chains/disambiguate/index.js +0 -92
- package/src/chains/disambiguate/index.spec.js +0 -25
- package/src/chains/dismantle/README.md +0 -67
- package/src/chains/dismantle/dismantle.examples.js +0 -27
- package/src/chains/dismantle/index.examples.js +0 -30
- package/src/chains/dismantle/index.js +0 -303
- package/src/chains/dismantle/index.spec.js +0 -32
- package/src/chains/expect/README.md +0 -171
- package/src/chains/expect/index.examples.js +0 -146
- package/src/chains/expect/index.js +0 -207
- package/src/chains/expect/index.spec.js +0 -324
- package/src/chains/filter-ambiguous/README.md +0 -11
- package/src/chains/filter-ambiguous/index.examples.js +0 -20
- package/src/chains/filter-ambiguous/index.js +0 -49
- package/src/chains/filter-ambiguous/index.spec.js +0 -31
- package/src/chains/glossary/README.md +0 -19
- package/src/chains/glossary/index.examples.js +0 -386
- package/src/chains/glossary/index.js +0 -75
- package/src/chains/glossary/index.spec.js +0 -19
- package/src/chains/intersections/README.md +0 -166
- package/src/chains/intersections/index.examples.js +0 -280
- package/src/chains/intersections/index.js +0 -218
- package/src/chains/intersections/intersection-result.json +0 -38
- package/src/chains/list/index.examples.js +0 -68
- package/src/chains/list/index.js +0 -214
- package/src/chains/list/index.spec.js +0 -67
- package/src/chains/list/list-result.json +0 -16
- package/src/chains/list/schema.json +0 -24
- package/src/chains/llm-logger/README.md +0 -366
- package/src/chains/llm-logger/index.js +0 -591
- package/src/chains/llm-logger/index.spec.js +0 -391
- package/src/chains/llm-logger/schema.json +0 -105
- package/src/chains/questions/index.examples.js +0 -69
- package/src/chains/questions/index.js +0 -135
- package/src/chains/questions/index.spec.js +0 -29
- package/src/chains/scan-js/index.js +0 -116
- package/src/chains/set-interval/README.md +0 -81
- package/src/chains/set-interval/index.examples.js +0 -64
- package/src/chains/set-interval/index.js +0 -152
- package/src/chains/set-interval/index.spec.js +0 -70
- package/src/chains/socratic/README.md +0 -17
- package/src/chains/socratic/index.js +0 -64
- package/src/chains/socratic/index.spec.js +0 -24
- package/src/chains/sort/index.examples.js +0 -36
- package/src/chains/sort/index.js +0 -163
- package/src/chains/sort/index.spec.js +0 -112
- package/src/chains/sort/sort-result.json +0 -16
- package/src/chains/summary-map/README.md +0 -41
- package/src/chains/summary-map/index.examples.js +0 -64
- package/src/chains/summary-map/index.js +0 -226
- package/src/chains/summary-map/index.spec.js +0 -153
- package/src/chains/test/index.js +0 -114
- package/src/chains/test-advice/index.js +0 -35
- package/src/chains/themes/README.md +0 -20
- package/src/chains/themes/index.examples.js +0 -17
- package/src/chains/themes/index.js +0 -28
- package/src/chains/themes/index.spec.js +0 -19
- package/src/chains/veiled-variants/index.examples.js +0 -18
- package/src/chains/veiled-variants/index.js +0 -107
- package/src/chains/veiled-variants/index.spec.js +0 -40
- package/src/constants/common.js +0 -13
- package/src/constants/messages.js +0 -3
- package/src/constants/models.js +0 -184
- package/src/index.js +0 -203
- package/src/json-schemas/README.md +0 -13
- package/src/json-schemas/cars-test.json +0 -11
- package/src/json-schemas/index.js +0 -12
- package/src/json-schemas/intent.json +0 -38
- package/src/json-schemas/schema-dot-org-photograph.json +0 -133
- package/src/json-schemas/schema-dot-org-place.json +0 -129
- package/src/lib/README.md +0 -26
- package/src/lib/any-signal/index.js +0 -28
- package/src/lib/assert/README.md +0 -84
- package/src/lib/assert/index.js +0 -50
- package/src/lib/bulk-filter/README.md +0 -22
- package/src/lib/bulk-filter/index.examples.js +0 -27
- package/src/lib/bulk-filter/index.js +0 -63
- package/src/lib/bulk-filter/index.spec.js +0 -38
- package/src/lib/bulk-find/README.md +0 -18
- package/src/lib/bulk-find/index.examples.js +0 -19
- package/src/lib/bulk-find/index.js +0 -30
- package/src/lib/bulk-find/index.spec.js +0 -41
- package/src/lib/chatgpt/index.js +0 -163
- package/src/lib/combinations/index.js +0 -30
- package/src/lib/combinations/index.spec.js +0 -23
- package/src/lib/editor/index.js +0 -31
- package/src/lib/functional/index.js +0 -28
- package/src/lib/logger-service/index.js +0 -32
- package/src/lib/parse-js-parts/index.js +0 -321
- package/src/lib/parse-js-parts/index.spec.js +0 -156
- package/src/lib/parse-llm-list/README.md +0 -39
- package/src/lib/parse-llm-list/index.js +0 -54
- package/src/lib/parse-llm-list/index.spec.js +0 -59
- package/src/lib/path-aliases/index.js +0 -37
- package/src/lib/path-aliases/index.spec.js +0 -64
- package/src/lib/pave/index.js +0 -34
- package/src/lib/pave/index.spec.js +0 -76
- package/src/lib/prompt-cache/index.js +0 -50
- package/src/lib/retry/index.js +0 -66
- package/src/lib/retry/index.spec.js +0 -86
- package/src/lib/ring-buffer/README.md +0 -82
- package/src/lib/ring-buffer/index.js +0 -235
- package/src/lib/ring-buffer/index.spec.js +0 -388
- package/src/lib/search-best-first/city-walk.spec.js +0 -37
- package/src/lib/search-best-first/index.js +0 -97
- package/src/lib/search-best-first/index.spec.js +0 -35
- package/src/lib/search-js-files/code-features-property-definitions.json +0 -123
- package/src/lib/search-js-files/index.examples.js +0 -22
- package/src/lib/search-js-files/index.js +0 -155
- package/src/lib/search-js-files/index.spec.js +0 -34
- package/src/lib/search-js-files/scan-file.js +0 -242
- package/src/lib/shorten-text/index.js +0 -25
- package/src/lib/shorten-text/index.spec.js +0 -68
- package/src/lib/strip-numeric/index.js +0 -5
- package/src/lib/strip-response/index.js +0 -30
- package/src/lib/template-replace/index.js +0 -23
- package/src/lib/template-replace/index.spec.js +0 -60
- package/src/lib/timed-abort-controller/index.js +0 -41
- package/src/lib/to-bool/index.js +0 -8
- package/src/lib/to-date/index.js +0 -11
- package/src/lib/to-enum/index.js +0 -14
- package/src/lib/to-number/index.js +0 -12
- package/src/lib/to-number-with-units/index.js +0 -51
- package/src/lib/transcribe/index.js +0 -78
- package/src/prompts/README.md +0 -17
- package/src/prompts/as-enum.js +0 -5
- package/src/prompts/as-json-schema.js +0 -9
- package/src/prompts/as-object-with-schema.js +0 -26
- package/src/prompts/as-schema-org-text.js +0 -25
- package/src/prompts/as-schema-org-type.js +0 -1
- package/src/prompts/blog-post.js +0 -7
- package/src/prompts/code-features.js +0 -24
- package/src/prompts/constants.js +0 -101
- package/src/prompts/features-json-schema.js +0 -27
- package/src/prompts/generate-collection.js +0 -26
- package/src/prompts/generate-list.js +0 -48
- package/src/prompts/generate-questions.js +0 -19
- package/src/prompts/index.js +0 -20
- package/src/prompts/intent.js +0 -60
- package/src/prompts/output-succinct-names.js +0 -3
- package/src/prompts/select-from-threshold.js +0 -17
- package/src/prompts/sort.js +0 -31
- package/src/prompts/style.js +0 -38
- package/src/prompts/summarize.js +0 -13
- package/src/prompts/token-budget.js +0 -3
- package/src/prompts/wrap-list.js +0 -11
- package/src/prompts/wrap-variable.js +0 -36
- package/src/services/llm-model/global-overrides.spec.js +0 -432
- package/src/services/llm-model/index.js +0 -308
- package/src/services/llm-model/model.js +0 -21
- package/src/services/llm-model/negotiate.spec.js +0 -447
- package/src/services/redis/index.js +0 -147
- package/src/test/setup.js +0 -20
- package/src/verblets/README.md +0 -26
- package/src/verblets/auto/index.examples.js +0 -31
- package/src/verblets/auto/index.js +0 -28
- package/src/verblets/auto/index.spec.js +0 -32
- package/src/verblets/bool/README.md +0 -36
- package/src/verblets/bool/index.examples.js +0 -80
- package/src/verblets/bool/index.js +0 -25
- package/src/verblets/bool/index.schema.json +0 -14
- package/src/verblets/bool/index.spec.js +0 -33
- package/src/verblets/central-tendency/README.md +0 -166
- package/src/verblets/central-tendency/central-tendency-result.json +0 -24
- package/src/verblets/central-tendency/index.examples.js +0 -196
- package/src/verblets/central-tendency/index.js +0 -171
- package/src/verblets/central-tendency/index.spec.js +0 -148
- package/src/verblets/conversation-turn/README.md +0 -33
- package/src/verblets/conversation-turn/index.examples.js +0 -218
- package/src/verblets/conversation-turn/index.js +0 -68
- package/src/verblets/conversation-turn/index.spec.js +0 -77
- package/src/verblets/conversation-turn-multi/README.md +0 -31
- package/src/verblets/conversation-turn-multi/index.examples.js +0 -160
- package/src/verblets/conversation-turn-multi/index.js +0 -104
- package/src/verblets/conversation-turn-multi/index.spec.js +0 -63
- package/src/verblets/enum/index.examples.js +0 -30
- package/src/verblets/enum/index.js +0 -18
- package/src/verblets/enum/index.spec.js +0 -35
- package/src/verblets/expect/README.md +0 -64
- package/src/verblets/expect/index.examples.js +0 -109
- package/src/verblets/expect/index.js +0 -75
- package/src/verblets/expect/index.spec.js +0 -127
- package/src/verblets/intent/index.examples.js +0 -139
- package/src/verblets/intent/index.js +0 -60
- package/src/verblets/intent/index.spec.js +0 -31
- package/src/verblets/intersection/README.md +0 -16
- package/src/verblets/intersection/index.examples.js +0 -89
- package/src/verblets/intersection/index.js +0 -125
- package/src/verblets/intersection/index.spec.js +0 -60
- package/src/verblets/intersection/intersection-result.json +0 -16
- package/src/verblets/list-expand/README.md +0 -10
- package/src/verblets/list-expand/index.examples.js +0 -14
- package/src/verblets/list-expand/index.js +0 -104
- package/src/verblets/list-expand/index.spec.js +0 -18
- package/src/verblets/list-expand/list-expand-result.json +0 -16
- package/src/verblets/list-filter/README.md +0 -22
- package/src/verblets/list-filter/index.examples.js +0 -26
- package/src/verblets/list-filter/index.js +0 -18
- package/src/verblets/list-filter/index.spec.js +0 -19
- package/src/verblets/list-find/README.md +0 -11
- package/src/verblets/list-find/index.examples.js +0 -15
- package/src/verblets/list-find/index.js +0 -17
- package/src/verblets/list-find/index.spec.js +0 -19
- package/src/verblets/list-group/README.md +0 -16
- package/src/verblets/list-group/index.examples.js +0 -16
- package/src/verblets/list-group/index.js +0 -112
- package/src/verblets/list-group/index.spec.js +0 -35
- package/src/verblets/list-group/list-group-result.json +0 -16
- package/src/verblets/list-map/README.md +0 -11
- package/src/verblets/list-map/index.examples.js +0 -15
- package/src/verblets/list-map/index.js +0 -26
- package/src/verblets/list-map/index.spec.js +0 -17
- package/src/verblets/list-reduce/README.md +0 -10
- package/src/verblets/list-reduce/index.examples.js +0 -14
- package/src/verblets/list-reduce/index.js +0 -21
- package/src/verblets/list-reduce/index.spec.js +0 -27
- package/src/verblets/list-reduce/index.spec.jsx +0 -27
- package/src/verblets/name/README.md +0 -15
- package/src/verblets/name/index.examples.js +0 -28
- package/src/verblets/name/index.js +0 -19
- package/src/verblets/name/index.spec.js +0 -33
- package/src/verblets/name-similar-to/README.md +0 -26
- package/src/verblets/name-similar-to/index.examples.js +0 -18
- package/src/verblets/name-similar-to/index.js +0 -20
- package/src/verblets/name-similar-to/index.spec.js +0 -13
- package/src/verblets/number/index.examples.js +0 -199
- package/src/verblets/number/index.js +0 -25
- package/src/verblets/number/index.spec.js +0 -33
- package/src/verblets/number-with-units/index.examples.js +0 -38
- package/src/verblets/number-with-units/index.js +0 -84
- package/src/verblets/number-with-units/index.spec.js +0 -46
- package/src/verblets/number-with-units/number-with-units-result.json +0 -23
- package/src/verblets/people-list/README.md +0 -28
- package/src/verblets/people-list/index.examples.js +0 -184
- package/src/verblets/people-list/index.js +0 -44
- package/src/verblets/people-list/index.spec.js +0 -49
- package/src/verblets/schema-org/index.examples.js +0 -51
- package/src/verblets/schema-org/index.js +0 -37
- package/src/verblets/schema-org/index.spec.js +0 -39
- package/src/verblets/sentiment/README.md +0 -10
- package/src/verblets/sentiment/index.examples.js +0 -20
- package/src/verblets/sentiment/index.js +0 -9
- package/src/verblets/sentiment/index.spec.js +0 -20
- package/src/verblets/to-object/README.md +0 -38
- package/src/verblets/to-object/index.examples.js +0 -29
- package/src/verblets/to-object/index.js +0 -131
- package/src/verblets/to-object/index.spec.js +0 -71
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as R from 'ramda';
|
|
2
|
-
|
|
3
|
-
// Returns a modifier function that generates a prompt fragment based on a given value
|
|
4
|
-
/*
|
|
5
|
-
* let openEndedDegree = findDegreeModifier(openEnded, [
|
|
6
|
-
* { threshold: 0.2, degree: 'must not be' },
|
|
7
|
-
* { threshold: 0.4, degree: 'must be minimally' },
|
|
8
|
-
* { threshold: 0.6, degree: 'must be somewhat' },
|
|
9
|
-
* { threshold: 0.8, degree: 'must be very' },
|
|
10
|
-
* { threshold: 1.0, degree: 'must be extremely' },
|
|
11
|
-
* ]);
|
|
12
|
-
* const openEndedPrompt = `Questions ${openEndedDegree} open-ended. `
|
|
13
|
-
*/
|
|
14
|
-
export default (value, thresholds = []) => {
|
|
15
|
-
const threshold = thresholds.find((t) => value <= t.threshold) || R.last(thresholds);
|
|
16
|
-
return threshold.degree;
|
|
17
|
-
};
|
package/src/prompts/sort.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import wrapVariable from './wrap-variable.js';
|
|
2
|
-
import {
|
|
3
|
-
contentIsDetails,
|
|
4
|
-
contentIsFixes,
|
|
5
|
-
contentIsMain,
|
|
6
|
-
contentIsSortCriteria,
|
|
7
|
-
onlyJSONStringArray,
|
|
8
|
-
} from './constants.js';
|
|
9
|
-
|
|
10
|
-
export const defaultSortDescription = 'alphabetical order';
|
|
11
|
-
export const defaultFixes = 'Ignore duplicates in the list';
|
|
12
|
-
export const defaultSortOrder = 'descending';
|
|
13
|
-
|
|
14
|
-
export default (
|
|
15
|
-
{ description = defaultSortDescription, fixes = defaultFixes, sortOrder = defaultSortOrder },
|
|
16
|
-
list
|
|
17
|
-
) => {
|
|
18
|
-
const listLines = JSON.stringify(list, undefined, 2);
|
|
19
|
-
|
|
20
|
-
return `${contentIsSortCriteria} ${wrapVariable(description, {
|
|
21
|
-
tag: 'criteria',
|
|
22
|
-
})}
|
|
23
|
-
|
|
24
|
-
${contentIsMain} ${wrapVariable(listLines, { tag: 'main-content' })}
|
|
25
|
-
|
|
26
|
-
${contentIsDetails} ${sortOrder} order
|
|
27
|
-
|
|
28
|
-
${contentIsFixes} ${wrapVariable(fixes, { tag: 'fixes' })}
|
|
29
|
-
|
|
30
|
-
${onlyJSONStringArray}`;
|
|
31
|
-
};
|
package/src/prompts/style.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export default (
|
|
2
|
-
text,
|
|
3
|
-
{
|
|
4
|
-
tone = ['informal'],
|
|
5
|
-
vocabulary = ['software engineering'],
|
|
6
|
-
sentenceStructure = ['varied sentence length', 'active voice'],
|
|
7
|
-
pointOfView = ['first person plural'],
|
|
8
|
-
noise = 0,
|
|
9
|
-
minWords = 0,
|
|
10
|
-
maxWords,
|
|
11
|
-
} = {}
|
|
12
|
-
) => {
|
|
13
|
-
const toneModifiers = `Tone: ${tone.join(', ')}`;
|
|
14
|
-
const vocabularyModifiers = `Vocabulary: ${vocabulary.join(', ')}`;
|
|
15
|
-
const sentenceStructureModifiers = `Sentence structure: ${sentenceStructure.join(', ')}`;
|
|
16
|
-
const pointOfViewModifiers = `Point of view: ${pointOfView.join(', ')}`;
|
|
17
|
-
const lengthModifier = `Use between ${minWords} and ${maxWords ?? 'any number'} of words`;
|
|
18
|
-
|
|
19
|
-
let noiseModifier = '';
|
|
20
|
-
if (noise > 0.5) {
|
|
21
|
-
noiseModifier =
|
|
22
|
-
"Completely reshape the ideas here, don't stick with the original structure. Don't change the meaning of the content though.";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return `Rewrite the following content:
|
|
26
|
-
\`\`\`
|
|
27
|
-
${text}
|
|
28
|
-
\`\`\`
|
|
29
|
-
|
|
30
|
-
Use the following style:
|
|
31
|
-
${toneModifiers}
|
|
32
|
-
${vocabularyModifiers}
|
|
33
|
-
${sentenceStructureModifiers}
|
|
34
|
-
${pointOfViewModifiers}
|
|
35
|
-
${lengthModifier}
|
|
36
|
-
${noiseModifier}
|
|
37
|
-
`;
|
|
38
|
-
};
|
package/src/prompts/summarize.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import wrapVariable from './wrap-variable.js';
|
|
2
|
-
import { onlyFullCode } from './constants.js';
|
|
3
|
-
|
|
4
|
-
export default (text, instructions = '') => {
|
|
5
|
-
return `Summarize the following content. ${onlyFullCode}
|
|
6
|
-
|
|
7
|
-
${wrapVariable(instructions, {
|
|
8
|
-
forceHTML: true,
|
|
9
|
-
tag: 'summarization-instructions',
|
|
10
|
-
})}
|
|
11
|
-
|
|
12
|
-
${wrapVariable(text, { forceHTML: true, tag: 'content-to-summarize' })}`;
|
|
13
|
-
};
|
package/src/prompts/wrap-list.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import wrapVariable from './wrap-variable.js';
|
|
2
|
-
|
|
3
|
-
export default (list = [], { introText = 'Consider the following items:' } = {}) => {
|
|
4
|
-
const listText = list.map((f, i) => ` - ${i + 1}. ${f}`).join('\n');
|
|
5
|
-
|
|
6
|
-
let listFragment = wrapVariable('\n');
|
|
7
|
-
if (list.length) {
|
|
8
|
-
listFragment = `${introText} ${wrapVariable(listText)}`;
|
|
9
|
-
}
|
|
10
|
-
return listFragment;
|
|
11
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export default (
|
|
2
|
-
variable,
|
|
3
|
-
{ forceHTML = false, name, tag = 'data', title, fit = 'comfortable' } = {}
|
|
4
|
-
) => {
|
|
5
|
-
if (!variable) {
|
|
6
|
-
return '';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let nameAttribute = '';
|
|
10
|
-
if (name) {
|
|
11
|
-
nameAttribute = `name="${name}"`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let variableResolved = typeof variable !== 'undefined' ? variable : '';
|
|
15
|
-
if (typeof variable !== 'string') {
|
|
16
|
-
variableResolved = JSON.stringify(variable, null, 2);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const isHTML = /\n/.test(variableResolved) || forceHTML || name;
|
|
20
|
-
let variableWrapped = !isHTML ? `"${variableResolved}"` : variableResolved;
|
|
21
|
-
if (isHTML) {
|
|
22
|
-
let fitChar = '\n';
|
|
23
|
-
if (fit !== 'comfortable') {
|
|
24
|
-
fitChar = '';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
variableWrapped = `<${tag}${nameAttribute}>${fitChar}${variableResolved}${fitChar}</${tag}>`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let titlePrefixed = variableWrapped;
|
|
31
|
-
if (title && variableResolved.length > 0) {
|
|
32
|
-
titlePrefixed = `${title} ${variableWrapped}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return titlePrefixed;
|
|
36
|
-
};
|
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import fetch from 'node-fetch';
|
|
3
|
-
import modelService from './index.js';
|
|
4
|
-
import Model from './model.js';
|
|
5
|
-
|
|
6
|
-
import { run as chatgptRun } from '../../lib/chatgpt/index.js';
|
|
7
|
-
|
|
8
|
-
// Mock node-fetch before importing chatgpt
|
|
9
|
-
vi.mock('node-fetch', () => ({
|
|
10
|
-
default: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
// Mock Redis
|
|
14
|
-
vi.mock('../../services/redis/index.js', () => ({
|
|
15
|
-
getClient: vi.fn().mockResolvedValue({
|
|
16
|
-
get: vi.fn().mockResolvedValue(null),
|
|
17
|
-
setex: vi.fn().mockResolvedValue('OK'),
|
|
18
|
-
}),
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
// Mock the prompt cache to avoid Redis dependencies
|
|
22
|
-
vi.mock('../../lib/prompt-cache/index.js', () => ({
|
|
23
|
-
get: vi.fn().mockResolvedValue({ result: null }),
|
|
24
|
-
set: vi.fn().mockResolvedValue('OK'),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
// Helper tokenizer
|
|
28
|
-
const tokenizer = (t) => t.split(' ');
|
|
29
|
-
|
|
30
|
-
describe('Global Override System', () => {
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
// Reset models and overrides before each test
|
|
33
|
-
modelService.models = {};
|
|
34
|
-
modelService.clearGlobalOverride(); // Clear all overrides
|
|
35
|
-
modelService.bestPublicModelKey = 'fastGood';
|
|
36
|
-
|
|
37
|
-
// Setup basic models for testing
|
|
38
|
-
modelService.models = {
|
|
39
|
-
fastGood: new Model({
|
|
40
|
-
name: 'gpt-4-fast-good',
|
|
41
|
-
maxContextWindow: 128000,
|
|
42
|
-
maxOutputTokens: 16384,
|
|
43
|
-
requestTimeout: 1000,
|
|
44
|
-
apiUrl: 'https://api.openai.com',
|
|
45
|
-
apiKey: 'test-key',
|
|
46
|
-
endpoint: '/v1/chat/completions',
|
|
47
|
-
tokenizer,
|
|
48
|
-
}),
|
|
49
|
-
fastCheap: new Model({
|
|
50
|
-
name: 'gpt-4-fast-cheap',
|
|
51
|
-
maxContextWindow: 128000,
|
|
52
|
-
maxOutputTokens: 8192,
|
|
53
|
-
requestTimeout: 1000,
|
|
54
|
-
apiUrl: 'https://api.openai.com',
|
|
55
|
-
apiKey: 'test-key',
|
|
56
|
-
endpoint: '/v1/chat/completions',
|
|
57
|
-
tokenizer,
|
|
58
|
-
}),
|
|
59
|
-
fastCheapReasoning: new Model({
|
|
60
|
-
name: 'gpt-4-fast-cheap-reasoning',
|
|
61
|
-
maxContextWindow: 200000,
|
|
62
|
-
maxOutputTokens: 50000,
|
|
63
|
-
requestTimeout: 3000,
|
|
64
|
-
apiUrl: 'https://api.openai.com',
|
|
65
|
-
apiKey: 'reasoning-key',
|
|
66
|
-
endpoint: '/v1/chat/completions',
|
|
67
|
-
tokenizer,
|
|
68
|
-
}),
|
|
69
|
-
customModel: new Model({
|
|
70
|
-
name: 'custom-model-name',
|
|
71
|
-
maxContextWindow: 64000,
|
|
72
|
-
maxOutputTokens: 8192,
|
|
73
|
-
requestTimeout: 2000,
|
|
74
|
-
apiUrl: 'https://custom.api.com',
|
|
75
|
-
apiKey: 'custom-key',
|
|
76
|
-
endpoint: '/v1/completions',
|
|
77
|
-
tokenizer,
|
|
78
|
-
}),
|
|
79
|
-
expensiveReasoning: new Model({
|
|
80
|
-
name: 'gpt-4-reasoning',
|
|
81
|
-
maxContextWindow: 200000,
|
|
82
|
-
maxOutputTokens: 100000,
|
|
83
|
-
requestTimeout: 5000,
|
|
84
|
-
apiUrl: 'https://api.openai.com',
|
|
85
|
-
apiKey: 'reasoning-key',
|
|
86
|
-
endpoint: '/v1/chat/completions',
|
|
87
|
-
tokenizer,
|
|
88
|
-
}),
|
|
89
|
-
fastReasoning: new Model({
|
|
90
|
-
name: 'gpt-4-fast-reasoning',
|
|
91
|
-
maxContextWindow: 200000,
|
|
92
|
-
maxOutputTokens: 50000,
|
|
93
|
-
requestTimeout: 3000,
|
|
94
|
-
apiUrl: 'https://api.openai.com',
|
|
95
|
-
apiKey: 'reasoning-key',
|
|
96
|
-
endpoint: '/v1/chat/completions',
|
|
97
|
-
tokenizer,
|
|
98
|
-
}),
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Reset and setup fetch mock for each test
|
|
102
|
-
vi.clearAllMocks();
|
|
103
|
-
fetch.mockResolvedValue({
|
|
104
|
-
ok: true,
|
|
105
|
-
status: 200,
|
|
106
|
-
json: () =>
|
|
107
|
-
Promise.resolve({
|
|
108
|
-
choices: [{ message: { content: 'Test response' } }],
|
|
109
|
-
}),
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
afterEach(() => {
|
|
114
|
-
vi.clearAllMocks();
|
|
115
|
-
modelService.clearGlobalOverride(); // Clean up after each test
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('Global Override Management', () => {
|
|
119
|
-
it('should set and get global overrides', () => {
|
|
120
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
121
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
122
|
-
|
|
123
|
-
expect(modelService.getGlobalOverride('modelName')).toBe('customModel');
|
|
124
|
-
expect(modelService.getGlobalOverride('temperature')).toBe(0.8);
|
|
125
|
-
expect(modelService.getGlobalOverride('maxTokens')).toBe(null);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should get all global overrides', () => {
|
|
129
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
130
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
131
|
-
|
|
132
|
-
const allOverrides = modelService.getAllGlobalOverrides();
|
|
133
|
-
expect(allOverrides.modelName).toBe('customModel');
|
|
134
|
-
expect(allOverrides.temperature).toBe(0.8);
|
|
135
|
-
expect(allOverrides.maxTokens).toBe(null);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should clear specific global overrides', () => {
|
|
139
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
140
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
141
|
-
|
|
142
|
-
modelService.clearGlobalOverride('modelName');
|
|
143
|
-
|
|
144
|
-
expect(modelService.getGlobalOverride('modelName')).toBe(null);
|
|
145
|
-
expect(modelService.getGlobalOverride('temperature')).toBe(0.8);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should clear all global overrides', () => {
|
|
149
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
150
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
151
|
-
|
|
152
|
-
modelService.clearGlobalOverride();
|
|
153
|
-
|
|
154
|
-
expect(modelService.getGlobalOverride('modelName')).toBe(null);
|
|
155
|
-
expect(modelService.getGlobalOverride('temperature')).toBe(null);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should throw error for invalid override keys', () => {
|
|
159
|
-
expect(() => {
|
|
160
|
-
modelService.setGlobalOverride('invalidKey', 'value');
|
|
161
|
-
}).toThrow('Invalid override key: invalidKey');
|
|
162
|
-
|
|
163
|
-
expect(() => {
|
|
164
|
-
modelService.clearGlobalOverride('invalidKey');
|
|
165
|
-
}).toThrow('Invalid override key: invalidKey');
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('Model Name Override', () => {
|
|
170
|
-
it('should override model selection globally', async () => {
|
|
171
|
-
// Set global model override
|
|
172
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
173
|
-
|
|
174
|
-
const result = await chatgptRun('Test prompt', {
|
|
175
|
-
modelOptions: {
|
|
176
|
-
modelName: 'fastGood', // This should be overridden
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
expect(result).toBe('Test response');
|
|
181
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
182
|
-
'https://custom.api.com/v1/completions',
|
|
183
|
-
expect.objectContaining({
|
|
184
|
-
headers: {
|
|
185
|
-
Authorization: 'Bearer custom-key',
|
|
186
|
-
'Content-Type': 'application/json',
|
|
187
|
-
},
|
|
188
|
-
body: expect.stringContaining('"model":"custom-model-name"'),
|
|
189
|
-
})
|
|
190
|
-
);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should override even when no modelOptions provided', async () => {
|
|
194
|
-
modelService.setGlobalOverride('modelName', 'expensiveReasoning');
|
|
195
|
-
|
|
196
|
-
const result = await chatgptRun('Test prompt');
|
|
197
|
-
|
|
198
|
-
expect(result).toBe('Test response');
|
|
199
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
200
|
-
'https://api.openai.com/v1/chat/completions',
|
|
201
|
-
expect.objectContaining({
|
|
202
|
-
headers: {
|
|
203
|
-
Authorization: 'Bearer reasoning-key',
|
|
204
|
-
'Content-Type': 'application/json',
|
|
205
|
-
},
|
|
206
|
-
body: expect.stringContaining('"model":"gpt-4-reasoning"'),
|
|
207
|
-
})
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('Negotiation Override', () => {
|
|
213
|
-
it('should override negotiation options globally', async () => {
|
|
214
|
-
modelService.setGlobalOverride('negotiate', { reasoning: true });
|
|
215
|
-
|
|
216
|
-
const result = await chatgptRun('Test prompt', {
|
|
217
|
-
modelOptions: {
|
|
218
|
-
modelName: 'fastGood',
|
|
219
|
-
negotiate: { fast: true, cheap: true }, // This should be overridden
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
expect(result).toBe('Test response');
|
|
224
|
-
// Should negotiate to reasoning model instead of fast+cheap
|
|
225
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
226
|
-
'https://api.openai.com/v1/chat/completions',
|
|
227
|
-
expect.objectContaining({
|
|
228
|
-
body: expect.stringContaining('"model":"gpt-4-fast-cheap-reasoning"'),
|
|
229
|
-
})
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should apply negotiation when none provided in options', async () => {
|
|
234
|
-
modelService.setGlobalOverride('negotiate', { fast: true, cheap: true });
|
|
235
|
-
|
|
236
|
-
const result = await chatgptRun('Test prompt', {
|
|
237
|
-
modelOptions: {
|
|
238
|
-
modelName: 'expensiveReasoning', // Should be overridden by negotiation
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
expect(result).toBe('Test response');
|
|
243
|
-
// Should negotiate instead of using explicit model
|
|
244
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
245
|
-
'https://api.openai.com/v1/chat/completions',
|
|
246
|
-
expect.objectContaining({
|
|
247
|
-
body: expect.stringContaining('"model":"gpt-4-fast-cheap"'),
|
|
248
|
-
})
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('Parameter Overrides', () => {
|
|
254
|
-
it('should override temperature globally', async () => {
|
|
255
|
-
modelService.setGlobalOverride('temperature', 0.9);
|
|
256
|
-
|
|
257
|
-
const result = await chatgptRun('Test prompt', {
|
|
258
|
-
modelOptions: {
|
|
259
|
-
modelName: 'fastGood',
|
|
260
|
-
temperature: 0.1, // This should be overridden
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
expect(result).toBe('Test response');
|
|
265
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
266
|
-
'https://api.openai.com/v1/chat/completions',
|
|
267
|
-
expect.objectContaining({
|
|
268
|
-
body: expect.stringContaining('"temperature":0.9'),
|
|
269
|
-
})
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should override multiple parameters', async () => {
|
|
274
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
275
|
-
modelService.setGlobalOverride('maxTokens', 1500);
|
|
276
|
-
modelService.setGlobalOverride('topP', 0.95);
|
|
277
|
-
|
|
278
|
-
const result = await chatgptRun('Test prompt', {
|
|
279
|
-
modelOptions: {
|
|
280
|
-
modelName: 'fastGood',
|
|
281
|
-
temperature: 0.1,
|
|
282
|
-
maxTokens: 500,
|
|
283
|
-
topP: 0.5,
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
expect(result).toBe('Test response');
|
|
288
|
-
|
|
289
|
-
const requestBody = JSON.parse(fetch.mock.calls[0][1].body);
|
|
290
|
-
expect(requestBody.temperature).toBe(0.8);
|
|
291
|
-
expect(requestBody.max_tokens).toBe(1500);
|
|
292
|
-
expect(requestBody.top_p).toBe(0.95);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('should preserve non-overridden parameters', async () => {
|
|
296
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
297
|
-
|
|
298
|
-
const result = await chatgptRun('Test prompt', {
|
|
299
|
-
modelOptions: {
|
|
300
|
-
modelName: 'fastGood',
|
|
301
|
-
temperature: 0.1,
|
|
302
|
-
maxTokens: 2000,
|
|
303
|
-
topP: 0.7,
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
expect(result).toBe('Test response');
|
|
308
|
-
|
|
309
|
-
const requestBody = JSON.parse(fetch.mock.calls[0][1].body);
|
|
310
|
-
expect(requestBody.temperature).toBe(0.8); // Overridden
|
|
311
|
-
expect(requestBody.max_tokens).toBe(2000); // Preserved
|
|
312
|
-
expect(requestBody.top_p).toBe(0.7); // Preserved
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
describe('Complex Override Scenarios', () => {
|
|
317
|
-
it('should combine model and parameter overrides', async () => {
|
|
318
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
319
|
-
modelService.setGlobalOverride('temperature', 0.9);
|
|
320
|
-
|
|
321
|
-
const result = await chatgptRun('Test prompt', {
|
|
322
|
-
modelOptions: {
|
|
323
|
-
modelName: 'fastGood',
|
|
324
|
-
temperature: 0.1,
|
|
325
|
-
maxTokens: 1000,
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
expect(result).toBe('Test response');
|
|
330
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
331
|
-
'https://custom.api.com/v1/completions',
|
|
332
|
-
expect.objectContaining({
|
|
333
|
-
headers: {
|
|
334
|
-
Authorization: 'Bearer custom-key',
|
|
335
|
-
'Content-Type': 'application/json',
|
|
336
|
-
},
|
|
337
|
-
body: expect.stringContaining('"temperature":0.9'),
|
|
338
|
-
})
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
const requestBody = JSON.parse(fetch.mock.calls[0][1].body);
|
|
342
|
-
expect(requestBody.model).toBe('custom-model-name');
|
|
343
|
-
expect(requestBody.max_tokens).toBe(1000); // Preserved
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('should handle override precedence correctly', async () => {
|
|
347
|
-
// Set both model name and negotiation overrides
|
|
348
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
349
|
-
modelService.setGlobalOverride('negotiate', { reasoning: true });
|
|
350
|
-
|
|
351
|
-
const result = await chatgptRun('Test prompt', {
|
|
352
|
-
modelOptions: {
|
|
353
|
-
modelName: 'fastGood',
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
expect(result).toBe('Test response');
|
|
358
|
-
// Negotiation should take precedence and override the model name
|
|
359
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
360
|
-
'https://api.openai.com/v1/chat/completions',
|
|
361
|
-
expect.objectContaining({
|
|
362
|
-
body: expect.stringContaining('"model":"gpt-4-fast-cheap-reasoning"'),
|
|
363
|
-
})
|
|
364
|
-
);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('should work with empty modelOptions', async () => {
|
|
368
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
369
|
-
modelService.setGlobalOverride('temperature', 0.7);
|
|
370
|
-
|
|
371
|
-
const result = await chatgptRun('Test prompt');
|
|
372
|
-
|
|
373
|
-
expect(result).toBe('Test response');
|
|
374
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
375
|
-
'https://custom.api.com/v1/completions',
|
|
376
|
-
expect.objectContaining({
|
|
377
|
-
headers: {
|
|
378
|
-
Authorization: 'Bearer custom-key',
|
|
379
|
-
'Content-Type': 'application/json',
|
|
380
|
-
},
|
|
381
|
-
body: expect.stringContaining('"temperature":0.7'),
|
|
382
|
-
})
|
|
383
|
-
);
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
describe('Override Isolation', () => {
|
|
388
|
-
it('should not affect subsequent calls after clearing overrides', async () => {
|
|
389
|
-
// Set override and make a call
|
|
390
|
-
modelService.setGlobalOverride('modelName', 'customModel');
|
|
391
|
-
|
|
392
|
-
await chatgptRun('Test prompt 1');
|
|
393
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
394
|
-
'https://custom.api.com/v1/completions',
|
|
395
|
-
expect.anything()
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
// Clear override and make another call
|
|
399
|
-
modelService.clearGlobalOverride('modelName');
|
|
400
|
-
fetch.mockClear();
|
|
401
|
-
|
|
402
|
-
await chatgptRun('Test prompt 2');
|
|
403
|
-
expect(fetch).toHaveBeenCalledWith(
|
|
404
|
-
'https://api.openai.com/v1/chat/completions',
|
|
405
|
-
expect.anything()
|
|
406
|
-
);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it('should apply overrides consistently across multiple calls', async () => {
|
|
410
|
-
modelService.setGlobalOverride('temperature', 0.8);
|
|
411
|
-
|
|
412
|
-
// First call
|
|
413
|
-
await chatgptRun('Test prompt 1', {
|
|
414
|
-
modelOptions: { modelName: 'fastGood', temperature: 0.1 },
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
// Second call
|
|
418
|
-
await chatgptRun('Test prompt 2', {
|
|
419
|
-
modelOptions: { modelName: 'customModel', temperature: 0.2 },
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Both calls should have temperature overridden to 0.8
|
|
423
|
-
expect(fetch).toHaveBeenCalledTimes(2);
|
|
424
|
-
|
|
425
|
-
const firstCall = JSON.parse(fetch.mock.calls[0][1].body);
|
|
426
|
-
const secondCall = JSON.parse(fetch.mock.calls[1][1].body);
|
|
427
|
-
|
|
428
|
-
expect(firstCall.temperature).toBe(0.8);
|
|
429
|
-
expect(secondCall.temperature).toBe(0.8);
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
});
|