@haisto/opencode-mem 2.14.3-beta.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 +165 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +457 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +486 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +5 -0
- 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 +28 -0
- package/dist/services/ai/opencode-provider.d.ts +36 -0
- package/dist/services/ai/opencode-provider.d.ts.map +1 -0
- package/dist/services/ai/opencode-provider.js +92 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.d.ts.map +1 -0
- package/dist/services/ai/provider-config.js +14 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +12 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +184 -0
- package/dist/services/ai/providers/base-provider.d.ts +25 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +23 -0
- package/dist/services/ai/providers/google-gemini.d.ts +16 -0
- package/dist/services/ai/providers/google-gemini.d.ts.map +1 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +14 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +318 -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 +182 -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 +166 -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/ai/validators/user-profile-validator.d.ts +13 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
- package/dist/services/ai/validators/user-profile-validator.js +111 -0
- package/dist/services/api-handlers.d.ts +164 -0
- package/dist/services/api-handlers.d.ts.map +1 -0
- package/dist/services/api-handlers.js +927 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.d.ts.map +1 -0
- package/dist/services/auto-capture.js +309 -0
- package/dist/services/cleanup-service.d.ts +23 -0
- package/dist/services/cleanup-service.d.ts.map +1 -0
- package/dist/services/cleanup-service.js +102 -0
- package/dist/services/client.d.ts +119 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +257 -0
- package/dist/services/context.d.ts +11 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +34 -0
- package/dist/services/deduplication-service.d.ts +30 -0
- package/dist/services/deduplication-service.d.ts.map +1 -0
- package/dist/services/deduplication-service.js +124 -0
- package/dist/services/embedding.d.ts +15 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +127 -0
- package/dist/services/jsonc.d.ts +7 -0
- package/dist/services/jsonc.d.ts.map +1 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +3 -0
- package/dist/services/language-detector.d.ts.map +1 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/logger.d.ts +11 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +97 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.d.ts.map +1 -0
- package/dist/services/migration-service.js +250 -0
- package/dist/services/privacy.d.ts +3 -0
- package/dist/services/privacy.d.ts.map +1 -0
- package/dist/services/privacy.js +7 -0
- package/dist/services/secret-resolver.d.ts +2 -0
- package/dist/services/secret-resolver.d.ts.map +1 -0
- package/dist/services/secret-resolver.js +55 -0
- package/dist/services/sqlite/connection-manager.d.ts +13 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
- package/dist/services/sqlite/connection-manager.js +74 -0
- package/dist/services/sqlite/shard-manager.d.ts +23 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
- package/dist/services/sqlite/shard-manager.js +288 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts +2 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
- package/dist/services/sqlite/sqlite-bootstrap.js +8 -0
- package/dist/services/sqlite/types.d.ts +42 -0
- package/dist/services/sqlite/types.d.ts.map +1 -0
- package/dist/services/sqlite/types.js +1 -0
- package/dist/services/sqlite/vector-search.d.ts +29 -0
- package/dist/services/sqlite/vector-search.d.ts.map +1 -0
- package/dist/services/sqlite/vector-search.js +279 -0
- package/dist/services/tags.d.ts +24 -0
- package/dist/services/tags.d.ts.map +1 -0
- package/dist/services/tags.js +145 -0
- 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 +235 -0
- package/dist/services/user-profile/profile-context.d.ts +2 -0
- package/dist/services/user-profile/profile-context.d.ts.map +1 -0
- package/dist/services/user-profile/profile-context.js +40 -0
- package/dist/services/user-profile/profile-utils.d.ts +3 -0
- package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +46 -0
- package/dist/services/user-profile/types.d.ts.map +1 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +23 -0
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
- package/dist/services/user-profile/user-profile-manager.js +337 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +41 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +192 -0
- package/dist/services/vector-backends/backend-factory.d.ts +3 -0
- package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
- package/dist/services/vector-backends/backend-factory.js +104 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/exact-scan-backend.js +63 -0
- package/dist/services/vector-backends/types.d.ts +51 -0
- package/dist/services/vector-backends/types.d.ts.map +1 -0
- package/dist/services/vector-backends/types.js +1 -0
- package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
- package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/usearch-backend.js +174 -0
- package/dist/services/web-server-worker.d.ts +2 -0
- package/dist/services/web-server-worker.d.ts.map +1 -0
- package/dist/services/web-server-worker.js +283 -0
- package/dist/services/web-server.d.ts +31 -0
- package/dist/services/web-server.d.ts.map +1 -0
- package/dist/services/web-server.js +356 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +1238 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/i18n.d.ts +2 -0
- package/dist/web/i18n.d.ts.map +1 -0
- package/dist/web/i18n.js +312 -0
- package/dist/web/index.html +293 -0
- package/dist/web/styles.css +1786 -0
- package/package.json +78 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { BaseAIProvider, applySafeExtraParams, } from "./base-provider.js";
|
|
2
|
+
import { log } from "../../logger.js";
|
|
3
|
+
import { UserProfileValidator } from "../validators/user-profile-validator.js";
|
|
4
|
+
function isErrorResponseBody(data) {
|
|
5
|
+
return (typeof data === "object" &&
|
|
6
|
+
data !== null &&
|
|
7
|
+
typeof data.status === "string" &&
|
|
8
|
+
typeof data.msg === "string");
|
|
9
|
+
}
|
|
10
|
+
function hasNonEmptyChoices(data) {
|
|
11
|
+
if (typeof data !== "object" || data === null)
|
|
12
|
+
return false;
|
|
13
|
+
const { choices } = data;
|
|
14
|
+
if (!Array.isArray(choices) || choices.length === 0)
|
|
15
|
+
return false;
|
|
16
|
+
const first = choices[0];
|
|
17
|
+
if (typeof first !== "object" || first === null)
|
|
18
|
+
return false;
|
|
19
|
+
if (typeof first.message !== "object" || first.message === null)
|
|
20
|
+
return false;
|
|
21
|
+
const { content, tool_calls } = first.message;
|
|
22
|
+
if (content !== undefined && content !== null && typeof content !== "string")
|
|
23
|
+
return false;
|
|
24
|
+
if (tool_calls !== undefined && !Array.isArray(tool_calls))
|
|
25
|
+
return false;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
export class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
29
|
+
aiSessionManager;
|
|
30
|
+
constructor(config, aiSessionManager) {
|
|
31
|
+
super(config);
|
|
32
|
+
this.aiSessionManager = aiSessionManager;
|
|
33
|
+
}
|
|
34
|
+
getProviderName() {
|
|
35
|
+
return "openai-chat";
|
|
36
|
+
}
|
|
37
|
+
supportsSession() {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
addToolResponse(sessionId, messages, toolCallId, content) {
|
|
41
|
+
const sequence = this.aiSessionManager.getLastSequence(sessionId) + 1;
|
|
42
|
+
this.aiSessionManager.addMessage({
|
|
43
|
+
aiSessionId: sessionId,
|
|
44
|
+
sequence,
|
|
45
|
+
role: "tool",
|
|
46
|
+
content,
|
|
47
|
+
toolCallId,
|
|
48
|
+
});
|
|
49
|
+
messages.push({
|
|
50
|
+
role: "tool",
|
|
51
|
+
tool_call_id: toolCallId,
|
|
52
|
+
content,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
filterIncompleteToolCallSequences(messages) {
|
|
56
|
+
const result = [];
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (i < messages.length) {
|
|
59
|
+
const msg = messages[i];
|
|
60
|
+
if (!msg) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
64
|
+
const toolCallIds = new Set(msg.toolCalls.map((tc) => tc.id));
|
|
65
|
+
const toolResponses = [];
|
|
66
|
+
let j = i + 1;
|
|
67
|
+
while (j < messages.length && messages[j]?.role === "tool") {
|
|
68
|
+
const toolMessage = messages[j];
|
|
69
|
+
if (toolMessage?.toolCallId && toolCallIds.has(toolMessage.toolCallId)) {
|
|
70
|
+
toolResponses.push(toolMessage);
|
|
71
|
+
toolCallIds.delete(toolMessage.toolCallId);
|
|
72
|
+
}
|
|
73
|
+
j++;
|
|
74
|
+
}
|
|
75
|
+
if (toolCallIds.size === 0) {
|
|
76
|
+
result.push(msg);
|
|
77
|
+
toolResponses.forEach((tr) => result.push(tr));
|
|
78
|
+
i = j;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
result.push(msg);
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
|
|
92
|
+
let session = this.aiSessionManager.getSession(sessionId, "openai-chat");
|
|
93
|
+
if (!session) {
|
|
94
|
+
session = this.aiSessionManager.createSession({
|
|
95
|
+
provider: "openai-chat",
|
|
96
|
+
sessionId,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const existingMessages = this.aiSessionManager.getMessages(session.id);
|
|
100
|
+
const messages = [];
|
|
101
|
+
const validatedMessages = this.filterIncompleteToolCallSequences(existingMessages);
|
|
102
|
+
for (const msg of validatedMessages) {
|
|
103
|
+
const apiMsg = {
|
|
104
|
+
role: msg.role,
|
|
105
|
+
content: msg.content,
|
|
106
|
+
};
|
|
107
|
+
if (msg.toolCalls) {
|
|
108
|
+
apiMsg.tool_calls = msg.toolCalls;
|
|
109
|
+
}
|
|
110
|
+
if (msg.toolCallId) {
|
|
111
|
+
apiMsg.tool_call_id = msg.toolCallId;
|
|
112
|
+
}
|
|
113
|
+
messages.push(apiMsg);
|
|
114
|
+
}
|
|
115
|
+
if (messages.length === 0) {
|
|
116
|
+
const sequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
117
|
+
this.aiSessionManager.addMessage({
|
|
118
|
+
aiSessionId: session.id,
|
|
119
|
+
sequence,
|
|
120
|
+
role: "system",
|
|
121
|
+
content: systemPrompt,
|
|
122
|
+
});
|
|
123
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
124
|
+
}
|
|
125
|
+
const userSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
126
|
+
this.aiSessionManager.addMessage({
|
|
127
|
+
aiSessionId: session.id,
|
|
128
|
+
sequence: userSequence,
|
|
129
|
+
role: "user",
|
|
130
|
+
content: userPrompt,
|
|
131
|
+
});
|
|
132
|
+
messages.push({ role: "user", content: userPrompt });
|
|
133
|
+
let iterations = 0;
|
|
134
|
+
const maxIterations = this.config.maxIterations ?? 5;
|
|
135
|
+
const iterationTimeout = this.config.iterationTimeout ?? 30000;
|
|
136
|
+
while (iterations < maxIterations) {
|
|
137
|
+
iterations++;
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
const timeout = setTimeout(() => controller.abort(), iterationTimeout);
|
|
140
|
+
try {
|
|
141
|
+
const requestBody = {
|
|
142
|
+
model: this.config.model,
|
|
143
|
+
messages,
|
|
144
|
+
tools: [toolSchema],
|
|
145
|
+
tool_choice: "auto",
|
|
146
|
+
};
|
|
147
|
+
if (this.config.memoryTemperature !== false) {
|
|
148
|
+
requestBody.temperature = this.config.memoryTemperature ?? 0.3;
|
|
149
|
+
}
|
|
150
|
+
if (this.config.extraParams) {
|
|
151
|
+
applySafeExtraParams(requestBody, this.config.extraParams);
|
|
152
|
+
}
|
|
153
|
+
const headers = {
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
};
|
|
156
|
+
if (this.config.apiKey) {
|
|
157
|
+
headers.Authorization = `Bearer ${this.config.apiKey}`;
|
|
158
|
+
}
|
|
159
|
+
const response = await fetch(`${this.config.apiUrl}/chat/completions`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers,
|
|
162
|
+
body: JSON.stringify(requestBody),
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
});
|
|
165
|
+
clearTimeout(timeout);
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
168
|
+
log("OpenAI Chat Completion API error", {
|
|
169
|
+
provider: this.getProviderName(),
|
|
170
|
+
model: this.config.model,
|
|
171
|
+
status: response.status,
|
|
172
|
+
error: errorText,
|
|
173
|
+
iteration: iterations,
|
|
174
|
+
});
|
|
175
|
+
let errorMessage = `API error: ${response.status} - ${errorText}`;
|
|
176
|
+
if (response.status === 400 &&
|
|
177
|
+
errorText.includes("unsupported_value") &&
|
|
178
|
+
errorText.includes("temperature")) {
|
|
179
|
+
errorMessage =
|
|
180
|
+
'Your model does not support the temperature parameter. Add "memoryTemperature": false to your config file to disable it.';
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: errorMessage,
|
|
185
|
+
iterations,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const data = await response.json();
|
|
189
|
+
if (isErrorResponseBody(data)) {
|
|
190
|
+
log("API returned error in response body", {
|
|
191
|
+
provider: this.getProviderName(),
|
|
192
|
+
model: this.config.model,
|
|
193
|
+
status: data.status,
|
|
194
|
+
msg: data.msg,
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: `API error: ${data.status} - ${data.msg}`,
|
|
199
|
+
iterations,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (!hasNonEmptyChoices(data)) {
|
|
203
|
+
const choices = typeof data === "object" && data !== null
|
|
204
|
+
? data.choices
|
|
205
|
+
: undefined;
|
|
206
|
+
log("Invalid API response format", {
|
|
207
|
+
provider: this.getProviderName(),
|
|
208
|
+
model: this.config.model,
|
|
209
|
+
response: JSON.stringify(data).slice(0, 1000),
|
|
210
|
+
hasChoices: Array.isArray(choices),
|
|
211
|
+
choicesLength: Array.isArray(choices) ? choices.length : undefined,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
error: "Invalid API response format",
|
|
216
|
+
iterations,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const choice = data.choices[0];
|
|
220
|
+
if (!choice) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
error: "Invalid API response format",
|
|
224
|
+
iterations,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const assistantSequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
228
|
+
const assistantMsg = {
|
|
229
|
+
aiSessionId: session.id,
|
|
230
|
+
sequence: assistantSequence,
|
|
231
|
+
role: "assistant",
|
|
232
|
+
content: choice.message.content ?? "",
|
|
233
|
+
};
|
|
234
|
+
if (choice.message.tool_calls) {
|
|
235
|
+
assistantMsg.toolCalls = choice.message.tool_calls;
|
|
236
|
+
}
|
|
237
|
+
this.aiSessionManager.addMessage(assistantMsg);
|
|
238
|
+
messages.push({
|
|
239
|
+
role: "assistant",
|
|
240
|
+
content: choice.message.content ?? null,
|
|
241
|
+
tool_calls: choice.message.tool_calls,
|
|
242
|
+
});
|
|
243
|
+
if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
|
|
244
|
+
for (const toolCall of choice.message.tool_calls) {
|
|
245
|
+
const toolCallId = toolCall.id;
|
|
246
|
+
if (toolCall.function.name === toolSchema.function.name) {
|
|
247
|
+
try {
|
|
248
|
+
const parsed = JSON.parse(toolCall.function.arguments);
|
|
249
|
+
const result = UserProfileValidator.validate(parsed);
|
|
250
|
+
if (!result.valid) {
|
|
251
|
+
throw new Error(result.errors.join(", "));
|
|
252
|
+
}
|
|
253
|
+
this.addToolResponse(session.id, messages, toolCallId, JSON.stringify({ success: true }));
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
data: result.data,
|
|
257
|
+
iterations,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
catch (validationError) {
|
|
261
|
+
const errorStack = validationError instanceof Error ? validationError.stack : undefined;
|
|
262
|
+
log("OpenAI tool response validation failed", {
|
|
263
|
+
error: String(validationError),
|
|
264
|
+
stack: errorStack,
|
|
265
|
+
errorType: validationError instanceof Error
|
|
266
|
+
? validationError.constructor.name
|
|
267
|
+
: typeof validationError,
|
|
268
|
+
toolName: toolSchema.function.name,
|
|
269
|
+
iteration: iterations,
|
|
270
|
+
rawArguments: toolCall.function.arguments.slice(0, 500),
|
|
271
|
+
});
|
|
272
|
+
const errorMessage = `Validation failed: ${String(validationError)}`;
|
|
273
|
+
this.addToolResponse(session.id, messages, toolCallId, JSON.stringify({ success: false, error: errorMessage }));
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: errorMessage,
|
|
277
|
+
iterations,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const wrongToolMessage = `Wrong tool called. Please use ${toolSchema.function.name} instead.`;
|
|
282
|
+
this.addToolResponse(session.id, messages, toolCallId, JSON.stringify({ success: false, error: wrongToolMessage }));
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
287
|
+
const retryPrompt = "Please use the save_memories tool to extract and save the memories from the conversation as instructed.";
|
|
288
|
+
this.aiSessionManager.addMessage({
|
|
289
|
+
aiSessionId: session.id,
|
|
290
|
+
sequence: retrySequence,
|
|
291
|
+
role: "user",
|
|
292
|
+
content: retryPrompt,
|
|
293
|
+
});
|
|
294
|
+
messages.push({ role: "user", content: retryPrompt });
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: `API request timeout (${iterationTimeout}ms)`,
|
|
302
|
+
iterations,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
error: String(error),
|
|
308
|
+
iterations,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
success: false,
|
|
314
|
+
error: `Max iterations (${maxIterations}) reached without tool call`,
|
|
315
|
+
iterations,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -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,EAAwB,MAAM,oBAAoB,CAAC;AAC/F,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;IA0H1B,OAAO,CAAC,eAAe;IA+BvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,gBAAgB;CAsBzB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { BaseAIProvider, applySafeExtraParams } 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
|
+
const maxIterations = this.config.maxIterations ?? 5;
|
|
29
|
+
const iterationTimeout = this.config.iterationTimeout ?? 30000;
|
|
30
|
+
while (iterations < maxIterations) {
|
|
31
|
+
iterations++;
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), iterationTimeout);
|
|
34
|
+
try {
|
|
35
|
+
const tool = ToolSchemaConverter.toResponsesAPI(toolSchema);
|
|
36
|
+
const requestBody = {
|
|
37
|
+
model: this.config.model,
|
|
38
|
+
input: currentPrompt,
|
|
39
|
+
tools: [tool],
|
|
40
|
+
};
|
|
41
|
+
if (conversationId) {
|
|
42
|
+
requestBody.conversation = conversationId;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
requestBody.instructions = systemPrompt;
|
|
46
|
+
}
|
|
47
|
+
if (this.config.extraParams) {
|
|
48
|
+
applySafeExtraParams(requestBody, this.config.extraParams);
|
|
49
|
+
}
|
|
50
|
+
const response = await fetch(`${this.config.apiUrl}/responses`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify(requestBody),
|
|
57
|
+
signal: controller.signal,
|
|
58
|
+
});
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
62
|
+
log("OpenAI Responses API error", {
|
|
63
|
+
provider: this.getProviderName(),
|
|
64
|
+
model: this.config.model,
|
|
65
|
+
status: response.status,
|
|
66
|
+
error: errorText,
|
|
67
|
+
iteration: iterations,
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: `API error: ${response.status} - ${errorText}`,
|
|
72
|
+
iterations,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const data = (await response.json());
|
|
76
|
+
conversationId = data.conversation || conversationId;
|
|
77
|
+
if (iterations === 1) {
|
|
78
|
+
const userSeq = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
79
|
+
this.aiSessionManager.addMessage({
|
|
80
|
+
aiSessionId: session.id,
|
|
81
|
+
sequence: userSeq,
|
|
82
|
+
role: "user",
|
|
83
|
+
content: userPrompt,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const toolCall = this.extractToolCall(data, toolSchema.function.name);
|
|
87
|
+
if (toolCall) {
|
|
88
|
+
this.aiSessionManager.updateSession(sessionId, "openai-responses", {
|
|
89
|
+
conversationId,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
data: this.validateResponse(toolCall),
|
|
94
|
+
iterations,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
currentPrompt = this.buildRetryPrompt(data);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: `API request timeout (${this.config.iterationTimeout}ms)`,
|
|
105
|
+
iterations,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: String(error),
|
|
111
|
+
iterations,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: `Max iterations (${this.config.maxIterations}) reached without tool call`,
|
|
118
|
+
iterations,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
extractToolCall(data, expectedToolName) {
|
|
122
|
+
if (!data.output || !Array.isArray(data.output)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
for (const item of data.output) {
|
|
126
|
+
if (item.type === "function_call" && item.name === expectedToolName) {
|
|
127
|
+
if (item.arguments) {
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(item.arguments);
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
log("Failed to parse function call arguments", {
|
|
134
|
+
error: String(error),
|
|
135
|
+
toolName: item.name,
|
|
136
|
+
arguments: item.arguments,
|
|
137
|
+
});
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
log("Function call found but no arguments", {
|
|
143
|
+
toolName: item.name,
|
|
144
|
+
callId: item.call_id,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
buildRetryPrompt(data) {
|
|
152
|
+
let assistantResponse = "";
|
|
153
|
+
if (data.output && Array.isArray(data.output)) {
|
|
154
|
+
for (const item of data.output) {
|
|
155
|
+
if (item.type === "message" && item.content) {
|
|
156
|
+
assistantResponse =
|
|
157
|
+
typeof item.content === "string" ? item.content : JSON.stringify(item.content);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return `Previous response: ${assistantResponse}\n\nPlease use the save_memories tool to extract and save the memories from the conversation as instructed.`;
|
|
163
|
+
}
|
|
164
|
+
validateResponse(data) {
|
|
165
|
+
if (!data || typeof data !== "object") {
|
|
166
|
+
throw new Error("Response is not an object");
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(data)) {
|
|
169
|
+
throw new Error("Response cannot be an array");
|
|
170
|
+
}
|
|
171
|
+
const keys = Object.keys(data);
|
|
172
|
+
if (keys.length === 0) {
|
|
173
|
+
throw new Error("Response object is empty");
|
|
174
|
+
}
|
|
175
|
+
for (const key of keys) {
|
|
176
|
+
if (data[key] === undefined || data[key] === null) {
|
|
177
|
+
throw new Error(`Response field '${key}' is null or undefined`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -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;AAS5B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,EAAE,CAAe;IACzB,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"}
|