@game_ryo/lsji 0.1.0 → 0.3.0

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.
Files changed (75) hide show
  1. package/package.json +15 -7
  2. package/src/cli.js +395 -62
  3. package/src/execution/budget/circuit-breaker.js +245 -0
  4. package/src/execution/budget/cost-tracker.js +387 -0
  5. package/src/execution/budget/index.js +63 -0
  6. package/src/execution/budget/token-counter.js +159 -0
  7. package/src/execution/engine.js +428 -0
  8. package/src/execution/hitl/approval-gate.js +210 -0
  9. package/src/execution/hitl/index.js +12 -0
  10. package/src/execution/hitl/notifier.js +151 -0
  11. package/src/execution/hitl/store.js +311 -0
  12. package/src/execution/idempotency.js +312 -0
  13. package/src/execution/index.js +14 -0
  14. package/src/index.js +80 -4
  15. package/src/llm/index.js +21 -0
  16. package/src/llm/llm-agent.js +357 -0
  17. package/src/llm/memory/conversation.js +271 -0
  18. package/src/llm/memory/episodic.js +312 -0
  19. package/src/llm/memory/index.js +12 -0
  20. package/src/llm/memory/semantic.js +324 -0
  21. package/src/llm/plugins/index.js +202 -0
  22. package/src/llm/prompt-manager.js +332 -0
  23. package/src/llm/providers/anthropic.js +250 -0
  24. package/src/llm/providers/base.js +116 -0
  25. package/src/llm/providers/local.js +163 -0
  26. package/src/llm/providers/openai.js +212 -0
  27. package/src/llm/tools/registry.js +342 -0
  28. package/src/server/index.js +416 -0
  29. package/src/server/ui/index.html +16 -0
  30. package/src/server/ui/package.json +19 -0
  31. package/src/server/ui/src/main.jsx +10 -0
  32. package/src/server/ui/src/styles.css +260 -0
  33. package/src/server/ui/vite.config.js +27 -0
  34. package/docs/README.md +0 -43
  35. package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
  36. package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
  37. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  38. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  39. package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
  40. package/docs/blog/authors.yml +0 -25
  41. package/docs/blog/tags.yml +0 -19
  42. package/docs/docs/api/agent.md +0 -151
  43. package/docs/docs/api/env.md +0 -133
  44. package/docs/docs/api/environments.md +0 -102
  45. package/docs/docs/api/qlearning.md +0 -138
  46. package/docs/docs/api/storage.md +0 -168
  47. package/docs/docs/architecture.md +0 -155
  48. package/docs/docs/cli.md +0 -210
  49. package/docs/docs/contributing.md +0 -162
  50. package/docs/docs/core-concepts.md +0 -152
  51. package/docs/docs/examples/advanced-training.md +0 -244
  52. package/docs/docs/examples/custom-environment.md +0 -198
  53. package/docs/docs/examples/custom-storage.md +0 -251
  54. package/docs/docs/getting-started.md +0 -91
  55. package/docs/docusaurus.config.ts +0 -149
  56. package/docs/package-lock.json +0 -19522
  57. package/docs/package.json +0 -49
  58. package/docs/sidebars.ts +0 -33
  59. package/docs/src/components/HomepageFeatures/index.tsx +0 -71
  60. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  61. package/docs/src/css/custom.css +0 -79
  62. package/docs/src/pages/index.module.css +0 -23
  63. package/docs/src/pages/index.tsx +0 -44
  64. package/docs/src/pages/markdown-page.mdx +0 -7
  65. package/docs/static/.nojekyll +0 -0
  66. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  67. package/docs/static/img/docusaurus.png +0 -0
  68. package/docs/static/img/favicon.ico +0 -0
  69. package/docs/static/img/logo.png +0 -0
  70. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  71. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  72. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  73. package/docs/tsconfig.json +0 -12
  74. package/legacy/worker.js +0 -166
  75. package/legacy/wrangler.toml +0 -11
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Token Counter
3
+ *
4
+ * Tracks token usage across different LLM providers with accurate counting.
5
+ */
6
+
7
+ import { createProvider } from '../../llm/providers/base.js';
8
+
9
+ /**
10
+ * Token usage record
11
+ * @typedef {Object} TokenUsage
12
+ * @property {number} inputTokens
13
+ * @property {number} outputTokens
14
+ * @property {number} totalTokens
15
+ * @property {string} model
16
+ * @property {string} provider
17
+ * @property {Date} timestamp
18
+ */
19
+
20
+ /**
21
+ * Token Counter - Tracks and estimates token usage
22
+ */
23
+ export class TokenCounter {
24
+ constructor() {
25
+ this.usageHistory = [];
26
+ this.providerCache = new Map();
27
+ }
28
+
29
+ /**
30
+ * Get or create provider instance for token estimation
31
+ */
32
+ async getProvider(config) {
33
+ const key = `${config.provider}:${config.model}`;
34
+ if (!this.providerCache.has(key)) {
35
+ const provider = await createProvider(config);
36
+ this.providerCache.set(key, provider);
37
+ }
38
+ return this.providerCache.get(key);
39
+ }
40
+
41
+ /**
42
+ * Estimate tokens for messages using provider's tokenizer
43
+ */
44
+ async estimateTokens(messages, providerConfig) {
45
+ const provider = await this.getProvider(providerConfig);
46
+ return provider.estimateTokens(messages);
47
+ }
48
+
49
+ /**
50
+ * Record actual token usage from a completion
51
+ */
52
+ recordUsage(usage) {
53
+ const record = {
54
+ ...usage,
55
+ timestamp: new Date(),
56
+ };
57
+ this.usageHistory.push(record);
58
+ return record;
59
+ }
60
+
61
+ /**
62
+ * Get total tokens used in a time range
63
+ */
64
+ getTotalTokens(since = null) {
65
+ let filtered = this.usageHistory;
66
+ if (since) {
67
+ filtered = this.usageHistory.filter(u => u.timestamp >= since);
68
+ }
69
+ return filtered.reduce((sum, u) => sum + (u.totalTokens || 0), 0);
70
+ }
71
+
72
+ /**
73
+ * Get tokens by model
74
+ */
75
+ getTokensByModel(since = null) {
76
+ let filtered = this.usageHistory;
77
+ if (since) {
78
+ filtered = this.usageHistory.filter(u => u.timestamp >= since);
79
+ }
80
+
81
+ const byModel = {};
82
+ for (const u of filtered) {
83
+ const model = u.model || 'unknown';
84
+ if (!byModel[model]) {
85
+ byModel[model] = { inputTokens: 0, outputTokens: 0, totalTokens: 0, count: 0 };
86
+ }
87
+ byModel[model].inputTokens += u.inputTokens || 0;
88
+ byModel[model].outputTokens += u.outputTokens || 0;
89
+ byModel[model].totalTokens += u.totalTokens || 0;
90
+ byModel[model].count += 1;
91
+ }
92
+ return byModel;
93
+ }
94
+
95
+ /**
96
+ * Get tokens by provider
97
+ */
98
+ getTokensByProvider(since = null) {
99
+ let filtered = this.usageHistory;
100
+ if (since) {
101
+ filtered = this.usageHistory.filter(u => u.timestamp >= since);
102
+ }
103
+
104
+ const byProvider = {};
105
+ for (const u of filtered) {
106
+ const provider = u.provider || 'unknown';
107
+ if (!byProvider[provider]) {
108
+ byProvider[provider] = { inputTokens: 0, outputTokens: 0, totalTokens: 0, count: 0 };
109
+ }
110
+ byProvider[provider].inputTokens += u.inputTokens || 0;
111
+ byProvider[provider].outputTokens += u.outputTokens || 0;
112
+ byProvider[provider].totalTokens += u.totalTokens || 0;
113
+ byProvider[provider].count += 1;
114
+ }
115
+ return byProvider;
116
+ }
117
+
118
+ /**
119
+ * Get usage history
120
+ */
121
+ getHistory(limit = 100) {
122
+ return this.usageHistory.slice(-limit);
123
+ }
124
+
125
+ /**
126
+ * Clear history
127
+ */
128
+ clear() {
129
+ this.usageHistory = [];
130
+ }
131
+
132
+ /**
133
+ * Get summary statistics
134
+ */
135
+ getSummary(since = null) {
136
+ let filtered = this.usageHistory;
137
+ if (since) {
138
+ filtered = this.usageHistory.filter(u => u.timestamp >= since);
139
+ }
140
+
141
+ const total = filtered.reduce((sum, u) => sum + (u.totalTokens || 0), 0);
142
+ const input = filtered.reduce((sum, u) => sum + (u.inputTokens || 0), 0);
143
+ const output = filtered.reduce((sum, u) => sum + (u.outputTokens || 0), 0);
144
+
145
+ return {
146
+ totalRequests: filtered.length,
147
+ totalTokens: total,
148
+ inputTokens: input,
149
+ outputTokens: output,
150
+ byModel: this.getTokensByModel(since),
151
+ byProvider: this.getTokensByProvider(since),
152
+ };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Global token counter instance
158
+ */
159
+ export const globalTokenCounter = new TokenCounter();
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Execution Engine
3
+ *
4
+ * Durable execution engine with automatic checkpointing and recovery.
5
+ * Provides workflow execution with resume capability.
6
+ */
7
+
8
+ import { createStorage } from '../index.js';
9
+ import { IdempotencyStore } from './idempotency.js';
10
+ import { v4 as uuidv4 } from 'uuid';
11
+
12
+ /**
13
+ * Checkpoint record
14
+ * @typedef {Object} CheckpointRecord
15
+ * @property {string} id - Checkpoint ID
16
+ * @property {string} workflowId - Workflow identifier
17
+ * @property {number} step - Step number
18
+ * @property {Object} state - Workflow state at checkpoint
19
+ * @property {Object} context - Execution context
20
+ * @property {Date} createdAt
21
+ * @property {string} [parentId] - Parent checkpoint ID
22
+ */
23
+
24
+ /**
25
+ * Workflow definition
26
+ * @typedef {Object} Workflow
27
+ * @property {string} id - Workflow ID
28
+ * @property {string} name - Workflow name
29
+ * @property {Function} execute - Async function(context) -> result
30
+ * @property {Array<string>} [checkpointSteps] - Steps to checkpoint after
31
+ */
32
+
33
+ /**
34
+ * Execution Engine - Durable workflow execution
35
+ */
36
+ export class ExecutionEngine {
37
+ constructor({ storage, checkpointStore, idempotencyStore, defaultCheckpointInterval = 5 } = {}) {
38
+ this.storage = storage;
39
+ this.checkpointStore = checkpointStore;
40
+ this.idempotencyStore = idempotencyStore;
41
+ this.defaultCheckpointInterval = defaultCheckpointInterval;
42
+ this.initialized = false;
43
+ }
44
+
45
+ /**
46
+ * Check if storage is SQL-based (has db with all method)
47
+ */
48
+ isSqlStorage() {
49
+ return this.storage.db && typeof this.storage.db.all === 'function';
50
+ }
51
+
52
+ /**
53
+ * Initialize checkpoint table
54
+ */
55
+ async initialize() {
56
+ if (this.initialized) return;
57
+
58
+ if (this.isSqlStorage()) {
59
+ await this.storage.db.exec(`
60
+ CREATE TABLE IF NOT EXISTS checkpoints (
61
+ id TEXT PRIMARY KEY,
62
+ workflow_id TEXT NOT NULL,
63
+ step INTEGER NOT NULL,
64
+ state TEXT NOT NULL,
65
+ context TEXT NOT NULL,
66
+ created_at TEXT NOT NULL,
67
+ parent_id TEXT
68
+ )
69
+ `);
70
+
71
+ await this.storage.db.exec(`
72
+ CREATE INDEX IF NOT EXISTS idx_checkpoints_workflow ON checkpoints(workflow_id)
73
+ `);
74
+
75
+ await this.storage.db.exec(`
76
+ CREATE INDEX IF NOT EXISTS idx_checkpoints_created ON checkpoints(created_at)
77
+ `);
78
+ }
79
+
80
+ this.initialized = true;
81
+ }
82
+
83
+ /**
84
+ * Execute a workflow with checkpointing
85
+ */
86
+ async execute(workflow, options = {}) {
87
+ await this.initialize();
88
+
89
+ const workflowId = options.workflowId || workflow.id || uuidv4();
90
+ const checkpointEvery = options.checkpointEvery || this.defaultCheckpointInterval;
91
+ const idempotencyKey = options.idempotencyKey;
92
+ const resumeFrom = options.resumeFrom; // checkpoint ID to resume from
93
+
94
+ let context = options.initialContext || {};
95
+ let step = 0;
96
+ let parentCheckpointId = null;
97
+
98
+ // Resume from checkpoint if specified
99
+ if (resumeFrom) {
100
+ const checkpoint = await this.getCheckpoint(resumeFrom);
101
+ if (checkpoint) {
102
+ context = { ...context, ...checkpoint.state };
103
+ step = checkpoint.step;
104
+ parentCheckpointId = checkpoint.id;
105
+ console.log(`Resuming workflow ${workflowId} from checkpoint ${resumeFrom} (step ${step})`);
106
+ } else {
107
+ throw new Error(`Checkpoint not found: ${resumeFrom}`);
108
+ }
109
+ }
110
+
111
+ // Wrap execution with idempotency if key provided
112
+ const executeWithIdempotency = async (fn) => {
113
+ if (idempotencyKey && this.idempotencyStore) {
114
+ return this.idempotencyStore.execute(
115
+ idempotencyKey,
116
+ workflowId,
117
+ { workflowId, step, context },
118
+ fn
119
+ );
120
+ }
121
+ return fn();
122
+ };
123
+
124
+ try {
125
+ // Execute workflow steps
126
+ const result = await executeWithIdempotency(async () => {
127
+ return await workflow.execute(context);
128
+ });
129
+
130
+ // Save final checkpoint
131
+ await this.saveCheckpoint({
132
+ workflowId,
133
+ step: step + 1,
134
+ state: context,
135
+ context: { ...context, result },
136
+ parentId: parentCheckpointId,
137
+ });
138
+
139
+ return { success: true, result, workflowId, checkpoints: await this.getCheckpoints(workflowId) };
140
+ } catch (error) {
141
+ // Save error checkpoint
142
+ await this.saveCheckpoint({
143
+ workflowId,
144
+ step,
145
+ state: context,
146
+ context: { ...context, error: error.message },
147
+ parentId: parentCheckpointId,
148
+ });
149
+
150
+ return { success: false, error: error.message, workflowId, checkpoints: await this.getCheckpoints(workflowId) };
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Execute a multi-step workflow with per-step checkpointing
156
+ */
157
+ async executeSteps(steps, options = {}) {
158
+ await this.initialize();
159
+
160
+ const workflowId = options.workflowId || uuidv4();
161
+ const checkpointEvery = options.checkpointEvery || this.defaultCheckpointInterval;
162
+ const idempotencyKey = options.idempotencyKey;
163
+ const resumeFrom = options.resumeFrom;
164
+
165
+ let context = options.initialContext || {};
166
+ let step = 0;
167
+ let parentCheckpointId = null;
168
+
169
+ // Resume from checkpoint
170
+ if (resumeFrom) {
171
+ const checkpoint = await this.getCheckpoint(resumeFrom);
172
+ if (checkpoint) {
173
+ context = { ...context, ...checkpoint.state };
174
+ step = checkpoint.step;
175
+ parentCheckpointId = checkpoint.id;
176
+ }
177
+ }
178
+
179
+ const results = [];
180
+
181
+ for (let i = step; i < steps.length; i++) {
182
+ const stepFn = steps[i];
183
+ step = i + 1;
184
+
185
+ try {
186
+ const stepResult = await stepFn(context);
187
+ results.push({ step: i, success: true, result: stepResult });
188
+ context = { ...context, [steps[i].name || `step_${i}`]: stepResult };
189
+ } catch (error) {
190
+ results.push({ step: i, success: false, error: error.message });
191
+
192
+ // Save error checkpoint
193
+ await this.saveCheckpoint({
194
+ workflowId,
195
+ step: i + 1,
196
+ state: context,
197
+ context: { ...context, error: error.message, step: i },
198
+ parentId: parentCheckpointId,
199
+ });
200
+
201
+ if (!options.continueOnError) {
202
+ return { success: false, results, workflowId, error: error.message };
203
+ }
204
+ }
205
+
206
+ // Checkpoint at intervals
207
+ if (step % checkpointEvery === 0) {
208
+ await this.saveCheckpoint({
209
+ workflowId,
210
+ step,
211
+ state: context,
212
+ context: { ...context, lastStep: i },
213
+ parentId: parentCheckpointId,
214
+ });
215
+ parentCheckpointId = await this.getLatestCheckpointId(workflowId);
216
+ }
217
+ }
218
+
219
+ // Final checkpoint
220
+ await this.saveCheckpoint({
221
+ workflowId,
222
+ step: steps.length,
223
+ state: context,
224
+ context: { ...context, results },
225
+ parentId: parentCheckpointId,
226
+ });
227
+
228
+ return { success: true, results, workflowId, context };
229
+ }
230
+
231
+ /**
232
+ * Save a checkpoint
233
+ */
234
+ async saveCheckpoint(data) {
235
+ await this.initialize();
236
+
237
+ const checkpoint = {
238
+ id: uuidv4(),
239
+ workflowId: data.workflowId,
240
+ step: data.step,
241
+ state: JSON.stringify(data.state),
242
+ context: JSON.stringify(data.context),
243
+ createdAt: new Date().toISOString(),
244
+ parentId: data.parentId || null,
245
+ };
246
+
247
+ if (this.isSqlStorage()) {
248
+ await this.storage.db.run(
249
+ `INSERT INTO checkpoints (id, workflow_id, step, state, context, created_at, parent_id)
250
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
251
+ [checkpoint.id, checkpoint.workflowId, checkpoint.step, checkpoint.state,
252
+ checkpoint.context, checkpoint.createdAt, checkpoint.parentId]
253
+ );
254
+ } else {
255
+ if (!this.memoryCheckpoints) this.memoryCheckpoints = new Map();
256
+ this.memoryCheckpoints.set(checkpoint.id, checkpoint);
257
+ }
258
+
259
+ return checkpoint;
260
+ }
261
+
262
+ /**
263
+ * Get a checkpoint by ID
264
+ */
265
+ async getCheckpoint(id) {
266
+ await this.initialize();
267
+
268
+ if (this.isSqlStorage()) {
269
+ const row = await this.storage.db.get('SELECT * FROM checkpoints WHERE id = ?', [id]);
270
+ if (!row) return null;
271
+ return {
272
+ id: row.id,
273
+ workflowId: row.workflow_id,
274
+ step: row.step,
275
+ state: JSON.parse(row.state),
276
+ context: JSON.parse(row.context),
277
+ createdAt: row.created_at,
278
+ parentId: row.parent_id,
279
+ };
280
+ } else {
281
+ return this.memoryCheckpoints?.get(id) || null;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Get all checkpoints for a workflow
287
+ */
288
+ async getCheckpoints(workflowId) {
289
+ await this.initialize();
290
+
291
+ if (this.isSqlStorage()) {
292
+ const rows = await this.storage.db.all(
293
+ 'SELECT * FROM checkpoints WHERE workflow_id = ? ORDER BY step ASC',
294
+ [workflowId]
295
+ );
296
+ return rows.map(row => ({
297
+ id: row.id,
298
+ workflowId: row.workflow_id,
299
+ step: row.step,
300
+ state: JSON.parse(row.state),
301
+ context: JSON.parse(row.context),
302
+ createdAt: row.created_at,
303
+ parentId: row.parent_id,
304
+ }));
305
+ } else {
306
+ const checkpoints = [];
307
+ for (const cp of this.memoryCheckpoints?.values() || []) {
308
+ if (cp.workflowId === workflowId) {
309
+ checkpoints.push(cp);
310
+ }
311
+ }
312
+ return checkpoints.sort((a, b) => a.step - b.step);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Get latest checkpoint ID for a workflow
318
+ */
319
+ async getLatestCheckpointId(workflowId) {
320
+ const checkpoints = await this.getCheckpoints(workflowId);
321
+ return checkpoints.length > 0 ? checkpoints[checkpoints.length - 1].id : null;
322
+ }
323
+
324
+ /**
325
+ * Recover workflow from latest checkpoint
326
+ */
327
+ async recover(workflowId) {
328
+ const checkpoints = await this.getCheckpoints(workflowId);
329
+ if (checkpoints.length === 0) {
330
+ return null;
331
+ }
332
+
333
+ const latest = checkpoints[checkpoints.length - 1];
334
+ return {
335
+ workflowId,
336
+ step: latest.step,
337
+ state: latest.state,
338
+ context: latest.context,
339
+ checkpointId: latest.id,
340
+ };
341
+ }
342
+
343
+ /**
344
+ * List all workflows with checkpoints
345
+ */
346
+ async listWorkflows() {
347
+ await this.initialize();
348
+
349
+ if (this.isSqlStorage()) {
350
+ const rows = await this.storage.db.all(`
351
+ SELECT workflow_id, MAX(step) as last_step, MAX(created_at) as last_checkpoint
352
+ FROM checkpoints
353
+ GROUP BY workflow_id
354
+ ORDER BY last_checkpoint DESC
355
+ `);
356
+ return rows;
357
+ }
358
+
359
+ // For memory storage, collect unique workflow IDs
360
+ const workflows = new Map();
361
+ for (const cp of this.memoryCheckpoints?.values() || []) {
362
+ if (!workflows.has(cp.workflowId)) {
363
+ workflows.set(cp.workflowId, {
364
+ workflow_id: cp.workflowId,
365
+ last_step: cp.step,
366
+ last_checkpoint: cp.createdAt,
367
+ });
368
+ } else {
369
+ const existing = workflows.get(cp.workflowId);
370
+ if (cp.step > existing.last_step) {
371
+ existing.last_step = cp.step;
372
+ existing.last_checkpoint = cp.createdAt;
373
+ }
374
+ }
375
+ }
376
+
377
+ return Array.from(workflows.values()).sort((a, b) =>
378
+ new Date(b.last_checkpoint) - new Date(a.last_checkpoint)
379
+ );
380
+ }
381
+
382
+ /**
383
+ * Delete checkpoints for a workflow
384
+ */
385
+ async deleteCheckpoints(workflowId) {
386
+ await this.initialize();
387
+
388
+ if (this.isSqlStorage()) {
389
+ await this.storage.db.run('DELETE FROM checkpoints WHERE workflow_id = ?', [workflowId]);
390
+ } else {
391
+ for (const [key, cp] of this.memoryCheckpoints?.entries() || []) {
392
+ if (cp.workflowId === workflowId) {
393
+ this.memoryCheckpoints.delete(key);
394
+ }
395
+ }
396
+ }
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Create execution engine from config
402
+ */
403
+ export async function createExecutionEngine(config = {}) {
404
+ const storage = await createStorage(
405
+ config.storage?.type || 'sqlite',
406
+ config.storage?.options || {}
407
+ );
408
+
409
+ let checkpointStore = storage;
410
+ if (config.checkpointStorage) {
411
+ checkpointStore = await createStorage(
412
+ config.checkpointStorage.type || 'sqlite',
413
+ config.checkpointStorage.options || {}
414
+ );
415
+ }
416
+
417
+ let idempotencyStore = null;
418
+ if (config.idempotency) {
419
+ idempotencyStore = await IdempotencyStore.create(config.idempotency);
420
+ }
421
+
422
+ return new ExecutionEngine({
423
+ storage,
424
+ checkpointStore,
425
+ idempotencyStore,
426
+ defaultCheckpointInterval: config.checkpointInterval,
427
+ });
428
+ }