om-memory-system 3.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.
- package/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface ShardInfo {
|
|
2
|
+
id: number;
|
|
3
|
+
scope: "user" | "project";
|
|
4
|
+
scopeHash: string;
|
|
5
|
+
shardIndex: number;
|
|
6
|
+
dbPath: string;
|
|
7
|
+
vectorCount: number;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface MemoryRecord {
|
|
12
|
+
id: string;
|
|
13
|
+
content: string;
|
|
14
|
+
vector: Float32Array;
|
|
15
|
+
tagsVector?: Float32Array;
|
|
16
|
+
containerTag: string;
|
|
17
|
+
tags?: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
updatedAt: number;
|
|
21
|
+
metadata?: string;
|
|
22
|
+
displayName?: string;
|
|
23
|
+
userName?: string;
|
|
24
|
+
userEmail?: string;
|
|
25
|
+
projectPath?: string;
|
|
26
|
+
projectName?: string;
|
|
27
|
+
gitRepoUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SearchResult {
|
|
30
|
+
id: string;
|
|
31
|
+
memory: string;
|
|
32
|
+
similarity: number;
|
|
33
|
+
createdAt?: number;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
displayName?: string;
|
|
37
|
+
userName?: string;
|
|
38
|
+
userEmail?: string;
|
|
39
|
+
projectPath?: string;
|
|
40
|
+
projectName?: string;
|
|
41
|
+
gitRepoUrl?: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MemoryRecord, SearchResult, ShardInfo } from "./types.js";
|
|
2
|
+
import type { TursoDb } from "./turso-db.js";
|
|
3
|
+
import type { Transaction } from "@libsql/client";
|
|
4
|
+
export declare class TursoVectorSearch {
|
|
5
|
+
insertVectorInTransaction(tx: Transaction, record: MemoryRecord): Promise<void>;
|
|
6
|
+
insertVector(db: TursoDb, record: MemoryRecord): Promise<void>;
|
|
7
|
+
searchInShard(shard: ShardInfo, queryVector: Float32Array, containerTag: string, limit: number, queryText?: string): Promise<SearchResult[]>;
|
|
8
|
+
private searchKind;
|
|
9
|
+
private exactScanKind;
|
|
10
|
+
searchAcrossShards(shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string): Promise<{
|
|
11
|
+
results: SearchResult[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}>;
|
|
14
|
+
deleteVector(db: TursoDb, memoryId: string): Promise<void>;
|
|
15
|
+
updateVector(db: TursoDb, memoryId: string, vector: Float32Array, tagsVector?: Float32Array): Promise<void>;
|
|
16
|
+
listMemories(db: TursoDb, containerTag: string, limit: number): Promise<Record<string, unknown>[]>;
|
|
17
|
+
getAllMemories(db: TursoDb): Promise<Record<string, unknown>[]>;
|
|
18
|
+
getAllMemoriesWithExtractedVectors(db: TursoDb): Promise<Array<Record<string, unknown> & {
|
|
19
|
+
vector_json: string | null;
|
|
20
|
+
}>>;
|
|
21
|
+
getMemoryById(db: TursoDb, memoryId: string): Promise<Record<string, unknown> | null>;
|
|
22
|
+
getMemoriesBySessionID(db: TursoDb, sessionID: string): Promise<Record<string, unknown>[]>;
|
|
23
|
+
countVectors(db: TursoDb, containerTag: string): Promise<number>;
|
|
24
|
+
countAllVectors(db: TursoDb): Promise<number>;
|
|
25
|
+
getDistinctTags(db: TursoDb): Promise<Record<string, unknown>[]>;
|
|
26
|
+
getProjectPathCounts(db: TursoDb): Promise<Array<{
|
|
27
|
+
projectPath: string;
|
|
28
|
+
count: number;
|
|
29
|
+
containerTag: string | null;
|
|
30
|
+
}>>;
|
|
31
|
+
updateProjectAssociation(db: TursoDb, oldContainerTag: string, update: {
|
|
32
|
+
containerTag: string;
|
|
33
|
+
projectPath?: string;
|
|
34
|
+
projectName?: string;
|
|
35
|
+
displayName?: string;
|
|
36
|
+
gitRepoUrl?: string | null;
|
|
37
|
+
}): Promise<number>;
|
|
38
|
+
pinMemory(db: TursoDb, memoryId: string): Promise<void>;
|
|
39
|
+
unpinMemory(db: TursoDb, memoryId: string): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare const tursoVectorSearch: TursoVectorSearch;
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { tursoConnectionManager } from "./connection-manager.js";
|
|
2
|
+
import { log } from "../logger.js";
|
|
3
|
+
import { distanceToSimilarity, vectorToJson } from "./vector-utils.js";
|
|
4
|
+
function parseMetadata(value) {
|
|
5
|
+
if (!value || typeof value !== "string")
|
|
6
|
+
return undefined;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(value);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class TursoVectorSearch {
|
|
15
|
+
async insertVectorInTransaction(tx, record) {
|
|
16
|
+
const contentVector = vectorToJson(record.vector);
|
|
17
|
+
const commonArgs = [
|
|
18
|
+
record.containerTag,
|
|
19
|
+
record.tags || null,
|
|
20
|
+
record.type || null,
|
|
21
|
+
record.createdAt,
|
|
22
|
+
record.updatedAt,
|
|
23
|
+
record.metadata || null,
|
|
24
|
+
record.displayName || null,
|
|
25
|
+
record.userName || null,
|
|
26
|
+
record.userEmail || null,
|
|
27
|
+
record.projectPath || null,
|
|
28
|
+
record.projectName || null,
|
|
29
|
+
record.gitRepoUrl || null,
|
|
30
|
+
];
|
|
31
|
+
if (record.tagsVector) {
|
|
32
|
+
await tx.execute({
|
|
33
|
+
sql: `
|
|
34
|
+
INSERT INTO memories (
|
|
35
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
36
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
37
|
+
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
38
|
+
`,
|
|
39
|
+
args: [
|
|
40
|
+
record.id,
|
|
41
|
+
record.content,
|
|
42
|
+
contentVector,
|
|
43
|
+
vectorToJson(record.tagsVector),
|
|
44
|
+
...commonArgs,
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await tx.execute({
|
|
50
|
+
sql: `
|
|
51
|
+
INSERT INTO memories (
|
|
52
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
53
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
54
|
+
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
55
|
+
`,
|
|
56
|
+
args: [record.id, record.content, contentVector, ...commonArgs],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async insertVector(db, record) {
|
|
60
|
+
const contentVector = vectorToJson(record.vector);
|
|
61
|
+
const commonArgs = [
|
|
62
|
+
record.containerTag,
|
|
63
|
+
record.tags || null,
|
|
64
|
+
record.type || null,
|
|
65
|
+
record.createdAt,
|
|
66
|
+
record.updatedAt,
|
|
67
|
+
record.metadata || null,
|
|
68
|
+
record.displayName || null,
|
|
69
|
+
record.userName || null,
|
|
70
|
+
record.userEmail || null,
|
|
71
|
+
record.projectPath || null,
|
|
72
|
+
record.projectName || null,
|
|
73
|
+
record.gitRepoUrl || null,
|
|
74
|
+
];
|
|
75
|
+
if (record.tagsVector) {
|
|
76
|
+
await db.execute(`
|
|
77
|
+
INSERT INTO memories (
|
|
78
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
79
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
80
|
+
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
`, [record.id, record.content, contentVector, vectorToJson(record.tagsVector), ...commonArgs]);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await db.execute(`
|
|
85
|
+
INSERT INTO memories (
|
|
86
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
87
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
88
|
+
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
89
|
+
`, [record.id, record.content, contentVector, ...commonArgs]);
|
|
90
|
+
}
|
|
91
|
+
async searchInShard(shard, queryVector, containerTag, limit, queryText) {
|
|
92
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
93
|
+
const queryJson = vectorToJson(queryVector);
|
|
94
|
+
// Over-fetch aggressively when filtering by container_tag after ANN,
|
|
95
|
+
// because post-filtering can discard many DiskANN neighbors.
|
|
96
|
+
const k = containerTag === "" ? Math.max(limit * 4, 32) : Math.max(limit * 16, 128);
|
|
97
|
+
const contentResults = await this.searchKind(db, queryJson, k, containerTag, "memories_vec_idx", "vector");
|
|
98
|
+
const tagsResults = await this.searchKind(db, queryJson, k, containerTag, "memories_tags_vec_idx", "tags_vector");
|
|
99
|
+
const candidateIds = new Set();
|
|
100
|
+
for (const result of contentResults)
|
|
101
|
+
candidateIds.add(result.id);
|
|
102
|
+
for (const result of tagsResults)
|
|
103
|
+
candidateIds.add(result.id);
|
|
104
|
+
const ids = Array.from(candidateIds);
|
|
105
|
+
if (ids.length === 0)
|
|
106
|
+
return [];
|
|
107
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
108
|
+
// Recompute exact distances for hydrated rows so tag-only / content-only ANN
|
|
109
|
+
// hits still get a full hybrid score (missing ANN side is not forced to 0).
|
|
110
|
+
const rows = await db.all(containerTag === ""
|
|
111
|
+
? `
|
|
112
|
+
SELECT id, content, tags, created_at, metadata, container_tag,
|
|
113
|
+
display_name, user_name, user_email, project_path, project_name,
|
|
114
|
+
git_repo_url, is_pinned,
|
|
115
|
+
vector_distance_cos(vector, vector32(?)) AS content_dist,
|
|
116
|
+
CASE WHEN tags_vector IS NOT NULL
|
|
117
|
+
THEN vector_distance_cos(tags_vector, vector32(?))
|
|
118
|
+
ELSE NULL END AS tags_dist
|
|
119
|
+
FROM memories
|
|
120
|
+
WHERE id IN (${placeholders})
|
|
121
|
+
`
|
|
122
|
+
: `
|
|
123
|
+
SELECT id, content, tags, created_at, metadata, container_tag,
|
|
124
|
+
display_name, user_name, user_email, project_path, project_name,
|
|
125
|
+
git_repo_url, is_pinned,
|
|
126
|
+
vector_distance_cos(vector, vector32(?)) AS content_dist,
|
|
127
|
+
CASE WHEN tags_vector IS NOT NULL
|
|
128
|
+
THEN vector_distance_cos(tags_vector, vector32(?))
|
|
129
|
+
ELSE NULL END AS tags_dist
|
|
130
|
+
FROM memories
|
|
131
|
+
WHERE id IN (${placeholders}) AND container_tag = ?
|
|
132
|
+
`, containerTag === ""
|
|
133
|
+
? [queryJson, queryJson, ...ids]
|
|
134
|
+
: [queryJson, queryJson, ...ids, containerTag]);
|
|
135
|
+
const queryWords = queryText
|
|
136
|
+
? queryText
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.split(/[\s,]+/)
|
|
139
|
+
.filter((word) => word.length > 1)
|
|
140
|
+
: [];
|
|
141
|
+
const hydratedResults = rows.map((row) => {
|
|
142
|
+
const contentSim = distanceToSimilarity(Number(row.content_dist));
|
|
143
|
+
const tagsSim = row.tags_dist == null || row.tags_dist === undefined
|
|
144
|
+
? 0
|
|
145
|
+
: distanceToSimilarity(Number(row.tags_dist));
|
|
146
|
+
const memoryTagsStr = String(row.tags || "");
|
|
147
|
+
const memoryTags = memoryTagsStr.split(",").map((tag) => tag.trim().toLowerCase());
|
|
148
|
+
let exactMatchBoost = 0;
|
|
149
|
+
if (queryWords.length > 0 && memoryTags.length > 0) {
|
|
150
|
+
const matches = queryWords.filter((word) => memoryTags.some((tag) => tag.includes(word) || word.includes(tag))).length;
|
|
151
|
+
exactMatchBoost = matches / Math.max(queryWords.length, 1);
|
|
152
|
+
}
|
|
153
|
+
const finalTagsSim = Math.max(tagsSim, exactMatchBoost);
|
|
154
|
+
const similarity = contentSim * 0.6 + finalTagsSim * 0.4;
|
|
155
|
+
return {
|
|
156
|
+
id: String(row.id),
|
|
157
|
+
memory: String(row.content),
|
|
158
|
+
similarity,
|
|
159
|
+
createdAt: Number(row.created_at),
|
|
160
|
+
tags: memoryTagsStr ? memoryTagsStr.split(",") : [],
|
|
161
|
+
metadata: parseMetadata(row.metadata),
|
|
162
|
+
containerTag: String(row.container_tag),
|
|
163
|
+
displayName: row.display_name ? String(row.display_name) : undefined,
|
|
164
|
+
userName: row.user_name ? String(row.user_name) : undefined,
|
|
165
|
+
userEmail: row.user_email ? String(row.user_email) : undefined,
|
|
166
|
+
projectPath: row.project_path ? String(row.project_path) : undefined,
|
|
167
|
+
projectName: row.project_name ? String(row.project_name) : undefined,
|
|
168
|
+
gitRepoUrl: row.git_repo_url ? String(row.git_repo_url) : undefined,
|
|
169
|
+
isPinned: row.is_pinned,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
hydratedResults.sort((a, b) => b.similarity - a.similarity);
|
|
173
|
+
return hydratedResults.slice(0, Math.max(0, limit));
|
|
174
|
+
}
|
|
175
|
+
async searchKind(db, queryJson, k, containerTag, indexName, columnName) {
|
|
176
|
+
try {
|
|
177
|
+
const rows = await db.all(containerTag === ""
|
|
178
|
+
? `
|
|
179
|
+
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
180
|
+
FROM vector_top_k('${indexName}', vector32(?), ?) AS v
|
|
181
|
+
CROSS JOIN memories m ON m.rowid = v.id
|
|
182
|
+
WHERE m.${columnName} IS NOT NULL
|
|
183
|
+
`
|
|
184
|
+
: `
|
|
185
|
+
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
186
|
+
FROM vector_top_k('${indexName}', vector32(?), ?) AS v
|
|
187
|
+
CROSS JOIN memories m ON m.rowid = v.id
|
|
188
|
+
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ?
|
|
189
|
+
`, containerTag === "" ? [queryJson, queryJson, k] : [queryJson, queryJson, k, containerTag]);
|
|
190
|
+
return rows.map((row) => ({
|
|
191
|
+
id: String(row.id),
|
|
192
|
+
similarity: distanceToSimilarity(Number(row.dist)),
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
log("Turso vector_top_k failed; falling back to exact scan", {
|
|
197
|
+
indexName,
|
|
198
|
+
error: String(error),
|
|
199
|
+
});
|
|
200
|
+
return this.exactScanKind(db, queryJson, k, containerTag, columnName);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async exactScanKind(db, queryJson, k, containerTag, columnName) {
|
|
204
|
+
const rows = await db.all(containerTag === ""
|
|
205
|
+
? `
|
|
206
|
+
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
207
|
+
FROM memories m
|
|
208
|
+
WHERE m.${columnName} IS NOT NULL
|
|
209
|
+
ORDER BY dist ASC
|
|
210
|
+
LIMIT ?
|
|
211
|
+
`
|
|
212
|
+
: `
|
|
213
|
+
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
214
|
+
FROM memories m
|
|
215
|
+
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ?
|
|
216
|
+
ORDER BY dist ASC
|
|
217
|
+
LIMIT ?
|
|
218
|
+
`, containerTag === "" ? [queryJson, k] : [queryJson, containerTag, k]);
|
|
219
|
+
return rows.map((row) => ({
|
|
220
|
+
id: String(row.id),
|
|
221
|
+
similarity: distanceToSimilarity(Number(row.dist)),
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
async searchAcrossShards(shards, queryVector, containerTag, limit, similarityThreshold, queryText) {
|
|
225
|
+
const shardErrors = [];
|
|
226
|
+
const shardPromises = shards.map(async (shard) => {
|
|
227
|
+
try {
|
|
228
|
+
return await this.searchInShard(shard, queryVector, containerTag, limit, queryText);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
232
|
+
log("Shard search error", { shardId: shard.id, error: message });
|
|
233
|
+
shardErrors.push({ shardId: shard.id, error: message });
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
const resultsArray = await Promise.all(shardPromises);
|
|
238
|
+
if (shardErrors.length > 0 && shardErrors.length === shards.length) {
|
|
239
|
+
throw new Error(`Vector search failed on all shards: ${shardErrors.map((entry) => `${entry.shardId}: ${entry.error}`).join("; ")}`);
|
|
240
|
+
}
|
|
241
|
+
const warnings = shardErrors.length > 0
|
|
242
|
+
? [
|
|
243
|
+
`Vector search completed with partial shard failures (${shardErrors.length}/${shards.length}): ${shardErrors
|
|
244
|
+
.map((entry) => `${entry.shardId}: ${entry.error}`)
|
|
245
|
+
.join("; ")}`,
|
|
246
|
+
]
|
|
247
|
+
: [];
|
|
248
|
+
if (warnings.length > 0) {
|
|
249
|
+
log("Vector search completed with partial shard failures", {
|
|
250
|
+
failedShards: shardErrors,
|
|
251
|
+
totalShards: shards.length,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const allResults = resultsArray.flat();
|
|
255
|
+
allResults.sort((a, b) => b.similarity - a.similarity);
|
|
256
|
+
return {
|
|
257
|
+
results: allResults
|
|
258
|
+
.filter((result) => result.similarity >= similarityThreshold)
|
|
259
|
+
.slice(0, limit),
|
|
260
|
+
warnings,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async deleteVector(db, memoryId) {
|
|
264
|
+
await db.run(`DELETE FROM memories WHERE id = ?`, [memoryId]);
|
|
265
|
+
}
|
|
266
|
+
async updateVector(db, memoryId, vector, tagsVector) {
|
|
267
|
+
const contentVector = vectorToJson(vector);
|
|
268
|
+
if (tagsVector) {
|
|
269
|
+
await db.execute(`UPDATE memories SET vector = vector32(?), tags_vector = vector32(?) WHERE id = ?`, [contentVector, vectorToJson(tagsVector), memoryId]);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
await db.execute(`UPDATE memories SET vector = vector32(?), tags_vector = NULL WHERE id = ?`, [contentVector, memoryId]);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async listMemories(db, containerTag, limit) {
|
|
276
|
+
return containerTag === ""
|
|
277
|
+
? db.all(`
|
|
278
|
+
SELECT * FROM memories
|
|
279
|
+
ORDER BY created_at DESC
|
|
280
|
+
LIMIT ?
|
|
281
|
+
`, [limit])
|
|
282
|
+
: db.all(`
|
|
283
|
+
SELECT * FROM memories
|
|
284
|
+
WHERE container_tag = ?
|
|
285
|
+
ORDER BY created_at DESC
|
|
286
|
+
LIMIT ?
|
|
287
|
+
`, [containerTag, limit]);
|
|
288
|
+
}
|
|
289
|
+
async getAllMemories(db) {
|
|
290
|
+
return db.all(`SELECT * FROM memories ORDER BY created_at DESC`);
|
|
291
|
+
}
|
|
292
|
+
async getAllMemoriesWithExtractedVectors(db) {
|
|
293
|
+
return db.all(`
|
|
294
|
+
SELECT
|
|
295
|
+
id,
|
|
296
|
+
content,
|
|
297
|
+
container_tag,
|
|
298
|
+
created_at,
|
|
299
|
+
vector_extract(vector) AS vector_json
|
|
300
|
+
FROM memories
|
|
301
|
+
ORDER BY created_at DESC
|
|
302
|
+
`);
|
|
303
|
+
}
|
|
304
|
+
async getMemoryById(db, memoryId) {
|
|
305
|
+
return db.get(`SELECT * FROM memories WHERE id = ?`, [memoryId]);
|
|
306
|
+
}
|
|
307
|
+
async getMemoriesBySessionID(db, sessionID) {
|
|
308
|
+
const rows = await db.all(`
|
|
309
|
+
SELECT * FROM memories
|
|
310
|
+
WHERE metadata LIKE ?
|
|
311
|
+
ORDER BY created_at DESC
|
|
312
|
+
`, [`%"sessionID":"${sessionID}"%`]);
|
|
313
|
+
return rows.map((row) => ({
|
|
314
|
+
...row,
|
|
315
|
+
tags: row.tags ? String(row.tags).split(",") : [],
|
|
316
|
+
metadata: row.metadata ? (parseMetadata(String(row.metadata)) ?? {}) : {},
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
async countVectors(db, containerTag) {
|
|
320
|
+
const row = await db.get(`SELECT COUNT(*) as count FROM memories WHERE container_tag = ?`, [
|
|
321
|
+
containerTag,
|
|
322
|
+
]);
|
|
323
|
+
return Number(row?.count ?? 0);
|
|
324
|
+
}
|
|
325
|
+
async countAllVectors(db) {
|
|
326
|
+
const row = await db.get(`SELECT COUNT(*) as count FROM memories`);
|
|
327
|
+
return Number(row?.count ?? 0);
|
|
328
|
+
}
|
|
329
|
+
async getDistinctTags(db) {
|
|
330
|
+
return db.all(`
|
|
331
|
+
SELECT DISTINCT
|
|
332
|
+
container_tag,
|
|
333
|
+
display_name,
|
|
334
|
+
user_name,
|
|
335
|
+
user_email,
|
|
336
|
+
project_path,
|
|
337
|
+
project_name,
|
|
338
|
+
git_repo_url
|
|
339
|
+
FROM memories
|
|
340
|
+
`);
|
|
341
|
+
}
|
|
342
|
+
async getProjectPathCounts(db) {
|
|
343
|
+
const rows = await db.all(`
|
|
344
|
+
SELECT
|
|
345
|
+
project_path AS project_path,
|
|
346
|
+
container_tag AS container_tag,
|
|
347
|
+
COUNT(*) AS cnt
|
|
348
|
+
FROM memories
|
|
349
|
+
WHERE project_path IS NOT NULL AND project_path != ''
|
|
350
|
+
GROUP BY project_path, container_tag
|
|
351
|
+
ORDER BY cnt DESC, project_path ASC
|
|
352
|
+
`);
|
|
353
|
+
return rows.map((row) => ({
|
|
354
|
+
projectPath: String(row.project_path),
|
|
355
|
+
count: Number(row.cnt ?? 0),
|
|
356
|
+
containerTag: row.container_tag ? String(row.container_tag) : null,
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
async updateProjectAssociation(db, oldContainerTag, update) {
|
|
360
|
+
return db.run(`
|
|
361
|
+
UPDATE memories SET
|
|
362
|
+
container_tag = ?,
|
|
363
|
+
project_path = ?,
|
|
364
|
+
project_name = ?,
|
|
365
|
+
display_name = ?,
|
|
366
|
+
git_repo_url = ?
|
|
367
|
+
WHERE container_tag = ?
|
|
368
|
+
`, [
|
|
369
|
+
update.containerTag,
|
|
370
|
+
update.projectPath ?? null,
|
|
371
|
+
update.projectName ?? null,
|
|
372
|
+
update.displayName ?? null,
|
|
373
|
+
update.gitRepoUrl ?? null,
|
|
374
|
+
oldContainerTag,
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
async pinMemory(db, memoryId) {
|
|
378
|
+
await db.run(`UPDATE memories SET is_pinned = 1 WHERE id = ?`, [memoryId]);
|
|
379
|
+
}
|
|
380
|
+
async unpinMemory(db, memoryId) {
|
|
381
|
+
await db.run(`UPDATE memories SET is_pinned = 0 WHERE id = ?`, [memoryId]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
export const tursoVectorSearch = new TursoVectorSearch();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function vectorToJson(vector: Float32Array): string;
|
|
2
|
+
export declare function blobToFloat32Array(value: unknown): Float32Array | null;
|
|
3
|
+
/** Parse libSQL `vector_extract()` JSON output (preferred over raw F32_BLOB bytes). */
|
|
4
|
+
export declare function parseExtractedVector(value: unknown): Float32Array | null;
|
|
5
|
+
export declare function distanceToSimilarity(distance: number): number;
|
|
6
|
+
/** Canonical text used for tag-vector embeddings (must stay consistent across write paths). */
|
|
7
|
+
export declare function formatTagsForEmbedding(tags: string[]): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function vectorToJson(vector) {
|
|
2
|
+
return JSON.stringify(Array.from(vector));
|
|
3
|
+
}
|
|
4
|
+
export function blobToFloat32Array(value) {
|
|
5
|
+
if (value == null)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
if (value instanceof Float32Array) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (value instanceof ArrayBuffer) {
|
|
12
|
+
if (value.byteLength === 0 || value.byteLength % 4 !== 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return new Float32Array(value);
|
|
16
|
+
}
|
|
17
|
+
if (ArrayBuffer.isView(value)) {
|
|
18
|
+
const view = value;
|
|
19
|
+
const byteLength = view.byteLength;
|
|
20
|
+
if (byteLength === 0 || byteLength % 4 !== 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(value);
|
|
28
|
+
return new Float32Array(parsed);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** Parse libSQL `vector_extract()` JSON output (preferred over raw F32_BLOB bytes). */
|
|
41
|
+
export function parseExtractedVector(value) {
|
|
42
|
+
if (value == null)
|
|
43
|
+
return null;
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(value);
|
|
47
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
return new Float32Array(parsed);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return blobToFloat32Array(value);
|
|
56
|
+
}
|
|
57
|
+
export function distanceToSimilarity(distance) {
|
|
58
|
+
const similarity = 1 - Number(distance);
|
|
59
|
+
if (!Number.isFinite(similarity))
|
|
60
|
+
return 0;
|
|
61
|
+
// Clamp float artifacts (docs: cos-distance can be slightly negative near exact matches).
|
|
62
|
+
return Math.max(0, Math.min(1, similarity));
|
|
63
|
+
}
|
|
64
|
+
/** Canonical text used for tag-vector embeddings (must stay consistent across write paths). */
|
|
65
|
+
export function formatTagsForEmbedding(tags) {
|
|
66
|
+
return `Topics: ${tags.join(", ")}`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
export declare function shouldRunAutomaticProfileCleanup(previousPromptCount: number, addedPromptCount: number, interval?: number): boolean;
|
|
3
|
+
export declare function performUserProfileLearning(ctx: PluginInput, directory: string): Promise<void>;
|
|
4
|
+
import { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX } from "../core/extraction.js";
|
|
5
|
+
export { createUserProfileAnalysisSchema, USER_PROFILE_LLM_CONFIDENCE_MAX };
|
|
6
|
+
export declare function createUserProfileToolSchema(existingProfile: boolean): {
|
|
7
|
+
type: "function";
|
|
8
|
+
function: {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: {
|
|
12
|
+
type: string;
|
|
13
|
+
properties: {
|
|
14
|
+
preferences: {
|
|
15
|
+
type: string;
|
|
16
|
+
items: {
|
|
17
|
+
type: string;
|
|
18
|
+
properties: {
|
|
19
|
+
category: {
|
|
20
|
+
type: string;
|
|
21
|
+
};
|
|
22
|
+
description: {
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
confidence: {
|
|
26
|
+
type: string;
|
|
27
|
+
minimum: number;
|
|
28
|
+
maximum: number;
|
|
29
|
+
};
|
|
30
|
+
evidence: {
|
|
31
|
+
type: string;
|
|
32
|
+
items: {
|
|
33
|
+
type: string;
|
|
34
|
+
};
|
|
35
|
+
maxItems: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
required: string[];
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
patterns: {
|
|
42
|
+
type: string;
|
|
43
|
+
items: {
|
|
44
|
+
type: string;
|
|
45
|
+
properties: {
|
|
46
|
+
category: {
|
|
47
|
+
type: string;
|
|
48
|
+
};
|
|
49
|
+
description: {
|
|
50
|
+
type: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
required: string[];
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
workflows: {
|
|
57
|
+
type: string;
|
|
58
|
+
items: {
|
|
59
|
+
type: string;
|
|
60
|
+
properties: {
|
|
61
|
+
description: {
|
|
62
|
+
type: string;
|
|
63
|
+
};
|
|
64
|
+
steps: {
|
|
65
|
+
type: string;
|
|
66
|
+
items: {
|
|
67
|
+
type: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
required: string[];
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
validations: {
|
|
75
|
+
type: string;
|
|
76
|
+
items: {
|
|
77
|
+
type: string;
|
|
78
|
+
properties: {
|
|
79
|
+
index: {
|
|
80
|
+
type: string;
|
|
81
|
+
};
|
|
82
|
+
verdict: {
|
|
83
|
+
type: string;
|
|
84
|
+
enum: string[];
|
|
85
|
+
};
|
|
86
|
+
reason: {
|
|
87
|
+
type: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
required: string[];
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
required: string[];
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
};
|