Skip to content

Commit

Permalink
GH-56 WIP Ai History Persistance Service
Browse files Browse the repository at this point in the history
Co-authored-by: Olaf Lessenich <[email protected]>
  • Loading branch information
ndoschek and xai committed Jul 30, 2024
1 parent 7df16e0 commit 205867f
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ export interface CommunicationRecordingService {
recordRequest(requestEntry: CommunicationHistoryEntry): void;
recordResponse(responseEntry: CommunicationHistoryEntry): void;
getHistory(agentId: string): CommunicationHistory;
setHistory(agentId: string, history: CommunicationHistory): void;
getRecordedAgents(): string[];
loadHistory(): Promise<void>
}
2 changes: 1 addition & 1 deletion packages/ai-core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './agent';
export * from './communication-recording-service';
export * from './communication-recording-types';
export * from './language-model';
export * from './language-model-delegate';
export * from './prompt-service';
Expand Down
3 changes: 2 additions & 1 deletion packages/ai-history/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
},
"theiaExtensions": [
{
"frontend": "lib/browser/ai-history-frontend-module"
"frontend": "lib/browser/ai-history-frontend-module",
"backend": "lib/node/ai-history-backend-module"
}
],
"keywords": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { CommunicationRecordingService } from '@theia/ai-core';
import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';

@injectable()
export class AIHistoryFrontendContribution implements FrontendApplicationContribution {

@inject(CommunicationRecordingService)
protected recordingService: CommunicationRecordingService;

async onStart(app: FrontendApplication): Promise<void> {
this.recordingService.loadHistory();
}
}
11 changes: 11 additions & 0 deletions packages/ai-history/src/browser/ai-history-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationRecordingService } from '@theia/ai-core';
import { FrontendApplicationContribution, WebSocketConnectionProvider } from '@theia/core/lib/browser';
import { ContainerModule } from '@theia/core/shared/inversify';
import { DefaultCommunicationRecordingService } from '../common/communication-recording-service';
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
import { AIHistoryFrontendContribution } from './ai-history-frontend-contribution';

export default new ContainerModule(bind => {
bind(FrontendApplicationContribution).to(AIHistoryFrontendContribution).inSingletonScope();

bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);

bind(AiHistoryPersistenceService).toDynamicValue(ctx => {
const connection = ctx.container.get(WebSocketConnectionProvider);
return connection.createProxy<AiHistoryPersistenceService>(aiHistoryPersistenceServicePath);
}).inSingletonScope();

});
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,37 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationHistory, CommunicationHistoryEntry, CommunicationRecordingService } from '@theia/ai-core';
import { injectable } from '@theia/core/shared/inversify';
import { inject, injectable } from '@theia/core/shared/inversify';
import { AiHistoryPersistenceService } from './history-persistence';

@injectable()
export class DefaultCommunicationRecordingService implements CommunicationRecordingService {

@inject(AiHistoryPersistenceService)
protected persistenceService: AiHistoryPersistenceService;

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

getRecordedAgents(): string[] {
return Object.keys(this.history);
}

getHistory(agentId: string): CommunicationHistory {
return this.history.get(agentId) || [];
}

setHistory(agentId: string, history: CommunicationHistory): void {
this.history.set(agentId, history);
}

recordRequest(requestEntry: CommunicationHistoryEntry): void {
console.log('Recording request:', requestEntry.request);
if (this.history.has(requestEntry.agentId)) {
this.history.get(requestEntry.agentId)?.push(requestEntry);
} else {
this.history.set(requestEntry.agentId, [requestEntry]);
}
this.persistenceService.saveHistory(requestEntry.agentId, this.history.get(requestEntry.agentId)!);
}

recordResponse(responseEntry: CommunicationHistoryEntry): void {
Expand All @@ -46,6 +59,14 @@ export class DefaultCommunicationRecordingService implements CommunicationRecord
matchingEntry.response = responseEntry.response;
matchingEntry.responseTime = responseEntry.timestamp - matchingEntry.timestamp;
}
this.persistenceService.saveHistory(responseEntry.agentId, this.history.get(responseEntry.agentId)!);
}
}

async loadHistory(): Promise<void> {
(await this.persistenceService.getRecordedAgents()).forEach(async agentId => {
const history = await this.persistenceService.loadHistory(agentId);
this.history.set(agentId, history);
});
}
}
26 changes: 26 additions & 0 deletions packages/ai-history/src/common/history-persistence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { CommunicationHistory } from '@theia/ai-core';

export const aiHistoryPersistenceServicePath = '/services/aiHistoryPersistenceService';

export const AiHistoryPersistenceService = Symbol('AiHistoryPersistenceService');
export interface AiHistoryPersistenceService {
saveHistory(agentId: string, history: CommunicationHistory): Promise<void>;
loadHistory(agentId: string): Promise<CommunicationHistory>;
getRecordedAgents(): Promise<string[]>;
}
38 changes: 38 additions & 0 deletions packages/ai-history/src/node/ai-history-backend-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationRecordingService } from '@theia/ai-core';
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
import { ContainerModule } from '@theia/core/shared/inversify';
import { DefaultCommunicationRecordingService } from '../common';
import { AiHistoryPersistenceService, aiHistoryPersistenceServicePath } from '../common/history-persistence';
import { FileCommunicationPersistenceService } from './communication-persistence-service';

export default new ContainerModule(bind => {
bind(DefaultCommunicationRecordingService).toSelf().inSingletonScope();
bind(CommunicationRecordingService).toService(DefaultCommunicationRecordingService);

bind(FileCommunicationPersistenceService).toSelf().inSingletonScope();
bind(AiHistoryPersistenceService).to(FileCommunicationPersistenceService);
bind(ConnectionHandler)
.toDynamicValue(
ctx =>
new RpcConnectionHandler(aiHistoryPersistenceServicePath, () =>
ctx.container.get(AiHistoryPersistenceService)
)
)
.inSingletonScope();

});
61 changes: 61 additions & 0 deletions packages/ai-history/src/node/communication-persistence-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { CommunicationHistory } from '@theia/ai-core';
import { URI } from '@theia/core';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { inject, injectable } from '@theia/core/shared/inversify';
import { readdirSync, readFileSync, writeFileSync } from 'fs';
import { AiHistoryPersistenceService } from '../common/history-persistence';

@injectable()
export class FileCommunicationPersistenceService implements AiHistoryPersistenceService {

@inject(EnvVariablesServer)
protected envServer: EnvVariablesServer;

async saveHistory(agentId: string, history: CommunicationHistory): Promise<void> {
const historyDir = await this.getHistoryDirectoryPath();
const fileName = `${historyDir}/${agentId}.json`;
writeFileSync(fileName, JSON.stringify(history, undefined, 2));
console.log(`Saving communication history for agent ${agentId} to ${fileName}`);
}

private async getHistoryDirectoryPath(): Promise<string> {
const configDir = new URI(await this.envServer.getConfigDirUri());
const historyDir = `${configDir.path.fsPath()}/agent-communication`;
return historyDir;
}

async loadHistory(agentId: string): Promise<CommunicationHistory> {
const historyDir = await this.getHistoryDirectoryPath();
const filePath = `${historyDir}/${agentId}.json`;
try {
const historyJson = readFileSync(filePath, 'utf-8');
const communicationHistory = JSON.parse(historyJson);
console.log(`Loaded communication history from ${agentId} from ${filePath}`);
return communicationHistory;
} catch (error) {
console.log(`Could not load communication history for agent ${agentId}. Returning empty history.`);
}
return [];
}

async getRecordedAgents(): Promise<string[]> {
const historyDir = await this.getHistoryDirectoryPath();
const files = readdirSync(historyDir);
return files.map((file: string) => file.replace('.json', ''));
}
}

0 comments on commit 205867f

Please sign in to comment.