@timmeck/brain-core 2.36.12 → 2.36.14

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 (125) hide show
  1. package/dist/cross-brain/__tests__/borg-sync-engine.test.d.ts +1 -0
  2. package/dist/cross-brain/__tests__/borg-sync-engine.test.js +240 -0
  3. package/dist/cross-brain/__tests__/borg-sync-engine.test.js.map +1 -0
  4. package/dist/cross-brain/borg-sync-engine.d.ts +62 -0
  5. package/dist/cross-brain/borg-sync-engine.js +215 -0
  6. package/dist/cross-brain/borg-sync-engine.js.map +1 -0
  7. package/dist/cross-brain/borg-types.d.ts +37 -0
  8. package/dist/cross-brain/borg-types.js +9 -0
  9. package/dist/cross-brain/borg-types.js.map +1 -0
  10. package/dist/embeddings/engine.js +2 -1
  11. package/dist/embeddings/engine.js.map +1 -1
  12. package/dist/index.d.ts +18 -1
  13. package/dist/index.js +14 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/llm/__tests__/anthropic-provider.test.d.ts +1 -0
  16. package/dist/llm/__tests__/anthropic-provider.test.js +121 -0
  17. package/dist/llm/__tests__/anthropic-provider.test.js.map +1 -0
  18. package/dist/llm/__tests__/llm-service.test.js +181 -40
  19. package/dist/llm/__tests__/llm-service.test.js.map +1 -1
  20. package/dist/llm/__tests__/ollama-embedding.test.d.ts +1 -0
  21. package/dist/llm/__tests__/ollama-embedding.test.js +128 -0
  22. package/dist/llm/__tests__/ollama-embedding.test.js.map +1 -0
  23. package/dist/llm/__tests__/ollama-provider.test.d.ts +1 -0
  24. package/dist/llm/__tests__/ollama-provider.test.js +213 -0
  25. package/dist/llm/__tests__/ollama-provider.test.js.map +1 -0
  26. package/dist/llm/__tests__/provider.test.d.ts +1 -0
  27. package/dist/llm/__tests__/provider.test.js +126 -0
  28. package/dist/llm/__tests__/provider.test.js.map +1 -0
  29. package/dist/llm/anthropic-provider.d.ts +41 -0
  30. package/dist/llm/anthropic-provider.js +86 -0
  31. package/dist/llm/anthropic-provider.js.map +1 -0
  32. package/dist/llm/index.d.ts +9 -1
  33. package/dist/llm/index.js +4 -0
  34. package/dist/llm/index.js.map +1 -1
  35. package/dist/llm/llm-service.d.ts +55 -7
  36. package/dist/llm/llm-service.js +184 -82
  37. package/dist/llm/llm-service.js.map +1 -1
  38. package/dist/llm/ollama-embedding.d.ts +46 -0
  39. package/dist/llm/ollama-embedding.js +93 -0
  40. package/dist/llm/ollama-embedding.js.map +1 -0
  41. package/dist/llm/ollama-provider.d.ts +80 -0
  42. package/dist/llm/ollama-provider.js +178 -0
  43. package/dist/llm/ollama-provider.js.map +1 -0
  44. package/dist/llm/provider.d.ts +120 -0
  45. package/dist/llm/provider.js +104 -0
  46. package/dist/llm/provider.js.map +1 -0
  47. package/dist/missions/mission-engine.d.ts +4 -0
  48. package/dist/missions/mission-engine.js +30 -8
  49. package/dist/missions/mission-engine.js.map +1 -1
  50. package/dist/notifications/__tests__/notification-service.test.d.ts +1 -0
  51. package/dist/notifications/__tests__/notification-service.test.js +176 -0
  52. package/dist/notifications/__tests__/notification-service.test.js.map +1 -0
  53. package/dist/notifications/discord-provider.d.ts +30 -0
  54. package/dist/notifications/discord-provider.js +89 -0
  55. package/dist/notifications/discord-provider.js.map +1 -0
  56. package/dist/notifications/email-provider.d.ts +41 -0
  57. package/dist/notifications/email-provider.js +101 -0
  58. package/dist/notifications/email-provider.js.map +1 -0
  59. package/dist/notifications/index.d.ts +8 -0
  60. package/dist/notifications/index.js +5 -0
  61. package/dist/notifications/index.js.map +1 -0
  62. package/dist/notifications/notification-provider.d.ts +75 -0
  63. package/dist/notifications/notification-provider.js +47 -0
  64. package/dist/notifications/notification-provider.js.map +1 -0
  65. package/dist/notifications/notification-service.d.ts +85 -0
  66. package/dist/notifications/notification-service.js +184 -0
  67. package/dist/notifications/notification-service.js.map +1 -0
  68. package/dist/notifications/telegram-provider.d.ts +30 -0
  69. package/dist/notifications/telegram-provider.js +78 -0
  70. package/dist/notifications/telegram-provider.js.map +1 -0
  71. package/dist/plugin/__tests__/plugin-registry.test.d.ts +1 -0
  72. package/dist/plugin/__tests__/plugin-registry.test.js +166 -0
  73. package/dist/plugin/__tests__/plugin-registry.test.js.map +1 -0
  74. package/dist/plugin/plugin-registry.d.ts +38 -0
  75. package/dist/plugin/plugin-registry.js +185 -0
  76. package/dist/plugin/plugin-registry.js.map +1 -0
  77. package/dist/plugin/types.d.ts +59 -0
  78. package/dist/plugin/types.js +2 -0
  79. package/dist/plugin/types.js.map +1 -0
  80. package/dist/research/adapters/__tests__/web-adapters.test.d.ts +1 -0
  81. package/dist/research/adapters/__tests__/web-adapters.test.js +106 -0
  82. package/dist/research/adapters/__tests__/web-adapters.test.js.map +1 -0
  83. package/dist/research/adapters/firecrawl-adapter.d.ts +57 -0
  84. package/dist/research/adapters/firecrawl-adapter.js +137 -0
  85. package/dist/research/adapters/firecrawl-adapter.js.map +1 -0
  86. package/dist/research/adapters/index.d.ts +3 -0
  87. package/dist/research/adapters/index.js +2 -0
  88. package/dist/research/adapters/index.js.map +1 -1
  89. package/dist/research/adapters/playwright-adapter.d.ts +54 -0
  90. package/dist/research/adapters/playwright-adapter.js +130 -0
  91. package/dist/research/adapters/playwright-adapter.js.map +1 -0
  92. package/dist/research/research-orchestrator.d.ts +3 -0
  93. package/dist/research/research-orchestrator.js +19 -1
  94. package/dist/research/research-orchestrator.js.map +1 -1
  95. package/dist/techradar/__tests__/techradar-engine.test.d.ts +1 -0
  96. package/dist/techradar/__tests__/techradar-engine.test.js +246 -0
  97. package/dist/techradar/__tests__/techradar-engine.test.js.map +1 -0
  98. package/dist/techradar/daily-digest.d.ts +18 -0
  99. package/dist/techradar/daily-digest.js +100 -0
  100. package/dist/techradar/daily-digest.js.map +1 -0
  101. package/dist/techradar/index.d.ts +5 -0
  102. package/dist/techradar/index.js +5 -0
  103. package/dist/techradar/index.js.map +1 -0
  104. package/dist/techradar/relevance-scorer.d.ts +29 -0
  105. package/dist/techradar/relevance-scorer.js +139 -0
  106. package/dist/techradar/relevance-scorer.js.map +1 -0
  107. package/dist/techradar/repo-watcher.d.ts +24 -0
  108. package/dist/techradar/repo-watcher.js +87 -0
  109. package/dist/techradar/repo-watcher.js.map +1 -0
  110. package/dist/techradar/techradar-engine.d.ts +69 -0
  111. package/dist/techradar/techradar-engine.js +382 -0
  112. package/dist/techradar/techradar-engine.js.map +1 -0
  113. package/dist/techradar/types.d.ts +87 -0
  114. package/dist/techradar/types.js +5 -0
  115. package/dist/techradar/types.js.map +1 -0
  116. package/dist/watchdog/__tests__/watchdog-service.test.d.ts +1 -0
  117. package/dist/watchdog/__tests__/watchdog-service.test.js +113 -0
  118. package/dist/watchdog/__tests__/watchdog-service.test.js.map +1 -0
  119. package/dist/watchdog/watchdog-service.d.ts +60 -0
  120. package/dist/watchdog/watchdog-service.js +275 -0
  121. package/dist/watchdog/watchdog-service.js.map +1 -0
  122. package/dist/watchdog/windows-service.d.ts +39 -0
  123. package/dist/watchdog/windows-service.js +179 -0
  124. package/dist/watchdog/windows-service.js.map +1 -0
  125. package/package.json +20 -2
package/dist/index.d.ts CHANGED
@@ -53,6 +53,15 @@ export { CrossBrainSubscriptionManager } from './cross-brain/subscription.js';
53
53
  export type { EventSubscription } from './cross-brain/subscription.js';
54
54
  export { CrossBrainCorrelator } from './cross-brain/correlator.js';
55
55
  export type { CorrelatorEvent, Correlation, EcosystemHealth, CorrelatorConfig } from './cross-brain/correlator.js';
56
+ export { BorgSyncEngine } from './cross-brain/borg-sync-engine.js';
57
+ export type { BorgDataProvider } from './cross-brain/borg-sync-engine.js';
58
+ export { DEFAULT_BORG_CONFIG } from './cross-brain/borg-types.js';
59
+ export type { BorgConfig, SyncPacket, SyncItem, SyncHistoryEntry } from './cross-brain/borg-types.js';
60
+ export { WatchdogService, createDefaultWatchdogConfig } from './watchdog/watchdog-service.js';
61
+ export type { WatchdogConfig, DaemonConfig, DaemonStatus } from './watchdog/watchdog-service.js';
62
+ export { WindowsServiceManager } from './watchdog/windows-service.js';
63
+ export { PluginRegistry } from './plugin/plugin-registry.js';
64
+ export type { BrainPlugin, PluginContext, PluginToolDefinition, PluginRouteDefinition, PluginManifest, PluginRecord } from './plugin/types.js';
56
65
  export { EcosystemService } from './ecosystem/service.js';
57
66
  export type { BrainStatus, EcosystemStatus, AggregatedAnalytics } from './ecosystem/service.js';
58
67
  export { WebhookService, runWebhookMigration } from './webhooks/service.js';
@@ -150,6 +159,9 @@ export type { EmotionalModelConfig, EmotionalDataSources, EmotionDimension, Mood
150
159
  export { DataScout, runDataScoutMigration, GitHubTrendingAdapter, NpmStatsAdapter, HackerNewsAdapter } from './research/data-scout.js';
151
160
  export type { ScoutDiscovery, ScoutAdapter, DataScoutStatus } from './research/data-scout.js';
152
161
  export { BraveSearchAdapter, JinaReaderAdapter } from './research/adapters/web-research-adapter.js';
162
+ export { PlaywrightAdapter } from './research/adapters/playwright-adapter.js';
163
+ export { FirecrawlAdapter } from './research/adapters/firecrawl-adapter.js';
164
+ export type { FirecrawlConfig } from './research/adapters/firecrawl-adapter.js';
153
165
  export { ResearchMissionEngine, runMissionMigration } from './missions/mission-engine.js';
154
166
  export type { Mission, MissionPhase, MissionSource, MissionStatus, MissionDepth } from './missions/mission-engine.js';
155
167
  export { UnifiedDashboardServer } from './unified/index.js';
@@ -163,10 +175,15 @@ export type { ConceptAbstractionConfig, ConceptDataSources, AbstractConcept, Con
163
175
  export { PeerNetwork } from './peer-network/index.js';
164
176
  export type { PeerInfo, PeerNetworkConfig, PeerNetworkStatus } from './peer-network/index.js';
165
177
  export { LLMService, runLLMServiceMigration } from './llm/index.js';
166
- export type { LLMServiceConfig, LLMResponse, LLMUsageStats, PromptTemplate } from './llm/index.js';
178
+ export { TaskRouter, AnthropicProvider, OllamaProvider, OllamaEmbeddingProvider } from './llm/index.js';
179
+ export type { LLMServiceConfig, LLMResponse, LLMUsageStats, PromptTemplate, ProviderInfo, LLMProvider, LLMMessage, LLMCallOptions, LLMProviderResponse, RoutingTier, AnthropicProviderConfig, OllamaProviderConfig, OllamaStatus, OllamaModelInfo, OllamaRunningModel, OllamaEmbeddingConfig, } from './llm/index.js';
167
180
  export { SignalScanner, runScannerMigration } from './scanner/index.js';
168
181
  export { GitHubCollector } from './scanner/index.js';
169
182
  export { HnCollector } from './scanner/index.js';
170
183
  export { CryptoCollector } from './scanner/index.js';
171
184
  export { scoreRepo, classifyLevel, classifyWithHysteresis, classifyPhase, scoreCrypto } from './scanner/index.js';
172
185
  export type { ScannerConfig, ScannedRepo, DailyStats, HnMention, CryptoToken, ScanResult, ScannerStatus, SignalLevel, RepoPhase, ScoreBreakdown, GitHubSearchResult, GitHubRepo, HnSearchResult, HnHit, CoinGeckoMarket, CoinGeckoTrending, } from './scanner/index.js';
186
+ export { TechRadarEngine, runTechRadarMigration, RepoWatcher, RelevanceScorer, DigestGenerator } from './techradar/index.js';
187
+ export type { TechRadarConfig, TechRadarEntry, TechRadarScanResult, TechRadarSource, TechRadarCategory, TechRadarRing, TechRadarAction, WatchedRepo, RepoRelease, DailyDigest, DigestEntry, DigestOpportunity, DigestActionItem, } from './techradar/index.js';
188
+ export { NotificationService, runNotificationMigration, DiscordProvider, TelegramProvider, EmailProvider } from './notifications/index.js';
189
+ export type { NotificationProvider, Notification, NotificationResult, NotificationPriority, NotificationEvent, NotificationProviderStatus, DiscordProviderConfig, TelegramProviderConfig, EmailProviderConfig, } from './notifications/index.js';
package/dist/index.js CHANGED
@@ -44,6 +44,13 @@ export { CrossBrainClient } from './cross-brain/client.js';
44
44
  export { CrossBrainNotifier } from './cross-brain/notifications.js';
45
45
  export { CrossBrainSubscriptionManager } from './cross-brain/subscription.js';
46
46
  export { CrossBrainCorrelator } from './cross-brain/correlator.js';
47
+ export { BorgSyncEngine } from './cross-brain/borg-sync-engine.js';
48
+ export { DEFAULT_BORG_CONFIG } from './cross-brain/borg-types.js';
49
+ // ── Watchdog ──────────────────────────────────────────────
50
+ export { WatchdogService, createDefaultWatchdogConfig } from './watchdog/watchdog-service.js';
51
+ export { WindowsServiceManager } from './watchdog/windows-service.js';
52
+ // ── Plugin SDK ────────────────────────────────────────────
53
+ export { PluginRegistry } from './plugin/plugin-registry.js';
47
54
  // ── Ecosystem ──────────────────────────────────────────────
48
55
  export { EcosystemService } from './ecosystem/service.js';
49
56
  // ── Webhooks ──────────────────────────────────────────────
@@ -133,6 +140,8 @@ export { EmotionalModel, runEmotionalMigration } from './emotional/index.js';
133
140
  // ── DataScout ──────────────────────────────────────────
134
141
  export { DataScout, runDataScoutMigration, GitHubTrendingAdapter, NpmStatsAdapter, HackerNewsAdapter } from './research/data-scout.js';
135
142
  export { BraveSearchAdapter, JinaReaderAdapter } from './research/adapters/web-research-adapter.js';
143
+ export { PlaywrightAdapter } from './research/adapters/playwright-adapter.js';
144
+ export { FirecrawlAdapter } from './research/adapters/firecrawl-adapter.js';
136
145
  // ── Research Missions ─────────────────────────────────
137
146
  export { ResearchMissionEngine, runMissionMigration } from './missions/mission-engine.js';
138
147
  // ── Unified Dashboard ──────────────────────────────────
@@ -147,10 +156,15 @@ export { ConceptAbstraction, runConceptAbstractionMigration } from './concept-ab
147
156
  export { PeerNetwork } from './peer-network/index.js';
148
157
  // ── LLM Service ──────────────────────────────────────────
149
158
  export { LLMService, runLLMServiceMigration } from './llm/index.js';
159
+ export { TaskRouter, AnthropicProvider, OllamaProvider, OllamaEmbeddingProvider } from './llm/index.js';
150
160
  // ── Scanner ──────────────────────────────────────────────
151
161
  export { SignalScanner, runScannerMigration } from './scanner/index.js';
152
162
  export { GitHubCollector } from './scanner/index.js';
153
163
  export { HnCollector } from './scanner/index.js';
154
164
  export { CryptoCollector } from './scanner/index.js';
155
165
  export { scoreRepo, classifyLevel, classifyWithHysteresis, classifyPhase, scoreCrypto } from './scanner/index.js';
166
+ // TechRadar
167
+ export { TechRadarEngine, runTechRadarMigration, RepoWatcher, RelevanceScorer, DigestGenerator } from './techradar/index.js';
168
+ // Notifications
169
+ export { NotificationService, runNotificationMigration, DiscordProvider, TelegramProvider, EmailProvider } from './notifications/index.js';
156
170
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { AnthropicProvider } from '../anthropic-provider.js';
3
+ vi.mock('../../utils/logger.js', () => ({
4
+ getLogger: () => ({
5
+ info: vi.fn(),
6
+ warn: vi.fn(),
7
+ error: vi.fn(),
8
+ debug: vi.fn(),
9
+ }),
10
+ }));
11
+ describe('AnthropicProvider', () => {
12
+ let fetchMock;
13
+ beforeEach(() => {
14
+ fetchMock = vi.fn().mockResolvedValue({
15
+ ok: true,
16
+ json: async () => ({
17
+ content: [{ type: 'text', text: 'Claude response' }],
18
+ usage: { input_tokens: 100, output_tokens: 50 },
19
+ }),
20
+ });
21
+ vi.stubGlobal('fetch', fetchMock);
22
+ });
23
+ afterEach(() => {
24
+ vi.unstubAllGlobals();
25
+ });
26
+ describe('isAvailable', () => {
27
+ it('returns false without API key', async () => {
28
+ const provider = new AnthropicProvider({ apiKey: undefined });
29
+ // Force no env var
30
+ const original = process.env.ANTHROPIC_API_KEY;
31
+ delete process.env.ANTHROPIC_API_KEY;
32
+ const p = new AnthropicProvider({});
33
+ expect(await p.isAvailable()).toBe(false);
34
+ if (original !== undefined)
35
+ process.env.ANTHROPIC_API_KEY = original;
36
+ });
37
+ it('returns true with API key', async () => {
38
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
39
+ expect(await provider.isAvailable()).toBe(true);
40
+ });
41
+ });
42
+ describe('properties', () => {
43
+ it('has correct name and cost tier', () => {
44
+ const provider = new AnthropicProvider();
45
+ expect(provider.name).toBe('anthropic');
46
+ expect(provider.costTier).toBe('expensive');
47
+ });
48
+ it('has correct capabilities', () => {
49
+ const provider = new AnthropicProvider();
50
+ expect(provider.capabilities.chat).toBe(true);
51
+ expect(provider.capabilities.generate).toBe(true);
52
+ expect(provider.capabilities.embed).toBe(false);
53
+ expect(provider.capabilities.reasoning).toBe(false);
54
+ });
55
+ });
56
+ describe('chat', () => {
57
+ it('sends correct request to Anthropic API', async () => {
58
+ const provider = new AnthropicProvider({ apiKey: 'test-key', model: 'claude-test' });
59
+ await provider.chat([
60
+ { role: 'system', content: 'You are helpful' },
61
+ { role: 'user', content: 'Hello' },
62
+ ]);
63
+ expect(fetchMock).toHaveBeenCalledTimes(1);
64
+ const [url, opts] = fetchMock.mock.calls[0];
65
+ expect(url).toBe('https://api.anthropic.com/v1/messages');
66
+ expect(opts.headers['x-api-key']).toBe('test-key');
67
+ expect(opts.headers['anthropic-version']).toBe('2023-06-01');
68
+ const body = JSON.parse(opts.body);
69
+ expect(body.model).toBe('claude-test');
70
+ expect(body.system).toBe('You are helpful');
71
+ expect(body.messages).toEqual([{ role: 'user', content: 'Hello' }]);
72
+ });
73
+ it('returns parsed response', async () => {
74
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
75
+ const result = await provider.chat([{ role: 'user', content: 'test' }]);
76
+ expect(result.text).toBe('Claude response');
77
+ expect(result.inputTokens).toBe(100);
78
+ expect(result.outputTokens).toBe(50);
79
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
80
+ });
81
+ it('passes maxTokens and temperature', async () => {
82
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
83
+ await provider.chat([{ role: 'user', content: 'test' }], { maxTokens: 512, temperature: 0.7 });
84
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
85
+ expect(body.max_tokens).toBe(512);
86
+ expect(body.temperature).toBe(0.7);
87
+ });
88
+ it('throws on API error', async () => {
89
+ fetchMock.mockResolvedValueOnce({
90
+ ok: false,
91
+ status: 429,
92
+ text: async () => 'Rate limited',
93
+ });
94
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
95
+ await expect(provider.chat([{ role: 'user', content: 'test' }]))
96
+ .rejects.toThrow('Anthropic API error (429)');
97
+ });
98
+ it('throws without API key', async () => {
99
+ const provider = new AnthropicProvider({ apiKey: undefined });
100
+ // Hack: clear the key
101
+ provider.apiKey = null;
102
+ await expect(provider.chat([{ role: 'user', content: 'test' }]))
103
+ .rejects.toThrow('No API key');
104
+ });
105
+ });
106
+ describe('generate', () => {
107
+ it('delegates to chat and returns text', async () => {
108
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
109
+ const result = await provider.generate('test prompt');
110
+ expect(result).toBe('Claude response');
111
+ });
112
+ });
113
+ describe('embed', () => {
114
+ it('returns empty array (not supported)', async () => {
115
+ const provider = new AnthropicProvider({ apiKey: 'test-key' });
116
+ const result = await provider.embed('test');
117
+ expect(result).toEqual([]);
118
+ });
119
+ });
120
+ });
121
+ //# sourceMappingURL=anthropic-provider.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-provider.test.js","sourceRoot":"","sources":["../../../src/llm/__tests__/anthropic-provider.test.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,23 @@ import { LLMService, runLLMServiceMigration } from '../llm-service.js';
12
12
  function createTestDb() {
13
13
  return new Database(':memory:');
14
14
  }
15
+ function createMockProvider(overrides = {}) {
16
+ return {
17
+ name: overrides.name ?? 'mock-local',
18
+ costTier: overrides.costTier ?? 'free',
19
+ capabilities: overrides.capabilities ?? { chat: true, generate: true, embed: true, reasoning: false },
20
+ isAvailable: overrides.isAvailable ?? (async () => true),
21
+ chat: overrides.chat ?? (async () => ({
22
+ text: 'Mock local response',
23
+ inputTokens: 20,
24
+ outputTokens: 10,
25
+ model: 'mock-local-model',
26
+ durationMs: 30,
27
+ })),
28
+ generate: overrides.generate ?? (async () => 'mock generated'),
29
+ embed: overrides.embed ?? (async () => [0.1, 0.2, 0.3]),
30
+ };
31
+ }
15
32
  describe('LLMService', () => {
16
33
  let db;
17
34
  beforeEach(() => {
@@ -46,27 +63,49 @@ describe('LLMService', () => {
46
63
  describe('constructor', () => {
47
64
  it('constructs without API key', () => {
48
65
  const svc = new LLMService(db, {});
49
- expect(svc.isAvailable()).toBe(false);
66
+ // Still "available" because Anthropic provider is registered (even without key)
67
+ expect(svc.getProviders()).toHaveLength(1);
50
68
  });
51
69
  it('constructs with API key', () => {
52
70
  const svc = new LLMService(db, { apiKey: 'test-key-123' });
53
71
  expect(svc.isAvailable()).toBe(true);
54
72
  });
55
- it('uses env var if no config key', () => {
56
- const original = process.env.ANTHROPIC_API_KEY;
57
- process.env.ANTHROPIC_API_KEY = 'env-key-456';
58
- try {
59
- const svc = new LLMService(db, {});
60
- expect(svc.isAvailable()).toBe(true);
61
- }
62
- finally {
63
- if (original !== undefined) {
64
- process.env.ANTHROPIC_API_KEY = original;
65
- }
66
- else {
67
- delete process.env.ANTHROPIC_API_KEY;
68
- }
69
- }
73
+ it('auto-registers Anthropic provider', () => {
74
+ const svc = new LLMService(db, { apiKey: 'test-key' });
75
+ const providers = svc.getProviders();
76
+ expect(providers).toHaveLength(1);
77
+ expect(providers[0].name).toBe('anthropic');
78
+ });
79
+ });
80
+ // ── Provider Management ────────────────────────────
81
+ describe('registerProvider', () => {
82
+ it('adds a new provider', () => {
83
+ const svc = new LLMService(db, { apiKey: 'test-key' });
84
+ svc.registerProvider(createMockProvider());
85
+ expect(svc.getProviders()).toHaveLength(2);
86
+ });
87
+ it('prevents duplicate registration', () => {
88
+ const svc = new LLMService(db, { apiKey: 'test-key' });
89
+ const mock = createMockProvider();
90
+ svc.registerProvider(mock);
91
+ svc.registerProvider(mock); // duplicate
92
+ expect(svc.getProviders()).toHaveLength(2); // anthropic + mock
93
+ });
94
+ it('removeProvider works', () => {
95
+ const svc = new LLMService(db, { apiKey: 'test-key' });
96
+ svc.registerProvider(createMockProvider());
97
+ expect(svc.getProviders()).toHaveLength(2);
98
+ svc.removeProvider('mock-local');
99
+ expect(svc.getProviders()).toHaveLength(1);
100
+ });
101
+ });
102
+ describe('getProviderStatus', () => {
103
+ it('returns async availability', async () => {
104
+ const svc = new LLMService(db, { apiKey: 'test-key' });
105
+ svc.registerProvider(createMockProvider());
106
+ const status = await svc.getProviderStatus();
107
+ expect(status).toHaveLength(2);
108
+ expect(status.find(p => p.name === 'mock-local')?.available).toBe(true);
70
109
  });
71
110
  });
72
111
  // ── getStats ───────────────────────────────────────
@@ -100,17 +139,26 @@ describe('LLMService', () => {
100
139
  const svc = new LLMService(db, { model: 'claude-test-model' });
101
140
  expect(svc.getStats().model).toBe('claude-test-model');
102
141
  });
142
+ it('includes provider info', () => {
143
+ const svc = new LLMService(db, { apiKey: 'test-key' });
144
+ svc.registerProvider(createMockProvider());
145
+ const stats = svc.getStats();
146
+ expect(stats.providers).toHaveLength(2);
147
+ expect(stats.providers.map(p => p.name)).toContain('anthropic');
148
+ expect(stats.providers.map(p => p.name)).toContain('mock-local');
149
+ });
103
150
  });
104
151
  // ── call (without API key) ─────────────────────────
105
152
  describe('call', () => {
106
- it('returns null when no API key', async () => {
153
+ it('returns null when no provider available', async () => {
154
+ // Create service without API key and no working providers
107
155
  const svc = new LLMService(db, {});
108
156
  const result = await svc.call('explain', 'test message');
109
157
  expect(result).toBeNull();
110
158
  });
111
159
  });
112
- // ── call (with mocked fetch) ───────────────────────
113
- describe('call with mocked API', () => {
160
+ // ── call (with mocked fetch — Anthropic) ───────────
161
+ describe('call with mocked Anthropic', () => {
114
162
  let svc;
115
163
  let fetchMock;
116
164
  beforeEach(() => {
@@ -140,20 +188,7 @@ describe('LLMService', () => {
140
188
  expect(result.inputTokens).toBe(100);
141
189
  expect(result.outputTokens).toBe(50);
142
190
  expect(result.cached).toBe(false);
143
- });
144
- it('sends correct headers', async () => {
145
- await svc.call('explain', 'test');
146
- expect(fetchMock).toHaveBeenCalledTimes(1);
147
- const callArgs = fetchMock.mock.calls[0];
148
- expect(callArgs[0]).toBe('https://api.anthropic.com/v1/messages');
149
- expect(callArgs[1].headers['x-api-key']).toBe('test-key');
150
- expect(callArgs[1].headers['anthropic-version']).toBe('2023-06-01');
151
- });
152
- it('sends template system prompt', async () => {
153
- await svc.call('creative_hypothesis', 'test');
154
- const body = JSON.parse(fetchMock.mock.calls[0][1].body);
155
- expect(body.system).toContain('creative research hypothesis generator');
156
- expect(body.messages[0].content).toBe('test');
191
+ expect(result.provider).toBe('anthropic');
157
192
  });
158
193
  it('updates stats after call', async () => {
159
194
  await svc.call('explain', 'test');
@@ -215,6 +250,7 @@ describe('LLMService', () => {
215
250
  expect(rows[0].template).toBe('explain');
216
251
  expect(rows[0].total_tokens).toBe(150);
217
252
  expect(rows[0].cached).toBe(0);
253
+ expect(rows[0].provider).toBe('anthropic');
218
254
  });
219
255
  it('records cached hits to database too', async () => {
220
256
  await svc.call('explain', 'test caching');
@@ -231,6 +267,112 @@ describe('LLMService', () => {
231
267
  expect(body.temperature).toBe(0.3);
232
268
  });
233
269
  });
270
+ // ── Multi-Provider Routing ─────────────────────────
271
+ describe('multi-provider routing', () => {
272
+ let svc;
273
+ let fetchMock;
274
+ beforeEach(() => {
275
+ fetchMock = vi.fn();
276
+ vi.stubGlobal('fetch', fetchMock);
277
+ });
278
+ afterEach(() => {
279
+ vi.unstubAllGlobals();
280
+ });
281
+ it('routes simple tasks to local provider', async () => {
282
+ svc = new LLMService(db, { apiKey: 'test-key', preferLocal: true });
283
+ svc.registerProvider(createMockProvider());
284
+ const result = await svc.call('summarize', 'summarize this');
285
+ expect(result).not.toBeNull();
286
+ expect(result.provider).toBe('mock-local');
287
+ expect(result.text).toBe('Mock local response');
288
+ // fetch should NOT have been called (local provider used)
289
+ expect(fetchMock).not.toHaveBeenCalled();
290
+ });
291
+ it('routes complex tasks to cloud provider', async () => {
292
+ fetchMock.mockResolvedValue({
293
+ ok: true,
294
+ json: async () => ({
295
+ content: [{ type: 'text', text: 'Claude response' }],
296
+ usage: { input_tokens: 100, output_tokens: 50 },
297
+ }),
298
+ });
299
+ svc = new LLMService(db, { apiKey: 'test-key', preferLocal: true });
300
+ svc.registerProvider(createMockProvider());
301
+ const result = await svc.call('synthesize_debate', 'debate this');
302
+ expect(result).not.toBeNull();
303
+ expect(result.provider).toBe('anthropic');
304
+ });
305
+ it('falls back to cloud when local provider fails', async () => {
306
+ fetchMock.mockResolvedValue({
307
+ ok: true,
308
+ json: async () => ({
309
+ content: [{ type: 'text', text: 'Claude fallback' }],
310
+ usage: { input_tokens: 50, output_tokens: 25 },
311
+ }),
312
+ });
313
+ const failingLocal = createMockProvider({
314
+ name: 'failing-local',
315
+ costTier: 'free',
316
+ chat: async () => { throw new Error('Local model crashed'); },
317
+ });
318
+ svc = new LLMService(db, { apiKey: 'test-key', preferLocal: true });
319
+ svc.registerProvider(failingLocal);
320
+ const result = await svc.call('summarize', 'test');
321
+ expect(result).not.toBeNull();
322
+ expect(result.provider).toBe('anthropic');
323
+ expect(result.text).toBe('Claude fallback');
324
+ expect(svc.getStats().errors).toBe(1); // failing local counted as error
325
+ });
326
+ it('returns null when all providers fail', async () => {
327
+ fetchMock.mockRejectedValue(new Error('Network error'));
328
+ const failingLocal = createMockProvider({
329
+ name: 'failing-local',
330
+ chat: async () => { throw new Error('crashed'); },
331
+ });
332
+ svc = new LLMService(db, { apiKey: 'test-key' });
333
+ svc.registerProvider(failingLocal);
334
+ const result = await svc.call('summarize', 'test');
335
+ expect(result).toBeNull();
336
+ });
337
+ it('forces specific provider when requested', async () => {
338
+ svc = new LLMService(db, { apiKey: 'test-key' });
339
+ svc.registerProvider(createMockProvider());
340
+ const result = await svc.call('explain', 'test', { provider: 'mock-local' });
341
+ expect(result).not.toBeNull();
342
+ expect(result.provider).toBe('mock-local');
343
+ });
344
+ it('skips unavailable providers', async () => {
345
+ fetchMock.mockResolvedValue({
346
+ ok: true,
347
+ json: async () => ({
348
+ content: [{ type: 'text', text: 'Cloud response' }],
349
+ usage: { input_tokens: 10, output_tokens: 10 },
350
+ }),
351
+ });
352
+ const unavailableLocal = createMockProvider({
353
+ name: 'offline-local',
354
+ isAvailable: async () => false,
355
+ });
356
+ svc = new LLMService(db, { apiKey: 'test-key' });
357
+ svc.registerProvider(unavailableLocal);
358
+ const result = await svc.call('summarize', 'test');
359
+ expect(result).not.toBeNull();
360
+ expect(result.provider).toBe('anthropic');
361
+ });
362
+ it('does not rate-limit free providers', async () => {
363
+ svc = new LLMService(db, {
364
+ apiKey: 'test-key',
365
+ maxCallsPerHour: 2, // Very low limit
366
+ });
367
+ svc.registerProvider(createMockProvider());
368
+ // Make 5 calls to local provider — should all succeed
369
+ for (let i = 0; i < 5; i++) {
370
+ const r = await svc.call('summarize', `q${i}`);
371
+ expect(r).not.toBeNull();
372
+ expect(r.provider).toBe('mock-local');
373
+ }
374
+ });
375
+ });
234
376
  // ── getUsageHistory ────────────────────────────────
235
377
  describe('getUsageHistory', () => {
236
378
  it('returns empty when no data', () => {
@@ -240,8 +382,8 @@ describe('LLMService', () => {
240
382
  it('groups by hour after manual insert', () => {
241
383
  const svc = new LLMService(db, {});
242
384
  // Insert test data
243
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))").run('hash1', 'explain', 'test-model', 100, 50, 150, 500, 0);
244
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))").run('hash2', 'ask', 'test-model', 80, 40, 120, 300, 0);
385
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))").run('hash1', 'explain', 'test-model', 100, 50, 150, 500, 0, 'anthropic');
386
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))").run('hash2', 'ask', 'test-model', 80, 40, 120, 300, 0, 'ollama');
245
387
  const history = svc.getUsageHistory(24);
246
388
  expect(history).toHaveLength(1); // Both in same hour
247
389
  expect(history[0].calls).toBe(2);
@@ -256,11 +398,11 @@ describe('LLMService', () => {
256
398
  });
257
399
  it('groups by template', () => {
258
400
  const svc = new LLMService(db, {});
259
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run('h1', 'explain', 'model', 100, 50, 150, 500, 0);
260
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run('h2', 'explain', 'model', 200, 80, 280, 600, 0);
261
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run('h3', 'ask', 'model', 50, 30, 80, 200, 0);
401
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run('h1', 'explain', 'model', 100, 50, 150, 500, 0, 'anthropic');
402
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run('h2', 'explain', 'model', 200, 80, 280, 600, 0, 'anthropic');
403
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run('h3', 'ask', 'model', 50, 30, 80, 200, 0, 'ollama');
262
404
  // Cached entries should be excluded
263
- db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run('h4', 'explain', 'model', 100, 50, 150, 0, 1);
405
+ db.prepare("INSERT INTO llm_usage (prompt_hash, template, model, input_tokens, output_tokens, total_tokens, duration_ms, cached, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run('h4', 'explain', 'model', 100, 50, 150, 0, 1, 'anthropic');
264
406
  const result = svc.getUsageByTemplate();
265
407
  expect(result).toHaveLength(2);
266
408
  const explain = result.find(r => r.template === 'explain');
@@ -358,7 +500,6 @@ describe('LLMService', () => {
358
500
  expect(result).not.toBeNull();
359
501
  expect(result.text).toBe('ok');
360
502
  }
361
- expect(fetchMock).toHaveBeenCalledTimes(templates.length);
362
503
  vi.unstubAllGlobals();
363
504
  });
364
505
  });