Skip to content

Commit

Permalink
feat: Integrate SCANOSS
Browse files Browse the repository at this point in the history
Adds SCANOSS integration in Theia, supporting AI-driven code snippet
scanning for open-source and security compliance.

Adds new @theia/scanoss and @theia/ai-scanoss packages to support
SCANOSS integration.

- Provides SCANOSS service for content scanning.
- Introduces preferences for API key configuration and automatic
  scanning options.
- Integrates SCANOSS action into AI Chat UI as part of the code response
  renderer
- Displays detailed match results with links and additional information.

Also:
- Adds a pluggable CodePartRendererAction interface for contributing
  actions to code parts in AI responses.
- Adapts code base for updated dependencies where required.
  • Loading branch information
sdirix committed Dec 13, 2024
1 parent 901c932 commit d8c2c83
Show file tree
Hide file tree
Showing 48 changed files with 1,413 additions and 47 deletions.
2 changes: 2 additions & 0 deletions examples/browser-only/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@theia/ai-history": "1.56.0",
"@theia/ai-ollama": "1.56.0",
"@theia/ai-openai": "1.56.0",
"@theia/ai-scanoss": "1.56.0",
"@theia/api-samples": "1.56.0",
"@theia/bulk-edit": "1.56.0",
"@theia/callhierarchy": "1.56.0",
Expand Down Expand Up @@ -53,6 +54,7 @@
"@theia/preview": "1.56.0",
"@theia/process": "1.56.0",
"@theia/property-view": "1.56.0",
"@theia/scanoss": "1.56.0",
"@theia/scm": "1.56.0",
"@theia/scm-extra": "1.56.0",
"@theia/search-in-workspace": "1.56.0",
Expand Down
6 changes: 6 additions & 0 deletions examples/browser-only/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
{
"path": "../../packages/ai-openai"
},
{
"path": "../../packages/ai-scanoss"
},
{
"path": "../../packages/bulk-edit"
},
Expand Down Expand Up @@ -119,6 +122,9 @@
{
"path": "../../packages/property-view"
},
{
"path": "../../packages/scanoss"
},
{
"path": "../../packages/scm"
},
Expand Down
2 changes: 2 additions & 0 deletions examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@theia/ai-mcp": "1.56.0",
"@theia/ai-ollama": "1.56.0",
"@theia/ai-openai": "1.56.0",
"@theia/ai-scanoss": "1.56.0",
"@theia/ai-terminal": "1.56.0",
"@theia/ai-workspace-agent": "1.56.0",
"@theia/api-provider-sample": "1.56.0",
Expand Down Expand Up @@ -70,6 +71,7 @@
"@theia/process": "1.56.0",
"@theia/property-view": "1.56.0",
"@theia/remote": "1.56.0",
"@theia/scanoss": "1.56.0",
"@theia/scm": "1.56.0",
"@theia/scm-extra": "1.56.0",
"@theia/search-in-workspace": "1.56.0",
Expand Down
6 changes: 6 additions & 0 deletions examples/browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
{
"path": "../../packages/ai-openai"
},
{
"path": "../../packages/ai-scanoss"
},
{
"path": "../../packages/ai-terminal"
},
Expand Down Expand Up @@ -146,6 +149,9 @@
{
"path": "../../packages/remote"
},
{
"path": "../../packages/scanoss"
},
{
"path": "../../packages/scm"
},
Expand Down
2 changes: 2 additions & 0 deletions examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@theia/ai-llamafile": "1.56.0",
"@theia/ai-ollama": "1.56.0",
"@theia/ai-openai": "1.56.0",
"@theia/ai-scanoss": "1.56.0",
"@theia/ai-terminal": "1.56.0",
"@theia/ai-workspace-agent": "1.56.0",
"@theia/api-provider-sample": "1.56.0",
Expand Down Expand Up @@ -72,6 +73,7 @@
"@theia/process": "1.56.0",
"@theia/property-view": "1.56.0",
"@theia/remote": "1.56.0",
"@theia/scanoss": "1.56.0",
"@theia/scm": "1.56.0",
"@theia/scm-extra": "1.56.0",
"@theia/search-in-workspace": "1.56.0",
Expand Down
6 changes: 6 additions & 0 deletions examples/electron/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
{
"path": "../../packages/ai-openai"
},
{
"path": "../../packages/ai-scanoss"
},
{
"path": "../../packages/ai-terminal"
},
Expand Down Expand Up @@ -140,6 +143,9 @@
{
"path": "../../packages/remote"
},
{
"path": "../../packages/scanoss"
},
{
"path": "../../packages/scm"
},
Expand Down
27 changes: 23 additions & 4 deletions packages/ai-chat-ui/src/browser/ai-chat-ui-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,27 @@ import { bindViewContribution, FrontendApplicationContribution, WidgetFactory }
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
import { EditorManager } from '@theia/editor/lib/browser';
import '../../src/browser/style/index.css';
import { AIChatContribution } from './ai-chat-ui-contribution';
import { AIChatInputWidget } from './chat-input-widget';
import { ChatNodeToolbarActionContribution } from './chat-node-toolbar-action-contribution';
import { ChatResponsePartRenderer } from './chat-response-part-renderer';
import { CodePartRenderer, CommandPartRenderer, ErrorPartRenderer, HorizontalLayoutPartRenderer, MarkdownPartRenderer, ToolCallPartRenderer } from './chat-response-renderer';
import {
AIEditorManager, AIEditorSelectionResolver,
GitHubSelectionResolver, TextFragmentSelectionResolver, TypeDocSymbolSelectionResolver
CodePartRenderer,
CodePartRendererAction,
CommandPartRenderer,
CopyToClipboardButtonAction,
ErrorPartRenderer,
HorizontalLayoutPartRenderer,
InsertCodeAtCursorButtonAction,
MarkdownPartRenderer,
ToolCallPartRenderer,
} from './chat-response-renderer';
import {
AIEditorManager,
AIEditorSelectionResolver,
GitHubSelectionResolver,
TextFragmentSelectionResolver,
TypeDocSymbolSelectionResolver,
} from './chat-response-renderer/ai-editor-manager';
import { createChatViewTreeWidget } from './chat-tree-view';
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
Expand All @@ -37,6 +49,7 @@ import { ChatViewWidget } from './chat-view-widget';
import { ChatViewWidgetToolbarContribution } from './chat-view-widget-toolbar-contribution';
import { EditorPreviewManager } from '@theia/editor-preview/lib/browser/editor-preview-manager';
import { QuestionPartRenderer } from './chat-response-renderer/question-part-renderer';
import '../../src/browser/style/index.css';

export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
bindViewContribution(bind, AIChatContribution);
Expand Down Expand Up @@ -72,6 +85,12 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
bind(serviceIdentifier).to(ChatViewMenuContribution).inSingletonScope()
);

bindContributionProvider(bind, CodePartRendererAction);
bind(CopyToClipboardButtonAction).toSelf().inSingletonScope();
bind(CodePartRendererAction).toService(CopyToClipboardButtonAction);
bind(InsertCodeAtCursorButtonAction).toSelf().inSingletonScope();
bind(CodePartRendererAction).toService(InsertCodeAtCursorButtonAction);

bind(AIEditorManager).toSelf().inSingletonScope();
rebind(EditorManager).toService(AIEditorManager);
rebind(EditorPreviewManager).toService(AIEditorManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import {
ChatResponseContent,
CodeChatResponseContent,
} from '@theia/ai-chat/lib/common';
import { UntitledResourceResolver, URI } from '@theia/core';
import { ContributionProvider, UntitledResourceResolver, URI } from '@theia/core';
import { ContextMenuRenderer, TreeNode } from '@theia/core/lib/browser';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { inject, injectable } from '@theia/core/shared/inversify';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import { ReactNode } from '@theia/core/shared/react';
import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
Expand All @@ -33,12 +32,25 @@ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
import { ChatViewTreeWidget, ResponseNode } from '../chat-tree-view/chat-view-tree-widget';
import { IMouseEvent } from '@theia/monaco-editor-core';

export const CodePartRendererAction = Symbol('CodePartRendererAction');
/**
* The CodePartRenderer offers to contribute arbitrary React nodes to the rendered code part.
* Technically anything can be rendered, however it is intended to be used for actions, like
* "Copy to Clipboard" or "Insert at Cursor".
*/
export interface CodePartRendererAction {
render(response: CodeChatResponseContent, parentNode: ResponseNode): ReactNode;
/**
* The priority determines the order in which the actions are rendered.
* The default priorities are 10 and 20.
*/
priority: number;
}

@injectable()
export class CodePartRenderer
implements ChatResponsePartRenderer<CodeChatResponseContent> {

@inject(ClipboardService)
protected readonly clipboardService: ClipboardService;
@inject(EditorManager)
protected readonly editorManager: EditorManager;
@inject(UntitledResourceResolver)
Expand All @@ -49,6 +61,8 @@ export class CodePartRenderer
protected readonly languageService: MonacoLanguages;
@inject(ContextMenuRenderer)
protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(ContributionProvider) @named(CodePartRendererAction)
protected readonly codePartRendererActions: ContributionProvider<CodePartRendererAction>;

canHandle(response: ChatResponseContent): number {
if (CodeChatResponseContent.is(response)) {
Expand All @@ -59,14 +73,14 @@ export class CodePartRenderer

render(response: CodeChatResponseContent, parentNode: ResponseNode): ReactNode {
const language = response.language ? this.languageService.getExtension(response.language) : undefined;

return (
<div className="theia-CodePartRenderer-root">
<div className="theia-CodePartRenderer-top">
<div className="theia-CodePartRenderer-left">{this.renderTitle(response)}</div>
<div className="theia-CodePartRenderer-right">
<CopyToClipboardButton code={response.code} clipboardService={this.clipboardService} />
<InsertCodeAtCursorButton code={response.code} editorManager={this.editorManager} />
<div className="theia-CodePartRenderer-right theia-CodePartRenderer-actions">
{this.codePartRendererActions.getContributions()
.sort((a, b) => a.priority - b.priority)
.map(action => action.render(response, parentNode))}
</div>
</div>
<div className="theia-CodePartRenderer-separator"></div>
Expand Down Expand Up @@ -123,6 +137,16 @@ export class CodePartRenderer
}
}

@injectable()
export class CopyToClipboardButtonAction implements CodePartRendererAction {
@inject(ClipboardService)
protected readonly clipboardService: ClipboardService;
priority = 10;
render(response: CodeChatResponseContent): ReactNode {
return <CopyToClipboardButton key='copyToClipBoard' code={response.code} clipboardService={this.clipboardService} />;
}
}

const CopyToClipboardButton = (props: { code: string, clipboardService: ClipboardService }) => {
const { code, clipboardService } = props;
const copyCodeToClipboard = React.useCallback(() => {
Expand All @@ -131,6 +155,16 @@ const CopyToClipboardButton = (props: { code: string, clipboardService: Clipboar
return <div className='button codicon codicon-copy' title='Copy' role='button' onClick={copyCodeToClipboard}></div>;
};

@injectable()
export class InsertCodeAtCursorButtonAction implements CodePartRendererAction {
@inject(EditorManager)
protected readonly editorManager: EditorManager;
priority = 20;
render(response: CodeChatResponseContent): ReactNode {
return <InsertCodeAtCursorButton key='insertCodeAtCursor' code={response.code} editorManager={this.editorManager} />;
}
}

const InsertCodeAtCursorButton = (props: { code: string, editorManager: EditorManager }) => {
const { code, editorManager } = props;
const insertCode = React.useCallback(() => {
Expand Down
43 changes: 43 additions & 0 deletions packages/ai-chat/src/common/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,59 @@ export interface ChatResponse {
asString(): string;
}

/**
* The ChatResponseModel wraps the actual ChatResponse with additional information like the current state, progress messages, a unique id etc.
*/
export interface ChatResponseModel {
/**
* Use this to be notified for any change in the response model
*/
readonly onDidChange: Event<void>;
/**
* The unique identifier of the response model
*/
readonly id: string;
/**
* The unique identifier of the request model this response is associated with
*/
readonly requestId: string;
/**
* In case there are progress messages, then they will be stored here
*/
readonly progressMessages: ChatProgressMessage[];
/**
* The actual response content
*/
readonly response: ChatResponse;
/**
* Indicates whether this response is complete. No further changes are expected if 'true'.
*/
readonly isComplete: boolean;
/**
* Indicates whether this response is canceled. No further changes are expected if 'true'.
*/
readonly isCanceled: boolean;
/**
* Some agents might need to wait for user input to continue. This flag indicates that.
*/
readonly isWaitingForInput: boolean;
/**
* Indicates whether an error occurred when processing the response. No further changes are expected if 'true'.
*/
readonly isError: boolean;
/**
* The agent who produced the response content, if there is one.
*/
readonly agentId?: string
/**
* An optional error object that caused the response to be in an error state.
*/
readonly errorObject?: Error;
/**
* Some functionality might want to store some data associated with the response.
* This can be used to store and retrieve such data.
*/
readonly data: { [key: string]: unknown };
}

/**********************
Expand Down Expand Up @@ -750,6 +791,8 @@ class ChatResponseModelImpl implements ChatResponseModel {
protected readonly _onDidChangeEmitter = new Emitter<void>();
onDidChange: Event<void> = this._onDidChangeEmitter.event;

data = {};

protected _id: string;
protected _requestId: string;
protected _progressMessages: ChatProgressMessage[];
Expand Down
10 changes: 10 additions & 0 deletions packages/ai-scanoss/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};
34 changes: 34 additions & 0 deletions packages/ai-scanoss/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div align='center'>

<br />

<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />

<h2>ECLIPSE THEIA - SCAN OSS AI EXTENSION</h2>

<hr />

</div>

## Description

Integrates SCANOSS content scanning into the Chat View.
Whenever a code listing is rendered, a scan action is offered to the user.

Via the preferences the user can switch between manual and automatic scanning.

## Additional Information

- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)
- [SCAN OSS Website](https://www.scanoss.com/)

## License

- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)

## Trademark

"Theia" is a trademark of the Eclipse Foundation
<https://www.eclipse.org/theia>
Loading

0 comments on commit d8c2c83

Please sign in to comment.