@arclabs561/ai-visual-test 0.5.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/.secretsignore.example +20 -0
- package/CHANGELOG.md +360 -0
- package/CONTRIBUTING.md +63 -0
- package/DEPLOYMENT.md +80 -0
- package/LICENSE +22 -0
- package/README.md +142 -0
- package/SECURITY.md +108 -0
- package/api/health.js +34 -0
- package/api/validate.js +252 -0
- package/index.d.ts +1221 -0
- package/package.json +112 -0
- package/public/index.html +149 -0
- package/src/batch-optimizer.mjs +451 -0
- package/src/bias-detector.mjs +370 -0
- package/src/bias-mitigation.mjs +233 -0
- package/src/cache.mjs +433 -0
- package/src/config.mjs +268 -0
- package/src/constants.mjs +80 -0
- package/src/context-compressor.mjs +350 -0
- package/src/convenience.mjs +617 -0
- package/src/cost-tracker.mjs +257 -0
- package/src/cross-modal-consistency.mjs +170 -0
- package/src/data-extractor.mjs +232 -0
- package/src/dynamic-few-shot.mjs +140 -0
- package/src/dynamic-prompts.mjs +361 -0
- package/src/ensemble/index.mjs +53 -0
- package/src/ensemble-judge.mjs +366 -0
- package/src/error-handler.mjs +67 -0
- package/src/errors.mjs +167 -0
- package/src/experience-propagation.mjs +128 -0
- package/src/experience-tracer.mjs +487 -0
- package/src/explanation-manager.mjs +299 -0
- package/src/feedback-aggregator.mjs +248 -0
- package/src/game-goal-prompts.mjs +478 -0
- package/src/game-player.mjs +548 -0
- package/src/hallucination-detector.mjs +155 -0
- package/src/helpers/playwright.mjs +80 -0
- package/src/human-validation-manager.mjs +516 -0
- package/src/index.mjs +364 -0
- package/src/judge.mjs +929 -0
- package/src/latency-aware-batch-optimizer.mjs +192 -0
- package/src/load-env.mjs +159 -0
- package/src/logger.mjs +55 -0
- package/src/metrics.mjs +187 -0
- package/src/model-tier-selector.mjs +221 -0
- package/src/multi-modal/index.mjs +36 -0
- package/src/multi-modal-fusion.mjs +190 -0
- package/src/multi-modal.mjs +524 -0
- package/src/natural-language-specs.mjs +1071 -0
- package/src/pair-comparison.mjs +277 -0
- package/src/persona/index.mjs +42 -0
- package/src/persona-enhanced.mjs +200 -0
- package/src/persona-experience.mjs +572 -0
- package/src/position-counterbalance.mjs +140 -0
- package/src/prompt-composer.mjs +375 -0
- package/src/render-change-detector.mjs +583 -0
- package/src/research-enhanced-validation.mjs +436 -0
- package/src/retry.mjs +152 -0
- package/src/rubrics.mjs +231 -0
- package/src/score-tracker.mjs +277 -0
- package/src/smart-validator.mjs +447 -0
- package/src/spec-config.mjs +106 -0
- package/src/spec-templates.mjs +347 -0
- package/src/specs/index.mjs +38 -0
- package/src/temporal/index.mjs +102 -0
- package/src/temporal-adaptive.mjs +163 -0
- package/src/temporal-batch-optimizer.mjs +222 -0
- package/src/temporal-constants.mjs +69 -0
- package/src/temporal-context.mjs +49 -0
- package/src/temporal-decision-manager.mjs +271 -0
- package/src/temporal-decision.mjs +669 -0
- package/src/temporal-errors.mjs +58 -0
- package/src/temporal-note-pruner.mjs +173 -0
- package/src/temporal-preprocessor.mjs +543 -0
- package/src/temporal-prompt-formatter.mjs +219 -0
- package/src/temporal-validation.mjs +159 -0
- package/src/temporal.mjs +415 -0
- package/src/type-guards.mjs +311 -0
- package/src/uncertainty-reducer.mjs +470 -0
- package/src/utils/index.mjs +175 -0
- package/src/validation-framework.mjs +321 -0
- package/src/validation-result-normalizer.mjs +64 -0
- package/src/validation.mjs +243 -0
- package/src/validators/accessibility-programmatic.mjs +345 -0
- package/src/validators/accessibility-validator.mjs +223 -0
- package/src/validators/batch-validator.mjs +143 -0
- package/src/validators/hybrid-validator.mjs +268 -0
- package/src/validators/index.mjs +34 -0
- package/src/validators/prompt-builder.mjs +218 -0
- package/src/validators/rubric.mjs +85 -0
- package/src/validators/state-programmatic.mjs +260 -0
- package/src/validators/state-validator.mjs +291 -0
- package/vercel.json +27 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Prompt Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats temporal notes (single-scale and multi-scale) for optimal VLM understanding.
|
|
5
|
+
* Provides different formatting strategies for different temporal aggregation types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { formatNotesForPrompt } from './temporal.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format temporal notes for prompt inclusion
|
|
12
|
+
*
|
|
13
|
+
* Handles both single-scale (AggregatedTemporalNotes) and multi-scale (MultiScaleAggregation)
|
|
14
|
+
* temporal notes, formatting them optimally for VLM understanding.
|
|
15
|
+
*
|
|
16
|
+
* @param {import('./index.mjs').AggregatedTemporalNotes | import('./index.mjs').MultiScaleAggregation | Array} temporalNotes - Temporal notes (aggregated, multi-scale, or raw array)
|
|
17
|
+
* @param {Object} options - Formatting options
|
|
18
|
+
* @param {boolean} [options.includeMultiScale=true] - Include multi-scale insights if available
|
|
19
|
+
* @param {boolean} [options.naturalLanguage=true] - Use natural language formatting
|
|
20
|
+
* @returns {string} Formatted temporal context for prompt
|
|
21
|
+
*/
|
|
22
|
+
export function formatTemporalForPrompt(temporalNotes, options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
includeMultiScale = true,
|
|
25
|
+
naturalLanguage = true
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
if (!temporalNotes) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle raw array - should be aggregated first (caller's responsibility)
|
|
33
|
+
if (Array.isArray(temporalNotes)) {
|
|
34
|
+
return ''; // Don't format raw arrays here
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle multi-scale aggregation
|
|
38
|
+
if (temporalNotes.scales && !temporalNotes.windows) {
|
|
39
|
+
return formatMultiScaleForPrompt(temporalNotes, { naturalLanguage });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle single-scale aggregation
|
|
43
|
+
if (temporalNotes.windows) {
|
|
44
|
+
return formatSingleScaleForPrompt(temporalNotes, { naturalLanguage });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format single-scale aggregated notes for prompt
|
|
52
|
+
*/
|
|
53
|
+
function formatSingleScaleForPrompt(aggregated, options = {}) {
|
|
54
|
+
const { naturalLanguage = true } = options;
|
|
55
|
+
|
|
56
|
+
if (naturalLanguage) {
|
|
57
|
+
// Natural language format
|
|
58
|
+
const parts = [];
|
|
59
|
+
|
|
60
|
+
parts.push('TEMPORAL CONTEXT:');
|
|
61
|
+
parts.push(aggregated.summary || 'Previous observations over time.');
|
|
62
|
+
|
|
63
|
+
if (aggregated.windows && aggregated.windows.length > 0) {
|
|
64
|
+
parts.push('\nTime Windows:');
|
|
65
|
+
aggregated.windows.slice(-5).forEach((window, i) => {
|
|
66
|
+
const timeRange = window.timeRange || `window ${window.window || i + 1}`;
|
|
67
|
+
const score = window.avgScore !== null && window.avgScore !== undefined
|
|
68
|
+
? `${window.avgScore.toFixed(1)}/10`
|
|
69
|
+
: 'N/A';
|
|
70
|
+
parts.push(` ${timeRange}: Score ${score}, ${window.noteCount || 0} observations`);
|
|
71
|
+
if (window.observations) {
|
|
72
|
+
parts.push(` Notes: ${window.observations.substring(0, 150)}${window.observations.length > 150 ? '...' : ''}`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (aggregated.coherence !== null && aggregated.coherence !== undefined) {
|
|
78
|
+
const coherenceDesc = aggregated.coherence > 0.7 ? 'high' : aggregated.coherence > 0.4 ? 'moderate' : 'low';
|
|
79
|
+
parts.push(`\nTemporal Coherence: ${(aggregated.coherence * 100).toFixed(0)}% (${coherenceDesc})`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (aggregated.conflicts && aggregated.conflicts.length > 0) {
|
|
83
|
+
parts.push(`\nCoherence Issues: ${aggregated.conflicts.length} potential conflicts detected`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
parts.push('\nWhen evaluating, consider how the current state relates to these temporal patterns and trends.');
|
|
87
|
+
|
|
88
|
+
return parts.join('\n');
|
|
89
|
+
} else {
|
|
90
|
+
// Structured format (original)
|
|
91
|
+
return formatNotesForPrompt(aggregated);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format multi-scale aggregation for prompt
|
|
97
|
+
*/
|
|
98
|
+
function formatMultiScaleForPrompt(multiScale, options = {}) {
|
|
99
|
+
const { naturalLanguage = true } = options;
|
|
100
|
+
|
|
101
|
+
if (!multiScale.scales || Object.keys(multiScale.scales).length === 0) {
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parts = [];
|
|
106
|
+
|
|
107
|
+
if (naturalLanguage) {
|
|
108
|
+
parts.push('TEMPORAL CONTEXT (Multi-Scale Analysis):');
|
|
109
|
+
parts.push('The experience has been analyzed across multiple time scales:');
|
|
110
|
+
parts.push('');
|
|
111
|
+
|
|
112
|
+
// Order scales by time (shortest to longest)
|
|
113
|
+
const scaleOrder = ['immediate', 'short', 'medium', 'long'];
|
|
114
|
+
const orderedScales = scaleOrder
|
|
115
|
+
.filter(scale => multiScale.scales[scale])
|
|
116
|
+
.map(scale => [scale, multiScale.scales[scale]]);
|
|
117
|
+
|
|
118
|
+
orderedScales.forEach(([scaleName, scaleData]) => {
|
|
119
|
+
const scaleDescriptions = {
|
|
120
|
+
immediate: 'Instant perception (0.1s) - visual feedback, animations',
|
|
121
|
+
short: 'Quick interaction (1s) - button responses, immediate feedback',
|
|
122
|
+
medium: 'Short task (5s) - form filling, quick actions',
|
|
123
|
+
long: 'Longer session (30s) - overall experience, engagement'
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
parts.push(`${scaleName.toUpperCase()} Scale (${scaleDescriptions[scaleName] || scaleName}):`);
|
|
127
|
+
|
|
128
|
+
if (scaleData.windows && scaleData.windows.length > 0) {
|
|
129
|
+
const recentWindows = scaleData.windows.slice(-3); // Last 3 windows
|
|
130
|
+
recentWindows.forEach((window, i) => {
|
|
131
|
+
const timeRange = window.timeRange || `window ${window.window || i + 1}`;
|
|
132
|
+
const score = window.avgScore !== null && window.avgScore !== undefined
|
|
133
|
+
? `${window.avgScore.toFixed(1)}/10`
|
|
134
|
+
: 'N/A';
|
|
135
|
+
parts.push(` ${timeRange}: Score ${score}`);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (scaleData.coherence !== null && scaleData.coherence !== undefined) {
|
|
139
|
+
const coherenceDesc = scaleData.coherence > 0.7 ? 'high' : scaleData.coherence > 0.4 ? 'moderate' : 'low';
|
|
140
|
+
parts.push(` Coherence: ${(scaleData.coherence * 100).toFixed(0)}% (${coherenceDesc})`);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
parts.push(` No windows in this scale`);
|
|
144
|
+
}
|
|
145
|
+
parts.push('');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Overall summary
|
|
149
|
+
if (multiScale.summary) {
|
|
150
|
+
parts.push(`Overall: ${multiScale.summary}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
parts.push('\nWhen evaluating, consider patterns at different time scales:');
|
|
154
|
+
parts.push('- Immediate: Visual feedback and animation quality');
|
|
155
|
+
parts.push('- Short: Interaction responsiveness and quick feedback');
|
|
156
|
+
parts.push('- Medium: Task completion flow and user journey');
|
|
157
|
+
parts.push('- Long: Overall experience quality and engagement');
|
|
158
|
+
|
|
159
|
+
return parts.join('\n');
|
|
160
|
+
} else {
|
|
161
|
+
// Structured format
|
|
162
|
+
parts.push('TEMPORAL CONTEXT (Multi-Scale):');
|
|
163
|
+
parts.push(multiScale.summary || 'Multi-scale temporal analysis');
|
|
164
|
+
parts.push('');
|
|
165
|
+
|
|
166
|
+
Object.entries(multiScale.scales).forEach(([scaleName, scaleData]) => {
|
|
167
|
+
parts.push(`${scaleName.toUpperCase()} Scale:`);
|
|
168
|
+
parts.push(` Windows: ${scaleData.windows?.length || 0}`);
|
|
169
|
+
parts.push(` Coherence: ${scaleData.coherence ? (scaleData.coherence * 100).toFixed(0) : 'N/A'}%`);
|
|
170
|
+
if (scaleData.windows && scaleData.windows.length > 0) {
|
|
171
|
+
scaleData.windows.slice(-3).forEach(window => {
|
|
172
|
+
const timeRange = window.timeRange || `window ${window.window}`;
|
|
173
|
+
const score = window.avgScore !== null && window.avgScore !== undefined
|
|
174
|
+
? `${window.avgScore.toFixed(1)}/10`
|
|
175
|
+
: 'N/A';
|
|
176
|
+
parts.push(` ${timeRange}: ${score}`);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
parts.push('');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return parts.join('\n');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Format temporal notes with optimal strategy
|
|
188
|
+
*
|
|
189
|
+
* Chooses the best formatting based on the type of temporal notes provided.
|
|
190
|
+
*
|
|
191
|
+
* @param {any} temporalNotes - Temporal notes (any format)
|
|
192
|
+
* @param {Object} options - Formatting options
|
|
193
|
+
* @returns {string} Formatted temporal context
|
|
194
|
+
*/
|
|
195
|
+
export function formatTemporalContext(temporalNotes, options = {}) {
|
|
196
|
+
if (!temporalNotes) {
|
|
197
|
+
return '';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Raw array - return empty (should be aggregated first)
|
|
201
|
+
if (Array.isArray(temporalNotes)) {
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Multi-scale - use multi-scale formatter
|
|
206
|
+
if (temporalNotes.scales && !temporalNotes.windows) {
|
|
207
|
+
return formatMultiScaleForPrompt(temporalNotes, options);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Single-scale - use single-scale formatter
|
|
211
|
+
if (temporalNotes.windows) {
|
|
212
|
+
return formatSingleScaleForPrompt(temporalNotes, options);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { formatSingleScaleForPrompt, formatMultiScaleForPrompt };
|
|
219
|
+
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Validation
|
|
3
|
+
* Input validation for temporal decision-making components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { MultiScaleError, PerceptionTimeError, SequentialContextError } from './temporal-errors.mjs';
|
|
7
|
+
import { warn } from './logger.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate temporal notes
|
|
11
|
+
*/
|
|
12
|
+
export function validateNotes(notes) {
|
|
13
|
+
if (!Array.isArray(notes)) {
|
|
14
|
+
throw new MultiScaleError('Notes must be an array', { received: typeof notes });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const validNotes = [];
|
|
18
|
+
const invalidNotes = [];
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < notes.length; i++) {
|
|
21
|
+
const note = notes[i];
|
|
22
|
+
|
|
23
|
+
if (!note || typeof note !== 'object') {
|
|
24
|
+
invalidNotes.push({ index: i, reason: 'Not an object' });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!note.timestamp && note.elapsed === undefined) {
|
|
29
|
+
invalidNotes.push({ index: i, reason: 'Missing timestamp or elapsed' });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (note.timestamp && typeof note.timestamp !== 'number') {
|
|
34
|
+
invalidNotes.push({ index: i, reason: 'Invalid timestamp type' });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (note.elapsed !== undefined && typeof note.elapsed !== 'number') {
|
|
39
|
+
invalidNotes.push({ index: i, reason: 'Invalid elapsed type' });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validNotes.push(note);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (invalidNotes.length > 0) {
|
|
47
|
+
warn(`[Temporal] ${invalidNotes.length} invalid notes filtered out:`, invalidNotes);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return validNotes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate and sort notes
|
|
55
|
+
* Convenience function that validates and sorts in one step
|
|
56
|
+
*/
|
|
57
|
+
export function validateAndSortNotes(notes) {
|
|
58
|
+
const validNotes = validateNotes(notes);
|
|
59
|
+
return validNotes.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate time scales
|
|
64
|
+
*/
|
|
65
|
+
export function validateTimeScales(timeScales) {
|
|
66
|
+
if (typeof timeScales !== 'object' || timeScales === null) {
|
|
67
|
+
throw new MultiScaleError('Time scales must be an object', { received: typeof timeScales });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const [name, value] of Object.entries(timeScales)) {
|
|
71
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
72
|
+
throw new MultiScaleError(`Invalid time scale ${name}: ${value}`, {
|
|
73
|
+
scaleName: name,
|
|
74
|
+
value,
|
|
75
|
+
type: typeof value
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate action for human perception time
|
|
85
|
+
*/
|
|
86
|
+
export function validateAction(action) {
|
|
87
|
+
const validActions = ['page-load', 'reading', 'interaction', 'evaluation', 'scanning', 'visual-appeal'];
|
|
88
|
+
|
|
89
|
+
if (typeof action !== 'string') {
|
|
90
|
+
throw new PerceptionTimeError('Action must be a string', { received: typeof action });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!validActions.includes(action)) {
|
|
94
|
+
throw new PerceptionTimeError(`Invalid action: ${action}`, {
|
|
95
|
+
action,
|
|
96
|
+
validActions
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate context for human perception time
|
|
105
|
+
*/
|
|
106
|
+
export function validatePerceptionContext(context) {
|
|
107
|
+
if (context === null || typeof context !== 'object') {
|
|
108
|
+
throw new PerceptionTimeError('Context must be an object', { received: typeof context });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (context.attentionLevel && !['focused', 'normal', 'distracted'].includes(context.attentionLevel)) {
|
|
112
|
+
throw new PerceptionTimeError(`Invalid attentionLevel: ${context.attentionLevel}`, {
|
|
113
|
+
attentionLevel: context.attentionLevel,
|
|
114
|
+
validLevels: ['focused', 'normal', 'distracted']
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (context.actionComplexity && !['simple', 'normal', 'complex'].includes(context.actionComplexity)) {
|
|
119
|
+
throw new PerceptionTimeError(`Invalid actionComplexity: ${context.actionComplexity}`, {
|
|
120
|
+
actionComplexity: context.actionComplexity,
|
|
121
|
+
validComplexities: ['simple', 'normal', 'complex']
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (context.contentLength !== undefined && (typeof context.contentLength !== 'number' || context.contentLength < 0)) {
|
|
126
|
+
throw new PerceptionTimeError('contentLength must be a non-negative number', {
|
|
127
|
+
contentLength: context.contentLength
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate sequential decision context options
|
|
136
|
+
*/
|
|
137
|
+
export function validateSequentialContextOptions(options) {
|
|
138
|
+
// Allow empty object or undefined (defaults will be used)
|
|
139
|
+
if (options === null || options === undefined) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Allow empty object {} (defaults will be used)
|
|
144
|
+
if (typeof options !== 'object') {
|
|
145
|
+
throw new SequentialContextError('Options must be an object', { received: typeof options });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Only validate if maxHistory is explicitly provided
|
|
149
|
+
if (options.maxHistory !== undefined && options.maxHistory !== null) {
|
|
150
|
+
if (typeof options.maxHistory !== 'number' || options.maxHistory < 1) {
|
|
151
|
+
throw new SequentialContextError('maxHistory must be a positive number', {
|
|
152
|
+
maxHistory: options.maxHistory
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|