Skip to content

Commit 281361f

Browse files
authored
feat: support chat agent rules and context management (#4578)
* feat: support process large image on tool result * fix: typos * chore: improve compress * chore: reverse chat history * chore: fix * chore: update iconfont * chore: add rules * chore: update icons * chore: update icons * chore: update icons * feat: support better llm context * feat: support rules configuration * feat: support add rules manualy * feat: support global and cursor rules * feat: support manage reference on header * chore: support add context by click @ * feat: support remove context by chat input * chore: fix types * feat: improve some cases * chore: use findApplicableRules function * chore: fix build * chore: update deps * chore: update locks
1 parent 3b97a07 commit 281361f

51 files changed

Lines changed: 2122 additions & 244 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/ai-native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@opensumi/ide-outline": "workspace:*",
4141
"@opensumi/ide-overlay": "workspace:*",
4242
"@opensumi/ide-preferences": "workspace:*",
43+
"@opensumi/ide-quick-open": "workspace:*",
4344
"@opensumi/ide-search": "workspace:*",
4445
"@opensumi/ide-terminal-next": "workspace:*",
4546
"@opensumi/ide-theme": "workspace:*",

packages/ai-native/src/browser/chat/chat-proxy.service.ts

Lines changed: 68 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import { Autowired, Injectable } from '@opensumi/di';
2-
import { AppConfig, PreferenceService } from '@opensumi/ide-core-browser';
2+
import { PreferenceService } from '@opensumi/ide-core-browser';
33
import {
44
AIBackSerivcePath,
55
CancellationToken,
66
ChatAgentViewServiceToken,
77
ChatFeatureRegistryToken,
8-
ChatServiceToken,
98
Deferred,
109
Disposable,
1110
IAIBackService,
1211
IAIReporter,
1312
IApplicationService,
1413
IChatProgress,
15-
getOperatingSystemName,
1614
} from '@opensumi/ide-core-common';
1715
import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings/ai-native';
1816
import { MonacoCommandRegistry } from '@opensumi/ide-editor/lib/browser/monaco-contrib/command/command.service';
@@ -27,10 +25,10 @@ import {
2725
IChatAgentService,
2826
IChatAgentWelcomeMessage,
2927
} from '../../common';
28+
import { DEFAULT_SYSTEM_PROMPT } from '../../common/prompts/system-prompt';
3029
import { ChatToolRender } from '../components/ChatToolRender';
3130
import { IChatAgentViewService } from '../types';
3231

33-
import { ChatService } from './chat.api.service';
3432
import { ChatFeatureRegistry } from './chat.feature.registry';
3533

3634
/**
@@ -53,9 +51,6 @@ export class ChatProxyService extends Disposable {
5351
@Autowired(MonacoCommandRegistry)
5452
private readonly monacoCommandRegistry: MonacoCommandRegistry;
5553

56-
@Autowired(ChatServiceToken)
57-
private aiChatService: ChatService;
58-
5954
@Autowired(IAIReporter)
6055
private readonly aiReporter: IAIReporter;
6156

@@ -71,9 +66,6 @@ export class ChatProxyService extends Disposable {
7166
@Autowired(IMessageService)
7267
private readonly messageService: IMessageService;
7368

74-
@Autowired(AppConfig)
75-
private readonly appConfig: AppConfig;
76-
7769
private chatDeferred: Deferred<void> = new Deferred<void>();
7870

7971
public getRequestOptions() {
@@ -112,79 +104,74 @@ export class ChatProxyService extends Disposable {
112104
initialProps: {},
113105
});
114106

115-
this.addDispose(
116-
this.chatAgentService.registerAgent({
117-
id: ChatProxyService.AGENT_ID,
118-
metadata: {
119-
systemPrompt:
120-
this.preferenceService.get<string>(
107+
this.applicationService.getBackendOS().then(() => {
108+
this.addDispose(
109+
this.chatAgentService.registerAgent({
110+
id: ChatProxyService.AGENT_ID,
111+
metadata: {
112+
systemPrompt: this.preferenceService.get<string>(
121113
AINativeSettingSectionsId.SystemPrompt,
122-
'You are a powerful AI coding assistant working in OpenSumi, a top IDE framework. You collaborate with a USER to solve coding tasks, which may involve creating, modifying, or debugging code, or answering questions. When the USER sends a message, relevant context (e.g., open files, cursor position, edit history, linter errors) may be attached. Use this information as needed.\n\n<tool_calling>\nYou have access to tools to assist with tasks. Follow these rules:\n1. Always adhere to the tool call schema and provide all required parameters.\n2. Only use tools explicitly provided; ignore unavailable ones.\n3. Avoid mentioning tool names to the USER (e.g., say "I will edit your file" instead of "I need to use the edit_file tool").\n4. Only call tools when necessary; respond directly if the task is general or you already know the answer.\n5. Explain to the USER why you’re using a tool before calling it.\n</tool_calling>\n\n<making_code_changes>\nWhen modifying code:\n1. Use code edit tools instead of outputting code unless explicitly requested.\n2. Limit tool calls to one per turn.\n3. Ensure generated code is immediately executable by including necessary imports, dependencies, and endpoints.\n4. For new projects, create a dependency management file (e.g., requirements.txt) and a README.\n5. For web apps, design a modern, user-friendly UI.\n6. Avoid generating non-textual or excessively long code.\n7. Read file contents before editing, unless appending a small change or creating a new file.\n8. Fix introduced linter errors if possible, but stop after 3 attempts and ask the USER for guidance.\n9. Reapply reasonable code edits if they weren’t followed initially.\n</making_code_changes>\n\nUse the appropriate tools to fulfill the USER’s request, ensuring all required parameters are provided or inferred from context.',
123-
) +
124-
`\n\n<user_info>\nThe user's OS is ${getOperatingSystemName()}. The absolute path of the user's workspace is ${
125-
this.appConfig.workspaceDir
126-
}.\n</user_info>`,
127-
},
128-
invoke: async (
129-
request: IChatAgentRequest,
130-
progress: (part: IChatProgress) => void,
131-
history: CoreMessage[],
132-
token: CancellationToken,
133-
): Promise<IChatAgentResult> => {
134-
this.chatDeferred = new Deferred<void>();
135-
136-
const { message, command } = request;
137-
let prompt: string = message;
138-
139-
if (command) {
140-
const commandHandler = this.chatFeatureRegistry.getSlashCommandHandler(command);
141-
if (commandHandler && commandHandler.providerPrompt) {
142-
const editor = this.monacoCommandRegistry.getActiveCodeEditor();
143-
const slashCommandPrompt = await commandHandler.providerPrompt(message, editor);
144-
prompt = slashCommandPrompt;
114+
DEFAULT_SYSTEM_PROMPT,
115+
),
116+
},
117+
invoke: async (
118+
request: IChatAgentRequest,
119+
progress: (part: IChatProgress) => void,
120+
history: CoreMessage[],
121+
token: CancellationToken,
122+
): Promise<IChatAgentResult> => {
123+
this.chatDeferred = new Deferred<void>();
124+
const { message, command } = request;
125+
let prompt: string = message;
126+
if (command) {
127+
const commandHandler = this.chatFeatureRegistry.getSlashCommandHandler(command);
128+
if (commandHandler && commandHandler.providerPrompt) {
129+
const editor = this.monacoCommandRegistry.getActiveCodeEditor();
130+
const slashCommandPrompt = await commandHandler.providerPrompt(message, editor);
131+
prompt = slashCommandPrompt;
132+
}
145133
}
146-
}
147-
148-
const stream = await this.aiBackService.requestStream(
149-
prompt,
150-
{
151-
requestId: request.requestId,
152-
sessionId: request.sessionId,
153-
history,
154-
images: request.images,
155-
...this.getRequestOptions(),
156-
},
157-
token,
158-
);
159-
160-
listenReadable<IChatProgress>(stream, {
161-
onData: (data) => {
162-
progress(data);
163-
},
164-
onEnd: () => {
165-
this.chatDeferred.resolve();
166-
},
167-
onError: (error) => {
168-
this.messageService.error(error.message);
169-
this.aiReporter.end(request.sessionId + '_' + request.requestId, {
170-
message: error.message,
171-
success: false,
172-
command,
173-
});
174-
},
175-
});
176-
177-
await this.chatDeferred.promise;
178-
return {};
179-
},
180-
provideSlashCommands: async (token: CancellationToken): Promise<IChatAgentCommand[]> =>
181-
this.chatFeatureRegistry
182-
.getAllSlashCommand()
183-
.map((s) => ({ ...s, name: s.name, description: s.description || '' })),
184-
provideChatWelcomeMessage: async (token: CancellationToken): Promise<IChatAgentWelcomeMessage | undefined> =>
185-
undefined,
186-
}),
187-
);
134+
135+
const stream = await this.aiBackService.requestStream(
136+
prompt,
137+
{
138+
requestId: request.requestId,
139+
sessionId: request.sessionId,
140+
history,
141+
images: request.images,
142+
...this.getRequestOptions(),
143+
},
144+
token,
145+
);
146+
147+
listenReadable<IChatProgress>(stream, {
148+
onData: (data) => {
149+
progress(data);
150+
},
151+
onEnd: () => {
152+
this.chatDeferred.resolve();
153+
},
154+
onError: (error) => {
155+
this.messageService.error(error.message);
156+
this.aiReporter.end(request.sessionId + '_' + request.requestId, {
157+
message: error.message,
158+
success: false,
159+
command,
160+
});
161+
},
162+
});
163+
164+
await this.chatDeferred.promise;
165+
return {};
166+
},
167+
provideSlashCommands: async (): Promise<IChatAgentCommand[]> =>
168+
this.chatFeatureRegistry
169+
.getAllSlashCommand()
170+
.map((s) => ({ ...s, name: s.name, description: s.description || '' })),
171+
provideChatWelcomeMessage: async (): Promise<IChatAgentWelcomeMessage | undefined> => undefined,
172+
}),
173+
);
174+
});
188175

189176
queueMicrotask(() => {
190177
this.chatAgentService.updateAgent(ChatProxyService.AGENT_ID, {});

packages/ai-native/src/browser/chat/chat.view.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,12 @@ export const AIChatView = () => {
617617
const { message, images, agentId, command, reportExtra } = value;
618618
const { actionType, actionSource } = reportExtra || {};
619619

620-
const request = aiChatService.createRequest(message, agentId!, images, command);
620+
const request = aiChatService.createRequest(
621+
message.replaceAll(LLM_CONTEXT_KEY_REGEX, ''),
622+
agentId!,
623+
images,
624+
command,
625+
);
621626
if (!request) {
622627
return;
623628
}
@@ -641,7 +646,7 @@ export const AIChatView = () => {
641646
600 * 1000,
642647
);
643648
msgHistoryManager.addUserMessage({
644-
content: message.replaceAll(LLM_CONTEXT_KEY_REGEX, ''),
649+
content: message,
645650
images: images || [],
646651
agentId: agentId!,
647652
agentCommand: command!,
@@ -695,14 +700,9 @@ export const AIChatView = () => {
695700
let processedContent = message;
696701
const filePattern = /\{\{@file:(.*?)\}\}/g;
697702
const fileMatches = message.match(filePattern);
698-
let isCleanContext = false;
699703
if (fileMatches) {
700704
for (const match of fileMatches) {
701705
const filePath = match.replace(/\{\{@file:(.*?)\}\}/, '$1');
702-
if (filePath && !isCleanContext) {
703-
isCleanContext = true;
704-
llmContextService.cleanFileContext();
705-
}
706706
const fileUri = new URI(filePath);
707707
const relativePath = (await workspaceService.asRelativePath(fileUri))?.path || fileUri.displayName;
708708
processedContent = processedContent.replace(match, `\`${LLM_CONTEXT_KEY.AttachedFile}${relativePath}\``);
@@ -738,6 +738,18 @@ export const AIChatView = () => {
738738
);
739739
}
740740
}
741+
const rulePattern = /\{\{@rule:(.*?)\}\}/g;
742+
const ruleMatches = processedContent.match(rulePattern);
743+
if (ruleMatches) {
744+
for (const match of ruleMatches) {
745+
const ruleName = match.replace(/\{\{@rule:(.*?)\}\}/, '$1');
746+
const ruleUri = new URI(ruleName);
747+
processedContent = processedContent.replace(
748+
match,
749+
`\`${LLM_CONTEXT_KEY.AttachedFile}${ruleUri.displayName}\``,
750+
);
751+
}
752+
}
741753
return handleAgentReply({ message: processedContent, images, agentId, command, reportExtra });
742754
},
743755
[handleAgentReply],

0 commit comments

Comments
 (0)