adaptive-memory-multi-model-router 1.3.1 → 1.4.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/README.md +33 -87
- package/dist/integrations/oauth.js +26 -0
- package/dist/memory/autoFetch.js +59 -0
- package/dist/memory/autoFetch.ts +109 -0
- package/dist/memory/memoryTree.js +81 -0
- package/dist/memory/obsidianVault.js +26 -0
- package/dist/utils/enhancedCompression.js +180 -0
- package/package.json +81 -147
- package/src/integrations/oauth.ts +280 -0
- package/src/memory/autoFetch.ts +109 -0
- package/src/memory/memoryTree.ts +242 -0
- package/src/memory/obsidianVault.ts +224 -0
- package/package.json.tmp +0 -0
package/package.json
CHANGED
|
@@ -1,174 +1,108 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adaptive-memory-multi-model-router",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"shortName": "A3M Router",
|
|
5
|
+
"displayName": "A3M Router - Adaptive Memory Multi-Model Router",
|
|
6
|
+
"description": "A3M Router - Adaptive Memory Multi-Model Router with learned routing (RouteLLM), prefix caching (RadixAttention), speculative decoding (Medusa), TokenJuice-style compression. 14 LLM providers, 10 integrations, Python bindings. 20x more adaptable for ML/AI developers.",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"a3m-router": "dist/cli.js"
|
|
9
|
+
"a3m-router": "dist/cli.js",
|
|
10
|
+
"adaptive-memory-multi-model-router": "dist/cli.js"
|
|
10
11
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./providers": "./dist/providers/registry.js",
|
|
15
|
+
"./memory": "./dist/memory/memoryTree.js",
|
|
16
|
+
"./cache": "./dist/cache/prefixCache.js",
|
|
17
|
+
"./compression": "./dist/utils/enhancedCompression.js",
|
|
18
|
+
"./autofetch": "./dist/memory/autoFetch.js",
|
|
19
|
+
"./vault": "./dist/memory/obsidianVault.js",
|
|
20
|
+
"./oauth": "./dist/integrations/oauth.js",
|
|
21
|
+
"./utils": "./dist/utils/tokenUtils.js",
|
|
22
|
+
"./cost": "./dist/cost/costTracker.js",
|
|
23
|
+
"./integrations": "./dist/integrations/index.js"
|
|
17
24
|
},
|
|
18
25
|
"keywords": [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"llm-orchestration",
|
|
29
|
-
"llm",
|
|
30
|
-
"agent-orchestration",
|
|
31
|
-
"multi-agent",
|
|
32
|
-
"agent",
|
|
33
|
-
"parallel",
|
|
34
|
-
"streaming",
|
|
35
|
-
"cost-tracking",
|
|
36
|
-
"cost-optimization",
|
|
37
|
-
"cache",
|
|
26
|
+
"a3m",
|
|
27
|
+
"a3m-router",
|
|
28
|
+
"adaptive",
|
|
29
|
+
"adaptive-routing",
|
|
30
|
+
"agent-discoverable",
|
|
31
|
+
"ai-native",
|
|
32
|
+
"ai-agents",
|
|
33
|
+
"anthropic",
|
|
34
|
+
"batch-processing",
|
|
38
35
|
"caching",
|
|
36
|
+
"cerberas",
|
|
39
37
|
"circuit-breaker",
|
|
40
|
-
"retry",
|
|
41
|
-
"exponential-backoff",
|
|
42
|
-
"mcts",
|
|
43
|
-
"monte-carlo-tree-search",
|
|
44
|
-
"workflow-optimization",
|
|
45
|
-
"hierarchical-planning",
|
|
46
|
-
"halo",
|
|
47
|
-
"episodic-memory",
|
|
48
|
-
"semantic-memory",
|
|
49
|
-
"agent-memory",
|
|
50
|
-
"python",
|
|
51
|
-
"python-bindings",
|
|
52
|
-
"pypi",
|
|
53
|
-
"langchain",
|
|
54
|
-
"llamaindex",
|
|
55
|
-
"llama-index",
|
|
56
|
-
"autogen",
|
|
57
|
-
"crewai",
|
|
58
|
-
"huggingface",
|
|
59
|
-
"transformers",
|
|
60
|
-
"agent-codegen",
|
|
61
|
-
"ai-coding",
|
|
62
|
-
"openai",
|
|
63
|
-
"anthropic",
|
|
64
|
-
"google",
|
|
65
|
-
"groq",
|
|
66
|
-
"cerebras",
|
|
67
|
-
"mistral",
|
|
68
|
-
"xai",
|
|
69
|
-
"zai",
|
|
70
38
|
"claude",
|
|
71
|
-
"
|
|
39
|
+
"claude-router",
|
|
40
|
+
"cohere",
|
|
41
|
+
"context-aware",
|
|
42
|
+
"cost-optimization",
|
|
43
|
+
"deepseek",
|
|
44
|
+
"deepseek-chat",
|
|
45
|
+
"embeddable",
|
|
46
|
+
"fireworks",
|
|
72
47
|
"gemini",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
48
|
+
"github-actions",
|
|
49
|
+
"gpt",
|
|
50
|
+
"gpt-4",
|
|
51
|
+
"gpt-4o",
|
|
52
|
+
"groq",
|
|
53
|
+
"huggingface",
|
|
54
|
+
"langchain",
|
|
55
|
+
"llm",
|
|
56
|
+
"llm-fusion",
|
|
57
|
+
"llm-optimization",
|
|
76
58
|
"llm-router",
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"memory-based-router",
|
|
80
|
-
"memory-based-llm-router",
|
|
81
|
-
"multi-llm-router",
|
|
82
|
-
"llm-memory-router",
|
|
83
|
-
"adaptive-router",
|
|
84
|
-
"adaptive-llm-router",
|
|
85
|
-
"intelligent-router",
|
|
86
|
-
"intelligent-llm-router",
|
|
87
|
-
"learning-router",
|
|
88
|
-
"contextual-router",
|
|
89
|
-
"context-aware-router",
|
|
90
|
-
"task-aware-router",
|
|
91
|
-
"memory-augmented",
|
|
92
|
-
"memory-augmented-llm",
|
|
93
|
-
"episodic-memory-router",
|
|
94
|
-
"semantic-memory-router",
|
|
95
|
-
"task-memory",
|
|
96
|
-
"cross-context-memory",
|
|
97
|
-
"token-compression",
|
|
98
|
-
"context-compression",
|
|
99
|
-
"ison-format",
|
|
100
|
-
"message-truncation",
|
|
101
|
-
"context-management",
|
|
59
|
+
"llm-routing",
|
|
60
|
+
"llmlingua",
|
|
102
61
|
"local-llm",
|
|
62
|
+
"memory",
|
|
63
|
+
"memory-based",
|
|
64
|
+
"memory-tree",
|
|
65
|
+
"mistral",
|
|
66
|
+
"mixtral",
|
|
67
|
+
"mllm",
|
|
68
|
+
"model-router",
|
|
69
|
+
"multi-model",
|
|
70
|
+
"multi-model-router",
|
|
103
71
|
"ollama",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"privacy-llm",
|
|
108
|
-
"batch-processing",
|
|
109
|
-
"batch-execution",
|
|
110
|
-
"priority-queue",
|
|
111
|
-
"rate-limiting",
|
|
112
|
-
"token-counting",
|
|
113
|
-
"cost-estimation",
|
|
114
|
-
"cost-prediction",
|
|
115
|
-
"parallel-execution",
|
|
116
|
-
"multi-provider",
|
|
117
|
-
"fallback-chain",
|
|
118
|
-
"intelligent-failover",
|
|
119
|
-
"kv-cache",
|
|
120
|
-
"routellm",
|
|
72
|
+
"openai",
|
|
73
|
+
"openrouter",
|
|
74
|
+
"perplexity",
|
|
121
75
|
"prefix-caching",
|
|
122
|
-
"
|
|
76
|
+
"provider-router",
|
|
77
|
+
"python-bindings",
|
|
78
|
+
"quantization",
|
|
79
|
+
"radixattention",
|
|
80
|
+
"routellm",
|
|
81
|
+
"self-hosting",
|
|
123
82
|
"speculative-decoding",
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"llmlingua",
|
|
130
|
-
"streamingllm",
|
|
131
|
-
"multimodel-orchestration",
|
|
132
|
-
"multi-agent-debate",
|
|
133
|
-
"self-consistency",
|
|
134
|
-
"tensor-parallelism",
|
|
135
|
-
"continuous-batching",
|
|
136
|
-
"arxiv",
|
|
137
|
-
"research-backed",
|
|
138
|
-
"icml",
|
|
139
|
-
"neurips",
|
|
140
|
-
"iclr"
|
|
83
|
+
"token-compression",
|
|
84
|
+
"tokenjuice",
|
|
85
|
+
"tmlpd",
|
|
86
|
+
"token-optimization",
|
|
87
|
+
"vllm"
|
|
141
88
|
],
|
|
142
|
-
"author": "
|
|
89
|
+
"author": "Das-rebel <subho@example.com>",
|
|
143
90
|
"license": "MIT",
|
|
144
|
-
"homepage": "https://github.com/Das-rebel/tmlpd-skill#readme",
|
|
145
91
|
"repository": {
|
|
146
92
|
"type": "git",
|
|
147
|
-
"url": "https://github.com/Das-rebel/
|
|
93
|
+
"url": "https://github.com/Das-rebel/adaptive-memory-multi-model-router"
|
|
148
94
|
},
|
|
149
95
|
"bugs": {
|
|
150
|
-
"url": "https://github.com/Das-rebel/
|
|
151
|
-
},
|
|
152
|
-
"dependencies": {
|
|
153
|
-
"nanoid": "^5.0.0"
|
|
96
|
+
"url": "https://github.com/Das-rebel/adaptive-memory-multi-model-router/issues"
|
|
154
97
|
},
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
"
|
|
98
|
+
"homepage": "https://github.com/Das-rebel/adaptive-memory-multi-model-router#readme",
|
|
99
|
+
"scripts": {
|
|
100
|
+
"test": "node test.js"
|
|
158
101
|
},
|
|
159
102
|
"engines": {
|
|
160
|
-
"node": ">=
|
|
103
|
+
"node": ">=16.0.0"
|
|
161
104
|
},
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
|
|
165
|
-
"Developer Tools",
|
|
166
|
-
"Programming"
|
|
167
|
-
],
|
|
168
|
-
"funding": {
|
|
169
|
-
"type": "individual",
|
|
170
|
-
"url": "https://github.com/sponsors/Das-rebel"
|
|
171
|
-
},
|
|
172
|
-
"shortName": "A3M Router",
|
|
173
|
-
"displayName": "A3M Router - Adaptive Memory Multi-Model Router"
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"nanoid": "^5.0.0"
|
|
107
|
+
}
|
|
174
108
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Integration Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides one-click OAuth for GitHub, Slack, Gmail, Notion
|
|
5
|
+
* with typed tool wrappers for each service.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface OAuthConfig {
|
|
9
|
+
clientId: string;
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
redirectUri: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface OAuthTokens {
|
|
15
|
+
accessToken: string;
|
|
16
|
+
refreshToken?: string;
|
|
17
|
+
expiresAt: number;
|
|
18
|
+
tokenType: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OAuthProvider {
|
|
22
|
+
name: string;
|
|
23
|
+
authUrl: string;
|
|
24
|
+
tokenUrl: string;
|
|
25
|
+
scopes: string[];
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Supported OAuth providers
|
|
30
|
+
export const OAUTH_PROVIDERS: Record<string, OAuthProvider> = {
|
|
31
|
+
github: {
|
|
32
|
+
name: 'GitHub',
|
|
33
|
+
authUrl: 'https://github.com/login/oauth/authorize',
|
|
34
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
35
|
+
scopes: ['repo', 'read:user', 'notifications'],
|
|
36
|
+
baseUrl: 'https://api.github.com'
|
|
37
|
+
},
|
|
38
|
+
slack: {
|
|
39
|
+
name: 'Slack',
|
|
40
|
+
authUrl: 'https://slack.com/oauth/v2/authorize',
|
|
41
|
+
tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
42
|
+
scopes: ['chat:write', 'channels:read', 'users:read'],
|
|
43
|
+
baseUrl: 'https://slack.com/api'
|
|
44
|
+
},
|
|
45
|
+
gmail: {
|
|
46
|
+
name: 'Gmail',
|
|
47
|
+
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
48
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
49
|
+
scopes: ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.readonly'],
|
|
50
|
+
baseUrl: 'https://gmail.googleapis.com/gmail/v1'
|
|
51
|
+
},
|
|
52
|
+
notion: {
|
|
53
|
+
name: 'Notion',
|
|
54
|
+
authUrl: 'https://api.notion.com/v1/oauth/authorize',
|
|
55
|
+
tokenUrl: 'https://api.notion.com/v1/oauth/token',
|
|
56
|
+
scopes: ['read_content', 'update_content', 'insert_database'],
|
|
57
|
+
baseUrl: 'https://api.notion.com/v1'
|
|
58
|
+
},
|
|
59
|
+
googlecalendar: {
|
|
60
|
+
name: 'Google Calendar',
|
|
61
|
+
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
62
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
63
|
+
scopes: ['https://www.googleapis.com/auth/calendar.events'],
|
|
64
|
+
baseUrl: 'https://www.googleapis.com/calendar/v3'
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export class OAuthManager {
|
|
69
|
+
private configs: Map<string, OAuthConfig>;
|
|
70
|
+
private tokens: Map<string, OAuthTokens>;
|
|
71
|
+
private state: Map<string, string>; // CSRF state
|
|
72
|
+
|
|
73
|
+
constructor() {
|
|
74
|
+
this.configs = new Map();
|
|
75
|
+
this.tokens = new Map();
|
|
76
|
+
this.state = new Map();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configure an OAuth provider
|
|
81
|
+
*/
|
|
82
|
+
configure(provider: string, config: OAuthConfig) {
|
|
83
|
+
this.configs.set(provider, config);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate authorization URL for a provider
|
|
88
|
+
*/
|
|
89
|
+
getAuthUrl(provider: string, state?: string): string {
|
|
90
|
+
const config = this.configs.get(provider);
|
|
91
|
+
const providerInfo = OAUTH_PROVIDERS[provider];
|
|
92
|
+
|
|
93
|
+
if (!config || !providerInfo) {
|
|
94
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Generate CSRF state
|
|
98
|
+
const csrfState = state || this.generateState(provider);
|
|
99
|
+
this.state.set(provider, csrfState);
|
|
100
|
+
|
|
101
|
+
const params = new URLSearchParams({
|
|
102
|
+
client_id: config.clientId,
|
|
103
|
+
redirect_uri: config.redirectUri,
|
|
104
|
+
scope: providerInfo.scopes.join(' '),
|
|
105
|
+
state: csrfState,
|
|
106
|
+
response_type: 'code'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return `${providerInfo.authUrl}?${params.toString()}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle OAuth callback and exchange code for tokens
|
|
114
|
+
*/
|
|
115
|
+
async handleCallback(provider: string, code: string, state: string): Promise<OAuthTokens> {
|
|
116
|
+
const config = this.configs.get(provider);
|
|
117
|
+
const providerInfo = OAUTH_PROVIDERS[provider];
|
|
118
|
+
|
|
119
|
+
if (!config || !providerInfo) {
|
|
120
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Validate state
|
|
124
|
+
const savedState = this.state.get(provider);
|
|
125
|
+
if (savedState && savedState !== state) {
|
|
126
|
+
throw new Error('Invalid OAuth state - CSRF mismatch');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Exchange code for tokens
|
|
130
|
+
const response = await fetch(providerInfo.tokenUrl, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'Accept': 'application/json'
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
client_id: config.clientId,
|
|
138
|
+
client_secret: config.clientSecret,
|
|
139
|
+
code,
|
|
140
|
+
redirect_uri: config.redirectUri,
|
|
141
|
+
grant_type: 'authorization_code'
|
|
142
|
+
})
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`Token exchange failed: ${response.statusText}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const tokens = await response.json() as OAuthTokens & { expires_in: number };
|
|
150
|
+
|
|
151
|
+
// Store tokens with expiration
|
|
152
|
+
const tokensWithExpiry: OAuthTokens = {
|
|
153
|
+
accessToken: tokens.access_token,
|
|
154
|
+
refreshToken: tokens.refresh_token,
|
|
155
|
+
expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : 0,
|
|
156
|
+
tokenType: tokens.token_type || 'Bearer'
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.tokens.set(provider, tokensWithExpiry);
|
|
160
|
+
return tokensWithExpiry;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get valid access token (refresh if needed)
|
|
165
|
+
*/
|
|
166
|
+
async getAccessToken(provider: string): Promise<string> {
|
|
167
|
+
const tokens = this.tokens.get(provider);
|
|
168
|
+
|
|
169
|
+
if (!tokens) {
|
|
170
|
+
throw new Error(`No tokens for provider: ${provider}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if expired
|
|
174
|
+
if (tokens.expiresAt && Date.now() >= tokens.expiresAt - 60000) {
|
|
175
|
+
if (tokens.refreshToken) {
|
|
176
|
+
await this.refreshToken(provider, tokens.refreshToken);
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error(`Token expired for ${provider} and no refresh token available`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return this.tokens.get(provider)!.accessToken;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Refresh access token
|
|
187
|
+
*/
|
|
188
|
+
async refreshToken(provider: string, refreshToken: string) {
|
|
189
|
+
const config = this.configs.get(provider);
|
|
190
|
+
const providerInfo = OAUTH_PROVIDERS[provider];
|
|
191
|
+
|
|
192
|
+
if (!config || !providerInfo) {
|
|
193
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const response = await fetch(providerInfo.tokenUrl, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: {
|
|
199
|
+
'Content-Type': 'application/json'
|
|
200
|
+
},
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
client_id: config.clientId,
|
|
203
|
+
client_secret: config.clientSecret,
|
|
204
|
+
refresh_token: refreshToken,
|
|
205
|
+
grant_type: 'refresh_token'
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
throw new Error(`Token refresh failed: ${response.statusText}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const tokens = await response.json() as OAuthTokens & { expires_in: number };
|
|
214
|
+
|
|
215
|
+
this.tokens.set(provider, {
|
|
216
|
+
accessToken: tokens.accessToken,
|
|
217
|
+
refreshToken: tokens.refreshToken || refreshToken,
|
|
218
|
+
expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : 0,
|
|
219
|
+
tokenType: tokens.tokenType || 'Bearer'
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Make authenticated API request
|
|
225
|
+
*/
|
|
226
|
+
async apiRequest(provider: string, endpoint: string, options: RequestInit = {}): Promise<any> {
|
|
227
|
+
const token = await this.getAccessToken(provider);
|
|
228
|
+
const providerInfo = OAUTH_PROVIDERS[provider];
|
|
229
|
+
|
|
230
|
+
if (!providerInfo) {
|
|
231
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const response = await fetch(`${providerInfo.baseUrl}${endpoint}`, {
|
|
235
|
+
...options,
|
|
236
|
+
headers: {
|
|
237
|
+
'Authorization': `Bearer ${token}`,
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
...options.headers
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return response.json();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if provider is connected
|
|
252
|
+
*/
|
|
253
|
+
isConnected(provider: string): boolean {
|
|
254
|
+
const tokens = this.tokens.get(provider);
|
|
255
|
+
if (!tokens) return false;
|
|
256
|
+
if (tokens.expiresAt && Date.now() >= tokens.expiresAt) return false;
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get connected providers
|
|
262
|
+
*/
|
|
263
|
+
getConnectedProviders(): string[] {
|
|
264
|
+
return Array.from(this.tokens.keys()).filter(p => this.isConnected(p));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Disconnect provider
|
|
269
|
+
*/
|
|
270
|
+
disconnect(provider: string) {
|
|
271
|
+
this.tokens.delete(provider);
|
|
272
|
+
this.state.delete(provider);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private generateState(provider: string): string {
|
|
276
|
+
return `${provider}_${Date.now()}_${Math.random().toString(36).substring(2)}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default OAuthManager;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Fetch Sync Loop
|
|
3
|
+
*
|
|
4
|
+
* Periodically syncs data from connected tools to provide
|
|
5
|
+
* context-aware routing decisions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface SyncConfig {
|
|
9
|
+
intervalMs: number;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
targets: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SyncResult {
|
|
15
|
+
target: string;
|
|
16
|
+
success: boolean;
|
|
17
|
+
items: number;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AutoFetch {
|
|
23
|
+
private intervalMs: number;
|
|
24
|
+
private enabled: boolean;
|
|
25
|
+
private targets: Set<string>;
|
|
26
|
+
private lastSync: Map<string, SyncResult>;
|
|
27
|
+
private timer: NodeJS.Timeout | null = null;
|
|
28
|
+
private syncHandlers: Map<string, () => Promise<SyncResult>>;
|
|
29
|
+
|
|
30
|
+
constructor(config: Partial<SyncConfig> = {}) {
|
|
31
|
+
this.intervalMs = config.intervalMs || 20 * 60 * 1000;
|
|
32
|
+
this.enabled = config.enabled !== false;
|
|
33
|
+
this.targets = new Set(config.targets || ['github', 'notion', 'slack']);
|
|
34
|
+
this.lastSync = new Map();
|
|
35
|
+
this.syncHandlers = new Map();
|
|
36
|
+
this.setupDefaultHandlers();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private setupDefaultHandlers() {
|
|
40
|
+
this.syncHandlers.set('github', async () => this.syncGitHub());
|
|
41
|
+
this.syncHandlers.set('notion', async () => this.syncNotion());
|
|
42
|
+
this.syncHandlers.set('slack', async () => this.syncSlack());
|
|
43
|
+
this.syncHandlers.set('gmail', async () => this.syncGmail());
|
|
44
|
+
this.syncHandlers.set('calendar', async () => this.syncCalendar());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
start() {
|
|
48
|
+
if (!this.enabled) return;
|
|
49
|
+
this.syncAll();
|
|
50
|
+
this.timer = setInterval(() => this.syncAll(), this.intervalMs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
stop() {
|
|
54
|
+
if (this.timer) {
|
|
55
|
+
clearInterval(this.timer);
|
|
56
|
+
this.timer = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async syncAll(): Promise<Map<string, SyncResult>> {
|
|
61
|
+
const results = new Map<string, SyncResult>();
|
|
62
|
+
for (const target of this.targets) {
|
|
63
|
+
const handler = this.syncHandlers.get(target);
|
|
64
|
+
if (handler) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await handler();
|
|
67
|
+
this.lastSync.set(target, result);
|
|
68
|
+
results.set(target, result);
|
|
69
|
+
} catch (error: any) {
|
|
70
|
+
const result: SyncResult = { target, success: false, items: 0, timestamp: Date.now(), error: error.message };
|
|
71
|
+
this.lastSync.set(target, result);
|
|
72
|
+
results.set(target, result);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getLastSync(target: string): SyncResult | undefined {
|
|
80
|
+
return this.lastSync.get(target);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
addHandler(target: string, handler: () => Promise<SyncResult>) {
|
|
84
|
+
this.syncHandlers.set(target, handler);
|
|
85
|
+
this.targets.add(target);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async syncGitHub(): Promise<SyncResult> {
|
|
89
|
+
return { target: 'github', success: true, items: 0, timestamp: Date.now() };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async syncNotion(): Promise<SyncResult> {
|
|
93
|
+
return { target: 'notion', success: true, items: 0, timestamp: Date.now() };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async syncSlack(): Promise<SyncResult> {
|
|
97
|
+
return { target: 'slack', success: true, items: 0, timestamp: Date.now() };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async syncGmail(): Promise<SyncResult> {
|
|
101
|
+
return { target: 'gmail', success: true, items: 0, timestamp: Date.now() };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async syncCalendar(): Promise<SyncResult> {
|
|
105
|
+
return { target: 'calendar', success: true, items: 0, timestamp: Date.now() };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default AutoFetch;
|