Skip to content

Commit 7b76804

Browse files
ndoschekxai
andcommitted
GH-56 WIP Ai History Persistance Service
Co-authored-by: Olaf Lessenich <[email protected]>
1 parent 328d26a commit 7b76804

8 files changed

+169
-3
lines changed

packages/ai-core/src/common/communication-recording-service.ts renamed to packages/ai-core/src/common/communication-recording-types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ export interface CommunicationRecordingService {
3131
recordRequest(requestEntry: CommunicationHistoryEntry): void;
3232
recordResponse(responseEntry: CommunicationHistoryEntry): void;
3333
getHistory(agentId: string): CommunicationHistory;
34+
setHistory(agentId: string, history: CommunicationHistory): void;
35+
getRecordedAgents(): string[];
3436
}

packages/ai-core/src/common/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
1515
// *****************************************************************************
1616
export * from './agent';
17-
export * from './communication-recording-service';
17+
export * from './communication-recording-types';
1818
export * from './language-model';
1919
export * from './language-model-delegate';
2020
export * from './prompt-service';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2024 EclipseSource GmbH.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { CommunicationRecordingService } from '@theia/ai-core';
18+
import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
19+
import { inject, injectable } from '@theia/core/shared/inversify';
20+
import { AiHistoryPersistenceService } from '../common/history-persistence';
21+
22+
@injectable()
23+
export class AIHistoryFrontendContribution implements FrontendApplicationContribution {
24+
25+
@inject(AiHistoryPersistenceService)
26+
protected persistenceService: AiHistoryPersistenceService;
27+
@inject(CommunicationRecordingService)
28+
protected recordingService: CommunicationRecordingService;
29+
30+
async onStart(app: FrontendApplication): Promise<void> {
31+
this.persistenceService.loadHistory();
32+
}
33+
34+
onStop(app: FrontendApplication): void {
35+
this.recordingService.getRecordedAgents().forEach(agentId => {
36+
const history = this.recordingService.getHistory(agentId);
37+
this.persistenceService.saveHistory(agentId, history);
38+
});
39+
}
40+
41+
}

packages/ai-history/src/browser/ai-history-frontend-module.ts

+11
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,21 @@
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
1515
// *****************************************************************************
1616
import { CommunicationRecordingService } from '@theia/ai-core';
17+
import { FrontendApplicationContribution, WebSocketConnectionProvider } from '@theia/core/lib/browser';
1718
import { ContainerModule } from '@theia/core/shared/inversify';
1819
import { DefaultCommunicationRecordingService } from '../common/communication-recording-service';
20+
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
21+
import { AIHistoryFrontendContribution } from './ai-history-frontend-contribution';
1922

2023
export default new ContainerModule(bind => {
24+
bind(FrontendApplicationContribution).to(AIHistoryFrontendContribution).inSingletonScope();
25+
2126
bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
2227
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);
28+
29+
bind(AiHistoryPersistenceService).toDynamicValue(ctx => {
30+
const connection = ctx.container.get(WebSocketConnectionProvider);
31+
return connection.createProxy<AiHistoryPersistenceService>(aiHistoryPersistenceServicePath);
32+
}).inSingletonScope();
33+
2334
});

packages/ai-history/src/common/communication-recording-service.ts

+10
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,26 @@ export class DefaultCommunicationRecordingService implements CommunicationRecord
2121

2222
protected history: Map<string, CommunicationHistory> = new Map();
2323

24+
getRecordedAgents(): string[] {
25+
return Object.keys(this.history);
26+
}
27+
2428
getHistory(agentId: string): CommunicationHistory {
2529
return this.history.get(agentId) || [];
2630
}
2731

32+
setHistory(agentId: string, history: CommunicationHistory): void {
33+
this.history.set(agentId, history);
34+
}
35+
2836
recordRequest(requestEntry: CommunicationHistoryEntry): void {
2937
console.log('Recording request:', requestEntry.request);
3038
if (this.history.has(requestEntry.agentId)) {
3139
this.history.get(requestEntry.agentId)?.push(requestEntry);
3240
} else {
3341
this.history.set(requestEntry.agentId, [requestEntry]);
3442
}
43+
// this.persistenceService.saveHistory(requestEntry.agentId, this.history.get(requestEntry.agentId)!);
3544
}
3645

3746
recordResponse(responseEntry: CommunicationHistoryEntry): void {
@@ -46,6 +55,7 @@ export class DefaultCommunicationRecordingService implements CommunicationRecord
4655
matchingEntry.response = responseEntry.response;
4756
matchingEntry.responseTime = responseEntry.timestamp - matchingEntry.timestamp;
4857
}
58+
// this.persistenceService.saveHistory(responseEntry.agentId, this.history.get(responseEntry.agentId)!);
4959
}
5060
}
5161
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2024 EclipseSource GmbH.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { CommunicationHistory } from '@theia/ai-core';
18+
19+
export const aiHistoryPersistenceServicePath = '/services/aiHistoryPersistenceService';
20+
21+
export const AiHistoryPersistenceService = Symbol('AiHistoryPersistenceService');
22+
export interface AiHistoryPersistenceService {
23+
saveHistory(agentId: string, history: CommunicationHistory): Promise<void>;
24+
loadHistory(): Promise<void>;
25+
}

packages/ai-history/src/node/ai-history-backend-module.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,26 @@
1313
//
1414
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
1515
// *****************************************************************************
16+
import { CommunicationRecordingService } from '@theia/ai-core';
17+
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
1618
import { ContainerModule } from '@theia/core/shared/inversify';
19+
import { DefaultCommunicationRecordingService } from '../common';
20+
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
21+
import { FileCommunicationPersistenceService } from './communication-persistence-service';
1722

1823
export default new ContainerModule(bind => {
19-
// bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
20-
// bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);
24+
bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
25+
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);
26+
27+
bind(FileCommunicationPersistenceService).toSelf().inSingletonScope();
28+
bind(AiHistoryPersistenceService).to(FileCommunicationPersistenceService);
29+
bind(ConnectionHandler)
30+
.toDynamicValue(
31+
ctx =>
32+
new RpcConnectionHandler(aiHistoryPersistenceServicePath, () =>
33+
ctx.container.get(AiHistoryPersistenceService)
34+
)
35+
)
36+
.inSingletonScope();
37+
2138
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2024 EclipseSource GmbH.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
import { CommunicationHistory, CommunicationRecordingService } from '@theia/ai-core';
17+
import { URI } from '@theia/core';
18+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
19+
import { inject, injectable } from '@theia/core/shared/inversify';
20+
import { readdirSync, readFileSync, writeFileSync } from 'fs';
21+
import { AiHistoryPersistenceService } from '../common/history-persistence';
22+
23+
@injectable()
24+
export class FileCommunicationPersistenceService implements AiHistoryPersistenceService {
25+
26+
@inject(EnvVariablesServer)
27+
protected envServer: EnvVariablesServer;
28+
@inject(CommunicationRecordingService)
29+
protected recordingService: CommunicationRecordingService;
30+
31+
async saveHistory(agentId: string, history: CommunicationHistory): Promise<void> {
32+
const historyDir = await this.getHistoryDirectoryPath();
33+
const fileName = `${historyDir}/${agentId}.json`;
34+
writeFileSync(fileName, JSON.stringify(history, undefined, 2));
35+
console.log(`Saving communication history for agent ${agentId} to ${fileName}`);
36+
}
37+
38+
private async getHistoryDirectoryPath(): Promise<string> {
39+
const configDir = new URI(await this.envServer.getConfigDirUri());
40+
const historyDir = `${configDir.path.fsPath()}/agent-communication`;
41+
return historyDir;
42+
}
43+
44+
async loadHistory(): Promise<void> {
45+
const historyDir = await this.getHistoryDirectoryPath();
46+
const fileNames = readdirSync(historyDir);
47+
for (const fileName of fileNames) {
48+
const agentId = fileName.replace('.json', '');
49+
const filePath = `${historyDir}/${fileName}`;
50+
try {
51+
const historyJson = readFileSync(filePath, 'utf-8');
52+
const communicationHistory = JSON.parse(historyJson);
53+
console.log(`Loaded communication history from ${agentId} from ${filePath}`);
54+
this.recordingService.setHistory(agentId, communicationHistory);
55+
} catch (error) {
56+
console.log(`Could not load communication history for agent ${agentId}. Returning empty history.`);
57+
}
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)