@xiaoxiamimengfb/my-opencode-mem 2.12.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 +155 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +411 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +427 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +4 -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 +30 -0
- package/dist/services/ai/opencode-provider.d.ts.map +1 -0
- package/dist/services/ai/opencode-provider.js +332 -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 +13 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +277 -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 +901 -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 +306 -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 +118 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +251 -0
- package/dist/services/context.d.ts +11 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +24 -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 +106 -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 +16 -0
- package/dist/services/logger.d.ts +2 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +51 -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 +268 -0
- package/dist/services/tags.d.ts +24 -0
- package/dist/services/tags.d.ts.map +1 -0
- package/dist/services/tags.js +146 -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 +231 -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 +292 -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 +1194 -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 +265 -0
- package/dist/web/index.html +284 -0
- package/dist/web/styles.css +1631 -0
- package/package.json +71 -0
|
@@ -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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { getDatabase } from "../../sqlite/sqlite-bootstrap.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { connectionManager } from "../../sqlite/connection-manager.js";
|
|
4
|
+
import { CONFIG } from "../../../config.js";
|
|
5
|
+
const Database = getDatabase();
|
|
6
|
+
const AI_SESSIONS_DB_NAME = "ai-sessions.db";
|
|
7
|
+
export class AISessionManager {
|
|
8
|
+
db;
|
|
9
|
+
dbPath;
|
|
10
|
+
sessionRetentionMs;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.dbPath = join(CONFIG.storagePath, AI_SESSIONS_DB_NAME);
|
|
13
|
+
this.db = connectionManager.getConnection(this.dbPath);
|
|
14
|
+
this.sessionRetentionMs = CONFIG.aiSessionRetentionDays * 24 * 60 * 60 * 1000;
|
|
15
|
+
this.initDatabase();
|
|
16
|
+
}
|
|
17
|
+
initDatabase() {
|
|
18
|
+
this.db.run(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS ai_sessions (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
provider TEXT NOT NULL,
|
|
22
|
+
session_id TEXT NOT NULL,
|
|
23
|
+
conversation_id TEXT,
|
|
24
|
+
metadata TEXT,
|
|
25
|
+
created_at INTEGER NOT NULL,
|
|
26
|
+
updated_at INTEGER NOT NULL,
|
|
27
|
+
expires_at INTEGER NOT NULL
|
|
28
|
+
)
|
|
29
|
+
`);
|
|
30
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_session_id ON ai_sessions(session_id)");
|
|
31
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_expires_at ON ai_sessions(expires_at)");
|
|
32
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_sessions_provider ON ai_sessions(provider)");
|
|
33
|
+
this.db.run(`
|
|
34
|
+
CREATE TABLE IF NOT EXISTS ai_messages (
|
|
35
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
36
|
+
ai_session_id TEXT NOT NULL,
|
|
37
|
+
sequence INTEGER NOT NULL,
|
|
38
|
+
role TEXT NOT NULL,
|
|
39
|
+
content TEXT NOT NULL,
|
|
40
|
+
tool_calls TEXT,
|
|
41
|
+
tool_call_id TEXT,
|
|
42
|
+
content_blocks TEXT,
|
|
43
|
+
created_at INTEGER NOT NULL,
|
|
44
|
+
FOREIGN KEY (ai_session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE
|
|
45
|
+
)
|
|
46
|
+
`);
|
|
47
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_messages_session ON ai_messages(ai_session_id, sequence)");
|
|
48
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_ai_messages_role ON ai_messages(ai_session_id, role)");
|
|
49
|
+
}
|
|
50
|
+
getSession(sessionId, provider) {
|
|
51
|
+
const stmt = this.db.prepare(`
|
|
52
|
+
SELECT * FROM ai_sessions
|
|
53
|
+
WHERE session_id = ? AND provider = ? AND expires_at > ?
|
|
54
|
+
`);
|
|
55
|
+
const row = stmt.get(sessionId, provider, Date.now());
|
|
56
|
+
if (!row)
|
|
57
|
+
return null;
|
|
58
|
+
return this.rowToSession(row);
|
|
59
|
+
}
|
|
60
|
+
createSession(params) {
|
|
61
|
+
const id = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const expiresAt = now + this.sessionRetentionMs;
|
|
64
|
+
this.db.run(`
|
|
65
|
+
INSERT INTO ai_sessions (
|
|
66
|
+
id, provider, session_id, conversation_id,
|
|
67
|
+
metadata, created_at, updated_at, expires_at
|
|
68
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
69
|
+
`, [
|
|
70
|
+
id,
|
|
71
|
+
params.provider,
|
|
72
|
+
params.sessionId,
|
|
73
|
+
params.conversationId || null,
|
|
74
|
+
JSON.stringify(params.metadata || {}),
|
|
75
|
+
now,
|
|
76
|
+
now,
|
|
77
|
+
expiresAt,
|
|
78
|
+
]);
|
|
79
|
+
return this.getSession(params.sessionId, params.provider);
|
|
80
|
+
}
|
|
81
|
+
updateSession(sessionId, provider, updates) {
|
|
82
|
+
const fields = [];
|
|
83
|
+
const values = [];
|
|
84
|
+
if (updates.conversationId !== undefined) {
|
|
85
|
+
fields.push("conversation_id = ?");
|
|
86
|
+
values.push(updates.conversationId);
|
|
87
|
+
}
|
|
88
|
+
if (updates.metadata !== undefined) {
|
|
89
|
+
fields.push("metadata = ?");
|
|
90
|
+
values.push(JSON.stringify(updates.metadata));
|
|
91
|
+
}
|
|
92
|
+
fields.push("updated_at = ?");
|
|
93
|
+
values.push(Date.now());
|
|
94
|
+
values.push(sessionId);
|
|
95
|
+
values.push(provider);
|
|
96
|
+
this.db.run(`
|
|
97
|
+
UPDATE ai_sessions
|
|
98
|
+
SET ${fields.join(", ")}
|
|
99
|
+
WHERE session_id = ? AND provider = ?
|
|
100
|
+
`, values);
|
|
101
|
+
}
|
|
102
|
+
cleanupExpiredSessions() {
|
|
103
|
+
const result = this.db.run(`DELETE FROM ai_sessions WHERE expires_at < ?`, [Date.now()]);
|
|
104
|
+
return result.changes;
|
|
105
|
+
}
|
|
106
|
+
deleteSession(sessionId, provider) {
|
|
107
|
+
this.db.run(`DELETE FROM ai_sessions WHERE session_id = ? AND provider = ?`, [
|
|
108
|
+
sessionId,
|
|
109
|
+
provider,
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
addMessage(message) {
|
|
113
|
+
this.db.run(`INSERT INTO ai_messages (
|
|
114
|
+
ai_session_id, sequence, role, content,
|
|
115
|
+
tool_calls, tool_call_id, content_blocks, created_at
|
|
116
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
117
|
+
message.aiSessionId,
|
|
118
|
+
message.sequence,
|
|
119
|
+
message.role,
|
|
120
|
+
message.content,
|
|
121
|
+
message.toolCalls ? JSON.stringify(message.toolCalls) : null,
|
|
122
|
+
message.toolCallId || null,
|
|
123
|
+
message.contentBlocks ? JSON.stringify(message.contentBlocks) : null,
|
|
124
|
+
Date.now(),
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
getMessages(aiSessionId) {
|
|
128
|
+
const stmt = this.db.prepare("SELECT * FROM ai_messages WHERE ai_session_id = ? ORDER BY sequence ASC");
|
|
129
|
+
const rows = stmt.all(aiSessionId);
|
|
130
|
+
return rows.map(this.rowToMessage);
|
|
131
|
+
}
|
|
132
|
+
getLastSequence(aiSessionId) {
|
|
133
|
+
const stmt = this.db.prepare("SELECT MAX(sequence) as max_seq FROM ai_messages WHERE ai_session_id = ?");
|
|
134
|
+
const row = stmt.get(aiSessionId);
|
|
135
|
+
return row?.max_seq ?? -1;
|
|
136
|
+
}
|
|
137
|
+
clearMessages(aiSessionId) {
|
|
138
|
+
this.db.run("DELETE FROM ai_messages WHERE ai_session_id = ?", [aiSessionId]);
|
|
139
|
+
}
|
|
140
|
+
rowToSession(row) {
|
|
141
|
+
return {
|
|
142
|
+
id: row.id,
|
|
143
|
+
provider: row.provider,
|
|
144
|
+
sessionId: row.session_id,
|
|
145
|
+
conversationId: row.conversation_id,
|
|
146
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
147
|
+
createdAt: row.created_at,
|
|
148
|
+
updatedAt: row.updated_at,
|
|
149
|
+
expiresAt: row.expires_at,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
rowToMessage(row) {
|
|
153
|
+
return {
|
|
154
|
+
id: row.id,
|
|
155
|
+
aiSessionId: row.ai_session_id,
|
|
156
|
+
sequence: row.sequence,
|
|
157
|
+
role: row.role,
|
|
158
|
+
content: row.content,
|
|
159
|
+
toolCalls: row.tool_calls ? JSON.parse(row.tool_calls) : undefined,
|
|
160
|
+
toolCallId: row.tool_call_id,
|
|
161
|
+
contentBlocks: row.content_blocks ? JSON.parse(row.content_blocks) : undefined,
|
|
162
|
+
createdAt: row.created_at,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export const aiSessionManager = new AISessionManager();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type AIProviderType = "openai-chat" | "openai-responses" | "anthropic" | "google-gemini";
|
|
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,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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class ToolSchemaConverter {
|
|
2
|
+
static toResponsesAPI(chatCompletionTool) {
|
|
3
|
+
return {
|
|
4
|
+
type: "function",
|
|
5
|
+
name: chatCompletionTool.function.name,
|
|
6
|
+
description: chatCompletionTool.function.description,
|
|
7
|
+
parameters: chatCompletionTool.function.parameters,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
static toAnthropic(chatCompletionTool) {
|
|
11
|
+
return {
|
|
12
|
+
name: chatCompletionTool.function.name,
|
|
13
|
+
description: chatCompletionTool.function.description,
|
|
14
|
+
input_schema: chatCompletionTool.function.parameters,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
static fromChatCompletion(tool) {
|
|
18
|
+
return {
|
|
19
|
+
chatCompletion: tool,
|
|
20
|
+
responsesAPI: this.toResponsesAPI(tool),
|
|
21
|
+
anthropic: this.toAnthropic(tool),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UserProfileData } from "../../user-profile/types.js";
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
data?: UserProfileData;
|
|
6
|
+
}
|
|
7
|
+
export declare class UserProfileValidator {
|
|
8
|
+
static validate(data: any): ValidationResult;
|
|
9
|
+
private static validatePreferences;
|
|
10
|
+
private static validatePatterns;
|
|
11
|
+
private static validateWorkflows;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=user-profile-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-profile-validator.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/validators/user-profile-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,gBAAgB;IAsC5C,OAAO,CAAC,MAAM,CAAC,mBAAmB;IA6BlC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAqB/B,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAsBjC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export class UserProfileValidator {
|
|
2
|
+
static validate(data) {
|
|
3
|
+
const errors = [];
|
|
4
|
+
if (!data || typeof data !== "object") {
|
|
5
|
+
return { valid: false, errors: ["Response is not an object"] };
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
return { valid: false, errors: ["Response cannot be an array"] };
|
|
9
|
+
}
|
|
10
|
+
const keys = Object.keys(data);
|
|
11
|
+
if (keys.length === 0) {
|
|
12
|
+
return { valid: false, errors: ["Response object is empty"] };
|
|
13
|
+
}
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
if (data[key] === undefined || data[key] === null) {
|
|
16
|
+
errors.push(`Field '${key}' is null or undefined`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (errors.length > 0) {
|
|
20
|
+
return { valid: false, errors };
|
|
21
|
+
}
|
|
22
|
+
if (data.preferences) {
|
|
23
|
+
const prefErrors = this.validatePreferences(data.preferences);
|
|
24
|
+
errors.push(...prefErrors);
|
|
25
|
+
}
|
|
26
|
+
if (data.patterns) {
|
|
27
|
+
const patternErrors = this.validatePatterns(data.patterns);
|
|
28
|
+
errors.push(...patternErrors);
|
|
29
|
+
}
|
|
30
|
+
if (data.workflows) {
|
|
31
|
+
const workflowErrors = this.validateWorkflows(data.workflows);
|
|
32
|
+
errors.push(...workflowErrors);
|
|
33
|
+
}
|
|
34
|
+
if (errors.length > 0) {
|
|
35
|
+
return { valid: false, errors };
|
|
36
|
+
}
|
|
37
|
+
return { valid: true, errors: [], data: data };
|
|
38
|
+
}
|
|
39
|
+
static validatePreferences(preferences) {
|
|
40
|
+
const errors = [];
|
|
41
|
+
if (!Array.isArray(preferences)) {
|
|
42
|
+
return ["preferences must be an array"];
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < preferences.length; i++) {
|
|
45
|
+
const pref = preferences[i];
|
|
46
|
+
if (!pref || typeof pref !== "object") {
|
|
47
|
+
errors.push(`preferences[${i}] is not an object`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!pref.category || typeof pref.category !== "string") {
|
|
51
|
+
errors.push(`preferences[${i}].category is missing or invalid`);
|
|
52
|
+
}
|
|
53
|
+
if (!pref.description || typeof pref.description !== "string") {
|
|
54
|
+
errors.push(`preferences[${i}].description is missing or invalid`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof pref.confidence !== "number") {
|
|
57
|
+
errors.push(`preferences[${i}].confidence is missing or invalid`);
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(pref.evidence)) {
|
|
60
|
+
errors.push(`preferences[${i}].evidence must be an array`);
|
|
61
|
+
}
|
|
62
|
+
else if (pref.evidence.length === 0) {
|
|
63
|
+
errors.push(`preferences[${i}].evidence cannot be empty`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return errors;
|
|
67
|
+
}
|
|
68
|
+
static validatePatterns(patterns) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
if (!Array.isArray(patterns)) {
|
|
71
|
+
return ["patterns must be an array"];
|
|
72
|
+
}
|
|
73
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
74
|
+
const pattern = patterns[i];
|
|
75
|
+
if (!pattern || typeof pattern !== "object") {
|
|
76
|
+
errors.push(`patterns[${i}] is not an object`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!pattern.category || typeof pattern.category !== "string") {
|
|
80
|
+
errors.push(`patterns[${i}].category is missing or invalid`);
|
|
81
|
+
}
|
|
82
|
+
if (!pattern.description || typeof pattern.description !== "string") {
|
|
83
|
+
errors.push(`patterns[${i}].description is missing or invalid`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return errors;
|
|
87
|
+
}
|
|
88
|
+
static validateWorkflows(workflows) {
|
|
89
|
+
const errors = [];
|
|
90
|
+
if (!Array.isArray(workflows)) {
|
|
91
|
+
return ["workflows must be an array"];
|
|
92
|
+
}
|
|
93
|
+
for (let i = 0; i < workflows.length; i++) {
|
|
94
|
+
const workflow = workflows[i];
|
|
95
|
+
if (!workflow || typeof workflow !== "object") {
|
|
96
|
+
errors.push(`workflows[${i}] is not an object`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!workflow.description || typeof workflow.description !== "string") {
|
|
100
|
+
errors.push(`workflows[${i}].description is missing or invalid`);
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(workflow.steps)) {
|
|
103
|
+
errors.push(`workflows[${i}].steps must be an array`);
|
|
104
|
+
}
|
|
105
|
+
else if (workflow.steps.length === 0) {
|
|
106
|
+
errors.push(`workflows[${i}].steps cannot be empty`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return errors;
|
|
110
|
+
}
|
|
111
|
+
}
|