opencode-mem 2.7.6 → 2.8.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 CHANGED
@@ -28,7 +28,7 @@ Add to your OpenCode configuration at `~/.config/opencode/opencode.json`:
28
28
 
29
29
  ```jsonc
30
30
  {
31
- "plugins": ["opencode-mem"],
31
+ "plugin": ["opencode-mem"],
32
32
  }
33
33
  ```
34
34
 
@@ -1 +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"}
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;AAMnF,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;IAmB3F,MAAM,CAAC,qBAAqB,IAAI,cAAc,EAAE;IAIhD,MAAM,CAAC,sBAAsB,IAAI,MAAM;CAGxC"}
@@ -2,6 +2,7 @@ import { BaseAIProvider } from "./providers/base-provider.js";
2
2
  import { OpenAIChatCompletionProvider } from "./providers/openai-chat-completion.js";
3
3
  import { OpenAIResponsesProvider } from "./providers/openai-responses.js";
4
4
  import { AnthropicMessagesProvider } from "./providers/anthropic-messages.js";
5
+ import { GoogleGeminiProvider } from "./providers/google-gemini.js";
5
6
  import { aiSessionManager } from "./session/ai-session-manager.js";
6
7
  export class AIProviderFactory {
7
8
  static createProvider(providerType, config) {
@@ -12,12 +13,14 @@ export class AIProviderFactory {
12
13
  return new OpenAIResponsesProvider(config, aiSessionManager);
13
14
  case "anthropic":
14
15
  return new AnthropicMessagesProvider(config, aiSessionManager);
16
+ case "google-gemini":
17
+ return new GoogleGeminiProvider(config, aiSessionManager);
15
18
  default:
16
19
  throw new Error(`Unknown provider type: ${providerType}`);
17
20
  }
18
21
  }
19
22
  static getSupportedProviders() {
20
- return ["openai-chat", "openai-responses", "anthropic"];
23
+ return ["openai-chat", "openai-responses", "anthropic", "google-gemini"];
21
24
  }
22
25
  static cleanupExpiredSessions() {
23
26
  return aiSessionManager.cleanupExpiredSessions();
@@ -0,0 +1,16 @@
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
+ /**
5
+ * Google Gemini Provider
6
+ * Supports Google's Gemini models (e.g. gemini-1.5-flash) via Google AI Studio API.
7
+ */
8
+ export declare class GoogleGeminiProvider extends BaseAIProvider {
9
+ private aiSessionManager;
10
+ constructor(config: any, aiSessionManager: AISessionManager);
11
+ getProviderName(): string;
12
+ supportsSession(): boolean;
13
+ private addToolResponse;
14
+ executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
15
+ }
16
+ //# sourceMappingURL=google-gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-gemini.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/google-gemini.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;AAIlE;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,cAAc;IACtD,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAI1B,OAAO,CAAC,eAAe;IA4BjB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;CAyN3B"}
@@ -0,0 +1,226 @@
1
+ import { BaseAIProvider } from "./base-provider.js";
2
+ import { AISessionManager } from "../session/ai-session-manager.js";
3
+ import { log } from "../../logger.js";
4
+ import { UserProfileValidator } from "../validators/user-profile-validator.js";
5
+ /**
6
+ * Google Gemini Provider
7
+ * Supports Google's Gemini models (e.g. gemini-1.5-flash) via Google AI Studio API.
8
+ */
9
+ export class GoogleGeminiProvider extends BaseAIProvider {
10
+ aiSessionManager;
11
+ constructor(config, aiSessionManager) {
12
+ super(config);
13
+ this.aiSessionManager = aiSessionManager;
14
+ }
15
+ getProviderName() {
16
+ return "google-gemini";
17
+ }
18
+ supportsSession() {
19
+ return true;
20
+ }
21
+ addToolResponse(sessionId, messages, toolCallId, content) {
22
+ const sequence = this.aiSessionManager.getLastSequence(sessionId) + 1;
23
+ this.aiSessionManager.addMessage({
24
+ aiSessionId: sessionId,
25
+ sequence,
26
+ role: "tool",
27
+ content,
28
+ toolCallId,
29
+ });
30
+ // Gemini tool response format
31
+ messages.push({
32
+ role: "function",
33
+ parts: [
34
+ {
35
+ functionResponse: {
36
+ name: toolCallId.split(":")[0], // Gemini expects the name of the function
37
+ response: JSON.parse(content),
38
+ },
39
+ },
40
+ ],
41
+ });
42
+ }
43
+ async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
44
+ let session = this.aiSessionManager.getSession(sessionId, "google-gemini");
45
+ if (!session) {
46
+ session = this.aiSessionManager.createSession({
47
+ provider: "google-gemini",
48
+ sessionId,
49
+ });
50
+ }
51
+ const existingMessages = this.aiSessionManager.getMessages(session.id);
52
+ const contents = [];
53
+ // System instruction is separate in Gemini API
54
+ const geminiSystemInstruction = {
55
+ parts: [{ text: systemPrompt }],
56
+ };
57
+ // Convert existing messages to Gemini format
58
+ for (const msg of existingMessages) {
59
+ if (msg.role === "system")
60
+ continue; // Skip system as it's passed separately
61
+ const role = msg.role === "assistant" ? "model" : "user";
62
+ const parts = [];
63
+ if (msg.content) {
64
+ parts.push({ text: msg.content });
65
+ }
66
+ if (msg.toolCalls) {
67
+ for (const tc of msg.toolCalls) {
68
+ parts.push({
69
+ functionCall: {
70
+ name: tc.function.name,
71
+ args: JSON.parse(tc.function.arguments),
72
+ },
73
+ });
74
+ }
75
+ }
76
+ if (msg.role === "tool") {
77
+ contents.push({
78
+ role: "function",
79
+ parts: [
80
+ {
81
+ functionResponse: {
82
+ name: (msg.toolCallId || "").split(":")[0],
83
+ response: JSON.parse(msg.content),
84
+ },
85
+ },
86
+ ],
87
+ });
88
+ continue;
89
+ }
90
+ contents.push({ role, parts });
91
+ }
92
+ if (contents.length === 0 || contents[contents.length - 1].role !== "user") {
93
+ const userSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
94
+ this.aiSessionManager.addMessage({
95
+ aiSessionId: session.id,
96
+ sequence: userSequence,
97
+ role: "user",
98
+ content: userPrompt,
99
+ });
100
+ contents.push({ role: "user", parts: [{ text: userPrompt }] });
101
+ }
102
+ let iterations = 0;
103
+ const maxIterations = this.config.maxIterations ?? 5;
104
+ const iterationTimeout = this.config.iterationTimeout ?? 30000;
105
+ // Gemini API expects the tool name as a function declaration
106
+ const tools = [
107
+ {
108
+ functionDeclarations: [
109
+ {
110
+ name: toolSchema.function.name,
111
+ description: toolSchema.function.description,
112
+ parameters: toolSchema.function.parameters,
113
+ },
114
+ ],
115
+ },
116
+ ];
117
+ while (iterations < maxIterations) {
118
+ iterations++;
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), iterationTimeout);
121
+ try {
122
+ const baseUrl = this.config.apiUrl || "https://generativelanguage.googleapis.com/v1beta";
123
+ const url = `${baseUrl}/models/${this.config.model}:generateContent?key=${this.config.apiKey}`;
124
+ const requestBody = {
125
+ contents,
126
+ systemInstruction: geminiSystemInstruction,
127
+ tools,
128
+ toolConfig: {
129
+ functionCallingConfig: {
130
+ mode: "ANY", // Force function calling
131
+ allowedFunctionNames: [toolSchema.function.name],
132
+ },
133
+ },
134
+ generationConfig: {
135
+ temperature: this.config.memoryTemperature ?? 0.3,
136
+ },
137
+ };
138
+ const response = await fetch(url, {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json" },
141
+ body: JSON.stringify(requestBody),
142
+ signal: controller.signal,
143
+ });
144
+ clearTimeout(timeout);
145
+ if (!response.ok) {
146
+ const errorText = await response.text().catch(() => response.statusText);
147
+ log("Gemini API error", {
148
+ status: response.status,
149
+ error: errorText,
150
+ iteration: iterations,
151
+ });
152
+ return {
153
+ success: false,
154
+ error: `Gemini API error: ${response.status} - ${errorText}`,
155
+ iterations,
156
+ };
157
+ }
158
+ const data = (await response.json());
159
+ const candidate = data.candidates?.[0];
160
+ if (!candidate || !candidate.content) {
161
+ return { success: false, error: "Invalid Gemini API response format", iterations };
162
+ }
163
+ const modelMsg = candidate.content;
164
+ const assistantSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
165
+ // Map Gemini response back to our internal message format
166
+ const assistantMsg = {
167
+ aiSessionId: session.id,
168
+ sequence: assistantSequence,
169
+ role: "assistant",
170
+ content: "",
171
+ toolCalls: [],
172
+ };
173
+ for (const part of modelMsg.parts) {
174
+ if (part.text)
175
+ assistantMsg.content += part.text;
176
+ if (part.functionCall) {
177
+ assistantMsg.toolCalls.push({
178
+ id: `${part.functionCall.name}:${Date.now()}`,
179
+ type: "function",
180
+ function: {
181
+ name: part.functionCall.name,
182
+ arguments: JSON.stringify(part.functionCall.args),
183
+ },
184
+ });
185
+ }
186
+ }
187
+ this.aiSessionManager.addMessage(assistantMsg);
188
+ contents.push(modelMsg);
189
+ if (assistantMsg.toolCalls.length > 0) {
190
+ for (const toolCall of assistantMsg.toolCalls) {
191
+ if (toolCall.function.name === toolSchema.function.name) {
192
+ try {
193
+ const parsed = JSON.parse(toolCall.function.arguments);
194
+ const result = UserProfileValidator.validate(parsed);
195
+ if (!result.valid)
196
+ throw new Error(result.errors.join(", "));
197
+ this.addToolResponse(session.id, contents, toolCall.id, JSON.stringify({ success: true }));
198
+ return { success: true, data: result.data, iterations };
199
+ }
200
+ catch (validationError) {
201
+ const errorMessage = `Validation failed: ${String(validationError)}`;
202
+ this.addToolResponse(session.id, contents, toolCall.id, JSON.stringify({ success: false, error: errorMessage }));
203
+ return { success: false, error: errorMessage, iterations };
204
+ }
205
+ }
206
+ }
207
+ }
208
+ // Retry if no tool call was made
209
+ const retryPrompt = "Please use the save_memories tool as instructed.";
210
+ const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
211
+ this.aiSessionManager.addMessage({
212
+ aiSessionId: session.id,
213
+ sequence: retrySequence,
214
+ role: "user",
215
+ content: retryPrompt,
216
+ });
217
+ contents.push({ role: "user", parts: [{ text: retryPrompt }] });
218
+ }
219
+ catch (error) {
220
+ clearTimeout(timeout);
221
+ return { success: false, error: String(error), iterations };
222
+ }
223
+ }
224
+ return { success: false, error: `Max iterations (${maxIterations}) reached`, iterations };
225
+ }
226
+ }
@@ -1,4 +1,4 @@
1
- export type AIProviderType = "openai-chat" | "openai-responses" | "anthropic";
1
+ export type AIProviderType = "openai-chat" | "openai-responses" | "anthropic" | "google-gemini";
2
2
  export interface AIMessage {
3
3
  id?: number;
4
4
  aiSessionId: string;
@@ -1 +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"}
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,GAAG,eAAe,CAAC;AAEhG,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mem",
3
- "version": "2.7.6",
3
+ "version": "2.8.0",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",