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
@@ -0,0 +1,181 @@
1
+ import { BaseAIProvider } from "./base-provider.js";
2
+ import { AISessionManager } from "../session/ai-session-manager.js";
3
+ import { log } from "../../logger.js";
4
+ export class OpenAIChatCompletionProvider extends BaseAIProvider {
5
+ aiSessionManager;
6
+ constructor(config, aiSessionManager) {
7
+ super(config);
8
+ this.aiSessionManager = aiSessionManager;
9
+ }
10
+ getProviderName() {
11
+ return "openai-chat";
12
+ }
13
+ supportsSession() {
14
+ return true;
15
+ }
16
+ async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
17
+ let session = this.aiSessionManager.getSession(sessionId, "openai-chat");
18
+ if (!session) {
19
+ session = this.aiSessionManager.createSession({
20
+ provider: "openai-chat",
21
+ sessionId,
22
+ });
23
+ }
24
+ const existingMessages = this.aiSessionManager.getMessages(session.id);
25
+ const messages = [];
26
+ for (const msg of existingMessages) {
27
+ const apiMsg = {
28
+ role: msg.role,
29
+ content: msg.content,
30
+ };
31
+ if (msg.toolCalls) {
32
+ apiMsg.tool_calls = msg.toolCalls;
33
+ }
34
+ if (msg.toolCallId) {
35
+ apiMsg.tool_call_id = msg.toolCallId;
36
+ }
37
+ messages.push(apiMsg);
38
+ }
39
+ if (messages.length === 0) {
40
+ const sequence = this.aiSessionManager.getLastSequence(session.id) + 1;
41
+ this.aiSessionManager.addMessage({
42
+ aiSessionId: session.id,
43
+ sequence,
44
+ role: "system",
45
+ content: systemPrompt,
46
+ });
47
+ messages.push({ role: "system", content: systemPrompt });
48
+ }
49
+ const userSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
50
+ this.aiSessionManager.addMessage({
51
+ aiSessionId: session.id,
52
+ sequence: userSequence,
53
+ role: "user",
54
+ content: userPrompt,
55
+ });
56
+ messages.push({ role: "user", content: userPrompt });
57
+ let iterations = 0;
58
+ while (iterations < this.config.maxIterations) {
59
+ iterations++;
60
+ const controller = new AbortController();
61
+ const timeout = setTimeout(() => controller.abort(), this.config.iterationTimeout);
62
+ try {
63
+ const requestBody = {
64
+ model: this.config.model,
65
+ messages,
66
+ tools: [toolSchema],
67
+ tool_choice: { type: "function", name: toolSchema.function.name },
68
+ temperature: 0.3,
69
+ };
70
+ const response = await fetch(`${this.config.apiUrl}/chat/completions`, {
71
+ method: "POST",
72
+ headers: {
73
+ "Content-Type": "application/json",
74
+ Authorization: `Bearer ${this.config.apiKey}`,
75
+ },
76
+ body: JSON.stringify(requestBody),
77
+ signal: controller.signal,
78
+ });
79
+ clearTimeout(timeout);
80
+ if (!response.ok) {
81
+ const errorText = await response.text().catch(() => response.statusText);
82
+ log("OpenAI Chat Completion API error", {
83
+ status: response.status,
84
+ error: errorText,
85
+ iteration: iterations,
86
+ });
87
+ return {
88
+ success: false,
89
+ error: `API error: ${response.status} - ${errorText}`,
90
+ iterations,
91
+ };
92
+ }
93
+ const data = (await response.json());
94
+ if (!data.choices || !data.choices[0]) {
95
+ return {
96
+ success: false,
97
+ error: "Invalid API response format",
98
+ iterations,
99
+ };
100
+ }
101
+ const choice = data.choices[0];
102
+ const assistantSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
103
+ const assistantMsg = {
104
+ aiSessionId: session.id,
105
+ sequence: assistantSequence,
106
+ role: "assistant",
107
+ content: choice.message.content || "",
108
+ };
109
+ if (choice.message.tool_calls) {
110
+ assistantMsg.toolCalls = choice.message.tool_calls;
111
+ }
112
+ this.aiSessionManager.addMessage(assistantMsg);
113
+ messages.push(choice.message);
114
+ if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
115
+ const toolCall = choice.message.tool_calls[0];
116
+ if (toolCall && toolCall.function.name === toolSchema.function.name) {
117
+ const parsed = JSON.parse(toolCall.function.arguments);
118
+ return {
119
+ success: true,
120
+ data: this.validateResponse(parsed),
121
+ iterations,
122
+ };
123
+ }
124
+ }
125
+ const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
126
+ const retryPrompt = "Please use the save_memories tool to extract and save the memories from the conversation as instructed.";
127
+ this.aiSessionManager.addMessage({
128
+ aiSessionId: session.id,
129
+ sequence: retrySequence,
130
+ role: "user",
131
+ content: retryPrompt,
132
+ });
133
+ messages.push({ role: "user", content: retryPrompt });
134
+ }
135
+ catch (error) {
136
+ clearTimeout(timeout);
137
+ if (error instanceof Error && error.name === "AbortError") {
138
+ return {
139
+ success: false,
140
+ error: `API request timeout (${this.config.iterationTimeout}ms)`,
141
+ iterations,
142
+ };
143
+ }
144
+ return {
145
+ success: false,
146
+ error: String(error),
147
+ iterations,
148
+ };
149
+ }
150
+ }
151
+ return {
152
+ success: false,
153
+ error: `Max iterations (${this.config.maxIterations}) reached without tool call`,
154
+ iterations,
155
+ };
156
+ }
157
+ validateResponse(data) {
158
+ if (!data || typeof data !== "object") {
159
+ throw new Error("Response is not an object");
160
+ }
161
+ if (data.memories && Array.isArray(data.memories)) {
162
+ const validMemories = data.memories.filter((m) => {
163
+ return (m &&
164
+ typeof m === "object" &&
165
+ typeof m.summary === "string" &&
166
+ m.summary.trim().length > 0 &&
167
+ (m.scope === "user" || m.scope === "project") &&
168
+ typeof m.type === "string" &&
169
+ m.type.trim().length > 0);
170
+ });
171
+ if (validMemories.length === 0) {
172
+ throw new Error("No valid memories in response");
173
+ }
174
+ return { memories: validMemories };
175
+ }
176
+ if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
177
+ return data;
178
+ }
179
+ throw new Error("Invalid response format: missing summary or memories field");
180
+ }
181
+ }
@@ -0,0 +1,14 @@
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 OpenAIResponsesProvider 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 extractToolCall;
11
+ private buildRetryPrompt;
12
+ private validateResponse;
13
+ }
14
+ //# sourceMappingURL=openai-responses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-responses.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-responses.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;AAsBvF,qBAAa,uBAAwB,SAAQ,cAAc;IACzD,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;IAuH1B,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,gBAAgB;CA+BzB"}
@@ -0,0 +1,191 @@
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 OpenAIResponsesProvider extends BaseAIProvider {
6
+ aiSessionManager;
7
+ constructor(config, aiSessionManager) {
8
+ super(config);
9
+ this.aiSessionManager = aiSessionManager;
10
+ }
11
+ getProviderName() {
12
+ return "openai-responses";
13
+ }
14
+ supportsSession() {
15
+ return true;
16
+ }
17
+ async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
18
+ let session = this.aiSessionManager.getSession(sessionId, "openai-responses");
19
+ if (!session) {
20
+ session = this.aiSessionManager.createSession({
21
+ provider: "openai-responses",
22
+ sessionId,
23
+ });
24
+ }
25
+ let conversationId = session.conversationId;
26
+ let currentPrompt = userPrompt;
27
+ let iterations = 0;
28
+ while (iterations < this.config.maxIterations) {
29
+ iterations++;
30
+ const controller = new AbortController();
31
+ const timeout = setTimeout(() => controller.abort(), this.config.iterationTimeout);
32
+ try {
33
+ const tool = ToolSchemaConverter.toResponsesAPI(toolSchema);
34
+ const requestBody = {
35
+ model: this.config.model,
36
+ input: currentPrompt,
37
+ tools: [tool],
38
+ };
39
+ if (conversationId) {
40
+ requestBody.conversation = conversationId;
41
+ }
42
+ else {
43
+ requestBody.instructions = systemPrompt;
44
+ }
45
+ const response = await fetch(`${this.config.apiUrl}/responses`, {
46
+ method: "POST",
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ Authorization: `Bearer ${this.config.apiKey}`,
50
+ },
51
+ body: JSON.stringify(requestBody),
52
+ signal: controller.signal,
53
+ });
54
+ clearTimeout(timeout);
55
+ if (!response.ok) {
56
+ const errorText = await response.text().catch(() => response.statusText);
57
+ log("OpenAI Responses API error", {
58
+ status: response.status,
59
+ error: errorText,
60
+ iteration: iterations,
61
+ });
62
+ return {
63
+ success: false,
64
+ error: `API error: ${response.status} - ${errorText}`,
65
+ iterations,
66
+ };
67
+ }
68
+ const data = (await response.json());
69
+ conversationId = data.conversation || conversationId;
70
+ if (iterations === 1) {
71
+ const userSeq = this.aiSessionManager.getLastSequence(session.id) + 1;
72
+ this.aiSessionManager.addMessage({
73
+ aiSessionId: session.id,
74
+ sequence: userSeq,
75
+ role: "user",
76
+ content: userPrompt,
77
+ });
78
+ }
79
+ const toolCall = this.extractToolCall(data, toolSchema.function.name);
80
+ if (toolCall) {
81
+ this.aiSessionManager.updateSession(sessionId, "openai-responses", {
82
+ conversationId,
83
+ });
84
+ return {
85
+ success: true,
86
+ data: this.validateResponse(toolCall),
87
+ iterations,
88
+ };
89
+ }
90
+ log("No tool call found, retrying", {
91
+ iteration: iterations,
92
+ expectedTool: toolSchema.function.name,
93
+ });
94
+ currentPrompt = this.buildRetryPrompt(data);
95
+ }
96
+ catch (error) {
97
+ clearTimeout(timeout);
98
+ if (error instanceof Error && error.name === "AbortError") {
99
+ return {
100
+ success: false,
101
+ error: `API request timeout (${this.config.iterationTimeout}ms)`,
102
+ iterations,
103
+ };
104
+ }
105
+ return {
106
+ success: false,
107
+ error: String(error),
108
+ iterations,
109
+ };
110
+ }
111
+ }
112
+ return {
113
+ success: false,
114
+ error: `Max iterations (${this.config.maxIterations}) reached without tool call`,
115
+ iterations,
116
+ };
117
+ }
118
+ extractToolCall(data, expectedToolName) {
119
+ if (!data.output || !Array.isArray(data.output)) {
120
+ log("Extract tool call: no output array", { hasOutput: !!data.output });
121
+ return null;
122
+ }
123
+ for (const item of data.output) {
124
+ if (item.type === "function_call" && item.name === expectedToolName) {
125
+ if (item.arguments) {
126
+ try {
127
+ const parsed = JSON.parse(item.arguments);
128
+ return parsed;
129
+ }
130
+ catch (error) {
131
+ log("Failed to parse function call arguments", {
132
+ error: String(error),
133
+ toolName: item.name,
134
+ arguments: item.arguments,
135
+ });
136
+ return null;
137
+ }
138
+ }
139
+ else {
140
+ log("Function call found but no arguments", {
141
+ toolName: item.name,
142
+ callId: item.call_id,
143
+ });
144
+ }
145
+ }
146
+ }
147
+ log("No matching function call found", {
148
+ expectedTool: expectedToolName,
149
+ foundTypes: data.output.map((item) => item.type),
150
+ foundNames: data.output.map((item) => item.name).filter(Boolean),
151
+ });
152
+ return null;
153
+ }
154
+ buildRetryPrompt(data) {
155
+ let assistantResponse = "";
156
+ if (data.output && Array.isArray(data.output)) {
157
+ for (const item of data.output) {
158
+ if (item.type === "message" && item.content) {
159
+ assistantResponse =
160
+ typeof item.content === "string" ? item.content : JSON.stringify(item.content);
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ return `Previous response: ${assistantResponse}\n\nPlease use the save_memories tool to extract and save the memories from the conversation as instructed.`;
166
+ }
167
+ validateResponse(data) {
168
+ if (!data || typeof data !== "object") {
169
+ throw new Error("Response is not an object");
170
+ }
171
+ if (data.memories && Array.isArray(data.memories)) {
172
+ const validMemories = data.memories.filter((m) => {
173
+ return (m &&
174
+ typeof m === "object" &&
175
+ typeof m.summary === "string" &&
176
+ m.summary.trim().length > 0 &&
177
+ (m.scope === "user" || m.scope === "project") &&
178
+ typeof m.type === "string" &&
179
+ m.type.trim().length > 0);
180
+ });
181
+ if (validMemories.length === 0) {
182
+ throw new Error("No valid memories in response");
183
+ }
184
+ return { memories: validMemories };
185
+ }
186
+ if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
187
+ return data;
188
+ }
189
+ throw new Error("Invalid response format: missing summary or memories field");
190
+ }
191
+ }
@@ -0,0 +1,21 @@
1
+ import type { AISession, SessionCreateParams, SessionUpdateParams, AIProviderType, AIMessage } from "./session-types.js";
2
+ export declare class AISessionManager {
3
+ private db;
4
+ private readonly dbPath;
5
+ private readonly sessionRetentionMs;
6
+ constructor();
7
+ private initDatabase;
8
+ getSession(sessionId: string, provider: AIProviderType): AISession | null;
9
+ createSession(params: SessionCreateParams): AISession;
10
+ updateSession(sessionId: string, provider: AIProviderType, updates: SessionUpdateParams): void;
11
+ cleanupExpiredSessions(): number;
12
+ deleteSession(sessionId: string, provider: AIProviderType): void;
13
+ addMessage(message: Omit<AIMessage, "id" | "createdAt">): void;
14
+ getMessages(aiSessionId: string): AIMessage[];
15
+ getLastSequence(aiSessionId: string): number;
16
+ clearMessages(aiSessionId: string): void;
17
+ private rowToSession;
18
+ private rowToMessage;
19
+ }
20
+ export declare const aiSessionManager: AISessionManager;
21
+ //# sourceMappingURL=ai-session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-session-manager.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/session/ai-session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACV,MAAM,oBAAoB,CAAC;AAM5B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;;IAS5C,OAAO,CAAC,YAAY;IAyCpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAYzE,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS;IA2BrD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8B9F,sBAAsB,IAAI,MAAM;IAKhC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAOhE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,IAAI;IAmB9D,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE;IAS7C,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAS5C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIxC,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,YAAY;CAarB;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
@@ -0,0 +1,165 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { join } from "node:path";
3
+ import { connectionManager } from "../../sqlite/connection-manager.js";
4
+ import { CONFIG } from "../../../config.js";
5
+ const AI_SESSIONS_DB_NAME = "ai-sessions.db";
6
+ export class AISessionManager {
7
+ db;
8
+ dbPath;
9
+ sessionRetentionMs;
10
+ constructor() {
11
+ this.dbPath = join(CONFIG.storagePath, AI_SESSIONS_DB_NAME);
12
+ this.db = connectionManager.getConnection(this.dbPath);
13
+ this.sessionRetentionMs = CONFIG.aiSessionRetentionDays * 24 * 60 * 60 * 1000;
14
+ this.initDatabase();
15
+ }
16
+ initDatabase() {
17
+ this.db.run(`
18
+ CREATE TABLE IF NOT EXISTS ai_sessions (
19
+ id TEXT PRIMARY KEY,
20
+ provider TEXT NOT NULL,
21
+ session_id TEXT NOT NULL,
22
+ conversation_id TEXT,
23
+ metadata TEXT,
24
+ created_at INTEGER NOT NULL,
25
+ updated_at INTEGER NOT NULL,
26
+ expires_at INTEGER NOT NULL
27
+ )
28
+ `);
29
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_session_id ON ai_sessions(session_id)");
30
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_expires_at ON ai_sessions(expires_at)");
31
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_provider ON ai_sessions(provider)");
32
+ this.db.run(`
33
+ CREATE TABLE IF NOT EXISTS ai_messages (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ ai_session_id TEXT NOT NULL,
36
+ sequence INTEGER NOT NULL,
37
+ role TEXT NOT NULL,
38
+ content TEXT NOT NULL,
39
+ tool_calls TEXT,
40
+ tool_call_id TEXT,
41
+ content_blocks TEXT,
42
+ created_at INTEGER NOT NULL,
43
+ FOREIGN KEY (ai_session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE
44
+ )
45
+ `);
46
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_messages_session ON ai_messages(ai_session_id, sequence)");
47
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_messages_role ON ai_messages(ai_session_id, role)");
48
+ }
49
+ getSession(sessionId, provider) {
50
+ const stmt = this.db.prepare(`
51
+ SELECT * FROM ai_sessions
52
+ WHERE session_id = ? AND provider = ? AND expires_at > ?
53
+ `);
54
+ const row = stmt.get(sessionId, provider, Date.now());
55
+ if (!row)
56
+ return null;
57
+ return this.rowToSession(row);
58
+ }
59
+ createSession(params) {
60
+ const id = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
61
+ const now = Date.now();
62
+ const expiresAt = now + this.sessionRetentionMs;
63
+ this.db.run(`
64
+ INSERT INTO ai_sessions (
65
+ id, provider, session_id, conversation_id,
66
+ metadata, created_at, updated_at, expires_at
67
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
68
+ `, [
69
+ id,
70
+ params.provider,
71
+ params.sessionId,
72
+ params.conversationId || null,
73
+ JSON.stringify(params.metadata || {}),
74
+ now,
75
+ now,
76
+ expiresAt,
77
+ ]);
78
+ return this.getSession(params.sessionId, params.provider);
79
+ }
80
+ updateSession(sessionId, provider, updates) {
81
+ const fields = [];
82
+ const values = [];
83
+ if (updates.conversationId !== undefined) {
84
+ fields.push("conversation_id = ?");
85
+ values.push(updates.conversationId);
86
+ }
87
+ if (updates.metadata !== undefined) {
88
+ fields.push("metadata = ?");
89
+ values.push(JSON.stringify(updates.metadata));
90
+ }
91
+ fields.push("updated_at = ?");
92
+ values.push(Date.now());
93
+ values.push(sessionId);
94
+ values.push(provider);
95
+ this.db.run(`
96
+ UPDATE ai_sessions
97
+ SET ${fields.join(", ")}
98
+ WHERE session_id = ? AND provider = ?
99
+ `, values);
100
+ }
101
+ cleanupExpiredSessions() {
102
+ const result = this.db.run(`DELETE FROM ai_sessions WHERE expires_at < ?`, [Date.now()]);
103
+ return result.changes;
104
+ }
105
+ deleteSession(sessionId, provider) {
106
+ this.db.run(`DELETE FROM ai_sessions WHERE session_id = ? AND provider = ?`, [
107
+ sessionId,
108
+ provider,
109
+ ]);
110
+ }
111
+ addMessage(message) {
112
+ this.db.run(`INSERT INTO ai_messages (
113
+ ai_session_id, sequence, role, content,
114
+ tool_calls, tool_call_id, content_blocks, created_at
115
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
116
+ message.aiSessionId,
117
+ message.sequence,
118
+ message.role,
119
+ message.content,
120
+ message.toolCalls ? JSON.stringify(message.toolCalls) : null,
121
+ message.toolCallId || null,
122
+ message.contentBlocks ? JSON.stringify(message.contentBlocks) : null,
123
+ Date.now(),
124
+ ]);
125
+ }
126
+ getMessages(aiSessionId) {
127
+ const stmt = this.db.prepare("SELECT * FROM ai_messages WHERE ai_session_id = ? ORDER BY sequence ASC");
128
+ const rows = stmt.all(aiSessionId);
129
+ return rows.map(this.rowToMessage);
130
+ }
131
+ getLastSequence(aiSessionId) {
132
+ const stmt = this.db.prepare("SELECT MAX(sequence) as max_seq FROM ai_messages WHERE ai_session_id = ?");
133
+ const row = stmt.get(aiSessionId);
134
+ return row?.max_seq ?? -1;
135
+ }
136
+ clearMessages(aiSessionId) {
137
+ this.db.run("DELETE FROM ai_messages WHERE ai_session_id = ?", [aiSessionId]);
138
+ }
139
+ rowToSession(row) {
140
+ return {
141
+ id: row.id,
142
+ provider: row.provider,
143
+ sessionId: row.session_id,
144
+ conversationId: row.conversation_id,
145
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
146
+ createdAt: row.created_at,
147
+ updatedAt: row.updated_at,
148
+ expiresAt: row.expires_at,
149
+ };
150
+ }
151
+ rowToMessage(row) {
152
+ return {
153
+ id: row.id,
154
+ aiSessionId: row.ai_session_id,
155
+ sequence: row.sequence,
156
+ role: row.role,
157
+ content: row.content,
158
+ toolCalls: row.tool_calls ? JSON.parse(row.tool_calls) : undefined,
159
+ toolCallId: row.tool_call_id,
160
+ contentBlocks: row.content_blocks ? JSON.parse(row.content_blocks) : undefined,
161
+ createdAt: row.created_at,
162
+ };
163
+ }
164
+ }
165
+ export const aiSessionManager = new AISessionManager();
@@ -0,0 +1,43 @@
1
+ export type AIProviderType = "openai-chat" | "openai-responses" | "anthropic";
2
+ export interface AIMessage {
3
+ id?: number;
4
+ aiSessionId: string;
5
+ sequence: number;
6
+ role: "system" | "user" | "assistant" | "tool";
7
+ content: string;
8
+ toolCalls?: Array<{
9
+ id: string;
10
+ type: "function";
11
+ function: {
12
+ name: string;
13
+ arguments: string;
14
+ };
15
+ }>;
16
+ toolCallId?: string;
17
+ contentBlocks?: Array<{
18
+ type: string;
19
+ [key: string]: any;
20
+ }>;
21
+ createdAt: number;
22
+ }
23
+ export interface AISession {
24
+ id: string;
25
+ provider: AIProviderType;
26
+ sessionId: string;
27
+ conversationId?: string;
28
+ metadata?: Record<string, any>;
29
+ createdAt: number;
30
+ updatedAt: number;
31
+ expiresAt: number;
32
+ }
33
+ export interface SessionCreateParams {
34
+ provider: AIProviderType;
35
+ sessionId: string;
36
+ conversationId?: string;
37
+ metadata?: Record<string, any>;
38
+ }
39
+ export interface SessionUpdateParams {
40
+ conversationId?: string;
41
+ metadata?: Record<string, any>;
42
+ }
43
+ //# sourceMappingURL=session-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-types.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/session/session-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,kBAAkB,GAAG,WAAW,CAAC;AAE9E,MAAM,WAAW,SAAS;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/C,CAAC,CAAC;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ export interface ChatCompletionTool {
2
+ type: "function";
3
+ function: {
4
+ name: string;
5
+ description: string;
6
+ parameters: {
7
+ type: string;
8
+ properties: Record<string, any>;
9
+ required: string[];
10
+ };
11
+ };
12
+ }
13
+ export interface ResponsesAPITool {
14
+ type: "function";
15
+ name: string;
16
+ description: string;
17
+ parameters: {
18
+ type: string;
19
+ properties: Record<string, any>;
20
+ required: string[];
21
+ };
22
+ }
23
+ export interface AnthropicTool {
24
+ name: string;
25
+ description: string;
26
+ input_schema: {
27
+ type: string;
28
+ properties: Record<string, any>;
29
+ required: string[];
30
+ };
31
+ }
32
+ export declare class ToolSchemaConverter {
33
+ static toResponsesAPI(chatCompletionTool: ChatCompletionTool): ResponsesAPITool;
34
+ static toAnthropic(chatCompletionTool: ChatCompletionTool): AnthropicTool;
35
+ static fromChatCompletion(tool: ChatCompletionTool): {
36
+ chatCompletion: ChatCompletionTool;
37
+ responsesAPI: ResponsesAPITool;
38
+ anthropic: AnthropicTool;
39
+ };
40
+ }
41
+ //# sourceMappingURL=tool-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-schema.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/tools/tool-schema.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE;YACV,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;SACpB,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,cAAc,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,gBAAgB;IAS/E,MAAM,CAAC,WAAW,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,aAAa;IAQzE,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG;QACnD,cAAc,EAAE,kBAAkB,CAAC;QACnC,YAAY,EAAE,gBAAgB,CAAC;QAC/B,SAAS,EAAE,aAAa,CAAC;KAC1B;CAOF"}