@gotza02/sequential-thinking 10000.2.0 ā 10000.2.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.bak +197 -0
- package/dist/.gemini_graph_cache.json.bak +1641 -0
- package/dist/graph.d.ts +7 -0
- package/dist/graph.js +136 -113
- package/dist/intelligent-code.d.ts +8 -0
- package/dist/intelligent-code.js +152 -125
- package/dist/intelligent-code.test.d.ts +1 -0
- package/dist/intelligent-code.test.js +104 -0
- package/dist/lib/formatters.d.ts +9 -0
- package/dist/lib/formatters.js +119 -0
- package/dist/lib/validators.d.ts +45 -0
- package/dist/lib/validators.js +232 -0
- package/dist/lib.js +23 -265
- package/dist/tools/sports/core/base.d.ts +3 -2
- package/dist/tools/sports/core/base.js +12 -10
- package/dist/tools/sports/core/cache.d.ts +9 -0
- package/dist/tools/sports/core/cache.js +25 -3
- package/dist/tools/sports/core/types.d.ts +6 -2
- package/dist/tools/sports/providers/api.d.ts +4 -0
- package/dist/tools/sports/providers/api.js +110 -27
- package/dist/tools/sports/tools/betting.js +16 -16
- package/dist/tools/sports/tools/league.d.ts +2 -7
- package/dist/tools/sports/tools/league.js +198 -8
- package/dist/tools/sports/tools/live.js +80 -38
- package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
- package/dist/tools/sports/tools/match-calculations.js +171 -0
- package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
- package/dist/tools/sports/tools/match-helpers.js +57 -0
- package/dist/tools/sports/tools/match.js +227 -125
- package/dist/tools/sports.js +3 -3
- package/dist/utils.d.ts +111 -44
- package/dist/utils.js +510 -305
- package/dist/utils.test.js +3 -3
- package/package.json +1 -1
- package/CLAUDE.md +0 -231
package/dist/lib.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import * as fs from 'fs/promises';
|
|
3
2
|
import { existsSync, readFileSync } from 'fs';
|
|
4
3
|
import * as path from 'path';
|
|
5
4
|
import { AsyncMutex } from './utils.js';
|
|
6
5
|
import { ContextManager } from './core/ContextManager.js';
|
|
7
6
|
import { RuleManager } from './core/RuleManager.js';
|
|
7
|
+
import { formatThought, generateMermaid } from './lib/formatters.js';
|
|
8
|
+
import { detectLoopsAndStalling, validateSolution, checkAntiInsanity, checkConfidenceCritical, calculateConfidenceScore } from './lib/validators.js';
|
|
8
9
|
export class SequentialThinkingServer {
|
|
9
10
|
thoughtHistory = [];
|
|
10
11
|
blocks = [];
|
|
@@ -390,79 +391,7 @@ export class SequentialThinkingServer {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
formatThought(thoughtData) {
|
|
393
|
-
|
|
394
|
-
const typeEmoji = {
|
|
395
|
-
'analysis': 'š',
|
|
396
|
-
'planning': 'š',
|
|
397
|
-
'critique': 'š',
|
|
398
|
-
'execution': 'ā”',
|
|
399
|
-
'observation': 'šļø',
|
|
400
|
-
'hypothesis': 'š”',
|
|
401
|
-
'reflexion': 'š',
|
|
402
|
-
'solution': 'ā
',
|
|
403
|
-
'generation': 'š',
|
|
404
|
-
'evaluation': 'āļø',
|
|
405
|
-
'selection': 'šÆ'
|
|
406
|
-
};
|
|
407
|
-
let prefix = '';
|
|
408
|
-
let context = '';
|
|
409
|
-
const type = thoughtType || 'analysis';
|
|
410
|
-
const emoji = typeEmoji[type] || 'š';
|
|
411
|
-
if (type === 'reflexion' || isRevision) {
|
|
412
|
-
prefix = chalk.yellow(`${emoji} Reflexion`);
|
|
413
|
-
if (revisesThought)
|
|
414
|
-
context += ` (revising #${revisesThought})`;
|
|
415
|
-
}
|
|
416
|
-
else if (type === 'critique') {
|
|
417
|
-
prefix = chalk.redBright(`${emoji} Critique`);
|
|
418
|
-
}
|
|
419
|
-
else if (type === 'execution') {
|
|
420
|
-
prefix = chalk.magenta(`${emoji} Execution`);
|
|
421
|
-
if (relatedToolCall)
|
|
422
|
-
context += ` [Tool: ${relatedToolCall}]`;
|
|
423
|
-
}
|
|
424
|
-
else if (type === 'observation') {
|
|
425
|
-
prefix = chalk.cyan(`${emoji} Observation`);
|
|
426
|
-
}
|
|
427
|
-
else if (type === 'planning') {
|
|
428
|
-
prefix = chalk.blue(`${emoji} Planning`);
|
|
429
|
-
}
|
|
430
|
-
else if (type === 'solution') {
|
|
431
|
-
prefix = chalk.green(`${emoji} Solution`);
|
|
432
|
-
}
|
|
433
|
-
else if (branchFromThought) {
|
|
434
|
-
prefix = chalk.green(`šæ Branch`);
|
|
435
|
-
context = ` (from #${branchFromThought}, ID: ${branchId})`;
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
prefix = chalk.blue(`${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}`);
|
|
439
|
-
}
|
|
440
|
-
if (blockId && blockId !== 'default' && blockId !== 'legacy-default') {
|
|
441
|
-
context += ` [Block: ${blockId}]`;
|
|
442
|
-
}
|
|
443
|
-
if (complexity)
|
|
444
|
-
context += ` [${complexity.toUpperCase()}]`;
|
|
445
|
-
if (score)
|
|
446
|
-
context += ` (Score: ${score})`;
|
|
447
|
-
if (selectedOption)
|
|
448
|
-
context += ` (Selected: ${selectedOption})`;
|
|
449
|
-
const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
|
|
450
|
-
const borderLength = Math.max(header.length, Math.min(thought.length, 80)) + 4;
|
|
451
|
-
const border = 'ā'.repeat(borderLength);
|
|
452
|
-
let extraContent = '';
|
|
453
|
-
if (options && options.length > 0) {
|
|
454
|
-
extraContent += `\nā Options:\n` + options.map(o => `ā - ${o}`).join('\n');
|
|
455
|
-
}
|
|
456
|
-
// Wrap long thoughts
|
|
457
|
-
const wrappedThought = thought.length > 76
|
|
458
|
-
? thought.match(/.{1,76}/g)?.map(line => `ā ${line.padEnd(borderLength - 2)} ā`).join('\n') || thought
|
|
459
|
-
: `ā ${thought.padEnd(borderLength - 2)} ā`;
|
|
460
|
-
return `
|
|
461
|
-
ā${border}ā
|
|
462
|
-
ā ${header.padEnd(borderLength - 2)} ā
|
|
463
|
-
ā${border}ā¤
|
|
464
|
-
${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrappedThought : `ā ${thought.padEnd(borderLength - 2)} ā`}${extraContent}
|
|
465
|
-
ā${border}ā`;
|
|
394
|
+
return formatThought(thoughtData);
|
|
466
395
|
}
|
|
467
396
|
// --- Extracted Helper Methods for processThought ---
|
|
468
397
|
/**
|
|
@@ -515,102 +444,24 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrapp
|
|
|
515
444
|
* Detect loops, stalling, and other thinking pattern issues
|
|
516
445
|
*/
|
|
517
446
|
detectLoopsAndStalling(input, blockThoughts, recentInBlock, lastThought, complexity) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
thinkingTypes.includes(input.thoughtType || 'analysis')) {
|
|
526
|
-
warnings.push(`ā ļø STALLING DETECTED: You have been thinking for 4 consecutive steps without execution. Consider changing thoughtType to 'execution' and calling a tool to make progress.`);
|
|
527
|
-
isStallingOrLooping = true;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Rule 2: Repeating Action Detection
|
|
531
|
-
if (input.thoughtType === 'execution') {
|
|
532
|
-
// Check Immune System Rules
|
|
533
|
-
const ruleWarnings = this.ruleManager.checkRules(input.thought);
|
|
534
|
-
ruleWarnings.forEach(w => warnings.push(w));
|
|
535
|
-
if (recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
|
|
536
|
-
warnings.push(`š LOOP DETECTED: You are attempting to execute the exact same action again. You MUST change your strategy or create a branch with a different approach.`);
|
|
537
|
-
isStallingOrLooping = true;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
// Rule 3: Missing Observation
|
|
541
|
-
if (lastThought &&
|
|
542
|
-
lastThought.thoughtType === 'execution' &&
|
|
543
|
-
input.thoughtType !== 'observation' &&
|
|
544
|
-
!input.branchFromThought) {
|
|
545
|
-
warnings.push(`š” INTERLEAVED HINT: Your last step was 'execution'. The next step SHOULD be 'observation' to record and analyze the tool result before continuing.`);
|
|
546
|
-
}
|
|
547
|
-
// Rule 4: Skipping Planning (Adaptive)
|
|
548
|
-
if (complexity !== 'low') {
|
|
549
|
-
const hasPlanning = blockThoughts.some(t => t.thoughtType === 'planning');
|
|
550
|
-
if (!hasPlanning &&
|
|
551
|
-
blockThoughts.length >= 2 &&
|
|
552
|
-
input.thoughtType === 'execution') {
|
|
553
|
-
warnings.push(`š PLANNING HINT: You're about to execute without a planning step. Consider adding a 'planning' thought first to outline your approach.`);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// Rule 5: Devil's Advocate (Critique Phase)
|
|
557
|
-
if (complexity === 'high' && input.thoughtType === 'execution') {
|
|
558
|
-
const hasCritique = blockThoughts.some(t => t.thoughtType === 'critique');
|
|
559
|
-
if (!hasCritique) {
|
|
560
|
-
warnings.push(`š CRITIQUE REQUIRED: High complexity task requires a 'critique' step before execution. Analyze risks and flaws in your plan first.`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
// Rule 6: Ending without Verification
|
|
564
|
-
if (input.nextThoughtNeeded === false) {
|
|
565
|
-
const hasVerification = this.thoughtHistory.some(t => t.thought.toLowerCase().includes('test') ||
|
|
566
|
-
t.thought.toLowerCase().includes('verify') ||
|
|
567
|
-
t.thought.toLowerCase().includes('check') ||
|
|
568
|
-
t.thoughtType === 'observation');
|
|
569
|
-
if (!hasVerification) {
|
|
570
|
-
warnings.push(`šØ CRITICAL: You are ending without explicit verification/testing. Consider adding an 'observation' or verification step.`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
// Rule 7: Same type loop
|
|
574
|
-
if (complexity !== 'low' && recentInBlock.length >= SequentialThinkingServer.STALLING_THRESHOLD &&
|
|
575
|
-
recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
|
|
576
|
-
warnings.push(`ā ļø TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
|
|
577
|
-
}
|
|
578
|
-
// Rule 8: Struggle Detection
|
|
579
|
-
const executionCount = blockThoughts.filter(t => t.thoughtType === 'execution').length;
|
|
580
|
-
if (executionCount >= SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD && input.thoughtType === 'execution') {
|
|
581
|
-
warnings.push(`š STRUGGLE DETECTED: You have executed ${SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD}+ commands in this block without reaching a solution. If you are fixing a bug and it's not working, STOP. Do not try a 4th time linearly. Use 'branchFromThought' to try a completely different approach.`);
|
|
582
|
-
}
|
|
583
|
-
// Rule 9: Premature Solution Detection
|
|
584
|
-
const hasObservation = blockThoughts.some(t => t.thoughtType === 'observation');
|
|
585
|
-
if (input.thoughtType === 'solution' && !hasObservation && blockThoughts.length < 4 && complexity !== 'low') {
|
|
586
|
-
warnings.push(`š¤ PREMATURE SOLUTION: You are concluding this block very quickly without any 'observation'. Are you hallucinating? Please verify with a tool first.`);
|
|
587
|
-
}
|
|
588
|
-
return { isStallingOrLooping, warnings };
|
|
447
|
+
return detectLoopsAndStalling(input, {
|
|
448
|
+
thoughtHistory: this.thoughtHistory,
|
|
449
|
+
blockThoughts,
|
|
450
|
+
recentInBlock,
|
|
451
|
+
lastThought,
|
|
452
|
+
complexity
|
|
453
|
+
}, (thought) => this.ruleManager.checkRules(thought));
|
|
589
454
|
}
|
|
590
455
|
/**
|
|
591
456
|
* Validate solution before accepting
|
|
592
457
|
*/
|
|
593
458
|
async validateSolution(input, blockThoughts, complexity, warnings) {
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const failureKeywords = ['error', 'fail', 'exception', 'undefined', 'not found', 'enoent'];
|
|
601
|
-
const hasFailure = failureKeywords.some(kw => lastObservation.toolResult?.toLowerCase().includes(kw));
|
|
602
|
-
if (hasFailure) {
|
|
603
|
-
warnings.push(`ā ļø FAILURE DETECTED: The last observation contains failure signals ("${lastObservation.toolResult?.substring(0, 50)}..."). Fix the error first.`);
|
|
604
|
-
}
|
|
605
|
-
// Smart Search Verification
|
|
606
|
-
const searchTools = ['web_search', 'google_web_search', 'read_webpage', 'search_file_content', 'google_search'];
|
|
607
|
-
const isSearch = lastObservation.relatedToolCall && searchTools.some(tool => lastObservation.relatedToolCall.includes(tool));
|
|
608
|
-
if (isSearch) {
|
|
609
|
-
warnings.push(`šµļø DATA INTEGRITY CHECK: You are concluding a search task. Verify: Did you actually find the specific answer?`);
|
|
610
|
-
}
|
|
611
|
-
// Double Verification for High Complexity
|
|
612
|
-
if (complexity === 'high' && observations.length < 2) {
|
|
613
|
-
warnings.push(`āļø DOUBLE VERIFICATION REQUIRED: High complexity tasks require at least 2 distinct observations/verifications before conclusion.`);
|
|
459
|
+
const validationWarnings = validateSolution(input, blockThoughts, complexity);
|
|
460
|
+
warnings.push(...validationWarnings);
|
|
461
|
+
// Handle high complexity double verification penalty
|
|
462
|
+
if (complexity === 'high') {
|
|
463
|
+
const observations = blockThoughts.filter(t => t.thoughtType === 'observation');
|
|
464
|
+
if (observations.length < 2 && validationWarnings.some(w => w.includes('DOUBLE VERIFICATION'))) {
|
|
614
465
|
this.confidenceScore -= 10;
|
|
615
466
|
}
|
|
616
467
|
}
|
|
@@ -628,23 +479,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrapp
|
|
|
628
479
|
* Update confidence score based on thought type and warnings
|
|
629
480
|
*/
|
|
630
481
|
updateConfidenceScore(input, warnings) {
|
|
631
|
-
|
|
632
|
-
this.confidenceScore = Math.max(0, this.confidenceScore - (5 * warnings.length));
|
|
633
|
-
}
|
|
634
|
-
if (input.thoughtType === 'reflexion') {
|
|
635
|
-
this.confidenceScore = Math.min(100, this.confidenceScore + 10);
|
|
636
|
-
}
|
|
637
|
-
else if (input.thoughtType === 'observation') {
|
|
638
|
-
const isError = input.toolResult?.toLowerCase().includes('error');
|
|
639
|
-
if (isError)
|
|
640
|
-
this.confidenceScore -= 10;
|
|
641
|
-
else
|
|
642
|
-
this.confidenceScore = Math.min(100, this.confidenceScore + 10);
|
|
643
|
-
}
|
|
644
|
-
else if (input.thoughtType === 'solution' && warnings.length === 0) {
|
|
645
|
-
// Bonus for clean solution
|
|
646
|
-
this.confidenceScore = Math.min(100, this.confidenceScore + 20);
|
|
647
|
-
}
|
|
482
|
+
this.confidenceScore = calculateConfidenceScore(this.confidenceScore, input, warnings.length);
|
|
648
483
|
}
|
|
649
484
|
// --- Helper Methods for processThought ---
|
|
650
485
|
/**
|
|
@@ -680,27 +515,10 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrapp
|
|
|
680
515
|
* Check anti-insanity - prevent repeating failed executions
|
|
681
516
|
*/
|
|
682
517
|
checkAntiInsanity(input, historyForCheck) {
|
|
683
|
-
const warnings =
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return false;
|
|
688
|
-
// Find the observation immediately following this execution
|
|
689
|
-
const nextThought = this.thoughtHistory.find(h => h.thoughtNumber === t.thoughtNumber + 1);
|
|
690
|
-
if (nextThought && nextThought.thoughtType === 'observation') {
|
|
691
|
-
const result = nextThought.toolResult?.toLowerCase() || "";
|
|
692
|
-
// Check for failure signals
|
|
693
|
-
return result.includes("error") || result.includes("fail") ||
|
|
694
|
-
result.includes("exception") || result.includes("enoent");
|
|
695
|
-
}
|
|
696
|
-
return false;
|
|
697
|
-
});
|
|
698
|
-
const isRepeatedFailure = failedExecutions.some(t => t.thought.trim() === input.thought.trim());
|
|
699
|
-
if (isRepeatedFailure) {
|
|
700
|
-
const failedItem = failedExecutions.find(t => t.thought.trim() === input.thought.trim());
|
|
701
|
-
warnings.push(`ā INSANITY CHECK: You are trying to run a command that ALREADY FAILED in thought #${failedItem?.thoughtNumber}. STOP. Do not repeat mistakes. Try a different parameter or tool.`);
|
|
702
|
-
this.confidenceScore -= 15;
|
|
703
|
-
}
|
|
518
|
+
const warnings = checkAntiInsanity(input, historyForCheck, this.thoughtHistory);
|
|
519
|
+
// Apply confidence penalty for repeated failures
|
|
520
|
+
if (warnings.some(w => w.includes('INSANITY CHECK'))) {
|
|
521
|
+
this.confidenceScore -= 15;
|
|
704
522
|
}
|
|
705
523
|
return warnings;
|
|
706
524
|
}
|
|
@@ -708,36 +526,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrapp
|
|
|
708
526
|
* Check if confidence is critical and return critical stop response if needed
|
|
709
527
|
*/
|
|
710
528
|
checkConfidenceCritical(input, historyForCheck) {
|
|
711
|
-
|
|
712
|
-
input.thoughtType === 'reflexion' ||
|
|
713
|
-
input.thoughtType === 'analysis') {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
// Smart Branching: Find the last valid planning point
|
|
717
|
-
const lastGoodPlan = historyForCheck
|
|
718
|
-
.slice()
|
|
719
|
-
.reverse()
|
|
720
|
-
.find(t => t.thoughtType === 'planning' || t.thoughtType === 'hypothesis');
|
|
721
|
-
const recoveryAdvice = lastGoodPlan
|
|
722
|
-
? `RECOMMENDATION: Branch from thought #${lastGoodPlan.thoughtNumber} to try a new approach.`
|
|
723
|
-
: `RECOMMENDATION: Use 'reflexion' to analyze why current attempts are failing.`;
|
|
724
|
-
return {
|
|
725
|
-
content: [{
|
|
726
|
-
type: "text",
|
|
727
|
-
text: JSON.stringify({
|
|
728
|
-
status: "CRITICAL_STOP",
|
|
729
|
-
feedback: [
|
|
730
|
-
"šØ CONFIDENCE CRITICAL (<50). Execution blocked to prevent further damage.",
|
|
731
|
-
"š STOP: You are likely in a loop or making repeated errors.",
|
|
732
|
-
"š REQUIRED ACTION: You must switch thoughtType to 'reflexion' to critique your errors.",
|
|
733
|
-
"ā ļø REMEMBER: You MUST provide a 'thought' string explaining your reflection. Do not leave it empty.",
|
|
734
|
-
recoveryAdvice
|
|
735
|
-
],
|
|
736
|
-
confidenceScore: this.confidenceScore
|
|
737
|
-
}, null, 2)
|
|
738
|
-
}],
|
|
739
|
-
isError: true
|
|
740
|
-
};
|
|
529
|
+
return checkConfidenceCritical(this.confidenceScore, input, historyForCheck);
|
|
741
530
|
}
|
|
742
531
|
// --- 2. The New Brain: Process Thought with Interleaved Logic (Opus 4.5 Style) ---
|
|
743
532
|
async processThought(input) {
|
|
@@ -847,38 +636,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('ā') ? wrapp
|
|
|
847
636
|
return 'THINKING';
|
|
848
637
|
}
|
|
849
638
|
generateMermaid() {
|
|
850
|
-
|
|
851
|
-
const recentThoughts = this.thoughtHistory.slice(-15); // Last 15 for readability
|
|
852
|
-
recentThoughts.forEach((t, i) => {
|
|
853
|
-
const id = `T${t.thoughtNumber}`;
|
|
854
|
-
const typeIcon = {
|
|
855
|
-
'analysis': 'š',
|
|
856
|
-
'planning': 'š',
|
|
857
|
-
'critique': 'š',
|
|
858
|
-
'execution': 'ā”',
|
|
859
|
-
'observation': 'šļø',
|
|
860
|
-
'hypothesis': 'š”',
|
|
861
|
-
'reflexion': 'š',
|
|
862
|
-
'solution': 'ā
',
|
|
863
|
-
'generation': 'š',
|
|
864
|
-
'evaluation': 'āļø',
|
|
865
|
-
'selection': 'šÆ'
|
|
866
|
-
}[t.thoughtType || 'analysis'] || 'š';
|
|
867
|
-
const label = `${typeIcon} ${t.thoughtNumber}`;
|
|
868
|
-
diagram += ` ${id}("${label}")\n`;
|
|
869
|
-
if (i > 0) {
|
|
870
|
-
const prevT = recentThoughts[i - 1];
|
|
871
|
-
const prevId = `T${prevT.thoughtNumber}`;
|
|
872
|
-
if (t.branchFromThought) {
|
|
873
|
-
diagram += ` T${t.branchFromThought} -->|branch: ${t.branchId}| ${id}\n`;
|
|
874
|
-
}
|
|
875
|
-
else {
|
|
876
|
-
const edgeLabel = t.thoughtType === 'observation' ? '|result|' : '';
|
|
877
|
-
diagram += ` ${prevId} -->${edgeLabel} ${id}\n`;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
return diagram;
|
|
639
|
+
return generateMermaid(this.thoughtHistory);
|
|
882
640
|
}
|
|
883
641
|
// Public method to start a new block explicitly
|
|
884
642
|
startNewBlock(blockId, topic) {
|
|
@@ -73,7 +73,8 @@ export declare abstract class APIProviderBase implements IDataProvider {
|
|
|
73
73
|
protected rateLimitUntil: Date | null;
|
|
74
74
|
protected lastCall: Date | null;
|
|
75
75
|
protected callCount: number;
|
|
76
|
-
constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number
|
|
76
|
+
constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number, // calls per minute
|
|
77
|
+
cache?: CacheService);
|
|
77
78
|
/**
|
|
78
79
|
* Check if provider has valid credentials
|
|
79
80
|
*/
|
|
@@ -131,7 +132,7 @@ export declare abstract class APIProviderBase implements IDataProvider {
|
|
|
131
132
|
export declare abstract class ScraperProviderBase implements IDataProvider {
|
|
132
133
|
protected cache: CacheService;
|
|
133
134
|
readonly type: ProviderType;
|
|
134
|
-
constructor();
|
|
135
|
+
constructor(cache?: CacheService);
|
|
135
136
|
isAvailable(): boolean;
|
|
136
137
|
getStatus(): ProviderStatus;
|
|
137
138
|
abstract getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SPORTS MODULE BASE CLASSES
|
|
3
3
|
* Abstract base classes and interfaces for data providers
|
|
4
4
|
*/
|
|
5
|
-
import { CacheService } from './cache.js';
|
|
5
|
+
import { CacheService, getGlobalCache } from './cache.js';
|
|
6
6
|
import { logger } from '../../../utils.js';
|
|
7
7
|
/**
|
|
8
8
|
* Abstract base class for API-based providers
|
|
@@ -16,13 +16,13 @@ export class APIProviderBase {
|
|
|
16
16
|
rateLimitUntil = null;
|
|
17
17
|
lastCall = null;
|
|
18
18
|
callCount = 0;
|
|
19
|
-
constructor(type, apiKey, baseUrl, rateLimit = 10 // calls per minute
|
|
20
|
-
) {
|
|
19
|
+
constructor(type, apiKey, baseUrl, rateLimit = 10, // calls per minute
|
|
20
|
+
cache) {
|
|
21
21
|
this.type = type;
|
|
22
22
|
this.apiKey = apiKey;
|
|
23
23
|
this.baseUrl = baseUrl;
|
|
24
24
|
this.rateLimit = rateLimit;
|
|
25
|
-
this.cache =
|
|
25
|
+
this.cache = cache || getGlobalCache();
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Check if provider has valid credentials
|
|
@@ -86,7 +86,9 @@ export class APIProviderBase {
|
|
|
86
86
|
// Handle rate limiting
|
|
87
87
|
if (response.status === 429) {
|
|
88
88
|
const retryAfter = response.headers.get('Retry-After');
|
|
89
|
-
|
|
89
|
+
const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : NaN;
|
|
90
|
+
const delayMs = isNaN(retrySeconds) ? 60000 : retrySeconds * 1000;
|
|
91
|
+
this.rateLimitUntil = new Date(Date.now() + delayMs);
|
|
90
92
|
return {
|
|
91
93
|
success: false,
|
|
92
94
|
error: 'Rate limited',
|
|
@@ -127,8 +129,8 @@ export class APIProviderBase {
|
|
|
127
129
|
export class ScraperProviderBase {
|
|
128
130
|
cache;
|
|
129
131
|
type = 'scraper';
|
|
130
|
-
constructor() {
|
|
131
|
-
this.cache =
|
|
132
|
+
constructor(cache) {
|
|
133
|
+
this.cache = cache || getGlobalCache();
|
|
132
134
|
}
|
|
133
135
|
isAvailable() {
|
|
134
136
|
return true; // Scraping is always "available"
|
|
@@ -181,8 +183,8 @@ export class ScraperProviderBase {
|
|
|
181
183
|
export class FallbackProvider {
|
|
182
184
|
providers;
|
|
183
185
|
cache;
|
|
184
|
-
type = '
|
|
185
|
-
constructor(providers, cache =
|
|
186
|
+
type = 'api-football'; // Most common API type
|
|
187
|
+
constructor(providers, cache = getGlobalCache()) {
|
|
186
188
|
this.providers = providers;
|
|
187
189
|
this.cache = cache;
|
|
188
190
|
}
|
|
@@ -275,7 +277,7 @@ export class FallbackProvider {
|
|
|
275
277
|
export class SportsToolBase {
|
|
276
278
|
cache;
|
|
277
279
|
constructor(cache) {
|
|
278
|
-
this.cache = cache ||
|
|
280
|
+
this.cache = cache || getGlobalCache();
|
|
279
281
|
}
|
|
280
282
|
/**
|
|
281
283
|
* Format error message for user
|
|
@@ -12,7 +12,12 @@ export declare class CacheService {
|
|
|
12
12
|
private maxSize;
|
|
13
13
|
private savePending;
|
|
14
14
|
private saveTimer;
|
|
15
|
+
cleanupInterval: NodeJS.Timeout | null;
|
|
15
16
|
constructor(cachePath?: string, maxAge?: number, maxSize?: number);
|
|
17
|
+
/**
|
|
18
|
+
* Initialize cache - must be called after construction
|
|
19
|
+
*/
|
|
20
|
+
initialize(): Promise<void>;
|
|
16
21
|
/**
|
|
17
22
|
* Generate a cache key from components
|
|
18
23
|
*/
|
|
@@ -37,6 +42,10 @@ export declare class CacheService {
|
|
|
37
42
|
* Clear all cache entries
|
|
38
43
|
*/
|
|
39
44
|
clear(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Stop cleanup interval and clear all resources
|
|
47
|
+
*/
|
|
48
|
+
dispose(): void;
|
|
40
49
|
/**
|
|
41
50
|
* Get cache statistics
|
|
42
51
|
*/
|
|
@@ -16,11 +16,17 @@ export class CacheService {
|
|
|
16
16
|
maxSize;
|
|
17
17
|
savePending = false;
|
|
18
18
|
saveTimer = null;
|
|
19
|
+
cleanupInterval = null;
|
|
19
20
|
constructor(cachePath = CACHE_CONFIG.CACHE_PATH, maxAge = CACHE_CONFIG.DEFAULT_TTL, maxSize = CACHE_CONFIG.MAX_SIZE) {
|
|
20
21
|
this.cachePath = path.resolve(cachePath);
|
|
21
22
|
this.maxAge = maxAge;
|
|
22
23
|
this.maxSize = maxSize;
|
|
23
|
-
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initialize cache - must be called after construction
|
|
27
|
+
*/
|
|
28
|
+
async initialize() {
|
|
29
|
+
await this.loadFromFile();
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* Generate a cache key from components
|
|
@@ -95,6 +101,20 @@ export class CacheService {
|
|
|
95
101
|
logger.info('Cache cleared');
|
|
96
102
|
this.scheduleSave();
|
|
97
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Stop cleanup interval and clear all resources
|
|
106
|
+
*/
|
|
107
|
+
dispose() {
|
|
108
|
+
if (this.cleanupInterval) {
|
|
109
|
+
clearInterval(this.cleanupInterval);
|
|
110
|
+
this.cleanupInterval = null;
|
|
111
|
+
}
|
|
112
|
+
if (this.saveTimer) {
|
|
113
|
+
clearTimeout(this.saveTimer);
|
|
114
|
+
this.saveTimer = null;
|
|
115
|
+
}
|
|
116
|
+
this.cache.clear();
|
|
117
|
+
}
|
|
98
118
|
/**
|
|
99
119
|
* Get cache statistics
|
|
100
120
|
*/
|
|
@@ -288,8 +308,10 @@ let globalCache = null;
|
|
|
288
308
|
export function getGlobalCache() {
|
|
289
309
|
if (!globalCache) {
|
|
290
310
|
globalCache = new CacheService();
|
|
311
|
+
// Initialize async (fire and forget - will be ready on next tick)
|
|
312
|
+
globalCache.initialize().catch(err => logger.error('Cache init failed:', err));
|
|
291
313
|
// Cleanup expired entries every 5 minutes
|
|
292
|
-
setInterval(() => {
|
|
314
|
+
globalCache.cleanupInterval = setInterval(() => {
|
|
293
315
|
globalCache?.cleanup();
|
|
294
316
|
}, 5 * 60 * 1000);
|
|
295
317
|
}
|
|
@@ -300,7 +322,7 @@ export function getGlobalCache() {
|
|
|
300
322
|
*/
|
|
301
323
|
export function resetGlobalCache() {
|
|
302
324
|
if (globalCache) {
|
|
303
|
-
globalCache.
|
|
325
|
+
globalCache.dispose();
|
|
304
326
|
}
|
|
305
327
|
globalCache = null;
|
|
306
328
|
}
|
|
@@ -138,7 +138,9 @@ export interface TableEntry {
|
|
|
138
138
|
drawn: number;
|
|
139
139
|
lost: number;
|
|
140
140
|
goalsFor: number;
|
|
141
|
-
|
|
141
|
+
goalsAgainst: number;
|
|
142
|
+
/** @deprecated Use goalsAgainst instead */
|
|
143
|
+
against?: number;
|
|
142
144
|
};
|
|
143
145
|
awayRecord?: {
|
|
144
146
|
played: number;
|
|
@@ -146,7 +148,9 @@ export interface TableEntry {
|
|
|
146
148
|
drawn: number;
|
|
147
149
|
lost: number;
|
|
148
150
|
goalsFor: number;
|
|
149
|
-
|
|
151
|
+
goalsAgainst: number;
|
|
152
|
+
/** @deprecated Use goalsAgainst instead */
|
|
153
|
+
against?: number;
|
|
150
154
|
};
|
|
151
155
|
lastFive?: {
|
|
152
156
|
result: 'W' | 'D' | 'L';
|
|
@@ -61,6 +61,10 @@ export declare class FootballDataProvider extends APIProviderBase {
|
|
|
61
61
|
export declare class SportsDBProvider extends APIProviderBase {
|
|
62
62
|
constructor();
|
|
63
63
|
protected getAuthHeaders(): Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Override callAPI to inject API key in URL for TheSportsDB
|
|
66
|
+
*/
|
|
67
|
+
protected callAPI<T>(endpoint: string, options?: RequestInit): Promise<APIResponse<T>>;
|
|
64
68
|
protected transformResponse<T>(data: any): T;
|
|
65
69
|
getMatch(matchId: string): Promise<APIResponse<Match>>;
|
|
66
70
|
getLiveMatches(): Promise<APIResponse<Match[]>>;
|