opencode-mem 1.0.0 → 2.0.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.
- package/README.md +80 -477
- package/dist/config.d.ts +5 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -88
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +1 -8
- package/dist/services/ai/ai-provider-factory.d.ts +8 -0
- package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
- package/dist/services/ai/ai-provider-factory.js +25 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +13 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +176 -0
- package/dist/services/ai/providers/base-provider.d.ts +21 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +6 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +12 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +181 -0
- package/dist/services/ai/providers/openai-responses.d.ts +14 -0
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-responses.js +191 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
- package/dist/services/ai/session/ai-session-manager.js +165 -0
- package/dist/services/ai/session/session-types.d.ts +43 -0
- package/dist/services/ai/session/session-types.d.ts.map +1 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +41 -0
- package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/api-handlers.d.ts +11 -3
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +143 -30
- package/dist/services/auto-capture.d.ts +1 -30
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +199 -396
- package/dist/services/cleanup-service.d.ts +3 -0
- package/dist/services/cleanup-service.d.ts.map +1 -1
- package/dist/services/cleanup-service.js +31 -4
- package/dist/services/client.d.ts +1 -0
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +3 -11
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +8 -4
- package/dist/services/user-memory-learning.d.ts +3 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -0
- package/dist/services/user-memory-learning.js +157 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +38 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +164 -0
- package/dist/services/web-server-worker.js +27 -6
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +0 -5
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/web/app.js +210 -120
- package/dist/web/index.html +14 -10
- package/dist/web/styles.css +326 -1
- package/package.json +4 -1
- package/dist/services/compaction.d.ts +0 -92
- package/dist/services/compaction.d.ts.map +0 -1
- package/dist/services/compaction.js +0 -421
- package/dist/services/sqlite-client.d.ts +0 -116
- package/dist/services/sqlite-client.d.ts.map +0 -1
- package/dist/services/sqlite-client.js +0 -284
- package/dist/services/web-server-lock.d.ts +0 -12
- package/dist/services/web-server-lock.d.ts.map +0 -1
- package/dist/services/web-server-lock.js +0 -157
- package/dist/web/favicon.svg +0 -14
package/dist/config.js
CHANGED
|
@@ -44,11 +44,9 @@ const DEFAULTS = {
|
|
|
44
44
|
containerTagPrefix: "opencode",
|
|
45
45
|
keywordPatterns: [],
|
|
46
46
|
autoCaptureEnabled: true,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
autoCaptureSummaryMaxLength: 0,
|
|
51
|
-
autoCaptureContextWindow: 3,
|
|
47
|
+
autoCaptureMaxIterations: 5,
|
|
48
|
+
autoCaptureIterationTimeout: 30000,
|
|
49
|
+
aiSessionRetentionDays: 7,
|
|
52
50
|
webServerEnabled: true,
|
|
53
51
|
webServerPort: 4747,
|
|
54
52
|
webServerHost: "127.0.0.1",
|
|
@@ -57,6 +55,7 @@ const DEFAULTS = {
|
|
|
57
55
|
autoCleanupRetentionDays: 30,
|
|
58
56
|
deduplicationEnabled: true,
|
|
59
57
|
deduplicationSimilarityThreshold: 0.9,
|
|
58
|
+
userMemoryAnalysisInterval: 10,
|
|
60
59
|
};
|
|
61
60
|
function isValidRegex(pattern) {
|
|
62
61
|
try {
|
|
@@ -146,29 +145,56 @@ const CONFIG_TEMPLATE = `{
|
|
|
146
145
|
|
|
147
146
|
"autoCaptureEnabled": true,
|
|
148
147
|
|
|
148
|
+
// Provider type: "openai-chat" | "openai-responses" | "anthropic"
|
|
149
|
+
"memoryProvider": "openai-chat",
|
|
150
|
+
|
|
149
151
|
// REQUIRED for auto-capture (all 3 must be set):
|
|
150
152
|
"memoryModel": "gpt-4o-mini",
|
|
151
153
|
"memoryApiUrl": "https://api.openai.com/v1",
|
|
152
154
|
"memoryApiKey": "sk-...",
|
|
153
155
|
|
|
154
|
-
// Examples for
|
|
155
|
-
//
|
|
156
|
+
// Examples for different providers:
|
|
157
|
+
// OpenAI Chat Completion (default, backward compatible):
|
|
158
|
+
// "memoryProvider": "openai-chat"
|
|
159
|
+
// "memoryModel": "gpt-4o-mini"
|
|
160
|
+
// "memoryApiUrl": "https://api.openai.com/v1"
|
|
161
|
+
// "memoryApiKey": "sk-..."
|
|
162
|
+
|
|
163
|
+
// OpenAI Responses API (recommended, with session support):
|
|
164
|
+
// "memoryProvider": "openai-responses"
|
|
165
|
+
// "memoryModel": "gpt-4o"
|
|
166
|
+
// "memoryApiUrl": "https://api.openai.com/v1"
|
|
167
|
+
// "memoryApiKey": "sk-..."
|
|
168
|
+
|
|
169
|
+
// Anthropic (with session support):
|
|
170
|
+
// "memoryProvider": "anthropic"
|
|
156
171
|
// "memoryModel": "claude-3-5-haiku-20241022"
|
|
157
172
|
// "memoryApiUrl": "https://api.anthropic.com/v1"
|
|
158
173
|
// "memoryApiKey": "sk-ant-..."
|
|
159
|
-
|
|
174
|
+
|
|
175
|
+
// Groq (OpenAI-compatible, use openai-chat provider):
|
|
176
|
+
// "memoryProvider": "openai-chat"
|
|
160
177
|
// "memoryModel": "llama-3.3-70b-versatile"
|
|
161
178
|
// "memoryApiUrl": "https://api.groq.com/openai/v1"
|
|
162
179
|
// "memoryApiKey": "gsk_..."
|
|
163
180
|
|
|
164
|
-
//
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
|
|
168
|
-
|
|
181
|
+
// Multi-iteration settings (for openai-responses and anthropic)
|
|
182
|
+
"autoCaptureMaxIterations": 5,
|
|
183
|
+
"autoCaptureIterationTimeout": 30000,
|
|
184
|
+
|
|
185
|
+
// Session management
|
|
186
|
+
"aiSessionRetentionDays": 7,
|
|
187
|
+
|
|
188
|
+
// ============================================
|
|
189
|
+
// User Memory Learning
|
|
190
|
+
// ============================================
|
|
169
191
|
|
|
170
|
-
//
|
|
171
|
-
|
|
192
|
+
// Analyze user prompts every N prompts to learn patterns and preferences
|
|
193
|
+
// When N uncaptured prompts accumulate, AI will analyze them to identify:
|
|
194
|
+
// - User preferences (code style, communication style)
|
|
195
|
+
// - User patterns (recurring topics, problem domains)
|
|
196
|
+
// - User workflows (development habits, sequences)
|
|
197
|
+
"userMemoryAnalysisInterval": 10,
|
|
172
198
|
|
|
173
199
|
// ============================================
|
|
174
200
|
// Search Settings
|
|
@@ -236,14 +262,13 @@ export const CONFIG = {
|
|
|
236
262
|
...(fileConfig.keywordPatterns ?? []).filter(isValidRegex),
|
|
237
263
|
],
|
|
238
264
|
autoCaptureEnabled: fileConfig.autoCaptureEnabled ?? DEFAULTS.autoCaptureEnabled,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
autoCaptureSummaryMaxLength: fileConfig.autoCaptureSummaryMaxLength ?? DEFAULTS.autoCaptureSummaryMaxLength,
|
|
243
|
-
autoCaptureContextWindow: fileConfig.autoCaptureContextWindow ?? DEFAULTS.autoCaptureContextWindow,
|
|
265
|
+
autoCaptureMaxIterations: fileConfig.autoCaptureMaxIterations ?? DEFAULTS.autoCaptureMaxIterations,
|
|
266
|
+
autoCaptureIterationTimeout: fileConfig.autoCaptureIterationTimeout ?? DEFAULTS.autoCaptureIterationTimeout,
|
|
267
|
+
memoryProvider: (fileConfig.memoryProvider ?? "openai-chat"),
|
|
244
268
|
memoryModel: fileConfig.memoryModel,
|
|
245
269
|
memoryApiUrl: fileConfig.memoryApiUrl,
|
|
246
270
|
memoryApiKey: fileConfig.memoryApiKey,
|
|
271
|
+
aiSessionRetentionDays: fileConfig.aiSessionRetentionDays ?? DEFAULTS.aiSessionRetentionDays,
|
|
247
272
|
webServerEnabled: fileConfig.webServerEnabled ?? DEFAULTS.webServerEnabled,
|
|
248
273
|
webServerPort: fileConfig.webServerPort ?? DEFAULTS.webServerPort,
|
|
249
274
|
webServerHost: fileConfig.webServerHost ?? DEFAULTS.webServerHost,
|
|
@@ -252,6 +277,7 @@ export const CONFIG = {
|
|
|
252
277
|
autoCleanupRetentionDays: fileConfig.autoCleanupRetentionDays ?? DEFAULTS.autoCleanupRetentionDays,
|
|
253
278
|
deduplicationEnabled: fileConfig.deduplicationEnabled ?? DEFAULTS.deduplicationEnabled,
|
|
254
279
|
deduplicationSimilarityThreshold: fileConfig.deduplicationSimilarityThreshold ?? DEFAULTS.deduplicationSimilarityThreshold,
|
|
280
|
+
userMemoryAnalysisInterval: fileConfig.userMemoryAnalysisInterval ?? DEFAULTS.userMemoryAnalysisInterval,
|
|
255
281
|
};
|
|
256
282
|
export function isConfigured() {
|
|
257
283
|
return true;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAyC/D,eAAO,MAAM,iBAAiB,EAAE,MA+kB/B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import { memoryClient } from "./services/client.js";
|
|
|
3
3
|
import { formatContextForPrompt } from "./services/context.js";
|
|
4
4
|
import { getTags } from "./services/tags.js";
|
|
5
5
|
import { stripPrivateContent, isFullyPrivate } from "./services/privacy.js";
|
|
6
|
-
import {
|
|
6
|
+
import { performAutoCapture } from "./services/auto-capture.js";
|
|
7
|
+
import { performUserMemoryLearning } from "./services/user-memory-learning.js";
|
|
8
|
+
import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
|
|
7
9
|
import { startWebServer, WebServer } from "./services/web-server.js";
|
|
8
10
|
import { isConfigured, CONFIG } from "./config.js";
|
|
9
11
|
import { log } from "./services/logger.js";
|
|
@@ -30,14 +32,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
30
32
|
const { directory } = ctx;
|
|
31
33
|
const tags = getTags(directory);
|
|
32
34
|
const injectedSessions = new Set();
|
|
33
|
-
const autoCaptureService = new AutoCaptureService();
|
|
34
35
|
let webServer = null;
|
|
35
|
-
log("Plugin loaded", {
|
|
36
|
-
directory,
|
|
37
|
-
tags,
|
|
38
|
-
configured: isConfigured(),
|
|
39
|
-
autoCaptureEnabled: autoCaptureService.isEnabled(),
|
|
40
|
-
});
|
|
41
36
|
if (!isConfigured()) {
|
|
42
37
|
log("Plugin disabled - memory system not configured");
|
|
43
38
|
}
|
|
@@ -51,7 +46,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
51
46
|
webServer = server;
|
|
52
47
|
const url = webServer.getUrl();
|
|
53
48
|
if (webServer.isServerOwner()) {
|
|
54
|
-
log("Web server started (owner)", { url });
|
|
55
49
|
if (ctx.client?.tui) {
|
|
56
50
|
ctx.client.tui
|
|
57
51
|
.showToast({
|
|
@@ -66,7 +60,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
62
|
else {
|
|
69
|
-
log("Web server already running (joined)", { url });
|
|
70
63
|
if (ctx.client?.tui) {
|
|
71
64
|
ctx.client.tui
|
|
72
65
|
.showToast({
|
|
@@ -98,13 +91,20 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
98
91
|
});
|
|
99
92
|
}
|
|
100
93
|
const shutdownHandler = async () => {
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
try {
|
|
95
|
+
if (webServer) {
|
|
96
|
+
await webServer.stop();
|
|
97
|
+
}
|
|
98
|
+
memoryClient.close();
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
log("Shutdown error", { error: String(error) });
|
|
103
|
+
process.exit(1);
|
|
103
104
|
}
|
|
104
105
|
};
|
|
105
106
|
process.on("SIGINT", shutdownHandler);
|
|
106
107
|
process.on("SIGTERM", shutdownHandler);
|
|
107
|
-
process.on("exit", shutdownHandler);
|
|
108
108
|
return {
|
|
109
109
|
"chat.message": async (input, output) => {
|
|
110
110
|
if (!isConfigured())
|
|
@@ -117,6 +117,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
117
117
|
const userMessage = textParts.map((p) => p.text).join("\n");
|
|
118
118
|
if (!userMessage.trim())
|
|
119
119
|
return;
|
|
120
|
+
userPromptManager.savePrompt(input.sessionID, output.message.id, directory, userMessage);
|
|
120
121
|
if (detectMemoryKeyword(userMessage)) {
|
|
121
122
|
const nudgePart = {
|
|
122
123
|
id: `memory-nudge-${Date.now()}`,
|
|
@@ -148,15 +149,18 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
148
149
|
try {
|
|
149
150
|
await memoryClient.warmup();
|
|
150
151
|
if (ctx.client?.tui) {
|
|
151
|
-
const autoCaptureStatus =
|
|
152
|
+
const autoCaptureStatus = CONFIG.autoCaptureEnabled &&
|
|
153
|
+
CONFIG.memoryModel &&
|
|
154
|
+
CONFIG.memoryApiUrl &&
|
|
155
|
+
CONFIG.memoryApiKey
|
|
152
156
|
? "Auto-capture: enabled"
|
|
153
|
-
:
|
|
157
|
+
: "Auto-capture: disabled";
|
|
154
158
|
await ctx.client.tui
|
|
155
159
|
.showToast({
|
|
156
160
|
body: {
|
|
157
161
|
title: "Memory System Ready!",
|
|
158
162
|
message: autoCaptureStatus,
|
|
159
|
-
variant:
|
|
163
|
+
variant: CONFIG.autoCaptureEnabled ? "success" : "warning",
|
|
160
164
|
duration: 3000,
|
|
161
165
|
},
|
|
162
166
|
})
|
|
@@ -236,17 +240,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
236
240
|
description: "Manage and query the local persistent memory system. Use 'search' to find relevant memories, 'add' to store new knowledge, 'profile' to view user profile, 'list' to see recent memories, 'forget' to remove a memory.",
|
|
237
241
|
args: {
|
|
238
242
|
mode: tool.schema
|
|
239
|
-
.enum([
|
|
240
|
-
"add",
|
|
241
|
-
"search",
|
|
242
|
-
"profile",
|
|
243
|
-
"list",
|
|
244
|
-
"forget",
|
|
245
|
-
"help",
|
|
246
|
-
"capture-now",
|
|
247
|
-
"auto-capture-toggle",
|
|
248
|
-
"auto-capture-stats",
|
|
249
|
-
])
|
|
243
|
+
.enum(["add", "search", "profile", "list", "forget", "help", "capture-now"])
|
|
250
244
|
.optional(),
|
|
251
245
|
content: tool.schema.string().optional(),
|
|
252
246
|
query: tool.schema.string().optional(),
|
|
@@ -307,16 +301,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
307
301
|
description: "Manually trigger memory capture for current session",
|
|
308
302
|
args: [],
|
|
309
303
|
},
|
|
310
|
-
{
|
|
311
|
-
command: "auto-capture-toggle",
|
|
312
|
-
description: "Enable/disable automatic memory capture",
|
|
313
|
-
args: [],
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
command: "auto-capture-stats",
|
|
317
|
-
description: "View auto-capture statistics for current session",
|
|
318
|
-
args: [],
|
|
319
|
-
},
|
|
320
304
|
],
|
|
321
305
|
scopes: {
|
|
322
306
|
user: "Cross-project user behaviors, preferences, patterns, requests",
|
|
@@ -485,39 +469,13 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
485
469
|
});
|
|
486
470
|
}
|
|
487
471
|
case "capture-now": {
|
|
488
|
-
|
|
472
|
+
const sessionID = toolCtx.sessionID;
|
|
473
|
+
await performAutoCapture(ctx, sessionID, directory);
|
|
489
474
|
return JSON.stringify({
|
|
490
475
|
success: true,
|
|
491
476
|
message: "Manual capture triggered",
|
|
492
477
|
});
|
|
493
478
|
}
|
|
494
|
-
case "auto-capture-toggle": {
|
|
495
|
-
const enabled = autoCaptureService.toggle();
|
|
496
|
-
return JSON.stringify({
|
|
497
|
-
success: true,
|
|
498
|
-
message: `Auto-capture ${enabled ? "enabled" : "disabled"}`,
|
|
499
|
-
enabled,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
case "auto-capture-stats": {
|
|
503
|
-
const stats = autoCaptureService.getStats(toolCtx.sessionID);
|
|
504
|
-
if (!stats) {
|
|
505
|
-
return JSON.stringify({
|
|
506
|
-
success: true,
|
|
507
|
-
message: "No capture data for this session",
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
return JSON.stringify({
|
|
511
|
-
success: true,
|
|
512
|
-
stats: {
|
|
513
|
-
lastCaptureTokens: stats.lastCaptureTokens,
|
|
514
|
-
minutesSinceCapture: Math.floor(stats.timeSinceCapture / 60000),
|
|
515
|
-
tokenThreshold: CONFIG.autoCaptureTokenThreshold,
|
|
516
|
-
minTokens: CONFIG.autoCaptureMinTokens,
|
|
517
|
-
enabled: autoCaptureService.isEnabled(),
|
|
518
|
-
},
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
479
|
default:
|
|
522
480
|
return JSON.stringify({
|
|
523
481
|
success: false,
|
|
@@ -537,32 +495,14 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
537
495
|
event: async (input) => {
|
|
538
496
|
const event = input.event;
|
|
539
497
|
const props = event.properties;
|
|
540
|
-
if (event.type === "message.updated") {
|
|
541
|
-
if (!autoCaptureService.isEnabled())
|
|
542
|
-
return;
|
|
543
|
-
const info = props?.info;
|
|
544
|
-
if (!info)
|
|
545
|
-
return;
|
|
546
|
-
const sessionID = info.sessionID;
|
|
547
|
-
if (!sessionID)
|
|
548
|
-
return;
|
|
549
|
-
if (info.role !== "assistant" || !info.finish)
|
|
550
|
-
return;
|
|
551
|
-
const tokens = info.tokens;
|
|
552
|
-
if (!tokens)
|
|
553
|
-
return;
|
|
554
|
-
const totalUsed = tokens.input + tokens.cache.read + tokens.output;
|
|
555
|
-
const shouldCapture = autoCaptureService.checkTokenThreshold(sessionID, totalUsed);
|
|
556
|
-
if (shouldCapture) {
|
|
557
|
-
performAutoCapture(ctx, autoCaptureService, sessionID, directory).catch((err) => log("Auto-capture failed", { error: String(err) }));
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
if (event.type === "session.deleted" && props?.sessionID) {
|
|
561
|
-
autoCaptureService.cleanup(props.sessionID);
|
|
562
|
-
}
|
|
563
498
|
if (event.type === "session.idle") {
|
|
564
499
|
if (!isConfigured())
|
|
565
500
|
return;
|
|
501
|
+
const sessionID = props?.sessionID;
|
|
502
|
+
if (sessionID) {
|
|
503
|
+
await performAutoCapture(ctx, sessionID, directory);
|
|
504
|
+
}
|
|
505
|
+
await performUserMemoryLearning(ctx, directory);
|
|
566
506
|
const { cleanupService } = await import("./services/cleanup-service.js");
|
|
567
507
|
const shouldRun = await cleanupService.shouldRunCleanup();
|
|
568
508
|
if (!shouldRun)
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";AAOA,QAAA,MAAQ,iBAAiB,sCAA+B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,eAAe,iBAAiB,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
4
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname } from "node:path";
|
|
5
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
5
|
const __dirname = dirname(__filename);
|
|
7
|
-
const projectRoot = join(__dirname, "..");
|
|
8
|
-
if (!existsSync(join(projectRoot, "node_modules"))) {
|
|
9
|
-
console.error("Error: node_modules not found. Run 'bun install' first.");
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
process.chdir(projectRoot);
|
|
13
6
|
const { OpenCodeMemPlugin } = await import("./index.js");
|
|
14
7
|
export { OpenCodeMemPlugin };
|
|
15
8
|
export default OpenCodeMemPlugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseAIProvider, type ProviderConfig } from "./providers/base-provider.js";
|
|
2
|
+
import type { AIProviderType } from "./session/session-types.js";
|
|
3
|
+
export declare class AIProviderFactory {
|
|
4
|
+
static createProvider(providerType: AIProviderType, config: ProviderConfig): BaseAIProvider;
|
|
5
|
+
static getSupportedProviders(): AIProviderType[];
|
|
6
|
+
static cleanupExpiredSessions(): number;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=ai-provider-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-provider-factory.d.ts","sourceRoot":"","sources":["../../../src/services/ai/ai-provider-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,GAAG,cAAc;IAgB3F,MAAM,CAAC,qBAAqB,IAAI,cAAc,EAAE;IAIhD,MAAM,CAAC,sBAAsB,IAAI,MAAM;CAGxC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseAIProvider } from "./providers/base-provider.js";
|
|
2
|
+
import { OpenAIChatCompletionProvider } from "./providers/openai-chat-completion.js";
|
|
3
|
+
import { OpenAIResponsesProvider } from "./providers/openai-responses.js";
|
|
4
|
+
import { AnthropicMessagesProvider } from "./providers/anthropic-messages.js";
|
|
5
|
+
import { aiSessionManager } from "./session/ai-session-manager.js";
|
|
6
|
+
export class AIProviderFactory {
|
|
7
|
+
static createProvider(providerType, config) {
|
|
8
|
+
switch (providerType) {
|
|
9
|
+
case "openai-chat":
|
|
10
|
+
return new OpenAIChatCompletionProvider(config, aiSessionManager);
|
|
11
|
+
case "openai-responses":
|
|
12
|
+
return new OpenAIResponsesProvider(config, aiSessionManager);
|
|
13
|
+
case "anthropic":
|
|
14
|
+
return new AnthropicMessagesProvider(config, aiSessionManager);
|
|
15
|
+
default:
|
|
16
|
+
throw new Error(`Unknown provider type: ${providerType}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
static getSupportedProviders() {
|
|
20
|
+
return ["openai-chat", "openai-responses", "anthropic"];
|
|
21
|
+
}
|
|
22
|
+
static cleanupExpiredSessions() {
|
|
23
|
+
return aiSessionManager.cleanupExpiredSessions();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseAIProvider, type ToolCallResult } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import { type ChatCompletionTool } from "../tools/tool-schema.js";
|
|
4
|
+
export declare class AnthropicMessagesProvider extends BaseAIProvider {
|
|
5
|
+
private aiSessionManager;
|
|
6
|
+
constructor(config: any, aiSessionManager: AISessionManager);
|
|
7
|
+
getProviderName(): string;
|
|
8
|
+
supportsSession(): boolean;
|
|
9
|
+
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
10
|
+
private extractToolUse;
|
|
11
|
+
private validateResponse;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=anthropic-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA2BvF,qBAAa,yBAA0B,SAAQ,cAAc;IAC3D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAmJ1B,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;CA+BzB"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { BaseAIProvider } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import { ToolSchemaConverter } from "../tools/tool-schema.js";
|
|
4
|
+
import { log } from "../../logger.js";
|
|
5
|
+
export class AnthropicMessagesProvider extends BaseAIProvider {
|
|
6
|
+
aiSessionManager;
|
|
7
|
+
constructor(config, aiSessionManager) {
|
|
8
|
+
super(config);
|
|
9
|
+
this.aiSessionManager = aiSessionManager;
|
|
10
|
+
}
|
|
11
|
+
getProviderName() {
|
|
12
|
+
return "anthropic";
|
|
13
|
+
}
|
|
14
|
+
supportsSession() {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
|
|
18
|
+
let session = this.aiSessionManager.getSession(sessionId, "anthropic");
|
|
19
|
+
if (!session) {
|
|
20
|
+
session = this.aiSessionManager.createSession({
|
|
21
|
+
provider: "anthropic",
|
|
22
|
+
sessionId,
|
|
23
|
+
metadata: { systemPrompt },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const storedMessages = this.aiSessionManager.getMessages(session.id);
|
|
27
|
+
const messages = [];
|
|
28
|
+
for (const msg of storedMessages) {
|
|
29
|
+
if (msg.role === "system")
|
|
30
|
+
continue;
|
|
31
|
+
const anthropicMsg = {
|
|
32
|
+
role: msg.role,
|
|
33
|
+
content: msg.contentBlocks || msg.content,
|
|
34
|
+
};
|
|
35
|
+
messages.push(anthropicMsg);
|
|
36
|
+
}
|
|
37
|
+
const userSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
38
|
+
this.aiSessionManager.addMessage({
|
|
39
|
+
aiSessionId: session.id,
|
|
40
|
+
sequence: userSequence,
|
|
41
|
+
role: "user",
|
|
42
|
+
content: userPrompt,
|
|
43
|
+
});
|
|
44
|
+
messages.push({ role: "user", content: userPrompt });
|
|
45
|
+
let iterations = 0;
|
|
46
|
+
const maxIterations = this.config.maxIterations;
|
|
47
|
+
while (iterations < maxIterations) {
|
|
48
|
+
iterations++;
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const timeout = setTimeout(() => controller.abort(), this.config.iterationTimeout);
|
|
51
|
+
try {
|
|
52
|
+
const tool = ToolSchemaConverter.toAnthropic(toolSchema);
|
|
53
|
+
const requestBody = {
|
|
54
|
+
model: this.config.model,
|
|
55
|
+
system: systemPrompt,
|
|
56
|
+
messages,
|
|
57
|
+
tools: [tool],
|
|
58
|
+
};
|
|
59
|
+
const response = await fetch(`${this.config.apiUrl}/messages`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
"x-api-key": this.config.apiKey,
|
|
64
|
+
"anthropic-version": "2023-06-01",
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(requestBody),
|
|
67
|
+
signal: controller.signal,
|
|
68
|
+
});
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
72
|
+
log("Anthropic Messages API error", {
|
|
73
|
+
status: response.status,
|
|
74
|
+
error: errorText,
|
|
75
|
+
iteration: iterations,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: `API error: ${response.status} - ${errorText}`,
|
|
80
|
+
iterations,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const data = (await response.json());
|
|
84
|
+
const assistantSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
85
|
+
this.aiSessionManager.addMessage({
|
|
86
|
+
aiSessionId: session.id,
|
|
87
|
+
sequence: assistantSequence,
|
|
88
|
+
role: "assistant",
|
|
89
|
+
content: JSON.stringify(data.content),
|
|
90
|
+
contentBlocks: data.content,
|
|
91
|
+
});
|
|
92
|
+
messages.push({
|
|
93
|
+
role: "assistant",
|
|
94
|
+
content: data.content,
|
|
95
|
+
});
|
|
96
|
+
const toolUse = this.extractToolUse(data, toolSchema.function.name);
|
|
97
|
+
if (toolUse) {
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
data: this.validateResponse(toolUse),
|
|
101
|
+
iterations,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (data.stop_reason === "end_turn") {
|
|
105
|
+
const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
106
|
+
const retryPrompt = "Please use the save_memories tool to extract and save the memories from the conversation as instructed.";
|
|
107
|
+
this.aiSessionManager.addMessage({
|
|
108
|
+
aiSessionId: session.id,
|
|
109
|
+
sequence: retrySequence,
|
|
110
|
+
role: "user",
|
|
111
|
+
content: retryPrompt,
|
|
112
|
+
});
|
|
113
|
+
messages.push({ role: "user", content: retryPrompt });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: `API request timeout (${this.config.iterationTimeout}ms)`,
|
|
125
|
+
iterations,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: String(error),
|
|
131
|
+
iterations,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
error: `Max iterations (${maxIterations}) reached without tool use`,
|
|
138
|
+
iterations,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
extractToolUse(data, expectedToolName) {
|
|
142
|
+
if (!data.content || !Array.isArray(data.content)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
for (const block of data.content) {
|
|
146
|
+
if (block.type === "tool_use" && block.name === expectedToolName && block.input) {
|
|
147
|
+
return block.input;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
validateResponse(data) {
|
|
153
|
+
if (!data || typeof data !== "object") {
|
|
154
|
+
throw new Error("Response is not an object");
|
|
155
|
+
}
|
|
156
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
157
|
+
const validMemories = data.memories.filter((m) => {
|
|
158
|
+
return (m &&
|
|
159
|
+
typeof m === "object" &&
|
|
160
|
+
typeof m.summary === "string" &&
|
|
161
|
+
m.summary.trim().length > 0 &&
|
|
162
|
+
(m.scope === "user" || m.scope === "project") &&
|
|
163
|
+
typeof m.type === "string" &&
|
|
164
|
+
m.type.trim().length > 0);
|
|
165
|
+
});
|
|
166
|
+
if (validMemories.length === 0) {
|
|
167
|
+
throw new Error("No valid memories in response");
|
|
168
|
+
}
|
|
169
|
+
return { memories: validMemories };
|
|
170
|
+
}
|
|
171
|
+
if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
throw new Error("Invalid response format: missing summary or memories field");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ToolCallResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: any;
|
|
4
|
+
error?: string;
|
|
5
|
+
iterations?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderConfig {
|
|
8
|
+
model: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
maxIterations: number;
|
|
12
|
+
iterationTimeout: number;
|
|
13
|
+
}
|
|
14
|
+
export declare abstract class BaseAIProvider {
|
|
15
|
+
protected config: ProviderConfig;
|
|
16
|
+
constructor(config: ProviderConfig);
|
|
17
|
+
abstract executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: any, sessionId: string): Promise<ToolCallResult>;
|
|
18
|
+
abstract getProviderName(): string;
|
|
19
|
+
abstract supportsSession(): boolean;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=base-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-provider.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/base-provider.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,8BAAsB,cAAc;IAClC,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC;gBAErB,MAAM,EAAE,cAAc;IAIlC,QAAQ,CAAC,eAAe,CACtB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,GAAG,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAE1B,QAAQ,CAAC,eAAe,IAAI,MAAM;IAElC,QAAQ,CAAC,eAAe,IAAI,OAAO;CACpC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseAIProvider, type ToolCallResult } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import type { ChatCompletionTool } from "../tools/tool-schema.js";
|
|
4
|
+
export declare class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
5
|
+
private aiSessionManager;
|
|
6
|
+
constructor(config: any, aiSessionManager: AISessionManager);
|
|
7
|
+
getProviderName(): string;
|
|
8
|
+
supportsSession(): boolean;
|
|
9
|
+
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
10
|
+
private validateResponse;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=openai-chat-completion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-chat-completion.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-chat-completion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAkBlE,qBAAa,4BAA6B,SAAQ,cAAc;IAC9D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IA2K1B,OAAO,CAAC,gBAAgB;CA+BzB"}
|