Skip to content

Commit

Permalink
feat: support llm tool call streaming and ui, more mcp tools
Browse files Browse the repository at this point in the history
  • Loading branch information
life2015 committed Feb 7, 2025
1 parent 301df94 commit 6d94523
Show file tree
Hide file tree
Showing 22 changed files with 638 additions and 169 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@ tools/workspace
# jupyter
.ipynb_checkpoints

*.tsbuildinfo
*.tsbuildinfo
.env
7 changes: 4 additions & 3 deletions packages/ai-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
"url": "[email protected]:opensumi/core.git"
},
"dependencies": {
"@ai-sdk/anthropic": "^1.0.9",
"@anthropic-ai/sdk": "^0.32.1",
"@ai-sdk/anthropic": "^1.1.6",
"@ai-sdk/deepseek": "^0.1.8",
"@anthropic-ai/sdk": "^0.36.3",
"@modelcontextprotocol/sdk": "^1.3.1",
"@opensumi/ide-components": "workspace:*",
"@opensumi/ide-core-common": "workspace:*",
Expand All @@ -41,7 +42,7 @@
"@opensumi/ide-utils": "workspace:*",
"@opensumi/ide-workspace": "workspace:*",
"@xterm/xterm": "5.5.0",
"ai": "^4.0.38",
"ai": "^4.1.21",
"ansi-regex": "^2.0.0",
"dom-align": "^1.7.0",
"openai": "^4.55.7",
Expand Down
15 changes: 15 additions & 0 deletions packages/ai-native/src/browser/ai-core.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,21 @@ export class AINativeBrowserContribution
},
],
});

// Register language model API key settings
registry.registerSettingSection(AI_NATIVE_SETTING_GROUP_ID, {
title: localize('preference.ai.native.apiKeys.title'),
preferences: [
{
id: AINativeSettingSectionsId.DeepseekApiKey,
localized: 'preference.ai.native.deepseek.apiKey',
},
{
id: AINativeSettingSectionsId.AnthropicApiKey,
localized: 'preference.ai.native.anthropic.apiKey',
},
],
});
}

if (this.aiNativeConfigService.capabilities.supportsInlineChat) {
Expand Down
9 changes: 4 additions & 5 deletions packages/ai-native/src/browser/chat/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,14 @@ export class ChatResponseModel extends Disposable {
this.#responseParts.push(progress);
this.#updateResponseText(quiet);
} else if (progress.kind === 'toolCall') {
// @ts-ignore
const find: IChatToolContent | undefined = this.#responseParts.find((item) => item.kind === 'toolCall' && (item.content.id === progress.content.id || item.content.index === progress.content.index));
const find = this.#responseParts.find((item) => item.kind === 'toolCall' && (item.content.id === progress.content.id));
if (find) {
find.content.function.arguments = find.content.function.arguments + progress.content.function.arguments;
this.#responseParts[responsePartLength] = find;
// @ts-ignore
find.content = progress.content;
// this.#responseParts[responsePartLength] = find;
} else {
this.#responseParts.push(progress);
}
console.log("🚀 ~ ChatResponseModel ~ updateContent ~ this.#responseParts:", this.#responseParts)
this.#updateResponseText(quiet);
}
}
Expand Down
25 changes: 20 additions & 5 deletions packages/ai-native/src/browser/chat/chat-proxy.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { Autowired, Injectable } from '@opensumi/di';
import { PreferenceService } from '@opensumi/ide-core-browser';
import {
AIBackSerivcePath,
CancellationToken,
ChatAgentViewServiceToken,
ChatFeatureRegistryToken,
ChatServiceToken,
Deferred,
Disposable,
IAIBackService,
IAIReporter,
IApplicationService,
IChatProgress,
uuid,
} from '@opensumi/ide-core-common';
uuid } from '@opensumi/ide-core-common';
import { AINativeSettingSectionsId } from '@opensumi/ide-core-common/lib/settings/ai-native';
import { IChatMessage } from '@opensumi/ide-core-common/lib/types/ai-native';
import { MonacoCommandRegistry } from '@opensumi/ide-editor/lib/browser/monaco-contrib/command/command.service';
import { listenReadable } from '@opensumi/ide-utils/lib/stream';
Expand All @@ -22,12 +25,12 @@ import {
IChatAgentService,
IChatAgentWelcomeMessage,
} from '../../common';
import { ChatToolRender } from '../components/ChatToolRender';
import { IChatAgentViewService } from '../types';

import { ChatService } from './chat.api.service';
import { ChatFeatureRegistry } from './chat.feature.registry';
import { ChatAgentViewServiceToken } from '@opensumi/ide-core-common';
import { IChatAgentViewService } from '../types';
import { ChatToolRender } from '../components/ChatToolRender';


/**
* @internal
Expand Down Expand Up @@ -58,6 +61,12 @@ export class ChatProxyService extends Disposable {
@Autowired(ChatAgentViewServiceToken)
private readonly chatAgentViewService: IChatAgentViewService;

@Autowired(PreferenceService)
private readonly preferenceService: PreferenceService;

@Autowired(IApplicationService)
private readonly applicationService: IApplicationService;

private chatDeferred: Deferred<void> = new Deferred<void>();

public registerDefaultAgent() {
Expand Down Expand Up @@ -91,12 +100,18 @@ export class ChatProxyService extends Disposable {
}
}

const model = 'claude-3-5-sonnet'; // TODO 从配置中获取
const apiKey = this.preferenceService.get<string>(AINativeSettingSectionsId.AnthropicApiKey);

const stream = await this.aiBackService.requestStream(
prompt,
{
requestId: request.requestId,
sessionId: request.sessionId,
history: this.aiChatService.getHistoryMessages(),
clientId: this.applicationService.clientId,
apiKey,
model,
},
token,
);
Expand Down
4 changes: 1 addition & 3 deletions packages/ai-native/src/browser/components/ChatReply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ const TreeRenderer = (props: { treeData: IChatResponseProgressFileTreeData }) =>
};

const ToolCallRender = (props: { toolCall: IChatToolContent['content'] }) => {
console.log("🚀 ~ ToolCallRender ~ props:", props)
const { toolCall } = props;
const chatAgentViewService = useInjectable<IChatAgentViewService>(ChatAgentViewServiceToken);
const [node, setNode] = useState<React.JSX.Element | null>(null);
Expand All @@ -172,7 +171,7 @@ const ToolCallRender = (props: { toolCall: IChatToolContent['content'] }) => {
deferred.promise.then(({ component: Component, initialProps }) => {
setNode(<Component {...initialProps} value={toolCall} />);
});
}, [toolCall]);
}, [toolCall.state]);

return node;
};
Expand Down Expand Up @@ -231,7 +230,6 @@ export const ChatReply = (props: IChatReplyProps) => {

disposableCollection.push(
request.response.onDidChange(() => {
console.log("🚀 ~ request.response.onDidChange ~ onDidChange:", 'onDidChange')
history.updateAssistantMessage(msgId, { content: request.response.responseText });

if (request.response.isComplete) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.chat-tool-render {
margin: 8px 0;
border: 1px solid #363636;
border-radius: 6px;
overflow: hidden;

.tool-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background-color: #2D2D2D;
cursor: pointer;
user-select: none;

&:hover {
background-color: #363636;
}
}

.tool-name {
display: flex;
align-items: center;
font-weight: 500;
color: #CCCCCC;
}

.expand-icon {
display: inline-block;
margin-right: 8px;
transition: transform 0.2s;
color: #888888;

&.expanded {
transform: rotate(90deg);
}
}

.tool-state {
display: flex;
align-items: center;
font-size: 12px;
color: #888888;
}

.state-icon {
display: flex;
align-items: center;
margin-right: 6px;
}

.loading-icon {
width: 12px;
height: 12px;
}

.state-label {
margin-left: 4px;
}

.tool-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
background-color: #1E1E1E;

&.expanded {
max-height: 1000px;
}
}

.tool-arguments,
.tool-result {
padding: 12px;
}

.section-label {
font-size: 12px;
color: #888888;
margin-bottom: 8px;
}

.tool-result {
border-top: 1px solid #363636;
}
}
86 changes: 67 additions & 19 deletions packages/ai-native/src/browser/components/ChatToolRender.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,77 @@
import React from 'react';
import cls from 'classnames';
import React, { useState } from 'react';

import { Icon } from '@opensumi/ide-core-browser/lib/components';
import { Loading } from '@opensumi/ide-core-browser/lib/components/ai-native';
import { IChatToolContent, uuid } from '@opensumi/ide-core-common';

import { CodeEditorWithHighlight } from './ChatEditor';
import styles from './ChatToolRender.module.less';

export const ChatToolRender = (props: { value: IChatToolContent['content'] }) => {
const { value } = props;
console.log('🚀 ~ ChatToolRender ~ toolCall:', value);
const { value } = props;
const [isExpanded, setIsExpanded] = useState(false);

if (!value || !value.function || !value.id) {
return null;
if (!value || !value.function || !value.id) {
return null;
}

const getStateInfo = (state?: string): { label: string; icon: React.ReactNode } => {
switch (state) {
case 'streaming-start':
case 'streaming':
return { label: 'Generating', icon: <Loading /> };
case 'complete':
return { label: 'Complete', icon: <Icon iconClass="codicon codicon-check" /> };
case 'result':
return { label: 'Result Ready', icon: <Icon iconClass="codicon codicon-check-all" /> };
default:
return { label: state || 'Unknown', icon: <Icon iconClass="codicon codicon-question" /> };
}
};

const toggleExpand = () => {
setIsExpanded(!isExpanded);
};

const stateInfo = getStateInfo(value.state);

return <div>
<span>Using Tool: </span>
<span>{value?.function?.name}</span>
<br />
<span></span>
{
value?.function?.arguments &&
(<CodeEditorWithHighlight
input={value?.function?.arguments}
language={'json'}
relationId={uuid(4)}
/>)
}
</div>;
return (
<div className={styles['chat-tool-render']}>
<div className={styles['tool-header']} onClick={toggleExpand}>
<div className={styles['tool-name']}>
<span className={cls(styles['expand-icon'], { [styles.expanded]: isExpanded })}></span>
{value?.function?.name}
</div>
{value.state && (
<div className={styles['tool-state']}>
<span className={styles['state-icon']}>{stateInfo.icon}</span>
<span className={styles['state-label']}>{stateInfo.label}</span>
</div>
)}
</div>
<div className={cls(styles['tool-content'], { [styles.expanded]: isExpanded })}>
{value?.function?.arguments && (
<div className={styles['tool-arguments']}>
<div className={styles['section-label']}>Arguments</div>
<CodeEditorWithHighlight
input={value?.function?.arguments}
language={'json'}
relationId={uuid(4)}
/>
</div>
)}
{value?.result && (
<div className={styles['tool-result']}>
<div className={styles['section-label']}>Result</div>
<CodeEditorWithHighlight
input={value.result}
language={'json'}
relationId={uuid(4)}
/>
</div>
)}
</div>
</div>
);
};
2 changes: 2 additions & 0 deletions packages/ai-native/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { GetFileTextByPathTool } from './mcp/tools/getFileTextByPath';
import { GetOpenEditorFileDiagnosticsTool } from './mcp/tools/getOpenEditorFileDiagnostics';
import { GetOpenEditorFileTextTool } from './mcp/tools/getOpenEditorFileText';
import { GetSelectedTextTool } from './mcp/tools/getSelectedText';
import { ReplaceOpenEditorFileByDiffPreviewerTool } from './mcp/tools/replaceOpenEditorFileByDiffPreviewer';
import { AINativePreferencesContribution } from './preferences';
import { AINativeCoreContribution, MCPServerContribution, TokenMCPServerRegistry } from './types';
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
Expand Down Expand Up @@ -96,6 +97,7 @@ export class AINativeModule extends BrowserModule {
GetCurrentFilePathTool,
FindFilesByNameSubstringTool,
GetDiagnosticsByPathTool,
ReplaceOpenEditorFileByDiffPreviewerTool,
// MCP Server Contributions END

{
Expand Down
Loading

1 comment on commit 6d94523

@opensumi
Copy link
Contributor

@opensumi opensumi bot commented on 6d94523 Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Next publish successful!

3.7.1-next-1739175521.0

Please sign in to comment.