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.
Files changed (71) hide show
  1. package/README.md +80 -477
  2. package/dist/config.d.ts +5 -5
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +46 -20
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +28 -88
  7. package/dist/plugin.d.ts.map +1 -1
  8. package/dist/plugin.js +1 -8
  9. package/dist/services/ai/ai-provider-factory.d.ts +8 -0
  10. package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
  11. package/dist/services/ai/ai-provider-factory.js +25 -0
  12. package/dist/services/ai/providers/anthropic-messages.d.ts +13 -0
  13. package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
  14. package/dist/services/ai/providers/anthropic-messages.js +176 -0
  15. package/dist/services/ai/providers/base-provider.d.ts +21 -0
  16. package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
  17. package/dist/services/ai/providers/base-provider.js +6 -0
  18. package/dist/services/ai/providers/openai-chat-completion.d.ts +12 -0
  19. package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
  20. package/dist/services/ai/providers/openai-chat-completion.js +181 -0
  21. package/dist/services/ai/providers/openai-responses.d.ts +14 -0
  22. package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
  23. package/dist/services/ai/providers/openai-responses.js +191 -0
  24. package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
  25. package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
  26. package/dist/services/ai/session/ai-session-manager.js +165 -0
  27. package/dist/services/ai/session/session-types.d.ts +43 -0
  28. package/dist/services/ai/session/session-types.d.ts.map +1 -0
  29. package/dist/services/ai/session/session-types.js +1 -0
  30. package/dist/services/ai/tools/tool-schema.d.ts +41 -0
  31. package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
  32. package/dist/services/ai/tools/tool-schema.js +24 -0
  33. package/dist/services/api-handlers.d.ts +11 -3
  34. package/dist/services/api-handlers.d.ts.map +1 -1
  35. package/dist/services/api-handlers.js +143 -30
  36. package/dist/services/auto-capture.d.ts +1 -30
  37. package/dist/services/auto-capture.d.ts.map +1 -1
  38. package/dist/services/auto-capture.js +199 -396
  39. package/dist/services/cleanup-service.d.ts +3 -0
  40. package/dist/services/cleanup-service.d.ts.map +1 -1
  41. package/dist/services/cleanup-service.js +31 -4
  42. package/dist/services/client.d.ts +1 -0
  43. package/dist/services/client.d.ts.map +1 -1
  44. package/dist/services/client.js +3 -11
  45. package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
  46. package/dist/services/sqlite/connection-manager.js +8 -4
  47. package/dist/services/user-memory-learning.d.ts +3 -0
  48. package/dist/services/user-memory-learning.d.ts.map +1 -0
  49. package/dist/services/user-memory-learning.js +157 -0
  50. package/dist/services/user-prompt/user-prompt-manager.d.ts +38 -0
  51. package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
  52. package/dist/services/user-prompt/user-prompt-manager.js +164 -0
  53. package/dist/services/web-server-worker.js +27 -6
  54. package/dist/services/web-server.d.ts.map +1 -1
  55. package/dist/services/web-server.js +0 -5
  56. package/dist/types/index.d.ts +5 -1
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/dist/web/app.js +210 -120
  59. package/dist/web/index.html +14 -10
  60. package/dist/web/styles.css +326 -1
  61. package/package.json +4 -1
  62. package/dist/services/compaction.d.ts +0 -92
  63. package/dist/services/compaction.d.ts.map +0 -1
  64. package/dist/services/compaction.js +0 -421
  65. package/dist/services/sqlite-client.d.ts +0 -116
  66. package/dist/services/sqlite-client.d.ts.map +0 -1
  67. package/dist/services/sqlite-client.js +0 -284
  68. package/dist/services/web-server-lock.d.ts +0 -12
  69. package/dist/services/web-server-lock.d.ts.map +0 -1
  70. package/dist/services/web-server-lock.js +0 -157
  71. 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
- autoCaptureTokenThreshold: 10000,
48
- autoCaptureMinTokens: 20000,
49
- autoCaptureMaxMemories: 10,
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 other providers:
155
- // Anthropic:
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
- // Groq (fast & cheap):
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
- // Token thresholds
165
- "autoCaptureTokenThreshold": 10000,
166
- "autoCaptureMinTokens": 20000,
167
- "autoCaptureMaxMemories": 10,
168
- "autoCaptureContextWindow": 3,
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
- // Summary length: 0 = AI decides optimal length, >0 = character limit
171
- "autoCaptureSummaryMaxLength": 0,
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
- autoCaptureTokenThreshold: fileConfig.autoCaptureTokenThreshold ?? DEFAULTS.autoCaptureTokenThreshold,
240
- autoCaptureMinTokens: fileConfig.autoCaptureMinTokens ?? DEFAULTS.autoCaptureMinTokens,
241
- autoCaptureMaxMemories: fileConfig.autoCaptureMaxMemories ?? DEFAULTS.autoCaptureMaxMemories,
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;
@@ -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;AAuC/D,eAAO,MAAM,iBAAiB,EAAE,MAkpB/B,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 { AutoCaptureService, performAutoCapture } from "./services/auto-capture.js";
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
- if (webServer) {
102
- await webServer.stop();
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 = autoCaptureService.isEnabled()
152
+ const autoCaptureStatus = CONFIG.autoCaptureEnabled &&
153
+ CONFIG.memoryModel &&
154
+ CONFIG.memoryApiUrl &&
155
+ CONFIG.memoryApiKey
152
156
  ? "Auto-capture: enabled"
153
- : autoCaptureService.getDisabledReason() || "Auto-capture: disabled";
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: autoCaptureService.isEnabled() ? "success" : "warning",
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
- await performAutoCapture(ctx, autoCaptureService, toolCtx.sessionID, directory);
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)
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";AAgBA,QAAA,MAAQ,iBAAiB,sCAA+B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,eAAe,iBAAiB,CAAC"}
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,6 @@
1
+ export class BaseAIProvider {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ }
@@ -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"}