Skip to content

Commit 404e031

Browse files
committed
feat: context service
1 parent 359aa42 commit 404e031

File tree

5 files changed

+97
-146
lines changed

5 files changed

+97
-146
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { PrismaClient } from '@prisma/client';
3+
import { z } from 'zod';
4+
5+
// import type { FileLike } from './types';
6+
7+
const ContextConfigSchema = z.object({
8+
files: z
9+
.object({
10+
id: z.string(),
11+
chunk_size: z.number(),
12+
name: z.string(),
13+
})
14+
.array(),
15+
});
16+
17+
type ContextConfig = z.infer<typeof ContextConfigSchema>;
18+
19+
@Injectable()
20+
export class CopilotContextService {
21+
private readonly sessionCache = new Map<string, ContextSession>();
22+
23+
constructor(private readonly db: PrismaClient) {}
24+
25+
private saveContext(
26+
workspaceId: string,
27+
id: string,
28+
config: ContextConfig
29+
): ContextSession {
30+
const context = new ContextSession(workspaceId, id, config, this.db);
31+
this.sessionCache.set(context.id, context);
32+
return context;
33+
}
34+
35+
async getOrCreate(workspaceId: string, id?: string): Promise<ContextSession> {
36+
if (id) {
37+
const context = this.sessionCache.get(id);
38+
if (context) return context;
39+
const ret = await this.db.aiContext.findUnique({
40+
where: { workspaceId, id },
41+
select: { config: true },
42+
});
43+
if (ret) {
44+
const config = ContextConfigSchema.safeParse(ret.config);
45+
if (config.success)
46+
return this.saveContext(workspaceId, id, config.data);
47+
throw new Error('Invalid context config');
48+
}
49+
}
50+
51+
const context = await this.db.aiContext.create({
52+
data: { workspaceId, config: { files: [] } },
53+
});
54+
const config = ContextConfigSchema.parse(context.config);
55+
return this.saveContext(workspaceId, context.id, config);
56+
}
57+
}
58+
59+
export class ContextSession implements AsyncDisposable {
60+
constructor(
61+
private readonly wsId: string,
62+
private readonly contextId: string,
63+
private readonly config: ContextConfig,
64+
private readonly db: PrismaClient
65+
) {}
66+
67+
get workspaceId() {
68+
return this.wsId;
69+
}
70+
71+
get id() {
72+
return this.contextId;
73+
}
74+
75+
async list() {}
76+
77+
// async add(content: FileLike, signal?: AbortSignal) {}
78+
79+
// async remove(fileId: string) {}
80+
81+
async save() {
82+
await this.db.aiContext.update({
83+
where: { workspaceId: this.wsId, id: this.contextId },
84+
data: { config: this.config },
85+
});
86+
}
87+
88+
async [Symbol.asyncDispose]() {
89+
await this.save?.();
90+
}
91+
}

packages/backend/server/src/plugins/copilot/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FeatureModule } from '../../core/features';
55
import { PermissionModule } from '../../core/permission';
66
import { QuotaModule } from '../../core/quota';
77
import { Plugin } from '../registry';
8+
import { CopilotContextService } from './context';
89
import { CopilotController } from './controller';
910
import { ChatMessageCache } from './message';
1011
import { PromptService } from './prompt';
@@ -43,6 +44,7 @@ registerCopilotProvider(PerplexityProvider);
4344
PromptsManagementResolver,
4445
CopilotWorkflowService,
4546
...CopilotWorkflowExecutors,
47+
CopilotContextService,
4648
],
4749
controllers: [CopilotController],
4850
contributesTo: ServerFeature.Copilot,

packages/backend/server/src/plugins/copilot/providers/openai/index.ts renamed to packages/backend/server/src/plugins/copilot/providers/openai.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
CopilotProviderSideError,
77
metrics,
88
UserFriendlyError,
9-
} from '../../../../base';
9+
} from '../../../base';
1010
import {
1111
ChatMessageRole,
1212
CopilotCapability,
@@ -18,10 +18,8 @@ import {
1818
CopilotTextToEmbeddingProvider,
1919
CopilotTextToImageProvider,
2020
CopilotTextToTextProvider,
21-
CopilotTextToTextWithContextProvider,
2221
PromptMessage,
23-
} from '../../types';
24-
import { Context, ContextService } from './context';
22+
} from '../types';
2523

2624
export const DEFAULT_DIMENSIONS = 256;
2725

@@ -30,15 +28,13 @@ const SIMPLE_IMAGE_URL_REGEX = /^(https?:\/\/|data:image\/)/;
3028
export class OpenAIProvider
3129
implements
3230
CopilotTextToTextProvider,
33-
CopilotTextToTextWithContextProvider,
3431
CopilotTextToEmbeddingProvider,
3532
CopilotTextToImageProvider,
3633
CopilotImageToTextProvider
3734
{
3835
static readonly type = CopilotProviderType.OpenAI;
3936
static readonly capabilities = [
4037
CopilotCapability.TextToText,
41-
CopilotCapability.TextToTextWithContext,
4238
CopilotCapability.TextToEmbedding,
4339
CopilotCapability.TextToImage,
4440
CopilotCapability.ImageToText,
@@ -63,12 +59,11 @@ export class OpenAIProvider
6359

6460
private readonly logger = new Logger(OpenAIProvider.type);
6561
private readonly instance: OpenAI;
66-
private readonly context: ContextService;
62+
6763
private existsModels: string[] | undefined;
6864

6965
constructor(config: ClientOptions) {
7066
this.instance = new OpenAI(config);
71-
this.context = new ContextService(this.instance);
7267
}
7368

7469
static assetsConfig(config: ClientOptions) {
@@ -83,10 +78,6 @@ export class OpenAIProvider
8378
return OpenAIProvider.capabilities;
8479
}
8580

86-
getContext(name: string, id?: string): Promise<Context> {
87-
return this.context.getOrCreate(name, id);
88-
}
89-
9081
async isModelAvailable(model: string): Promise<boolean> {
9182
const knownModels = this.availableModels.includes(model);
9283
if (knownModels) return true;

packages/backend/server/src/plugins/copilot/providers/openai/context.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.

packages/backend/server/src/plugins/copilot/types.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ export enum CopilotProviderType {
166166

167167
export enum CopilotCapability {
168168
TextToText = 'text-to-text',
169-
TextToTextWithContext = 'text-to-text-with-context',
170169
TextToEmbedding = 'text-to-embedding',
171170
TextToImage = 'text-to-image',
172171
ImageToImage = 'image-to-image',
@@ -184,14 +183,6 @@ const CopilotChatOptionsSchema = CopilotProviderOptionsSchema.merge(
184183

185184
export type CopilotChatOptions = z.infer<typeof CopilotChatOptionsSchema>;
186185

187-
const CopilotChatWithContextOptionsSchema = CopilotProviderOptionsSchema.merge(
188-
z.object({ contextId: z.string().optional() }).strict()
189-
);
190-
191-
export type CopilotChatWithContextOptions = z.infer<
192-
typeof CopilotChatWithContextOptionsSchema
193-
>;
194-
195186
const CopilotEmbeddingOptionsSchema = CopilotProviderOptionsSchema.extend({
196187
dimensions: z.number(),
197188
}).optional();
@@ -214,7 +205,7 @@ export type CopilotContextFile = {
214205
id: string; // fileId
215206
created_at: number;
216207
// embedding status
217-
status: 'in_progress' | 'completed' | 'cancelled' | 'failed';
208+
status: 'in_progress' | 'completed' | 'failed';
218209
};
219210

220211
/**
@@ -241,13 +232,6 @@ export interface FileLike extends BlobLike {
241232
readonly name: string;
242233
}
243234

244-
export interface CopilotContext {
245-
list(): Promise<CopilotContextFile[]>;
246-
add(content: FileLike, signal?: AbortSignal): Promise<string>;
247-
addByFileId(context: string, signal?: AbortSignal): Promise<string>;
248-
remove(fileId: string): Promise<boolean>;
249-
}
250-
251235
export interface CopilotProvider {
252236
readonly type: CopilotProviderType;
253237
getCapabilities(): CopilotCapability[];
@@ -267,20 +251,6 @@ export interface CopilotTextToTextProvider extends CopilotProvider {
267251
): AsyncIterable<string>;
268252
}
269253

270-
export interface CopilotTextToTextWithContextProvider extends CopilotProvider {
271-
generateText(
272-
messages: PromptMessage[],
273-
model?: string,
274-
options?: CopilotChatWithContextOptions
275-
): Promise<string>;
276-
generateTextStream(
277-
messages: PromptMessage[],
278-
model?: string,
279-
options?: CopilotChatOptions
280-
): AsyncIterable<string>;
281-
getContext(name: string, id?: string): Promise<CopilotContext>;
282-
}
283-
284254
export interface CopilotTextToEmbeddingProvider extends CopilotProvider {
285255
generateEmbedding(
286256
messages: string[] | string,
@@ -330,7 +300,6 @@ export interface CopilotImageToImageProvider extends CopilotProvider {
330300

331301
export type CapabilityToCopilotProvider = {
332302
[CopilotCapability.TextToText]: CopilotTextToTextProvider;
333-
[CopilotCapability.TextToTextWithContext]: CopilotTextToTextWithContextProvider;
334303
[CopilotCapability.TextToEmbedding]: CopilotTextToEmbeddingProvider;
335304
[CopilotCapability.TextToImage]: CopilotTextToImageProvider;
336305
[CopilotCapability.ImageToText]: CopilotImageToTextProvider;

0 commit comments

Comments
 (0)